[READ-ONLY] Mirror of https://github.com/FoxxMD/logging. A typed, opinionated, batteries-included, Pino-based logging solution for backend TS/JS projects foxxmd.github.io/logging
child-logger logging logging-library nodejs pinojs typescript-library
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: Implement pretty customization and additional destinations when using loggerApp funcs

FoxxMD (Mar 8, 2024, 11:52 AM EST) e677a931 4930462b

+171 -23
+53 -8
README.md
··· 9 9 Features: 10 10 11 11 * Fully typed for Typescript projects 12 - * One-line, turn-key logging to console and rotating file 13 - * Child (nested) loggers with hierarchical label prefixes for log messages 12 + * One-line, [turn-key logging](#quick-start) to console and rotating file 13 + * [Child (nested) loggers](#child-loggers) with hierarchical label prefixes for log messages 14 14 * Per-destination level filtering configurable via ENV or arguments 15 15 * Clean, opinionated log output format powered by [pino-pretty](https://github.com/pinojs/pino-pretty): 16 - * Standard timestamp (iso8601) 17 16 * Colorized output when stream is TTY and supports it 18 - * Automatically serialize passed objects and Errors, including [Error Cause](https://github.com/tc39/proposal-error-cause) 19 - * Bring-Your-Own settings 20 - * Add or use your own streams for destinations 17 + * Automatically [serialize passed objects and Errors](#serializing-objects-and-errors), including [Error Cause](https://github.com/tc39/proposal-error-cause) 18 + * [Bring-Your-Own settings](#additional-app-logger-configuration) 19 + * Add or use your own streams/[transports](https://getpino.io/#/docs/transports?id=known-transports) for destinations 21 20 * All pino-pretty configs are exposed and extensible 21 + * [Build-Your-Own Logger](#building-a-logger) 22 + * Don't want to use any of the pre-built transports? Leverage the convenience of @foxxmd/logging wrappers and default settings but build your logger from scratch 22 23 23 24 <img src="/assets/example.png" alt="example log output"> 25 + 26 + **Documentation best viewed on [https://foxxmd.github.io/logging](https://foxxmd.github.io/logging)** 24 27 25 28 # Install 26 29 ··· 145 148 * For rolling log files 146 149 * 147 150 * When 148 - * * value passed to rolling destination is a string (`filePath` from LogOptions is a string) 151 + * * value passed to rolling destination is a string (`path` option) and 149 152 * * `frequency` is defined 150 153 * 151 154 * This determines the format of the datetime inserted into the log file name: 152 155 * 153 156 * * `unix` - unix epoch timestamp in milliseconds 154 - * * `iso` - Full [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) datetime IE '2024-03-07T20:11:34Z' 157 + * * `iso` - Full ISO8601 datetime IE '2024-03-07T20:11:34Z' 155 158 * * `auto` 156 159 * * When frequency is `daily` only inserts date IE YYYY-MM-DD 157 160 * * Otherwise inserts full ISO8601 datetime ··· 181 184 ``` 182 185 183 186 </details> 187 + 188 + ### Additional App Logger Configuration 189 + 190 + `loggerApp` and `loggerAppRolling` accept an optional second parameter, [`LoggerAppExtras`](https://foxxmd.github.io/logging/types/index.LoggerAppExtras.html) that allows adding additional log destinations or pino-pretty customization: 191 + 192 + ```ts 193 + export interface LoggerAppExtras { 194 + /** 195 + * Additional pino-pretty options that are applied to the built-in console/log streams 196 + * */ 197 + pretty?: PrettyOptions 198 + /** 199 + * Additional logging destinations to use alongside the built-in console/log stream. These can be any created by buildDestination* functions or other Pino Transports 200 + * */ 201 + destinations?: LogLevelStreamEntry[] 202 + } 203 + ``` 204 + 205 + Some defaults and convenience variables for pino-pretty options are available in `@foxxmd/logging/factory` prefixed with `PRETTY_`. See [factory variables docs](https://foxxmd.github.io/logging/modules/factory.html) for all options. 206 + 207 + An example using the extras parameter: 208 + 209 + ```ts 210 + import { loggerApp } from '@foxxmd/logging'; 211 + import { 212 + PRETTY_ISO8601, // replaces standard timestamp with ISO8601 format 213 + buildDestinationFile 214 + } from "@foxxmd/logging/factory"; 215 + 216 + const warnFileDestination = buildDestinationFile('warn', {path: './myLogs/warn.log'}); 217 + 218 + const logger = loggerApp({}, { 219 + destinations: [warnFileDestination], 220 + pretty: { 221 + translateTime: PRETTY_ISO8601 222 + } 223 + }); 224 + logger.debug('Test'); 225 + // [2024-03-07T11:27:41-05:00] DEBUG: Test 226 + ``` 227 + 228 + See [Building A Logger](#building-a-logger) for more information. 184 229 185 230 ## Usage 186 231
+6 -1
src/factory.ts
··· 1 - import {prettyConsole, prettyFile, prettyOptsFactory} from "./pretty.js"; 1 + import {prettyConsole, prettyFile, PRETTY_COLORS, PRETTY_COLORS_STR, PRETTY_LEVELS, PRETTY_LEVELS_STR, PRETTY_ISO8601, prettyOptsFactory} from "./pretty.js"; 2 2 import {buildDestinationStream, buildDestinationRollingFile, buildDestinationStdout, buildDestinationStderr, buildDestinationFile} from "./destinations.js"; 3 3 import {buildLogger} from './loggers.js'; 4 4 import {FileDestination, StreamDestination} from './types.js' ··· 6 6 export { 7 7 prettyConsole, 8 8 prettyFile, 9 + PRETTY_COLORS, 10 + PRETTY_COLORS_STR, 11 + PRETTY_LEVELS, 12 + PRETTY_LEVELS_STR, 13 + PRETTY_ISO8601, 9 14 prettyOptsFactory, 10 15 buildDestinationStream, 11 16 buildDestinationStdout,
+25 -7
src/loggers.ts
··· 1 1 import {parseLogOptions} from "./funcs.js"; 2 - import {Logger, LogLevel, LogLevelStreamEntry, LogOptions} from "./types.js"; 2 + import {Logger, LoggerAppExtras, LogLevel, LogLevelStreamEntry, LogOptions} from "./types.js"; 3 3 import {buildDestinationFile, buildDestinationRollingFile, buildDestinationStdout} from "./destinations.js"; 4 4 import {pino} from "pino"; 5 5 ··· 64 64 * */ 65 65 export const loggerDebug = buildLogger('debug', [buildDestinationStdout('debug')]); 66 66 67 - export const loggerApp = (config: LogOptions | object = {}) => { 67 + export const loggerApp = (config: LogOptions | object = {}, extras?: LoggerAppExtras) => { 68 + 69 + const { 70 + pretty = {}, 71 + destinations = [], 72 + } = extras || {}; 73 + 68 74 const options = parseLogOptions(config); 69 - const streams: LogLevelStreamEntry[] = [buildDestinationStdout(options.console)]; 75 + const streams: LogLevelStreamEntry[] = [ 76 + buildDestinationStdout(options.console, pretty), 77 + ...destinations 78 + ]; 70 79 71 80 let error: Error; 72 81 if(options.file.level !== false) { 73 82 try { 74 - const file = buildDestinationFile(options.file.level, {...options.file, append: true}); 83 + const file = buildDestinationFile(options.file.level, {...options.file, ...pretty, append: true}); 75 84 if (file !== undefined) { 76 85 streams.push(file); 77 86 } ··· 87 96 return logger; 88 97 } 89 98 90 - export const loggerAppRolling = async (config: LogOptions | object = {}) => { 99 + export const loggerAppRolling = async (config: LogOptions | object = {}, extras?: LoggerAppExtras) => { 100 + 101 + const { 102 + pretty = {}, 103 + destinations = [], 104 + } = extras || {}; 105 + 91 106 const options = parseLogOptions(config); 92 - const streams: LogLevelStreamEntry[] = [buildDestinationStdout(options.console)]; 107 + const streams: LogLevelStreamEntry[] = [ 108 + buildDestinationStdout(options.console, pretty), 109 + ...destinations 110 + ]; 93 111 94 112 let error: Error; 95 113 if(options.file.level !== false) { 96 114 try { 97 - const file = await buildDestinationRollingFile(options.file.level, options.file); 115 + const file = await buildDestinationRollingFile(options.file.level, {...options.file, ...pretty}); 98 116 if (file !== undefined) { 99 117 streams.push(file); 100 118 }
+75 -6
src/pretty.ts
··· 1 1 import {PrettyOptions} from "pino-pretty"; 2 2 import {CWD} from "./util.js"; 3 3 4 + /** 5 + * Additional levels included in @foxxmd/logging as an object 6 + * 7 + * These are always applied when using prettyOptsFactory() but can be overridden 8 + * */ 9 + export const PRETTY_LEVELS: Extract<PrettyOptions['customLevels'], object> = { 10 + verbose: 25, 11 + log: 21, 12 + }; 13 + /** 14 + * Additional levels included in @foxxmd/logging as a string 15 + * 16 + * These are always applied when using prettyOptsFactory() but can be overridden 17 + * */ 18 + export const PRETTY_LEVELS_STR: Extract<PrettyOptions['customLevels'], string> = 'verbose:25,log:21'; 19 + 20 + /** 21 + * Additional level colors included in @foxxmd/logging as an object 22 + * 23 + * These are always applied when using prettyOptsFactory() but can be overridden 24 + * */ 25 + export const PRETTY_COLORS_STR: Extract<PrettyOptions['customColors'], string> = 'verbose:magenta,log:greenBright'; 26 + /** 27 + * Additional level colors included in @foxxmd/logging as a string 28 + * 29 + * These are always applied when using prettyOptsFactory() but can be overridden 30 + * */ 31 + export const PRETTY_COLORS: Extract<PrettyOptions['customColors'], object> = { 32 + 'verbose': 'magenta', 33 + 'log': 'greenBright' 34 + } 35 + 36 + /** 37 + * Use on `translateTime` pino-pretty option to print timestamps in ISO8601 format 38 + * */ 39 + export const PRETTY_ISO8601 = 'SYS:yyyy-mm-dd"T"HH:MM:ssp'; 40 + 4 41 export const prettyOptsFactory = (opts: PrettyOptions = {}): PrettyOptions => { 42 + const {customLevels = {}, customColors = {}, ...rest} = opts; 43 + 5 44 return { 6 45 messageFormat: (log, messageKey, levelLabel, {colors}) => { 7 46 const labels: string[] = log.labels as string[] ?? []; ··· 18 57 hideObject: false, 19 58 ignore: 'pid,hostname,labels,err', 20 59 translateTime: 'SYS:standard', 21 - customLevels: { 22 - verbose: 25, 23 - log: 21, 24 - }, 25 - customColors: 'verbose:magenta,log:greenBright', 60 + customLevels: buildLevels(customLevels), 61 + customColors: buildColors(customColors), 26 62 colorizeObjects: true, 27 63 // @ts-ignore 28 64 useOnlyCustomProps: false, 29 - ...opts 65 + ...rest 30 66 } 31 67 } 68 + 69 + const buildLevels = (userLevels: PrettyOptions['customLevels'] = {}): PrettyOptions['customLevels'] => { 70 + let levels: PrettyOptions['customLevels']; 71 + if (typeof userLevels === 'string') { 72 + levels = `${PRETTY_LEVELS_STR},${userLevels}`; 73 + } else { 74 + levels = { 75 + ...PRETTY_LEVELS, 76 + ...userLevels 77 + } 78 + } 79 + return levels; 80 + } 81 + 82 + const buildColors = (userColors: PrettyOptions['customColors'] = {}): PrettyOptions['customColors'] => { 83 + // pino-pretty has a bug that assumes customColors is always a string 84 + // so we need to rebuild as string even if an object is given 85 + 86 + let colors: PrettyOptions['customColors']; 87 + if (typeof userColors === 'string') { 88 + colors = `${PRETTY_COLORS_STR},${userColors}`; 89 + } else { 90 + colors = { 91 + ...PRETTY_COLORS, 92 + ...userColors 93 + } 94 + colors = Object.entries(colors).reduce((acc, [k, v]) => { 95 + return acc.concat(`${k}:${v}`) 96 + }, []).join(','); 97 + } 98 + return colors; 99 + } 100 + 32 101 /** 33 102 * Pre-defined pretty options for use with console/stream output 34 103 *
+12 -1
src/types.ts
··· 88 88 * For rolling log files 89 89 * 90 90 * When 91 - * * value passed to rolling destination is a string (`filePath` from LogOptions is a string) 91 + * * value passed to rolling destination is a string (`path` from LogOptions is a string) and 92 92 * * `frequency` is defined 93 93 * 94 94 * This determines the format of the datetime inserted into the log file name: ··· 152 152 export type StreamDestination = Omit<PrettyOptions, 'destination'> & {destination: number | DestinationStream | NodeJS.WritableStream}; 153 153 154 154 export type LogOptionsParsed = Omit<Required<LogOptions>, 'file'> & { file: FileLogOptionsParsed } 155 + 156 + export interface LoggerAppExtras { 157 + /** 158 + * Additional pino-pretty options that are applied to the built-in console/log streams 159 + * */ 160 + pretty?: PrettyOptions 161 + /** 162 + * Additional logging destinations to use alongside the built-in console/log stream. These can be any created by buildDestination* functions or other [Pino Transports](https://getpino.io/#/docs/transports?id=known-transports) 163 + * */ 164 + destinations?: LogLevelStreamEntry[] 165 + }