gcp-manager.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  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) {
  36. gcpEntry.message = entry.err.stack
  37. }
  38. if (entry.name) {
  39. gcpEntry.serviceContext = { service: entry.name }
  40. }
  41. } else {
  42. gcpEntry.message = entry.msg
  43. }
  44. // Severity
  45. if (entry.level) {
  46. gcpEntry.severity = bunyan.nameFromLevel[entry.level]
  47. }
  48. // HTTP request information
  49. if (entry.req || entry.res || entry.responseTimeMs) {
  50. const httpRequest = {}
  51. if (entry.req) {
  52. const req = entry.req
  53. httpRequest.requestMethod = req.method
  54. httpRequest.requestUrl = req.url
  55. httpRequest.remoteIp = req.remoteAddress
  56. if (req.headers) {
  57. if (req.headers['content-length']) {
  58. httpRequest.requestSize = parseInt(req.headers['content-length'], 10)
  59. }
  60. httpRequest.userAgent = req.headers['user-agent']
  61. httpRequest.referer = req.headers.referer
  62. }
  63. }
  64. if (entry.res) {
  65. const res = entry.res
  66. httpRequest.status = res.statusCode
  67. if (res.headers && res.headers['content-length']) {
  68. if (res.headers['content-length']) {
  69. httpRequest.responseSize = parseInt(res.headers['content-length'], 10)
  70. }
  71. }
  72. }
  73. if (entry.responseTimeMs) {
  74. const responseTimeSec = entry.responseTimeMs / 1000
  75. httpRequest.latency = `${responseTimeSec}s`
  76. }
  77. gcpEntry.httpRequest = httpRequest
  78. }
  79. // Labels are indexed in GCP. We copy the project, doc and user ids to labels to enable fast filtering
  80. const projectId = gcpEntry.projectId || gcpEntry.project_id
  81. const userId = gcpEntry.userId || gcpEntry.user_id
  82. const docId = gcpEntry.docId || gcpEntry.doc_id
  83. if (projectId || userId || docId) {
  84. const labels = {}
  85. if (projectId) {
  86. labels.projectId = projectId
  87. }
  88. if (userId) {
  89. labels.userId = userId
  90. }
  91. if (docId) {
  92. labels.docId = docId
  93. }
  94. gcpEntry['logging.googleapis.com/labels'] = labels
  95. }
  96. return gcpEntry
  97. }
  98. function omit(obj, excludedFields) {
  99. return Object.fromEntries(
  100. Object.entries(obj).filter(([key]) => !excludedFields.includes(key))
  101. )
  102. }
  103. module.exports = { convertLogEntry }