> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Better Stack Exceptions

> Capture exceptions and logs in Better Stack via Sentry behind the framework's ILogger interface.

`BetterstackExceptionLogger` is the [Logger](/components/logger) component's [Better Stack](https://betterstack.com) error-tracking backend. It wraps the official `@sentry/node` SDK to capture exceptions — with tags, contexts, breadcrumbs, and extra data — and ship them, along with structured logs, to Better Stack's Sentry-compatible ingest. It implements the framework's `ILogger` interface, so call sites stay provider-agnostic and you can swap the backend without touching the code that emits errors.

## Why Better Stack Exceptions

* **Rich exception capture.** Errors are sent through Sentry with tags, contexts, and breadcrumbs for full debugging detail.
* **Exception-aware `error()`.** Pass an `IException` and its name, status, date, and structured stack trace are attached automatically.
* **Structured logs too.** `warn`, `info`, `debug`, `log`, and `success` ship as Sentry logs alongside your exceptions.
* **Sentry-compatible ingest.** Reuses the Sentry SDK against Better Stack's DSN, so existing Sentry tooling applies.
* **Buffered delivery.** `flush()` drains pending events before the process exits.
* **Container-managed.** Registered with `@decorator.logger()` and resolved from the container.

## Installation

`BetterstackExceptionLogger` ships with `@ooneex/logger` and depends on the Sentry Node SDK.

```bash theme={null}
bun add @ooneex/logger @sentry/node
```

## Environment variables

| Variable                                         | Required | Purpose                                                                                                     |
| ------------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| `BETTERSTACK_EXCEPTION_LOGGER_APPLICATION_TOKEN` | Yes      | Better Stack application token used in the Sentry DSN. Missing throws `LoggerException` (`TOKEN_REQUIRED`). |
| `BETTERSTACK_EXCEPTION_LOGGER_INGESTING_HOST`    | Yes      | Better Stack ingesting host used in the DSN. Missing throws `LoggerException` (`EXCEPTION_LOG_FAILED`).     |

```bash theme={null}
BETTERSTACK_EXCEPTION_LOGGER_APPLICATION_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxx
BETTERSTACK_EXCEPTION_LOGGER_INGESTING_HOST=s1234567.eu-nbg-2.betterstackdata.com
```

Both values are validated when `BetterstackExceptionLogger` is constructed, so misconfiguration fails fast at startup.

## How it works

On construction, the backend calls `Sentry.init()` with a DSN assembled from your application token and ingesting host, enabling logs and full trace sampling. `error()` opens a Sentry scope: a string message is captured as an `Error` with a breadcrumb (and your `data` as extras), while an `IException` is captured with `exception.name`/`exception.status` tags, an `exception` context (name, status, date, stack), a breadcrumb, and your `data` as extras. The remaining levels ship as Sentry logs — `warn`, `info`, and `debug` map directly, and `log`/`success` use `info` with a `level` field (`LOG` / `SUCCESS`). Delivery is buffered; `flush()` waits up to two seconds to drain it.

| Method                    | Purpose                                                            |
| ------------------------- | ------------------------------------------------------------------ |
| `init()`                  | No-op; the SDK is initialized in the constructor.                  |
| `error(message, data?)`   | Capture an exception. `message` may be a `string` or `IException`. |
| `warn(message, data?)`    | Ship a warning log.                                                |
| `info(message, data?)`    | Ship an informational log.                                         |
| `debug(message, data?)`   | Ship a debug log.                                                  |
| `log(message, data?)`     | Ship a general log (sent as `info` with `level: "LOG"`).           |
| `success(message, data?)` | Ship a success log (sent as `info` with `level: "SUCCESS"`).       |
| `flush()`                 | Drain pending events (up to 2s).                                   |

## Usage

```typescript theme={null}
import { container } from "@ooneex/container";
import { BetterstackExceptionLogger } from "@ooneex/logger";

const logger = container.get(BetterstackExceptionLogger);

logger.info("Worker started", { region: "eu" });
logger.warn("Retry scheduled", { attempt: 2 });

// Flush before the process exits
await logger.flush();
```

`error()` accepts a plain message or an `IException` — when given an exception it attaches the name, status, date, and structured stack trace:

