123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671 |
- 'use strict';
- const assume = require('assume');
- const { format } = require('logform');
- const Writable = require('readable-stream/writable');
- const TransportStream = require('../');
- const Parent = require('./fixtures/parent');
- const { testLevels, testOrder } = require('./fixtures');
- const {
- infosFor,
- logFor,
- levelAndMessage,
- toException,
- toWriteReq
- } = require('abstract-winston-transport/utils');
- const { LEVEL, MESSAGE } = require('triple-beam');
- /*
- * Returns the provided `info` object with the appropriate LEVEL,
- * and MESSAGE symbols defined.
- */
- function infoify(info) {
- info[LEVEL] = info.level;
- info[MESSAGE] = info.message;
- return info;
- };
- describe('TransportStream', () => {
- it('should have the appropriate methods defined', () => {
- const transport = new TransportStream();
- assume(transport).instanceof(Writable);
- assume(transport._write).is.a('function');
- // eslint-disable-next-line no-undefined
- assume(transport.log).equals(undefined);
- });
- it('should accept a custom log function invoked on _write', () => {
- const log = logFor(1);
- const transport = new TransportStream({ log });
- assume(transport.log).equals(log);
- });
- it('should invoke a custom log function on _write', done => {
- const info = {
- [LEVEL]: 'test',
- level: 'test',
- message: 'Testing ... 1 2 3.'
- };
- const transport = new TransportStream({
- log(actual) {
- assume(actual).equals(info);
- done();
- }
- });
- transport.write(info);
- });
- describe('_write(info, enc, callback)', () => {
- it('should log to any level when { level: undefined }', done => {
- const expected = testOrder.map(levelAndMessage);
- const transport = new TransportStream({
- log: logFor(testOrder.length, (err, infos) => {
- if (err) {
- return done(err);
- }
- assume(infos.length).equals(expected.length);
- assume(infos).deep.equals(expected);
- done();
- })
- });
- expected.forEach(transport.write.bind(transport));
- });
- it('should not log when no info object is provided', done => {
- const expected = testOrder.map(levelAndMessage).map((info, i) => {
- if (testOrder.length > (i + 1)) {
- info.private = true;
- }
- return info;
- });
- const transport = new TransportStream({
- format: format(info => {
- if (info.private) return false;
- return info;
- })(),
- log: logFor(1, (err, infos) => {
- if (err) {
- return done(err);
- }
- assume(infos.length).equals(1);
- assume(infos.pop()).deep.equals(expected.pop());
- done();
- })
- });
- expected.forEach(transport.write.bind(transport));
- });
- it('should only log messages BELOW the level priority', done => {
- const expected = testOrder.map(levelAndMessage);
- const transport = new TransportStream({
- level: 'info',
- log: logFor(5, (err, infos) => {
- if (err) {
- return done(err);
- }
- assume(infos.length).equals(5);
- assume(infos).deep.equals(expected.slice(0, 5));
- done();
- })
- });
- transport.levels = testLevels;
- expected.forEach(transport.write.bind(transport));
- });
- it('{ level } should be ignored when { handleExceptions: true }', () => {
- const expected = testOrder.map(levelAndMessage).map(info => {
- info.exception = true;
- return info;
- });
- const transport = new TransportStream({
- level: 'info',
- log: logFor(testOrder.length, (err, infos) => {
- // eslint-disable-next-line no-undefined
- assume(err).equals(undefined);
- assume(infos.length).equals(expected.length);
- assume(infos).deep.equals(expected);
- })
- });
- transport.levels = testLevels;
- expected.forEach(transport.write.bind(transport));
- });
- describe('when { exception: true } in info', () => {
- it('should not invoke log when { handleExceptions: false }', done => {
- const expected = [{
- exception: true,
- [LEVEL]: 'error',
- level: 'error',
- message: 'Test exception handling'
- }, {
- [LEVEL]: 'test',
- level: 'test',
- message: 'Testing ... 1 2 3.'
- }];
- const transport = new TransportStream({
- log(info) {
- // eslint-disable-next-line no-undefined
- assume(info.exception).equals(undefined);
- done();
- }
- });
- expected.forEach(transport.write.bind(transport));
- });
- it('should invoke log when { handleExceptions: true }', done => {
- const actual = [];
- const expected = [{
- exception: true, [LEVEL]: 'error',
- level: 'error',
- message: 'Test exception handling'
- }, {
- [LEVEL]: 'test',
- level: 'test',
- message: 'Testing ... 1 2 3.'
- }];
- const transport = new TransportStream({
- handleExceptions: true,
- log(info, next) {
- actual.push(info);
- if (actual.length === expected.length) {
- assume(actual).deep.equals(expected);
- return done();
- }
- next();
- }
- });
- expected.forEach(transport.write.bind(transport));
- });
- });
- });
- describe('_writev(chunks, callback)', () => {
- it('invokes .log() for each of the valid chunks when necessary in streams plumbing', done => {
- const expected = infosFor({
- count: 50,
- levels: testOrder
- });
- const transport = new TransportStream({
- log: logFor(50 * testOrder.length, (err, infos) => {
- if (err) {
- return done(err);
- }
- assume(infos.length).equals(expected.length);
- assume(infos).deep.equals(expected);
- done();
- })
- });
- //
- // Make the standard _write throw to ensure that _writev is called.
- //
- transport._write = () => {
- throw new Error('TransportStream.prototype._write should never be called.');
- };
- transport.cork();
- expected.forEach(transport.write.bind(transport));
- transport.uncork();
- });
- it('should not log when no info object is provided in streams plumbing', done => {
- const expected = testOrder.map(levelAndMessage).map((info, i) => {
- if (testOrder.length > (i + 1)) {
- info.private = true;
- }
- return info;
- });
- const transport = new TransportStream({
- format: format(info => {
- if (info.private) {
- return false;
- }
- return info;
- })(),
- log: logFor(1, (err, infos) => {
- if (err) {
- return done(err);
- }
- assume(infos.length).equals(1);
- assume(infos.pop()).deep.equals(expected.pop());
- done();
- })
- });
- //
- // Make the standard _write throw to ensure that _writev is called.
- //
- transport._write = () => {
- throw new Error('TransportStream.prototype._write should never be called.');
- };
- transport.cork();
- expected.forEach(transport.write.bind(transport));
- transport.uncork();
- });
- it('ensures a format is applied to each info when no .logv is defined', done => {
- const expected = infosFor({ count: 10, levels: testOrder });
- const transport = new TransportStream({
- format: format.json(),
- log: logFor(10 * testOrder.length, (err, infos) => {
- if (err) {
- return done(err);
- }
- assume(infos.length).equals(expected.length);
- infos.forEach((info, i) => {
- assume(info[MESSAGE]).equals(JSON.stringify(expected[i]));
- });
- done();
- })
- });
- //
- // Make the standard _write throw to ensure that _writev is called.
- //
- transport._write = () => {
- throw new Error('TransportStream.prototype._write should never be called.');
- };
- transport.cork();
- expected.forEach(transport.write.bind(transport));
- transport.uncork();
- });
- it('invokes .logv with all valid chunks when necessary in streams plumbing', done => {
- const expected = infosFor({
- count: 50,
- levels: testOrder
- });
- const transport = new TransportStream({
- level: 'info',
- log() {
- throw new Error('.log() should never be called');
- },
- logv(chunks, callback) {
- assume(chunks.length).equals(250);
- callback(); // eslint-disable-line callback-return
- done();
- }
- });
- //
- // Make the standard _write throw to ensure that _writev is called.
- //
- transport._write = () => {
- throw new Error('TransportStream.prototype._write should never be called.');
- };
- transport.cork();
- transport.levels = testLevels;
- expected.forEach(transport.write.bind(transport));
- transport.uncork();
- });
- });
- describe('parent (i.e. "logger") ["pipe", "unpipe"]', () => {
- it('should define { level, levels } on "pipe"', done => {
- const parent = new Parent({
- level: 'info',
- levels: testLevels
- });
- const transport = new TransportStream({
- log(info, next) {
- assume(info.level).equals('info');
- assume(info.message).equals('ok sure');
- next();
- done();
- }
- });
- parent.pipe(transport);
- setImmediate(() => {
- assume(transport.level).equals(undefined);
- assume(transport.levels).equals(testLevels);
- assume(transport.parent).equals(parent);
- assume(transport.parent.level).equals('info');
- transport.write(infoify({ level: 'parrot', message: 'never logged' }));
- transport.write(infoify({ level: 'info', message: 'ok sure' }));
- });
- });
- it('should not overwrite existing { level } on "pipe"', done => {
- const parent = new Parent({
- level: 'info',
- levels: testLevels
- });
- const transport = new TransportStream({
- level: 'error',
- log(info, next) {
- assume(info.level).equals('error');
- assume(info.message).equals('ok sure');
- next();
- done();
- }
- });
- parent.pipe(transport);
- setImmediate(() => {
- assume(transport.level).equals('error');
- assume(transport.levels).equals(testLevels);
- assume(transport.parent).equals(parent);
- transport.write(infoify({ level: 'info', message: 'never logged' }));
- transport.write(infoify({ level: 'error', message: 'ok sure' }));
- });
- });
- it('should respond to changes in parent logging level', done => {
- const parent = new Parent({
- level: 'error',
- levels: testLevels
- });
- const transport = new TransportStream({
- log(info, next) {
- assume(info.level).equals('parrot');
- assume(info.message).equals('eventually log this');
- next();
- done();
- }
- });
- parent.pipe(transport);
- setImmediate(() => {
- assume(transport.levels).equals(testLevels);
- assume(transport.parent).equals(parent);
- transport.write(infoify({ level: 'info', message: 'never logged' }));
- parent.level = 'parrot';
- transport.write(infoify({ level: 'parrot', message: 'eventually log this' }));
- });
- });
- it('should unset parent on "unpipe"', done => {
- const parent = new Parent({
- level: 'info',
- levels: testLevels
- });
- const transport = new TransportStream({
- level: 'error',
- log() {}
- });
- //
- // Trigger "pipe" first so that transport.parent is set.
- //
- parent.pipe(transport);
- setImmediate(() => {
- assume(transport.parent).equals(parent);
- //
- // Now verify that after "unpipe" it is set to 'null'.
- //
- parent.unpipe(transport);
- setImmediate(() => {
- assume(transport.parent).equals(null);
- done();
- });
- });
- });
- it('should invoke a close method on "unpipe"', done => {
- const parent = new Parent({
- level: 'info',
- levels: testLevels
- });
- const transport = new TransportStream({
- log() {}
- });
- //
- // Test will only successfully complete when `close`
- // is invoked
- //
- transport.close = () => {
- assume(transport.parent).equals(null);
- done();
- };
- //
- // Trigger "pipe" first so that transport.parent is set.
- //
- parent.pipe(transport);
- setImmediate(() => {
- assume(transport.parent).equals(parent);
- parent.unpipe(transport);
- });
- });
- });
- describe('_accept(info)', function () {
- it('should filter only log messages BELOW the level priority', () => {
- const expected = testOrder
- .map(levelAndMessage)
- .map(toWriteReq);
- const transport = new TransportStream({
- level: 'info'
- });
- transport.levels = testLevels;
- const filtered = expected.filter(transport._accept, transport)
- .map(write => write.chunk.level);
- assume(filtered).deep.equals([
- 'error',
- 'warn',
- 'dog',
- 'cat',
- 'info'
- ]);
- });
- it('should filter out { exception: true } when { handleExceptions: false }', () => {
- const expected = testOrder
- .map(toException)
- .map(toWriteReq);
- const transport = new TransportStream({
- handleExceptions: false,
- level: 'info'
- });
- transport.levels = testLevels;
- const filtered = expected.filter(transport._accept, transport)
- .map(info => info.level);
- assume(filtered).deep.equals([]);
- });
- it('should include ALL { exception: true } when { handleExceptions: true }', () => {
- const expected = testOrder
- .map(toException)
- .map(toWriteReq);
- const transport = new TransportStream({
- handleExceptions: true,
- level: 'info'
- });
- transport.levels = testLevels;
- const filtered = expected.filter(transport._accept, transport)
- .map(write => write.chunk.level);
- assume(filtered).deep.equals(testOrder);
- });
- });
- describe('{ format }', function () {
- it('logs the output of the provided format', done => {
- const expected = {
- [LEVEL]: 'info',
- level: 'info',
- message: 'there will be json'
- };
- const transport = new TransportStream({
- format: format.json(),
- log(info) {
- assume(info[MESSAGE]).equals(JSON.stringify(expected));
- done();
- }
- });
- transport.write(expected);
- });
- it('treats the original object immutable', done => {
- const expected = {
- [LEVEL]: 'info',
- level: 'info',
- message: 'there will be json'
- };
- const transport = new TransportStream({
- format: format.json(),
- log(info) {
- assume(info).not.equals(expected);
- done();
- }
- });
- transport.write(expected);
- });
- it('_write continues to write after a format throws', done => {
- const transport = new TransportStream({
- format: format.printf((info) => {
- // Set a trap.
- if (info.message === 'ENDOR') {
- throw new Error('ITS A TRAP!');
- }
- return info.message;
- }),
- log(info, callback) {
- callback();
- assume(info.level).equals('info');
- assume(info.message).equals('safe');
- done();
- }
- });
- try {
- transport.write({ level: 'info', message: 'ENDOR' });
- } catch (ex) {
- assume(ex.message).equals('ITS A TRAP!');
- }
- transport.write({ level: 'info', message: 'safe' });
- });
- it('_writev continues to write after a format throws', done => {
- const transport = new TransportStream({
- format: format.printf((info) => {
- // Set a trap.
- if (info.message === 'ENDOR') {
- throw new Error('ITS A TRAP!');
- }
- return info.message;
- }),
- log(info, callback) {
- assume(info.level).is.a('string');
- assume(info.message).is.a('string');
- callback();
- if (info.message === 'safe') {
- done();
- }
- }
- });
- const infos = infosFor({
- count: 10,
- levels: testOrder
- });
- try {
- transport.cork();
- infos.forEach(info => transport.write(info));
- transport.write({ level: 'info', message: 'ENDOR' });
- transport.uncork();
- } catch (ex) {
- assume(ex.message).equals('ITS A TRAP!');
- }
- transport.write({ level: 'info', message: 'safe' });
- });
- });
- describe('{ silent }', () => {
- const silentTransport = new TransportStream({
- silent: true,
- format: format.json(),
- log() {
- assume(false).true('.log() was called improperly');
- }
- });
- it('{ silent: true } ._write() never calls `.log`', done => {
- const expected = {
- [LEVEL]: 'info',
- level: 'info',
- message: 'there will be json'
- };
- silentTransport.write(expected);
- setImmediate(() => done());
- });
- it('{ silent: true } ._writev() never calls `.log`', done => {
- const expected = {
- [LEVEL]: 'info',
- level: 'info',
- message: 'there will be json'
- };
- silentTransport.cork();
- for (let i = 0; i < 15; i++) {
- silentTransport.write(expected);
- }
- silentTransport.uncork();
- setImmediate(() => done());
- });
- it('{ silent: true } ensures ._accept(write) always returns false', () => {
- const accepted = silentTransport._accept({ chunk: {} });
- assume(accepted).false();
- });
- });
- });
|