npm package: @n7e/log
Description: A lightweight and extendable logging library.
Author: Martin Pettersson
Latest version: 0.3.0

# Log

A lightweight and extendable logging library.

For further insights, read on.

- [Installation](#installation)
- [Logger](#logger)
    - [Logger Implementations](#logger-implementations)
    - [Log Levels](#log-levels)
    - [Logging Messages](#logging-messages)
    - [Message Context](#message-context)
    - [Log Formatters](#log-formatters)
- [Logger Builder](#logger-builder)
    - [Aggregate Logger Builder](#aggregate-logger-builder)


## Installation

To install this library use your favorite package manager. No additional steps
are required to start using the library.

```shell
npm install @n7e/log
```

*This library is implemented in TypeScript but can be used with JavaScript
without any additional steps.*


## Logger

Loggers has the ability to send messages of varying severity to a wide range of
targets.

All loggers implement the `Logger` interface. To reference it simply import it
and use it as a type.

```typescript
import type { Logger } from "@n7e/log";

function someFunction(logger: Logger): void {
    // ...
}
```

*`Logger` is just an interface describing the functionality of a logger. To
create a logger instance you need to reference a specific
[implementation](#logger-implementations).*


### Logger Implementations

This library provides the following loggers:

<dl>
    <dt>ConsoleLogger</dt>
    <dd>Logs messages to the global JavaScript console object.</dd>
    <dt>StreamLogger</dt>
    <dd>Logs messages to any writable stream.</dd>
    <dt>AggregateLogger</dt>
    <dd>
        Delegates the logging procedure to a set of registered loggers.
        <br>
        To create an aggregate logger use the
        <a href="#aggregate-logger-builder">aggregate logger builder</a>.
    </dd>
</dl>


### Log Levels

Log messages are assigned a severity level to enable filtering and specific
actions to be taken on certain severity levels.

A defined set of log levels are available through an enum and can be accessed
like so:

```typescript
import { LogLevel } from "@n7e/log";

logger.log(LogLevel.INFO, "This is an informational message");
```

The available log levels are:

<dl>
    <dt>Emergency</dt>
    <dd>System is unusable.</dd>
    <dt>Alert</dt>
    <dd>
        Action must be taken immediately.
        <br>
        Example: Entire website down, database unavailable, etc. This should
        trigger the SMS alerts and wake you up.
    </dd>
    <dt>Critical</dt>
    <dd>
        Critical conditions.
        <br>
        Example: Application component unavailable, unexpected exception.
    </dd>
    <dt>Error</dt>
    <dd>
        Runtime errors that do not require immediate action but should typically
        be logged and monitored.
    </dd>
    <dt>Warning</dt>
    <dd>
        Exceptional occurrences that are not errors.
        <br>
        Example: Use of deprecated APIs, poor use of an API, undesirable things
        that are not necessarily wrong.
    </dd>
    <dt>Notice</dt>
    <dd>Normal but significant events.</dd>
    <dt>Info</dt>
    <dd>
        Interesting events.
        <br>
        Example: User logs in, SQL logs.
    </dd>
    <dt>Debug</dt>
    <dd>Detailed debug information.</dd>
</dl>


### Logging Messages

To produce log messages the `Logger` interface provides a dedicated method for
each log level as well as a `log()` method to log messages with a provided log
level. Although it's ultimately up to the implementation either method should
produce the same result. The following lines should essentially be equal in
functionality.

```typescript
import { LogLevel } from "@n7e/log";

logger.info("This is an informative message");
logger.log(LogLevel.INFO, "This is an informative message");
```

Since implementations can send log messages to any number of remote resources
all methods of the `Logger` interface returns a promise. If it's important to
ensure the message has been received by the target the promise should be
awaited.

```typescript
await logger.info("This is an informative message");
```


#### Message Context

Every logged message can have an optional associated context object. The
significance of the context is up to the logger implementation and/or
[log formatters](#log-formatters) used by the logger.

```typescript
logger.info("This is an informative message", {key: "value"});
```

*The log message may or may not be affected by the passed context object.*


#### Log Formatters

Some logger implementations accept a log formatter to format the message being
logged. All log formatters implement the `LogFormat` interface and can be
imported from the `@n7e/log/format` module.

```typescript
import type { LogFormatter } from "@n7e/log/format";

function someFunction(formatter: LogFormatter): void {
    // ...
}
```

*`LogFormatter` is just an interface describing the functionality of a log
formatter. To create a log formatter instance you need to reference a specific
implementation.*

This library provides the following log formatters:

<dl>
    <dt>InterpolationFormatter</dt>
    <dd>
        Replaces log message tokens with context values. Tokens are context
        property names surrounded by curly braces. e.g. `{name}`.
    </dd>
    <dt>LogLevelFormatter</dt>
    <dd>Prepends the log level to the log message.</dd>
    <dt>AggregateLogFormatter</dt>
    <dd>Delegates the format procedure to a set of registered log formatters.</dd>
</dl>


## Logger Builder

Logger builders has the ability to create logger instances.

All logger builders implement the `LoggerBuilder` interface. To reference it
simply import it and use it as a type.

```typescript
import type { LoggerBuilder } from "@n7e/log";

function someFunction(loggerBuilder: LoggerBuilder): void {
    // ...
}
```

*`LoggerBuilder` is just an interface describing the functionality of a logger
builder. To create a logger builder instance you need to reference a specific
[implementation](#aggregate-logger-builder).*


### Aggregate Logger Builder

To create an aggregate logger instance import and use the provided
`AggregateLoggerBuilder` logger builder.

```typescript
import { AggregateLoggerBuilder } from "@n7e/log";

const loggerBuilder = new AggregateLoggerBuilder();
```

To configure the build aggregate logger instance any number of loggers can be
provided to the builder.

```typescript
loggerBuilder.addLogger(new ConsoleLogger());
```

If a logger should log records for and above a certain log level the desired log
level can be provided.

```typescript
loggerBuilder.addLogger(new ConsoleLogger(), LogLevel.WARNING);
```

The console logger will now only log messages with log level: warning, error,
critical, alert or emergency.

If you need to customize the delegation behaviour you can configure the logger
to use a custom predicate to determine whether to delegate to a logger. The
provided predicate must conform to the `AggregateLoggerDelegationPredicate`
interface.

```typescript
loggerBuilder.useDelegationPredicate(() => true);
```

To produce an aggregate logger instance simply call the `build()` method when
all desired loggers have been configured.

```typescript
const logger = loggerBuilder.build();
```