Skip to main content
Module

std/log/README.md

Deno standard library
Go to Latest
File

Log

Usage

import * as log from "https://deno.land/std/log/mod.ts";

// Simple default logger out of the box. You can customize it
// by overriding logger and handler named "default", or providing
// additional logger configurations
log.debug("Hello world");
log.info("Hello world");
log.warning("Hello world");
log.error("Hello world");
log.critical("500 Internal server error");

// custom configuration with 2 loggers (the default and `tasks` loggers)
await log.setup({
  handlers: {
    console: new log.handlers.ConsoleHandler("DEBUG"),

    file: new log.handlers.FileHandler("WARNING", {
      filename: "./log.txt",
      // you can change format of output message using any keys in `LogRecord`
      formatter: "{levelName} {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 "WARNING" level
logger.warning("buzz"); // 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("buzz"); // 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
unknownLogger = log.getLogger("mystery");
unknownLogger.info("foobar"); // no-op

Advanced usage

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.

LogRecord

LogRecord is an object that encapsulates provided message and arguments as well some meta data that can be later used when formatting a message.

class LogRecord {
  readonly msg: string;
  readonly args: any[];
  readonly datetime: Date;
  readonly level: number;
  readonly levelName: string;
}

Handlers

Handlers are responsible for actual output of log messages. When a handler is called by a logger, it firstly checks that 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.

log module comes with three built-in handlers:

ConsoleHandler

This is the default logger. It will output color coded log messages to the console via console.log(). This logger takes HandlerOptions:

type FormatterFunction = (logRecord: LogRecord) => string;

interface HandlerOptions {
  formatter?: string | FormatterFunction; //see `Custom message format` below
}

FileHandler

This handler will output to a file using an optional mode (default is a, e.g. append). The file will grow indefinitely. This logger takes FileOptions:

interface FileHandlerOptions {
  formatter?: string | FormatterFunction; //see `Custom message format` below
  filename: string;
  mode?: LogMode; // 'a', 'w', 'x'
}

Behavior of the log modes is as follows:

  • 'a' - Default mode. Appends new log messages to the end of an existing log file, or create a new log file if none exists
  • 'w' - Upon creation of the handler, any existing log file will be removed and a new one created.
  • 'x' - This will create a new log file and throw an error if one already exists

This handler requires --allow-write permission on the log file.

RotatingFileHandler

This handler extends the functionality of the FileHandler by “rotating” the log file when it reaches a certain size. maxBytes specifies the maximum size in bytes that the log file can grow to before rolling over to a new one. If the size of the new log message plus the current log file size exceeds maxBytes then a roll over is triggered. When a roll over occurs, before the log message is written, the log file is renamed and appended with .1. If a .1 version already existed, it would have been renamed .2 first and so on. The maximum number of log files to keep is specified by maxBackupCount. After the renames are complete the log message is written to the original, now blank, file.

Example: Given log.txt, log.txt.1, log.txt.2 and log.txt.3, a maxBackupCount of 3 and a new log message which would cause log.txt to exceed maxBytes, then log.txt.2 would be renamed to log.txt.3 (thereby discarding the original contents of log.txt.3 since 3 is the maximum number of backups to keep), log.txt.1 would be renamed to log.txt.2, log.txt would be renamed to log.txt.1 and finally log.txt would be created from scratch where the new log message would be written.

Options for this handler are:

interface RotatingFileHandlerOptions {
  maxBytes: number;
  maxBackupCount: number;
  formatter?: string | FormatterFunction; //see `Custom message format` below
  filename: string;
  mode?: LogMode; // 'a', 'w', 'x'
}

Additional notes on mode as described above:

  • 'a' Default mode. As above, this will pick up where the logs left off in rotation, or create a new log file if it doesn’t exist.
  • 'w' in addition to starting with a clean filename, this mode will also cause any existing backups (up to maxBackupCount) to be deleted on setup giving a fully clean slate.
  • 'x' requires that neither filename, nor any backups (up to maxBackupCount), exist before setup

This handler requires both --allow-read and --allow-write permissions on the log files.

Custom message format

If you want to override default format of message you can define formatter option for handler. It can be either simple string-based format that uses LogRecord fields or more complicated function-based one that takes LogRecord as argument and outputs string.

Eg.

await log.setup({
  handlers: {
    stringFmt: new log.handlers.ConsoleHandler("DEBUG", {
      formatter: "[{levelName}] {msg}"
    }),

    functionFmt: new log.handlers.ConsoleHandler("DEBUG", {
      formatter: logRecord => {
        let msg = `${logRecord.level} ${logRecord.msg}`;

        logRecord.args.forEach((arg, index) => {
          msg += `, arg${index}: ${arg}`;
        });

        return msg;
      }
    }),
  },

  loggers: {
     default: {
         level: "DEBUG",
         handlers: ["stringFmt", "functionFmt"],
     },
  }
})

// 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

Custom handlers

Custom handlers can be implemented by subclassing BaseHandler or WriterHandler.

BaseHandler is bare-bones handler that has no output logic at all,

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 FileHandler and TestHandler.