diff --git a/src/examples/basic.ts b/src/examples/basic.ts index 8973813..16879db 100644 --- a/src/examples/basic.ts +++ b/src/examples/basic.ts @@ -2,47 +2,47 @@ import z from 'zod'; import { BaseSchema, Logger } from '../logger'; const schema = { - debug: { - 'stats': z.object({ - a: z.number(), - }), - }, - error: { - 'error': z.object({}), - }, - info: {}, - warn: {}, + debug: { + stats: z.object({ + a: z.number(), + }), + }, + error: { + error: z.object({}), + }, + info: {}, + warn: {}, } satisfies BaseSchema; const logger = new Logger({ - service: 'my-service', - schema, + service: 'my-service', + schema, }); logger.debug('stats', { - a: 123, + a: 123, }); // This will only log the "a" field logger.debug('stats', { - a: 123, - /** - * This will show the following typescript error - * - * Argument of type '{ a: number; b: number; }' is not assignable to parameter of type '{ a: number; }'. - * Object literal may only specify known properties, and 'b' does not exist in type '{ a: number; }'. - */ - b: 123123, + a: 123, + /** + * This will show the following typescript error + * + * Argument of type '{ a: number; b: number; }' is not assignable to parameter of type '{ a: number; }'. + * Object literal may only specify known properties, and 'b' does not exist in type '{ a: number; }'. + */ + b: 123123, }); logger.debug('stats', { - a: 1 + a: 1, }); // `error` is required when using `.error` method // `cause` is optional logger.error('error', { - error: new Error('A thing happened', { - cause: new Error('This thing caused it'), - }) + error: new Error('A thing happened', { + cause: new Error('This thing caused it'), + }), }); diff --git a/src/logger.ts b/src/logger.ts index a5d628b..a66b2d4 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -63,13 +63,19 @@ export type BaseSchema = { [level in LogLevel]?: Record>; }; -type Options = { +type Options = { service: string; schema?: Schema; defaultMeta?: Record; }; -type MetaForSchema = Message extends keyof Schema[Level] +const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); +type Literal = z.infer; +type Json = Literal | { [key: string]: Json } | Json[]; + +type MetaForSchema = [Message] extends [never] + ? z.input> + : Message extends keyof Schema[Level] ? Schema[Level][Message] extends z.ZodType ? z.input : undefined @@ -173,11 +179,17 @@ export class Logger { this.schema = options.schema; } - private log( - level: LogLevel, + private log( + level: Level, message: Message, - data: Schema[LogLevel][Message] extends z.ZodType ? z.input : undefined, + data: Data extends z.ZodType ? z.input : z.input, ) { + // If we don't have a schema just log the message + if (!this.schema) { + this.logger[level](message as string, data); + return; + } + // Ensure meta is valid before logging const parser = this.schema?.[level]?.[message as string]; const parsedData = parser?.safeParse(data); @@ -194,26 +206,42 @@ export class Logger { this.logger[level](message as string, meta); } - debug(message: Message, meta: DebugMeta) { - this.log('debug', message, meta); + debug( + message: [Message] extends [never] ? string : Message, + meta: DebugMeta, + ) { + this.log('debug', message as Message, meta as any); } - info(message: Message, meta: InfoMeta) { - this.log('info', message, meta); + info( + message: [Message] extends [never] ? string : Message, + meta: InfoMeta, + ) { + this.log('info', message as Message, meta as any); } - warn(message: Message, meta: WarnMeta) { - this.log('warn', message, meta); + warn( + message: [Message] extends [never] ? string : Message, + meta: WarnMeta, + ) { + this.log('warn', message as Message, meta as any); } - error(message: Message, meta: ErrorMeta) { + error( + message: [Message] extends [never] ? string : Message, + meta: ErrorMeta, + ) { // If the error isn't an error object make it so // This is to prevent issues where something other than an Error is thrown // When passing this to transports like Axiom it really needs to be a real Error class if (meta?.error && !(meta?.error instanceof Error)) meta.error = new Error(`Unknown Error: ${String(meta.error)}`); - this.log('error', message, { - ...meta, - error: serialiseError(meta?.error as Error), - }); + this.log( + 'error', + message as Message, + { + ...meta, + error: serialiseError(meta?.error as Error), + } as any, + ); } }