logger.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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. import 'winston-daily-rotate-file'
  7. import path from 'path'
  8. /**
  9. * Possible log levels.
  10. */
  11. const levels = {
  12. error: 0,
  13. warn: 1,
  14. info: 2,
  15. http: 3,
  16. debug: 4,
  17. }
  18. /**
  19. * Creates basic Winston logger. Console output redirected to the stderr.
  20. *
  21. * @returns Winston logger options
  22. *
  23. */
  24. function createDefaultLoggerOptions(): winston.LoggerOptions {
  25. const level = () => {
  26. const env = process.env.NODE_ENV || 'development'
  27. const isDevelopment = env === 'development'
  28. return isDevelopment ? 'debug' : 'warn'
  29. }
  30. // Colors
  31. const colors = {
  32. error: 'red',
  33. warn: 'yellow',
  34. info: 'green',
  35. http: 'magenta',
  36. debug: 'white',
  37. }
  38. winston.addColors(colors)
  39. // Formats
  40. const format = winston.format.combine(
  41. winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
  42. winston.format.colorize(),
  43. winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`)
  44. )
  45. // Redirect all logs to the stderr
  46. const transports = [new winston.transports.Console({ stderrLevels: Object.keys(levels), format })]
  47. return {
  48. level: level(),
  49. levels,
  50. transports,
  51. }
  52. }
  53. /**
  54. * Creates basic Winston logger.
  55. *
  56. * @returns Winston logger
  57. *
  58. */
  59. function createDefaultLogger(): winston.Logger {
  60. const defaultOptions = createDefaultLoggerOptions()
  61. return winston.createLogger(defaultOptions)
  62. }
  63. // Default global logger variable
  64. let InnerLogger = createDefaultLogger()
  65. // Enables changing the underlying logger which is default import in other modules.
  66. const proxy = new Proxy(InnerLogger, {
  67. get(target: winston.Logger, propKey: symbol) {
  68. const method = Reflect.get(target, propKey)
  69. return (...args: unknown[]) => {
  70. return method.apply(InnerLogger, args)
  71. }
  72. },
  73. })
  74. export default proxy
  75. /**
  76. * Creates Express-Winston default logger options.
  77. *
  78. */
  79. export function createExpressDefaultLoggerOptions(): expressWinston.LoggerOptions {
  80. return {
  81. winstonInstance: proxy,
  82. level: 'http',
  83. }
  84. }
  85. /**
  86. * Creates Express-Winston error logger options.
  87. *
  88. */
  89. export function createExpressErrorLoggerOptions(): expressWinston.LoggerOptions {
  90. return {
  91. winstonInstance: proxy,
  92. level: 'error',
  93. msg: '{{req.method}} {{req.path}}: Error {{res.statusCode}}: {{err.message}}',
  94. }
  95. }
  96. /**
  97. * Creates Express-Winston error logger.
  98. *
  99. * @param options - express winston logger options.
  100. * @returns Express-Winston error logger
  101. *
  102. */
  103. export function errorLogger(options: expressWinston.LoggerOptions): ErrorRequestHandler {
  104. return expressWinston.errorLogger(options)
  105. }
  106. /**
  107. * Creates Express-Winston logger handler.
  108. *
  109. * @param options - express winston logger options.
  110. * @returns Express-Winston logger handler
  111. *
  112. */
  113. export function httpLogger(options: expressWinston.LoggerOptions): Handler {
  114. return expressWinston.logger(options)
  115. }
  116. /**
  117. * Creates Winston logger with ElasticSearch and File transports.
  118. * @param customOptions - logger options
  119. * @returns Winston logger
  120. *
  121. */
  122. function createCustomLogger(customOptions: LogConfig): winston.Logger {
  123. const loggerOptions = createDefaultLoggerOptions()
  124. // Transports
  125. let transports: transport[] = []
  126. if (loggerOptions.transports !== undefined) {
  127. transports = Array.isArray(loggerOptions.transports) ? loggerOptions.transports : [loggerOptions.transports]
  128. }
  129. if (customOptions.elasticSearchEndpoint) {
  130. transports.push(createElasticTransport(customOptions.elasticSearchlogSource, customOptions.elasticSearchEndpoint))
  131. }
  132. if (customOptions.filePath) {
  133. transports.push(
  134. createFileTransport(
  135. customOptions.filePath,
  136. customOptions.fileFrequency,
  137. customOptions.maxFileNumber,
  138. customOptions.maxFileSize
  139. )
  140. )
  141. }
  142. // Logger
  143. const logger = winston.createLogger(loggerOptions)
  144. // Handle logger error.
  145. logger.on('error', (err) => {
  146. // Allow console for logging errors of the logger.
  147. /* eslint-disable no-console */
  148. console.error('Error in logger caught:', err)
  149. })
  150. return logger
  151. }
  152. /**
  153. * Updates the default system logger with elastic search capabilities.
  154. *
  155. * @param customOptions - logger options
  156. */
  157. export function initNewLogger(options: LogConfig): void {
  158. InnerLogger = createCustomLogger(options)
  159. }
  160. /**
  161. * Creates winston logger transport for the elastic search engine.
  162. *
  163. * @param logSource - source tag for log entries.
  164. * @param elasticSearchEndpoint - elastic search engine endpoint.
  165. * @returns elastic search winston transport
  166. */
  167. function createElasticTransport(logSource: string, elasticSearchEndpoint: string): winston.transport {
  168. const possibleLevels = ['warn', 'error', 'debug', 'info']
  169. let elasticLogLevel = process.env.ELASTIC_LOG_LEVEL ?? ''
  170. elasticLogLevel = elasticLogLevel.toLowerCase().trim()
  171. if (!possibleLevels.includes(elasticLogLevel)) {
  172. elasticLogLevel = 'debug' // default
  173. }
  174. const esTransportOpts = {
  175. level: elasticLogLevel,
  176. clientOpts: { node: elasticSearchEndpoint, maxRetries: 5 },
  177. index: 'storage-node',
  178. format: ecsformat(),
  179. source: logSource,
  180. retryLimit: 10,
  181. }
  182. return new ElasticsearchTransport(esTransportOpts)
  183. }
  184. /**
  185. * Creates winston logger file transport.
  186. *
  187. * @param fileName - log file path
  188. * @param fileFrequency - file frequence (daily,montly, etc.)
  189. * @param maxFiles - maximum number of the log files
  190. * @param maxSize - maximum log file size
  191. * @returns winston file transport
  192. */
  193. function createFileTransport(
  194. filepath: string,
  195. fileFrequency: Frequency,
  196. maxFiles: number,
  197. maxSize: number
  198. ): winston.transport {
  199. const options = {
  200. filename: path.join(filepath, 'colossus-%DATE%.log'),
  201. datePattern: DatePatternByFrequency[fileFrequency || 'daily'],
  202. maxSize,
  203. maxFiles,
  204. level: 'debug',
  205. format: ecsformat(),
  206. }
  207. return new winston.transports.DailyRotateFile(options)
  208. }
  209. export const DatePatternByFrequency = {
  210. yearly: 'YYYY',
  211. monthly: 'YYYY-MM',
  212. daily: 'YYYY-MM-DD',
  213. hourly: 'YYYY-MM-DD-HH',
  214. }
  215. /** File frequency for */
  216. export type Frequency = keyof typeof DatePatternByFrequency
  217. /**
  218. * Configuration for the ElasticSearch and File loggers
  219. */
  220. export type LogConfig = {
  221. /** Path to log files */
  222. filePath?: string
  223. /** Maximum log file size */
  224. maxFileSize: number
  225. /** Maximum number of the log files */
  226. maxFileNumber: number
  227. /** Log files update frequency (yearly, monthly, daily, hourly) */
  228. fileFrequency: Frequency
  229. /** Source tag for log entries. */
  230. elasticSearchlogSource: string
  231. /** Elastic search engine endpoint */
  232. elasticSearchEndpoint?: string
  233. }