diff --git a/__tests__/unit/contextSourcingTest.ts b/__tests__/unit/contextSourcingTest.ts index 024239e..f52d564 100644 --- a/__tests__/unit/contextSourcingTest.ts +++ b/__tests__/unit/contextSourcingTest.ts @@ -1,7 +1,7 @@ import ClientLogger from "../../src/ClientLogger"; import { DETAILS_KEY, HOST_KEY, - IContext, SOURCE_KEY, TS_KEY, + IContext, ITimestamps, ITimestampsHash, SOURCE_KEY, TS_KEY, } from "../../src/IContext"; import {ILogger} from "../../src/ILogger"; import { Levels } from "../../src/LogLevel"; @@ -9,102 +9,198 @@ import ServerLogger from "../../src/ServerLogger"; // Unit tests run on NodeJS, so we have access to its packages. import { hostname } from "os"; +import {ISender} from "../../src/Senders/ISender"; +import {IStrategy} from "../../src/Strategies/IStrategy"; -let result; +type StrategyFactory = (sender?: ISender) => IStrategy; -const emptyStrategy = () => ({ +const newEmptyStrategy: StrategyFactory = () => ({ customizeLogger: () => [], selectSenders: () => [], }); -const TestSender = new class { +class TestSender { + public result: IContext = {}; + public send(level, message, context): void { - result = { level, message, context }; + this.result = { level, message, context }; } -}(); +} -const logStrategy = { - ...emptyStrategy(), - selectSenders: () => [TestSender], -}; +const newLogStrategy: StrategyFactory = (sender: TestSender) => ({ + ...newEmptyStrategy(), + selectSenders: () => [sender], +}); function testContextSourcing(): void { - function newClientLogger(): ILogger { - const cl: ILogger = new ClientLogger(logStrategy); + function newClientLogger(sender: ISender): ILogger { + const cl: ILogger = new ClientLogger(newLogStrategy(sender)); return cl; } - function newServerLogger(): ILogger { - const sl: ILogger = new ServerLogger(logStrategy); + function newServerLogger(sender: ISender): ServerLogger { + const sl: ServerLogger = new ServerLogger(newLogStrategy(sender)); return sl; } - test.only("Client logging, pure", () => { - const cl = newClientLogger(); + test("Client logging, pure, no processor", () => { + const sender: TestSender = new TestSender(); + const cl = newClientLogger(sender); // Constant strings are immutable. const message = "some message"; + const side = "client"; // Const objects are not immutable: catch details being overwritten. const details = { a: "A" }; const expectedDetails = { ...details }; - const t1 = +new Date(); + const t1: number = +new Date(); cl.log(Levels.WARNING, message, details); - const t2 = +new Date(); + const t2: number = +new Date(); const expected = { context: { [DETAILS_KEY]: details, - // HOST_KEY: not on client logger. - [SOURCE_KEY]: "client", + // HOST_KEY: not on client contexts. + [SOURCE_KEY]: side, // TS_KEY: Cannot test content just with toMatchObject. }, level: Levels.WARNING, message, }; expect(t2).toBeGreaterThanOrEqual(t1); + const result = sender.result; expect(typeof result).toBe("object"); + expect(result).not.toHaveProperty(HOST_KEY); expect(result).toMatchObject(expected); - expect(result).toHaveProperty(TS_KEY); - expect(typeof result[TS_KEY]).toBe("object"); - expect(result[TS_KEY]).toHaveProperty("log"); - expect(result[TS_KEY].log).toBeGreaterThanOrEqual(t1); - expect(result[TS_KEY].log).toBeLessThanOrEqual(t2); + + const actualContext = result.context; + // No side properties without processors. + expect(actualContext).not.toHaveProperty(side); + + expect(actualContext).toHaveProperty(TS_KEY); + const ts: ITimestampsHash = actualContext[TS_KEY]; + expect(typeof ts).toBe("object"); + expect(ts).toHaveProperty(side); + expect(ts[side]).toHaveProperty("log"); + expect(ts[side].log).toBeGreaterThanOrEqual(t1); + expect(ts[side].log).toBeLessThanOrEqual(t2); }); - test.only("Server logging, pure", () => { - const sl = newServerLogger(); + test("Server logging, pure, no processor", () => { + const sender: TestSender = new TestSender(); + const sl = newServerLogger(sender); // Constant strings are immutable. const message = "some message"; const host = hostname(); + const side = "server"; // Const objects are not immutable: catch details being overwritten. const details = { a: "A" }; const expectedDetails = { ...details }; - const t1 = +new Date(); + const t1: number = +new Date(); sl.log(Levels.WARNING, message, details); - const t2 = +new Date(); + const t2: number = +new Date(); + + const expected = { + context: { + [DETAILS_KEY]: expectedDetails, + [HOST_KEY]: host, + [SOURCE_KEY]: side, + // TS_KEY: Cannot test content just with toMatchObject. + }, + level: Levels.WARNING, + message, + }; + expect(t2).toBeGreaterThanOrEqual(t1); + const result = sender.result; + expect(typeof result).toBe("object"); + expect(result).toMatchObject(expected); + + const actualContext = result.context; + expect(actualContext).toHaveProperty(TS_KEY); + const ts: ITimestampsHash = actualContext[TS_KEY]; + expect(typeof ts).toBe("object"); + expect(ts).toHaveProperty(side); + expect(ts[side]).toHaveProperty("log"); + expect(ts[side].log).toBeGreaterThanOrEqual(t1); + expect(ts[side].log).toBeLessThanOrEqual(t2); + }); + + test("Server logging from client log, no processor", () => { + const sender: TestSender = new TestSender(); + const sl = newServerLogger(sender); + + // Constant strings are immutable. + const message = "some message"; + const host = hostname(); + + // Const objects are not immutable: catch details being overwritten. + const details = { a: "A" }; + const expectedDetails = { ...details }; + + const clientContext: IContext = { + processorObject: { foo: "bar" }, + processorScalar: "baz", + }; + const expectedClientContext = { ...clientContext }; + + const side: string = "client"; + + // Go back in time to ensure sequencing t1 < client log < client send < t2. + const t1: number = +new Date() - 3; + + const initialContext = { + [DETAILS_KEY]: expectedDetails, + // HOST_KEY: not on client contexts. + [TS_KEY]: { + [side]: { + log: t1 + 1, + send: t1 + 2, + } as ITimestamps, + // No "server" yet. + } as ITimestampsHash, + [SOURCE_KEY]: side, + [side]: clientContext, + }; + + sl.logExtended(Levels.WARNING, message, initialContext, "client"); + const t2: number = +new Date(); const expected = { context: { [DETAILS_KEY]: expectedDetails, [HOST_KEY]: host, - [SOURCE_KEY]: "server", + [SOURCE_KEY]: side, // TS_KEY: Cannot test content just with toMatchObject. + [side]: expectedClientContext, + // No "server" content without a server processor. }, level: Levels.WARNING, message, }; + expect(t2).toBeGreaterThanOrEqual(t1); + const result = sender.result; + expect(typeof result).toBe("object"); expect(result).toMatchObject(expected); - expect(result).toHaveProperty(TS_KEY); + + const actualContext = result.context; + // No side property without a processor. + expect(actualContext).not.toHaveProperty("server"); + + expect(actualContext).toHaveProperty(TS_KEY); + const ts: ITimestampsHash = actualContext[TS_KEY]; expect(typeof result[TS_KEY]).toBe("object"); - expect(result[TS_KEY]).toHaveProperty("log"); - expect(result[TS_KEY].log).toBeGreaterThanOrEqual(t1); - expect(result[TS_KEY].log).toBeLessThanOrEqual(t2); + expect(typeof ts).toBe("object"); + expect(ts).toHaveProperty(side); + expect(ts).toHaveProperty("server"); + expect(ts.server).toHaveProperty("log"); + expect(ts.server.log).toBeGreaterThanOrEqual(t1); + expect(ts.server.log).toBeLessThanOrEqual(t2); }); } diff --git a/src/IContext.ts b/src/IContext.ts index 84754ab..299c242 100644 --- a/src/IContext.ts +++ b/src/IContext.ts @@ -13,7 +13,7 @@ interface ITimestamps { } interface ITimestampsHash { - [key: string]: ITimestamps; + [side: string]: ITimestamps; } interface IContext { @@ -31,5 +31,6 @@ export { TS_KEY, IDetails, IContext, + ITimestamps, ITimestampsHash, }; diff --git a/src/ServerLogger.ts b/src/ServerLogger.ts index 203981a..2e23ea8 100644 --- a/src/ServerLogger.ts +++ b/src/ServerLogger.ts @@ -265,9 +265,6 @@ class ServerLogger extends Logger implements ILogger { * The event level. * @param message * The event message. - * @param details - * The details submitted with the message: any additional data added to - * the message by the upstream (client/cordova) log() caller(). * @param context * The context added to the details by upstream processors. * @param source @@ -275,9 +272,9 @@ class ServerLogger extends Logger implements ILogger { * * @throws InvalidArgumentException */ - public logExtended(level: LogLevel.Levels, message: string, details: {}, context: IContext, source: string): void { + public logExtended(level: LogLevel.Levels, message: string, context: IContext, source: string): void { this.validateLevel(level); - this.send(this.strategy, level, message, context3); + this.send(this.strategy, level, message, context); } /** @@ -293,7 +290,7 @@ class ServerLogger extends Logger implements ILogger { * @returns {void} */ public logMethod({ level = LogLevel.INFO, message = "", context = {} }) { - this.logExtended(level, message, {}, context, ClientLogger.side); + this.logExtended(level, message, context, ClientLogger.side); } /**