docs

AdonisJS agent

Install @unhandled/agent-adonisjs, connect it to a service with a DSN, and see what it records without extra code.

@unhandled/agent-adonisjs is Unhandled's first-party package for AdonisJS. It exports traces, logs, metrics and exceptions from an AdonisJS app to Unhandled over OTLP/HTTP. It is built on @unhandled/node, which runs the OpenTelemetry Node SDK, so every Node SDK option works in config/unhandled.ts. The agent adds route names, the signed-in user, the exception handler hook, and spans for Ace commands, mail and @adonisjs/queue. Use it for an AdonisJS API, a queue worker or a full-stack app. Other Node.js frameworks have their own packages, listed under SDKs.

Requirements

  • AdonisJS 6.2 or later, or AdonisJS 7.
  • Node.js 24 or later.
  • An Unhandled service and its DSN. Give each deployable (API, worker, web app) its own service. See Services.

Install

  1. Add the package

    Terminal
    node ace add @unhandled/agent-adonisjs

    node ace add installs the package with the package manager your app uses, then runs its configure hook. AdonisJS 6.2 has no add command (it arrived in 6.3). On 6.2, install the package with your package manager and run node ace configure @unhandled/agent-adonisjs. The same command re-runs the hook on any version.

    The hook creates two files and edits five:

    • config/
      • unhandled.tsdefineConfig with dev, dsn and environment
    • otel.tsstarts the agent before the app loads
    • bin/
      • server.tsimport '../otel.js' added as the first import
      • console.tssame import, same position
    • adonisrc.tsregisters @unhandled/agent-adonisjs/unhandled_provider
    • start/
      • kernel.tsunhandled_middleware added first in the router stack
      • env.tsvalidators for APP_ENV and UNHANDLED_DSN
    • .envAPP_ENV=development and an empty UNHANDLED_DSN

    If bin/server.ts or bin/console.ts already imports ../otel.js, the command leaves it alone. In .env it only adds keys that are missing, so a second run keeps the DSN you pasted. start/env.ts accepts development, staging or production for APP_ENV and an optional string for UNHANDLED_DSN. The command does not add APP_NAME or APP_VERSION, and the agent does not need them.

  2. Set the DSN

    Create the service in your project and copy the DSN from the dialog. Unhandled shows the full DSN once. After that the service page shows only the last four characters of the key. Rotate key on the service issues a new DSN and revokes the old key at once.

    Set the DSN and the environment wherever that service runs:

    .env
    APP_ENV=production
    UNHANDLED_DSN=https://[email protected]/0b4f6c2e-8a1d-4c3e-9f7a-1234567890ab

    The DSN holds the ingest endpoint, the API key and the service id. Unhandled files the data under the service that owns the DSN, whatever service name your app reports. The DSN section covers the format and what happens when the DSN does not parse.

  3. Send a request

    Start the app and call any route. In development that is the dev server:

    Terminal
    node ace serve --hmr

    In staging and production, build first and start the built server. The build does not copy .env, so set the variables where the server runs:

    Terminal
    node ace build
    node build/bin/server.js

    The trace appears under the service and environment within seconds. If it does not, work through Troubleshooting.

Already on @adonisjs/otel

Run one package, not both. When otel.ts starts @adonisjs/otel, the configure hook stops without changing any file. When the @adonisjs/otel provider is registered or its SDK is running, the agent's provider throws at boot and names both ways out.

You can keep the official @adonisjs/otel package and send its data to Unhandled, either with unhandledDestinations() and your DSN or with four OTEL_* variables. The setup dialog shows the variables under Already using @adonisjs/otel. Keep @adonisjs/otel explains both and lists what you give up compared to the agent.

Generated files

config/unhandled.ts:

config/unhandled.ts
import env from '#start/env'
import { defineConfig } from '@unhandled/agent-adonisjs'

export default defineConfig({
  dev: env.get('NODE_ENV') === 'development',
  dsn: env.get('UNHANDLED_DSN'),
  environment: env.get('APP_ENV'),
})

otel.ts:

otel.ts
import { init } from '@unhandled/agent-adonisjs/init'

await init(import.meta.dirname)

Keep otel.ts first

bin/server.ts
// Must stay the first import so auto-instrumentation can patch modules on load
import '../otel.js'
await import('reflect-metadata')
const { Ignitor, prettyPrintError } = await import('@adonisjs/core/ignitor')

That is the AdonisJS 7 starter kit's shape. Apps that came from AdonisJS 6 import reflect-metadata and @adonisjs/core statically after otel.js, and that works too: both shapes record request spans.

Environment variables

UNHANDLED_DSNstring

Connection string for one service. Without it the agent exports nothing to Unhandled.

APP_ENVproduction | staging | development

The generated config passes it to environment. Set it in every deployment.

UNHANDLED_ENVproduction | staging | development

Environment used when the config does not set environment. Takes precedence over APP_ENV and NODE_ENV. Any other value throws at boot.

UNHANDLED_VERSIONstring

Pins the code version. Takes precedence over APP_VERSION.

APP_VERSIONstring

Pins the code version when UNHANDLED_VERSION is unset.

OTEL_SERVICE_NAMEstring

Service name for extra destinations and local setups.

APP_NAMEstring

Service name used when OTEL_SERVICE_NAME is unset.

The agent skips placeholder versions such as 0.0.1 and falls back to your platform, CI or git commit. The full order is in Service name and version.

The agent reads these variables from the process environment, so a bad UNHANDLED_ENV throws UnhandledConfigError rather than failing start/env.ts. To validate the optional ones with the rest of your config, add them to start/env.ts:

start/env.ts
UNHANDLED_ENV: Env.schema.enum.optional(['development', 'staging', 'production'] as const),
UNHANDLED_VERSION: Env.schema.string.optional(),

Local development

The generated config turns on dev when NODE_ENV is development. Dev mode ignores the DSN, so a laptop never writes into a production service. It exports only when you also set endpoint to a local OTLP collector or add an extra destination. Set debug: true to print spans to the console.

Under NODE_ENV=test the agent stays off unless you set enabled yourself.

What is recorded

After the install, with no other code:

  • HTTP requests, as SERVER spans named after the route (GET /orders/:id) with http.route, adonis.route.name and the response status. See HTTP tracing.
  • Database queries, ClickHouse calls through @clickhouse/client, Redis commands, outgoing HTTP calls and the other auto-instrumentations that @unhandled/node turns on. See What is recorded.
  • Mail sent with @adonisjs/mail, @adonisjs/queue jobs and Ace commands. See Mail, queue and Ace commands.
  • Exceptions from the Adonis exception handler and from failed Ace commands, with source lines and the cause chain. See Users and exceptions.
  • Pino logs, Node runtime metrics, and host CPU, memory and network metrics.

Next steps