# Log ## Usage ```ts 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(log.LogLevels.DEBUG, { 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: log.LogLevels.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. ```ts 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`: ```typescript 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`: ```typescript 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: ```typescript 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. ```ts 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`.