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

# AdonisJS

> Install and configure the Monocle agent in your AdonisJS application

This guide covers everything you need to instrument your AdonisJS application with Monocle. You will learn how to:

* Install and configure the agent
* Track authenticated users across traces
* Capture exceptions and custom messages
* Use tracing helpers and decorators
* Configure instrumentations (mail, cache, jobs, AI, MCP)
* Fan out telemetry to multiple destinations

## Prerequisites

* AdonisJS 6.2.0+ or AdonisJS 7.0.0+
* A Monocle account with an API key

## Installation

Install and configure the package using the Ace CLI.

```bash theme={"theme":"vesper"}
node ace add @monocle.sh/adonisjs-agent
```

<Accordion title="See what this command does">
  1. Creates `config/monocle.ts` configuration file
  2. Creates `otel.ts` initialization file at project root
  3. Adds the `otel.ts` import as the first import in `bin/server.ts` and `bin/console.ts`
  4. Registers the Monocle provider in `adonisrc.ts`
  5. Registers the Monocle middleware as the first router middleware
  6. Adds required environment variables to `.env` and `start/env.ts`
</Accordion>

After running the command, add your API key to `.env`:

```bash title=".env" theme={"theme":"vesper"}
MONOCLE_API_KEY=mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

You can find your API key in your application settings on the Monocle dashboard.

That's it. Your application now has automatic tracing for HTTP requests, database queries, Redis operations, and more.

<Warning>
  **The import order is critical.**

  OpenTelemetry must initialize before any other code loads. The SDK needs to patch libraries like `http`, `pg`, and `redis` before they are imported. That is why `otel.ts` is imported as the very first line in `bin/server.ts`.

  If you move or remove the `import '../otel.js'` line, auto-instrumentation will not work.
</Warning>

## Environment Variables

The agent requires these environment variables:

| Variable          | Description                                            | Required |
| ----------------- | ------------------------------------------------------ | -------- |
| `MONOCLE_API_KEY` | Your Monocle API key                                   | Yes      |
| `APP_NAME`        | Service name for identification in Monocle             | Yes      |
| `APP_VERSION`     | Service version (e.g., git sha, semver)                | Yes      |
| `APP_ENV`         | Environment: `development`, `staging`, or `production` | Yes      |

<Note>
  The `APP_ENV` variable determines which environment your telemetry data is
  associated with in Monocle. You can filter traces, logs, and metrics by
  environment using the environment selector in the dashboard.
</Note>

## Configuration

The configuration file is located at `config/monocle.ts`. Here is a minimal setup:

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
import { defineConfig } from "@monocle.sh/adonisjs-agent";
import env from "#start/env";

export default defineConfig({
  apiKey: env.get("MONOCLE_API_KEY"),
  serviceName: env.get("APP_NAME"),
  serviceVersion: env.get("APP_VERSION"),
  environment: env.get("APP_ENV"),
});
```

### Full configuration reference

All options are optional except `apiKey`, `serviceName`, `serviceVersion`, and `environment`.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
import { defineConfig } from "@monocle.sh/adonisjs-agent";
import env from "#start/env";

export default defineConfig({
  apiKey: env.get("MONOCLE_API_KEY"),
  serviceName: env.get("APP_NAME"),
  serviceVersion: env.get("APP_VERSION"),
  environment: env.get("APP_ENV"),

  // Custom ingestion endpoint (default: https://ingest.monocle.sh)
  endpoint: "https://ingest.monocle.sh",

  // Skip OPTIONS/CORS preflight requests (default: true)
  ignoreOptionsRequests: true,

  // Enable gzip compression (default: true)
  compression: true,

  // Trace batching configuration
  batch: {
    maxExportBatchSize: 512,
    scheduledDelayMillis: 5000,
    exportTimeoutMillis: 30000,
    maxQueueSize: 2048,
  },

  // Host metrics: CPU, memory, network (default: enabled)
  hostMetrics: {
    enabled: true,
  },

  // Sampling ratio (0.0 to 1.0). Use 0.1 for high-traffic production.
  // Default: 1.0 (100% of traces sampled)
  samplingRatio: 1.0,

  // Feature toggles (all enabled by default except CLI)
  mail: { enabled: true },
  cache: { enabled: true },
  queue: { enabled: true },
  bullmq: { enabled: true },
  ai: { enabled: true },
  cli: { enabled: true },
});
```

### Customizing instrumentations

You can configure individual OpenTelemetry instrumentations by passing options keyed by their package name.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...

  instrumentations: {
    "@opentelemetry/instrumentation-http": {
      // Add URLs to ignore (merged with defaults by default)
      ignoredUrls: ["/internal/*", "/api/ping"],

      // Set to false to replace default ignored URLs instead of merging
      mergeIgnoredUrls: true,

      // Ignore static files like css, js, images (default: true)
      ignoreStaticFiles: true,
    },

    // Disable a specific instrumentation entirely
    "@opentelemetry/instrumentation-pg": { enabled: false },
  },
});
```

