Skip to content

Commit c25d832

Browse files
authored
feat: add axiom and otlp adapters (#35)
1 parent f52d7cc commit c25d832

File tree

18 files changed

+1991
-28
lines changed

18 files changed

+1991
-28
lines changed
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 9 additions & 0 deletions
Loading

apps/docs/app/components/LandingFeatures.vue

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,20 @@ log.set({ cart: { items, total } })
2525
})`,
2626
},
2727
{
28-
title: 'Agent-Ready',
29-
description: 'Structured JSON output that AI agents can parse and understand.',
30-
code: `{
31-
"level": "error",
32-
"why": "Card declined",
33-
"fix": "Try another card"
34-
}`,
28+
title: 'Log Draining',
29+
description: 'Send logs to external services in fire-and-forget mode. Never blocks your response.',
30+
code: `nitroApp.hooks.hook('evlog:drain',
31+
async (ctx) => {
32+
await sendToAxiom(ctx.event)
33+
}
34+
)`,
3535
},
3636
{
37-
title: 'Nuxt & Nitro',
38-
description: 'First-class integration. Auto-create loggers, auto-emit at request end.',
39-
code: `export default defineNuxtConfig({
40-
modules: ['evlog/nuxt'],
41-
})`,
37+
title: 'Built-in Adapters',
38+
description: 'Zero-config adapters for Axiom, OTLP (Grafana, Datadog, Honeycomb), or build your own.',
39+
code: `import { createAxiomDrain } from 'evlog/axiom'
40+
import { createOTLPDrain } from 'evlog/otlp'
41+
// Reads config from env vars`,
4242
},
4343
{
4444
title: 'Smart Sampling',
@@ -47,6 +47,21 @@ log.set({ cart: { items, total } })
4747
rates: { info: 10, warn: 50 },
4848
keep: [{ status: 400 }]
4949
}`,
50+
},
51+
{
52+
title: 'Nuxt & Nitro',
53+
description: 'First-class integration. Auto-create loggers, auto-emit at request end.',
54+
code: `export default defineNuxtConfig({
55+
modules: ['evlog/nuxt'],
56+
})`,
57+
},
58+
{
59+
title: 'Client Transport',
60+
description: 'Send browser logs to your server. Automatic enrichment with server context.',
61+
code: `// Browser
62+
log.info({ action: 'click' })
63+
// → Sent to /api/_evlog/ingest
64+
// → Enriched & drained server-side`,
5065
},
5166
{
5267
title: 'Pretty & JSON',
@@ -55,6 +70,15 @@ log.set({ cart: { items, total } })
5570
user: { id: 1, plan: "pro" }
5671
cart: { items: 3 }`,
5772
},
73+
{
74+
title: 'Agent-Ready',
75+
description: 'Structured JSON output that AI agents can parse and understand.',
76+
code: `{
77+
"level": "error",
78+
"why": "Card declined",
79+
"fix": "Try another card"
80+
}`,
81+
},
5882
]
5983
</script>
6084

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
title: Adapters
2+
icon: i-custom-plug
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
---
2+
title: Overview
3+
description: Send your logs to external services with evlog adapters.
4+
---
5+
6+
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.
7+
8+
## How Adapters Work
9+
10+
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.
11+
12+
```typescript [server/plugins/evlog-drain.ts]
13+
import { createAxiomDrain } from 'evlog/axiom'
14+
15+
export default defineNitroPlugin((nitroApp) => {
16+
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
17+
})
18+
```
19+
20+
## Available Adapters
21+
22+
::card-group
23+
:::card
24+
---
25+
icon: i-custom-axiom
26+
title: Axiom
27+
to: /adapters/axiom
28+
---
29+
Send logs to Axiom for powerful querying and dashboards.
30+
:::
31+
32+
:::card
33+
---
34+
icon: i-simple-icons-opentelemetry
35+
title: OTLP
36+
to: /adapters/otlp
37+
---
38+
OpenTelemetry Protocol for Grafana, Datadog, Honeycomb, and more.
39+
:::
40+
41+
:::card
42+
---
43+
icon: i-lucide-code
44+
title: Custom
45+
to: /adapters/custom
46+
---
47+
Build your own adapter for any destination.
48+
:::
49+
::
50+
51+
## Multiple Destinations
52+
53+
Send logs to multiple services simultaneously:
54+
55+
```typescript [server/plugins/evlog-drain.ts]
56+
import { createAxiomDrain } from 'evlog/axiom'
57+
import { createOTLPDrain } from 'evlog/otlp'
58+
59+
export default defineNitroPlugin((nitroApp) => {
60+
const axiom = createAxiomDrain()
61+
const otlp = createOTLPDrain()
62+
63+
nitroApp.hooks.hook('evlog:drain', async (ctx) => {
64+
await Promise.allSettled([axiom(ctx), otlp(ctx)])
65+
})
66+
})
67+
```
68+
69+
## Drain Context
70+
71+
Every adapter receives a `DrainContext` with:
72+
73+
| Field | Type | Description |
74+
|-------|------|-------------|
75+
| `event` | `WideEvent` | The complete log event with all accumulated context |
76+
| `request` | `object` | Request metadata (`method`, `path`, `requestId`) |
77+
| `headers` | `object` | Safe HTTP headers (sensitive headers are filtered) |
78+
79+
::callout{icon="i-lucide-shield-check" color="success"}
80+
**Security:** Sensitive headers (`authorization`, `cookie`, `x-api-key`, etc.) are automatically filtered and never passed to adapters.
81+
::
82+
83+
## Zero-Config Setup
84+
85+
All adapters support automatic configuration via environment variables. No code changes needed when deploying to different environments:
86+
87+
```bash [.env]
88+
# Axiom
89+
NUXT_AXIOM_TOKEN=xaat-xxx
90+
NUXT_AXIOM_DATASET=my-logs
91+
92+
# OTLP
93+
NUXT_OTLP_ENDPOINT=https://otlp.example.com
94+
```
95+
96+
```typescript [server/plugins/evlog-drain.ts]
97+
import { createAxiomDrain } from 'evlog/axiom'
98+
99+
export default defineNitroPlugin((nitroApp) => {
100+
// Automatically reads from env vars
101+
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
102+
})
103+
```
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---
2+
title: Axiom
3+
description: Send logs to Axiom for powerful querying, dashboards, and alerting.
4+
---
5+
6+
[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.
7+
8+
## Installation
9+
10+
The Axiom adapter is included in the main evlog package:
11+
12+
```typescript
13+
import { createAxiomDrain } from 'evlog/axiom'
14+
```
15+
16+
## Quick Start
17+
18+
### 1. Get your Axiom credentials
19+
20+
1. Create an [Axiom account](https://app.axiom.co)
21+
2. Create a dataset for your logs
22+
3. Generate an API token with ingest permissions
23+
24+
### 2. Set environment variables
25+
26+
```bash [.env]
27+
NUXT_AXIOM_TOKEN=xaat-your-token-here
28+
NUXT_AXIOM_DATASET=your-dataset-name
29+
```
30+
31+
### 3. Create the drain plugin
32+
33+
```typescript [server/plugins/evlog-drain.ts]
34+
import { createAxiomDrain } from 'evlog/axiom'
35+
36+
export default defineNitroPlugin((nitroApp) => {
37+
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
38+
})
39+
```
40+
41+
That's it! Your logs will now appear in Axiom.
42+
43+
## Configuration
44+
45+
The adapter reads configuration from multiple sources (highest priority first):
46+
47+
1. **Overrides** passed to `createAxiomDrain()`
48+
2. **Runtime config** at `runtimeConfig.evlog.axiom`
49+
3. **Runtime config** at `runtimeConfig.axiom`
50+
4. **Environment variables** (`NUXT_AXIOM_*` or `AXIOM_*`)
51+
52+
### Environment Variables
53+
54+
| Variable | Description |
55+
|----------|-------------|
56+
| `NUXT_AXIOM_TOKEN` | API token with ingest permissions |
57+
| `NUXT_AXIOM_DATASET` | Dataset name to ingest logs into |
58+
| `NUXT_AXIOM_ORG_ID` | Organization ID (required for Personal Access Tokens) |
59+
60+
You can also use `AXIOM_TOKEN`, `AXIOM_DATASET`, and `AXIOM_ORG_ID` as fallbacks.
61+
62+
### Runtime Config
63+
64+
Configure via `nuxt.config.ts` for type-safe configuration:
65+
66+
```typescript [nuxt.config.ts]
67+
export default defineNuxtConfig({
68+
runtimeConfig: {
69+
axiom: {
70+
token: '', // Set via NUXT_AXIOM_TOKEN
71+
dataset: '', // Set via NUXT_AXIOM_DATASET
72+
},
73+
},
74+
})
75+
```
76+
77+
### Override Options
78+
79+
Pass options directly to override any configuration:
80+
81+
```typescript [server/plugins/evlog-drain.ts]
82+
import { createAxiomDrain } from 'evlog/axiom'
83+
84+
export default defineNitroPlugin((nitroApp) => {
85+
nitroApp.hooks.hook('evlog:drain', createAxiomDrain({
86+
dataset: 'production-logs',
87+
timeout: 10000, // 10 seconds
88+
}))
89+
})
90+
```
91+
92+
### Full Configuration Reference
93+
94+
| Option | Type | Default | Description |
95+
|--------|------|---------|-------------|
96+
| `token` | `string` | - | API token (required) |
97+
| `dataset` | `string` | - | Dataset name (required) |
98+
| `orgId` | `string` | - | Organization ID (for PAT tokens) |
99+
| `baseUrl` | `string` | `https://api.axiom.co` | Axiom API base URL |
100+
| `timeout` | `number` | `5000` | Request timeout in milliseconds |
101+
102+
## Querying Logs in Axiom
103+
104+
evlog sends structured wide events that are perfect for Axiom's APL query language:
105+
106+
```apl
107+
// Find slow requests
108+
['your-dataset']
109+
| where duration > 1000
110+
| project timestamp, path, duration, status
111+
112+
// Error rate by endpoint
113+
['your-dataset']
114+
| where level == "error"
115+
| summarize count() by path
116+
| order by count_ desc
117+
118+
// Request volume over time
119+
['your-dataset']
120+
| summarize count() by bin(timestamp, 1h)
121+
| render timechart
122+
```
123+
124+
## Troubleshooting
125+
126+
### Missing dataset or token error
127+
128+
```
129+
[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_DATASET and NUXT_AXIOM_TOKEN
130+
```
131+
132+
Make sure your environment variables are set and the server was restarted after adding them.
133+
134+
### 401 Unauthorized
135+
136+
Your token may be invalid or expired. Generate a new token in the Axiom dashboard with **Ingest** permissions.
137+
138+
### 403 Forbidden with PAT tokens
139+
140+
Personal Access Tokens require an organization ID:
141+
142+
```bash [.env]
143+
NUXT_AXIOM_ORG_ID=your-org-id
144+
```
145+
146+
## Direct API Usage
147+
148+
For advanced use cases, you can use the lower-level functions:
149+
150+
```typescript
151+
import { sendToAxiom, sendBatchToAxiom } from 'evlog/axiom'
152+
153+
// Send a single event
154+
await sendToAxiom(event, {
155+
token: 'xaat-xxx',
156+
dataset: 'logs',
157+
})
158+
159+
// Send multiple events in one request
160+
await sendBatchToAxiom(events, {
161+
token: 'xaat-xxx',
162+
dataset: 'logs',
163+
})
164+
```

0 commit comments

Comments
 (0)