gcp-manager.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. const bunyan = require('bunyan')
  2. /**
  3. * When we copy log entry fields, omit some bunyan core fields that are not
  4. * interesting, that have a special meaning in GCP, or that we will process
  5. * separately.
  6. */
  7. const ENTRY_FIELDS_TO_OMIT = [
  8. 'level',
  9. 'name',
  10. 'hostname',
  11. 'v',
  12. 'pid',
  13. 'msg',
  14. 'err',
  15. 'req',
  16. 'res',
  17. ]
  18. /**
  19. * Convert a bunyan log entry to a format that GCP understands
  20. */
  21. function convertLogEntry(entry) {
  22. const gcpEntry = omit(entry, ENTRY_FIELDS_TO_OMIT)
  23. // Error information. In GCP, the stack trace goes in the message property.
  24. // This enables the error reporting feature.
  25. if (entry.err) {
  26. if (entry.err.info) {
  27. Object.assign(gcpEntry, entry.err.info)
  28. }
  29. if (entry.err.code) {
  30. gcpEntry.code = entry.err.code
  31. }
  32. if (entry.err.signal) {
  33. gcpEntry.signal = entry.err.signal
  34. }
  35. if (entry.err.stack !== '(no stack)') {
  36. gcpEntry.message = entry.err.stack
  37. } else if (entry.err.message) {
  38. gcpEntry.message = entry.err.message
  39. }
  40. if (entry.name) {
  41. gcpEntry.serviceContext = { service: entry.name }
  42. }
  43. }
  44. // Log message
  45. if (entry.msg) {
  46. if (gcpEntry.message) {
  47. // A message has already been extracted from the error. Keep the extra
  48. // message in the msg property.
  49. gcpEntry.msg = entry.msg
  50. } else {
  51. gcpEntry.message = entry.msg
  52. }
  53. }
  54. // Severity
  55. if (entry.level) {
  56. gcpEntry.severity = bunyan.nameFromLevel[entry.level]
  57. }
  58. // HTTP request information
  59. if (entry.req || entry.res || entry.responseTimeMs) {
  60. const httpRequest = {}
  61. if (entry.req) {
  62. const req = entry.req
  63. httpRequest.requestMethod = req.method
  64. httpRequest.requestUrl = req.url
  65. httpRequest.remoteIp = req.remoteAddress
  66. if (req.headers) {
  67. if (req.headers['content-length']) {
  68. httpRequest.requestSize = parseInt(req.headers['content-length'], 10)
  69. }
  70. httpRequest.userAgent = req.headers['user-agent']
  71. httpRequest.referer = req.headers.referer
  72. }
  73. }
  74. if (entry.res) {
  75. const res = entry.res
  76. httpRequest.status = res.statusCode
  77. if (res.headers && res.headers['content-length']) {
  78. if (res.headers['content-length']) {
  79. httpRequest.responseSize = parseInt(res.headers['content-length'], 10)
  80. }
  81. }
  82. }
  83. if (entry.responseTimeMs) {
  84. const responseTimeSec = entry.responseTimeMs / 1000
  85. httpRequest.latency = `${responseTimeSec}s`
  86. }
  87. gcpEntry.httpRequest = httpRequest
  88. }
  89. // Labels are indexed in GCP. We copy the project, doc and user ids to labels to enable fast filtering
  90. const projectId =
  91. gcpEntry.projectId ||
  92. gcpEntry.project_id ||
  93. (entry.req && entry.req.projectId)
  94. const userId =
  95. gcpEntry.userId || gcpEntry.user_id || (entry.req && entry.req.userId)
  96. const docId =
  97. gcpEntry.docId || gcpEntry.doc_id || (entry.req && entry.req.docId)
  98. if (projectId || userId || docId) {
  99. const labels = {}
  100. if (projectId) {
  101. labels.projectId = projectId
  102. }
  103. if (userId) {
  104. labels.userId = userId
  105. }
  106. if (docId) {
  107. labels.docId = docId
  108. }
  109. gcpEntry['logging.googleapis.com/labels'] = labels
  110. }
  111. return gcpEntry
  112. }
  113. function omit(obj, excludedFields) {
  114. return Object.fromEntries(
  115. Object.entries(obj).filter(([key]) => !excludedFields.includes(key))
  116. )
  117. }
  118. module.exports = { convertLogEntry }