<!--suppress ALL -->
<h1 align="center">Welcome to Workers Loki Logger 👋</h1>

[![Test](https://github.com/SirCremefresh/workers-loki-logger/actions/workflows/test.js.yml/badge.svg)](https://github.com/SirCremefresh/workers-loki-logger/actions/workflows/test.js.yml)
![npm](https://img.shields.io/npm/v/workers-loki-logger?style=flat-square)

> A Logger for the Cloudflare workers environment that sends the logs to grafana loki

## Install

```sh
npm install workers-loki-logger
```

## Usage

When creating a logger and writing to it all logs are first stored in memory.
It is sent to loki when the flush method is called.   
It is recommended to use a wrapper function for the logger to ensure that the logs are flushed to loki.

[Example with wrapper function](#example-with-wrapper-function)

## Features

- Send logs to loki
- Format exceptions as string
- MDC to add additional information to the logs

## API

### new Logger()

> Creates a new logger

```typescript
import {Logger} from 'workers-loki-logger';

new Logger({
  cloudflareContext: context, // Cloudflare context if this option is set it will use the waitUntil function on flush
  lokiSecret: 'some-secret', // Secret for loki authentication
  lokiUrl: 'https://logs-prod-eu-west-0.grafana.net', // Url to loki
  stream: { // Stream options for loki
    worker: 'esm-worker-simple',
    environment: 'development',
  },
  mdc: { // If this option is set it will be used as the initial value for the MDC
    requestPath: '/some/path',
  }
});
```

### logger.{info,warn,error,fatal}()

> Write log

```typescript
import {Logger} from 'workers-loki-logger';

declare const logger: Logger;

logger.info('message');

logger.warn('message');
logger.warn('message', new Error('some error'));

logger.error('message');
logger.error('message', new Error('some error'));

logger.fatal('message');
logger.fatal('message', new Error('some error'));
```

### logger.{mdcSet,mdcDelete,mdcGet}()

> Change MDC

```typescript
import {Logger} from 'workers-loki-logger';

declare const logger: Logger;

logger.mdcSet('key', 'value');
logger.mdcGet('key');
logger.mdcDelete('key');
```

### logger.flush()

> Write logs to loki

```typescript
import {Logger} from 'workers-loki-logger';

declare const logger: Logger;

await logger.flush();
```

## Example

### Example with wrapper function

```typescript
import {Logger} from 'workers-loki-logger';

function getLogger(context: ExecutionContext, lokiSecret: string) {
  return new Logger({
    cloudflareContext: context,
    lokiSecret,
    lokiUrl: 'https://logs-prod-eu-west-0.grafana.net',
    stream: {
      worker: 'esm-worker-simple',
      environment: 'development',
    }
  });
}

type Environment = {
  LOKI_SECRET: string
}

// This function could be moved to a separate file and imported by multiple workers
function onFetch(handler: (request: Request, environment: Environment, context: ExecutionContext, logger: Logger) => Promise<Response>) {
  return async (request: Request, environment: Environment, context: ExecutionContext) => {
    const logger = getLogger(context, environment.LOKI_SECRET);
    logger.mdcSet('requestUrl', request.url);
    let response;
    try {
      response = await handler(request, environment, context, logger);
    } catch (error) {
      logger.error('Caught error', error);
      response = new Response('Internal Server Error', {status: 500});
    } finally {
      await logger.flush();
    }
    return response;
  };
}

export default {
  fetch: onFetch(async (request, environment, context, logger) => {
    logger.info('Request received' + request.url);
    return await fetch(request);
  })
};
```

### Example with manually calling flush

```typescript
import {Logger} from 'workers-loki-logger';

function getLogger(context: ExecutionContext, lokiSecret: string) {
  return new Logger({
    cloudflareContext: context,
    lokiSecret,
    lokiUrl: 'https://logs-prod-eu-west-0.grafana.net',
    stream: {
      worker: 'esm-worker-simple',
      environment: 'development',
    }
  });
}

export default {
  async fetch(request: Request, environment: { LOKI_SECRET: string }, context: ExecutionContext) {
    const logger = getLogger(context, environment.LOKI_SECRET);
    logger.mdcSet('requestUrl', request.url);
    logger.info('Request received' + request.url);
    let response;
    try {
      response = await fetch(request);
    } catch (error) {
      logger.error('Caught error', error);
      response = new Response('Internal Server Error', {status: 500});
    } finally {
      await logger.flush();
    }
    return response;
  }
};
```

## Run tests

```sh
npm test
```

## Me

👤 **Donato Wolfisberg (donato@wolfibserg.dev) [@SirCremefresh](https://github.com/SirCremefresh)**
