1 | "use strict"
|
2 |
|
3 | const uuid = require("uuid/v4")
|
4 | const { ErrorReporting } = require("@google-cloud/error-reporting")
|
5 |
|
6 | const reqIdHeader = "X-Request-Id"
|
7 |
|
8 | module.exports = function createCtxLogger(logger, options={}, fetchUser=null) {
|
9 | const gcloudErrorReporting = new ErrorReporting(gcloudOptions(options))
|
10 | const errorHandler = initErrorHandler(gcloudErrorReporting, fetchUser || (async () => {}))
|
11 | const logMiddleware = initLogMiddleware(logger, errorHandler)
|
12 |
|
13 | return logMiddleware
|
14 | }
|
15 |
|
16 | function initLogMiddleware(logger, errorHandler) {
|
17 | return async (ctx, next) => {
|
18 | let reqId = ctx.reqId = ctx.request.get(reqIdHeader) || uuid()
|
19 | let log = logger.child(reqId)
|
20 | let requestTimer = log.timer()
|
21 |
|
22 | ctx.log = log
|
23 | ctx.requestTimer = requestTimer
|
24 | ctx.errorReporting = errorHandler.gcloudErrorReporting
|
25 |
|
26 | try {
|
27 | await next()
|
28 | } catch (e) {
|
29 | await errorHandler(ctx, e)
|
30 | } finally {
|
31 | ctx.response.set("X-Server-Request-Id", ctx.reqId)
|
32 | ctx.requestTimer.end("%s %s > %d", ctx.request.method, ctx.request.url, ctx.response.status)
|
33 | }
|
34 | }
|
35 | }
|
36 |
|
37 | function initErrorHandler(gcloudErrorReporting, fetchUser) {
|
38 | const handler = async (ctx, e) => {
|
39 | if (e instanceof Error === false) {
|
40 | e = new Error(e)
|
41 | }
|
42 |
|
43 | if (!e.status) e.status = 500
|
44 | if (!e.jsonBody) e.jsonBody = {}
|
45 |
|
46 | ctx.type = "json"
|
47 | ctx.status = e.status
|
48 |
|
49 | ctx.log.debug("%s %s < params: %O, body: %O", ctx.request.method, ctx.request.url, ctx.params, ctx.request.body)
|
50 |
|
51 | if (!e.expose) {
|
52 | let user = await fetchUser(ctx)
|
53 |
|
54 | let requestInformation = {
|
55 | method: ctx.method,
|
56 | url: ctx.url,
|
57 | userAgent: ctx.request.get("user-agent"),
|
58 | referrer: ctx.request.get("referrer"),
|
59 | statusCode: ctx.status,
|
60 | remoteAddress: ctx.ip,
|
61 | requestId: ctx.request.get("x-request-id") }
|
62 |
|
63 | let errorMessage = gcloudErrorReporting.event()
|
64 | .setMessage(`${e.stack}\n\nreqId:${ctx.reqId}`)
|
65 | .consumeRequestInformation(requestInformation)
|
66 |
|
67 | if (user) errorMessage.setUser(user)
|
68 |
|
69 | gcloudErrorReporting.report(errorMessage, (err) => {
|
70 | if (err) return ctx.log.trace(`Error ${err} while reporting to google cloud`)
|
71 | ctx.log.trace("Error reported to GCloud")
|
72 | })
|
73 |
|
74 | ctx.log.error(e)
|
75 | }
|
76 |
|
77 | e.jsonBody.status = e.status
|
78 | e.jsonBody.message = e.expose ? e.message : "Error"
|
79 |
|
80 | ctx.body = {
|
81 | error: e.jsonBody,
|
82 | reqId: ctx.reqId
|
83 | }
|
84 | }
|
85 |
|
86 | handler.gcloudErrorReporting = gcloudErrorReporting
|
87 | return handler
|
88 | }
|
89 |
|
90 | function gcloudOptions(options) {
|
91 | let pckg = require("package.json")
|
92 | let serviceContext = options.serviceContext || {
|
93 | service: pckg.name,
|
94 | version: pckg.version }
|
95 |
|
96 | let gcloudOptions = {
|
97 | serviceContext,
|
98 | projectId: options.projectId,
|
99 | reportUnhandledRejections: true,
|
100 | logLevel: 2 || options.logLevel,
|
101 | reportMode: options.reportMode || (options.ignoreEnvironmentCheck ? "always" : "production")
|
102 | }
|
103 | if (options.credentials) {
|
104 | gcloudOptions.credentials = options.credentials
|
105 | }
|
106 |
|
107 | return gcloudOptions
|
108 | }
|