2019-02-02 09:41:54 -05:00
|
|
|
# Log
|
|
|
|
|
|
|
|
## Usage
|
2018-12-19 13:16:45 -05:00
|
|
|
|
2019-01-02 09:12:48 -05:00
|
|
|
```ts
|
2019-03-06 10:24:53 -05:00
|
|
|
import * as log from "https://deno.land/std/log/mod.ts";
|
2018-12-19 13:16:45 -05:00
|
|
|
|
2020-04-09 07:45:24 -04:00
|
|
|
// Simple default logger out of the box. You can customize it
|
|
|
|
// by overriding logger and handler named "default", or providing
|
2020-06-12 09:27:41 -04:00
|
|
|
// additional logger configurations. You can log any data type.
|
2019-01-02 09:12:48 -05:00
|
|
|
log.debug("Hello world");
|
2020-06-12 09:27:41 -04:00
|
|
|
log.info(123456);
|
|
|
|
log.warning(true);
|
|
|
|
log.error({ foo: "bar", fizz: "bazz" });
|
2019-01-02 09:12:48 -05:00
|
|
|
log.critical("500 Internal server error");
|
2018-12-19 13:16:45 -05:00
|
|
|
|
2020-04-09 07:45:24 -04:00
|
|
|
// custom configuration with 2 loggers (the default and `tasks` loggers)
|
2019-01-02 09:12:48 -05:00
|
|
|
await log.setup({
|
2019-01-01 22:46:17 -05:00
|
|
|
handlers: {
|
|
|
|
console: new log.handlers.ConsoleHandler("DEBUG"),
|
2019-02-02 09:41:54 -05:00
|
|
|
|
2020-04-29 09:38:44 -04:00
|
|
|
file: new log.handlers.FileHandler("WARNING", {
|
2019-02-02 09:41:54 -05:00
|
|
|
filename: "./log.txt",
|
2020-04-09 07:45:24 -04:00
|
|
|
// you can change format of output message using any keys in `LogRecord`
|
2020-03-28 13:03:49 -04:00
|
|
|
formatter: "{levelName} {msg}",
|
|
|
|
}),
|
2019-01-01 22:46:17 -05:00
|
|
|
},
|
2018-12-24 10:28:01 -05:00
|
|
|
|
2019-01-01 22:46:17 -05:00
|
|
|
loggers: {
|
2019-02-02 09:41:54 -05:00
|
|
|
// configure default logger available via short-hand methods above
|
2019-01-01 22:46:17 -05:00
|
|
|
default: {
|
|
|
|
level: "DEBUG",
|
2020-03-28 13:03:49 -04:00
|
|
|
handlers: ["console", "file"],
|
2019-02-02 09:41:54 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
tasks: {
|
2020-04-29 09:38:44 -04:00
|
|
|
level: "ERROR",
|
2020-03-28 13:03:49 -04:00
|
|
|
handlers: ["console"],
|
|
|
|
},
|
|
|
|
},
|
2019-01-02 09:12:48 -05:00
|
|
|
});
|
|
|
|
|
2019-02-02 09:41:54 -05:00
|
|
|
let logger;
|
|
|
|
|
|
|
|
// get default logger
|
|
|
|
logger = log.getLogger();
|
|
|
|
logger.debug("fizz"); // logs to `console`, because `file` handler requires "WARNING" level
|
2020-06-12 09:27:41 -04:00
|
|
|
logger.warning(41256); // logs to both `console` and `file` handlers
|
2019-02-02 09:41:54 -05:00
|
|
|
|
|
|
|
// get custom logger
|
|
|
|
logger = log.getLogger("tasks");
|
2020-04-09 07:45:24 -04:00
|
|
|
logger.debug("fizz"); // won't get output because this logger has "ERROR" level
|
2020-06-12 09:27:41 -04:00
|
|
|
logger.error({ productType: "book", value: "126.11" }); // log to `console`
|
2019-01-02 09:12:48 -05:00
|
|
|
|
|
|
|
// 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
|
2020-04-29 09:38:44 -04:00
|
|
|
const unknownLogger = log.getLogger("mystery");
|
2019-01-01 22:46:17 -05:00
|
|
|
unknownLogger.info("foobar"); // no-op
|
|
|
|
```
|
2019-02-02 09:41:54 -05:00
|
|
|
|
|
|
|
## Advanced usage
|
|
|
|
|
|
|
|
### Loggers
|
|
|
|
|
2020-04-09 07:45:24 -04:00
|
|
|
Loggers are objects that you interact with. When you use a logger method it
|
2019-10-09 17:22:22 -04:00
|
|
|
constructs a `LogRecord` and passes it down to its handlers for output. To
|
2020-04-09 07:45:24 -04:00
|
|
|
create custom loggers, specify them in `loggers` when calling `log.setup`.
|
2019-02-02 09:41:54 -05:00
|
|
|
|
|
|
|
#### `LogRecord`
|
|
|
|
|
2019-10-09 17:22:22 -04:00
|
|
|
`LogRecord` is an object that encapsulates provided message and arguments as
|
|
|
|
well some meta data that can be later used when formatting a message.
|
2019-02-02 09:41:54 -05:00
|
|
|
|
|
|
|
```ts
|
2020-04-09 07:45:24 -04:00
|
|
|
class LogRecord {
|
|
|
|
readonly msg: string;
|
|
|
|
readonly args: any[];
|
|
|
|
readonly datetime: Date;
|
|
|
|
readonly level: number;
|
|
|
|
readonly levelName: string;
|
2019-02-02 09:41:54 -05:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### Handlers
|
|
|
|
|
2020-04-09 07:45:24 -04:00
|
|
|
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
|
2019-10-09 17:22:22 -04:00
|
|
|
level of the handler. If level check passes, handlers formats log record into
|
|
|
|
string and outputs it to target.
|
2019-02-02 09:41:54 -05:00
|
|
|
|
2020-04-09 07:45:24 -04:00
|
|
|
`log` module comes with three built-in handlers:
|
2019-02-02 09:41:54 -05:00
|
|
|
|
2020-04-09 07:45:24 -04:00
|
|
|
#### `ConsoleHandler`
|
2019-02-02 09:41:54 -05:00
|
|
|
|
2020-04-09 07:45:24 -04:00
|
|
|
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.
|
2020-06-05 23:39:49 -04:00
|
|
|
append). The file will grow indefinitely. It uses a buffer for writing to file.
|
|
|
|
Logs can be manually flushed with `fileHandler.flush()`. Log messages with a log
|
|
|
|
level greater than error are immediately flushed. Logs are also flushed on
|
|
|
|
process completion. This logger takes `FileOptions`:
|
2020-04-09 07:45:24 -04:00
|
|
|
|
|
|
|
```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.
|
|
|
|
|
2020-06-05 23:39:49 -04:00
|
|
|
This handler uses a buffer for writing log messages to file. Logs can be
|
|
|
|
manually flushed with `fileHandler.flush()`. Log messages with a log level
|
|
|
|
greater than ERROR are immediately flushed. Logs are also flushed on process
|
|
|
|
completion.
|
2020-06-01 18:31:17 -04:00
|
|
|
|
2020-04-09 07:45:24 -04:00
|
|
|
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
|
2019-02-02 09:41:54 -05:00
|
|
|
|
2019-10-09 17:22:22 -04:00
|
|
|
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.
|
2019-02-02 09:41:54 -05:00
|
|
|
|
|
|
|
Eg.
|
|
|
|
|
|
|
|
```ts
|
|
|
|
await log.setup({
|
|
|
|
handlers: {
|
|
|
|
stringFmt: new log.handlers.ConsoleHandler("DEBUG", {
|
|
|
|
formatter: "[{levelName}] {msg}"
|
|
|
|
}),
|
|
|
|
|
|
|
|
functionFmt: new log.handlers.ConsoleHandler("DEBUG", {
|
|
|
|
formatter: logRecord => {
|
2019-04-30 11:19:55 -04:00
|
|
|
let msg = `${logRecord.level} ${logRecord.msg}`;
|
2019-02-02 09:41:54 -05:00
|
|
|
|
|
|
|
logRecord.args.forEach((arg, index) => {
|
2019-04-30 11:19:55 -04:00
|
|
|
msg += `, arg${index}: ${arg}`;
|
2019-02-02 09:41:54 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2019-10-09 17:22:22 -04:00
|
|
|
Custom handlers can be implemented by subclassing `BaseHandler` or
|
|
|
|
`WriterHandler`.
|
2019-02-02 09:41:54 -05:00
|
|
|
|
|
|
|
`BaseHandler` is bare-bones handler that has no output logic at all,
|
|
|
|
|
2019-10-09 17:22:22 -04:00
|
|
|
`WriterHandler` is an abstract class that supports any target with `Writer`
|
|
|
|
interface.
|
2019-02-02 09:41:54 -05:00
|
|
|
|
2019-10-09 17:22:22 -04:00
|
|
|
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.
|
2019-02-02 09:41:54 -05:00
|
|
|
|
|
|
|
For examples check source code of `FileHandler` and `TestHandler`.
|
2020-06-12 09:27:41 -04:00
|
|
|
|
|
|
|
### Inline Logging
|
|
|
|
|
|
|
|
Log functions return the data passed in the `msg` parameter. Data is returned
|
|
|
|
regardless if the logger actually logs it.
|
|
|
|
|
|
|
|
```ts
|
|
|
|
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
|
|
|
|
```
|
|
|
|
|
|
|
|
### 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.
|
|
|
|
|
|
|
|
```ts
|
|
|
|
// `expensiveFn(5)` is only evaluated if this logger is configured for debug logging
|
|
|
|
logger.debug(() => `this is expensive: ${expensiveFn(5)}`);
|
|
|
|
```
|
|
|
|
|
|
|
|
NOTE: When using lazy log evaluation, `undefined` will be returned if the
|
|
|
|
resolver function is not called because the logger won't log it. E.g.
|
|
|
|
|
|
|
|
```ts
|
|
|
|
await log.setup({
|
|
|
|
handlers: {
|
|
|
|
console: new log.handlers.ConsoleHandler("DEBUG"),
|
|
|
|
},
|
|
|
|
|
|
|
|
loggers: {
|
|
|
|
tasks: {
|
|
|
|
level: "ERROR",
|
|
|
|
handlers: ["console"],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
// not logged, as debug < error
|
|
|
|
const data: string | undefined = logger.debug(() => someExpenseFn(5, true));
|
|
|
|
console.log(data); // undefined
|
|
|
|
```
|