Default ignored URLs include `/health`, `/healthz`, `/ready`, `/readiness`, `/metrics`, `/favicon.ico` and other common health check paths.

## User Context

To associate telemetry data with authenticated users, call `Monocle.setUser()` in your authentication middleware. This lets you filter traces and exceptions by user in the dashboard.

```typescript title="app/middleware/silent_auth_middleware.ts" theme={"theme":"vesper"}
import type { NextFn } from "@adonisjs/core/types/http";
import type { HttpContext } from "@adonisjs/core/http";
import { Monocle } from "@monocle.sh/adonisjs-agent";

export default class SilentAuthMiddleware {
  async handle(ctx: HttpContext, next: NextFn) {
    await ctx.auth.check();

    if (ctx.auth.user) {
      Monocle.setUser({
        id: ctx.auth.user.id,
        email: ctx.auth.user.email,
        name: ctx.auth.user.fullName,
      });
    }

    return next();
  }
}
```

The `name` field takes priority in the Monocle UI, falling back to `email`, then `id`.

<Warning>
  Do not include sensitive data (passwords, tokens, API keys) in user
  attributes. These values are sent to Monocle and will be visible in trace
  viewers.
</Warning>

## Exception Tracking

Exceptions thrown during a request are automatically captured by the agent. It hooks into AdonisJS's `ExceptionHandler.report()` method and respects your ignore rules (`ignoreExceptions`, `ignoreStatuses`, `ignoreCodes`).

### Manual exception capture

You can manually capture exceptions anywhere in your code.

```typescript title="app/services/payment_service.ts" theme={"theme":"vesper"}
import { Monocle } from "@monocle.sh/adonisjs-agent";

try {
  await processPayment(order);
} catch (error) {
  Monocle.captureException(error, {
    user: { id: "123", email: "user@example.com" },
    tags: { component: "payment" },
    extra: { orderId: order.id, amount: order.total },
  });
  throw error;
}
```

### Capture messages

Use `Monocle.captureMessage()` to capture custom messages when you want to track something that is not an exception. Messages appear in the Exceptions dashboard alongside errors.

```typescript title="app/services/order_service.ts" theme={"theme":"vesper"}
import { Monocle } from "@monocle.sh/adonisjs-agent";

// Simple message
Monocle.captureMessage("User failed to complete checkout");

// With severity level and context
Monocle.captureMessage("Order processing took too long", {
  level: "warning",
  user: { id: "123" },
  tags: { feature: "checkout" },
  extra: { orderId: "order-456", durationMs: 15000 },
});
```

Supported levels: `debug`, `info`, `log`, `warning`, `error`, `fatal`.

## Tracing Helpers

### Creating custom spans

Use `record()` to wrap a section of code in a span. This is useful when you want to measure the duration of a specific operation that is not automatically instrumented.

```typescript title="app/services/report_service.ts" theme={"theme":"vesper"}
import { record } from "@monocle.sh/adonisjs-agent/helpers";

const report = await record("report.generate", async (span) => {
  span.setAttributes({ "report.type": "monthly", "report.userId": userId });

  const data = await fetchReportData(userId);
  return generatePdf(data);
});
```

`record()` handles both sync and async callbacks, automatically records exceptions, and closes the span when done.

### Decorators

The `@span()` decorator wraps a single method in a span. The `@spanAll()` decorator wraps every public method in a class.

```typescript title="app/services/order_service.ts" theme={"theme":"vesper"}
import { inject } from "@adonisjs/core";
import { span, spanAll } from "@monocle.sh/adonisjs-agent/decorators";

// Wrap a single method
export class OrderService {
  @span()
  async findById(id: string) {
    return Order.findOrFail(id);
  }

  @span({ name: "order.create", attributes: { operation: "create" } })
  async create(data: CreateOrderData) {
    return Order.create(data);
  }
}

// Or wrap all methods at once
@spanAll({ prefix: "user" })
export class UserService {
  async findById(id: string) { /* span: user.findById */ }
  async create(data: UserData) { /* span: user.create */ }
}
```

