PostHog Adapter
PostHog is an open-source product analytics platform. The evlog PostHog adapter sends your wide events to PostHog Logs via the standard OTLP format, giving you a dedicated log viewer with filtering, search, and tail mode — all using your existing PostHog API key.
Installation
The PostHog adapter comes bundled with evlog:
import { createPostHogDrain } from 'evlog/posthog'
Quick Start
1. Get your PostHog project API key
- Log in to your PostHog dashboard
- Go to Settings > Project > Project API Key
- Copy the key (starts with
phc_)
2. Set environment variables
NUXT_POSTHOG_API_KEY=phc_your-project-api-key
3. Create the drain plugin
import { createPostHogDrain } from 'evlog/posthog'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createPostHogDrain())
})
That's it! Your wide events will now appear in PostHog Logs with full OTLP structure including severity levels, trace context, and structured attributes.
Configuration
The adapter reads configuration from multiple sources (highest priority first):
- Overrides passed to
createPostHogDrain() - Runtime config at
runtimeConfig.evlog.posthog - Runtime config at
runtimeConfig.posthog - Environment variables (
NUXT_POSTHOG_*orPOSTHOG_*)
Environment Variables
| Variable | Description |
|---|---|
NUXT_POSTHOG_API_KEY | Project API key (starts with phc_) |
NUXT_POSTHOG_HOST | PostHog host URL (for EU or self-hosted) |
You can also use POSTHOG_API_KEY and POSTHOG_HOST as fallbacks.
Runtime Config
Configure via nuxt.config.ts for type-safe configuration:
export default defineNuxtConfig({
runtimeConfig: {
posthog: {
apiKey: '', // Set via NUXT_POSTHOG_API_KEY
host: '', // Set via NUXT_POSTHOG_HOST
},
},
})
Override Options
Pass options directly to override any configuration:
import { createPostHogDrain } from 'evlog/posthog'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createPostHogDrain({
host: 'https://eu.i.posthog.com',
timeout: 10000,
}))
})
Full Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | - | Project API key (required) |
host | string | https://us.i.posthog.com | PostHog host URL |
timeout | number | 5000 | Request timeout in milliseconds |
How It Works
Under the hood, createPostHogDrain() wraps the OTLP adapter's sendBatchToOTLP() with PostHog-specific defaults:
- Endpoint:
{host}/i/v1/logs(PostHog's OTLP log ingest endpoint) - Auth:
Authorization: Bearer {apiKey}header - Format: Standard OTLP
ExportLogsServiceRequestwith severity levels, trace context, and structured attributes
Regions
PostHog offers US and EU cloud hosting. Set the host to match your region:
| Region | Host |
|---|---|
| US (default) | https://us.i.posthog.com |
| EU | https://eu.i.posthog.com |
| Self-hosted | Your instance URL |
# EU region
NUXT_POSTHOG_API_KEY=phc_xxx
NUXT_POSTHOG_HOST=https://eu.i.posthog.com
Querying Logs in PostHog
Once your logs are flowing, use the Logs tab in PostHog to query them:
- Go to Logs and filter by service, severity, or any structured attribute
- Use the search bar to find specific log entries
- Click on a log entry to see all structured attributes
PostHog Events (Custom Events)
If you prefer sending logs as PostHog custom events (e.g., for product analytics, cohorts, or funnels), use createPostHogEventsDrain():
import { createPostHogEventsDrain } from 'evlog/posthog'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain({
eventName: 'server_request',
distinctId: 'my-backend-service',
}))
})
createPostHogDrain()) is significantly cheaper.Events Configuration
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | - | Project API key (required) |
host | string | https://us.i.posthog.com | PostHog host URL |
eventName | string | evlog_wide_event | PostHog event name |
distinctId | string | event.service | Override distinct_id for all events |
timeout | number | 5000 | Request timeout in milliseconds |
Event Format
evlog maps wide events to PostHog events:
| evlog Field | PostHog Field |
|---|---|
config.distinctId or userId or service | distinct_id (fallback chain) |
timestamp | timestamp |
level | properties.level |
service | properties.service |
environment | properties.environment |
| All other fields | properties.* |
Distinct ID Resolution
The distinct_id follows a fallback chain:
config.distinctId— explicit override increatePostHogEventsDrain()event.userId— automatically picked up if present as a stringevent.service— final fallback
Logs vs Events
createPostHogDrain() | createPostHogEventsDrain() | |
|---|---|---|
| Format | OTLP Logs (/i/v1/logs) | PostHog Events (/batch/) |
| PostHog UI | Logs viewer | Events explorer |
| Cost | Lower (dedicated logs pipeline) | Higher (counts as events) |
| Best for | Debugging, log search, observability | Product analytics, cohorts, funnels |
You can use both drains simultaneously to get the best of both worlds:
import { createPostHogDrain, createPostHogEventsDrain } from 'evlog/posthog'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createPostHogDrain())
nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain())
})
Troubleshooting
Missing apiKey error
[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogDrain()
Make sure your environment variable is set and the server was restarted after adding it.
Events not appearing
PostHog processes events asynchronously. There may be a short delay (typically under 1 minute) before events appear in the dashboard.
- Check the server console for
[evlog/posthog]error messages - Verify your API key is correct and starts with
phc_ - Confirm your
hostmatches your PostHog region (US vs EU)
Wrong region
If you're on PostHog EU but using the default US host, event delivery will fail and the adapter will log errors (for example under [evlog/posthog]) to your server console. Set the correct host:
NUXT_POSTHOG_HOST=https://eu.i.posthog.com
Direct API Usage
For advanced use cases, you can use the lower-level functions:
import { sendToPostHog, sendBatchToPostHog } from 'evlog/posthog'
// Send a single event to PostHog Logs (OTLP)
await sendToPostHog(event, {
apiKey: 'phc_xxx',
})
// Send multiple events in one request
await sendBatchToPostHog(events, {
apiKey: 'phc_xxx',
})
For custom events, use the events-specific functions:
import { sendToPostHogEvents, sendBatchToPostHogEvents, toPostHogEvent } from 'evlog/posthog'
// Send a single custom event
await sendToPostHogEvents(event, {
apiKey: 'phc_xxx',
})
// Send multiple custom events in one request
await sendBatchToPostHogEvents(events, {
apiKey: 'phc_xxx',
})
// Convert event to PostHog format (for inspection)
const posthogEvent = toPostHogEvent(event, { apiKey: 'phc_xxx' })
Next Steps
- Axiom Adapter - Send logs to Axiom
- OTLP Adapter - Send logs via OpenTelemetry Protocol
- Custom Adapters - Build your own adapter
- Best Practices - Security and production tips