feat(metrics): metrics autocapture for HTTP, database and runtime metrics - #992
DanielVisca wants to merge 1 commit into
Conversation
… metrics
`metrics={"autocapture": True}` starts the installed OpenTelemetry instrumentors with private meter and tracer providers and exports to `/i/v1/metrics`. The packages come from the new `posthog[metrics]` extra.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated-By: PostHog Desktop
Task-Id: ef9b57c2-47ff-47e6-a509-8873ffe1fab4
posthog-python Compliance ReportDate: 2026-09-29T21:53:40.590014+00:00 ✅ All Tests Passed!121/121 tests passed Capture_V1 Tests✅ 95/95 tests passed View Details
Capture_Ai Tests✅ 5/5 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
Feature_Flags_Local_Evaluation Tests✅ 4/4 tests passed View Details
|
|
[Medium risk] Adds metrics autocapture feature using OpenTelemetry. The PR is not ready to merge until prefork workers can initialize autocapture correctly. Reviews (1) · Last reviewed commit: "feat(metrics): add metrics autocapture f..." |
| if _active is not None: | ||
| log.warning( | ||
| "Metrics autocapture is already running in this process, so this client does not start it again." | ||
| ) | ||
| return None |
There was a problem hiding this comment.
Autocapture survives fork unchanged If a client starts autocapture before a prefork server creates workers, each child inherits
_active. The client's fork hook does not reinitialize autocapture, so a worker trying to start its own instance is told one is already running and cannot start it. Reinitialize the autocapture state, lock, and reader lifecycle in the child.
Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog/metrics_autocapture.py
Line: 272-276
Comment:
**Autocapture survives fork unchanged** If a client starts autocapture before a prefork server creates workers, each child inherits `_active`. The client's fork hook does not reinitialize autocapture, so a worker trying to start its own instance is told one is already running and cannot start it. Reinitialize the autocapture state, lock, and reader lifecycle in the child.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
|
||
| reader = sdk_metrics_export.PeriodicExportingMetricReader( | ||
| _GatedExporter( | ||
| endpoint=f"{host}/i/v1/metrics", |
There was a problem hiding this comment.
Trailing slash changes metrics path If a custom host ends in
/, this builds an endpoint containing //i/v1/metrics. The existing event request path removes the trailing slash, so metrics can be sent to a different path and fail to arrive. Strip the trailing slash before appending the metrics path.
| endpoint=f"{host}/i/v1/metrics", | |
| endpoint=f"{host.rstrip('/')}/i/v1/metrics", |
Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog/metrics_autocapture.py
Line: 341
Comment:
**Trailing slash changes metrics path** If a custom host ends in `/`, this builds an endpoint containing `//i/v1/metrics`. The existing event request path removes the trailing slash, so metrics can be sent to a different path and fail to arrive. Strip the trailing slash before appending the metrics path.
```suggestion
endpoint=f"{host.rstrip('/')}/i/v1/metrics",
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.|
|
||
| # Stable HTTP names (`http.server.request.duration`, seconds), the same | ||
| # names posthog-node sends. Read once, when the first instrumentor loads. | ||
| os.environ.setdefault("OTEL_SEMCONV_STABILITY_OPT_IN", "http") |
There was a problem hiding this comment.
Autocapture changes global conventions Starting autocapture sets
OTEL_SEMCONV_STABILITY_OPT_IN for the whole process and shutdown does not restore it. If the application loads its own instrumentors later, their HTTP metric names and units can change without the application opting in. Avoid changing process-wide configuration, or restore it when autocapture stops.
Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog/metrics_autocapture.py
Line: 311
Comment:
**Autocapture changes global conventions** Starting autocapture sets `OTEL_SEMCONV_STABILITY_OPT_IN` for the whole process and shutdown does not restore it. If the application loads its own instrumentors later, their HTTP metric names and units can change without the application opting in. Avoid changing process-wide configuration, or restore it when autocapture stops.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
💡 Motivation and Context
Metrics needs hand-written
client.metrics.*calls today. We want metrics to be the easiest setup: install the SDK, set one flag, and get HTTP, database and runtime metrics.Why: #967 was closed because a hand-rolled HTTP wrapper duplicates OpenTelemetry instrumentation, and PostHog accepts OTLP directly (#914 took the hand-rolled route for runtime metrics). So this change starts the official OpenTelemetry instrumentors for you instead of writing new ones. Companion to PostHog/posthog-js#5149, with the same config shape.
posthog[metrics]extra.opentelemetry-bootstrap -a installadds the instrumentors for the installed libraries; autocapture finds them through theopentelemetry_instrumentorentry points, asopentelemetry-instrumentdoes.http.server.request.duration, seconds), the same names posthog-node sends. Requests to the PostHog host are excluded.db.client.operation.duration. SQLAlchemy spans are not counted, because they wrap a driver span. Only bounded attributes; never the statement.cpython.gc.*fromsystem_metrics(process-level only, no host-widesystem.*).shutdown()exports the last window and uninstruments.telemetry.distro.name=posthog-pythonso we can measure adoption.AppConfig.ready(); Flask: beforeFlask(...)).💚 How did you test it?
posthog/test/test_metrics_autocapture.py(13 tests).ruff,mypy(baseline) andpython -W error -c "import posthog"clean. Full suite: 4155 passed; 2 failures intest/ai/langchain/test_callbacks.pyalso fail onmainin this sandbox (they need network access).hogliwith themetricsintent): a Flask + SQLAlchemy/psycopg2 + redis + requests app. In ClickHouse I sawhttp.server.request.duration(withhttp.route),http.client.request.duration,db.client.operation.duration(postgresql and redis),process.*andcpython.gc.*. No series for the PostHog host.📝 Checklist
If releasing new changes
sampo addto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Connection.executeshortcut; drivers and SQLAlchemy use cursors, so the tests use cursors.Created with PostHog Desktop
🤖 Generated with Claude Code