```typescript theme={null}
import { container } from "@ooneex/container";
import { BetterstackExceptionLogger } from "@ooneex/logger";
import type { IException } from "@ooneex/exception";

const logger = container.get(BetterstackExceptionLogger);

try {
  await processPayment(order);
} catch (error) {
  // Captured through Sentry with tags, context, and a breadcrumb
  logger.error(error as IException, { orderId: order.id });
}
```

Inject it into a service to capture errors as part of your domain logic:

```typescript theme={null}
import { inject } from "@ooneex/container";
import { BetterstackExceptionLogger } from "@ooneex/logger";

export class PaymentService {
  constructor(
    @inject(BetterstackExceptionLogger) private readonly logger: BetterstackExceptionLogger,
  ) {}

  public async charge(order: Order) {
    try {
      await this.gateway.charge(order);
    } catch (error) {
      this.logger.error(error as IException, { orderId: order.id });
      throw error;
    }
  }
}
```

## Use in the app

In an `@ooneex/app` application, register `BetterstackExceptionLogger` on the `onException` slot of your `App` config. Unlike the general `loggers` array, `onException` of `AppConfigType` is typed as a single `LoggerClassType` and is the dedicated slot for the backend that handles uncaught exceptions — the framework registers it with the container and routes exceptions to it at startup.

```bash theme={null}
bun add @ooneex/app @ooneex/logger @sentry/node
```

```typescript theme={null}
import { App } from "@ooneex/app";
import { BetterstackExceptionLogger, TerminalLogger } from "@ooneex/logger";

const app = new App({
  routing: { prefix: "/api" },
  loggers: [TerminalLogger],
  onException: BetterstackExceptionLogger,
});

await app.run();
```

Here `loggers` handles your normal application logs while `onException` sends uncaught exceptions to Better Stack — the two slots are independent, so you can keep terminal output in development and still capture exceptions remotely.

Set `BETTERSTACK_EXCEPTION_LOGGER_APPLICATION_TOKEN` and `BETTERSTACK_EXCEPTION_LOGGER_INGESTING_HOST` in your `.env.yml` (or environment) so the SDK can initialize. You can also inject the logger directly to capture handled errors inside your domain logic:

```typescript theme={null}
import { inject } from "@ooneex/container";
import { BetterstackExceptionLogger } from "@ooneex/logger";

export class CheckoutService {
  constructor(
    @inject(BetterstackExceptionLogger) private readonly logger: BetterstackExceptionLogger,
  ) {}

  public async completeOrder(order: Order) {
    try {
      // ... persist the order ...
    } catch (error) {
      this.logger.error(error as IException, { orderId: order.id });
      throw error;
    }
  }
}
```

Because delivery is buffered, flush before the process exits — for example from a shutdown hook:

```typescript theme={null}
import { container } from "@ooneex/container";
import { BetterstackExceptionLogger } from "@ooneex/logger";

process.on("SIGTERM", async () => {
  await container.get(BetterstackExceptionLogger).flush();
  process.exit(0);
});
```

## Exceptions

`BetterstackExceptionLogger` throws `LoggerException` on misconfiguration, carrying a machine-readable `key`.

| Key                    | When                                                                  |
| ---------------------- | --------------------------------------------------------------------- |
| `TOKEN_REQUIRED`       | Constructed without `BETTERSTACK_EXCEPTION_LOGGER_APPLICATION_TOKEN`. |
| `EXCEPTION_LOG_FAILED` | Constructed without `BETTERSTACK_EXCEPTION_LOGGER_INGESTING_HOST`.    |

```typescript theme={null}
import { LoggerException } from "@ooneex/logger";

try {
  const logger = container.get(BetterstackExceptionLogger);
} catch (error) {
  if (error instanceof LoggerException && error.key === "TOKEN_REQUIRED") {
    console.error("Better Stack is missing its application token", error.data);
  }
  throw error;
}
```

## Best practices

* **Call `flush()` on exit.** Delivery is buffered; drain it before the process terminates so you don't drop the last events.
* **Log exceptions as exceptions.** Pass the `IException` to `error()` so its name, status, date, and stack trace are captured as Sentry context.
* **Attach context via `data`.** Pass identifiers (order id, user id) as `data` so they land as Sentry extras for debugging.
* **Keep the token and host in the environment.** Load both from `.env`; never hard-code them.
* **Use it for error tracking.** For general structured log shipping, pair it with [Better Stack Logger](/integrations/betterstack-logger).

See the [Logger component](/components/logger) for the provider interface and how logs flow through the framework.