### Other helpers

```typescript theme={"theme":"vesper"}
import {
  getCurrentSpan,
  setAttributes,
  recordEvent,
  injectTraceContext,
  extractTraceContext,
} from "@monocle.sh/adonisjs-agent/helpers";

// Get the current active span
const span = getCurrentSpan();

// Add attributes to the current span
setAttributes({ "order.total": 99.99, "order.currency": "EUR" });

// Record an event on the current span
recordEvent("order.confirmed", { orderId: "123" });

// Distributed tracing: inject context into outgoing headers
const headers = {};
injectTraceContext(headers);

// Distributed tracing: extract context from incoming headers
const context = extractTraceContext(request.headers);
```

## Instrumentations

### HTTP

HTTP tracing is configured with the top-level `http` option.

Alongside the standard HTTP instrumentation options, Monocle adds few specific features:

* `sanitizeUrls` to redact sensitive query parameters from URL span attributes
* `captureBodies` to record request and response payloads on spans

Both features are disabled by default. Configure `sanitizeUrls` to enable URL sanitization. For body capture, set `captureBodies.enabled: true`, or opt in per side with `incoming.request.enabled`, `incoming.response.enabled`, and so on.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...

  // High-level HTTP tracing options.
  http: {
    // Ignore noisy routes before spans are exported.
    ignoredUrls: ["/internal/*", "/api/ping"],

    // Redact sensitive query params in URL span attributes.
    sanitizeUrls: {
      queryParams: ["code", "state", "token", "id_token", "session_state"],
      censor: "REDACTED",
    },

    // Capture request and response payloads on HTTP spans.
    captureBodies: {
      enabled: true,

      // Server-side traffic handled by your Adonis app.
      incoming: {
        request: {
          // Limit the number of bytes stored on the span.
          maxBodySize: 16_384,
        },
        response: {},
      },

      // Client-side traffic instrumented by node:http / node:https.
      outgoing: {
        request: {},
        response: {},
      },

      // Declarative redaction merged with Monocle's built-in safe defaults.
      redact: {
        bodyKeys: ["password", "token", "secret"],
        headers: ["authorization", "cookie"],
        queryParams: ["token"],
        censor: "REDACTED",
      },

      // Final hook to mutate or replace the captured payload.
      transform: (record) => {
        if (!record.http.url?.includes("/oauth/callback")) return;

        record.http.body = { redacted: true };
      },
    },
  },
});
```

#### URL sanitization

`sanitizeUrls` rewrites URL span attributes such as `http.url`, `url.full`, `http.target`, and `url.query`.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...

  http: {
    sanitizeUrls: {
      queryParams: ["code", "state", "id_token", "session_state"],
    },
  },
});
```

For example, an OAuth callback like:

```text theme={"theme":"vesper"}
/oauth/callback?code=oauth-code&state=oauth-state&id_token=jwt-token
```

becomes:

```text theme={"theme":"vesper"}
/oauth/callback?code=REDACTED&state=REDACTED&id_token=REDACTED
```

For more control, you can provide a custom `sanitize` function that takes the full URL object and returns a sanitized string.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...  
  http: {
    sanitizeUrls: {
      sanitize: (url) => {
        // Only keep the pathname for OAuth callbacks
        if (url.pathname === "/oauth/callback") return url.pathname;

        // Otherwise, keep the full URL
        return url.href;
      },
    },
  },
});
```

#### Body capture scope

`incoming` captures traffic handled by your Adonis app:

* `incoming.request` for the HTTP request body sent to your server
* `incoming.response` for the HTTP response body returned by your server

`outgoing` captures traffic created by Node HTTP clients inside your app:

* `outgoing.request` for the request body your app sends to another service
* `outgoing.response` for the response body returned by that service

<Note>
  `captureBodies.outgoing` only applies to traffic instrumented by
  `@opentelemetry/instrumentation-http`, which means `node:http` and
  `node:https`. It does not cover `fetch` or `undici`.
</Note>

#### Body capture defaults

`captureBodies` is opt-in because payload capture can expose sensitive data.

When enabled, Monocle:

* captures `application/json` and `application/x-www-form-urlencoded` by default
* stores at most `10_000` bytes per request or response unless you increase `maxBodySize`
* shows captured payloads in the **HTTP Body** tab of the trace details view

<Warning>
  Body capture should be enabled deliberately and reviewed carefully. Payloads can
  contain credentials or sensitive information. Keep capture scoped to the
  endpoints you need, prefer the smallest possible `maxBodySize`, and use
  `redact` or `transform` to remove anything sensitive before it is exported.
</Warning>

#### Redaction order

`captureBodies.redact` applies declarative redaction rules first.

`captureBodies.transform` runs after that and lets you mutate or replace the captured payload programmatically.

The declarative `redact` rules extend Monocle's built-in safe defaults. Common secrets such as `password`, `token`, `code`, `access_token`, `refresh_token`, `authorization`, and many others are redacted automatically even if you do not list them yourself.

#### Advanced overrides

If you need low-level OpenTelemetry overrides, you can still configure `instrumentations["@opentelemetry/instrumentation-http"]`.

When both are present, the low-level instrumentation config takes priority over the top-level `http` option.

### Mail

If `@adonisjs/mail` is installed, the agent automatically instruments all emails sent through the mail service. Every email appears as a span in your traces with subject, recipients, and transport info. You can monitor email activity in the dedicated Emails dashboard.

To disable:

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...
  mail: false,
});
```

