Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/FoxxMD/logging. A typed, opinionated, batteries-included, Pino-based logging solution for backend T foxxmd.github.io/logging
child-logger logging logging-library nodejs pinojs typescript-library
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223import pinoRoll from 'pino-roll';import { LogLevelStreamEntry, LogLevel, StreamDestination, FileDestination, JsonPrettyDestination,} from "./types.js";import {destination, DestinationStream} from "pino";import {build, prettyFactory} from "pino-pretty"import {PRETTY_OPTS_FILE, prettyOptsConsoleFactory, prettyOptsFileFactory} from "./pretty.js";import {pathIsWriteable, parseBool} from "./util.js";import path from "path";import {Transform, TransformCallback} from "node:stream";import pump from 'pump';import abstractTransport from 'pino-abstract-transport';
const pRoll = pinoRoll as unknown as typeof pinoRoll.default;
/** * Creates a `LogLevelStreamEntry` stream that writes to a rolling file at or above the minimum `level` * */export const buildDestinationRollingFile = async (level: LogLevel | false, options: FileDestination): Promise<LogLevelStreamEntry | undefined> => { if (level === false) { return undefined; } const { path: logPath, size, frequency, timestamp = 'auto', ...rest } = options;
if(size === undefined && frequency === undefined) { throw new Error(`For rolling files must specify at least one of 'frequency' , 'size'`); }
const testPath = typeof logPath === 'function' ? logPath() : logPath;
try { pathIsWriteable(testPath); } catch (e: any) { throw new Error('Cannot write logs to rotating file due to an error while trying to access the specified logging directory', {cause: e}); }
const pInfo = path.parse(testPath); let filePath: string | (() => string); if(typeof logPath === 'string' && frequency !== undefined) { filePath = () => { let dtStr: string; switch(timestamp) { case 'unix': dtStr = Date.now().toString(); break; case 'iso': dtStr = new Date().toISOString(); break; case 'auto': dtStr = frequency === 'daily' ? new Date().toISOString().split('T')[0] : new Date().toISOString(); break; } return path.resolve(pInfo.dir, `${pInfo.name}-${dtStr}`) } } else { filePath = path.resolve(pInfo.dir, pInfo.name); } const rollingDest = await pRoll({ file: filePath, size, frequency, extension: pInfo.ext, mkdir: true, sync: false, });
return { level: level, stream: build({...PRETTY_OPTS_FILE, ...rest, destination: rollingDest}) };}
/** * Creates a `LogLevelStreamEntry` stream that writes to a static file at or above the minimum `level` * */export const buildDestinationFile = (level: LogLevel | false, options: FileDestination): LogLevelStreamEntry | undefined => { if (level === false) { return undefined; }
const {path: logPathVal, ...rest} = options;
try { const filePath = typeof logPathVal === 'function' ? logPathVal() : logPathVal; pathIsWriteable(filePath); const dest = destination({dest: filePath, mkdir: true, sync: false})
return { level: level, stream: build({...prettyOptsFileFactory(rest), ...rest, destination: dest}) }; } catch (e: any) { throw new Error('WILL NOT write to file due to an error while trying to access the specified directory', {cause: e}); }}
/** * Creates a `LogLevelStreamEntry` stream that writes to a `NodeJs.WriteableStream` or [Sonic Boom `DestinationStream`](https://github.com/pinojs/sonic-boom) at or above the minimum `level` * * @see DestinationStream * */export const buildDestinationStream = (level: LogLevel, options: StreamDestination): LogLevelStreamEntry => { return { level: level, stream: build({...prettyOptsConsoleFactory(options)}) }}
/** * Creates a `LogLevelStreamEntry` stream that writes the raw log data with prettified log line, as either a JSON string or object, to a `NodeJs.WriteableStream` or [Sonic Boom `DestinationStream`](https://github.com/pinojs/sonic-boom) at or above the minimum `level` * * Use this to get raw log data + formatted line rendered by pino-pretty. The prettified line is set to the `line` key in the object. * * WARNING: This is not a fast operation. The log data must be parsed to json before being prettified, which also parses data to json. If you only need raw log data you should pass a plain Passthrough or Transform stream as an additional destination and then JSON.parse() on 'data' event manually. * * If used with `object: true` then * * * the `destination` cannot be a file or SonicBoom object * * the `destination` stream passed MUST be set to `objectMode: true` * * @see DestinationStream * */export const buildDestinationJsonPrettyStream = (level: LogLevel, options: JsonPrettyDestination): LogLevelStreamEntry => { const {object = false} = options; const factoryOpts = prettyOptsConsoleFactory(options); const prettyFunc = prettyFactory(factoryOpts);
const stream = new Transform({ objectMode: object, autoDestroy: true, transform(chunk: any, encoding: BufferEncoding, callback: TransformCallback) { const data = JSON.parse(chunk.toString()); const line = prettyFunc(chunk); data.line = line.substring(0, line.length - 1); if(object) { callback(null, data); } else { callback(null, `${JSON.stringify(data)}\n`); } } });
let destinationStream: DestinationStream | NodeJS.WritableStream;
if(typeof options.destination === 'object' && typeof options.destination.write === 'function') { destinationStream = options.destination; } else { destinationStream = destination({...factoryOpts, dest: stream}); }
if(object) { // @ts-ignore pump(stream, destinationStream)
return { level: level, stream: stream } }
// @ts-ignore const transport = abstractTransport(function (source) { source.on('unknown', function (line) { destinationStream.write(line + '\n') })
// @ts-ignore pump(source, stream, destinationStream); }, { parse: 'lines'});
return { level: level, stream: transport }}
// docker stdout/err only colorizes if run with `-it` flag or `tty: true` in docker-compose// but most common outputs and web log viewers (portainer, dozzle) support colors and using those flags/options is not common for most users// so// can use COLORED_STD=true hint in Dockerfile to coerce colorizing output when running in a docker container.// and using this instead of FORCE_COLOR (used by colorette) so that we only affect console output instead of all streams// and value must evaluate to a truthy value to allow users to disable easilyconst coloredEnv = process.env.COLORED_STD;const coloredConsole = (coloredEnv === undefined || coloredEnv === '') ? undefined : parseBool(coloredEnv);const prettyColorizeEnv: {colorize?: boolean} = {};// colorette only does autodetection if `colorize` prop is not present *at all*, rather than just being undefined// so need to use default object and only add if we detect there is a non-empty valueif(coloredConsole !== undefined) { prettyColorizeEnv.colorize = coloredConsole;}
/** * Creates a `LogLevelStreamEntry` stream that writes to STDOUT at or above the minimum `level` * * @source * @see buildDestinationStream * */export const buildDestinationStdout = (level: LogLevel, options: Omit<StreamDestination, 'destination'> = {}): LogLevelStreamEntry => { const opts = {...prettyColorizeEnv, ...options, destination: destination({dest: 1, sync: true})} return buildDestinationStream(level, opts);}
/** * Creates a `LogLevelStreamEntry` stream that writes to STDERR at or above the minimum `level` * * @source * @see buildDestinationStream * */export const buildDestinationStderr = (level: LogLevel, options: Omit<StreamDestination, 'destination'> = {}): LogLevelStreamEntry => { const opts = {...prettyColorizeEnv, ...options, destination: destination({dest: 2, sync: true})}; return buildDestinationStream(level, opts);}