npm package: @leaflink/snitch
Description: Front end logging inspired by winston.
Latest version: 2.0.1

# @leaflink/snitch

[![Contact Us](https://img.shields.io/badge/slack-%23front--end-pink.svg?logo=slack)](https://leaflink.slack.com/archives/CDXDCR0RX)
![Released via semantic-release](https://img.shields.io/badge/release_method-semantic_release-blue)

Simple and extensible browser logging, inspired by [Winston](https://github.com/winstonjs/winston).


<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of Contents

- [Installation](#installation)
- [Transports](#transports)
- [Log Levels](#log-levels)
- [Usage](#usage)
  - [Singleton Logger](#singleton-logger)
  - [New Logger](#new-logger)
  - [Console Transport](#console-transport)
  - [Sentry Transport](#sentry-transport)
  - [Datadog Logs Transport](#datadog-logs-transport)
  - [With Log Level](#with-log-level)
  - [Custom Transports](#custom-transports)
- [Testing](#testing)
  - [Stubbing snitch](#stubbing-snitch)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Installation

```bash
npm i @leaflink/snitch
# or
pnpm add @leaflink/snitch
```

## Transports

Transports are a way of routing log messages to multiple destinations, with the ability to pre-process or format the message. `@leaflink/snitch` includes three transports by default (`console`, Sentry, and Datadog), but they can be extended using the `Transport` type exported by the logger.

## Log Levels

Transports can be created with a log level to only handle messages with a certain severity. Log levels supported by `@leaflink/snitch`, in decreasing order by severity, are:

- `error`
- `warn`
- `info`
- `debug`

These levels cascade *up*, so a transport created with level `debug` will handle logs with that level as well as `info`, `warn`, and `error`, but a transport created with level `error` will not handle logs with the levels `warn`, `info`, or `debug`.

The level a log was called at is passed to transports to help determine how a message should be handled (for example, logging with `console.<level>` in the console transport).

## Usage

### Singleton Logger

The default export exposed by `@leaflink/snitch` is a singleton logger instance with no initial transports, which can be shared between modules easily. It does not include any transports by default to allow flexibility in per-environment transports.

```typescript
import logger from '@leaflink/snitch';
import { ConsoleTransport } from '@leaflink/snitch/transports/console';

if (config.debug) {
  logger.addTransport(new ConsoleTransport());
}
```

### New Logger

| Name | Default | Description |
| --- | --- | --- |
| `transports` | `[]` | Define transports when creating new logger instead of adding with `.addTransport` |

In other situations, you may want to have a logger instance (or multiple instances) created and managed inside your application. To support this, `@leaflink/snitch` also exports a `Logger` class that can be used to create logger instances, optionally with predefined transports.

```typescript
import { Logger } from '@leaflink/snitch';
import { ConsoleTransport } from '@leaflink/snitch/transports/console';

const loggerInstance = new Logger({
  transports: [new ConsoleTransport()],
});

// `logger.log` is an alias for `logger.info` for convenience
loggerInstance.log('Example log message');
```

### Console Transport

Parameters:

| Name | Default | Description |
| --- | --- | --- |
| `level` | `debug` | Minimum log level to handle. |

`main.ts`
```typescript
import logger from '@leaflink/snitch';
import { ConsoleTransport } from '@leaflink/snitch/transports/console';

logger.addTransport(new ConsoleTransport());
```

call in component file
```typescript
import logger from '@leaflink/snitch';

try {
  await someErroringMethod()
} catch (err) {
  logger.error(err)
}
```

### Sentry Transport

Parameters:

| Name | Default | Description |
| --- | --- | --- |
| `level` | `debug` | Minimum log level to handle. |
| `sentryInstance` | N/A, required | Initialized Sentry logger instance. |

`main.ts`
```typescript
import * as Sentry from '@sentry/vue';
import logger from '@leaflink/snitch';
import { SentryTransport } from '@leaflink/snitch/transports/sentry';

// Important: init Sentry instance before creating transport
Sentry.init({
  // ...
});
logger.addTransport(new SentryTransport({
  sentryInstance: Sentry,
}));
```

call in component file
```typescript
import logger from '@leaflink/snitch';

try {
  await someErroringMethod()
} catch (err) {
  // optional error context object
  const errorContext: Record<string, any> = getErrorContext();
  // `err` is Error | string
  logger.error(err, errorContext);
}
```

### Datadog Logs Transport

Parameters:

| Name | Default | Description |
| --- | --- | --- |
| `level` | `info` | Minimum log level to handle. |
| `datadogLogsInstance` | N/A, required | Initialized Datadog Logs instance. |

See [Datadog Logs documentation](https://docs.datadoghq.com/logs/log_collection/javascript/#custom-logs) for more on custom logging with Datadog.

`main.ts`
```typescript
import { datadogLogs } from '@datadog/browser-logs';
import logger from '@leaflink/snitch';
import { DatadogTransport } from '@leaflink/snitch/transports/datadog';

// Important: init Datadog logs instance before creating transport
datadogLogs.init({
  // ...
});
logger.addTransport(new DatadogTransport({
  datadogLogsInstance: datadogLogs,
}));
```

call in component file
```typescript
import logger from '@leaflink/snitch';

logger.info('File loaded!');

try {
  await someErroringMethod()
} catch (err) {
  // If an Error is passed as the message, this is captured as an exception in Datadog
  logger.error(err, { id: 123 });
}
```

### With Log Level

`main.ts`
```typescript
import logger from '@leaflink/snitch';
import { ConsoleTransport } from '@leaflink/snitch/transports/console';

logger.addTransport(new ConsoleTransport({
  level: 'info'
}));
```

### Custom Transports

`custom-transport.ts`
```typescript
import { LogLevel, Transport } from '@leaflink/snitch';
// imagine this has a `report(options<{message, context}>)` method
import CustomDestinationInstance from '@example/destination';

interface CustomTransportOptions {
  level?: LogLevel;
}

export class CustomTransport implements Transport {
  level: LogLevel;
  log: (message: string | object, meta: Record<string, unknown> | undefined, level: LogLevel) => void;

  constructor(opts?: ConsoleTransportOptions) {
    this.level = opts?.level || 'debug';
    this.log = (message, meta) => {
      CustomDestinationInstance.report({
        message,
        context: 'meta',
      })
    };
  }
}
```

`main.ts`
```typescript
import logger from '@leaflink/snitch';
import { CustomTransport } from './custom-transport';

logger.add(new CustomTransport());
```

Note: You can add a custom transport in your project, but consider opening a PR in this repo instead!

## Testing

### Stubbing snitch

When there's a need to mock this logger or you want to test if a specific log was printed, you can just mock it inside you test file:

```ts
import logger from '@leaflink/snitch';

vi.mock('@leaflink/snitch');

it('should redirect to error page if fetching the user profile fails', async () => {
  // ...
  expect(logger.error).toHaveBeenCalled();
})
```