From 20a6b79397407228073daa221d491cd626a7df6d Mon Sep 17 00:00:00 2001 From: Petr Heinz Date: Fri, 11 Aug 2023 14:43:45 +0200 Subject: [PATCH] Relaxing Context type for better DX (#71) --- example-project/index.js | 23 +++++++++++++++++------ packages/bunyan/src/bunyan.test.ts | 7 +++---- packages/core/src/base.test.ts | 2 +- packages/node/src/node.ts | 14 +++++++++++++- packages/types/README.md | 8 ++++---- packages/types/src/types.ts | 8 ++++---- packages/winston/src/winston.test.ts | 5 ++--- 7 files changed, 44 insertions(+), 23 deletions(-) diff --git a/example-project/index.js b/example-project/index.js index ac4bb81..fcc20bc 100644 --- a/example-project/index.js +++ b/example-project/index.js @@ -19,14 +19,14 @@ const logger = new Logtail(process.argv[2], { sendLogsToConsoleOutput: true }); // Usage // Send debug level log using the debug() method -logger.debug("I am using Logtail!"); +const debugLog = logger.debug("I am using Logtail!"); // Send info level log using the info() method -logger.info("An interesting event occured!") +const infoLog = logger.info("An interesting event occured!"); // Send warn level log using the warn() method // You can add additional structured data to help you troubleshoot your code as shown below -logger.warn("Something is not quite right, better check on it.",{ +const warningLog = logger.warn("Something is not quite right, better check on it.",{ user:{ username:"someuser", email:"someuser@example.com" @@ -36,9 +36,20 @@ logger.warn("Something is not quite right, better check on it.",{ } }); -// Send error level log using the error() method -logger.error("Oops! An runtime ERROR occurred!").then( - // Logging methods are async function returning Promises +function callbackThatMightFail() { + throw new Error("Testing error") +} + +let errorLog; +try { + callbackThatMightFail(); +} catch (err) { + // Send error level log using the error() method + errorLog = logger.error("Oops! An runtime ERROR occurred!", err); +} + +// Logging methods are async function returning Promises +Promise.all([debugLog, infoLog, warningLog, errorLog]).then( // OnResolve write message function() { console.log("All done! You can check your logs now.") diff --git a/packages/bunyan/src/bunyan.test.ts b/packages/bunyan/src/bunyan.test.ts index 6535e24..02d8a27 100644 --- a/packages/bunyan/src/bunyan.test.ts +++ b/packages/bunyan/src/bunyan.test.ts @@ -1,6 +1,6 @@ import bunyan, { LogLevelString } from "bunyan"; import { Logtail } from "@logtail/node"; -import {Context, LogLevel} from "@logtail/types"; +import { LogLevel } from "@logtail/types"; import { LogtailStream } from "./bunyan"; @@ -130,9 +130,8 @@ describe("Bunyan tests", () => { it("should include correct context fields", async done => { const logtail = new Logtail("test"); logtail.setSync(async logs => { - const context = logs[0].context as Context; - const runtime = context.runtime as Context; - expect(runtime.file).toMatch("bunyan.test.ts") + const context = logs[0].context; + expect(context.runtime.file).toMatch("bunyan.test.ts") done(); return logs; }); diff --git a/packages/core/src/base.test.ts b/packages/core/src/base.test.ts index 251dce8..1256073 100644 --- a/packages/core/src/base.test.ts +++ b/packages/core/src/base.test.ts @@ -257,7 +257,7 @@ describe("base class tests", () => { expect(log.message).toBe(message); // Context should contain a stack trace - expect((log as any).stack).toBe(e.stack); + expect(log.stack).toBe(e.stack); }); it("should not ignore exceptions if `ignoreExceptions` opt == false and `throwExceptions` opt == true", async () => { diff --git a/packages/node/src/node.ts b/packages/node/src/node.ts index 88060af..bec8013 100644 --- a/packages/node/src/node.ts +++ b/packages/node/src/node.ts @@ -66,6 +66,10 @@ export class Node extends Base { const wrappedContext: unknown = { extra: context }; context = wrappedContext as TContext; } + if (context instanceof Error) { + const wrappedContext: unknown = { error: context }; + context = wrappedContext as TContext; + } // Process/sync the log, per `Base` logic context = { ...getStackContext(this, stackContextHint), ...context }; @@ -108,6 +112,12 @@ export class Node extends Base { } return value.toISOString(); + } else if (value instanceof Error) { + return { + name: value.name, + message: value.message, + stack: value.stack?.split("\n"), + }; } else if ((typeof value === "object" || Array.isArray(value)) && (maxDepth < 1 || visitedObjects.has(value))) { if (visitedObjects.has(value)) { if (this._options.contextObjectCircularRefWarn) { @@ -143,8 +153,10 @@ export class Node extends Base { visitedObjects.delete(value); return logClone; - } else { + } else if (typeof value === 'undefined') { return undefined; + } else { + return ``; } } } diff --git a/packages/types/README.md b/packages/types/README.md index 15cac5e..dfbf70d 100644 --- a/packages/types/README.md +++ b/packages/types/README.md @@ -131,10 +131,10 @@ We call this 'context' and these are the types: ```typescript /** - * Context type - a string/number/bool/Date, or a nested object of the same + * Context type - a nested object of serializable types (a string / number / bool / null / undefined / Array / Date / Error) */ -export type ContextKey = string | number | boolean | Date; -export type Context = { [key: string]: ContextKey | Context }; +export type ContextKey = any; +export type Context = { [key: string]: ContextKey }; ``` ### `ILogtailLog` @@ -146,7 +146,7 @@ interface ILogtailLog { dt: Date; level: LogLevel; // <-- see `LogLevel` above message: string; - [key: string]: ContextKey | Context; // <-- see `Context` above + [key: string]: ContextKey; // <-- see `Context` above } ``` diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 5b7aff0..e79c3c5 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -98,10 +98,10 @@ export enum LogLevel { } /** - * Context type - a string/number/bool/Date, or a nested object of the same + * Context type - a nested object of serializable types (a string / number / bool / null / undefined / Array / Date / Error) */ -export type ContextKey = string | number | boolean | Date | null; -export type Context = { [key: string]: ContextKey | Context }; +export type ContextKey = any; +export type Context = { [key: string]: ContextKey }; export type StackContextHint = { fileName: string, methodNames: [string] }; /** @@ -111,7 +111,7 @@ export interface ILogtailLog { dt: Date; level: ILogLevel; message: string; - [key: string]: ContextKey | Context; + [key: string]: ContextKey; } /** diff --git a/packages/winston/src/winston.test.ts b/packages/winston/src/winston.test.ts index 8900d9b..1ed606a 100644 --- a/packages/winston/src/winston.test.ts +++ b/packages/winston/src/winston.test.ts @@ -211,8 +211,7 @@ describe("Winston logging tests", () => { const logs = await logged; - const context = logs[0].context as Context; - const runtime = context.runtime as Context; - expect(runtime.file).toMatch("winston.test.ts"); + const context = logs[0].context; + expect(context.runtime.file).toMatch("winston.test.ts"); }); });