17
17
18
18
'use strict'
19
19
20
- const { MESSAGE } = require ( 'triple-beam' )
20
+ const { MESSAGE , SPLAT } = require ( 'triple-beam' )
21
21
const safeStableStringify = require ( 'safe-stable-stringify' )
22
22
const {
23
23
version,
24
- formatError,
25
24
formatHttpRequest,
26
25
formatHttpResponse
27
26
} = require ( '@elastic/ecs-helpers' )
@@ -48,11 +47,11 @@ try {
48
47
const stringify = safeStableStringify . configure ( { deterministic : false } )
49
48
50
49
const reservedFields = {
50
+ '@timestamp' : true ,
51
+ error : true ,
51
52
level : true ,
52
53
'log.level' : true ,
53
- ecs : true ,
54
- '@timestamp' : true ,
55
- err : true ,
54
+ message : true ,
56
55
req : true ,
57
56
res : true
58
57
}
@@ -82,6 +81,117 @@ class EcsWinstonTransform {
82
81
'ecs.version' : version
83
82
}
84
83
84
+ // Error handling. Winston has a number of ways that it does something with
85
+ // `Error` instances passed to a logger.
86
+ //
87
+ // 1. `log.warn('a message', new Error('boom'))`
88
+ // If `info[SPLAT][0] instanceof Error`, then convert it to `error.*` fields
89
+ // in place of `info.stack`.
90
+ //
91
+ // 2. Winston logger configured to handle uncaughtException and/or unhandledRejection.
92
+ // If `info.exception: true` and level is "error" and `info.trace` is an
93
+ // Array and `info.message` starts with "uncaughtException:" or
94
+ // "unhandledRejection:", then convert to `error.*` fields. These
95
+ // conditions are to infer the `info` shape returned by Winston's
96
+ // `ExceptionHandler` and `RejectionHandler`.
97
+ // In this case the redundant `stack`, `trace`, `date` fields are dropped
98
+ // and error details are moved to the `error.*` fields.
99
+ //
100
+ // If `opts.convertErr === true` (the default), then the next two forms are
101
+ // considered as well.
102
+ //
103
+ // 3. `log.warn(new Error('boom'))`
104
+ // `log.warn(new Error(''))`
105
+ // `log.warn(new Error('boom'), {foo: 'bar'})`
106
+ // If `info instanceof Error` or `info.message instanceof Error`, then
107
+ // convert it to `error.*` fields. The latter two are a little strange, but
108
+ // Winston's logger will transform that to `{ message: new Error(...) }`
109
+ // and "logform/errors.js" will handle that.
110
+ //
111
+ // 4. `log.warn('a message', { err: new Error('boom') })`
112
+ // If `info.err instanceof Error`, then convert to `error.*` fields.
113
+ // Note: This feature doesn't really belong because it extends error
114
+ // handling beyond what is typical in Winston. It remains for backward
115
+ // compatibility.
116
+ let err
117
+ let delErrorLevel = false
118
+ const splat0 = SPLAT && info [ SPLAT ] && info [ SPLAT ] [ 0 ]
119
+ if ( splat0 instanceof Error ) { // case 1
120
+ // Undo the addition of this error's enumerable properties to the
121
+ // top-level info object.
122
+ err = splat0
123
+ delete info . stack
124
+ for ( const propName in err ) {
125
+ delete info [ propName ]
126
+ }
127
+ } else if ( info . exception === true &&
128
+ info . level === 'error' &&
129
+ Array . isArray ( info . trace ) &&
130
+ ( info . message . startsWith ( 'uncaughtException:' ) ||
131
+ info . message . startsWith ( 'unhandledRejection:' ) ) ) { // case 2
132
+ // The 'stack', 'trace', and trace in the 'message' are redundant.
133
+ // 'date' is also redundant with '@timestamp'.
134
+ delete info . stack
135
+ delete info . trace
136
+ delete info . date
137
+ ecsFields . message = info . message . split ( / \n / , 1 ) [ 0 ]
138
+ // istanbul ignore else
139
+ if ( info . error instanceof Error ) {
140
+ err = info . error
141
+ } else {
142
+ ecsFields . error = {
143
+ message : info . error . toString ( )
144
+ }
145
+ }
146
+ delete info . error
147
+ // Dev Note: We *could* translate some of the process and os fields, but
148
+ // we don't currently.
149
+ // https://www.elastic.co/guide/en/ecs/current/ecs-process.html
150
+ // https://www.elastic.co/guide/en/ecs/current/ecs-host.html
151
+ } else if ( convertErr ) { // cases 3 and 4
152
+ if ( info instanceof Error ) {
153
+ // With `log.info(err)`, Winston incorrectly uses `err` as the info
154
+ // object -- (a) mutating it and (b) resulting in not being able to
155
+ // differentiate `defaultMeta` and `err` properties.
156
+ // The best we can do is, at least, not serialize `error.level` using
157
+ // the incorrectly added `level` field.
158
+ err = info
159
+ delErrorLevel = true
160
+ } else if ( info . message instanceof Error ) {
161
+ // `log.info(err, {...})` or `log.info(new Error(''))` with empty message.
162
+ err = info . message
163
+ ecsFields . message = err . message
164
+ } else if ( info . err instanceof Error ) {
165
+ err = info . err
166
+ delete info . err
167
+ }
168
+ }
169
+
170
+ // If we have an Error instance, then serialize it to `error.*` fields.
171
+ if ( err ) {
172
+ // First we add err's enumerable fields, as `logform.errors()` does.
173
+ ecsFields . error = Object . assign ( { } , err )
174
+ if ( delErrorLevel ) {
175
+ delete ecsFields . error . level
176
+ }
177
+ // Then add standard ECS error fields (https://www.elastic.co/guide/en/ecs/current/ecs-error.html).
178
+ // istanbul ignore next
179
+ ecsFields . error . type = toString . call ( err . constructor ) === '[object Function]'
180
+ ? err . constructor . name
181
+ : err . name
182
+ ecsFields . error . message = err . message
183
+ ecsFields . error . stack_trace = err . stack
184
+ // The add some additional fields. `cause` is handled by
185
+ // `logform.errors({cause: true})`. This implementation ensures it is
186
+ // always a string to avoid its type varying depending on the value.
187
+ // istanbul ignore next -- so coverage works for Node.js <16.9.0
188
+ if ( err . cause ) {
189
+ ecsFields . error . cause = err . cause instanceof Error
190
+ ? err . cause . stack
191
+ : err . cause . toString ( )
192
+ }
193
+ }
194
+
85
195
// Add all unreserved fields.
86
196
const keys = Object . keys ( info )
87
197
for ( let i = 0 , len = keys . length ; i < len ; i ++ ) {
@@ -165,15 +275,6 @@ class EcsWinstonTransform {
165
275
}
166
276
}
167
277
168
- // https://www.elastic.co/guide/en/ecs/current/ecs-error.html
169
- if ( info . err !== undefined ) {
170
- if ( convertErr ) {
171
- formatError ( ecsFields , info . err )
172
- } else {
173
- ecsFields . err = info . err
174
- }
175
- }
176
-
177
278
// https://www.elastic.co/guide/en/ecs/current/ecs-http.html
178
279
if ( info . req !== undefined ) {
179
280
if ( convertReqRes ) {
0 commit comments