
Instrumenting First-Party Metrics
FreeEasily record application metrics with PostHog.
Free · Opens the source repo
What Instrumenting First-Party Metrics does
Instrumenting First-Party Metrics provides developers with a straightforward approach to integrate application metrics into the PostHog Metrics product. This skill is designed specifically for those working within PostHog's own codebase, allowing them to record counters, gauges, and histograms in a manner consistent with how customers utilize the metrics functionality. The skill guides users through the process of selecting the appropriate instrumentation path based on their environment, whether it be a monorepo setup with Python, Node services, or standalone scripts.
The skill emphasizes the importance of adhering to version requirements for the PostHog SDK, ensuring that developers are aware of the specific versions that support the metrics functionality. By providing clear instructions on how to validate that metrics are correctly recorded and arrive at the intended destination, this skill helps eliminate common pitfalls associated with instrumenting metrics. Developers can easily follow the documented SDK paths or use the OpenTelemetry (OTel) fallback when necessary, ensuring they have a reliable method for collecting performance data.
This skill is particularly useful for teams that are already leveraging PostHog for their analytics needs and want to extend their capabilities by capturing additional metrics from their applications. It is also beneficial for those who may be unsure about the SDK's compatibility with their current environment, as it provides a safety net through the OTel fallback. By following the structured steps outlined in the skill, developers can ensure that their metrics instrumentation is both effective and compliant with PostHog's standards.
When to use it
Use this skill when adding application metrics in a PostHog monorepo or when you need to ensure metrics are pushed to PostHog metrics correctly.
When not to use it
This skill may not be suitable if you are not using PostHog or if your application does not require detailed metrics instrumentation.
What you can build with it
Integrating Metrics in a Python Monorepo
Use this skill to implement metrics tracking within a Python monorepo, ensuring all metrics are recorded correctly.
Fallback to OTel for Node Services
When working with Node services that do not support PostHog's SDK, leverage the OTel fallback to capture necessary metrics.
Validating Metrics Arrival
Follow the validation steps in the skill to ensure that your recorded metrics are arriving correctly in the PostHog Metrics UI.
How to install Instrumenting First-Party Metrics
View source1. Install with the skills CLI
npx skills add posthog/posthog/instrumenting-first-party-metrics --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by posthogInstrumenting first-party metrics
Goal: get application metrics from PostHog's own code into the PostHog Metrics product (posthog.metrics table, Metrics UI), the same way customers do.
Follow the public docs wherever possible; use OTel only as the fallback when the SDK path isn't available in your environment.
Never invent env vars or hand-roll OTel providers — every environment below already has a working path.
Step 1 — identify the environment and pick the path
| Where you are | First choice | Fallback |
|---|---|---|
| Monorepo Python (web, Celery, Temporal) | SDK: posthoganalytics.default_client.metrics — IF the pinned version supports it (see version gates) | OtelInstrumentFactory in posthog/otel_metrics.py |
Monorepo Node services (nodejs/) | — (services don't run posthog-node) | internal twin: nodejs/src/common/metrics/otel-metrics.ts |
| PostHog-owned standalone service / script / other repo | SDK per public docs: posthog.metrics.count/gauge/histogram | OTLP env vars per docs (OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=<host>/i/v1/metrics, Bearer project token) |
Step 2 — check the version gate (don't assume)
posthog.metrics shipped in: posthog-python 7.23.0 (posthoganalytics is the same package renamed), posthog-node 5.43.0, posthog-js ~1.399.0 (runtime check: typeof posthog.metrics?.count === 'function').
- Monorepo:
grep posthoganalytics pyproject.tomland compare against 7.23.0. Below the gate → use the OTel fallback until the bump lands. - The monorepo is bump-ready:
apps.pysets the module-level metrics config (service name/version/environment) and Celery'sworker_process_shutdownflushes the final window, both inert on pre-7.23 versions. Onceposthoganalytics>=7.23is pinned, the SDK path works from web and Celery with no further app changes. - Elsewhere: check the lockfile/requirements against the versions above; upgrade rather than work around.
Step 3 — instrument (keep it doc-shaped)
SDK path (mirrors the public docs exactly):
client.metrics.count("invoices.processed", 1, attributes={"plan": "pro"})
client.metrics.gauge("queue.depth", 42)
client.metrics.histogram("job.duration", 187, unit="ms")
- In the monorepo (post-bump) the client is
posthoganalytics.default_client— config and flush hooks are already wired; just record. - Short-lived processes and recycling workers must flush (
client.metrics.flush()); the monorepo Celery hook already does this. - Set a service name (monorepo: already configured from
OTEL_SERVICE_NAME, fallbackposthog); it's how the Metrics UI filters.
OTel fallback in the monorepo — posthog/otel_metrics.py, zero setup by the caller:
from posthog.otel_metrics import OtelInstrumentFactory
_otel = OtelInstrumentFactory("myarea")
_otel.counter("myarea.jobs.processed").add(1, {"outcome": "success"})
_otel.histogram("myarea.job.duration", unit="s").record(1.87, {"queue": "default"})
_otel.gauge("myarea.backlog").set(42)
Reference call sites: products/dashboards/backend/access.py (smallest), products/replay_vision/backend/temporal/metrics.py (full module).
If a prometheus_client instrument already exists at the site and its Grafana series must be kept, mirror it with record_counter_twin/record_histogram_twin/record_gauge_twin/timed_histogram_twin instead of a direct instrument — the twin derives name/buckets from it so the sinks can't drift.
Rules for both paths: dot-separated stable names (jobs.processed, not metric1); explicit unit on histograms; low-cardinality attributes only (route, status, plan — never user/session/request IDs; team_id sparingly and deliberately).
Step 4 — validate it actually works
- Know where it lands. SDK path in the monorepo → the dogfood US project (token set in
apps.py). Internal OTel path → whatever project charts'OTEL_METRICS_EXPORT_TOKENpoints at. These can differ — confirm before building dashboards. - Dev/test gotchas. In monorepo DEBUG and TEST the default client is
disabled→ the SDK path records nothing locally (by design).OTEL_METRICS_EXPORT_URL/_TOKENare unset locally → the OTel factory no-ops. To exercise the pipe for real, use a scratch script with an explicitPosthog(token, host, metrics={"service_name": "<yourname>-scratch"})client against a real project, orbin/verify-metrics-pipeto check the local collector pipe itself — it only reports the ingestion services' own metrics (logs-ingestion/metrics-ingestion/nodejsservice names), never a metric you emit from Python; use the arrival checks below for that. - Observe arrival (~1 min ingestion lag): MCP
metric-names-list(search your metric name) thenquery-metrics(counters:increase; gauges:avg; histograms:histogram_quantile), or the Metrics UI name picker, or SQL:SELECT * FROM posthog.metrics WHERE metric_name = '...' ORDER BY timestamp DESC LIMIT 10. - Unit tests. OTel factory twins/instruments swallow errors by design — assert on behavior around them, or use
reset_otel_metrics_for_tests()+override_settingsto exercise gating. SDK path: mock the client or assert againstclient.metrics._seriesstate; never hit the network in tests.
What not to do
- Don't add env vars.
OTEL_METRICS_EXPORT_URL/_TOKEN(internal push) are charts-level deployment config;OTEL_EXPORTER_OTLP_METRICS_*belongs in external apps only. Unset means safe no-op, not misconfiguration. - Don't build
MeterProviders/exporters or cache OTel instruments yourself —posthog/otel_metrics.pyowns lazy, fork-safe, per-PID provider lifecycle. - Don't hand-roll a workaround when the version gate fails — the fix is the dependency bump (wiring is pre-landed), or the OTel factory in the meantime.
Adjacent (not this skill's job)
Grafana dashboards via scraped prometheus_client instruments (port 8001, always-on), and pushed_metrics_registry/PushGatewayTask for one-shot batch jobs (PROM_PUSHGATEWAY_ADDRESS), still exist and keep working — this skill is about the Metrics product.
Keep a prom instrument (with a twin) only when an existing Grafana dashboard depends on it.
Frequently asked questions about Instrumenting First-Party Metrics
Similar skills
Turborepo
Optimized build system for JavaScript/TypeScript monorepos.
Azure Pipelines Validation
Streamline your Azure DevOps pipeline changes locally.
Azure Developer CLI
Streamline your Azure project workflows with best practices.
Azure Container Registry CLI
Manage Azure Container Registry resources with ease.
Aspire
Build and orchestrate polyglot distributed applications seamlessly.
Vercel CLI
Manage and deploy Vercel projects from the command line.
