feat(metrics): make the posthoganalytics sdk metrics api bump-ready for web and celery - #72430
Conversation
…or web and celery Pre-lands the wiring so client.metrics works the moment posthoganalytics is bumped to >=7.23: apps.py sets the module-level metrics config (service name, version, environment) that setup() will consume, and the celery worker_process_shutdown handler flushes the SDK's final aggregation window so recycled prefork children don't drop their tail samples. Both changes are inert on the currently pinned SDK version, which predates the metrics API. Generated-By: PostHog Code Task-Id: 470062dd-05c0-40bf-bfa3-bc89b194121e
|
Hey @DanielVisca! 👋 It looks like your git author email on this PR isn't your
You can fix it for this repo with: git config user.email "you@posthog.com"Or set it globally with |
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
posthog/apps.py:81
**Fallback Splits Service Identity**
When `OTEL_SERVICE_NAME` is unset, SDK metrics use `posthog` while the existing OTel resource uses `posthog-django-default`. Metrics and traces from the same process then appear under different service names, which breaks service-based correlation and aggregation.
### Issue 2 of 2
posthog/celery.py:240
**Shutdown Flush Can Delay Recycling**
If `metrics.flush()` waits on an unavailable metrics endpoint, this synchronous call holds the Celery child inside `worker_process_shutdown`. During an outage, worker recycling can stall until the SDK's network timeout rather than allowing the child to exit promptly.
Reviews (1): Last reviewed commit: "feat(metrics): make the posthoganalytics..." | Re-trigger Greptile |
|
Note 🤖 stamphog reviewed Small, currently-inert scaffolding change, but Greptile's shutdown-flush concern is substantive and unaddressed: the flush call is synchronous with no timeout, so once the SDK is bumped (the PR's stated intent is that no further app review happens at that point) a stalled metrics endpoint could hang celery worker shutdown during an outage.
Gate mechanics and policy version
|
|
Both are real and fixable. Here's the context: 1. Service name divergence (
service_name = os.environ.get("OTEL_SERVICE_NAME", "posthog-django-default")So the fallback should match: 2. Synchronous flush stalling shutdown ( This is the exact same hazard The simplest bounded pattern consistent with what this codebase already does: import threading
try:
client = posthoganalytics.default_client
metrics = getattr(client, "metrics", None) if client is not None else None
if metrics is not None:
t = threading.Thread(target=metrics.flush, daemon=True)
t.start()
t.join(timeout=5.0)
except Exception:
logger.warning("posthoganalytics_metrics_flush_failed", exc_info=True)The |
🤖 CI report
|
| File | Patch | Uncovered changed lines |
|---|---|---|
posthog/celery.py |
89.5% | 258–259 |
🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 29869761140 -n patch-coverage), or the coverage-data block at the end of this comment.
Per-product line coverage (touched products)
| Product | Coverage | Lines |
|---|---|---|
platform_features |
██░░░░░░░░░░░░░░░░░░ 12.1% |
7 / 58 |
batch_exports |
████████░░░░░░░░░░░░ 39.6% |
8,416 / 21,244 |
demo |
███████████░░░░░░░░░ 56.2% |
1,497 / 2,663 |
warehouse_sources_queue |
████████████░░░░░░░░ 59.2% |
148 / 250 |
tasks |
█████████████░░░░░░░ 66.4% |
26,566 / 40,009 |
data_tools |
██████████████░░░░░░ 70.0% |
63 / 90 |
ai_gateway |
███████████████░░░░░ 75.0% |
9 / 12 |
signals |
████████████████░░░░ 78.9% |
20,138 / 25,521 |
cdp |
████████████████░░░░ 80.7% |
3,118 / 3,864 |
data_modeling |
█████████████████░░░ 82.8% |
5,529 / 6,675 |
wizard |
█████████████████░░░ 84.8% |
935 / 1,102 |
notebooks |
█████████████████░░░ 85.3% |
7,266 / 8,522 |
agent_platform |
█████████████████░░░ 86.4% |
3,807 / 4,405 |
actions |
█████████████████░░░ 86.6% |
717 / 828 |
cohorts |
█████████████████░░░ 87.0% |
4,303 / 4,945 |
product_tours |
██████████████████░░ 87.9% |
1,303 / 1,482 |
exports |
██████████████████░░ 88.4% |
6,943 / 7,853 |
engineering_analytics |
██████████████████░░ 88.9% |
5,507 / 6,196 |
business_knowledge |
██████████████████░░ 89.0% |
4,391 / 4,936 |
conversations |
██████████████████░░ 89.2% |
16,508 / 18,509 |
dashboards |
██████████████████░░ 89.4% |
5,982 / 6,692 |
visual_review |
██████████████████░░ 89.4% |
5,818 / 6,505 |
mcp_analytics |
██████████████████░░ 89.7% |
2,663 / 2,969 |
alerts |
██████████████████░░ 89.9% |
4,054 / 4,508 |
error_tracking |
██████████████████░░ 90.1% |
9,761 / 10,832 |
early_access_features |
██████████████████░░ 90.1% |
1,031 / 1,144 |
streamlit_apps |
██████████████████░░ 90.4% |
2,501 / 2,767 |
slack_app |
██████████████████░░ 90.6% |
8,989 / 9,926 |
links |
██████████████████░░ 90.6% |
183 / 202 |
marketing_analytics |
██████████████████░░ 91.0% |
11,792 / 12,964 |
stamphog |
██████████████████░░ 91.0% |
3,993 / 4,387 |
product_analytics |
██████████████████░░ 91.3% |
5,804 / 6,355 |
mcp_store |
██████████████████░░ 91.9% |
4,257 / 4,634 |
data_warehouse |
███████████████████░ 92.6% |
19,249 / 20,790 |
ai_observability |
███████████████████░ 92.7% |
14,758 / 15,926 |
notifications |
███████████████████░ 92.7% |
1,031 / 1,112 |
web_analytics |
███████████████████░ 92.9% |
13,890 / 14,950 |
workflows |
███████████████████░ 92.9% |
5,648 / 6,077 |
surveys |
███████████████████░ 93.0% |
5,730 / 6,163 |
posthog_ai |
███████████████████░ 93.2% |
1,325 / 1,421 |
approvals |
███████████████████░ 93.3% |
3,395 / 3,640 |
reminders |
███████████████████░ 93.4% |
468 / 501 |
managed_migrations |
███████████████████░ 93.8% |
1,220 / 1,300 |
tracing |
███████████████████░ 93.9% |
2,635 / 2,805 |
legal_documents |
███████████████████░ 94.1% |
1,568 / 1,667 |
endpoints |
███████████████████░ 94.1% |
8,640 / 9,177 |
messaging |
███████████████████░ 94.3% |
2,682 / 2,845 |
revenue_analytics |
███████████████████░ 94.5% |
3,598 / 3,809 |
skills |
███████████████████░ 94.5% |
2,893 / 3,061 |
review_hog |
███████████████████░ 94.6% |
6,808 / 7,193 |
logs |
███████████████████░ 95.4% |
9,935 / 10,416 |
experiments |
███████████████████░ 95.7% |
24,469 / 25,569 |
replay_vision |
███████████████████░ 96.0% |
14,504 / 15,103 |
growth |
███████████████████░ 96.1% |
3,233 / 3,364 |
annotations |
███████████████████░ 96.2% |
732 / 761 |
feature_flags |
███████████████████░ 96.4% |
17,027 / 17,654 |
user_interviews |
███████████████████░ 96.5% |
2,639 / 2,735 |
warehouse_sources |
███████████████████░ 96.8% |
308,098 / 318,323 |
access_control |
███████████████████░ 96.8% |
851 / 879 |
customer_analytics |
███████████████████░ 97.3% |
7,780 / 7,999 |
data_catalog |
███████████████████░ 97.4% |
2,345 / 2,407 |
analytics_platform |
████████████████████ 98.0% |
2,102 / 2,145 |
metrics |
████████████████████ 98.2% |
2,491 / 2,536 |
pulse |
████████████████████ 98.4% |
2,017 / 2,049 |
live_debugger |
████████████████████ 99.2% |
613 / 618 |
field_notes |
████████████████████ 99.4% |
158 / 159 |
Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.
…back Generated-By: PostHog Code Task-Id: 934a73f2-8545-4575-b4b2-497a4c181f9c
There was a problem hiding this comment.
Contained instrumentation change (celery shutdown flush hook, app config), inert until a future SDK bump, with a bounded/timeout-guarded flush and tests covering the hung-flush and pre-bump-inert cases; both Greptile-flagged issues were fixed and acknowledged with 👍 reactions, and no holds or unresolved concerns remain.
- Author wrote 0% of the modified lines and has 29 merged PRs in these paths (familiarity MODERATE).
- 👍 on the PR from greptile-apps[bot], hex-security-app[bot].
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✓ | no deny categories matched |
| size | ✓ | 42L, 2F substantive, 105L/3F incl. docs/generated/snapshots — within ceiling |
| tier | ✓ | T1-agent / T1c-medium (105L, 3F, single-area, feat) |
| stamphog 2.0.0b3 | .stamphog/policy.yml @ 3af0672 · reviewed head 99c2508 |
Problem
We want to record metrics from our own backend the same way customers do —
posthog.metrics.count/gauge/histogramvia the SDK — instead of hand-rolling OTel plumbing. Theclient.metricsAPI shipped in posthog-python 7.23, but bumpingposthoganalyticsalone isn't enough:apps.pyconfigures has no metrics config, so every series would shipservice.name='unknown_service'--max-tasks-per-child) without flushing the SDK's in-memory aggregation window, silently dropping up to one flush interval of samples per recycle — the same failure modeph_scoped_captureexists for on the events sideChanges
Pre-lands the wiring so the version bump becomes the only remaining switch. Both changes are inert on the pinned 7.20.4:
posthog/apps.py: setsposthoganalytics.metrics = {service_name, service_version, environment}(ignored by the pinned SDK; consumed bysetup()once bumped — needs PostHog/posthog-python#753 which adds the module-level passthrough)posthog/celery.py:worker_process_shutdownflushes the SDK's final metrics window, guarded so it no-ops on SDK versions without the metrics API and never breaks worker shutdownNote
This deliberately does NOT bump the dependency. When the bump lands, no further app changes are needed for
posthoganalytics.default_client.metricsto work from web and celery.How did you test this code?
TDD — tests written and observed red first (flush hook absent →
flushnot called; config attr absent →None), then green after the implementation:posthog/test/test_celery.py: flush hook flushes the tail window; parameterized no-raise guard covering no default client, a real client on the pinned SDK version (locks in inertness pre-bump — a bareclient.metrics.flush()would raise on every worker recycle), and a flush that throws; config test that fails if someone deletes the "unused" attr assignment pre-bump. All 6 pass;ruffandmypy/tyclean on the touched files.setup()in parent →os.fork()→ child records → the exact hook logic flushes on exit. The child's series arrived in the Metrics product (verified via the metrics query API); adisabled=Trueclient recorded and sent nothing; flush on an untouched client is a safe no-op.Automatic notifications
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Written by Claude (PostHog Code session) after investigating why agents keep inventing env vars to "push metrics into PostHog". Skills invoked: /writing-tests, /setup-web-tests (env bootstrap), /skills-store. Key decisions: flush hook uses
getattr(client, "metrics", None)rather than a version check so it self-activates on bump;service_namefalls back to"posthog"becausesettings.OTEL_SERVICE_NAMEdefaults toNoneoutside prod (found by observation — the config test failed on it). Companion PRs: PostHog/posthog-python#753 (module-level metrics passthrough), #72413 (skill documenting the patterns).Created with PostHog Code