forked from ericelliott/bunyan-request-logger
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrequest-logger.js
207 lines (177 loc) · 5.14 KB
/
request-logger.js
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
'use strict';
var
// Lightweight logging library:
bunyan = require('bunyan'),
// For the request ID:
cuid = require('cuid'),
// For object property overrides:
assign = require('lodash/object/assign'),
/**
* Get long stack traces for the error logger.
* @param {error} err Error object
* @return {string} Stack trace
*/
getFullStack = function getFullStack(err) {
var ret = err.stack || err.toString(),
cause;
if (err.cause && typeof (err.cause) ===
'function') {
cause = err.cause();
if (cause) {
ret += '\nCaused by: ' +
getFullStack(cause);
}
}
return ret;
},
// To create a custom Bunyan serializer,
// just return the desired object
// serialization.
//
// Regardless of your serialization settings,
// all bunyan messages automatically include:
//
// * App name
// * hostname
// * pid (Process ID)
// * Log level
// * Whatever object you pass in to be logged
// * An optional message (default: empty string)
// * Timestamp
// * Log format version number
serializers = {
req: function reqSerializer(req) {
if (!req || !req.connection) {
return req;
}
return {
url: req.url,
method: req.method,
protocol: req.protocol,
requestId: req.requestId,
// In case there's a proxy server:
ip: req.headers['x-forwarded-for'] ||
req.connection.remoteAddress,
headers: req.headers
};
},
res: function resSerializer(res) {
if (!res) {
return res;
}
return {
statusCode: res.statusCode,
headers: res._header,
requestId: res.requestId,
responseTime: res.responseTime
};
},
err: function errSerializer(err) {
if (!err || !err.stack) {
return err;
}
return {
message: err.message,
name: err.name,
stack: getFullStack(err),
code: err.code,
signal: err.signal,
requestId: err.requestId
};
}
},
// Bunyan offers lots of other options,
// including extensible output stream types.
//
// You might be interested in
// node-bunyan-syslog, in particular.
defaults = {
name: 'unnamed app',
serializers: assign({}, bunyan.stdSerializers,
serializers)
},
/**
* Take bunyan options, monkey patch request
* and response objects for better logging,
* and return a logger instance.
*
* @param {object} options See bunyan docs
* @param {boolean} options.logParams
* Pass true to log request parameters
* in a separate log.info() call.
* @return {object} logger See bunyan docs
* @return {function} logger.requestLogger
* (See below)
*/
createLogger = function (options) {
var settings = assign({}, defaults, options),
log = bunyan.createLogger(settings);
log.requestLogger = function
createRequestLogger() {
return function requestLogger(req, res,
next) {
// Used to calculate response times:
var startTime = new Date();
// Add a unique identifier to the request.
req.requestId = cuid();
// Log the request
log.info({req: req});
// Make sure responses get logged, too:
req.on('end', function () {
res.responseTime = new Date() - startTime;
res.requestId = req.requestId;
log.info({res: res});
});
next();
};
};
log.errorLogger = function
createErrorLogger() {
return function errorLogger(err, req, res,
next) {
var status = err.status || res && res.status;
// Add the requestId so we can link the
// error back to the originating request.
err.requestId = req && req.requestId;
// Omit stack from the 4xx range
if (status >= 400 &&
status <=499) {
delete err.stack;
}
log.error({err: err});
next(err);
};
};
// Tracking pixel / web bug
//
// Using a 1x1 transparent gif allows you to
// use the logger in emails or embed the
// tracking pixel on third party sites without
// requiring to JavaScript.
log.route = function route() {
return function pixel(req, res) {
var data;
if (settings.logParams && req.params) {
data = assign({}, req.params, {
requestId: req.requestId
});
log.info(req.params);
}
res.header('content-type', 'image/gif');
// GIF images can be so small, it's
// easy to just inline it instead of
// loading from a file:
res.send(
'GIF89a\u0001\u0000\u0001\u0000' +
'\u00A1\u0001\u0000\u0000\u0000\u0000' +
'\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF' +
'\u00FF\u00FF\u00FF\u0021\u00F9\u0004' +
'\u0001\u000A\u0000\u0001\u0000\u002C' +
'\u0000\u0000\u0000\u0000\u0001\u0000' +
'\u0001\u0000\u0000\u0002\u0002\u004C' +
'\u0001\u0000;');
};
};
return log;
};
module.exports = createLogger;