1 |
|
2 |
|
3 | 'use strict';
|
4 |
|
5 | var Stream = require('stream').Stream;
|
6 | var util = require('util');
|
7 |
|
8 | var assert = require('assert-plus');
|
9 | var bunyan = require('bunyan');
|
10 | var LRU = require('lru-cache');
|
11 | var uuid = require('uuid');
|
12 |
|
13 |
|
14 |
|
15 | var sprintf = util.format;
|
16 | var DEFAULT_REQ_ID = uuid.v4();
|
17 | var STR_FMT = '[object %s<level=%d, limit=%d, maxRequestIds=%d>]';
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | function appendStream(streams, s) {
|
31 | assert.arrayOfObject(streams, 'streams');
|
32 | assert.object(s, 'stream');
|
33 |
|
34 | if (s instanceof Stream) {
|
35 | streams.push({
|
36 | raw: false,
|
37 | stream: s
|
38 | });
|
39 | } else {
|
40 | assert.optionalBool(s.raw, 'stream.raw');
|
41 | assert.object(s.stream, 'stream.stream');
|
42 | streams.push(s);
|
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 | function RequestCaptureStream(opts) {
|
75 | assert.object(opts, 'options');
|
76 | assert.optionalObject(opts.stream, 'options.stream');
|
77 | assert.optionalArrayOfObject(opts.streams, 'options.streams');
|
78 | assert.optionalNumber(opts.level, 'options.level');
|
79 | assert.optionalNumber(opts.maxRecords, 'options.maxRecords');
|
80 | assert.optionalNumber(opts.maxRequestIds, 'options.maxRequestIds');
|
81 | assert.optionalBool(opts.dumpDefault, 'options.dumpDefault');
|
82 |
|
83 | var self = this;
|
84 | Stream.call(this);
|
85 |
|
86 | this.level = opts.level ? bunyan.resolveLevel(opts.level) : bunyan.WARN;
|
87 | this.limit = opts.maxRecords || 100;
|
88 | this.maxRequestIds = opts.maxRequestIds || 1000;
|
89 |
|
90 |
|
91 | this.requestMap = new LRU({
|
92 | max: self.maxRequestIds
|
93 | });
|
94 | this.dumpDefault = opts.dumpDefault;
|
95 |
|
96 | this._offset = -1;
|
97 | this._rings = [];
|
98 |
|
99 | this.streams = [];
|
100 |
|
101 | if (opts.stream) {
|
102 | appendStream(this.streams, opts.stream);
|
103 | }
|
104 |
|
105 | if (opts.streams) {
|
106 | opts.streams.forEach(appendStream.bind(null, this.streams));
|
107 | }
|
108 |
|
109 | this.haveNonRawStreams = false;
|
110 |
|
111 | for (var i = 0; i < this.streams.length; i++) {
|
112 | if (!this.streams[i].raw) {
|
113 | this.haveNonRawStreams = true;
|
114 | break;
|
115 | }
|
116 | }
|
117 | }
|
118 | util.inherits(RequestCaptureStream, Stream);
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | RequestCaptureStream.prototype.write = function write(record) {
|
129 | var req_id = record.req_id || DEFAULT_REQ_ID;
|
130 | var ring;
|
131 | var self = this;
|
132 |
|
133 | if (!(ring = this.requestMap.get(req_id))) {
|
134 | if (++this._offset > this.maxRequestIds) {
|
135 | this._offset = 0;
|
136 | }
|
137 |
|
138 | if (this._rings.length <= this._offset) {
|
139 | this._rings.push(
|
140 | new bunyan.RingBuffer({
|
141 | limit: self.limit
|
142 | })
|
143 | );
|
144 | }
|
145 |
|
146 | ring = this._rings[this._offset];
|
147 | ring.records.length = 0;
|
148 | this.requestMap.set(req_id, ring);
|
149 | }
|
150 |
|
151 | assert.ok(ring, 'no ring found');
|
152 |
|
153 |
|
154 | ring.write(record);
|
155 |
|
156 | if (record.level >= this.level) {
|
157 | var i, r, ser;
|
158 |
|
159 | for (i = 0; i < ring.records.length; i++) {
|
160 | r = ring.records[i];
|
161 |
|
162 | if (this.haveNonRawStreams) {
|
163 | ser = JSON.stringify(r, bunyan.safeCycles()) + '\n';
|
164 | }
|
165 | self.streams.forEach(function forEach(s) {
|
166 | s.stream.write(s.raw ? r : ser);
|
167 | });
|
168 | }
|
169 | ring.records.length = 0;
|
170 |
|
171 | if (this.dumpDefault) {
|
172 | var defaultRing = self.requestMap.get(DEFAULT_REQ_ID);
|
173 |
|
174 | for (i = 0; i < defaultRing.records.length; i++) {
|
175 | r = defaultRing.records[i];
|
176 |
|
177 | if (this.haveNonRawStreams) {
|
178 | ser = JSON.stringify(r, bunyan.safeCycles()) + '\n';
|
179 | }
|
180 | self.streams.forEach(function forEach(s) {
|
181 | s.stream.write(s.raw ? r : ser);
|
182 | });
|
183 | }
|
184 | defaultRing.records.length = 0;
|
185 | }
|
186 | }
|
187 | };
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 | RequestCaptureStream.prototype.toString = function toString() {
|
197 | return sprintf(
|
198 | STR_FMT,
|
199 | this.constructor.name,
|
200 | this.level,
|
201 | this.limit,
|
202 | this.maxRequestIds
|
203 | );
|
204 | };
|
205 |
|
206 |
|
207 |
|
208 | var SERIALIZERS = {
|
209 | err: bunyan.stdSerializers.err,
|
210 | req: bunyan.stdSerializers.req,
|
211 | res: bunyan.stdSerializers.res,
|
212 | client_req: clientReq,
|
213 | client_res: clientRes
|
214 | };
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 | function clientReq(req) {
|
225 | if (!req) {
|
226 | return req;
|
227 | }
|
228 |
|
229 | var host;
|
230 |
|
231 | try {
|
232 | host = req.host.split(':')[0];
|
233 | } catch (e) {
|
234 | host = false;
|
235 | }
|
236 |
|
237 | return {
|
238 | method: req ? req.method : false,
|
239 | url: req ? req.path : false,
|
240 | address: host,
|
241 | port: req ? req.port : false,
|
242 | headers: req ? req.headers : false
|
243 | };
|
244 | }
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | function clientRes(res) {
|
255 | if (!res || !res.statusCode) {
|
256 | return res;
|
257 | }
|
258 |
|
259 | return {
|
260 | statusCode: res.statusCode,
|
261 | headers: res.headers
|
262 | };
|
263 | }
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 | function createLogger(name) {
|
274 | return bunyan.createLogger({
|
275 | name: name,
|
276 | serializers: SERIALIZERS,
|
277 | streams: [
|
278 | {
|
279 | level: 'warn',
|
280 | stream: process.stderr
|
281 | },
|
282 | {
|
283 | level: 'debug',
|
284 | type: 'raw',
|
285 | stream: new RequestCaptureStream({
|
286 | stream: process.stderr
|
287 | })
|
288 | }
|
289 | ]
|
290 | });
|
291 | }
|
292 |
|
293 |
|
294 |
|
295 | module.exports = {
|
296 | RequestCaptureStream: RequestCaptureStream,
|
297 | serializers: SERIALIZERS,
|
298 | createLogger: createLogger
|
299 | };
|