logging-manager.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. const Stream = require('node:stream')
  2. const bunyan = require('bunyan')
  3. const GCPManager = require('./gcp-manager')
  4. const Serializers = require('./serializers')
  5. const {
  6. FileLogLevelChecker,
  7. GCEMetadataLogLevelChecker,
  8. } = require('./log-level-checker')
  9. const LoggingManager = {
  10. /**
  11. * @param {string} name - The name of the logger
  12. */
  13. initialize(name) {
  14. this.isProduction =
  15. (process.env.NODE_ENV || '').toLowerCase() === 'production'
  16. this.defaultLevel =
  17. process.env.LOG_LEVEL || (this.isProduction ? 'info' : 'debug')
  18. this.loggerName = name
  19. this.logger = bunyan.createLogger({
  20. name,
  21. serializers: {
  22. err: Serializers.err,
  23. error: Serializers.err,
  24. req: Serializers.req,
  25. res: Serializers.res,
  26. },
  27. streams: [this._getOutputStreamConfig()],
  28. })
  29. this._setupRingBuffer()
  30. this._setupLogLevelChecker()
  31. return this
  32. },
  33. /**
  34. * @param {Record<string, any>|string} attributes - Attributes to log (nice serialization for err, req, res)
  35. * @param {string} [message] - Optional message
  36. * @signature `debug(attributes, message)`
  37. * @signature `debug(message)`
  38. */
  39. debug(attributes, message, ...args) {
  40. return this.logger.debug(attributes, message, ...args)
  41. },
  42. /**
  43. * @param {Record<string, any>|string} attributes - Attributes to log (nice serialization for err, req, res)
  44. * @param {string} [message]
  45. * @signature `info(attributes, message)`
  46. * @signature `info(message)`
  47. */
  48. info(attributes, message, ...args) {
  49. return this.logger.info(attributes, message, ...args)
  50. },
  51. /**
  52. * @param {Record<string, any>} attributes - Attributes to log (nice serialization for err, req, res)
  53. * @param {string} [message]
  54. */
  55. error(attributes, message, ...args) {
  56. if (this.ringBuffer !== null && Array.isArray(this.ringBuffer.records)) {
  57. attributes.logBuffer = this.ringBuffer.records.filter(function (record) {
  58. return record.level !== 50
  59. })
  60. }
  61. this.logger.error(attributes, message, ...Array.from(args))
  62. },
  63. /**
  64. * Alias to the error method.
  65. * @param {Record<string, any>} attributes - Attributes to log (nice serialization for err, req, res)
  66. * @param {string} [message]
  67. */
  68. err(attributes, message, ...args) {
  69. return this.error(attributes, message, ...args)
  70. },
  71. /**
  72. * @param {Record<string, any>|string} attributes - Attributes to log (nice serialization for err, req, res)
  73. * @param {string} [message]
  74. * @signature `warn(attributes, message)`
  75. * @signature `warn(message)`
  76. */
  77. warn(attributes, message, ...args) {
  78. return this.logger.warn(attributes, message, ...args)
  79. },
  80. /**
  81. * @param {Record<string, any>} attributes - Attributes to log (nice serialization for err, req, res)
  82. * @param {string} [message]
  83. */
  84. fatal(attributes, message) {
  85. this.logger.fatal(attributes, message)
  86. },
  87. _getOutputStreamConfig() {
  88. switch (process.env.LOGGING_FORMAT) {
  89. case 'gke': {
  90. const stream = new Stream.Writable({
  91. objectMode: true,
  92. write(entry, encoding, callback) {
  93. const gcpEntry = GCPManager.convertLogEntry(entry)
  94. // eslint-disable-next-line no-console
  95. console.log(JSON.stringify(gcpEntry, bunyan.safeCycles()))
  96. setImmediate(callback)
  97. },
  98. })
  99. return { level: this.defaultLevel, type: 'raw', stream }
  100. }
  101. case 'gce': {
  102. const { LoggingBunyan } = require('@google-cloud/logging-bunyan')
  103. return new LoggingBunyan({
  104. logName: this.loggerName,
  105. serviceContext: { service: this.loggerName },
  106. }).stream(this.defaultLevel)
  107. }
  108. default: {
  109. return { level: this.defaultLevel, stream: process.stdout }
  110. }
  111. }
  112. },
  113. _setupRingBuffer() {
  114. this.ringBufferSize = parseInt(process.env.LOG_RING_BUFFER_SIZE) || 0
  115. if (this.ringBufferSize > 0) {
  116. this.ringBuffer = new bunyan.RingBuffer({ limit: this.ringBufferSize })
  117. this.logger.addStream({
  118. level: 'trace',
  119. type: 'raw',
  120. stream: this.ringBuffer,
  121. })
  122. } else {
  123. this.ringBuffer = null
  124. }
  125. },
  126. _setupLogLevelChecker() {
  127. const logLevelSource = (
  128. process.env.LOG_LEVEL_SOURCE || 'file'
  129. ).toLowerCase()
  130. if (this.logLevelChecker) {
  131. this.logLevelChecker.stop()
  132. this.logLevelChecker = null
  133. }
  134. if (this.isProduction) {
  135. switch (logLevelSource) {
  136. case 'file':
  137. this.logLevelChecker = new FileLogLevelChecker(
  138. this.logger,
  139. this.defaultLevel
  140. )
  141. break
  142. case 'gce_metadata':
  143. this.logLevelChecker = new GCEMetadataLogLevelChecker(
  144. this.logger,
  145. this.defaultLevel
  146. )
  147. break
  148. case 'none':
  149. break
  150. default:
  151. // eslint-disable-next-line no-console
  152. console.log(`Unrecognised log level source: ${logLevelSource}`)
  153. }
  154. if (this.logLevelChecker) {
  155. this.logLevelChecker.start()
  156. }
  157. }
  158. },
  159. }
  160. LoggingManager.initialize('default')
  161. function handleWarning(err) {
  162. LoggingManager.warn({ err }, 'Warning details')
  163. }
  164. process.on('warning', handleWarning)
  165. LoggingManager.removeWarningHandler = () => {
  166. process.off('warning', handleWarning)
  167. }
  168. module.exports = LoggingManager