### Cache

If `@adonisjs/cache` (Bentocache) is installed, the agent instruments cache operations (`get`, `set`, `getOrSet`, `delete`, etc.). You can monitor hit rates, latency, and per-key breakdowns in the Cache dashboard.

Cache keys are automatically sanitized to reduce cardinality. UUIDs, numeric IDs, and hex hashes are replaced with `*`. For example, `users:550e8400-e29b-41d4-a716-446655440000` becomes `users:*`.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...
  cache: {
    // Only trace cache ops within a request/command (default: false)
    requireParentSpan: false,

    // Include cache key names in spans (default: true)
    includeKeys: true,

    // Custom key sanitizer
    keySanitizer: (key) => key.replace(/session:.+/, "session:*"),
  },
});
```

To disable entirely:

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...
  cache: false,
});
```

### Background Jobs

#### @adonisjs/queue

If `@adonisjs/queue` (boringqueue) is installed, the agent instruments job dispatch and execution. Jobs appear in the Jobs dashboard with execution count, error rate, and latency metrics.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...
  queue: {
    // How consumer spans relate to producer spans
    // 'link': consumer span links to producer (default)
    // 'parent': consumer span is a child of the producer span
    executionSpanLinkMode: "link",
  },
});
```

#### BullMQ

BullMQ is also supported. The agent instruments `Queue.add`, `Queue.addBulk`, `FlowProducer.add`, `FlowProducer.addBulk`, and `Worker` processing. Jobs show up in the same Jobs dashboard.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...
  bullmq: {
    executionSpanLinkMode: "link",
  },
});
```

To disable either:

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...
  queue: false,
  bullmq: false,
});
```

#### Sampling noisy jobs

Use the `sampling(ctx)` hook to reduce trace volume for high-throughput jobs while keeping failed executions.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
import { defineConfig, Sample } from "@monocle.sh/adonisjs-agent";

export default defineConfig({
  // ...
  sampling(ctx) {
    if (ctx.kind === "job" && ctx.name === "SendNewsletterEmail") {
      return Sample.rate(0.01).keepErrors();
    }

    return Sample.default();
  },
});
```

See [Job Sampling](/features/job-sampling) for all sampling rules and caveats.

### AI SDK

If the Vercel AI SDK (`ai` package) is installed, the agent auto-instruments `generateText`, `streamText`, `generateObject`, `streamObject`, `embed`, `embedMany`, and `rerank` calls. You can monitor AI activity, costs, and token usage in the AI Agents dashboard.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...
  ai: {
    // Record prompt/input data in spans (default: true)
    recordInputs: true,

    // Record response/output data in spans (default: true)
    recordOutputs: true,
  },
});
```

#### Conversation tracking

Use `withConversationId()` to group multiple AI calls into a single conversation flow. This enables the Conversations tab in the AI dashboard.

```typescript title="app/services/chat_service.ts" theme={"theme":"vesper"}
import { withConversationId, getConversationId } from "@monocle.sh/adonisjs-agent/ai";

const response = await withConversationId("conv-123", async () => {
  return generateText({
    model: openai("gpt-4o"),
    prompt: "Hello!",
  });
});
```

To disable:

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...
  ai: false,
});
```

### CLI Commands

Ace CLI command execution is traced by default. Scaffolding commands (`make:*`, `generate:*`) and long-running queue workers are excluded automatically.

