This release is 1 version behind 0.8.0 — the latest version of @logtape/logtape. Jump to latest
Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
Built and signed on GitHub Actions
Simple logging library with zero dependencies for Deno/Node.js/Bun/browsers
This package works with Cloudflare Workers, Node.js, Deno, Bun, Browsers
JSR Score
100%
Published
a month ago (0.7.1)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546import type { LogLevel } from "./level.ts"; import util from "./nodeUtil.ts"; import type { LogRecord } from "./record.ts"; /** * A text formatter is a function that accepts a log record and returns * a string. * * @param record The log record to format. * @returns The formatted log record. */ export type TextFormatter = (record: LogRecord) => string; /** * The severity level abbreviations. */ const levelAbbreviations: Record<LogLevel, string> = { "debug": "DBG", "info": "INF", "warning": "WRN", "error": "ERR", "fatal": "FTL", }; /** * A platform-specific inspect function. In Deno, this is {@link Deno.inspect}, * and in Node.js/Bun it is `util.inspect()`. If neither is available, it * falls back to {@link JSON.stringify}. * * @param value The value to inspect. * @param options The options for inspecting the value. * If `colors` is `true`, the output will be ANSI-colored. * @returns The string representation of the value. */ const inspect: (value: unknown, options?: { colors?: boolean }) => string = // @ts-ignore: Deno global // dnt-shim-ignore "Deno" in globalThis && "inspect" in globalThis.Deno && // @ts-ignore: Deno global // dnt-shim-ignore typeof globalThis.Deno.inspect === "function" // @ts-ignore: Deno global // dnt-shim-ignore ? globalThis.Deno.inspect.bind(globalThis.Deno) // @ts-ignore: Node.js global // dnt-shim-ignore : util != null && "inspect" in util && typeof util.inspect === "function" // @ts-ignore: Node.js global // dnt-shim-ignore ? util.inspect.bind(util) : (v) => JSON.stringify(v); /** * The formatted values for a log record. * @since 0.6.0 */ export interface FormattedValues { /** * The formatted timestamp. */ timestamp: string; /** * The formatted log level. */ level: string; /** * The formatted category. */ category: string; /** * The formatted message. */ message: string; /** * The unformatted log record. */ record: LogRecord; } /** * The various options for the built-in text formatters. * @since 0.6.0 */ export interface TextFormatterOptions { /** * The timestamp format. This can be one of the following: * * - `"date-time-timezone"`: The date and time with the full timezone offset * (e.g., `"2023-11-14 22:13:20.000 +00:00"`). * - `"date-time-tz"`: The date and time with the short timezone offset * (e.g., `"2023-11-14 22:13:20.000 +00"`). * - `"date-time"`: The date and time without the timezone offset * (e.g., `"2023-11-14 22:13:20.000"`). * - `"time-timezone"`: The time with the full timezone offset but without * the date (e.g., `"22:13:20.000 +00:00"`). * - `"time-tz"`: The time with the short timezone offset but without the date * (e.g., `"22:13:20.000 +00"`). * - `"time"`: The time without the date or timezone offset * (e.g., `"22:13:20.000"`). * - `"date"`: The date without the time or timezone offset * (e.g., `"2023-11-14"`). * - `"rfc3339"`: The date and time in RFC 3339 format * (e.g., `"2023-11-14T22:13:20.000Z"`). * * Alternatively, this can be a function that accepts a timestamp and returns * a string. * * The default is `"date-time-timezone"`. */ timestamp?: | "date-time-timezone" | "date-time-tz" | "date-time" | "time-timezone" | "time-tz" | "time" | "date" | "rfc3339" | ((ts: number) => string); /** * The log level format. This can be one of the following: * * - `"ABBR"`: The log level abbreviation in uppercase (e.g., `"INF"`). * - `"FULL"`: The full log level name in uppercase (e.g., `"INFO"`). * - `"L"`: The first letter of the log level in uppercase (e.g., `"I"`). * - `"abbr"`: The log level abbreviation in lowercase (e.g., `"inf"`). * - `"full"`: The full log level name in lowercase (e.g., `"info"`). * - `"l"`: The first letter of the log level in lowercase (e.g., `"i"`). * * Alternatively, this can be a function that accepts a log level and returns * a string. * * The default is `"ABBR"`. */ level?: | "ABBR" | "FULL" | "L" | "abbr" | "full" | "l" | ((level: LogLevel) => string); /** * The separator between category names. For example, if the separator is * `"·"`, the category `["a", "b", "c"]` will be formatted as `"a·b·c"`. * The default separator is `"·"`. * * If this is a function, it will be called with the category array and * should return a string, which will be used for rendering the category. */ category?: string | ((category: readonly string[]) => string); /** * The format of the embedded values. * * A function that renders a value to a string. This function is used to * render the values in the log record. The default is [`util.inspect()`] in * Node.js/Bun and [`Deno.inspect()`] in Deno. * * [`util.inspect()`]: https://nodejs.org/api/util.html#utilinspectobject-options * [`Deno.inspect()`]: https://docs.deno.com/api/deno/~/Deno.inspect * @param value The value to render. * @returns The string representation of the value. */ value?: (value: unknown) => string; /** * How those formatted parts are concatenated. * * A function that formats the log record. This function is called with the * formatted values and should return a string. Note that the formatted * *should not* include a newline character at the end. * * By default, this is a function that formats the log record as follows: * * ``` * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world! * ``` * @param values The formatted values. * @returns The formatted log record. */ format?: (values: FormattedValues) => string; } /** * Get a text formatter with the specified options. Although it's flexible * enough to create a custom formatter, if you want more control, you can * create a custom formatter that satisfies the {@link TextFormatter} type * instead. * * For more information on the options, see {@link TextFormatterOptions}. * * By default, the formatter formats log records as follows: * * ``` * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world! * ``` * @param options The options for the text formatter. * @returns The text formatter. * @since 0.6.0 */ export function getTextFormatter( options: TextFormatterOptions = {}, ): TextFormatter { const timestampRenderer = options.timestamp == null || options.timestamp === "date-time-timezone" ? (ts: number): string => new Date(ts).toISOString().replace("T", " ").replace("Z", " +00:00") : options.timestamp === "date-time-tz" ? (ts: number): string => new Date(ts).toISOString().replace("T", " ").replace("Z", " +00") : options.timestamp === "date-time" ? (ts: number): string => new Date(ts).toISOString().replace("T", " ").replace("Z", "") : options.timestamp === "time-timezone" ? (ts: number): string => new Date(ts).toISOString().replace(/.*T/, "").replace("Z", " +00:00") : options.timestamp === "time-tz" ? (ts: number): string => new Date(ts).toISOString().replace(/.*T/, "").replace("Z", " +00") : options.timestamp === "time" ? (ts: number): string => new Date(ts).toISOString().replace(/.*T/, "").replace("Z", "") : options.timestamp === "date" ? (ts: number): string => new Date(ts).toISOString().replace(/T.*/, "") : options.timestamp === "rfc3339" ? (ts: number): string => new Date(ts).toISOString() : options.timestamp; const categorySeparator = options.category ?? "·"; const valueRenderer = options.value ?? inspect; const levelRenderer = options.level == null || options.level === "ABBR" ? (level: LogLevel): string => levelAbbreviations[level] : options.level === "abbr" ? (level: LogLevel): string => levelAbbreviations[level].toLowerCase() : options.level === "FULL" ? (level: LogLevel): string => level.toUpperCase() : options.level === "full" ? (level: LogLevel): string => level : options.level === "L" ? (level: LogLevel): string => level.charAt(0).toUpperCase() : options.level === "l" ? (level: LogLevel): string => level.charAt(0) : options.level; const formatter: (values: FormattedValues) => string = options.format ?? (({ timestamp, level, category, message }: FormattedValues) => `${timestamp} [${level}] ${category}: ${message}`); return (record: LogRecord): string => { let message = ""; for (let i = 0; i < record.message.length; i++) { if (i % 2 === 0) message += record.message[i]; else message += valueRenderer(record.message[i]); } const timestamp = timestampRenderer(record.timestamp); const level = levelRenderer(record.level); const category = typeof categorySeparator === "function" ? categorySeparator(record.category) : record.category.join(categorySeparator); const values: FormattedValues = { timestamp, level, category, message, record, }; return `${formatter(values)}\n`; }; } /** * The default text formatter. This formatter formats log records as follows: * * ``` * 2023-11-14 22:13:20.000 +00:00 [INF] category·subcategory: Hello, world! * ``` * * @param record The log record to format. * @returns The formatted log record. */ export const defaultTextFormatter: TextFormatter = getTextFormatter(); const RESET = "\x1b[0m"; /** * The ANSI colors. These can be used to colorize text in the console. * @since 0.6.0 */ export type AnsiColor = | "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white"; const ansiColors: Record<AnsiColor, string> = { black: "\x1b[30m", red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m", blue: "\x1b[34m", magenta: "\x1b[35m", cyan: "\x1b[36m", white: "\x1b[37m", }; /** * The ANSI text styles. * @since 0.6.0 */ export type AnsiStyle = | "bold" | "dim" | "italic" | "underline" | "strikethrough"; const ansiStyles: Record<AnsiStyle, string> = { bold: "\x1b[1m", dim: "\x1b[2m", italic: "\x1b[3m", underline: "\x1b[4m", strikethrough: "\x1b[9m", }; const defaultLevelColors: Record<LogLevel, AnsiColor | null> = { debug: "blue", info: "green", warning: "yellow", error: "red", fatal: "magenta", }; /** * The various options for the ANSI color formatter. * @since 0.6.0 */ export interface AnsiColorFormatterOptions extends TextFormatterOptions { /** * The timestamp format. This can be one of the following: * * - `"date-time-timezone"`: The date and time with the full timezone offset * (e.g., `"2023-11-14 22:13:20.000 +00:00"`). * - `"date-time-tz"`: The date and time with the short timezone offset * (e.g., `"2023-11-14 22:13:20.000 +00"`). * - `"date-time"`: The date and time without the timezone offset * (e.g., `"2023-11-14 22:13:20.000"`). * - `"time-timezone"`: The time with the full timezone offset but without * the date (e.g., `"22:13:20.000 +00:00"`). * - `"time-tz"`: The time with the short timezone offset but without the date * (e.g., `"22:13:20.000 +00"`). * - `"time"`: The time without the date or timezone offset * (e.g., `"22:13:20.000"`). * - `"date"`: The date without the time or timezone offset * (e.g., `"2023-11-14"`). * - `"rfc3339"`: The date and time in RFC 3339 format * (e.g., `"2023-11-14T22:13:20.000Z"`). * * Alternatively, this can be a function that accepts a timestamp and returns * a string. * * The default is `"date-time-tz"`. */ timestamp?: | "date-time-timezone" | "date-time-tz" | "date-time" | "time-timezone" | "time-tz" | "time" | "date" | "rfc3339" | ((ts: number) => string); /** * The ANSI style for the timestamp. `"dim"` is used by default. */ timestampStyle?: AnsiStyle | null; /** * The ANSI color for the timestamp. No color is used by default. */ timestampColor?: AnsiColor | null; /** * The ANSI style for the log level. `"bold"` is used by default. */ levelStyle?: AnsiStyle | null; /** * The ANSI colors for the log levels. The default colors are as follows: * * - `"debug"`: `"blue"` * - `"info"`: `"green"` * - `"warning"`: `"yellow"` * - `"error"`: `"red"` * - `"fatal"`: `"magenta"` */ levelColors?: Record<LogLevel, AnsiColor | null>; /** * The ANSI style for the category. `"dim"` is used by default. */ categoryStyle?: AnsiStyle | null; /** * The ANSI color for the category. No color is used by default. */ categoryColor?: AnsiColor | null; } /** * Get an ANSI color formatter with the specified options. * * ![A preview of an ANSI color formatter.](https://i.imgur.com/I8LlBUf.png) * @param option The options for the ANSI color formatter. * @returns The ANSI color formatter. * @since 0.6.0 */ export function getAnsiColorFormatter( options: AnsiColorFormatterOptions = {}, ): TextFormatter { const format = options.format; const timestampStyle = typeof options.timestampStyle === "undefined" ? "dim" : options.timestampStyle; const timestampColor = options.timestampColor ?? null; const timestampPrefix = `${ timestampStyle == null ? "" : ansiStyles[timestampStyle] }${timestampColor == null ? "" : ansiColors[timestampColor]}`; const timestampSuffix = timestampStyle == null && timestampColor == null ? "" : RESET; const levelStyle = typeof options.levelStyle === "undefined" ? "bold" : options.levelStyle; const levelColors = options.levelColors ?? defaultLevelColors; const categoryStyle = typeof options.categoryStyle === "undefined" ? "dim" : options.categoryStyle; const categoryColor = options.categoryColor ?? null; const categoryPrefix = `${ categoryStyle == null ? "" : ansiStyles[categoryStyle] }${categoryColor == null ? "" : ansiColors[categoryColor]}`; const categorySuffix = categoryStyle == null && categoryColor == null ? "" : RESET; return getTextFormatter({ timestamp: "date-time-tz", value(value: unknown): string { return inspect(value, { colors: true }); }, ...options, format({ timestamp, level, category, message, record }): string { const levelColor = levelColors[record.level]; timestamp = `${timestampPrefix}${timestamp}${timestampSuffix}`; level = `${levelStyle == null ? "" : ansiStyles[levelStyle]}${ levelColor == null ? "" : ansiColors[levelColor] }${level}${levelStyle == null && levelColor == null ? "" : RESET}`; return format == null ? `${timestamp} ${level} ${categoryPrefix}${category}:${categorySuffix} ${message}` : format({ timestamp, level, category: `${categoryPrefix}${category}${categorySuffix}`, message, record, }); }, }); } /** * A text formatter that uses ANSI colors to format log records. * * ![A preview of ansiColorFormatter.](https://i.imgur.com/I8LlBUf.png) * * @param record The log record to format. * @returns The formatted log record. * @since 0.5.0 */ export const ansiColorFormatter: TextFormatter = getAnsiColorFormatter(); /** * A console formatter is a function that accepts a log record and returns * an array of arguments to pass to {@link console.log}. * * @param record The log record to format. * @returns The formatted log record, as an array of arguments for * {@link console.log}. */ export type ConsoleFormatter = (record: LogRecord) => readonly unknown[]; /** * The styles for the log level in the console. */ const logLevelStyles: Record<LogLevel, string> = { "debug": "background-color: gray; color: white;", "info": "background-color: white; color: black;", "warning": "background-color: orange; color: black;", "error": "background-color: red; color: white;", "fatal": "background-color: maroon; color: white;", }; /** * The default console formatter. * * @param record The log record to format. * @returns The formatted log record, as an array of arguments for * {@link console.log}. */ export function defaultConsoleFormatter(record: LogRecord): readonly unknown[] { let msg = ""; const values: unknown[] = []; for (let i = 0; i < record.message.length; i++) { if (i % 2 === 0) msg += record.message[i]; else { msg += "%o"; values.push(record.message[i]); } } const date = new Date(record.timestamp); const time = `${date.getUTCHours().toString().padStart(2, "0")}:${ date.getUTCMinutes().toString().padStart(2, "0") }:${date.getUTCSeconds().toString().padStart(2, "0")}.${ date.getUTCMilliseconds().toString().padStart(3, "0") }`; return [ `%c${time} %c${levelAbbreviations[record.level]}%c %c${ record.category.join("\xb7") } %c${msg}`, "color: gray;", logLevelStyles[record.level], "background-color: default;", "color: gray;", "color: default;", ...values, ]; }