Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/app/assets/icons/axiom.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/docs/app/assets/icons/plug.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 36 additions & 12 deletions apps/docs/app/components/LandingFeatures.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ log.set({ cart: { items, total } })
})`,
},
{
title: 'Agent-Ready',
description: 'Structured JSON output that AI agents can parse and understand.',
code: `{
"level": "error",
"why": "Card declined",
"fix": "Try another card"
}`,
title: 'Log Draining',
description: 'Send logs to external services in fire-and-forget mode. Never blocks your response.',
code: `nitroApp.hooks.hook('evlog:drain',
async (ctx) => {
await sendToAxiom(ctx.event)
}
)`,
},
{
title: 'Nuxt & Nitro',
description: 'First-class integration. Auto-create loggers, auto-emit at request end.',
code: `export default defineNuxtConfig({
modules: ['evlog/nuxt'],
})`,
title: 'Built-in Adapters',
description: 'Zero-config adapters for Axiom, OTLP (Grafana, Datadog, Honeycomb), or build your own.',
code: `import { createAxiomDrain } from 'evlog/axiom'
import { createOTLPDrain } from 'evlog/otlp'
// Reads config from env vars`,
},
{
title: 'Smart Sampling',
Expand All @@ -47,6 +47,21 @@ log.set({ cart: { items, total } })
rates: { info: 10, warn: 50 },
keep: [{ status: 400 }]
}`,
},
{
title: 'Nuxt & Nitro',
description: 'First-class integration. Auto-create loggers, auto-emit at request end.',
code: `export default defineNuxtConfig({
modules: ['evlog/nuxt'],
})`,
},
{
title: 'Client Transport',
description: 'Send browser logs to your server. Automatic enrichment with server context.',
code: `// Browser
log.info({ action: 'click' })
// → Sent to /api/_evlog/ingest
// → Enriched & drained server-side`,
},
{
title: 'Pretty & JSON',
Expand All @@ -55,6 +70,15 @@ log.set({ cart: { items, total } })
user: { id: 1, plan: "pro" }
cart: { items: 3 }`,
},
{
title: 'Agent-Ready',
description: 'Structured JSON output that AI agents can parse and understand.',
code: `{
"level": "error",
"why": "Card declined",
"fix": "Try another card"
}`,
},
]
</script>

Expand Down
2 changes: 2 additions & 0 deletions apps/docs/content/3.adapters/.navigation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
title: Adapters
icon: i-custom-plug
103 changes: 103 additions & 0 deletions apps/docs/content/3.adapters/1.overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
title: Overview
description: Send your logs to external services with evlog adapters.
---

Adapters let you send logs to external observability platforms. evlog provides built-in adapters for popular services, and you can create custom adapters for any destination.

## How Adapters Work

Adapters hook into the `evlog:drain` event, which fires after each request completes. The drain runs in **fire-and-forget** mode, meaning it never blocks the HTTP response.

```typescript [server/plugins/evlog-drain.ts]
import { createAxiomDrain } from 'evlog/axiom'

export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})
```

## Available Adapters

::card-group
:::card
---
icon: i-custom-axiom
title: Axiom
to: /adapters/axiom
---
Send logs to Axiom for powerful querying and dashboards.
:::

:::card
---
icon: i-simple-icons-opentelemetry
title: OTLP
to: /adapters/otlp
---
OpenTelemetry Protocol for Grafana, Datadog, Honeycomb, and more.
:::

:::card
---
icon: i-lucide-code
title: Custom
to: /adapters/custom
---
Build your own adapter for any destination.
:::
::

## Multiple Destinations

Send logs to multiple services simultaneously:

```typescript [server/plugins/evlog-drain.ts]
import { createAxiomDrain } from 'evlog/axiom'
import { createOTLPDrain } from 'evlog/otlp'

export default defineNitroPlugin((nitroApp) => {
const axiom = createAxiomDrain()
const otlp = createOTLPDrain()

nitroApp.hooks.hook('evlog:drain', async (ctx) => {
await Promise.allSettled([axiom(ctx), otlp(ctx)])
})
})
```

## Drain Context

Every adapter receives a `DrainContext` with:

| Field | Type | Description |
|-------|------|-------------|
| `event` | `WideEvent` | The complete log event with all accumulated context |
| `request` | `object` | Request metadata (`method`, `path`, `requestId`) |
| `headers` | `object` | Safe HTTP headers (sensitive headers are filtered) |

::callout{icon="i-lucide-shield-check" color="success"}
**Security:** Sensitive headers (`authorization`, `cookie`, `x-api-key`, etc.) are automatically filtered and never passed to adapters.
::

## Zero-Config Setup

All adapters support automatic configuration via environment variables. No code changes needed when deploying to different environments:

```bash [.env]
# Axiom
NUXT_AXIOM_TOKEN=xaat-xxx
NUXT_AXIOM_DATASET=my-logs