Command arguments and flags are captured as span attributes (`cli.args.*`, `cli.flags.*`).

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...
  cli: {
    // Glob patterns to include (all commands by default)
    include: ["migration:*", "db:*"],
    // Glob patterns to exclude (defaults below)
    exclude: ["make:*", "generate:*", "queue:work", "queue:listen"],
  },
});
```

To disable entirely:

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
export default defineConfig({
  // ...
  cli: false,
});
```

<Warning>
  **CLI tracing requires `otel.ts` in `bin/console.ts`.**

  Just like `bin/server.ts`, your `bin/console.ts` file must import `otel.ts` as its very first import for CLI instrumentation to work. The `node ace configure` command adds this automatically, but if you set up manually or upgraded from an older version, make sure it is there:

  ```typescript title="bin/console.ts" theme={"theme":"vesper"}
  import '../otel.js' // Must be the first import

  import { Kernel } from '@adonisjs/core/ace'
  // ...
  ```
</Warning>

### MCP Server

If you are building an MCP server with `@modelcontextprotocol/sdk`, you can instrument it to track tool calls, clients, and performance in the MCP Monitoring dashboard.

```typescript title="app/mcp/server.ts" theme={"theme":"vesper"}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { instrumentMcpServer } from "@monocle.sh/adonisjs-agent/mcp";

const server = new McpServer({
  name: "my-mcp-server",
  version: "1.0.0",
});

// Instrument before registering tools
instrumentMcpServer(server, {
  // Capture tool arguments in spans (default: false)
  recordInputs: true,
  // Capture tool results in spans (default: false)
  recordOutputs: true,
});

// Register tools, then connect transport as usual
server.tool("my-tool", async () => { /* ... */ });
server.connect(transport);
```

## Multi-Destination OTLP

You can send telemetry to additional OTLP backends alongside Monocle. The Monocle destination is always injected automatically.

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
import { defineConfig, destinations } from "@monocle.sh/adonisjs-agent";

export default defineConfig({
  // ...

  destinations: [
    destinations.otlp({
      name: "grafana",
      url: "https://otlp-gateway.grafana.net/otlp",
      headers: {
        Authorization: `Basic ${btoa("instance_id:api_key")}`,
      },
    }),
  ],
});
```

Each destination receives the full telemetry data (traces, metrics, logs).

## Disabling Telemetry

If no API key is provided, telemetry is automatically disabled. This is useful for local development.

```bash title=".env.development" theme={"theme":"vesper"}
MONOCLE_API_KEY=
```

## Migrating from @adonisjs/otel

If you are already using `@adonisjs/otel`, switching to the Monocle agent is straightforward. The agent wraps `@adonisjs/otel` and provides the same API with additional capabilities.

### 1. Swap the package

```bash theme={"theme":"vesper"}
node ace remove @adonisjs/otel
node ace add @monocle.sh/adonisjs-agent
```

### 2. Rename config file

Rename `config/otel.ts` to `config/monocle.ts` and replace `defineConfig` import:

```typescript title="config/monocle.ts" theme={"theme":"vesper"}
import { defineConfig } from "@adonisjs/otel"; // [!code --]
import { defineConfig } from "@monocle.sh/adonisjs-agent"; // [!code ++]
```

### 3. Update all imports

Replace all `@adonisjs/otel` imports with their Monocle equivalents:

```typescript theme={"theme":"vesper"}
import { record, getCurrentSpan } from "@adonisjs/otel/helpers"; // [!code --]
import { record, getCurrentSpan } from "@monocle.sh/adonisjs-agent/helpers"; // [!code ++]

import { span, spanAll } from "@adonisjs/otel/decorators"; // [!code --]
import { span, spanAll } from "@monocle.sh/adonisjs-agent/decorators"; // [!code ++]
```

### Why use Monocle's helpers

The Monocle agent re-exports most helpers from `@adonisjs/otel` unchanged (`getCurrentSpan`, `setAttributes`, `recordEvent`, etc.). However, `record`, `handleError`, `@span`, and `@spanAll` are enhanced versions that add **source code context extraction**.

When an exception is caught by these helpers, the agent reads the actual source files from disk and attaches the surrounding lines of code to the span. This is what powers the source code preview in the Monocle exception viewer. You get the exact line that errored with surrounding context, without needing source maps.

The agent also fully serializes the **error cause chain** (`Error.cause`), including `AggregateError` children, so nested errors are never lost.

These enhancements only work when importing from `@monocle.sh/adonisjs-agent`. If you keep using `@adonisjs/otel` imports directly, you will get standard OpenTelemetry error recording without the extra context.
