diff --git a/package-lock.json b/package-lock.json index b2d53aa..1eeecb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "p-event": "^6.0.0", "sinon": "^17.0.1", "sinon-chai": "^3.7.0", + "ts-essentials": "^9.4.1", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "tshy": "^1.7.0", @@ -2864,6 +2865,20 @@ "node": ">=8.0" } }, + "node_modules/ts-essentials": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.4.1.tgz", + "integrity": "sha512-oke0rI2EN9pzHsesdmrOrnqv1eQODmJpd/noJjwj2ZPC3Z4N2wbjrOEqnsEgmvlO2+4fBb0a794DCna2elEVIQ==", + "dev": true, + "peerDependencies": { + "typescript": ">=4.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", diff --git a/package.json b/package.json index 3195eff..065ead2 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "p-event": "^6.0.0", "sinon": "^17.0.1", "sinon-chai": "^3.7.0", + "ts-essentials": "^9.4.1", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "tshy": "^1.7.0", diff --git a/src/destinations.ts b/src/destinations.ts index 958bc4e..cb4d9ea 100644 --- a/src/destinations.ts +++ b/src/destinations.ts @@ -1,28 +1,23 @@ import pinoRoll from 'pino-roll'; -import {LogLevelStreamEntry, LogLevel, LogOptions} from "./types.js"; -import {DestinationStream} from "pino"; +import {LogLevelStreamEntry, LogLevel, LogOptions, StreamDestination, FileDestination} from "./types.js"; +import {DestinationStream, pino} from "pino"; import prettyDef, {PrettyOptions} from "pino-pretty"; import {prettyConsole, prettyFile} from "./pretty.js"; -import {logPath} from "./constants.js"; import {fileOrDirectoryIsWriteable} from "./util.js"; import path from "path"; import {ErrorWithCause} from "pony-cause"; const pRoll = pinoRoll as unknown as typeof pinoRoll.default; -export const buildDestinationFile = async (options: Required & { - path?: string -}): Promise => { - const {file} = options; - if (file === false) { +export const buildDestinationRollingFile = async (level: LogLevel | false, options: FileDestination): Promise => { + if (level === false) { return undefined; } - - const logToPath = options.path ?? logPath; + const {path: logPath, ...rest} = options; try { - fileOrDirectoryIsWriteable(logToPath); + fileOrDirectoryIsWriteable(logPath); const rollingDest = await pRoll({ - file: path.resolve(logToPath, 'app'), + file: path.resolve(logPath, 'app'), size: 10, frequency: 'daily', get extension() { @@ -33,19 +28,51 @@ export const buildDestinationFile = async (options: Required & { }); return { - level: file as LogLevel, - stream: prettyDef.default({...prettyFile, destination: rollingDest}) + level: level, + stream: prettyDef.default({...prettyFile, ...rest, destination: rollingDest}) }; } catch (e: any) { throw new ErrorWithCause('WILL NOT write logs to rotating file due to an error while trying to access the specified logging directory', {cause: e as Error}); } } -export const buildDestinationStream = (options: Required & { - stream?: DestinationStream | NodeJS.WritableStream -} & PrettyOptions): LogLevelStreamEntry => { - const {console, stream = 1, ...rest} = options; + +export const buildDestinationFile = (level: LogLevel | false, options: FileDestination): LogLevelStreamEntry | undefined => { + if (level === false) { + return undefined; + } + + const {path: logPath, ...rest} = options; + + try { + fileOrDirectoryIsWriteable(logPath); + const dest = pino.destination({dest: path, sync: false}) + + return { + level: level, + stream: prettyDef.default({...prettyFile, ...rest, destination: dest}) + }; + } catch (e: any) { + throw new ErrorWithCause('WILL NOT write to file due to an error while trying to access the specified directory', {cause: e as Error}); + } +} + +export const buildDestinationStream = (level: LogLevel, options: StreamDestination): LogLevelStreamEntry => { + return { + level: level, + stream: prettyDef.default({...prettyConsole, ...options}) + } +} + +export const buildDestinationStdout = (level: LogLevel, options: Omit = {}): LogLevelStreamEntry => { + return { + level: level, + stream: prettyDef.default({...prettyConsole, ...options, destination: 1}) + } +} + +export const buildDestinationStderr = (level: LogLevel, options: Omit = {}): LogLevelStreamEntry => { return { - level: console as LogLevel, - stream: prettyDef.default({...prettyConsole, ...rest, destination: stream, sync: true}) + level: level, + stream: prettyDef.default({...prettyConsole, ...options, destination: 2}) } } diff --git a/src/factory.ts b/src/factory.ts index 3d68fd5..8111558 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -1,5 +1,5 @@ import {prettyConsole, prettyFile, prettyOptsFactory} from "./pretty.js"; -import {buildDestinationStream, buildDestinationFile} from "./destinations.js"; +import {buildDestinationStream, buildDestinationRollingFile, buildDestinationStdout, buildDestinationStderr, buildDestinationFile} from "./destinations.js"; import {buildLogger} from './loggers.js'; export { @@ -7,6 +7,9 @@ export { prettyFile, prettyOptsFactory, buildDestinationStream, + buildDestinationStdout, + buildDestinationStderr, buildDestinationFile, + buildDestinationRollingFile, buildLogger } diff --git a/src/loggers.ts b/src/loggers.ts index 9666315..d8c18d9 100644 --- a/src/loggers.ts +++ b/src/loggers.ts @@ -1,7 +1,8 @@ import {parseLogOptions} from "./funcs.js"; import {Logger, LogLevel, LogLevelStreamEntry, LogOptions} from "./types.js"; -import {buildDestinationStream, buildDestinationFile} from "./destinations.js"; +import {buildDestinationRollingFile, buildDestinationStdout} from "./destinations.js"; import {pino} from "pino"; +import {logPath} from "./constants.js"; export const buildLogger = (defaultLevel: LogLevel, streams: LogLevelStreamEntry[]): Logger => { const plogger = pino({ @@ -40,14 +41,14 @@ export const childLogger = (parent: Logger, labelsVal: any | any[] = [], context } return newChild } -export const loggerTest = buildLogger('silent', [buildDestinationStream(parseLogOptions({level: 'debug'}))]); +export const loggerTest = buildLogger('silent', [buildDestinationStdout('debug')]); -export const loggerInit = buildLogger('debug', [buildDestinationStream(parseLogOptions({level: 'debug'}))]); +export const loggerInit = buildLogger('debug', [buildDestinationStdout('debug')]); export const loggerApp = async (config: LogOptions | object = {}) => { const options = parseLogOptions(config); - const streams: LogLevelStreamEntry[] = [buildDestinationStream(options)]; - const file = await buildDestinationFile(options); + const streams: LogLevelStreamEntry[] = [buildDestinationStdout(options.console)]; + const file = await buildDestinationRollingFile(options.file, {path: logPath}); if(file !== undefined) { streams.push(file); } diff --git a/src/pretty.ts b/src/pretty.ts index 21d37b3..0bcbc2c 100644 --- a/src/pretty.ts +++ b/src/pretty.ts @@ -29,7 +29,8 @@ export const prettyOptsFactory = (opts: PrettyOptions = {}): PrettyOptions => { ...opts } } -export const prettyConsole: PrettyOptions = prettyOptsFactory() +export const prettyConsole: PrettyOptions = prettyOptsFactory({sync: true}) export const prettyFile: PrettyOptions = prettyOptsFactory({ colorize: false, + sync: false, }); diff --git a/src/types.ts b/src/types.ts index d261c15..afec151 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ -import {Level, Logger as PinoLogger, StreamEntry} from 'pino'; +import {DestinationStream, Level, Logger as PinoLogger, StreamEntry} from 'pino'; import {ErrorWithCause} from "pony-cause"; +import {PrettyOptions} from "pino-pretty"; export type LogLevel = typeof LOG_LEVELS[number]; export const LOG_LEVELS= ['silent', 'fatal', 'error', 'warn', 'info', 'log', 'verbose', 'debug'] as const; @@ -51,3 +52,6 @@ export type LogData = Record & { labels: any[] msg: string | Error | ErrorWithCause } + +export type FileDestination = Omit & {path: string}; +export type StreamDestination = Omit & {destination: number | DestinationStream | NodeJS.WritableStream}; diff --git a/tests/index.test.ts b/tests/index.test.ts index 32dc4e6..f32f001 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -7,7 +7,7 @@ import {sleep} from "../src/util.js"; import {LogData, LOG_LEVELS} from "../src/types.js"; import withLocalTmpDir from 'with-local-tmp-dir'; import {readdirSync,} from 'node:fs'; -import {buildDestinationStream, buildDestinationFile} from "../src/destinations.js"; +import {buildDestinationStream, buildDestinationRollingFile} from "../src/destinations.js"; import {buildLogger} from "../src/loggers.js"; @@ -17,8 +17,9 @@ const testConsoleLogger = (config?: object): [Logger, Transform, Transform] => { const rawStream = new PassThrough(); const logger = buildLogger('debug', [ buildDestinationStream( + opts.console, { - stream: testStream, + destination: testStream, colorize: false, ...opts } @@ -32,10 +33,12 @@ const testConsoleLogger = (config?: object): [Logger, Transform, Transform] => { } const testFileLogger = async (config?: object) => { - const streamEntry = await buildDestinationFile( + const opts = parseLogOptions(config); + const streamEntry = await buildDestinationRollingFile( + opts.file, { path: '.', - ...parseLogOptions(config) + ...opts } ); return buildLogger('debug', [