AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Posthog Logging

skill-anbturki-claude-toolkit-posthog-logging · by anbturki

Set up, audit, and fix PostHog logging via OTLP — pino transport, structured logs, request context, events, and user linking. Use when PostHog logs are missing, delayed, broken, or need setup from scratch.

No reviews yet
0 installs
38 views
0.0% view→install

Install

$ agentstack add skill-anbturki-claude-toolkit-posthog-logging

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-anbturki-claude-toolkit-posthog-logging)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
5mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Posthog Logging? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

PostHog Logging & Events

Set up or fix PostHog observability. Covers OTLP log ingestion, server-side events, and user profile linking.

Phase 1: Assess Current State

Before changing anything:

  1. Check if PostHog SDK is installed — search for posthog-node in package.json
  2. Check if OTLP transport is installed — search for pino-opentelemetry-transport in package.json
  3. Find the logger — search for pino/winston/bunyan setup in the codebase
  4. Check env vars — find POSTHOG_API_KEY (must start with phc_), POSTHOG_HOST
  5. Check infrastructure — read Terraform/Docker/k8s configs for env injection, log drivers
  6. Query PostHog directly — use PostHog MCP tools (logs-query, logs-list-attributes) to check if logs are arriving
  7. Check CloudWatch/stdout — use AWS CLI or docker logs to verify logs are emitting at all

Phase 2: PostHog OTLP Log Transport

Required packages

pino
pino-opentelemetry-transport
posthog-node  (for events, not logs)

OTLP Configuration (env var approach — recommended)

The transport reads standard OpenTelemetry env vars. Set these before the logger initializes:

OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://us.i.posthog.com/i/v1/logs
OTEL_EXPORTER_OTLP_LOGS_HEADERS=Authorization=Bearer 
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http
  • EU cloud: https://eu.i.posthog.com/i/v1/logs
  • US cloud: https://us.i.posthog.com/i/v1/logs
  • Auth: Bearer (project API key, NOT personal API key)

Pino Transport Setup

// Set OTEL env vars BEFORE creating the transport
if (!process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
  process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'https://us.i.posthog.com/i/v1/logs'
  process.env.OTEL_EXPORTER_OTLP_LOGS_HEADERS = `Authorization=Bearer ${POSTHOG_API_KEY}`
  process.env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL = 'http'
}

const transport = pino.transport({
  targets: [
    {
      target: 'pino-opentelemetry-transport',
      level: 'info',
      options: {
        resourceAttributes: {
          'service.name': 'my-service',
          'deployment.environment': process.env.NODE_ENV,
        },
      },
    },
    { target: 'pino/file', level: 'info' },  // stdout for CloudWatch/Docker
  ],
})

Critical: Transport Resilience

pino.transport({ targets }) runs in a single worker thread. If the OTLP transport fails to initialize, stdout dies too — zero logs to CloudWatch. Always wrap in try/catch:

try {
  const transport = pino.transport({ targets: [...] })
  transport.on('error', (err) => console.error('[logger] transport error:', err.message))
  return pino(opts, transport)
} catch (err) {
  console.error('[logger] OTLP transport failed, falling back to stdout:', err)
  return pino(opts)  // plain stdout, no worker thread
}

Batch Tuning

The default BatchLogRecordProcessor buffers for 5 seconds. To reduce delay:

OTEL_BLRP_SCHEDULE_DELAY=1000   # flush every 1s instead of 5s

Add this to ECS task definition / Docker env / k8s deployment.

Shutdown

Pino's onExit handler tries to synchronously flush the OTLP transport, which can block for 10s and crash. Always flush async before exit:

function shutdown() {
  log.info('Shutting down...')
  app.stop()
    .then(() => Promise.all([closeDb(), posthog?.shutdown()]))
    .then(() => new Promise((resolve) => log.flush(() => resolve())))
    .finally(() => process.exit(0))
}

Phase 3: User Linking (posthogdistinctid)

To link logs to PostHog user profiles, include posthog_distinct_id as a log attribute:

// In your pino mixin (via AsyncLocalStorage):
if (store.userId) {
  ctx.userId = store.userId
  ctx.posthog_distinct_id = store.userId  // PostHog uses this to link to person profiles
}

The user must also be identified via posthog.identify() or posthog.capture() with person properties for the profile to exist.

Phase 4: Server-Side Events (posthog-node)

Separate from logs — use posthog-node for product analytics events:

import { PostHog } from 'posthog-node'

const posthog = new PostHog('', {
  host: 'https://us.i.posthog.com',
})

// Capture events
posthog.capture({ distinctId: userId, event: 'wallet_topup', properties: { amount } })

// MUST call on shutdown
await posthog.shutdown()

Event Plugin Pattern (Elysia/Express)

Fire events in onAfterResponse hooks — don't block request handlers:

export const posthogPlugin = new Elysia({ name: 'posthog' })
  .onAfterResponse({ as: 'global' }, (ctx) => {
    if (!posthog || !ctx.user?.id) return
    posthog.capture({
      distinctId: ctx.user.id,
      event: 'api_request',
      properties: { method: ctx.request.method, path: ctx.path, status: ctx.set.status },
    })
  })

Phase 5: Audit Log Coverage

For every source file in the server:

Routes need:

  • Business-context logs after meaningful operations (not request entry — the request logger handles that)
  • Structured data: IDs, amounts, statuses, counts
  • Warn/error for rejected requests with reason
  • Error logs with full error object in catch blocks

Libs need:

  • Log before irreversible operations (external APIs, payments)
  • Log after successful completion with result context
  • Debug-level for cache hits and routine lookups
  • Info-level for creates, updates, deletes
  • Error-level for unexpected failures

Avoid duplicate logging

  • Libs own: operation details, external API calls, DB mutations
  • Routes own: business decisions, rejections, user-facing errors
  • Plugins own: cross-cutting (auth, rate limiting, request lifecycle)

If a lib logs the operation, the route should NOT re-log the same data.

Request Context (AsyncLocalStorage)

Every log line should automatically include:

  • requestId — UUID per request
  • clientIp — from X-Forwarded-For
  • userId / userEmail — enriched after auth
  • posthog_distinct_id — same as userId, for PostHog person linking

Phase 6: Verify End-to-End

Check PostHog is receiving logs

Use PostHog MCP tools:

logs-query: dateFrom=, serviceNames=["my-service"], limit=10
logs-list-attributes: attributeType="log" — verify custom attributes appear

Check CloudWatch/stdout

aws logs tail /ecs/ --follow --region 

Check for silent failures

  • Search CloudWatch for [logger] transport error — indicates OTLP transport issues
  • Search for _flushSync took too long — indicates shutdown crash
  • If zero pino JSON lines in CloudWatch but server is healthy — transport worker thread died

Known PostHog Limitations

  • Live Tail is buggyPostHog/posthog#43467 (Dec 2025, still open). Events show up in processed feed but live tail misses them. No client-side fix.
  • Ingestion delay — ~1-5s client-side batch + PostHog pipeline. Reduce with OTEL_BLRP_SCHEDULE_DELAY=1000.
  • Protocol — Use http (JSON format), not http/protobuf. PostHog may not accept protobuf on all endpoints.

Output Format

[STATUS] file:line — Description
  Current: 
  Fix: 
  Why: 

Statuses: MISSING, BROKEN, NOISY, LEAK, OK

Summary

"Audited N files. PostHog transport: [WORKING/BROKEN]. Log coverage: X missing, Y broken, Z noisy."

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.