diff --git a/.eslintrc b/.eslintrc index 3830a1a..654397a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,6 +7,8 @@ }, "globals": { + "console": true, + // Jest. "afterAll": false, "afterEach": false, diff --git a/.travis.yml b/.travis.yml index 951b1dd..e2d502e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,10 @@ node_js: - 10 before_script: + # Codecov preparation. + - npm install -g codecov + + # CodeClimate preparation. - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter -d before-build @@ -21,4 +25,8 @@ script: yarn test-ci after_script: + # CodeCode report. + - codecov + + # CodeClimate report. - ./cc-test-reporter after-build -d --exit-code $TRAVIS_TEST_RESULT diff --git a/README.md b/README.md index 1a42430..ac03824 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -FiLog: a Meteor 1.4-1.6 logging package +FiLog: a Meteor 1.4-1.7 logging package ======================================= [![Build Status](https://travis-ci.org/fgm/filog.svg?branch=master)](https://travis-ci.org/fgm/filog) -[![Test Coverage](https://api.codeclimate.com/v1/badges/f380bfd73a491c472221/test_coverage)](https://codeclimate.com/github/fgm/filog/test_coverage) +[![CodeCov Test Coverage](https://codecov.io/gh/fgm/filog/branch/master/graph/badge.svg)](https://codecov.io/gh/fgm/filog) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/fgm/filog/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/fgm/filog/?branch=master) [![Known Vulnerabilities](https://snyk.io/test/github/fgm/filog/badge.svg?targetFile=package.json)](https://snyk.io/test/github/fgm/filog?targetFile=package.json) @@ -202,3 +202,5 @@ page, failing the integration tests. $ meteor npm run test-integration $ meteor npm run test $ meteor npm run cover + +**TIP** Reading the `.travis.yml` file can be useful too. diff --git a/__tests__/unit/browserProcessorTest.ts b/__tests__/unit/browserProcessorTest.ts index b168c2c..f2b30a0 100644 --- a/__tests__/unit/browserProcessorTest.ts +++ b/__tests__/unit/browserProcessorTest.ts @@ -1,4 +1,5 @@ import BrowserProcessor from "../../src/Processors/BrowserProcessor"; +import NullFn from "../../src/NullFn"; /* This test hand-builds the MemoryInfo object in window.Performance, because * that class is not defined outside Chrome. @@ -10,10 +11,6 @@ function testBrowserProcessor() { this[key] = values[key]; } } - - toJSON() { - return { memory: {} }; - } } let initialContext; @@ -27,20 +24,23 @@ function testBrowserProcessor() { }); test("should fail outside the browser", () => { + let processor; expect(() => { - const processor = new BrowserProcessor(navigator, window); + processor = new BrowserProcessor(navigator, window); processor.process(initialContext); }).not.toThrow(ReferenceError); + processor = null; expect(() => { - const processor = new BrowserProcessor(); - processor.process(initialContext); + processor = new BrowserProcessor(); }).toThrow(ReferenceError); + expect(processor).toBeNull(); + processor = null; expect(() => { - const processor = new BrowserProcessor(null, null); - processor.process(initialContext); + processor = new BrowserProcessor(null, null); }).toThrow(ReferenceError); + expect(processor).toBeNull(); }); test("should at least return defaults", () => { @@ -54,6 +54,23 @@ function testBrowserProcessor() { expect(actual).not.toHaveProperty("browser.product"); }); + test("Should ignore extra defaults on browser", () => { + const MAGIC = "xyzzy"; + const win = Object.assign({}, { performance: new Performance({ memory: { + jsHeapSizeLimit: 1, + totalJSHeapSize: 2, + usedJSHeapSize: 3, + } }) }); + + const processor = new BrowserProcessor(navigator, win); + Object.prototype[MAGIC] = NullFn; + const processed = processor.process(initialContext); + expect(processed).toHaveProperty('browser'); + delete Object.prototype[MAGIC]; + const browser = processed.browser; + expect(browser).not.toHaveProperty(MAGIC); + }); + test("should serialize performance.memory correctly", () => { const keys = [ "jsHeapSizeLimit", diff --git a/__tests__/unit/logContextTest.ts b/__tests__/unit/logContextTest.ts index 214dffb..f0aac0e 100644 --- a/__tests__/unit/logContextTest.ts +++ b/__tests__/unit/logContextTest.ts @@ -9,7 +9,8 @@ function testImmutableContext() { selectSenders: () => [], }; test("should not modify context in log() calls", () => { - const logger = new Logger(strategy); + const logger = new Logger(strategy ); + logger.side = 'test'; const originalContext = {}; const context = { ...originalContext }; @@ -29,7 +30,7 @@ function testImmutableContext() { function testMessageContext() { let result; - const referenceContext = { a: "A" }; + const referenceContext = () => ({ a: "A" }); const sender = new class { send(level, message, context) { result = { level, message, context }; @@ -41,73 +42,117 @@ function testMessageContext() { selectSenders: () => [sender], }; - const DETAILS_KEY = "message_details"; - - test(`should add the message argument to ${DETAILS_KEY}`, () => { + /** + * log(..., { a: 1 }) should ... + * + * - log { message_details: { a: 1} }. + */ + test(`should add the message argument to ${Logger.KEY_DETAILS}`, () => { const logger = new Logger(strategy); result = null; - logger.log(LogLevel.DEBUG, "some message", referenceContext); + logger.log(LogLevel.DEBUG, "some message", referenceContext()); - const actual = result.context[DETAILS_KEY].a; + const actual = result.context[Logger.KEY_DETAILS].a; const expected = "A"; // Message details is set expect(actual).toBe(expected); }); - test(`should merge contents of existing ${DETAILS_KEY} context key`, () => { + /** + * log(..., { a: 1 }) should ... + * + * - NOT log { a: 1 }. + */ + test("should not add the message arguments to context root", () => { const logger = new Logger(strategy); result = null; - const originalContext = Object.assign({ [DETAILS_KEY]: { foo: "bar" } }, referenceContext); - logger.log(LogLevel.DEBUG, "some message", originalContext); + logger.log(LogLevel.DEBUG, "some message", referenceContext()); - const actual = result.context[DETAILS_KEY]; - - // Original top-level key should be in top [DETAILS_KEY]. - const expectedReference = "A"; - expect(actual).toHaveProperty("a"); - expect(actual.a).toBe(expectedReference); - - // Key nested in original message_detail should also in top [DETAILS_KEY]. - const expectedNested = "bar"; - expect(actual).toHaveProperty("foo"); - expect(actual.foo).toBe(expectedNested); + const actual = result.context.hasOwnProperty("a"); + const expected = false; + // Message details is set + expect(actual).toBe(expected); }); - test(`should not merge existing ${DETAILS_KEY} context key itself`, () => { + /** + * log(..., { a: "A", message_details: { foo: "bar" } }) should... + * + * - log { message_details: { a: "A", message_details: { foo: "bar" } } }, + * unlike the message_details merging it did until 0.1.18 included. + */ + test(`should not merge contents of existing ${Logger.KEY_DETAILS} context key`, () => { const logger = new Logger(strategy); result = null; - const originalContext = Object.assign({ [DETAILS_KEY]: { foo: "bar" } }, referenceContext); + const originalContext = Object.assign({ [Logger.KEY_DETAILS]: { foo: "bar" } }, referenceContext()); logger.log(LogLevel.DEBUG, "some message", originalContext); - // Message+_details should not contain a nested [DETAILS_KEY]. - const actual = result.context[DETAILS_KEY]; - expect(actual).not.toHaveProperty(DETAILS_KEY); + const actual = result.context; + expect(actual).not.toHaveProperty("a"); + expect(actual).not.toHaveProperty("foo"); + expect(actual).toHaveProperty(Logger.KEY_DETAILS); + + // Original top-level keys should still be in top [KEY_DETAILS]. + const actualDetails = actual[Logger.KEY_DETAILS]; + expect(actualDetails).toHaveProperty("a", "A"); + expect(actualDetails).toHaveProperty(Logger.KEY_DETAILS); + expect(actualDetails).not.toHaveProperty("foo"); + + // Key nested in original message_detail should remain in place. + const actualNested = actualDetails[Logger.KEY_DETAILS]; + expect(actualNested).not.toHaveProperty("a", "A"); + expect(actualNested).not.toHaveProperty(Logger.KEY_DETAILS); + expect(actualNested).toHaveProperty("foo", 'bar'); }); - test(`should keep the latest keys when merging existing ${DETAILS_KEY}`, () => { + /** + * log(..., { a: "A", message_details: { a: "A" } }) should... + * + * - log { message_details: { a: "A", message_details: { a: "A" } } }, + * unlike the message_details merging it did until 0.1.18 included. + */ + test(`should not merge existing ${Logger.KEY_DETAILS} context key itself`, () => { const logger = new Logger(strategy); result = null; - const originalContext = Object.assign(referenceContext, { [DETAILS_KEY]: { a: "B" } }); + const originalContext = Object.assign({ [Logger.KEY_DETAILS]: { a: "A" } }, referenceContext()); logger.log(LogLevel.DEBUG, "some message", originalContext); - // [DETAILS_KEY] should contain the newly added value for key "a", not the - // one present in the initial [DETAILS_KEY]. - const actual = result.context[DETAILS_KEY]; - const expected = "A"; - // Message details is set - expect(actual).toHaveProperty("a"); - expect(actual.a).toBe(expected); + // Message_details should only contain a nested [KEY_DETAILS]. + const actual = result.context; + const keys = Object.keys(actual).sort(); + expect(keys.length).toBe(3); + expect(keys).toEqual([Logger.KEY_DETAILS, Logger.KEY_SOURCE, Logger.KEY_TS]); + expect(actual).toHaveProperty(Logger.KEY_DETAILS); + + // Original top-level keys should still be in top [KEY_DETAILS]. + const actualDetails = actual[Logger.KEY_DETAILS]; + expect(Object.keys(actualDetails).length).toBe(2); + expect(actualDetails).toHaveProperty("a", "A"); + expect(actualDetails).toHaveProperty(Logger.KEY_DETAILS); + + // Key nested in original message_detail should remain in place. + const actualNested = actualDetails[Logger.KEY_DETAILS]; + expect(Object.keys(actualNested).length).toBe(1); + expect(actualNested).toHaveProperty("a", "A"); }); - test("should not add the message arguments to context root", () => { + /** + * log(..., { a: "A", message_details: { a: "B" } }) should ... + * + * - log { message_details: { a: "A", message_details: { a: "B" } } }. + */ + test(`should not merge keys within ${Logger.KEY_DETAILS}`, () => { const logger = new Logger(strategy); result = null; - logger.log(LogLevel.DEBUG, "some message", referenceContext); + const originalContext = Object.assign({ [Logger.KEY_DETAILS]: { a: "B" } }, referenceContext()); + logger.log(LogLevel.DEBUG, "some message", originalContext); - const actual = result.context.hasOwnProperty("a"); - const expected = false; - // Message details is set - expect(actual).toBe(expected); + // [KEY_DETAILS] should contain the newly added value for key "a", not the + // one present in the initial [KEY_DETAILS]. + const actual = result.context[Logger.KEY_DETAILS]; + const expected = "A"; + // Message details is set. + expect(actual).toHaveProperty("a"); + expect(actual.a).toBe(expected); }); } @@ -303,7 +348,9 @@ function testProcessors() { class TimeWarp extends ProcessorBase { // Let's do the time warp again. process(context) { - context.timestamp = { log: +new Date("1978-11-19 05:00:00") }; + context[Logger.KEY_TS] = { + test: { log: +new Date("1978-11-19 05:00:00") }, + }; context.hostname = "remote"; return context; } @@ -317,6 +364,7 @@ function testProcessors() { selectSenders: () => [this.sender], }; this.logger = new Logger(this.strategy); + this.logger.side = 'test'; }); test("processors should be able to modify the content of ordinary existing keys", () => { @@ -368,8 +416,8 @@ function testProcessors() { expect(this.sender.logs.length).toBe(1); const [,, context] = this.sender.logs.pop(); expect(context).toHaveProperty("hostname", "local"); - expect(context).toHaveProperty("timestamp.log"); - const lag = ts - context.timestamp.log; + expect(context).toHaveProperty(`${Logger.KEY_TS}.${this.logger.side}.log`); + const lag = ts - context[Logger.KEY_TS][this.logger.side].log; expect(lag).toBeGreaterThanOrEqual(0); // No sane machine should take more than 100 msec to return from log() with // such a fast sending configuration. @@ -384,8 +432,8 @@ function testProcessors() { expect(this.sender.logs.length).toBe(1); const [,, context] = this.sender.logs.pop(); expect(context).toHaveProperty("hostname", "remote"); - expect(context).toHaveProperty("timestamp.log"); - const lag = ts - context.timestamp.log; + expect(context).toHaveProperty(`${Logger.KEY_TS}.${this.logger.side}.log`); + const lag = ts - context[Logger.KEY_TS][this.logger.side].log; expect(lag).toBeGreaterThanOrEqual(0); // No sane machine should take more than 100 msec to return from log() with // such a fast sending configuration. The TimeWarp processor attempts to diff --git a/__tests__/unit/meteorUserProcessorTest.ts b/__tests__/unit/meteorUserProcessorTest.ts index 9c23537..435b492 100644 --- a/__tests__/unit/meteorUserProcessorTest.ts +++ b/__tests__/unit/meteorUserProcessorTest.ts @@ -1,24 +1,31 @@ import MeteorUserProcessor from "../../src/Processors/MeteorUserProcessor"; +import ServerLogger from "../../src/ServerLogger"; function testMeteorUserProcessor() { const mockMeteor = { isServer: true }; const mockAccountsPackage = { "accounts-base": { AccountsServer: true } }; + const SIDE = ServerLogger.side; const postProcessorDelete = data => { let result = Object.assign({}, data); - delete result.meteor.user.services; + delete result.server.user.services; return result; }; const forcedUserId = 42; const postProcessorUpdate = data => { let result = Object.assign({}, data); - result.meteor.user = forcedUserId; + result.server.user = forcedUserId; return result; }; test("should accept a collection name", () => { - const data = { anything: "goes" }; + const data = { + anything: "goes", + // Actual contexts always have a "source" top-level key, added by + // Logger.log() before invoking buildContext(). + "source": SIDE, + }; global.Package = mockAccountsPackage; const processorRaw = new MeteorUserProcessor(mockMeteor); const processorDeletor = new MeteorUserProcessor(mockMeteor, postProcessorDelete); @@ -27,15 +34,16 @@ function testMeteorUserProcessor() { expect(processorRaw).toBeInstanceOf(MeteorUserProcessor); const resultRaw = processorRaw.process(data); - expect(resultRaw.meteor.user.services).toBeDefined(); + expect(resultRaw).toHaveProperty(SIDE); + expect(resultRaw[SIDE].user.services).toBeDefined(); expect(processorDeletor).toBeInstanceOf(MeteorUserProcessor); const resultDeleted = processorDeletor.process(data); - expect(resultDeleted.meteor.user.services).toBeUndefined(); + expect(resultDeleted[SIDE].user.services).toBeUndefined(); expect(processorUpdater).toBeInstanceOf(MeteorUserProcessor); const resultUpdated = processorUpdater.process(data); - expect(resultUpdated.meteor.user).toBe(forcedUserId); + expect(resultUpdated[SIDE].user).toBe(forcedUserId); }); } diff --git a/__tests__/unit/mongodbSenderTest.ts b/__tests__/unit/mongodbSenderTest.ts index 2dd37d6..af6a000 100644 --- a/__tests__/unit/mongodbSenderTest.ts +++ b/__tests__/unit/mongodbSenderTest.ts @@ -1,5 +1,6 @@ const sinon = require("sinon"); +import Logger from "../../src/Logger"; import MongoDbSender from "../../src/Senders/MongodbSender"; function testMongoDbSender() { @@ -26,7 +27,7 @@ function testMongoDbSender() { const collection = 25; expect(() => new MongoDbSender(mongo, collection)).toThrowError(Error); }); - test("should add a \"store\" timestamp to empty context", () => { + test("should add a \"send\" timestamp to empty context", () => { const collection = new mongo.Collection("fake"); const sender = new MongoDbSender(mongo, collection); const insertSpy = sinon.spy(sender.store, "insert"); @@ -44,7 +45,7 @@ function testMongoDbSender() { // Message is passed. expect(callArgs.message).toBe(inboundArgs[1]); - const timestamp = callArgs.context.timestamp.store; + const timestamp = callArgs.context[Logger.KEY_TS].server.send; // A numeric store timestamp is passed. expect(typeof timestamp).toBe("number"); // Timestamp is later than 'before'. @@ -52,7 +53,7 @@ function testMongoDbSender() { // Timestamp is earlier than 'after'. expect(timestamp <= after).toBe(true); }); - test("should add a \"store\" timestamp to non-empty context", () => { + test("should add a \"send\" timestamp to non-empty context", () => { const collection = new mongo.Collection("fake"); const sender = new MongoDbSender(mongo, collection); const insertSpy = sinon.spy(sender.store, "insert"); @@ -68,7 +69,7 @@ function testMongoDbSender() { // Message is passed. expect(callArgs.message).toBe(inboundArgs[1]); - const timestamp = callArgs.context.timestamp.store; + const timestamp = callArgs.context[Logger.KEY_TS].server.send; // A numeric store timestamp is passed. expect(typeof timestamp).toBe("number"); // Timestamp is later than 'before'. diff --git a/__tests__/unit/serverLoggerTest.ts b/__tests__/unit/serverLoggerTest.ts index 4c19939..59d491c 100644 --- a/__tests__/unit/serverLoggerTest.ts +++ b/__tests__/unit/serverLoggerTest.ts @@ -4,29 +4,48 @@ const sinon = require("sinon"); import NullFn from "../../src/NullFn"; +import Logger from "../../src/Logger"; +import LogLevel from "../../src/LogLevel"; +import ProcessorBase from "../../src/Processors/ProcessorBase"; import ServerLogger from "../../src/ServerLogger"; +const emptyStrategy = () => ({ + customizeLogger: () => [], + selectSenders: () => [], +}); + +const LOG_SOURCE = "test"; +const MAGIC = "xyzzy"; + +let result; +const TestSender = new class { + send(level, message, context) { + result = { level, message, context }; + } +}(); +const logStrategy = { ...emptyStrategy(), selectSenders: () => [TestSender] }; + const testConstructor = () => { - const strategy = { - customizeLogger: () => [], - selectSenders: () => [], - }; global.Meteor = { methods: NullFn }; test("Should provide default parameters", () => { - const logger = new ServerLogger(strategy); - // logRequestHeaders correctly defaulted. + const logger = new ServerLogger(emptyStrategy()); + expect(logger.enableMethod).toBe(true); expect(logger.logRequestHeaders).toBe(true); - // servePath correctly defaulted. - expect(logger.servePath).toBe("/logger"); - // maxReqListeners correctly defaulted. expect(logger.maxReqListeners).toBe(11); + expect(logger.servePath).toBe("/logger"); }); test("Should not add unknown parameters", () => { - const logger = new ServerLogger(strategy, null, { foo: "bar" }); - // Unknown argument foo is not set on instance. + // Extra property on values prototype and on object itself. + Object.prototype[MAGIC] = NullFn; + const values = { foo: "bar" }; + + const logger = new ServerLogger(emptyStrategy(), null, values); + delete Object.prototype[MAGIC]; + // Unknown argument from object and prototype are not set on instance. expect(typeof logger.foo).toBe("undefined"); + expect(typeof logger[MAGIC]).toBe("undefined"); }); test("Should not overwrite passed parameters", () => { @@ -35,7 +54,7 @@ const testConstructor = () => { servePath: 42, maxReqListeners: 30, }; - const logger = new ServerLogger(strategy, null, options); + const logger = new ServerLogger(emptyStrategy(), null, options); for (const k of Object.keys(options)) { expect(logger[k]).toBe(options[k]); @@ -51,11 +70,6 @@ const testConnect = () => { }, }; - const strategy = { - customizeLogger: () => [], - selectSenders: () => [], - }; - afterEach(() => { connectSpy.resetHistory(); }); @@ -65,33 +79,154 @@ const testConnect = () => { }); test("Should only register with connect when WebApp is passed", () => { - const loggerHappy = new ServerLogger(strategy, mockWebApp, {}); + const loggerHappy = new ServerLogger(emptyStrategy(), mockWebApp, {}); expect(loggerHappy.constructor.name).toBe("ServerLogger"); expect(connectSpy.calledOnce).toBe(true); connectSpy.resetHistory(); - const loggerSad = new ServerLogger(strategy, null, {}); + const loggerSad = new ServerLogger(emptyStrategy(), null, {}); expect(loggerSad.constructor.name).toBe("ServerLogger"); expect(connectSpy.calledOnce).toBe(true); }); test("Should register with connect with default path", () => { - const logger = new ServerLogger(strategy, mockWebApp, {}); + const logger = new ServerLogger(emptyStrategy(), mockWebApp, {}); expect(logger.constructor.name).toBe("ServerLogger"); expect(connectSpy.alwaysCalledWith(mockWebApp, "/logger")).toBe(true); }); test("Should register with connect with chosen path", () => { const servePath = "/eightfold"; - const logger = new ServerLogger(strategy, mockWebApp, { servePath }); + const logger = new ServerLogger(emptyStrategy(), mockWebApp, { servePath }); expect(logger.constructor.name).toBe("ServerLogger"); expect(connectSpy.calledOnce).toBe(true); expect(connectSpy.alwaysCalledWith(mockWebApp, servePath)).toBe(true); }); }; +const testBuildContext = () => { + test("Should apply argument source over context", () => { + const logger = new ServerLogger(emptyStrategy()); + const actual = logger.buildContext({}, LOG_SOURCE, { [Logger.KEY_SOURCE]: "other" }); + expect(actual).toHaveProperty(Logger.KEY_SOURCE, LOG_SOURCE); + }); + + test("Should merge details, applying argument over context", () => { + const logger = new ServerLogger(emptyStrategy()); + const argumentDetails = { a: "A", d: "D1" }; + const initialContext = { [Logger.KEY_DETAILS]: { b: "B", d: "D2" } }; + const actual = logger.buildContext(argumentDetails, LOG_SOURCE, initialContext); + const expected = { + // Argument detail overwrites context detail. + [Logger.KEY_DETAILS]: { a: "A", b: "B", d: "D1" }, + }; + expect(actual).toMatchObject(expected); + }); + + test("Should merge context", () => { + const logger = new ServerLogger(emptyStrategy()); + const initialContext = { foo: "bar" }; + const actual = logger.buildContext({}, LOG_SOURCE, initialContext); + expect(actual).toMatchObject({ [LOG_SOURCE]: initialContext }); + }); +}; + +const testLogExtended = () => { + let buildContextSpy; + + beforeAll(() => { + buildContextSpy = sinon.spy(ServerLogger.prototype, "buildContext"); + }); + + test("Should reject invalid log levels", () => { + const logger = new ServerLogger(emptyStrategy()); + expect(() => { + logger.logExtended(-1, "message", {}, {}, LOG_SOURCE); + }).toThrow(); + }); + + test("Should build context", () => { + const logger = new ServerLogger(emptyStrategy()); + logger.logExtended(LogLevel.INFO, "message", {}, {}, LOG_SOURCE); + expect(buildContextSpy.calledOnce).toBe(true); + }); + + test("Should timestamp context", () => { + const logger = new ServerLogger(logStrategy); + const t0 = + new Date(); + logger.logExtended(LogLevel.INFO, "message", {}, {}, LOG_SOURCE); + const t1 = + new Date(); + expect(result).toHaveProperty('context.timestamp.server.log'); + const actual = result.context.timestamp.server.log; + expect(actual).toBeGreaterThanOrEqual(t0); + expect(actual).toBeLessThanOrEqual(t1); + }); + + test("Should factor source timestamp", () => { + const logger = new ServerLogger(logStrategy); + const t0 = + new Date(); + const clientTsKey = 'whatever'; + const sourceContext = { + [Logger.KEY_TS]: { + [LOG_SOURCE]: { + [clientTsKey]: t0, + }, + }, + }; + logger.logExtended(LogLevel.INFO, "message", {}, sourceContext, LOG_SOURCE); + expect(result).not.toHaveProperty(`context.${LOG_SOURCE}.${Logger.KEY_TS}`); + expect(result).toHaveProperty(`context.${Logger.KEY_TS}`); + expect(result).toHaveProperty(`context.${Logger.KEY_TS}.${LOG_SOURCE}`); + expect(result).toHaveProperty(`context.${Logger.KEY_TS}.${LOG_SOURCE}.${clientTsKey}`); + const actual = result.context[Logger.KEY_TS][LOG_SOURCE][clientTsKey]; + expect(actual).toBe(t0); + }); + + test("Should apply processors", () => { + const logger = new ServerLogger(logStrategy, null, { foo: "bar" }); + const P1 = class extends ProcessorBase { + process(context) { + return { ...context, extra: "p1", p1: "p1" }; + } + }; + const P2 = class extends ProcessorBase { + process(context) { + return { ...context, extra: "p2", p2: "p2" }; + } + }; + logger.processors.push(new P1()); + logger.processors.push(new P2()); + const initialContext = { + // Should remain. + c: "C", + // Should be overwritten twice. + extra: "initial", + }; + logger.logExtended(LogLevel.INFO, "message", { "some": "detail" }, initialContext, LOG_SOURCE); + // Expected: { + // message_details: (..not tested here..), + // source: "test", + // test: { c: "C", extra: "initial" }, + // server: { p1: "p1", p2: "p2", extra: "p2" } + // timestamp: (..not tested here..) + // } + expect(result).toHaveProperty('context'); + const actual = result.context; + expect(actual).not.toHaveProperty("c"); + expect(actual).not.toHaveProperty("p1"); + expect(actual).not.toHaveProperty("p2"); + expect(actual).not.toHaveProperty("extra"); + expect(actual).toHaveProperty(LOG_SOURCE, initialContext); + expect(actual).toHaveProperty(ServerLogger.side); + expect(actual[ServerLogger.side]).toHaveProperty("p1", "p1"); + expect(actual[ServerLogger.side]).toHaveProperty("p2", "p2"); + expect(actual[ServerLogger.side]).toHaveProperty("extra", "p2"); + }); +}; export { + testBuildContext, testConnect, testConstructor, + testLogExtended, }; diff --git a/__tests__/unit/stringifyTest.ts b/__tests__/unit/stringifyTest.ts index 3ab003d..229d62c 100644 --- a/__tests__/unit/stringifyTest.ts +++ b/__tests__/unit/stringifyTest.ts @@ -22,6 +22,7 @@ function testStringifyMessage() { [{ message: "foo" }, "foo"], [{ message: 25 }, "25"], [{ message: o }, JSON.stringify(value)], + [{ message: { toString: () => "Hello" } }, "Hello"], [{}, "{}"], [[], "[]"], ["foo", "foo"], diff --git a/__tests__/unit/test.ts b/__tests__/unit/test.ts index 99127eb..4a0d8b5 100644 --- a/__tests__/unit/test.ts +++ b/__tests__/unit/test.ts @@ -4,13 +4,14 @@ import { testObjectifyContext, testProcessors, } from "./logContextTest"; +import { testConsoleSender } from "./consoleSenderTest"; import { testLogLevelNames, testLogLevels } from "./logLevelsTest"; import { testMeteorUserProcessor } from "./meteorUserProcessorTest"; import { testMongoDbSender } from "./mongodbSenderTest"; import { testSerializeDeepObject } from "./serializationTest"; import { testStrategyConstruction } from "./strategyTest"; import { testStringifyMessage } from "./stringifyTest"; -import { testConnect, testConstructor } from "./serverLoggerTest"; +import { testBuildContext, testConnect, testConstructor, testLogExtended } from "./serverLoggerTest"; import { testBrowserProcessor } from "./browserProcessorTest"; describe("Unit", () => { @@ -25,13 +26,18 @@ describe("Unit", () => { }); describe("ServerLogger", () => { describe("constructor", testConstructor); + describe("buildContext", testBuildContext); describe("messageContext", testMessageContext); describe("objectifyContext", testObjectifyContext); + describe("logExtended", testLogExtended); describe("setUpConnect", testConnect); describe("stringifyMessage", testStringifyMessage); describe("serializeDeepObject", testSerializeDeepObject); }); - describe("MongoDbSender", testMongoDbSender); + describe("Senders", () => { + describe("ConsoleSender", testConsoleSender); + describe("MongoDbSender", testMongoDbSender); + }); describe("MeteorUserProcessor", testMeteorUserProcessor); describe("BrowserProcessor", testBrowserProcessor); }); diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..8d41634 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,19 @@ +coverage: + notify: + slack: + default: + branches: null + flags: null + only_pulls: false + paths: null + threshold: 0.0 + url: https://hooks.slack.com/services/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 + parsers: + javascript: + enable_partials: true + precision: 1 + range: 70..95 + round: down + +ignore: + - "^test/" # ignore folders and all its contents diff --git a/lib/ClientLogger.d.ts b/lib/ClientLogger.d.ts index 8be1e3c..ac71cd0 100644 --- a/lib/ClientLogger.d.ts +++ b/lib/ClientLogger.d.ts @@ -14,8 +14,13 @@ declare const ClientLogger: { processors: import("./Processors/IProcessor").IProcessor[]; tk: any; strategy: import("./Strategies/IStrategy").IStrategy; + applyProcessors(rawContext: any): any; + doProcess(apply: any, contextToProcess: any): any; + processorReducer(accu: any, current: any): any; report(e: Error): void; reportSubscriber(e: Error): void; + send(strategy: any, level: any, message: any, sentContext: any): void; + validateLevel(requestedLevel: any): void; arm(): void; disarm(delay?: number): void; _meteorLog(): void; diff --git a/lib/Logger.d.ts b/lib/Logger.d.ts index 93404db..4c07283 100644 --- a/lib/Logger.d.ts +++ b/lib/Logger.d.ts @@ -9,6 +9,24 @@ declare const Logger: { processors: IProcessor[]; tk: any; strategy: IStrategy; + applyProcessors(rawContext: any): any; + doProcess(apply: any, contextToProcess: any): any; + /** + * Reduce callback for processors. + * + * @private + * @see Logger.log() + * + * @param {Object} accu + * The reduction accumulator. + * @param {ProcessorBase} current + * The current process to apply in the reduction. + * + * @returns {Object} + * The result of the current reduction step. + * + */ + processorReducer(accu: any, current: any): any; /** * The callback invoked by TraceKit * @@ -25,6 +43,36 @@ declare const Logger: { * @see Logger#arm */ reportSubscriber(e: Error): void; + /** + * Actually send a message with a processed context using a strategy. + * + * @see Logger.log() + * @private + * + * @param {StrategyBase} strategy + * The sending strategy. + * @param {number} level + * An RFC5424 level. + * @param {string} message + * The message template. + * @param {object} sentContext + * A message context, possibly including a message_details key to separate + * data passed to the log() call from data added by processors. + * + * @returns {void} + */ + send(strategy: any, level: any, message: any, sentContext: any): void; + /** + * Ensure a log level is in the allowed value set. + * + * @see Logger.log() + * + * @param {*} requestedLevel + * A possibly invalid severity level. + * + * @returns {void} + */ + validateLevel(requestedLevel: any): void; /** * Arm the report subscriber. * diff --git a/lib/Logger.js b/lib/Logger.js index 43e1d90..ab43c93 100644 --- a/lib/Logger.js +++ b/lib/Logger.js @@ -78,6 +78,75 @@ var Logger = (_a = /** @class */ (function () { } return LogLevel.Names[numericLevel]; }; + class_1.prototype.applyProcessors = function (rawContext) { + var _a; + // Context may contain message_details and timestamps from upstream. Merge them. + var DETAILS_KEY = "message_details"; + var TS_KEY = "timestamp"; + var HOST_KEY = "hostname"; + var context1 = rawContext; + if (!rawContext[DETAILS_KEY]) { + context1 = (_a = {}, _a[DETAILS_KEY] = rawContext, _a); + } + var _b = DETAILS_KEY, initialDetails = rawContext[_b], _c = TS_KEY, initialTs = rawContext[_c], _d = HOST_KEY, initialHost = rawContext[_d], contextWithoutDetails = __rest(rawContext, [typeof _b === "symbol" ? _b : _b + "", typeof _c === "symbol" ? _c : _c + "", typeof _d === "symbol" ? _d : _d + ""]); + var processedContext = this.processors.reduce(this.processorReducer, context1); + var processedDetails = __assign({}, initialDetails, processedContext[DETAILS_KEY]); + if (Object.keys(processedDetails).length > 0) { + processedContext[DETAILS_KEY] = processedDetails; + } + processedContext[TS_KEY] = __assign({}, initialTs, processedContext[TS_KEY]); + // Only add the initial [HOST_KEY] if none has been added and one existed. + if (typeof processedContext[HOST_KEY] === "undefined" && typeof initialHost !== "undefined") { + processedContext[HOST_KEY] = initialHost; + } + // // Set aside reserved keys to allow restoring them after processing. + // const { + // [DETAILS_KEY]: initialDetails, + // [TS_KEY]: initialTs, + // [HOST_KEY]: initialHost, + // ...contextWithoutDetails + // } = rawContext; + // + // const processedContext = this.processors.reduce(processorReducer, { [DETAILS_KEY]: contextWithoutDetails }); + // + // // New context details keys, if any, with the same name override existing ones. + // const details = { ...initialDetails, ...processedContext[DETAILS_KEY] }; + // if (Object.keys(details).length > 0) { + // processedContext[DETAILS_KEY] = details; + // } + // processedContext[TS_KEY] = { ...initialTs, ...processedContext[TS_KEY] }; + // + // // Only add the initial [HOST_KEY] if none has been added and one existed. + // if (typeof processedContext[HOST_KEY] === "undefined" && typeof initialHost !== "undefined") { + // processedContext[HOST_KEY] = initialHost; + // } + return processedContext; + }; + class_1.prototype.doProcess = function (apply, contextToProcess) { + var finalContext = apply ? this.applyProcessors(contextToProcess) : contextToProcess; + // A timestamp is required, so insert it forcefully. + finalContext.timestamp = { log: Date.now() }; + return finalContext; + }; + /** + * Reduce callback for processors. + * + * @private + * @see Logger.log() + * + * @param {Object} accu + * The reduction accumulator. + * @param {ProcessorBase} current + * The current process to apply in the reduction. + * + * @returns {Object} + * The result of the current reduction step. + * + */ + class_1.prototype.processorReducer = function (accu, current) { + var result = current.process(accu); + return result; + }; /** * The callback invoked by TraceKit * @@ -98,6 +167,45 @@ var Logger = (_a = /** @class */ (function () { class_1.prototype.reportSubscriber = function (e) { this.log(LogLevel.ERROR, e.message, e); }; + /** + * Actually send a message with a processed context using a strategy. + * + * @see Logger.log() + * @private + * + * @param {StrategyBase} strategy + * The sending strategy. + * @param {number} level + * An RFC5424 level. + * @param {string} message + * The message template. + * @param {object} sentContext + * A message context, possibly including a message_details key to separate + * data passed to the log() call from data added by processors. + * + * @returns {void} + */ + class_1.prototype.send = function (strategy, level, message, sentContext) { + var senders = strategy.selectSenders(level, message, sentContext); + senders.forEach(function (sender) { + sender.send(level, message, sentContext); + }); + }; + /** + * Ensure a log level is in the allowed value set. + * + * @see Logger.log() + * + * @param {*} requestedLevel + * A possibly invalid severity level. + * + * @returns {void} + */ + class_1.prototype.validateLevel = function (requestedLevel) { + if (!Number.isInteger(requestedLevel) || +requestedLevel < LogLevel.EMERGENCY || +requestedLevel > LogLevel.DEBUG) { + throw new InvalidArgumentException_1.default("The level argument to log() must be an RFC5424 level."); + } + }; /** * Arm the report subscriber. * @@ -141,58 +249,11 @@ var Logger = (_a = /** @class */ (function () { class_1.prototype._meteorLog = function () { return; }; /** @inheritDoc */ class_1.prototype.log = function (level, message, initialContext, process) { - var _this = this; if (initialContext === void 0) { initialContext = {}; } if (process === void 0) { process = true; } - /** - * Reduce callback for processors. - * - * @see Logger.log() - * - * @param accu - * The reduction accumulator. - * @param current - * The current process to apply in the reduction. - * - * @returns - * The result of the current reduction step. - */ - var processorReducer = function (accu, current) { - var result = current.process(accu); - return result; - }; - var applyProcessors = function (rawContext) { - // Context may contain message_details and timestamps from upstream. Merge them. - var _a; - var _b = DETAILS_KEY, initialDetails = rawContext[_b], _c = TS_KEY, initialTs = rawContext[_c], _d = HOST_KEY, initialHost = rawContext[_d], - // TS forbids trailing commas on rest parms, TSLint requires them... - // tslint:disable-next-line - contextWithoutDetails = __rest(rawContext, [typeof _b === "symbol" ? _b : _b + "", typeof _c === "symbol" ? _c : _c + "", typeof _d === "symbol" ? _d : _d + ""]); - var processedContext = _this.processors.reduce(processorReducer, (_a = {}, - _a[DETAILS_KEY] = contextWithoutDetails, - _a)); - // New context details keys, if any, with the same name override existing ones. - var details = __assign({}, initialDetails, processedContext[DETAILS_KEY]); - if (Object.keys(details).length > 0) { - processedContext[DETAILS_KEY] = details; - } - processedContext[TS_KEY] = __assign({}, initialTs, processedContext[TS_KEY]); - // Only add the initial [HOST_KEY] if none has been added and one existed. - if (typeof processedContext[HOST_KEY] === "undefined" && typeof initialHost !== "undefined") { - processedContext[HOST_KEY] = initialHost; - } - return processedContext; - }; - if (!Number.isInteger(level) || +level < LogLevel.EMERGENCY || +level > LogLevel.DEBUG) { - throw new InvalidArgumentException_1.default("The level argument to log() must be an RFC5424 level."); - } - var finalContext = process ? applyProcessors(initialContext) : initialContext; - // A timestamp is required, so insert it forcefully. - finalContext.timestamp = { log: Date.now() }; - var senders = this.strategy.selectSenders(level, String(message), finalContext); - senders.forEach(function (sender) { - sender.send(level, String(message), finalContext); - }); + this.validateLevel(level); + var finalContext = this.doProcess(process, details); + this.send(this.strategy, level, message, finalContext); }; /** @inheritDoc */ class_1.prototype.debug = function (message, context) { diff --git a/lib/Logger.js.map b/lib/Logger.js.map index 4c48f87..eec54cd 100644 --- a/lib/Logger.js.map +++ b/lib/Logger.js.map @@ -1 +1 @@ -{"version":3,"file":"Logger.js","sourceRoot":"","sources":["../src/Logger.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"Logger.js","sourceRoot":"","sources":["../src/Logger.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/ServerLogger.js b/lib/ServerLogger.js index 58da00f..06934e3 100644 --- a/lib/ServerLogger.js +++ b/lib/ServerLogger.js @@ -219,6 +219,7 @@ var ServerLogger = /** @class */ (function (_super) { ServerLogger.prototype.log = function (level, message, rawContext, cooked) { if (cooked === void 0) { cooked = true; } rawContext.hostname = this.hostname; + console.log([level, message, rawContext]); _super.prototype.log.call(this, level, message, rawContext, cooked); }; /** diff --git a/lib/ServerLogger.js.map b/lib/ServerLogger.js.map index f187220..3f8f55c 100644 --- a/lib/ServerLogger.js.map +++ b/lib/ServerLogger.js.map @@ -1 +1 @@ -{"version":3,"file":"ServerLogger.js","sourceRoot":"","sources":["../src/ServerLogger.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"ServerLogger.js","sourceRoot":"","sources":["../src/ServerLogger.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/package.json b/package.json index dbf1580..f894ad8 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,7 @@ "@types/meteor": "^1.4.19", "@types/sinon": "^5.0.2", "axios": "^0.18.0", - "eslint": "^4.19.1", - "eslint-plugin-react": "^7.7.0", + "codecov": "^3.0.4", "growl": "^1.9.2", "ink-docstrap": "^1.3.0", "install": "^0.11.0", diff --git a/src/ClientLogger.ts b/src/ClientLogger.ts index b96208a..f167687 100644 --- a/src/ClientLogger.ts +++ b/src/ClientLogger.ts @@ -4,6 +4,8 @@ import Logger from "./Logger"; +const SIDE = "client"; + /** * ClientLogger is the client-side implementation of Logger. * @@ -11,7 +13,16 @@ import Logger from "./Logger"; * extension-specialization point. * * @extends Logger + * + * @property {string} side */ -const ClientLogger = class extends Logger {}; +const ClientLogger = class extends Logger { + constructor(strategy) { + super(strategy); + this.side = SIDE; + } +}; + +ClientLogger.side = SIDE; export default ClientLogger; diff --git a/src/Logger.ts b/src/Logger.ts index 7d5ef92..545d36a 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -10,6 +10,8 @@ import {IStrategy} from "./Strategies/IStrategy"; // const logMethodNames = ["log", "debug", "info", "warn", "error", "_exception" ]; +const SIDE = "unknown"; + const DETAILS_KEY = "message_details"; const HOST_KEY = "hostname"; const TS_KEY = "timestamp"; @@ -24,6 +26,14 @@ interface ISendContext { /** * Logger is the base class for loggers. + * + * @property {string} side + * Which logger is this? Expected values: "client", "server", "cordova". + * @property {string} KEY_DETAILS + * @property {string} KEY_HOST + * @property {string} KEY_SOURCE + * @property {string} KEY_TS + * @property {string} METHOD */ const Logger = class implements ILogger { public static readonly METHOD = "filog:log"; @@ -56,45 +66,137 @@ const Logger = class implements ILogger { * * @param {StrategyBase} strategy * The sender selection strategy to apply. + * */ constructor(public strategy: IStrategy) { this.processors = []; + this.side = SIDE; this.tk = TraceKit; this.strategy.customizeLogger(this); } + /** + * Apply processors to a context, preserving reserved keys. + * + * @protected + * + * @param {Object} rawContext + * The context to process. + * + * @returns {Object} + * The processed context. + */ + applyProcessors(rawContext) { + const { + [Logger.KEY_TS]: initialTs, + [Logger.KEY_HOST]: initialHost, + } = rawContext; + + const processedContext = this.processors.reduce(this.processorReducer, rawContext); + + // Timestamp is protected against modifications, for traceability. + processedContext[Logger.KEY_TS] = { ...initialTs, ...processedContext[Logger.KEY_TS] }; + + // Only add the initial [Logger.KEY_HOST] if none has been added and one existed. + if (typeof processedContext[Logger.KEY_HOST] === "undefined" && typeof initialHost !== "undefined") { + processedContext[Logger.KEY_HOST] = initialHost; + } + + return processedContext; + } + + /** + * Arm the report subscriber. + * + * @returns {void} + * + * @see Logger#reportSubscriber + */ + public arm() { + this.tk.report.subscribe(this.reportSubscriber.bind(this)); + } + + doProcess(apply, contextToProcess) { + const finalContext = apply ? this.applyProcessors(contextToProcess) : contextToProcess; + + // A timestamp is required, so insert it forcefully. + finalContext.timestamp = { log: Date.now() }; + return finalContext; + } + + /** + * Reduce callback for processors. + * + * @private + * @see Logger.log() + * + * @param {Object} accu + * The reduction accumulator. + * @param {ProcessorBase} current + * The current process to apply in the reduction. + * + * @returns {Object} + * The result of the current reduction step. + * + */ + processorReducer(accu, current) { + const result = current.process(accu); + return result; + } + /** * The callback invoked by TraceKit * - * @param e + * @param {Error} e * Error on which to report. */ - public report(e: Error): void { + public report(e): void { this.tk.report(e); } /** * Error-catching callback when the logger is arm()-ed. * - * @param e + * @param {Error} e * The error condition to log. * * @see Logger#arm */ - public reportSubscriber(e: Error): void { + public reportSubscriber(e): void { this.log(LogLevel.ERROR, e.message, e); } /** - * Arm the report subscriber. + * Build a context object from log() details. * - * @returns {void} + * @protected * - * @see Logger#reportSubscriber + * @see Logger.log + * + * @param {Object} details + * The message details passed to log(). + * @param {string} source + * The source for the event. + * @param {Object} context + * Optional: a pre-existing context. + * + * @returns {Object} + * The context with details moved to the message_details subkey. */ - public arm() { - this.tk.report.subscribe(this.reportSubscriber.bind(this)); + buildContext(details, source, context = {}) { + const context1 = { + ...context, + [Logger.KEY_DETAILS]: details, + [Logger.KEY_SOURCE]: source, + }; + + if (details[Logger.KEY_HOST]) { + context1[Logger.KEY_HOST] = details[Logger.KEY_HOST]; + delete context1[Logger.KEY_DETAILS][Logger.KEY_HOST]; + } + + return context1; } /** @@ -114,19 +216,10 @@ const Logger = class implements ILogger { }, delay); } - /** - * Implements the standard Meteor logger methods. - * - * This method is an implementation detail: do not depend on it. - * - * @param {String} level - * debug, info, warn, or error - * - * @returns {void} - * - * @todo (or not ?) merge in the funky Meteor logic from the logging package. - */ - public _meteorLog() { return; } + /** @inheritDoc */ + public error(message: object|string, context: object = {}): void { + this.log(LogLevel.ERROR, message, context); + } /** @inheritDoc */ public log( @@ -135,68 +228,16 @@ const Logger = class implements ILogger { initialContext: object = {}, process: boolean = true, ): void { - /** - * Reduce callback for processors. - * - * @see Logger.log() - * - * @param accu - * The reduction accumulator. - * @param current - * The current process to apply in the reduction. - * - * @returns - * The result of the current reduction step. - */ - const processorReducer = (accu: object, current: IProcessor): object => { - const result = current.process(accu); - return result; - }; - - const applyProcessors = (rawContext: ISendContext): {} => { - // Context may contain message_details and timestamps from upstream. Merge them. - - const { - [DETAILS_KEY]: initialDetails, - [TS_KEY]: initialTs, - [HOST_KEY]: initialHost, - // TS forbids trailing commas on rest parms, TSLint requires them... - // tslint:disable-next-line - ...contextWithoutDetails - } = rawContext; - - const processedContext: ISendContext = this.processors.reduce(processorReducer, { - [DETAILS_KEY]: contextWithoutDetails, - }); - - // New context details keys, if any, with the same name override existing ones. - const details = { ...initialDetails, ...processedContext[DETAILS_KEY] }; - if (Object.keys(details).length > 0) { - processedContext[DETAILS_KEY] = details; - } - processedContext[TS_KEY] = { ...initialTs, ...processedContext[TS_KEY] }; - - // Only add the initial [HOST_KEY] if none has been added and one existed. - if (typeof processedContext[HOST_KEY] === "undefined" && typeof initialHost !== "undefined") { - processedContext[HOST_KEY] = initialHost; - } - - return processedContext; - }; - - if (!Number.isInteger(level) || +level < LogLevel.EMERGENCY || +level > LogLevel.DEBUG) { - throw new InvalidArgumentException("The level argument to log() must be an RFC5424 level."); - } + this.validateLevel(level); + const context1 = this.buildContext(details, this.side); - const finalContext: ISendContext = process ? applyProcessors(initialContext) : initialContext; + const context2 = process + ? this.applyProcessors(context1) + : context1; - // A timestamp is required, so insert it forcefully. - finalContext.timestamp = { log: Date.now() }; + this.stamp(context2, "log"); - const senders = this.strategy.selectSenders(level, String(message), finalContext); - senders.forEach((sender) => { - sender.send(level, String(message), finalContext); - }); + this.send(this.strategy, level, message, context2); } /** @inheritDoc */ @@ -209,15 +250,48 @@ const Logger = class implements ILogger { this.log(LogLevel.INFORMATIONAL, message, context); } + /** + * Ensure a log level is in the allowed value set. + * + * @see Logger.log() + * + * @param {Number} requestedLevel + * A RFC5424 level. + * + * @returns {void} + * + * @throws InvalidArgumentException + * As per PSR-3, if level is not a valid RFC5424 level. + */ + validateLevel(requestedLevel) { + if (!Number.isInteger(requestedLevel) || +requestedLevel < LogLevel.EMERGENCY || +requestedLevel > LogLevel.DEBUG) { + throw new InvalidArgumentException("The level argument to log() must be an RFC5424 level."); + } + } + /** @inheritDoc */ public warn(message: object|string, context: object = {}): void { this.log(LogLevel.WARNING, message, context); } - /** @inheritDoc */ - public error(message: object|string, context: object = {}): void { - this.log(LogLevel.ERROR, message, context); - } + /** + * Implements the standard Meteor logger methods. + * + * This method is an implementation detail: do not depend on it. + * + * @param {String} level + * debug, info, warn, or error + * + * @todo (or not ?) merge in the funky Meteor logic from the logging package. + */ + public _meteorLog(): void { return; } }; +Logger.KEY_DETAILS = "message_details"; +Logger.KEY_HOST = "hostname"; +Logger.KEY_SOURCE = "source"; +Logger.KEY_TS = "timestamp"; +Logger.METHOD = "filog:log"; +Logger.side = SIDE; + export default Logger; diff --git a/src/Processors/BrowserProcessor.ts b/src/Processors/BrowserProcessor.ts index df83215..1806cf1 100644 --- a/src/Processors/BrowserProcessor.ts +++ b/src/Processors/BrowserProcessor.ts @@ -55,7 +55,6 @@ const BrowserProcessor = class extends ProcessorBase implements IProcessor { constructor(nav: INavigator, win: IWindow) { super(); const actualNav = nav || (typeof navigator === "object" && navigator); - const actualWin = win || (typeof window === "object" && window); if (typeof actualNav !== "object" || typeof actualWin !== "object" || !actualNav || !actualWin) { @@ -86,7 +85,7 @@ const BrowserProcessor = class extends ProcessorBase implements IProcessor { // Overwrite existing browser keys in context, keeping non-overwritten ones. for (const key in browserDefaults) { if (browserDefaults.hasOwnProperty(key)) { - result.browser[key as keyof IBrowserInfo] = this.navigator[key] || browserDefaults[key as keyof IBrowserInfo]; + result.browser[key as keyof IBrowserInfo] = this.navigator[key] ? this.navigator[key] : browserDefaults[key as keyof IBrowserInfo]; } } diff --git a/src/Processors/MeteorUserProcessor.ts b/src/Processors/MeteorUserProcessor.ts index c881b76..e24f229 100644 --- a/src/Processors/MeteorUserProcessor.ts +++ b/src/Processors/MeteorUserProcessor.ts @@ -177,13 +177,14 @@ const MeteorUserProcessor = class extends ProcessorBase implements IProcessor { delete user.services.resume; } - // Overwrite any previous userId information in context. - let result: {} = Object.assign({}, context, { - meteor: { - platform: this.platform, - user, - }, - }); + // Overwrite any previous userId information in context. Unlike client or + // mobile information, a straight server-side log context is not rebuilt by + // a call to logExtended, so it needs to be set directly in place under a + // platform key. + const userContext = (context[Logger.KEY_SOURCE] === this.platform) + ? { [this.platform]: { user } } + : { user }; + let result = Object.assign({}, context, userContext); if (this.postProcess) { result = this.postProcess(result); diff --git a/src/Senders/ConsoleSender.ts b/src/Senders/ConsoleSender.ts index 82a92f5..06e1695 100644 --- a/src/Senders/ConsoleSender.ts +++ b/src/Senders/ConsoleSender.ts @@ -17,9 +17,17 @@ import SenderBase from "./SenderBase"; const ConsoleSender = class extends SenderBase { constructor() { super(); - if (!console) { + if (typeof console === "undefined" || console === null || typeof console !== "object") { throw new Error("Console sender needs a console object."); } + ["log", "info", "warn", "error"].forEach((method) => { + if (typeof console[method] === "undefined") { + throw new Error(`Console is missing method ${method}.`); + } + if (console[method].constructor.name !== "Function") { + throw new Error(`Console property method ${method} is not a function.`); + } + }); } /** @inheritDoc */ diff --git a/src/Senders/MongodbSender.ts b/src/Senders/MongodbSender.ts index 5f7012b..4db016b 100644 --- a/src/Senders/MongodbSender.ts +++ b/src/Senders/MongodbSender.ts @@ -3,6 +3,7 @@ */ import {Mongo} from "meteor/mongo"; import SenderBase from "./SenderBase"; +import ServerLogger from "../ServerLogger"; /** * MongodbSender sends logs to the Meteor standard database. @@ -41,12 +42,16 @@ const MongodbSender = class extends SenderBase { const defaultedContext: ISendingContext = { ...context, timestamp: {} }; const doc = { level, message, context: {} as ISendingContext }; - // It should contain a timestamp object if it comes from ClientLogger. - if (typeof defaultedContext.timestamp === "undefined") { - defaultedContext.timestamp = {}; + // It should contain a timestamp.{side} object if it comes from any Logger. + if (typeof defaultedContext[Logger.KEY_TS] === "undefined") { + defaultedContext[Logger.KEY_TS] = { + server: {}, + }; } doc.context = defaultedContext; - doc.context.timestamp.store = Date.now(); + + // doc.context.timestamp.server is known to exist from above. + Logger.prototype.stamp.call({ side: ServerLogger.side }, doc.context, "send"); this.store.insert(doc); } }; diff --git a/src/Senders/SyslogSender.ts b/src/Senders/SyslogSender.ts index e73d154..58cfe7f 100644 --- a/src/Senders/SyslogSender.ts +++ b/src/Senders/SyslogSender.ts @@ -6,6 +6,7 @@ import * as path from "path"; import * as util from "util"; import * as LogLevel from "../LogLevel"; import SenderBase from "./SenderBase"; +import ServerLogger from "../ServerLogger"; type Serializer = (doc: object) => string; @@ -92,11 +93,18 @@ const SyslogSender = class extends SenderBase { message, }; - // It should already contain a timestamp object anyway. if (typeof context !== "undefined") { doc.context = context; } + // It should contain a timestamp.{side} object if it comes from any Logger. + if (typeof doc.context[Logger.KEY_TS] === "undefined") { + doc.context[Logger.KEY_TS] = { + server: {}, + }; + } + // doc.context.timestamp.server is known to exist from above. + Logger.prototype.stamp.call({ side: ServerLogger.side }, doc.context, "send"); this.syslog.log(level, this.serialize(doc)); } diff --git a/src/ServerLogger.ts b/src/ServerLogger.ts index 5dff7d2..5456f92 100644 --- a/src/ServerLogger.ts +++ b/src/ServerLogger.ts @@ -3,6 +3,7 @@ */ import {IncomingMessage, ServerResponse} from "http"; import {WebApp} from "meteor/webapp"; +import ClientLogger from './ClientLogger'; import { hostname } from "os"; import process from "process"; import * as util from "util"; @@ -21,12 +22,18 @@ interface IServerLoggerConstructorParameters { servePath?: string; } +const SIDE = "server"; + /** * An extension of the base logger which accepts log input on a HTTP URL. * * Its main method is log(level, message, context). * * @see ServerLogger.log + * + * @extends Logger + * + * @property {string} side */ class ServerLogger extends Logger { /** @@ -136,12 +143,14 @@ class ServerLogger extends Logger { ) { super(strategy); this.output = process.stdout; + this.side = SIDE; const defaultParameters: IServerLoggerConstructorParameters = { enableMethod: true, logRequestHeaders: true, // Preserve the legacy Filog default, but allow configuration. maxReqListeners: 11, servePath: "/logger", + verbose: false, }; // Loop on defaults, not arguments, to avoid injecting any random junk. @@ -165,6 +174,46 @@ class ServerLogger extends Logger { this.setupConnect(webapp, this.servePath); } + /** + * Build a context object from log() details. + * + * @protected + * + * @see Logger.log + * + * @param {Object} details + * The message details passed to log(). + * @param {string} source + * The source for the event. + * @param {Object} context + * Optional: a pre-existing context. + * + * @returns {Object} + * The context with details moved to the message_details subkey. + */ + buildContext(details, source, context = {}) { + // Ignore source and host keys from caller context. + const { + [Logger.KEY_DETAILS]: sourceDetails, + [Logger.KEY_SOURCE]: ignoredSource, + [Logger.KEY_HOST]: ignoredHostName, + [Logger.KEY_TS]: sourceTs, + ...context1 + } = context; + + // In case of conflict, argument details overwrites caller details. + const mergedDetails = { ...sourceDetails, ...details }; + + const context2 = { + ...super.buildContext(mergedDetails, source), + [source]: context1, + [Logger.KEY_HOST]: this.hostname, + [Logger.KEY_TS]: sourceTs, + }; + + return context2; + } + /** * Handle a log message from the client. * @@ -210,7 +259,8 @@ class ServerLogger extends Logger { if (this.logRequestHeaders) { context.requestHeaders = req.headers; } - this.log(level, message, context, false); + const { [Logger.KEY_DETAILS]: details, ...nonDetails } = context; + this.logExtended(level, message, details, nonDetails, ClientLogger.side); res.statusCode = 200; result = ""; } catch (err) { @@ -232,6 +282,53 @@ class ServerLogger extends Logger { super.log(level, message, rawContext, cooked); } + /** + * Extended syntax for log() method. + * + * @private + * + * @param {number} level + * The event level. + * @param {string} message + * The event message. + * @param {Object} details + * The details submitted with the message: any additional data added to + * the message by the upstream (client/cordova) log() caller(). + * @param {Object} context + * The context added to the details by upstream processors. + * @param {string} source + * The upstream sender type. + * + * @returns {void} + * + * @throws InvalidArgumentException + */ + logExtended(level, message, details, context, source) { + this.validateLevel(level); + const context1 = this.buildContext(details, source, context); + const context2 = this.applyProcessors(context1); + const { + [Logger.KEY_DETAILS]: processedDetails, + [Logger.KEY_SOURCE]: processedSource, + [source]: processedSourceContext, + [Logger.KEY_HOST]: processedHost, + [Logger.KEY_TS]: processedTs, + ...serverContext + } = context2; + + const context3 = { + [Logger.KEY_DETAILS]: processedDetails, + [Logger.KEY_SOURCE]: processedSource, + [source]: processedSourceContext, + [Logger.KEY_HOST]: processedHost, + [Logger.KEY_TS]: processedTs, + [ServerLogger.side]: serverContext, + }; + this.stamp(context3, "log"); + + this.send(this.strategy, level, message, context3); + } + /** * The Meteor server method registered a ${Logger.METHOD}. * @@ -245,7 +342,7 @@ class ServerLogger extends Logger { * @returns {void} */ public logMethod({ level = LogLevel.INFO, message = "", context = {} }) { - this.log(level, message, context, true); + this.logExtended(level, message, {}, context, ClientLogger.side); } /** @@ -255,19 +352,24 @@ class ServerLogger extends Logger { * The Meteor webapp service (Connect wrapper). * @param servePath * The path on which to expose the server logger. Must NOT start by a "/". - * - * @returns {void} */ - public setupConnect(webapp: OptionalWebApp, servePath: string) { + public setupConnect(webapp: OptionalWebApp, servePath: string): void { this.webapp = webapp; if (this.webapp) { - this.output.write(`Serving logger on ${servePath}.\n`); - const app = this.webapp.connectHandlers; + if (this.verbose) { + this.output.write(`Serving logger on ${servePath}.\n`); + } + let app = this.webapp.connectHandlers; app.use(this.servePath, this.handleClientLogRequest.bind(this)); - } else { - this.output.write(`Not serving logger, path ${servePath}.\n`); + } + else { + if (this.verbose) { + this.output.write(`Not serving logger, path ${servePath}.\n`); + } } } } +ServerLogger.side = SIDE; + export default ServerLogger; diff --git a/src/Strategies/StrategyBase.js b/src/Strategies/StrategyBase.js new file mode 100644 index 0000000..2fda543 --- /dev/null +++ b/src/Strategies/StrategyBase.js @@ -0,0 +1,49 @@ +/** + * @fileOverview Base Strategy. + */ + +import NullSender from "../Senders/NullSender"; + +/** + * StrategyBase is an "abstract" strategy. + * + * Strategies customize the active Sender instances for a given log event. + * + * @see SenderBase + */ +const StrategyBase = class { + constructor(init = true) { + if (init) { + this.senders = [new NullSender()]; + } + } + + /** + * Select senders to use for a given log event. + * + * @param {int} level + * The log event level.. + * @param {string} message + * The log event string/template. + * @param {object} context + * The context of the log event. + * + * @returns {function[]} + * An array of senders to use for this event. + */ + selectSenders() { + return this.senders; + } + + /** + * This method may modify the logger methods, e.g. to do nothing on debug. + * + * @param {Logger} logger + * A logger service to customize. + * + * @returns {void} + */ + customizeLogger() {} +}; + +export default StrategyBase; diff --git a/test/unit/consoleSenderTest.js b/test/unit/consoleSenderTest.js new file mode 100644 index 0000000..630687a --- /dev/null +++ b/test/unit/consoleSenderTest.js @@ -0,0 +1,53 @@ +/** + * @fileOverview Test file for ConsoleSender. + */ + +/** global: console */ + +import ConsoleSender from "../../src/Senders/ConsoleSender"; +import NullFn from "../../src/NullFn"; + +let savedConsole; + +function testConsoleSender() { + beforeEach(() => { + savedConsole = console; + }); + + afterEach(() => { + console = savedConsole; + }); + + test("constructor accepts well-formed console object", () => { + const sender = new ConsoleSender(); + expect(sender).toBeInstanceOf(ConsoleSender); + }); + + test("constructor validates console object", () => { + const invalid = [ + null, + true, + 62, + "a", + [], + {}, + { log: NullFn, info: NullFn, warn: NullFn }, + { log: NullFn, info: NullFn, warn: NullFn, error: false }, + ]; + + invalid.forEach(c => { + console = c; + let sender = null; + expect(() => { + sender = new ConsoleSender(); + }).toThrow(); + expect(sender).toBe(null); + }); + + + }); +} + +export { + testConsoleSender +};