diff --git a/src/index.ts b/src/index.ts index 9bde549..827e978 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import {ZodError} from 'zod' import {type JsonSchema7Type} from 'zod-to-json-schema' import * as zodValidationError from 'zod-validation-error' import {flattenedProperties, incompatiblePropertyPairs, getDescription} from './json-schema' -import {primitiveOrJsonConsoleLogger} from './logging' +import {lineByLineConsoleLogger} from './logging' import {Logger, TrpcCliParams} from './types' import {parseProcedureInputs} from './zod-procedure' @@ -52,7 +52,7 @@ export const trpcCli = ({router, ...params}: TrpcCliParams< ) async function run(runParams?: {argv?: string[]; logger?: Logger; process?: {exit: (code: number) => never}}) { - const logger = {...primitiveOrJsonConsoleLogger, ...runParams?.logger} + const logger = {...lineByLineConsoleLogger, ...runParams?.logger} const _process = runParams?.process || process let verboseErrors: boolean = false diff --git a/src/logging.ts b/src/logging.ts index e86985c..af7e750 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,9 +1,13 @@ -import {LogMethod, Logger} from './types' +import {LogMethod as LogFn, Logger} from './types' -export const primitiveOrJsonLogger = getLoggerTransformer(log => { - const transformed: LogMethod = (...args) => { - if (args.length === 1 && Array.isArray(args[0])) { - args[0].forEach(item => transformed(item)) +export const lineByLineLogger = getLoggerTransformer(log => { + /** + * @param args values to log. if `logger.info('a', 1)` is called, `args` will be `['a', 1]` + * @param depth tracks whether the current call recursive. Used to make sure we don't flatten nested arrays + */ + const wrapper = (args: unknown[], depth: number) => { + if (args.length === 1 && Array.isArray(args[0]) && depth === 0) { + args[0].forEach(item => wrapper([item], 1)) } else if (args.every(isPrimitive)) { log(...args) } else if (args.length === 1) { @@ -13,7 +17,7 @@ export const primitiveOrJsonLogger = getLoggerTransformer(log => { } } - return transformed + return (...args) => wrapper(args, 0) }) const isPrimitive = (value: unknown): value is string | number | boolean => { @@ -21,8 +25,9 @@ const isPrimitive = (value: unknown): value is string | number | boolean => { return type === 'string' || type === 'number' || type === 'boolean' } -type TransformLogMethod = (method: LogMethod) => LogMethod +type TransformLogMethod = (log: LogFn) => LogFn +/** Takes a function that wraps an individual log function, and returns a function that wraps the `info` and `error` functions for a logger */ function getLoggerTransformer(transform: TransformLogMethod) { return (logger: Logger): Logger => { const info = logger.info && transform(logger.info) @@ -31,4 +36,12 @@ function getLoggerTransformer(transform: TransformLogMethod) { } } -export const primitiveOrJsonConsoleLogger = primitiveOrJsonLogger(console) +/** + * A logger which uses `console.log` and `console.error` to log in the following way: + * - Primitives are logged directly + * - Arrays are logged item-by-item + * - Objects are logged as JSON + * + * This is useful for logging structured data in a human-readable way, and for piping logs to other tools. + */ +export const lineByLineConsoleLogger = lineByLineLogger(console) diff --git a/test/logging.test.ts b/test/logging.test.ts index 1d88f09..99cf26d 100644 --- a/test/logging.test.ts +++ b/test/logging.test.ts @@ -1,10 +1,10 @@ import {beforeEach, expect, test, vi} from 'vitest' -import {primitiveOrJsonLogger} from '../src/logging' +import {lineByLineLogger} from '../src/logging' const info = vi.fn() const error = vi.fn() const mocks = {info, error} -const jsonish = primitiveOrJsonLogger(mocks) +const jsonish = lineByLineLogger(mocks) beforeEach(() => { vi.clearAllMocks() @@ -43,6 +43,24 @@ test('primitives array', async () => { `) }) +test('array array', async () => { + jsonish.info!([ + ['m1', 'm2'], + ['m3', 'm4'], + ]) + + expect(info).toMatchInlineSnapshot(` + [ + "m1", + "m2" + ] + [ + "m3", + "m4" + ] + `) +}) + test('multi primitives', async () => { jsonish.info!('m1', 11, true, 'm2') jsonish.info!('m1', 12, false, 'm2')