# OTLP
NUXT_OTLP_ENDPOINT=https://otlp.example.com
```

```typescript [server/plugins/evlog-drain.ts]
import { createAxiomDrain } from 'evlog/axiom'

export default defineNitroPlugin((nitroApp) => {
// Automatically reads from env vars
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})
```
164 changes: 164 additions & 0 deletions apps/docs/content/3.adapters/2.axiom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
---
title: Axiom
description: Send logs to Axiom for powerful querying, dashboards, and alerting.
---

[Axiom](https://axiom.co) is a cloud-native logging platform with powerful querying capabilities. The evlog Axiom adapter sends your wide events directly to Axiom datasets.

## Installation

The Axiom adapter is included in the main evlog package:

```typescript
import { createAxiomDrain } from 'evlog/axiom'
```

## Quick Start

### 1. Get your Axiom credentials

1. Create an [Axiom account](https://app.axiom.co)
2. Create a dataset for your logs
3. Generate an API token with ingest permissions

### 2. Set environment variables

```bash [.env]
NUXT_AXIOM_TOKEN=xaat-your-token-here
NUXT_AXIOM_DATASET=your-dataset-name
```

### 3. Create the drain plugin

```typescript [server/plugins/evlog-drain.ts]
import { createAxiomDrain } from 'evlog/axiom'

export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})
```

That's it! Your logs will now appear in Axiom.

## Configuration

The adapter reads configuration from multiple sources (highest priority first):

1. **Overrides** passed to `createAxiomDrain()`
2. **Runtime config** at `runtimeConfig.evlog.axiom`
3. **Runtime config** at `runtimeConfig.axiom`
4. **Environment variables** (`NUXT_AXIOM_*` or `AXIOM_*`)

### Environment Variables

| Variable | Description |
|----------|-------------|
| `NUXT_AXIOM_TOKEN` | API token with ingest permissions |
| `NUXT_AXIOM_DATASET` | Dataset name to ingest logs into |
| `NUXT_AXIOM_ORG_ID` | Organization ID (required for Personal Access Tokens) |

You can also use `AXIOM_TOKEN`, `AXIOM_DATASET`, and `AXIOM_ORG_ID` as fallbacks.

### Runtime Config

Configure via `nuxt.config.ts` for type-safe configuration:

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
runtimeConfig: {
axiom: {
token: '', // Set via NUXT_AXIOM_TOKEN
dataset: '', // Set via NUXT_AXIOM_DATASET
},
},
})
```

### Override Options

Pass options directly to override any configuration:

```typescript [server/plugins/evlog-drain.ts]
import { createAxiomDrain } from 'evlog/axiom'

export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createAxiomDrain({
dataset: 'production-logs',
timeout: 10000, // 10 seconds
}))
})
```

### Full Configuration Reference

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `token` | `string` | - | API token (required) |
| `dataset` | `string` | - | Dataset name (required) |
| `orgId` | `string` | - | Organization ID (for PAT tokens) |
| `baseUrl` | `string` | `https://api.axiom.co` | Axiom API base URL |
| `timeout` | `number` | `5000` | Request timeout in milliseconds |

## Querying Logs in Axiom

evlog sends structured wide events that are perfect for Axiom's APL query language:

```apl
// Find slow requests
['your-dataset']
| where duration > 1000
| project timestamp, path, duration, status

// Error rate by endpoint
['your-dataset']
| where level == "error"
| summarize count() by path
| order by count_ desc

// Request volume over time
['your-dataset']
| summarize count() by bin(timestamp, 1h)
| render timechart
```

## Troubleshooting

### Missing dataset or token error

```
[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_DATASET and NUXT_AXIOM_TOKEN
```

Make sure your environment variables are set and the server was restarted after adding them.

### 401 Unauthorized

Your token may be invalid or expired. Generate a new token in the Axiom dashboard with **Ingest** permissions.

### 403 Forbidden with PAT tokens

Personal Access Tokens require an organization ID:

```bash [.env]
NUXT_AXIOM_ORG_ID=your-org-id
```

## Direct API Usage

For advanced use cases, you can use the lower-level functions:

```typescript
import { sendToAxiom, sendBatchToAxiom } from 'evlog/axiom'

// Send a single event
await sendToAxiom(event, {
token: 'xaat-xxx',
dataset: 'logs',
})

// Send multiple events in one request
await sendBatchToAxiom(events, {
token: 'xaat-xxx',
dataset: 'logs',
})
```
Loading
Loading