Adapters

PostHog Adapter

Send wide events to PostHog Logs via OTLP for structured log querying, debugging, and observability in your PostHog dashboard.

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:

server/plugins/evlog-drain.ts
import { createPostHogDrain } from 'evlog/posthog'

Quick Start

1. Get your PostHog project API key

  1. Log in to your PostHog dashboard
  2. Go to Settings > Project > Project API Key
  3. Copy the key (starts with phc_)

2. Set environment variables

.env
NUXT_POSTHOG_API_KEY=phc_your-project-api-key

3. Create the drain plugin

server/plugins/evlog-drain.ts
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):

  1. Overrides passed to createPostHogDrain()
  2. Runtime config at runtimeConfig.evlog.posthog
  3. Runtime config at runtimeConfig.posthog
  4. Environment variables (NUXT_POSTHOG_* or POSTHOG_*)

Environment Variables

VariableDescription
NUXT_POSTHOG_API_KEYProject API key (starts with phc_)
NUXT_POSTHOG_HOSTPostHog 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:

nuxt.config.ts
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:

server/plugins/evlog-drain.ts
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

OptionTypeDefaultDescription
apiKeystring-Project API key (required)
hoststringhttps://us.i.posthog.comPostHog host URL
timeoutnumber5000Request 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 ExportLogsServiceRequest with severity levels, trace context, and structured attributes

Regions

PostHog offers US and EU cloud hosting. Set the host to match your region:

RegionHost
US (default)https://us.i.posthog.com
EUhttps://eu.i.posthog.com
Self-hostedYour instance URL
.env
# 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:

  1. Go to Logs and filter by service, severity, or any structured attribute
  2. Use the search bar to find specific log entries
  3. 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():

server/plugins/evlog-drain.ts
import { createPostHogEventsDrain } from 'evlog/posthog'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain({
    eventName: 'server_request',
    distinctId: 'my-backend-service',
  }))
})
Custom events count towards your PostHog event quota. PostHog Logs (the default createPostHogDrain()) is significantly cheaper.

Events Configuration

OptionTypeDefaultDescription
apiKeystring-Project API key (required)
hoststringhttps://us.i.posthog.comPostHog host URL
eventNamestringevlog_wide_eventPostHog event name
distinctIdstringevent.serviceOverride distinct_id for all events
timeoutnumber5000Request timeout in milliseconds

Event Format

evlog maps wide events to PostHog events:

evlog FieldPostHog Field
config.distinctId or userId or servicedistinct_id (fallback chain)
timestamptimestamp
levelproperties.level
serviceproperties.service
environmentproperties.environment
All other fieldsproperties.*

Distinct ID Resolution

The distinct_id follows a fallback chain:

  1. config.distinctId — explicit override in createPostHogEventsDrain()
  2. event.userId — automatically picked up if present as a string
  3. event.service — final fallback

Logs vs Events

createPostHogDrain()createPostHogEventsDrain()
FormatOTLP Logs (/i/v1/logs)PostHog Events (/batch/)
PostHog UILogs viewerEvents explorer
CostLower (dedicated logs pipeline)Higher (counts as events)
Best forDebugging, log search, observabilityProduct analytics, cohorts, funnels

You can use both drains simultaneously to get the best of both worlds:

server/plugins/evlog-drain.ts
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.

  1. Check the server console for [evlog/posthog] error messages
  2. Verify your API key is correct and starts with phc_
  3. Confirm your host matches 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:

.env
NUXT_POSTHOG_HOST=https://eu.i.posthog.com

Direct API Usage

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

server/utils/posthog.ts
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:

server/utils/posthog.ts
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