diff --git a/README.md b/README.md index f007715..709aae2 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ const configuration = { { name: "bar", level: "log", - tag: `[${new Date(Date.now()).toLocaleTimeString()}] BAR -`, + tag: `BAR -`, + timestamp: () => `[${new Date(Date.now()).toLocaleTimeString()}]`, }, ], }; @@ -49,7 +50,7 @@ let dlog = createDeblog(configuration); In the example above, we are configuring the deblog instance saved in the variable `dlog` in order to expose two custom logging methods named `foo` and `bar`, respectively. We are also specifying that the log `foo` will provide a logging level of **debug**, whilst the log `bar` will provide a logging level of **log**. The logging levels here are just the names of the methods that will be called on the **console** object wrapped by the deblog instance. Therefore, `console.debug()` and `console.log()` will be called for each call of `dlog.foo()` and `dlog.bar()`, respectively. -Both logging methods are configured with a `tag` that will be printed at the beginning of each print in the console. As you can see for the **foo** log, you can customize a tag as you prefer. Furthermore, both logs are enabled using the `enabled` property in the configuration. Omitting such a property will enable the log by default. +Both logging methods are configured with a `tag` that will be printed at the beginning of each print in the console. As you can see for the **foo** log, you can define a custom function that returns a string representing a timestamp that will be printed before the tag. Furthermore, both logs are enabled using the `enabled` property in the configuration. Omitting such a property will enable the log by default. Once created, a Deblog instance can be used like this: @@ -119,6 +120,7 @@ This will produce the following output: name: string, level: "log" | "debug" | "info" | "warn" | "error", tag?: string, + timestamp?: boolean | (() => string) enabled?: boolean //default: true } ] @@ -136,6 +138,7 @@ A log configuration is structured with the following properties: - `name`: A string representing the name of the logging method that will be exposed by the deblog instance. - `level`: The method that will be called on the console object. The only allowed values are `"log" | "debug" | "info" | "warn" | "error"`. For commodity, you can import and use the enumeration `LogLevels` to assign a proper value to this property. - `tag`: A string to be attached at the beginning of the log line printed in the console. +- `timestamp`: It is used to configure a timestamp before the tag and the message of the log. When `true` is provided, a timestamp is generated using `[${(new Date()).toLocaleTimeString()}]`. You can also provide a function returning a string to customize your timestamp format or decide whatever to return. The default value is `false`. - `enabled`: Specifies if the logging method will print in the console when it will be called. ## Deblog Repository diff --git a/src/create_deblog.ts b/src/create_deblog.ts index dc6e9db..3100193 100644 --- a/src/create_deblog.ts +++ b/src/create_deblog.ts @@ -50,8 +50,28 @@ export function createDeblog(config: IDeblogConfig): IDe let flag = log.enabled ?? true; let defFlag = flag; + + if (typeof log.timestamp !== "undefined" && typeof log.timestamp !== "function" && typeof log.timestamp !== "boolean") { + throw new Error("Invalid timestamp configuration. The only types allowed are boolean and () => string."); + } + + //TODO: continue from here. refactor arguments array and probably change the tests. let tempLog = (function () { - flag && console[log.level](`${log.tag}`, ...arguments); + + let ts: string | undefined; + if (typeof log.timestamp !== "undefined") { + if (typeof log.timestamp === "function") { + ts = log.timestamp(); + } else if (typeof log.timestamp === "boolean") { + ts = `[${(new Date()).toLocaleTimeString()}]`; + } + } + + let args: string[] = []; + ts && args.push(ts); + log.tag && args.push(log.tag); + args = [...args, ...arguments]; + flag && console[log.level](...args); }); tempLog.enable = () => (flag = true); tempLog.disable = () => (flag = false); diff --git a/src/types.ts b/src/types.ts index ae2bb1c..a31bf68 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ export type TLogLevel = LogLevels | "log" | "debug" | "info" | "warn" | "error" export interface ILogConfig { name: string; level: TLogLevel; + timestamp?: boolean | (() => string); tag?: string; enabled?: boolean; } diff --git a/test/deblog.spec.ts b/test/deblog.spec.ts index 68bbe90..a5662ab 100644 --- a/test/deblog.spec.ts +++ b/test/deblog.spec.ts @@ -157,9 +157,9 @@ describe("test createDeblog for proper deblog creation", () => { deblog.yell("I am yelling in console.info"); deblog.quiet("I am quiet in console.log"); - expect(mockedConsoleLog).toBeCalledWith("undefined", "I am barking in console.log"); - expect(mockedConsoleInfo).toBeCalledWith("undefined", "I am yelling in console.info"); - expect(mockedConsoleLog).toBeCalledWith("undefined", "I am quiet in console.log"); + expect(mockedConsoleLog).toBeCalledWith("I am barking in console.log"); + expect(mockedConsoleInfo).toBeCalledWith("I am yelling in console.info"); + expect(mockedConsoleLog).toBeCalledWith("I am quiet in console.log"); deblog.disableAllBut("bark"); @@ -167,9 +167,9 @@ describe("test createDeblog for proper deblog creation", () => { deblog.yell("Don't yell me"); deblog.quiet("Don't quiet me"); - expect(mockedConsoleLog).toBeCalledWith("undefined", "Do bark me"); - expect(mockedConsoleInfo).not.toBeCalledWith("undefined", "Don't yell me"); - expect(mockedConsoleLog).not.toBeCalledWith("undefined", "Don't quiet me"); + expect(mockedConsoleLog).toBeCalledWith("Do bark me"); + expect(mockedConsoleInfo).not.toBeCalledWith("Don't yell me"); + expect(mockedConsoleLog).not.toBeCalledWith("Don't quiet me"); deblog.restoreAll(); @@ -177,9 +177,9 @@ describe("test createDeblog for proper deblog creation", () => { deblog.yell("Yell info logs again"); deblog.quiet("Quite logs again"); - expect(mockedConsoleLog).toBeCalledWith("undefined", "Bark still logs"); - expect(mockedConsoleInfo).toBeCalledWith("undefined", "Yell info logs again"); - expect(mockedConsoleLog).toBeCalledWith("undefined", "Quite logs again"); + expect(mockedConsoleLog).toBeCalledWith("Bark still logs"); + expect(mockedConsoleInfo).toBeCalledWith("Yell info logs again"); + expect(mockedConsoleLog).toBeCalledWith("Quite logs again"); jest.restoreAllMocks(); }); @@ -203,21 +203,21 @@ describe("test createDeblog for proper deblog creation", () => { deblog.yell("Yell!"); deblog.quiet("Quiet"); - expect(mockedConsoleLog).toBeCalledWith("undefined", "Bark!"); - expect(mockedConsoleLog).toBeCalledWith("undefined", "Yell!"); - expect(mockedConsoleLog).not.toBeCalledWith("undefined", "Quiet!"); + expect(mockedConsoleLog).toBeCalledWith("Bark!"); + expect(mockedConsoleLog).toBeCalledWith("Yell!"); + expect(mockedConsoleLog).not.toBeCalledWith("Quiet!"); deblog.quiet.enable(); deblog.quiet("Quiet again!"); - expect(mockedConsoleLog).toBeCalledWith("undefined", "Quiet again!"); + expect(mockedConsoleLog).toBeCalledWith("Quiet again!"); deblog.yell.disable(); deblog.bark("Bark again!"); deblog.yell("Yell again"); - expect(mockedConsoleLog).toBeCalledWith("undefined", "Bark again!"); - expect(mockedConsoleLog).not.toBeCalledWith("undefined", "Yell again"); + expect(mockedConsoleLog).toBeCalledWith("Bark again!"); + expect(mockedConsoleLog).not.toBeCalledWith("Yell again"); jest.restoreAllMocks(); }); }); @@ -271,4 +271,72 @@ describe("test persistence of deblogs", () => { allDebs = getDeblogs(); expect(allDebs).toHaveLength(0); }); +}); + +describe("test timestamp feature", () => { + it("should add a timestamp (comformant to LocaleTimeString) to the log if the configuration is a boolean", () => { + + jest.spyOn(console, "log").mockImplementation(() => {}); + + const config: IDeblogConfig = { + logs: [ + { name: "bark", level: "log", tag: "[BARK]", timestamp: true }, + ] + } + + const deblog = createDeblog(config); + + deblog.bark("What's the time?"); + expect(console.log).toBeCalledWith(expect.stringMatching(/^\[\d{2}:\d{2}:\d{2}\]$/), "[BARK]", "What's the time?"); + jest.restoreAllMocks(); + }); + + it("should add a timestamp to the log if a function returning a string as configuration is provided", () => { + + jest.spyOn(console, "log").mockImplementation(() => {}); + + const config: IDeblogConfig = { + logs: [ + { name: "bark", level: "log", tag: "[BARK]", timestamp: () => `[${new Date().toISOString()}]` }, + ] + } + + const deblog = createDeblog(config); + + deblog.bark("What's the time?"); + expect(console.log).toBeCalledWith(expect.stringMatching(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/), "[BARK]", "What's the time?"); + jest.restoreAllMocks(); + }); + + it("should throw is a timestamp configuration that its not a boolean or function is provided", () => { + + const config: IDeblogConfig = { + logs: [ + { name: "bark", level: "log", tag: "[BARK]", timestamp: 123456 } + ] + } + + expect(() => createDeblog(config)).toThrowError("Invalid timestamp configuration. The only types allowed are boolean and () => string."); + }); + + it("shoud generate two different timestamp if a boolean configuration is provided", () => { + let logMock = jest.spyOn(console, "log").mockImplementation(() => {}); + + const config: IDeblogConfig = { + logs: [ + { name: "bark", level: "log", tag: "[BARK]", timestamp: true }, + ] + } + + const deblog = createDeblog(config); + + deblog.bark("First Bark"); + jest.useFakeTimers({ now: Date.now() + 5000 }); + deblog.bark("second Bark"); + jest.useRealTimers(); + + expect(logMock.mock.calls[0][0]).not.toBe(logMock.mock.calls[1][0]); + + jest.restoreAllMocks(); + }); }); \ No newline at end of file