-
Notifications
You must be signed in to change notification settings - Fork 242
Expand file tree
/
Copy pathlogging.js
More file actions
182 lines (164 loc) · 5.52 KB
/
logging.js
File metadata and controls
182 lines (164 loc) · 5.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
* Copyright Elasticsearch B.V. and other contributors where applicable.
* Licensed under the BSD 2-Clause License; you may not use this file except in
* compliance with the BSD 2-Clause License.
*/
'use strict'
// Internal logging for the Elastic APM Node.js Agent.
//
// Promised interface:
// - The amount of logging can be controlled via the `logLevel` config var,
// and via the `log_level` central config var.
// - A custom logger can be provided via the `logging` config var.
//
// Nothing else about this package's logging (e.g. structure or the particular
// message text) is promised/stable.
//
// Per https://github.com/elastic/apm/blob/main/specs/agents/logging.md
// the valid log levels are:
// - trace
// - debug
// - info (default)
// - warning
// - error
// - critical
// - off
//
// Before this spec, the supported levels were:
// - trace
// - debug
// - info (default)
// - warn - both "warn" and "warning" will be supported for backward compat
// - error
// - fatal - mapped to "critical" for backward compat
var ecsFormat = require('@elastic/ecs-pino-format')
var pino = require('pino')
var semver = require('semver')
const DEFAULT_LOG_LEVEL = 'info'
// Used to mark loggers created here, for use by `isLoggerCustom()`.
const LOGGER_IS_OURS_SYM = Symbol('ElasticAPMLoggerIsOurs')
const PINO_LEVEL_FROM_LEVEL_NAME = {
trace: 'trace',
debug: 'debug',
info: 'info',
warning: 'warn',
warn: 'warn', // Supported for backwards compat
error: 'error',
critical: 'fatal',
fatal: 'fatal', // Supported for backwards compat
off: 'silent'
}
// SafePinoDestWrapper provides a pino destination that will pass logging calls
// to a given `customLogger`. The custom logger must have the following API:
//
// - `.trace(string)`
// - `.debug(string)`
// - `.info(string)`
// - `.warn(string)`
// - `.error(string)`
// - `.fatal(string)`
//
// The limitation of this wrapping is that structured data fields are *not*
// passed on to the custom logger. I.e. this is a fallback mechanism.
class SafePinoDestWrapper {
constructor (customLogger) {
this.customLogger = customLogger
this.logFnNameFromLastLevel = pino.levels.labels
this[Symbol.for('pino.metadata')] = true
}
write (s) {
const { lastMsg, lastLevel } = this
const logFnName = this.logFnNameFromLastLevel[lastLevel]
this.customLogger[logFnName](lastMsg)
}
}
// Create a pino logger for the agent.
//
// By default `createLogger()` will return a pino logger that logs to stdout
// in ecs-logging format, set to the "info" level.
//
// @param {String} levelName - Optional, default "info". It is meant to be one
// of the log levels specified in the top of file comment. For backward
// compatibility it falls back to "trace".
// @param {Object} customLogger - Optional. A custom logger object to which
// log messages will be passed. It must provide
// trace/debug/info/warn/error/fatal methods that take a string argument.
//
// Internally the agent uses structured logging using the pino API
// (https://getpino.io/#/docs/api?id=logger). However, with a custom logger,
// log record fields other than the *message* are dropped, to avoid issues
// with incompatible logger APIs.
//
// As a special case, if the provided logger is a *pino logger instance*,
// then it will be used directly.
function createLogger (levelName, customLogger) {
let dest
const serializers = {
err: pino.stdSerializers.err,
req: pino.stdSerializers.req,
res: pino.stdSerializers.res
}
if (!levelName) {
levelName = DEFAULT_LOG_LEVEL
}
let pinoLevel = PINO_LEVEL_FROM_LEVEL_NAME[levelName]
if (!pinoLevel) {
// For backwards compat, support an earlier bug where an unknown log level
// was accepted.
// TODO: Consider being more strict on this for v4.0.0.
pinoLevel = 'trace'
}
if (customLogger) {
// Is this a pino logger? If so, it supports the API the agent requires and
// can be used directly. We must add our custom serializers.
if (Symbol.for('pino.serializers') in customLogger) {
// Pino added `options` second arg to `logger.child` in 6.12.0.
if (semver.gte(customLogger.version, '6.12.0')) {
return customLogger.child({}, { serializers })
}
return customLogger.child({
serializers
})
}
// Otherwise, we fallback to wrapping the provided logger such that the
// agent can use the pino logger API without breaking. The limitation is
// that only the log *message* is logged. Extra structured fields are
// dropped.
dest = new SafePinoDestWrapper(customLogger)
// Our wrapping logger level should be 'trace', to pass through all
// messages to the wrapped logger.
pinoLevel = 'trace'
} else {
// Log to stdout, the same default as pino itself.
dest = pino.destination(1)
}
const logger = pino({
name: 'elastic-apm-node',
base: {}, // Don't want pid and hostname fields.
level: pinoLevel,
serializers,
...ecsFormat({ apmIntegration: false })
}, dest)
if (!customLogger) {
logger[LOGGER_IS_OURS_SYM] = true // used for isLoggerCustom()
}
return logger
}
function isLoggerCustom (logger) {
return !logger[LOGGER_IS_OURS_SYM]
}
// Adjust the level on the given logger.
function setLogLevel (logger, levelName) {
const pinoLevel = PINO_LEVEL_FROM_LEVEL_NAME[levelName]
if (!pinoLevel) {
logger.warn('unknown log levelName "%s": cannot setLogLevel', levelName)
} else {
logger.level = pinoLevel
}
}
module.exports = {
DEFAULT_LOG_LEVEL,
createLogger,
isLoggerCustom,
setLogLevel
}