Skip to content

feat(metrics): make the posthoganalytics sdk metrics api bump-ready for web and celery - #72430

Merged
DanielVisca merged 2 commits into
masterfrom
posthog-code/bump-ready-sdk-metrics
Jul 21, 2026
Merged

feat(metrics): make the posthoganalytics sdk metrics api bump-ready for web and celery#72430
DanielVisca merged 2 commits into
masterfrom
posthog-code/bump-ready-sdk-metrics

Conversation

@DanielVisca

Copy link
Copy Markdown
Contributor

Problem

We want to record metrics from our own backend the same way customers do — posthog.metrics.count/gauge/histogram via the SDK — instead of hand-rolling OTel plumbing. The client.metrics API shipped in posthog-python 7.23, but bumping posthoganalytics alone isn't enough:

  • the module-level client apps.py configures has no metrics config, so every series would ship service.name='unknown_service'
  • celery prefork children recycle (--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 mode ph_scoped_capture exists for on the events side

Changes

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: sets posthoganalytics.metrics = {service_name, service_version, environment} (ignored by the pinned SDK; consumed by setup() once bumped — needs PostHog/posthog-python#753 which adds the module-level passthrough)
  • posthog/celery.py: worker_process_shutdown flushes the SDK's final metrics window, guarded so it no-ops on SDK versions without the metrics API and never breaks worker shutdown

Note

This deliberately does NOT bump the dependency. When the bump lands, no further app changes are needed for posthoganalytics.default_client.metrics to work from web and celery.

How did you test this code?

TDD — tests written and observed red first (flush hook absent → flush not 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 bare client.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; ruff and mypy/ty clean on the touched files.
  • Behavioral validation with the real 7.27 SDK in a celery-prefork-shaped harness: module config → 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); a disabled=True client recorded and sent nothing; flush on an untouched client is a safe no-op.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

🤖 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_name falls back to "posthog" because settings.OTEL_SERVICE_NAME defaults to None outside 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

…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
@DanielVisca DanielVisca self-assigned this Jul 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hey @DanielVisca! 👋

It looks like your git author email on this PR isn't your @posthog.com address (danielvisca96@gmail.com). Since you're on the PostHog team, it's worth pointing your local git author email at your @posthog.com address. Why it matters:

  • Consistent work identity in git history — internal tooling that attributes commits to team members keys off your @posthog.com address.
  • Keeps team contributions easy to tell apart from external community ones when scanning history.

You can fix it for this repo with:

git config user.email "you@posthog.com"

Or set it globally with git config --global user.email "you@posthog.com". No need to redo this PR — just a nudge for next time. 🙂

@DanielVisca
DanielVisca marked this pull request as ready for review July 20, 2026 21:35
@DanielVisca DanielVisca added the stamphog Request AI approval (no full review) label Jul 20, 2026
@greptile-apps

greptile-apps Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix 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

Comment thread posthog/celery.py Outdated
Comment thread posthog/apps.py Outdated
@stamphog

stamphog Bot commented Jul 20, 2026

Copy link
Copy Markdown

Note

🤖 stamphog reviewed 0c34edde594e79118a42d0a3c7c5ee9de00330a8 — verdict: REFUSED

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.

  • Author wrote 0% of the modified lines and has 29 merged PRs in these paths (familiarity MODERATE).
  • greptile-apps[bot] reviewed the current head.
  • Unresolved inline comment from @greptile-apps on posthog/celery.py: worker_process_shutdown calls metrics.flush() synchronously with no timeout, which could stall worker recycling if the metrics endpoint is unreachable once the SDK is bumped and this code activates.
  • Unresolved inline comment from @greptile-apps on posthog/apps.py: service_name fallback ('posthog') diverges from the existing OTel resource's service name ('posthog-django-default'), splitting service identity across metrics and traces.
  • No human or second reviewer has weighed in; only a bot COMMENTED review is present.
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list no deny categories matched
size 23L, 2F substantive, 63L/3F incl. docs/generated/snapshots — within ceiling
tier T1-agent / T1b-small (63L, 3F, single-area, feat)
stamphog 2.0.0b3 .stamphog/policy.yml @ 4bc5898 · reviewed head 0c34edd

@stamphog stamphog Bot removed the stamphog Request AI approval (no full review) label Jul 20, 2026
@greptile-apps

greptile-apps Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Both are real and fixable. Here's the context:

1. Service name divergence (apps.py:81)

otel_instrumentation.py:86 shows the OTel resource uses:

service_name = os.environ.get("OTEL_SERVICE_NAME", "posthog-django-default")

So the fallback should match:

            "service_name": settings.OTEL_SERVICE_NAME or "posthog-django-default",

2. Synchronous flush stalling shutdown (celery.py)

This is the exact same hazard otel_instrumentation.py already works around. The comment there (shutdown_on_exit=False, using force_flush(timeout_millis=5_000)) explicitly documents that an unbounded sync flush on exit can hold the process for up to ~63s when the collector is unreachable. The same risk applies here once the SDK is bumped.

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 daemon=True means even if join times out, the thread won't prevent process exit. A 5s ceiling matches the OTel force_flush timeout already in use.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Playwright — all passed

All tests passed.

View test results →

⚠️ Backend coverage — 96.0% of changed backend lines covered — 2 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ███████████████████░ 96.0% (51 / 53)

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
@DanielVisca DanielVisca added the stamphog Request AI approval (no full review) label Jul 21, 2026

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@DanielVisca
DanielVisca merged commit b0d4188 into master Jul 21, 2026
376 of 384 checks passed
@DanielVisca
DanielVisca deleted the posthog-code/bump-ready-sdk-metrics branch July 21, 2026 22:02
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-21 22:25 UTC Run
prod-us ✅ Deployed 2026-07-21 22:41 UTC Run
prod-eu ✅ Deployed 2026-07-21 22:38 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stamphog Request AI approval (no full review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant