// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. /** * Logging library with the support for terminal and file outputs. Also provides * interfaces for building custom loggers. * * ## Loggers * * Loggers are objects that you interact with. When you use a logger method it * constructs a `LogRecord` and passes it down to its handlers for output. To * create custom loggers, specify them in `loggers` when calling `log.setup`. * * ## Custom message format * * If you want to override default format of message you can define `formatter` * option for handler. It can a function that takes `LogRecord` * as argument and outputs string. * * The default log format is `{levelName} {msg}`. * * ### Logging Structured JSON Lines * * To output logs in a structured JSON format you can configure most handlers * with a formatter that produces a JSON string. Either use the premade * `log.formatters.jsonFormatter` or write your own function that takes a * {@linkcode LogRecord} and returns a JSON.stringify'd object. * If you want the log to go to stdout then use {@linkcode ConsoleHandler} with * the configuration `useColors: false` to turn off the ANSI terminal colors. * * ```ts * import * as log from "@std/log"; * * log.setup({ * handlers: { * default: new log.ConsoleHandler("DEBUG", { * formatter: log.formatters.jsonFormatter, * useColors: false, * }), * }, * }); * ``` * * The first argument passed to a log function is always treated as the * message and will be stringified differently. To have arguments JSON.stringify'd * you must pass them after the first. * * ```ts * import * as log from "@std/log"; * * log.info("This is the message", { thisWillBe: "JSON.stringify'd"}); * // {"level":"INFO","datetime":1702501580505,"message":"This is the message","args":{"thisWillBe":"JSON.stringify'd"}} * * log.info({ thisWontBe: "JSON.stringify'd"}, "This is an argument"); * // {"level":"INFO","datetime":1702501580505,"message":"{\"thisWontBe\":\"JSON.stringify'd\"}","args":"This is an argument"} * ``` * * ## Inline Logging * * Log functions return the data passed in the `msg` parameter. Data is returned * regardless if the logger actually logs it. * * ## Lazy Log Evaluation * * Some log statements are expensive to compute. In these cases, you can use * lazy log evaluation to prevent the computation taking place if the logger * won't log the message. * * > NOTE: When using lazy log evaluation, `undefined` will be returned if the * > resolver function is not called because the logger won't log it. It is an * > antipattern use lazy evaluation with inline logging because the return value * > depends on the current log level. * * ## For module authors * * The authors of public modules can let the users display the internal logs of the * module by using a custom logger: * * ```ts * import { getLogger } from "@std/log"; * * function logger() { * return getLogger("my-awesome-module"); * } * * function sum(a: number, b: number) { * logger().debug(`running ${a} + ${b}`); * return a + b; * } * * function mult(a: number, b: number) { * logger().debug(`running ${a} * ${b}`); * return a * b; * } * ``` * * The user of the module can then display the internal logs with: * * ```ts, ignore * import * as log from "@std/log"; * import { sum } from "/mod.ts"; * * log.setup({ * handlers: { * console: new log.ConsoleHandler("DEBUG"), * }, * * loggers: { * "my-awesome-module": { * level: "DEBUG", * handlers: ["console"], * }, * }, * }); * * sum(1, 2); // prints "running 1 + 2" to the console * ``` * * Please note that, due to the order of initialization of the loggers, the * following won't work: * * ```ts * import { getLogger } from "@std/log"; * * const logger = getLogger("my-awesome-module"); * * function sum(a: number, b: number) { * logger.debug(`running ${a} + ${b}`); // no message will be logged, because getLogger() was called before log.setup() * return a + b; * } * ``` * * @example * ```ts ignore * import * as log from "@std/log"; * * // Simple default logger out of the box. You can customize it * // by overriding logger and handler named "default", or providing * // additional logger configurations. You can log any data type. * log.debug("Hello world"); * log.info(123456); * log.warn(true); * log.error({ foo: "bar", fizz: "bazz" }); * log.critical("500 Internal server error"); * * // custom configuration with 2 loggers (the default and `tasks` loggers). * log.setup({ * handlers: { * console: new log.ConsoleHandler("DEBUG"), * * file: new log.FileHandler("WARN", { * filename: "./log.txt", * // you can change format of output message using any keys in `LogRecord`. * formatter: (record) => `${record.levelName} ${record.msg}`, * }), * }, * * loggers: { * // configure default logger available via short-hand methods above. * default: { * level: "DEBUG", * handlers: ["console", "file"], * }, * * tasks: { * level: "ERROR", * handlers: ["console"], * }, * }, * }); * * let logger; * * // get default logger. * logger = log.getLogger(); * logger.debug("fizz"); // logs to `console`, because `file` handler requires "WARN" level. * logger.warn(41256); // logs to both `console` and `file` handlers. * * // get custom logger * logger = log.getLogger("tasks"); * logger.debug("fizz"); // won't get output because this logger has "ERROR" level. * logger.error({ productType: "book", value: "126.11" }); // log to `console`. * * // if you try to use a logger that hasn't been configured * // you're good to go, it gets created automatically with level set to 0 * // so no message is logged. * const unknownLogger = log.getLogger("mystery"); * unknownLogger.info("foobar"); // no-op * ``` * * @example * Custom message format example * ```ts * import * as log from "@std/log"; * * log.setup({ * handlers: { * stringFmt: new log.ConsoleHandler("DEBUG", { * formatter: (record) => `[${record.levelName}] ${record.msg}`, * }), * * functionFmt: new log.ConsoleHandler("DEBUG", { * formatter: (logRecord) => { * let msg = `${logRecord.level} ${logRecord.msg}`; * * logRecord.args.forEach((arg, index) => { * msg += `, arg${index}: ${arg}`; * }); * * return msg; * }, * }), * * anotherFmt: new log.ConsoleHandler("DEBUG", { * formatter: (record) => `[${record.loggerName}] - ${record.levelName} ${record.msg}`, * }), * }, * * loggers: { * default: { * level: "DEBUG", * handlers: ["stringFmt", "functionFmt"], * }, * dataLogger: { * level: "INFO", * handlers: ["anotherFmt"], * }, * }, * }); * * // calling: * log.debug("Hello, world!", 1, "two", [3, 4, 5]); * // results in: [DEBUG] Hello, world! * // output from "stringFmt" handler. * // 10 Hello, world!, arg0: 1, arg1: two, arg3: [3, 4, 5] // output from "functionFmt" formatter. * * // calling: * log.getLogger("dataLogger").error("oh no!"); * // results in: * // [dataLogger] - ERROR oh no! // output from anotherFmt handler. * ``` * * @example * JSON to stdout with no color example * ```ts * import * as log from "@std/log"; * * log.setup({ * handlers: { * jsonStdout: new log.ConsoleHandler("DEBUG", { * formatter: log.formatters.jsonFormatter, * useColors: false, * }), * }, * * loggers: { * default: { * level: "DEBUG", * handlers: ["jsonStdout"], * }, * }, * }); * * // calling: * log.info("Hey"); * // results in: * // {"level":"INFO","datetime":1702481922294,"message":"Hey"} * * // calling: * log.info("Hey", { product: "nail" }); * // results in: * // {"level":"INFO","datetime":1702484111115,"message":"Hey","args":{"product":"nail"}} * * // calling: * log.info("Hey", 1, "two", [3, 4, 5]); * // results in: * // {"level":"INFO","datetime":1702481922294,"message":"Hey","args":[1,"two",[3,4,5]]} * ``` * * @example * Custom JSON example * ```ts * import * as log from "@std/log"; * * log.setup({ * handlers: { * customJsonFmt: new log.ConsoleHandler("DEBUG", { * formatter: (record) => JSON.stringify({ * lvl: record.level, * msg: record.msg, * time: record.datetime.toISOString(), * name: record.loggerName, * }), * useColors: false, * }), * }, * * loggers: { * default: { * level: "DEBUG", * handlers: ["customJsonFmt"], * }, * }, * }); * * // calling: * log.info("complete"); * // results in: * // {"lvl":20,"msg":"complete","time":"2023-12-13T16:38:27.328Z","name":"default"} * ``` * * @example * Inline Logging * ```ts * import * as logger from "@std/log"; * * const stringData: string = logger.debug("hello world"); * const booleanData: boolean = logger.debug(true, 1, "abc"); * const fn = (): number => { * return 123; * }; * const resolvedFunctionData: number = logger.debug(fn()); * console.log(stringData); // 'hello world' * console.log(booleanData); // true * console.log(resolvedFunctionData); // 123 * ``` * * @example * Lazy Log Evaluation * ```ts * import * as log from "@std/log"; * * log.setup({ * handlers: { * console: new log.ConsoleHandler("DEBUG"), * }, * * loggers: { * tasks: { * level: "ERROR", * handlers: ["console"], * }, * }, * }); * * function someExpensiveFn(num: number, bool: boolean) { * // do some expensive computation * } * * // not logged, as debug < error. * const data = log.debug(() => someExpensiveFn(5, true)); * console.log(data); // undefined * ``` * * Handlers are responsible for actual output of log messages. When a handler is * called by a logger, it firstly checks that {@linkcode LogRecord}'s level is * not lower than level of the handler. If level check passes, handlers formats * log record into string and outputs it to target. * * ## Custom handlers * * Custom handlers can be implemented by subclassing {@linkcode BaseHandler} or * {@linkcode WriterHandler}. * * {@linkcode BaseHandler} is bare-bones handler that has no output logic at all, * * {@linkcode WriterHandler} is an abstract class that supports any target with * `Writer` interface. * * During setup async hooks `setup` and `destroy` are called, you can use them * to open and close file/HTTP connection or any other action you might need. * * For examples check source code of {@linkcode FileHandler}` * and {@linkcode TestHandler}. * * @module */ export * from "./base_handler.ts"; export * from "./console_handler.ts"; export * from "./file_handler.ts"; export * from "./rotating_file_handler.ts"; export * from "./levels.ts"; export * from "./logger.ts"; export * from "./formatters.ts"; export * from "./critical.ts"; export * from "./debug.ts"; export * from "./error.ts"; export * from "./get_logger.ts"; export * from "./info.ts"; export * from "./setup.ts"; export * from "./warn.ts";