Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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