logger.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import winston, { transport } from 'winston'
  2. import ecsformat from '@elastic/ecs-winston-format'
  3. import expressWinston from 'express-winston'
  4. import { Handler, ErrorRequestHandler } from 'express'
  5. import { ElasticsearchTransport } from 'winston-elasticsearch'
  6. /**
  7. * Possible log levels.
  8. */
  9. const levels = {
  10. error: 0,
  11. warn: 1,
  12. info: 2,
  13. http: 3,
  14. debug: 4,
  15. }
  16. /**
  17. * Creates basic Winston logger. Console output redirected to the stderr.
  18. *
  19. * @returns Winston logger options
  20. *
  21. */
  22. function createDefaultLoggerOptions(): winston.LoggerOptions {
  23. const level = () => {
  24. const env = process.env.NODE_ENV || 'development'
  25. const isDevelopment = env === 'development'
  26. return isDevelopment ? 'debug' : 'warn'
  27. }
  28. // Colors
  29. const colors = {
  30. error: 'red',
  31. warn: 'yellow',
  32. info: 'green',
  33. http: 'magenta',
  34. debug: 'white',
  35. }
  36. winston.addColors(colors)
  37. // Formats
  38. const format = winston.format.combine(
  39. winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
  40. winston.format.colorize(),
  41. winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`)
  42. )
  43. // Redirect all logs to the stderr
  44. const transports = [new winston.transports.Console({ stderrLevels: Object.keys(levels), format })]
  45. return {
  46. level: level(),
  47. levels,
  48. transports,
  49. }
  50. }
  51. /**
  52. * Creates basic Winston logger.
  53. *
  54. * @returns Winston logger
  55. *
  56. */
  57. function createDefaultLogger(): winston.Logger {
  58. const defaultOptions = createDefaultLoggerOptions()
  59. return winston.createLogger(defaultOptions)
  60. }
  61. // Default global logger variable
  62. let InnerLogger = createDefaultLogger()
  63. // Enables changing the underlying logger which is default import in other modules.
  64. const proxy = new Proxy(InnerLogger, {
  65. get(target: winston.Logger, propKey: symbol) {
  66. const method = Reflect.get(target, propKey)
  67. return (...args: unknown[]) => {
  68. return method.apply(InnerLogger, args)
  69. }
  70. },
  71. })
  72. export default proxy
  73. /**
  74. * Creates Express-Winston logger handler.
  75. * @param logSource - source tag for log entries.
  76. * @param elasticSearchEndpoint - elastic search engine endpoint (optional).
  77. * @returns Express-Winston logger handler
  78. *
  79. */
  80. export function httpLogger(logSource: string, elasticSearchEndpoint?: string): Handler {
  81. // ElasticSearch server date format.
  82. const elasticDateFormat = 'YYYY-MM-DDTHH:mm:ss'
  83. const transports: winston.transport[] = [
  84. new winston.transports.Console({
  85. format: winston.format.combine(winston.format.timestamp({ format: elasticDateFormat }), winston.format.json()),
  86. }),
  87. ]
  88. if (elasticSearchEndpoint) {
  89. const esTransport = createElasticTransport(logSource, elasticSearchEndpoint)
  90. transports.push(esTransport)
  91. }
  92. const opts: expressWinston.LoggerOptions = {
  93. transports,
  94. meta: true,
  95. msg: 'HTTP {{req.method}} {{req.url}}',
  96. expressFormat: true,
  97. colorize: false,
  98. }
  99. return expressWinston.logger(opts)
  100. }
  101. /**
  102. * Creates Express-Winston error logger.
  103. *
  104. * @returns Express-Winston error logger
  105. *
  106. */
  107. export function errorLogger(): ErrorRequestHandler {
  108. return expressWinston.errorLogger({
  109. transports: [new winston.transports.Console()],
  110. format: winston.format.combine(winston.format.json()),
  111. })
  112. }
  113. /**
  114. * Creates clean Console Winston logger for standard output.
  115. *
  116. * @returns Winston logger
  117. *
  118. */
  119. export function createStdConsoleLogger(): winston.Logger {
  120. const format = winston.format.printf((info) => `${info.message}`)
  121. const transports = [new winston.transports.Console()]
  122. return winston.createLogger({
  123. levels,
  124. format,
  125. transports,
  126. })
  127. }
  128. /**
  129. * Creates Winston logger with Elastic search.
  130. * @param logSource - source tag for log entries.
  131. * @param elasticSearchEndpoint - elastic search engine endpoint.
  132. * @returns Winston logger
  133. *
  134. */
  135. function createElasticLogger(logSource: string, elasticSearchEndpoint: string): winston.Logger {
  136. const loggerOptions = createDefaultLoggerOptions()
  137. // Transports
  138. let transports: transport[] = []
  139. if (loggerOptions.transports !== undefined) {
  140. transports = Array.isArray(loggerOptions.transports) ? loggerOptions.transports : [loggerOptions.transports]
  141. }
  142. const esTransport = createElasticTransport(logSource, elasticSearchEndpoint)
  143. transports.push(esTransport)
  144. // Logger
  145. const logger = winston.createLogger(loggerOptions)
  146. // Handle logger error.
  147. logger.on('error', (err) => {
  148. // Allow console for logging errors of the logger.
  149. /* eslint-disable no-console */
  150. console.error('Error in logger caught:', err)
  151. })
  152. return logger
  153. }
  154. /**
  155. * Updates the default system logger with elastic search capabilities.
  156. *
  157. * @param logSource - source tag for log entries.
  158. * @param elasticSearchEndpoint - elastic search engine endpoint.
  159. */
  160. export function initElasticLogger(logSource: string, elasticSearchEndpoint: string): void {
  161. InnerLogger = createElasticLogger(logSource, elasticSearchEndpoint)
  162. }
  163. /**
  164. * Creates winston logger transport for the elastic search engine.
  165. *
  166. * @param logSource - source tag for log entries.
  167. * @param elasticSearchEndpoint - elastic search engine endpoint.
  168. * @returns elastic search winston transport
  169. */
  170. function createElasticTransport(logSource: string, elasticSearchEndpoint: string): winston.transport {
  171. const possibleLevels = ['warn', 'error', 'debug', 'info']
  172. let elasticLogLevel = process.env.ELASTIC_LOG_LEVEL ?? ''
  173. elasticLogLevel = elasticLogLevel.toLowerCase().trim()
  174. if (!possibleLevels.includes(elasticLogLevel)) {
  175. elasticLogLevel = 'debug' // default
  176. }
  177. const esTransportOpts = {
  178. level: elasticLogLevel,
  179. clientOpts: { node: elasticSearchEndpoint, maxRetries: 5 },
  180. index: 'storage-node',
  181. format: ecsformat(),
  182. source: logSource,
  183. }
  184. return new ElasticsearchTransport(esTransportOpts)
  185. }