UNPKG

15.9 kBJavaScriptView Raw
1const sinon = require('sinon');
2const request = require('request-promise');
3const nock = require('nock');
4const assert = require('assert');
5const moment = require('moment');
6const zlib = require('zlib');
7const logzioLogger = require('../lib/logzio-nodejs.js');
8const hrtimemock = require('hrtimemock');
9
10const dummyHost = 'logz.io';
11const nockHttpAddress = `http://${dummyHost}:8070`;
12
13const createLogger = function createLogger(options) {
14 const myOptions = options;
15 myOptions.token = 'testToken';
16 myOptions.type = 'test-node';
17 myOptions.debug = true;
18 myOptions.host = dummyHost;
19 myOptions.sendIntervalMs = options.sendIntervalMs || 1000;
20 return logzioLogger.createLogger(myOptions);
21};
22
23const sendLogs = (logger, count = 1, message = 'hello there from test') => {
24 Array(count).fill().forEach((item, i) => {
25 logger.log({
26 message: `${message} #${i}`,
27 id: i,
28 });
29 });
30};
31
32describe('logger', () => {
33 describe('logs a single line', () => {
34 beforeAll((done) => {
35 sinon
36 .stub(request, 'post')
37 .resolves({
38 statusCode: 200,
39 });
40 done();
41 });
42
43 afterAll((done) => {
44 request.post.restore();
45 done();
46 });
47
48 it('sends log as a string', (done) => {
49 const logger = createLogger({
50 bufferSize: 1,
51 callback: done,
52 });
53 sinon.spy(logger, '_createBulk');
54
55 const logMsg = 'hello there from test';
56 logger.log(logMsg);
57 assert.equal(logger._createBulk.getCall(0).args[0][0].message, logMsg);
58 logger._createBulk.restore();
59 logger.close();
60 });
61
62 it('should send a log with an object as additional param', (done) => {
63 const logger = createLogger({
64 bufferSize: 1,
65 callback: done,
66 });
67 sinon.spy(logger, '_createBulk');
68 const obj = { key1: "val1", key2: "val2"};
69 const strMsg = 'message: ';
70 const expectedLog = strMsg + JSON.stringify(obj);
71 logger.log(strMsg, obj);
72
73 assert.equal(logger._createBulk.getCall(0).args[0][0].message, expectedLog);
74 logger._createBulk.restore();
75 logger.close();
76 });
77
78 it('sends log as a string with extra fields', (done) => {
79 const logger = createLogger({
80 bufferSize: 1,
81 callback: done,
82 extraFields: {
83 extraField1: 'val1',
84 extraField2: 'val2',
85 },
86 });
87 sinon.spy(logger, '_createBulk');
88
89 const logMsg = 'hello there from test';
90 logger.log(logMsg);
91 assert.equal(logger._createBulk.getCall(0).args[0][0].extraField1, 'val1');
92 assert.equal(logger._createBulk.getCall(0).args[0][0].extraField2, 'val2');
93
94 logger._createBulk.restore();
95 logger.close();
96 });
97
98 it('sends log as an object', (done) => {
99 const logger = createLogger({
100 bufferSize: 1,
101 callback: done,
102 });
103 sinon.spy(logger, '_createBulk');
104
105 const logMsg = {
106 message: 'hello there from test',
107 };
108 logger.log(logMsg);
109 assert.equal(logger._createBulk.getCall(0).args[0][0].message, logMsg.message);
110
111 logger._createBulk.restore();
112 logger.close();
113 });
114
115 it('sends log as an object with extra fields', (done) => {
116 const logger = createLogger({
117 bufferSize: 1,
118 callback: done,
119 extraFields: {
120 extraField1: 'val1',
121 extraField2: 'val2',
122 },
123 });
124 sinon.spy(logger, '_createBulk');
125
126 const logMsg = {
127 message: 'hello there from test',
128 };
129 logger.log(logMsg);
130 assert.equal(logger._createBulk.getCall(0).args[0][0].extraField1, 'val1');
131 assert.equal(logger._createBulk.getCall(0).args[0][0].extraField2, 'val2');
132
133 logger._createBulk.restore();
134 logger.close();
135 });
136
137 it('sends compressed log as an object with extra fields', (done) => {
138 const extraField1 = 'val1';
139 const extraField2 = 'val2';
140 const logger = createLogger({
141 bufferSize: 1,
142 callback: onDone,
143 extraFields: {
144 extraField1,
145 extraField2,
146 },
147 compress: true,
148 });
149
150 sinon.spy(logger, '_tryToSend');
151 const logMsg = {
152 message: 'hello there from test',
153 };
154
155 logger.log(logMsg);
156
157 function onDone() {
158 assert.equal(logger._tryToSend.getCall(0).args[0].headers['content-encoding'], 'gzip');
159 const unzipBody = JSON.parse(zlib.gunzipSync(logger._tryToSend.getCall(0).args[0].body));
160 assert.equal(unzipBody.message, logMsg.message);
161 assert.equal(unzipBody.extraField1, extraField1);
162 assert.equal(unzipBody.extraField2, extraField2);
163 logger._tryToSend.restore();
164 logger.close();
165 done();
166 }
167 });
168
169 it('sends log as an object with type', (done) => {
170 const logger = createLogger({
171 bufferSize: 1,
172 callback: done,
173 });
174 sinon.spy(logger, '_createBulk');
175
176 const logMsg = {
177 message: 'hello there from test',
178 type: 'myTestType',
179 };
180
181 logger.log(logMsg);
182 assert.equal(logger._createBulk.getCall(0).args[0][0].message, logMsg.message);
183 assert.equal(logger._createBulk.getCall(0).args[0][0].type, logMsg.type);
184
185 logger._createBulk.restore();
186 logger.close();
187 });
188
189 it('should not include nano timestamp by default', (done) => {
190 let logger = createLogger({
191 bufferSize: 1,
192 callback: done
193 });
194 sinon.spy(logger, '_createBulk');
195
196 logger.log({
197 message: 'hello there from test',
198 });
199 assert.equal(logger._createBulk.getCall(0).args[0][0].hasOwnProperty('@timestamp_nano_secs'), false);
200
201 logger._createBulk.restore();
202 logger.close();
203 });
204
205 it('should add a valid nano-sec timestamp to the log', (done) => {
206 var mockTime = 0.123456;
207 var expectedLogTime = '000123456';
208
209 logger = createLogger({
210 bufferSize: 1,
211 callback: done,
212 addTimestampWithNanoSecs: true,
213 });
214 sinon.spy(logger, '_createBulk');
215 hrtimemock(mockTime);
216 process.hrtime();
217 logger.log({
218 message: 'hello there from test'
219 })
220 const mockLogCall = logger._createBulk.getCall(0).args[0][0];
221 assert.equal(mockLogCall['@timestamp_nano'].endsWith(expectedLogTime), true);
222
223 logger._createBulk.restore();
224 logger.close();
225 });
226
227 it('writes a log message without @timestamp', (done) => {
228 const logger = createLogger({
229 // buffer is 2 so we could access the log before we send it, to analyze it
230 bufferSize: 2,
231 callback: done,
232 });
233
234 const fakeTime = moment('2011-09-01').valueOf();
235
236 // Fake the current time, so we could test on it
237 const clock = sinon.useFakeTimers(fakeTime);
238 logger.log({
239 message: 'hello there from test',
240 });
241 clock.restore();
242
243 assert.equal(fakeTime, moment(logger.messages[logger.messages.length - 1]['@timestamp'].valueOf()));
244 logger.close();
245 });
246
247 it('writes a log message with a custom @timestamp', (done) => {
248 const logger = createLogger({
249 // buffer is 2 so we could access the log before we send it, to analyze it
250 bufferSize: 2,
251 callback: done,
252 });
253
254 const fakeTime = moment('2011-09-01');
255
256 logger.log({
257 message: 'hello there from test',
258 '@timestamp': fakeTime.format(),
259 });
260
261 assert.equal(fakeTime.format(), logger.messages[logger.messages.length - 1]['@timestamp']);
262 logger.close();
263 });
264 });
265
266 describe('logs multiple lines', () => {
267 beforeAll((done) => {
268 sinon
269 .stub(request, 'post')
270 .resolves({
271 statusCode: 200,
272 });
273 done();
274 });
275
276 afterAll((done) => {
277 request.post.restore();
278 done();
279 });
280
281 it('Send multiple lines', (done) => {
282 const logger = createLogger({
283 bufferSize: 3,
284 callback: done,
285 });
286
287 sendLogs(logger, 3);
288
289 logger.close();
290 });
291
292 it('Send multiple bulks', (done) => {
293 let timesCalled = 0;
294 const bufferSize = 3;
295 const logCount = 6;
296 const expectedTimes = logCount / bufferSize;
297
298 function assertCalled() {
299 timesCalled += 1;
300
301 if (expectedTimes === timesCalled) {
302 done();
303 } else if (timesCalled > expectedTimes) {
304 throw new Error('called more than expected');
305 }
306 }
307
308 const logger = createLogger({
309 bufferSize,
310 callback: assertCalled,
311 });
312
313 sendLogs(logger, logCount);
314
315 logger.close();
316 });
317 });
318
319 describe('#log-closing', () => {
320 beforeAll((done) => {
321 sinon
322 .stub(request, 'post')
323 .resolves({
324 statusCode: 200,
325 });
326 done();
327 });
328
329 afterAll((done) => {
330 request.post.restore();
331 done();
332 });
333
334 it('Don\'t allow logs after closing', (done) => {
335 const logger = createLogger({
336 bufferSize: 1,
337 });
338 logger.close();
339 try {
340 logger.log({
341 messge: 'hello there from test',
342 });
343 done('Expected an error when logging into a closed log!');
344 } catch (ex) {
345 done();
346 }
347 });
348 });
349
350 describe('timers', () => {
351 beforeAll((done) => {
352 sinon
353 .stub(request, 'post')
354 .resolves({
355 statusCode: 200,
356 });
357 done();
358 });
359
360 afterAll((done) => {
361 request.post.restore();
362 done();
363 });
364
365 it('timer send test', (done) => {
366 const bufferSize = 100;
367 let timesCalled = 0;
368 const expectedTimes = 2;
369
370 function assertCalled() {
371 timesCalled += 1;
372 if (expectedTimes === timesCalled) done();
373 }
374
375 const logger = createLogger({
376 bufferSize,
377 callback: assertCalled,
378 sendIntervalMs: 10,
379 });
380
381 // These messages should be sent in 1 bulk 10 seconds from now (due to sendIntervalMs)
382 sendLogs(logger, 3);
383 // Schedule 100 msgs (buffer size)
384 // which should be sent in one bulk 11 seconds from start
385 setTimeout(() => {
386 sendLogs(logger, bufferSize);
387 logger.close();
388 }, 30);
389 }, 5000);
390 });
391
392 describe('recovers after server fails one time', () => {
393 let errorAndThenSuccessScope;
394 let extraRequestScope;
395 const socketDelay = 20;
396
397 beforeAll((done) => {
398 nock.cleanAll();
399 errorAndThenSuccessScope = nock(nockHttpAddress)
400 .post('/')
401 .socketDelay(socketDelay)
402 .query(true)
403 .once()
404 .reply(200, '')
405
406 // success
407 .post('/')
408 .socketDelay(0)
409 .query(true)
410 .once()
411 .reply(200, '');
412
413 extraRequestScope = nock(nockHttpAddress)
414 .filteringPath(() => '/')
415 .post('/')
416 .once()
417 .reply(200, '');
418
419 done();
420 });
421
422 afterAll((done) => {
423 nock.restore();
424 nock.cleanAll();
425 done();
426 });
427
428 it('Msgs are only sent once', (done) => {
429 // very small timeout so the first request will fail (nock setup this way above) and
430 // then second attempt will succeed
431 const logger = createLogger({
432 bufferSize: 1,
433 sendIntervalMs: 50000,
434 timeout: socketDelay / 2,
435 sleepUntilNextRetry: socketDelay * 2,
436 });
437
438 sendLogs(logger);
439 logger.close();
440
441 setTimeout(() => {
442 if (!errorAndThenSuccessScope.isDone()) {
443 done(new Error(`pending mocks: ${errorAndThenSuccessScope.pendingMocks()}`));
444 } else if (extraRequestScope.isDone()) {
445 done(new Error('We don\'t expect another request'));
446 } else {
447 done();
448 }
449 }, socketDelay * 5);
450 }, 10000);
451 });
452
453 describe('bad request', () => {
454 afterEach((done) => {
455 request.post.restore();
456 done();
457 });
458
459 it('bad request with code', (done) => {
460 sinon
461 .stub(request, 'post')
462 .rejects({
463 statusCode: 400,
464 cause: { code: 'BAD_REQUEST' },
465 messge: 'bad',
466 });
467
468 const logger = createLogger({
469 bufferSize: 3,
470 callback(err) {
471 if (err) {
472 done();
473 return;
474 }
475
476 done(new Error('Expected an error'));
477 },
478 });
479
480 sendLogs(logger, 3);
481
482 logger.close();
483 });
484
485 it('bad request with no cause nor code', (done) => {
486 sinon
487 .stub(request, 'post')
488 .rejects({
489 statusCode: 400,
490 message: 'bad',
491 });
492
493 const logger = createLogger({
494 bufferSize: 3,
495 callback(err) {
496 if (err) {
497 done();
498 return;
499 }
500
501 done(new Error('Expected an error'));
502 },
503 });
504
505 sendLogs(logger, 3);
506
507 logger.close();
508 });
509 });
510
511 describe('Logger callback', () => {
512 it('should execute external logger', (done) => {
513 const internalLogger = {
514 log: () => {
515 done();
516 },
517 };
518
519 const logger = createLogger({
520 bufferSize: 1,
521 internalLogger,
522 });
523
524 sendLogs(logger);
525 logger.close();
526 });
527 });
528});