From 2936f0a5dae2a1c0b25db47aefa5cdd57c624ec4 Mon Sep 17 00:00:00 2001 From: "releaser-ai-plugin[bot]" <273148615+releaser-ai-plugin[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 13:00:49 +0000 Subject: [PATCH] chore: sync skills (agent-skills-v0.113.0, context-mill@v1.16.0) --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- .cursor-plugin/plugin.json | 2 +- gemini-extension.json | 2 +- skills/.sync-manifest | 5 + .../downloading-batch-export-files/SKILL.md | 127 ++++ skills/exploring-llm-evaluations/SKILL.md | 2 +- skills/grouping-noisy-errors/SKILL.md | 290 ++++++++ skills/instrument-error-tracking/SKILL.md | 7 +- .../references/django.md | 259 ++++++++ .../references/flask.md | 113 ++++ .../references/laravel.md | 150 +++++ .../references/nuxt.md | 2 +- .../references/php.md | 222 +++++++ .../references/react.md | 10 +- .../references/ruby-on-rails.md | 625 +++++++++++++---- .../references/ruby.md | 6 +- skills/instrument-feature-flags/SKILL.md | 4 + .../references/adding-feature-flag-code.md | 74 ++- .../references/django.md | 259 ++++++++ .../references/flask.md | 113 ++++ .../references/laravel.md | 150 +++++ .../references/react.md | 10 +- .../references/ruby-on-rails.md | 595 +++++++++++++++++ skills/instrument-integration/SKILL.md | 4 +- .../references/EXAMPLE-php.md | 527 +++++++++++++++ .../references/django.md | 58 +- .../references/flask.md | 46 +- .../instrument-integration/references/js.md | 1 - .../references/laravel.md | 127 +++- .../instrument-integration/references/node.md | 4 +- .../instrument-integration/references/php.md | 629 ++++++++++++++++++ .../references/posthog-js.md | 2 +- .../references/posthog-node.md | 2 +- .../references/posthog-python.md | 298 +++++---- .../references/python.md | 12 +- .../references/react-router-v6.md | 14 +- .../references/react-router-v7-data-mode.md | 14 +- .../react-router-v7-declarative-mode.md | 14 +- .../react-router-v7-framework-mode.md | 14 +- .../references/react.md | 10 +- .../references/ruby-on-rails.md | 227 +++++-- .../instrument-integration/references/ruby.md | 294 ++++++-- skills/instrument-llm-analytics/SKILL.md | 70 +- .../references/anthropic.md | 18 +- .../references/autogen.md | 18 +- .../references/azure-openai.md | 18 +- .../references/basics.md | 18 +- .../references/calculating-costs.md | 4 +- .../references/cerebras.md | 18 +- .../references/cohere.md | 18 +- .../references/crewai.md | 18 +- .../references/deepseek.md | 18 +- .../references/dspy.md | 18 +- .../references/fireworks-ai.md | 18 +- .../references/google.md | 18 +- .../references/groq.md | 18 +- .../references/helicone.md | 18 +- .../references/hugging-face.md | 18 +- .../references/instructor.md | 18 +- .../references/langchain.md | 18 +- .../references/langgraph.md | 18 +- .../references/litellm.md | 18 +- .../references/llamaindex.md | 18 +- .../references/manual-capture.md | 16 +- .../references/mastra.md | 18 +- .../references/mirascope.md | 18 +- .../references/mistral.md | 18 +- .../references/ollama.md | 18 +- .../references/openai-agents.md | 20 +- .../references/openai.md | 16 +- .../references/openrouter.md | 18 +- .../references/perplexity.md | 18 +- .../references/portkey.md | 18 +- .../references/pydantic-ai.md | 18 +- .../references/semantic-kernel.md | 18 +- .../references/smolagents.md | 18 +- .../references/together-ai.md | 18 +- .../references/traces.md | 12 +- .../references/vercel-ai.md | 18 +- .../references/xai.md | 18 +- skills/instrument-logs/references/search.md | 15 +- skills/instrument-product-analytics/SKILL.md | 4 +- .../references/EXAMPLE-php.md | 527 +++++++++++++++ .../references/django.md | 58 +- .../references/flask.md | 46 +- .../references/laravel.md | 127 +++- .../references/php.md | 629 ++++++++++++++++++ .../references/posthog-python.md | 298 +++++---- .../references/python.md | 12 +- .../references/react-router-v6.md | 14 +- .../references/react-router-v7-data-mode.md | 14 +- .../react-router-v7-declarative-mode.md | 14 +- .../react-router-v7-framework-mode.md | 14 +- .../references/ruby-on-rails.md | 227 +++++-- .../references/ruby.md | 294 ++++++-- skills/investigating-error-issue/SKILL.md | 364 ++++++++++ skills/querying-posthog-data/SKILL.md | 2 +- .../references/available-functions.md | 2 + .../references/example-error-tracking.md | 4 +- .../references/example-funnel-trends.md | 2 +- .../references/example-logs.md | 2 +- .../references/example-session-replay.md | 8 +- .../references/example-sessions.md | 2 +- ....md => models-ai-observability-reviews.md} | 0 skills/suppressing-noisy-errors/SKILL.md | 341 ++++++++++ skills/triaging-error-issues/SKILL.md | 155 +++++ 107 files changed, 8137 insertions(+), 1080 deletions(-) create mode 100644 skills/downloading-batch-export-files/SKILL.md create mode 100644 skills/grouping-noisy-errors/SKILL.md create mode 100644 skills/instrument-error-tracking/references/django.md create mode 100644 skills/instrument-error-tracking/references/flask.md create mode 100644 skills/instrument-error-tracking/references/laravel.md create mode 100644 skills/instrument-error-tracking/references/php.md create mode 100644 skills/instrument-feature-flags/references/django.md create mode 100644 skills/instrument-feature-flags/references/flask.md create mode 100644 skills/instrument-feature-flags/references/laravel.md create mode 100644 skills/instrument-feature-flags/references/ruby-on-rails.md create mode 100644 skills/instrument-integration/references/EXAMPLE-php.md create mode 100644 skills/instrument-integration/references/php.md create mode 100644 skills/instrument-product-analytics/references/EXAMPLE-php.md create mode 100644 skills/instrument-product-analytics/references/php.md create mode 100644 skills/investigating-error-issue/SKILL.md rename skills/querying-posthog-data/references/{models-llm-analytics-reviews.md => models-ai-observability-reviews.md} (100%) create mode 100644 skills/suppressing-noisy-errors/SKILL.md create mode 100644 skills/triaging-error-issues/SKILL.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index b7ff50d..bd3e4ee 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "posthog", "description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Claude Code. Optionally capture Claude Code sessions to PostHog LLM Analytics.", - "version": "1.1.26", + "version": "1.1.27", "author": { "name": "PostHog", "email": "hey@posthog.com", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index f36e833..b0721b0 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "posthog", - "version": "1.0.24", + "version": "1.0.25", "description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Codex", "author": { "name": "PostHog", diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 8695ded..0d9ede6 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "posthog", "displayName": "PostHog", - "version": "1.1.21", + "version": "1.1.22", "description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Cursor", "author": { "name": "PostHog", diff --git a/gemini-extension.json b/gemini-extension.json index 9068f1d..29c35d7 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "posthog", - "version": "1.0.23", + "version": "1.0.24", "description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Gemini CLI", "mcpServers": { "posthog": { diff --git a/skills/.sync-manifest b/skills/.sync-manifest index 20ead84..23cf756 100644 --- a/skills/.sync-manifest +++ b/skills/.sync-manifest @@ -13,6 +13,7 @@ diagnosing-failed-warehouse-syncs diagnosing-missing-recordings diagnosing-sdk-health diagnosing-stacktrace-symbolication +downloading-batch-export-files exploring-apm-traces exploring-autocapture-events exploring-live-traffic @@ -25,6 +26,7 @@ finding-deleted-feature-flags finding-experiments finding-replay-for-issue formatting-insight-axes +grouping-noisy-errors inbox-exploration instrument-error-tracking instrument-feature-flags @@ -33,6 +35,7 @@ instrument-llm-analytics instrument-logs instrument-product-analytics investigate-metric +investigating-error-issue investigating-replay managing-experiment-lifecycle managing-path-cleaning-rules @@ -43,6 +46,8 @@ setting-up-a-data-warehouse-source signals skills-store suggesting-data-imports +suppressing-noisy-errors +triaging-error-issues triaging-visual-review-runs tuning-incremental-sync-config working-with-skills diff --git a/skills/downloading-batch-export-files/SKILL.md b/skills/downloading-batch-export-files/SKILL.md new file mode 100644 index 0000000..6b3c7bd --- /dev/null +++ b/skills/downloading-batch-export-files/SKILL.md @@ -0,0 +1,127 @@ +--- +name: downloading-batch-export-files +description: > + Export PostHog events, persons, or sessions on demand and download the resulting files. Use when the user asks to + download/export raw PostHog data, create a one-off file export, fetch a Parquet or JSONLines export, or use the + file_download_batch_exports API. Covers starting the export with MCP, polling completion, and downloading via the + existing REST redirect endpoint. +--- + +# Downloading batch export files + +Use this skill when a user wants a one-off downloadable export of PostHog data. +The export is started and monitored through MCP, but the final file download uses the existing REST endpoint directly. + +## Available MCP tools + +| Tool | Purpose | +| ---------------------------------------------- | -------------------------------------------------------- | +| `posthog:file-download-batch-exports-create` | Start an on-demand export and return the run ID | +| `posthog:file-download-batch-exports-retrieve` | Poll the run status and return file IDs after completion | + +Do not rely on a generated MCP tool for the `/download/` endpoint. +That endpoint is a redirecting file download endpoint, so raw HTTP/download handling is the right interface until MCP has explicit redirect support. + +## Workflow + +### 1. Choose the export shape + +Ask a short clarifying question if the user did not specify the required inputs: + +- `model`: one of `events`, `persons`, or `sessions` +- `data_interval_start` and `data_interval_end`: ISO 8601 datetimes; the range must be at most one week +- `file.format`: `Parquet` or `JSONLines`; prefer `Parquet` for compact analytics exports and `JSONLines` for line-oriented text processing +- `file.compression`: optional, one of `zstd`, `gzip`, `brotli`, `lz4`, or `snappy`. If `JSONLines` was chosen as format, only `gzip` and `brotli` are supported. +- `file.max_size_mb`: optional maximum part size in MB; set this when the user wants multiple smaller files instead of a single (potentially large) file. + +For `events`, `include` and `exclude` are optional event-name filters. +Use them only when the user asks for specific events or wants to omit specific events. + +### 2. Start the export + +Call `posthog:file-download-batch-exports-create` with the selected shape. +The response contains an `id` for the export run. + +Example request: + +```json +{ + "model": "events", + "file": { + "format": "JSONLines", + "compression": "gzip" + }, + "include": ["$pageview"], + "data_interval_start": "2026-05-25T00:00:00Z", + "data_interval_end": "2026-05-26T00:00:00Z" +} +``` + +### 3. Poll until completion + +Call `posthog:file-download-batch-exports-retrieve` with the returned `id`. + +Status handling: + +| Status | Action | +| ------------------------------------------------------------------------- | --------------------------------------------- | +| `Starting` or `Running` | Wait briefly and poll again | +| `Completed` | Read the `files` array and download each file | +| `Cancelled` | Stop and report that the run was cancelled | +| `Failed`, `FailedRetryable`, `FailedBilling`, `Terminated`, or `TimedOut` | Stop and report the `error` field | + +When `Completed`, the `files` array contains file UUIDs. +For single-file exports it usually contains one UUID. +For split exports, download every UUID unless the user asked for a specific part. + +### 4. Optionally, cancel a running export + +If required by the user, a running export can be cancelled by calling `posthog:file-download-batch-exports-cancel-create` with the returned `id`. + +An export that has already finished or has already failed may not be cancelled. + +After cancelling an export, the `id` may not be used anymore and the export must start again from the beginning. However, you may still use the `id` to retrieve the export status (which will always be `Cancelled`). + +### 5. Download files through REST + +Use a direct authenticated HTTP request to the existing endpoint: + +```text +GET /api/projects/{project_id}/file_download_batch_exports/{run_id}/download/{part}/ +``` + +`part` can be either: + +- a file UUID from the `files` array returned by `file-download-batch-exports-retrieve` +- a zero-based file index, ordered by key + +If there is only one file, this also works without `part`: + +```text +GET /api/projects/{project_id}/file_download_batch_exports/{run_id}/download/ +``` + +Let the HTTP client follow the redirect, or inspect the `Location` header if you need the temporary signed URL. +Use the same PostHog authentication context as other API calls. + +### 6. Save, do not print, file contents + +Treat the result as a file download, not a chat response. +Parquet is binary and must be written as bytes. +JSONLines may still be large; save it to a file rather than pasting the contents unless the user explicitly asks for a tiny sample. + +Use a filename that includes the model, run ID, and part identifier when possible, for example: + +```text +posthog-events--.jsonl.gz +posthog-persons--.parquet +``` + +## Watch-outs + +- The maximum export interval is one week. Split longer user requests into separate export runs or ask which week to export. +- A run can briefly report `Running` after completion while file records are being created. Poll again instead of failing immediately. +- Download URLs are temporary. If a URL expires, call the REST download endpoint again for a fresh redirect. +- Do not send the signed URL to unrelated services unless the user explicitly asks; it grants temporary access to the exported file. +- If the user wants all parts of a split export, iterate over every UUID in `files`; do not assume part `0` is enough. +- Large batch exports may take a few minutes or even longer to complete. Suggest to the user that they can speed-up their download by including only certain events or narrowing the date range. diff --git a/skills/exploring-llm-evaluations/SKILL.md b/skills/exploring-llm-evaluations/SKILL.md index 4b5f3a4..49cc44f 100644 --- a/skills/exploring-llm-evaluations/SKILL.md +++ b/skills/exploring-llm-evaluations/SKILL.md @@ -50,7 +50,7 @@ AI-generated summary of pass/fail/N/A patterns across many runs. | `posthog:execute-sql` | Ad-hoc HogQL over `$ai_evaluation` events | | `posthog:query-llm-trace` | Drill into the underlying generation that an evaluation scored | -All `llma-evaluation-*` tools are defined in `products/llm_analytics/mcp/tools.yaml`. +All `llma-evaluation-*` tools are defined in `products/ai_observability/mcp/tools.yaml`. ## Event schema diff --git a/skills/grouping-noisy-errors/SKILL.md b/skills/grouping-noisy-errors/SKILL.md new file mode 100644 index 0000000..89cab0b --- /dev/null +++ b/skills/grouping-noisy-errors/SKILL.md @@ -0,0 +1,290 @@ +--- +name: grouping-noisy-errors +description: > + Consolidate PostHog error tracking issues that are the same actual + error reported under different fingerprints. Use when the user asks + "why do I have so many TypeError issues that look the same?", "merge + these duplicates", "stop splitting this error into new issues", or + wants to clean up fingerprint sprawl. Decides between a one-shot merge + of existing issues and a durable grouping rule that keeps future + events from creating new fingerprints. Does NOT group conceptually + similar bugs across different runtimes, SDKs, or call sites. +--- + +# Grouping noisy errors + +The same error can be reported as dozens of separate issues when stack frames or +messages contain volatile data — random IDs, dynamic file paths, build hashes, +anonymous function names. The fix is two-step: merge the existing issues into one +target, then create a grouping rule so future events from the same call site +share a single canonical fingerprint instead of spawning new ones. + +Important up front: "same error" here is narrow. Two issues that share a name or +a sentence of message text but came from different code paths, different SDKs, +or different runtimes are **different errors** and should stay separate, even if +the user thinks of them as "the same kind of bug". Grouping a frontend +`TypeError` together with a backend `TypeError` because both messages contain +"undefined" destroys the signal that lets the team find each one. The criteria +in step 1 exist to keep that from happening. + +## Available tools + +| Tool | Purpose | +| ---------------------------------------------- | ------------------------------------------------------ | +| `posthog:query-error-tracking-issues-list` | Find candidate duplicate issues | +| `posthog:query-error-tracking-issue` | Pull compact details for an individual issue | +| `posthog:query-error-tracking-issue-events` | Sampled `$exception` events with stack and message | +| `posthog:error-tracking-issues-merge-create` | Merge existing issues into a target | +| `posthog:error-tracking-issues-split-create` | Surgically split fingerprints back out if a merge errs | +| `posthog:error-tracking-grouping-rules-create` | Auto-group future events into one issue | +| `posthog:error-tracking-grouping-rules-list` | Check existing grouping rules before adding new ones | +| `posthog:error-tracking-issues-partial-update` | Rename or re-describe the target after a merge | + +## Merge vs grouping rule + +The two tools solve different halves of the problem: + +- **Merge** is one-shot. It collapses existing issues into a target and re-attaches + their events. Future events still group by their original fingerprints — if the + same noisy pattern keeps producing new fingerprints, merging is a treadmill. +- **Grouping rule** is durable. It rewrites the fingerprint of any matching + event to `custom-rule:` at ingestion time, so all future matches + share one canonical fingerprint rather than spawning new ones. The first + match either creates a new issue keyed off that fingerprint, or routes to + whatever issue is already bound to it. + +Use both together when the issue is recurring: merge historical duplicates +into a target issue, then create the rule. The rule API does **not** accept a +target issue ID — once the rule starts firing, the resulting `custom-rule:...` +issue can be merged into the same target so the consolidation sticks. Use +merge alone for historical sprawl that you don't expect to recur. Use a +grouping rule alone for a brand-new pattern you're getting ahead of, when +you don't need to consolidate with an existing issue. + +## Workflow + +### Step 1 — Confirm the duplicates + +Search by exception type or message to find candidates: + +```json +posthog:query-error-tracking-issues-list +{ + "searchQuery": "TypeError: Cannot read property", + "status": "active", + "limit": 50, + "orderBy": "occurrences", + "dateRange": { "date_from": "-30d" } +} +``` + +For each candidate, pull one sampled exception event to compare stack, type, +and message: + +```json +posthog:query-error-tracking-issue-events +{ + "issueId": "", + "limit": 1, + "verbosity": "stack" +} +``` + +Run this once per candidate. The tool defaults to `onlyAppFrames: true`, which +makes the top in-app frame stand out at a glance. If two candidates share the +same top frame and same exception type, they're likely the same error — but +verify against the full checklist below before merging. + +#### Are they the same error? + +Treat two issues as duplicates only when **every one** of these matches: + +- `$lib` is the same SDK (`posthog-js`, `posthog-python`, `posthog-node`, + `posthog-android`, etc.). Errors from different SDKs almost always come from + different code paths even when the exception type matches. +- The exception type is identical (`$exception_types`). +- The top in-app stack frame points at the same file and same function. Line + numbers and minor offsets within that function are fine; a different file or + a different function on top means a different bug. +- The message follows the same template, with differences confined to volatile + data — IDs, hashes, timestamps, dynamic paths. If the difference is a + different verb, object, or operation, it's a different bug. +- `$exception_handled` agrees (both handled or both unhandled). A caught + variant and an uncaught variant are different code paths and benefit from + staying separate. + +If any single one of those differs, they are not duplicates — investigate +separately (`investigating-error-issue`). + +#### What NOT to group together + +These are the failure modes that destroy debugging signal. Do not group +across any of them, even when the user describes them as "the same kind of +bug": + +- **Frontend and backend variants of the same exception type.** A `TypeError` + from a browser bundle and a `TypeError` from a Node service share a name and + often a message word, but the stack, the runtime, and the fix all differ. +- **Different SDKs / platforms.** `posthog-js` vs `posthog-python` vs + `posthog-android` are different call sites. +- **Same type, different file or function on top of the stack.** A + `NullPointerException` thrown from `OrderService.cancel` is not the same bug + as one thrown from `PaymentService.refund`, even if both messages say + "user was null". +- **Caught vs uncaught.** Two issues that differ only in `$exception_handled` + are usually a code path that swallows the error in one place and lets it + propagate in another — keeping them separate makes that visible. +- **Conceptually-similar bugs that happen to share a phrase.** "Cannot read + property of undefined" appears in many independent bugs. Without matching + stack frames, message similarity alone is not enough. + +### Step 2 — Pick the target issue + +Pick the issue that should absorb the others: + +- **Most occurrences** — keeps the dominant issue so dashboards stay continuous +- **Best name and description** — if the user has annotated one, prefer it +- **Earliest `first_seen`** — preserves the original timeline + +Note the target's ID. The other candidates become `ids` to merge in. + +### Step 3 — Merge existing duplicates + +```json +posthog:error-tracking-issues-merge-create +{ + "id": "", + "ids": ["", "", "..."] +} +``` + +Merge is destructive (annotation `destructive: true`) — once issues are merged +into a target, the source issues are gone from the active list. Confirm the +target with the user before calling. Cap each merge call at ~50 source IDs to +keep failures localized; for larger sprawl, batch. + +Merged changes may not appear in the issue list immediately — re-listing right +after the call can still show the source issues for a short window. If a +follow-up `error-tracking-issues-list` call looks unchanged, wait a few seconds +and re-query rather than re-issuing the merge. + +If after the merge the target's metadata looks wrong (a duplicate had a better +name), use `error-tracking-issues-partial-update` to fix the name or description +on the target rather than re-merging. + +### Step 4 — Decide if a grouping rule is warranted + +A grouping rule is worth creating when both are true: + +- The pattern keeps producing new fingerprints (you have seen new duplicates + appear since the last merge) +- You can describe the pattern with property filters that won't accidentally + swallow unrelated errors + +The canonical exception properties (`$exception_types`, `$exception_values` +for messages, `$exception_sources` for file paths, `$exception_functions` for +function names) are arrays at capture time. The property filter compiler +[special-cases them](https://github.com/PostHog/posthog/blob/master/posthog/hogql/property.py#L904) — it parses the JSON-materialized column +and wraps the filter in `arrayExists(v -> ..., JSONExtract(...))`, so all +the standard operators (`exact`, `is_not`, `icontains`, `not_icontains`, +`regex`, `not_regex`) work against individual elements with the bare value: +`exact "TypeError"`, not `exact '["TypeError"]'` or `regex '"TypeError"'`. + +The singular forms (`$exception_type`, `$exception_message`) and +`$exception_stack_trace_raw` are emitted on a fraction of a percent of events; +filtering on them produces a rule that silently never matches. + +If the volatility is in the message (e.g., +`TypeError at /static/main..js`), a regex filter on `$exception_values` +works. If the volatility is in line numbers within a known file, `icontains` +on `$exception_sources` does. `$exception_handled` is also a useful narrowing +dimension — separate handled vs unhandled rather than mixing them. + +Skip the grouping rule when: + +- The duplicates are historical (one-off backfill, no new occurrences) — merge + is enough +- You can't write a filter narrow enough to be safe — broaden the merge cadence + instead and revisit later + +### Step 5 — Create the grouping rule + +Translate the step 1 "same error" checklist into rule filters. A rule that +matches more loosely than the checklist will silently merge unrelated bugs +forever — the rule is more dangerous than the merge because it runs against +every future event. At a minimum, scope by SDK and exception type, and add +a third dimension (file path via `$exception_sources`, or a specific message +phrase via `$exception_values`) to pin the call site: + +```json +posthog:error-tracking-grouping-rules-create +{ + "filters": { + "type": "AND", + "values": [ + { + "type": "event", + "key": "$lib", + "operator": "exact", + "value": "posthog-js" + }, + { + "type": "event", + "key": "$exception_types", + "operator": "exact", + "value": "TypeError" + }, + { + "type": "event", + "key": "$exception_sources", + "operator": "icontains", + "value": "/static/checkout/" + }, + { + "type": "event", + "key": "$exception_values", + "operator": "icontains", + "value": "Cannot read property" + } + ] + }, + "description": "Cleanup: collapse noisy checkout TypeError fingerprints (posthog-js)" +} +``` + +Rules are evaluated in order. List existing rules first +(`posthog:error-tracking-grouping-rules-list`) — if a rule already partially +covers the pattern, prefer adjusting its filter over stacking a near-duplicate. + +The optional `assignee` field auto-assigns issues created by the rule. Skip it +unless the user explicitly wants ownership baked into the rule. + +### Step 6 — Verify and consolidate + +Sample the merged issue's recent events to confirm the merge succeeded. +Watch for the rule's `custom-rule:` fingerprint to start matching +events — the first match creates a new issue (or routes to whatever was +already bound to that fingerprint). To keep events under your historical +target rather than scattered across the new custom-rule issue, run a second +merge folding the custom-rule issue into the target. + +If new (non-rule) fingerprints continue appearing despite the rule, its +filter is too narrow — widen it. + +## Tips + +- The user often confuses grouping rules with assignment rules. Grouping rules + decide _which_ issue an event lands in. Assignment rules decide _who_ owns the + resulting issue. +- Don't merge issues that "look similar" without inspecting events. Two + `TypeError`s in different files are different bugs. +- Stack frames are the canonical grouping signal — ingestion already + fingerprints on the stack, so a stable stack groups itself. A grouping rule + is for cases where the natural fingerprint sprays (volatile filenames, + hashed function names, dynamic line numbers) and you need to override it. +- Disabling or tightening a grouping rule does not retroactively un-group + existing events; future events route correctly, past events stay where they + are. Use `error-tracking-issues-split-create` if you need to surgically + separate fingerprints back out of a merged issue. +- Grouping rules are visible in the UI under Project settings → Error tracking → + Grouping rules; mention this when the user asks where rules live. diff --git a/skills/instrument-error-tracking/SKILL.md b/skills/instrument-error-tracking/SKILL.md index 0c05821..0f60ee1 100644 --- a/skills/instrument-error-tracking/SKILL.md +++ b/skills/instrument-error-tracking/SKILL.md @@ -13,7 +13,7 @@ metadata: Use this skill to add PostHog error tracking that captures and monitors exceptions in your application. Use it after implementing features or reviewing PRs to ensure errors are tracked with full stack traces and source maps. If PostHog is not yet installed, this skill also covers initial SDK setup. Supports any platform or language. -Supported platforms: React, Next.js, Web (JavaScript), Node.js, Python, Ruby, Ruby on Rails, Go, Angular, Svelte, Nuxt, React Native, Flutter, Android, and Hono. +Supported platforms: React, Next.js, Web (JavaScript), Node.js, Python, PHP, Ruby, Ruby on Rails, Go, Angular, Svelte, Nuxt, React Native, Flutter, Android, and Hono. ## Instructions @@ -65,8 +65,13 @@ STEP 8: Verify and clean up. - `references/nextjs.md` - Next.js error tracking installation - docs - `references/node.md` - Node.js error tracking installation - docs - `references/python.md` - Python error tracking installation - docs +- `references/django.md` - Django - docs +- `references/flask.md` - Flask - docs +- `references/php.md` - Php error tracking installation - docs +- `references/laravel.md` - Laravel - docs - `references/ruby.md` - Ruby error tracking installation - docs - `references/ruby-on-rails.md` - Ruby on rails error tracking installation - docs +- `references/ruby-on-rails.md` - Ruby on rails - docs - `references/go.md` - Go error tracking installation - docs - `references/angular.md` - Angular error tracking installation - docs - `references/svelte.md` - Sveltekit error tracking installation - docs diff --git a/skills/instrument-error-tracking/references/django.md b/skills/instrument-error-tracking/references/django.md new file mode 100644 index 0000000..af5d8ba --- /dev/null +++ b/skills/instrument-error-tracking/references/django.md @@ -0,0 +1,259 @@ +# Django - Docs + +PostHog makes it easy to get data about traffic and usage of your Django app. Integrating PostHog enables analytics, custom events capture, feature flags, error tracking, and more. + +This guide walks you through integrating PostHog into your Django app using the [Python SDK](/docs/libraries/python.md). + +## Beta: integration via LLM + +Install PostHog for Django in seconds with our wizard by running this prompt with [LLM coding agents](/blog/envoy-wizard-llm-agent.md) like Cursor and Bolt, or by running it in your terminal. + +`npx @posthog/wizard@latest` + +[Learn more](/wizard.md) + +Or, to integrate manually, continue with the rest of this guide. + +## Installation + +To start, run `pip install posthog` to install PostHog’s Python SDK. + +> **Note:** Version `7.x` of the PostHog Python SDK requires Python 3.10 or higher. + +Then, configure PostHog in your app config so it's initialized when Django starts: + +your\_app/apps.py + +PostHog AI + +```python +from django.apps import AppConfig +import posthog +class YourAppConfig(AppConfig): + name = 'your_app_name' + def ready(self): + posthog.api_key = '' + posthog.host = 'https://us.i.posthog.com' +``` + +Next, if you haven't done so already, add your `AppConfig` to `INSTALLED_APPS` in `settings.py`: + +settings.py + +PostHog AI + +```python +INSTALLED_APPS = [ + # ... other apps + 'your_app_name.apps.YourAppConfig', +] +``` + +You can find your project token and instance address in [your project settings](https://app.posthog.com/project/settings). + +To capture events from any file, import `posthog` and call the method you need. For example: + +Python + +PostHog AI + +```python +import posthog +from posthog import identify_context +def some_request(request): + with posthog.new_context(): + # Django includes request.user for anonymous visitors too. Only identify + # the context when the visitor is logged in. + if request.user.is_authenticated: + identify_context(str(request.user.pk)) + posthog.capture('event_name') +``` + +Events captured without a context or explicit `distinct_id` are sent as [anonymous events](/docs/data/anonymous-vs-identified-events.md) with an auto-generated `distinct_id`. See the [Python SDK docs](/docs/libraries/python.md#person-profiles-and-properties) for more details. + +## Identifying users + +> **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). +> +> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. + +## Django contexts middleware + +The Python SDK provides a Django middleware that automatically wraps all requests with a [context](/docs/libraries/python.md#contexts). This middleware extracts session and user information from each request and tags all events captured during that request with relevant metadata. + +### Basic setup + +Add the middleware to your Django settings. If your app uses Django authentication, place it after `django.contrib.auth.middleware.AuthenticationMiddleware` so the middleware can use the authenticated Django user as a distinct ID fallback and capture the user's email. + +Python + +PostHog AI + +```python +MIDDLEWARE = [ + # ... other middleware + 'posthog.integrations.django.PosthogContextMiddleware', + # ... other middleware +] +``` + +The middleware uses the globally configured `posthog` client by default, so you don't need to create or pass it a separate client instance. + +The middleware automatically extracts and uses: + +- **Session ID** from the `X-POSTHOG-SESSION-ID` header, if present +- **Distinct ID** from the `X-POSTHOG-DISTINCT-ID` header, if present, falling back to the authenticated Django user's `pk` (Django's primary-key alias, which works with custom user models) +- **User email** from the authenticated Django user's `email` as `email` +- **Current URL** as `$current_url` +- **Request method** as `$request_method` +- **Request path** as `$request_path` +- **Forwarded IP address** from `X-Forwarded-For` as `$ip` +- **User agent** from `User-Agent` as `$user_agent` + +The session and distinct ID headers are sanitized before use. Empty values are ignored, control characters are removed, values are trimmed, and values are capped at 1000 characters. + +All events captured during the request (including exceptions) include these properties and are associated with the extracted session and distinct ID. + +If you are using PostHog on your frontend, the JavaScript Web SDK will add the session and distinct ID headers automatically if you enable tracing headers. + +JavaScript + +PostHog AI + +```javascript +posthog.init('', { + __add_tracing_headers: ['your-backend-domain.com'] +}) +``` + +### Exception capture + +By default, the middleware captures exceptions and sends them to PostHog's error tracking using the globally configured `posthog` client. This includes Django view exceptions that Django converts into error responses. + +Disable this by setting: + +Python + +PostHog AI + +```python +# settings.py +POSTHOG_MW_CAPTURE_EXCEPTIONS = False +``` + +### Adding custom tags + +Use `POSTHOG_MW_EXTRA_TAGS` to add custom properties to all requests: + +Python + +PostHog AI + +```python +# settings.py +def add_user_tags(request): + # type: (HttpRequest) -> Dict[str, Any] + tags = {} + if hasattr(request, 'user') and request.user.is_authenticated: + # Use pk instead of id so this works with custom User primary keys. + tags['user_id'] = str(request.user.pk) + tags['email'] = request.user.email + return tags +POSTHOG_MW_EXTRA_TAGS = add_user_tags +``` + +#### Filtering requests + +Skip tracking for certain requests using `POSTHOG_MW_REQUEST_FILTER`: + +Python + +PostHog AI + +```python +# settings.py +def should_track_request(request): + # type: (HttpRequest) -> bool + # Don't track health checks or admin requests + if request.path.startswith('/health') or request.path.startswith('/admin'): + return False + return True +POSTHOG_MW_REQUEST_FILTER = should_track_request +``` + +### Modifying default tags + +Use `POSTHOG_MW_TAG_MAP` to modify or remove default tags: + +Python + +PostHog AI + +```python +# settings.py +def customize_tags(tags): + # type: (Dict[str, Any]) -> Dict[str, Any] + # Remove URL for privacy + tags.pop('$current_url', None) + # Add custom prefix to method + if '$request_method' in tags: + tags['http_method'] = tags.pop('$request_method') + return tags +POSTHOG_MW_TAG_MAP = customize_tags +``` + +### Complete configuration example + +Python + +PostHog AI + +```python +# settings.py +def add_request_context(request): + # type: (HttpRequest) -> Dict[str, Any] + tags = {} + if hasattr(request, 'user') and request.user.is_authenticated: + tags['user_type'] = 'authenticated' + # Use pk instead of id so this works with custom User primary keys. + tags['user_id'] = str(request.user.pk) + else: + tags['user_type'] = 'anonymous' + # Add request info + tags['user_agent'] = request.META.get('HTTP_USER_AGENT', '') + return tags +def filter_tracking(request): + # type: (HttpRequest) -> bool + # Skip internal endpoints + return not request.path.startswith(('/health', '/metrics', '/admin')) +def clean_tags(tags): + # type: (Dict[str, Any]) -> Dict[str, Any] + # Remove sensitive data + tags.pop('user_agent', None) + return tags +POSTHOG_MW_EXTRA_TAGS = add_request_context +POSTHOG_MW_REQUEST_FILTER = filter_tracking +POSTHOG_MW_TAG_MAP = clean_tags +POSTHOG_MW_CAPTURE_EXCEPTIONS = True +``` + +All events captured within the request context automatically include the configured tags and are associated with the session and user identified from the request headers or Django authentication. + +The middleware supports both sync (WSGI) and async (ASGI) Django applications. In async mode, it uses Django's `request.auser()` API when available to avoid synchronous user access. + +## Next steps + +For any technical questions for how to integrate specific PostHog features into Django (such as analytics, feature flags, A/B testing, etc.), have a look at our [Python SDK docs](/docs/libraries/python.md). + +Alternatively, the following tutorials can help you get started: + +- [Setting up Django analytics, feature flags, and more](/tutorials/django-analytics.md) +- [How to set up A/B tests in Django](/tutorials/django-ab-tests.md) + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/skills/instrument-error-tracking/references/flask.md b/skills/instrument-error-tracking/references/flask.md new file mode 100644 index 0000000..e8ec7bf --- /dev/null +++ b/skills/instrument-error-tracking/references/flask.md @@ -0,0 +1,113 @@ +# Flask - Docs + +PostHog makes it easy to get data about traffic and usage of your Flask app. Integrating PostHog enables analytics, custom events capture, feature flags, error tracking, and more. + +This guide walks you through integrating PostHog into your Flask app using the [Python SDK](/docs/libraries/python.md). + +## Installation + +To start, run `pip install posthog` to install PostHog’s Python SDK. + +> **Note:** Version `7.x` of the PostHog Python SDK requires Python 3.10 or higher. + +Then, initialize PostHog where you'd like to use it. For example, here's how to capture an event in a simple route: + +app.py + +PostHog AI + +```python +from flask import Flask +from posthog import Posthog +app = Flask(__name__) +posthog = Posthog( + '', + host='https://us.i.posthog.com', +) +@app.route('/api/dashboard', methods=['POST']) +def api_dashboard(): + posthog.capture( + 'dashboard_api_called', + distinct_id='distinct_id_of_your_user', + ) + return '', 204 +``` + +You can find your project token and instance address in [your project settings](https://app.posthog.com/project/settings). + +## Identifying users + +> **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). +> +> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. + +## Request contexts + +Use [contexts](/docs/libraries/python.md#contexts) to share identity, session IDs, and tags across multiple captures during a request: + +Python + +PostHog AI + +```python +from flask import request, session +from posthog import identify_context, set_context_session, tag +@app.route('/api/dashboard', methods=['POST']) +def api_dashboard(): + with posthog.new_context(): + distinct_id = request.headers.get('X-POSTHOG-DISTINCT-ID') or session.get('user_id') + if distinct_id: + identify_context(str(distinct_id)) + session_id = request.headers.get('X-POSTHOG-SESSION-ID') + if session_id: + set_context_session(session_id) + tag('$current_url', request.url) + tag('$request_method', request.method) + tag('$request_path', request.path) + posthog.capture('dashboard_api_called') + return '', 204 +``` + +Events captured without a context or explicit `distinct_id` are sent as [anonymous events](/docs/data/anonymous-vs-identified-events.md) with an auto-generated `distinct_id`. See the [Python SDK docs](/docs/libraries/python.md#person-profiles-and-properties) for more details. + +## Error tracking + +Flask has built-in error handlers. This means PostHog’s default exception autocapture won’t work and we need to manually capture errors instead using `capture_exception()`: + +Python + +PostHog AI + +```python +from flask import Flask, jsonify +from posthog import Posthog +app = Flask(__name__) +posthog = Posthog('', host='https://us.i.posthog.com') +@app.errorhandler(Exception) +def handle_exception(e): + # Capture methods, including capture_exception, return the UUID of the captured event, + # which you can use to find specific errors users encountered + event_id = posthog.capture_exception(e) + # You can show the event ID to your user, and ask them to include it in bug reports + response = jsonify({'message': str(e), 'error_id': event_id}) + response.status_code = 500 + return response +``` + +## Next steps + +For any technical questions for how to integrate specific PostHog features into Flask (such as analytics, feature flags, A/B testing, etc.), have a look at our [Python SDK docs](/docs/libraries/python.md). + +Alternatively, the following tutorials can help you get started: + +- [How to set up analytics in Python and Flask](/tutorials/python-analytics.md) +- [How to set up feature flags in Python and Flask](/tutorials/python-feature-flags.md) +- [How to set up A/B tests in Python and Flask](/tutorials/python-ab-testing.md) + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/skills/instrument-error-tracking/references/laravel.md b/skills/instrument-error-tracking/references/laravel.md new file mode 100644 index 0000000..022f900 --- /dev/null +++ b/skills/instrument-error-tracking/references/laravel.md @@ -0,0 +1,150 @@ +# Laravel - Docs + +PostHog integrates with Laravel through the [PostHog PHP SDK](/docs/libraries/php.md). This page covers Laravel-specific setup. For SDK features such as event capture, identifying users, feature flags, group analytics, and configuration options, see the [PHP SDK docs](/docs/libraries/php.md). + +## Installation + +Install the PHP SDK as described in the [PHP installation guide](/docs/libraries/php.md#installation), then add your project token and host to `.env`: + +.env + +PostHog AI + +```bash +POSTHOG_API_KEY= +POSTHOG_HOST=https://us.i.posthog.com +``` + +Add PostHog to Laravel's services config: + +config/services.php + +PostHog AI + +```php +'posthog' => [ + 'api_key' => env('POSTHOG_API_KEY'), + 'host' => env('POSTHOG_HOST', 'https://us.i.posthog.com'), +], +``` + +Initialize PostHog in the `boot` method of `app/Providers/AppServiceProvider.php`: + +app/Providers/AppServiceProvider.php + +PostHog AI + +```php + config('services.posthog.host'), + ] + ); + } +} +``` + +## Request context middleware + +Client SDKs such as [PostHog JS](/docs/libraries/js.md) can send tracing headers to your Laravel backend. The PHP SDK can read `X-PostHog-Distinct-Id` and `X-PostHog-Session-Id` headers and apply them to events captured during the request. Add middleware like this: + +app/Http/Middleware/PostHogRequestContext.php + +PostHog AI + +```php +headers->all()); + $context['properties'] = array_merge( + $context['properties'] ?? [], + array_filter([ + '$current_url' => $request->fullUrl(), + '$request_method' => $request->method(), + '$request_path' => $request->getPathInfo(), + '$user_agent' => $request->userAgent(), + '$ip' => $request->ip(), + ], static fn ($value): bool => $value !== null && $value !== '') + ); + return PostHog::withContext( + $context, + static fn (): Response => $next($request), + ['fresh' => true] + ); + } +} +``` + +Register this middleware using your Laravel version's normal middleware registration. + +## Error tracking in Laravel + +The PHP SDK supports [error tracking](/docs/libraries/php.md#error-tracking), but Laravel handles most request exceptions before they become uncaught PHP exceptions. Capture Laravel-reported exceptions explicitly. + +In Laravel 11 and later, add a report callback in `bootstrap/app.php`: + +bootstrap/app.php + +PostHog AI + +```php +use Illuminate\Foundation\Configuration\Exceptions; +use PostHog\PostHog; +use Throwable; +->withExceptions(function (Exceptions $exceptions): void { + $exceptions->report(function (Throwable $e): void { + if (! config('services.posthog.api_key')) { + return; + } + PostHog::captureException( + $e, + auth()->id() !== null ? (string) auth()->id() : null, + [ + '$current_url' => request()->fullUrl(), + '$request_method' => request()->method(), + ] + ); + }); +}) +``` + +For older Laravel versions, call `PostHog::captureException()` from your exception handler's `report` method. + +## Long-running processes + +In normal PHP request lifecycles, queued events flush when the client is destroyed. In long-running Laravel processes such as queue workers, Horizon, or Octane, call `PostHog::flush()` after capturing important events or at the end of a job/request. + +## Next steps + +See the [PHP SDK docs](/docs/libraries/php.md) for usage examples and the full API reference. + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/skills/instrument-error-tracking/references/nuxt.md b/skills/instrument-error-tracking/references/nuxt.md index 9356c06..e68ffce 100644 --- a/skills/instrument-error-tracking/references/nuxt.md +++ b/skills/instrument-error-tracking/references/nuxt.md @@ -65,7 +65,7 @@ }, sourcemaps: { enabled: true, - project: '', // Your project ID from PostHog settings https://app.posthog.com/settings/environment#variables + projectId: '', // Your project ID, found in your environment settings: https://app.posthog.com/settings/environment#variables personalApiKey: '', // Your personal API key from PostHog settings https://app.posthog.com/settings/user-api-keys (requires organization:read and error_tracking:write scopes) releaseName: 'my-application', // Optional: defaults to git repository name releaseVersion: '1.0.0', // Optional: defaults to current git commit diff --git a/skills/instrument-error-tracking/references/php.md b/skills/instrument-error-tracking/references/php.md new file mode 100644 index 0000000..4e7c67d --- /dev/null +++ b/skills/instrument-error-tracking/references/php.md @@ -0,0 +1,222 @@ +# PHP Error Tracking installation - Docs + +1. 1 + + ## Install the PHP SDK + + Required + + Install the [PostHog PHP SDK](/docs/libraries/php.md) via Composer: + + Terminal + + PostHog AI + + ```bash + composer require posthog/posthog-php + ``` + +2. 2 + + ## Initialize the client + + Required + + Set your project token and instance address before making any calls: + + PHP + + PostHog AI + + ```php + PostHog\PostHog::init( + '', + ['host' => 'https://us.i.posthog.com'] + ); + ``` + + You can find your project token and instance address in the [project settings](https://app.posthog.com/settings/project) page in PostHog. + +3. 3 + + ## Capture exceptions + + Required + + Use `captureException` to manually capture exceptions and send them to PostHog as `$exception` events with full stack traces. + + ### Basic usage + + PHP + + PostHog AI + + ```php + try { + // Your code that might throw + riskyOperation(); + } catch (\Throwable $e) { + PostHog\PostHog::captureException($e, 'user_distinct_id'); + } + ``` + + ### With additional properties + + You can pass extra properties to include with the exception event: + + PHP + + PostHog AI + + ```php + try { + processOrder($orderId); + } catch (\Throwable $e) { + PostHog\PostHog::captureException($e, 'user_distinct_id', [ + 'order_id' => $orderId, + 'environment' => 'production', + ]); + } + ``` + + You can also pass a plain string if you want to send an error message without a `Throwable`. + +4. 4 + + ## Enable automatic capture + + Recommended + + Automatic capture is opt-in for PHP. When enabled, the SDK installs handlers for uncaught exceptions. With the default `capture_errors: true`, it also captures PHP errors and fatal shutdown errors. + + PHP + + PostHog AI + + ```php + PostHog\PostHog::init( + '', + [ + 'host' => 'https://us.i.posthog.com', + 'error_tracking' => [ + 'enabled' => true, + ], + ] + ); + ``` + + **Existing handlers are preserved** + + The SDK chains existing exception and error handlers instead of replacing your app's behavior. + +5. 5 + + ## Identify users and attach request context + + Recommended + + By default, automatically captured errors are anonymous. Use `context_provider` to attach a `distinctId` and request metadata to every automatically captured error event. + + PHP + + PostHog AI + + ```php + PostHog\PostHog::init( + '', + [ + 'host' => 'https://us.i.posthog.com', + 'error_tracking' => [ + 'enabled' => true, + 'context_provider' => static function (array $payload): array { + return [ + 'distinctId' => $_SESSION['user_id'] ?? null, + 'properties' => [ + '$current_url' => $_SERVER['REQUEST_URI'] ?? null, + '$request_method' => $_SERVER['REQUEST_METHOD'] ?? null, + '$exception_source' => $payload['source'] ?? null, + ], + ]; + }, + ], + ] + ); + ``` + + If `distinctId` is omitted, PostHog sends the event with an auto-generated ID and sets `$process_person_profile` to `false`. + +6. 6 + + ## Configure error tracking options + + Optional + + PHP + + PostHog AI + + ```php + PostHog\PostHog::init( + '', + [ + 'host' => 'https://us.i.posthog.com', + 'error_tracking' => [ + 'enabled' => true, + 'capture_errors' => true, + 'excluded_exceptions' => [ + \InvalidArgumentException::class, + ], + 'max_frames' => 20, + 'context_provider' => static function (array $payload): array { + return [ + 'distinctId' => $_SESSION['user_id'] ?? null, + 'properties' => [], + ]; + }, + ], + ] + ); + ``` + + | Option | Type | Default | Description | + | --- | --- | --- | --- | + | enabled | boolean | false | Enables automatic error tracking handlers. Manual captureException works regardless. | + | capture_errors | boolean | true | When enabled, also captures PHP errors and fatal shutdown errors in addition to uncaught exceptions. | + | excluded_exceptions | array of class strings | [] | Throwable classes to skip during automatic capture. | + | max_frames | integer | 20 | Maximum number of stack frames included in $exception_list. | + | context_provider | callable or null | null | Callback that returns distinctId and extra event properties for automatic captures. | + +7. ## Verify error tracking + + Recommended + + Trigger a test exception to confirm events are being sent to PostHog. You should see them appear in the [Error Tracking](https://app.posthog.com/error_tracking) tab. + + PHP + + PostHog AI + + ```php + PostHog\PostHog::init( + '', + [ + 'host' => 'https://us.i.posthog.com', + 'error_tracking' => [ + 'enabled' => true, + ], + ] + ); + try { + throw new \Exception('Test exception from PHP'); + } catch (\Throwable $e) { + PostHog\PostHog::captureException($e, 'test_user'); + } + ``` + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/skills/instrument-error-tracking/references/react.md b/skills/instrument-error-tracking/references/react.md index 88351b0..583df03 100644 --- a/skills/instrument-error-tracking/references/react.md +++ b/skills/instrument-error-tracking/references/react.md @@ -34,15 +34,15 @@ Required - Add your PostHog project token and host to your environment variables. For Vite-based React apps, use the `VITE_PUBLIC_` prefix: + Add your PostHog project token and host to your environment variables. For Vite-based React apps, use the `VITE_` prefix to expose them to the client: .env PostHog AI ```bash - VITE_PUBLIC_POSTHOG_PROJECT_TOKEN= - VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + VITE_POSTHOG_TOKEN= + VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. 3 @@ -64,12 +64,12 @@ import App from './App.jsx' import { PostHogProvider } from '@posthog/react' const options = { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', } as const createRoot(document.getElementById('root')).render( - + diff --git a/skills/instrument-error-tracking/references/ruby-on-rails.md b/skills/instrument-error-tracking/references/ruby-on-rails.md index de1d156..ad8cb5b 100644 --- a/skills/instrument-error-tracking/references/ruby-on-rails.md +++ b/skills/instrument-error-tracking/references/ruby-on-rails.md @@ -1,187 +1,590 @@ -# Ruby on Rails error tracking installation - Docs +# Ruby on Rails - Docs -1. 1 +PostHog makes it easy to get data about traffic and usage of your Ruby on Rails app. Integrating PostHog enables analytics, custom event capture, feature flags, and automatic exception tracking. - ## Install the gems +This guide walks you through integrating PostHog into your Rails app using the [posthog-rails gem](https://github.com/PostHog/posthog-ruby/tree/main/posthog-rails). - Required +## Beta: integration via LLM - Add the `posthog-ruby` and `posthog-rails` gems to your Gemfile: +Install PostHog for Rails in seconds with our wizard by running this prompt with [LLM coding agents](/blog/envoy-wizard-llm-agent.md) like Cursor and Bolt, or by running it in your terminal. - Gemfile +`npx @posthog/wizard@latest` - PostHog AI +[Learn more](/wizard.md) - ```ruby - gem "posthog-ruby" - gem "posthog-rails" - ``` +Or, to integrate manually, continue with the rest of this guide. - Then run: +## Features - Terminal +- **Automatic exception tracking** – Captures unhandled and rescued exceptions +- **ActiveJob instrumentation** – Tracks background job exceptions +- **User context** – Automatically associates exceptions with the current user +- **Smart filtering** – Excludes common Rails exceptions (404s, etc.) by default +- **Request context** – Adds request metadata and optional PostHog tracing header identity/session context to captured events +- **Rails 7.0+ error reporter** – Integrates with Rails' built-in error reporting - PostHog AI +## Installation - ```bash - bundle install - ``` +Add both gems to your Gemfile: -2. 2 +Gemfile - ## Generate the initializer +PostHog AI - Required +```ruby +gem 'posthog-ruby' +gem 'posthog-rails' +``` - Run the install generator to create the PostHog initializer: +Then run: + +Terminal + +PostHog AI + +```bash +bundle install +``` + +## Identifying users + +> **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). +> +> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. + +### Generate the initializer + +Run the install generator to create the PostHog initializer: + +Terminal + +PostHog AI + +```bash +rails generate posthog:install +``` + +This creates `config/initializers/posthog.rb` with sensible defaults and documentation. + +## Configuration + +`PostHog.init` creates a single client instance used across your app. Avoid creating multiple `PostHog::Client` instances with the same API key, as this can cause dropped events and inconsistent behavior. + +The generated initializer includes the most common options: + +config/initializers/posthog.rb + +PostHog AI + +```ruby +# Rails-specific configuration +PostHog::Rails.configure do |config| + config.auto_capture_exceptions = true # Enable automatic exception capture (default: false) + config.report_rescued_exceptions = true # Report exceptions Rails rescues (default: false) + config.auto_instrument_active_job = true # Instrument background jobs (default: false) + config.use_tracing_headers = true # Use PostHog tracing headers for identity/session context (default: true) + config.capture_user_context = true # Include authenticated user info in exceptions (default: true) + config.current_user_method = :current_user # Method to get current user (default: :current_user) + config.user_id_method = nil # Method to get ID from user object (default: auto-detect) + # Add additional exceptions to ignore + config.excluded_exceptions = ['MyCustomError'] +end +# Core PostHog client initialization +PostHog.init do |config| + # Required: Your PostHog project API key + config.api_key = '' + # Optional: Your PostHog instance URL + config.host = 'https://us.i.posthog.com' + # Optional: Personal API key for feature flags + config.personal_api_key = 'phx_xxxxxxxxx' + # Maximum number of events to queue before dropping (default: 10000) + config.max_queue_size = 10_000 + # Send events synchronously on the calling thread (default: false) + config.sync_mode = false + # Feature flags polling interval in seconds (default: 30) + config.feature_flags_polling_interval = 30 + # Feature flag request timeout in seconds (default: 3) + config.feature_flag_request_timeout_seconds = 3 + # Error callback to detect misconfiguration + config.on_error = proc { |status, msg| + Rails.logger.error("PostHog error: #{msg}") + } + # Before-send callback to modify or drop events + config.before_send = proc { |event| + event[:properties] ||= {} + event[:properties]['environment'] = Rails.env + event + } + # Disable network calls in test mode + config.test_mode = true if Rails.env.test? +end +``` + +You can find your project token and instance address in [your project settings](https://us.posthog.com/project/settings). + +> **Tip:** Use [`Rails.application.credentials`](https://guides.rubyonrails.org/security.html#custom-credentials) to avoid hardcoding API keys. First, add your keys and then reference them in your initializer: +> +> Terminal +> +> PostHog AI +> +> ```bash +> rails credentials:edit +> ``` +> +> config/credentials.yml.enc +> +> PostHog AI +> +> ```yaml +> posthog: +> api_key: +> host: https://us.i.posthog.com +> personal_api_key: phx_xxxxxxxxx +> ``` +> +> config/initializers/posthog.rb +> +> PostHog AI +> +> ```ruby +> config.api_key = Rails.application.credentials.posthog[:api_key] +> config.host = Rails.application.credentials.posthog[:host] +> config.personal_api_key = Rails.application.credentials.posthog[:personal_api_key] +> ``` + +## Capturing events + +Track custom events anywhere in your Rails app: + +Ruby + +PostHog AI + +```ruby +PostHog.capture({ + distinct_id: current_user.id, + event: 'post_created', + properties: { title: @post.title } +}) +``` + +Identify a user and set their person properties: + +Ruby + +PostHog AI + +```ruby +PostHog.identify({ + distinct_id: current_user.id, + properties: { + email: current_user.email, + plan: current_user.plan + } +}) +``` + +The Rails integration delegates methods like `capture`, `identify`, `alias`, `group_identify`, `evaluate_flags`, `capture_exception`, `flush`, and `shutdown` to the initialized `PostHog::Client`. + +## Request context + +PostHog Rails automatically applies request-scoped context to events captured during web requests. Request metadata such as `$current_url`, `$request_method`, `$request_path`, `$user_agent`, and `$ip` is added to event properties. + +When `use_tracing_headers` is enabled, PostHog tracing headers (`X-PostHog-Distinct-Id` and `X-PostHog-Session-Id`) are also used as default `distinct_id` and `$session_id` values. Explicit `distinct_id` and properties passed to `PostHog.capture` always take precedence. + +Disable tracing header identity/session capture if you do not want client-supplied tracing headers used for server-side events. Request metadata is still captured: - Terminal +Ruby - PostHog AI +PostHog AI - ```bash - rails generate posthog:install - ``` +```ruby +PostHog::Rails.config.use_tracing_headers = false +``` - This will create `config/initializers/posthog.rb` with sensible defaults and documentation. +## Error tracking -3. 3 +For full details on setting up error tracking with Rails, see our [Rails error tracking installation guide](/docs/error-tracking/installation/ruby-on-rails.md). - ## Configure PostHog +### Automatic exception tracking - Required +When `auto_capture_exceptions` is enabled, exceptions are automatically captured: - Update `config/initializers/posthog.rb` with your project token and host: +Ruby - config/initializers/posthog.rb +PostHog AI - PostHog AI +```ruby +class PostsController < ApplicationController + def show + @post = Post.find(params[:id]) + # Any exception here is automatically captured + end +end +``` - ```ruby - PostHog.init do |config| - config.api_key = '' - config.host = 'https://us.i.posthog.com' - end - ``` +`report_rescued_exceptions` controls whether exceptions Rails rescues (for example, exceptions rendered by Rails error pages) are captured. Enable it along with `auto_capture_exceptions` for complete error visibility, or leave it disabled to capture only unhandled exceptions. -4. 4 +### Manual exception capture - ## Send events +You can also manually capture exceptions: - Recommended +Ruby - Once installed, you can manually send events to test your integration: +PostHog AI - Ruby +```ruby +PostHog.capture_exception( + exception, + current_user.id, + { custom_property: 'value' } +) +``` - PostHog AI +If you evaluated feature flags for the request, pass the same snapshot to include matching flag properties on the exception event: - ```ruby - PostHog.capture({ - distinct_id: 'user_123', - event: 'button_clicked', - properties: { - button_name: 'signup' - } - }) - ``` +Ruby -5. 5 +PostHog AI - ## Configure error tracking +```ruby +flags = PostHog.evaluate_flags(current_user.id) +PostHog.capture_exception( + exception, + current_user.id, + { custom_property: 'value' }, + flags: flags +) +``` - Required +### Background job exceptions - Update `config/initializers/posthog.rb` to enable automatic exception capture: +When `auto_instrument_active_job` is enabled, ActiveJob exceptions are automatically captured with job context: - config/initializers/posthog.rb +Ruby - PostHog AI +PostHog AI - ```ruby - PostHog::Rails.configure do |config| - config.auto_capture_exceptions = true - config.report_rescued_exceptions = true - config.auto_instrument_active_job = true - config.capture_user_context = true - config.current_user_method = :current_user - end - ``` +```ruby +class EmailJob < ApplicationJob + def perform(user_id) + user = User.find(user_id) + UserMailer.welcome(user).deliver_now + # Exceptions are automatically captured + end +end +``` -6. 6 +#### Associating jobs with users - ## Automatic exception capture +By default, PostHog extracts a `distinct_id` from job arguments by looking for a `user_id` key in hash arguments: - Recommended +Ruby - With `auto_capture_exceptions` enabled, exceptions are automatically captured from your controllers: +PostHog AI - app/controllers/posts\_controller.rb +```ruby +# PostHog will automatically use options[:user_id] as the distinct_id +ProcessOrderJob.perform_later(order.id, user_id: current_user.id) +``` - PostHog AI +For more control, use the `posthog_distinct_id` class method. The proc or block receives the same arguments as `perform`: - ```ruby - class PostsController < ApplicationController - def show - @post = Post.find(params[:id]) - # Any exception here is automatically captured - end +Ruby + +PostHog AI + +```ruby +class SendWelcomeEmailJob < ApplicationJob + posthog_distinct_id ->(user, _options) { user.id } + def perform(user, options = {}) + UserMailer.welcome(user).deliver_now + end +end +``` + +You can also use a block: + +Ruby + +PostHog AI + +```ruby +class ProcessOrderJob < ApplicationJob + posthog_distinct_id do |_order, notify_user_id| + notify_user_id + end + def perform(order, notify_user_id) + # Process the order... + end +end +``` + +### Rails 7.0+ error reporter + +PostHog integrates with Rails' built-in error reporting: + +Ruby + +PostHog AI + +```ruby +# These errors are automatically sent to PostHog +Rails.error.handle do + # Code that might raise an error +end +Rails.error.record(exception, context: { user_id: current_user.id }) +``` + +PostHog automatically extracts the user's distinct ID from `user_id` or `distinct_id` in the context hash. Other context keys are included as properties on the exception event. + +### User context + +PostHog Rails automatically captures authenticated user information from your controllers for exceptions. Authenticated Rails user context takes precedence over client-supplied tracing headers for exception identity. + +If your user method has a different name, configure it: + +Ruby + +PostHog AI + +```ruby +PostHog::Rails.config.current_user_method = :logged_in_user +``` + +#### User ID extraction + +By default, PostHog Rails auto-detects the user's distinct ID by trying these methods in order: + +1. `posthog_distinct_id` – Define this on your User model for full control +2. `distinct_id` – Common analytics convention +3. `id` – Standard ActiveRecord primary key +4. `pk` – Primary key alias +5. `uuid` – For UUID-based primary keys + +It also checks hash-like users for `id`, `pk`, and `uuid` keys. + +You can configure a specific method: + +Ruby + +PostHog AI + +```ruby +PostHog::Rails.config.user_id_method = :email +``` + +Or define a method on your User model: + +Ruby + +PostHog AI + +```ruby +class User < ApplicationRecord + def posthog_distinct_id + "user_#{id}" # or external_id, or any unique identifier + end +end +``` + +### Excluded exceptions + +The following exceptions are not reported by default (common 4xx errors): + +- `AbstractController::ActionNotFound` +- `ActionController::BadRequest` +- `ActionController::InvalidAuthenticityToken` +- `ActionController::InvalidCrossOriginRequest` +- `ActionController::MethodNotAllowed` +- `ActionController::NotImplemented` +- `ActionController::ParameterMissing` +- `ActionController::RoutingError` +- `ActionController::UnknownFormat` +- `ActionController::UnknownHttpMethod` +- `ActionDispatch::Http::Parameters::ParseError` +- `ActiveRecord::RecordNotFound` +- `ActiveRecord::RecordNotUnique` + +Add more with: + +Ruby + +PostHog AI + +```ruby +PostHog::Rails.config.excluded_exceptions = ['MyException'] +``` + +## Feature flags + +Evaluate flags once for the current user, then read values from the returned snapshot: + +Ruby + +PostHog AI + +```ruby +class PostsController < ApplicationController + def show + flags = PostHog.evaluate_flags(current_user.id) + if flags.enabled?('new-post-design') + render 'posts/show_new' + else + render 'posts/show' end - ``` + end +end +``` + +For multivariate flags and experiments, use `get_flag`: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +variant = flags.get_flag('checkout-experiment') +if variant == 'test' + # Do something differently +end +``` + +When capturing an event after branching on a flag, pass the same `flags` snapshot so the event includes the exact flag values used by your code: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +PostHog.capture({ + distinct_id: current_user.id, + event: 'checkout_started', + flags: flags.only_accessed +}) +``` + +For local evaluation, ensure you've set `personal_api_key`: -7. 7 +Ruby - ## Background jobs +PostHog AI - Optional +```ruby +config.personal_api_key = Rails.application.credentials.posthog[:personal_api_key] +``` - When `auto_instrument_active_job` is enabled, ActiveJob exceptions are automatically captured: +See our [Ruby SDK docs](/docs/libraries/ruby.md#local-evaluation) for details on local evaluation with Puma and Unicorn servers. - app/jobs/email\_job.rb +> **Note:** `PostHog.is_feature_enabled`, `PostHog.get_feature_flag`, `PostHog.get_feature_flag_result`, `PostHog.get_feature_flag_payload`, and `PostHog.capture({ ..., send_feature_flags: true })` still work during the migration period, but they're deprecated. Prefer `PostHog.evaluate_flags` for new code. + +## Testing + +In your test environment, disable network calls with test mode: + +config/environments/test.rb + +PostHog AI + +```ruby +PostHog.init do |config| + config.api_key = '' + config.test_mode = true +end +``` + +Or in your specs: + +spec/rails\_helper.rb + +PostHog AI + +```ruby +RSpec.configure do |config| + config.before(:each) do + allow(PostHog).to receive(:capture) + end +end +``` + +## Configuration reference + +### Core PostHog options + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| api_key | String | required | Your PostHog project token. | +| host | String | https://us.i.posthog.com | Fully qualified PostHog API host. | +| personal_api_key | String | nil | Personal API key for local feature flag evaluation and remote config payloads. | +| max_queue_size | Integer | 10000 | Maximum number of events to keep in the async queue before dropping new events. | +| test_mode | Boolean | false | Keep events queued and do not send them. Useful for tests. | +| sync_mode | Boolean | false | Send events synchronously on the calling thread. | +| on_error | Proc | no-op | Callback called as on_error.call(status, error). | +| feature_flags_polling_interval | Integer | 30 | Seconds between local feature flag definition polls. | +| feature_flag_request_timeout_seconds | Integer | 3 | Timeout, in seconds, for feature flag requests. | +| before_send | Proc | nil | Callback that receives the event hash before it is queued or sent. Return a modified event hash, or nil to drop the event. | + +The `PostHog.init` block supports the options above. Less common core options like `batch_size`, `disable_singleton_warning`, `skip_ssl_verification`, and the experimental `flag_definition_cache_provider` can be passed as an options hash to `PostHog.init(...)`; see the [Ruby SDK docs](/docs/libraries/ruby.md#configuration) for details. + +### Rails-specific options + +Configure these via `PostHog::Rails.configure` or `PostHog::Rails.config`: + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| auto_capture_exceptions | Boolean | false | Automatically capture exceptions. | +| report_rescued_exceptions | Boolean | false | Report exceptions Rails rescues. | +| auto_instrument_active_job | Boolean | false | Capture ActiveJob exceptions with job context. | +| excluded_exceptions | Array | [] | Additional exception class names to ignore. | +| use_tracing_headers | Boolean | true | Use X-PostHog-Distinct-Id and X-PostHog-Session-Id as request-scoped defaults. | +| capture_user_context | Boolean | true | Include authenticated user info in exceptions. | +| current_user_method | Symbol | :current_user | Controller method used to fetch the current user. | +| user_id_method | Symbol | nil | Method used to extract the distinct ID from the user object. Auto-detects when nil. | + +## Troubleshooting + +### Exceptions not being captured + +1. Verify PostHog is initialized: + + Ruby PostHog AI ```ruby - class EmailJob < ApplicationJob - def perform(user_id) - user = User.find(user_id) - UserMailer.welcome(user).deliver_now - # Exceptions are automatically captured with job context - end - end + Rails.console + > PostHog.initialized? + => true ``` -8. 8 - - ## Manually capture exceptions - - Optional +2. Check your excluded exceptions list. - You can also manually capture exceptions that you handle in your application: +3. Verify middleware is installed: Ruby PostHog AI ```ruby - PostHog.capture_exception( - exception, - current_user.id, - { custom_property: 'value' } - ) + Rails.application.middleware ``` -9. ## Verify error tracking +### User context not working - Recommended +1. Verify `current_user_method` matches your controller method. +2. Check that the user object responds to `posthog_distinct_id`, `distinct_id`, `id`, `pk`, or `uuid`. +3. If using a custom identifier, set `PostHog::Rails.config.user_id_method = :your_method`. - *Confirm events are being sent to PostHog* +### Feature flags not working - Before proceeding, let's make sure exception events are being captured and sent to PostHog. You should see events appear in the activity feed. +Ensure you've set `personal_api_key` in your configuration. - ![Activity feed with events](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250729_ouxl_f788dd8cd2.png)![Activity feed with events](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250729_owae_7c3490822c.png) +## Next steps - [Check for exceptions in PostHog](https://app.posthog.com/activity/explore) +For any technical questions for how to integrate specific PostHog features into Rails (such as analytics, feature flags, A/B testing, etc.), have a look at our [Ruby SDK docs](/docs/libraries/ruby.md). ### Community questions diff --git a/skills/instrument-error-tracking/references/ruby.md b/skills/instrument-error-tracking/references/ruby.md index 337af28..214ef13 100644 --- a/skills/instrument-error-tracking/references/ruby.md +++ b/skills/instrument-error-tracking/references/ruby.md @@ -80,8 +80,8 @@ rescue => e posthog.capture_exception( e, - distinct_id: 'user_distinct_id', - properties: { + 'user_distinct_id', + { custom_property: 'custom_value' } ) @@ -94,7 +94,7 @@ | --- | --- | --- | | exception | Exception | The exception object to capture (required) | | distinct_id | String | The distinct ID of the user (optional) | - | properties | Hash | Additional properties to attach to the exception event (optional) | + | additional_properties | Hash | Additional properties to attach to the exception event (optional) | 5. ## Verify error tracking diff --git a/skills/instrument-feature-flags/SKILL.md b/skills/instrument-feature-flags/SKILL.md index af51634..e308f44 100644 --- a/skills/instrument-feature-flags/SKILL.md +++ b/skills/instrument-feature-flags/SKILL.md @@ -55,8 +55,12 @@ STEP 6: Set up environment variables. - `references/web.md` - Web feature flags installation - docs - `references/nodejs.md` - Node.js feature flags installation - docs - `references/python.md` - Python feature flags installation - docs +- `references/django.md` - Django - docs +- `references/flask.md` - Flask - docs - `references/php.md` - Php feature flags installation - docs +- `references/laravel.md` - Laravel - docs - `references/ruby.md` - Ruby feature flags installation - docs +- `references/ruby-on-rails.md` - Ruby on rails - docs - `references/go.md` - Go feature flags installation - docs - `references/java.md` - Java feature flags installation - docs - `references/rust.md` - Rust feature flags installation - docs diff --git a/skills/instrument-feature-flags/references/adding-feature-flag-code.md b/skills/instrument-feature-flags/references/adding-feature-flag-code.md index 718d475..b23a8eb 100644 --- a/skills/instrument-feature-flags/references/adding-feature-flag-code.md +++ b/skills/instrument-feature-flags/references/adding-feature-flag-code.md @@ -65,6 +65,24 @@ The `onFeatureFlags` callback receives the following parameters: You won't usually need to use these, but they are useful if you want to be extra careful about feature flags not being loaded yet because of a network error and/or a network timeout (see `feature_flag_request_timeout_ms`). +### Evaluating only specific flags + +By default, the JavaScript SDK requests that every eligible feature flag be evaluated for the current user. If you'd only like to evaluate and return a subset of flags, pass `flag_keys` when initializing PostHog: + +Web + +PostHog AI + +```javascript +posthog.init('', { + api_host: 'https://us.i.posthog.com', + defaults: '2026-01-30', + flag_keys: ['checkout-flow', 'new-dashboard'], +}) +``` + +PostHog scopes evaluation and the response to those keys for this SDK instance. Dependency flags required to evaluate requested flags may also be evaluated and returned. Leave `flag_keys` unset to evaluate all eligible flags. + ### Reloading feature flags Feature flag values are cached. If something has changed with your user and you'd like to refetch their flag values, call: @@ -896,6 +914,8 @@ if ($enabledVariant === 'variant-key') { // replace 'variant-key' with the key o `$flags->getFlag()` returns the variant string for multivariate flags, `true` for enabled boolean flags, `false` for disabled flags, and `null` when the flag wasn't returned by the evaluation. +You can also call `$flags->getKeys()` to list the evaluated flag keys, or `$flags->getEventProperties()` to get the `$feature/` and `$active_feature_flags` properties that would be attached to a captured event. + > **Note:** `PostHog::isFeatureEnabled()`, `PostHog::getFeatureFlag()`, `PostHog::getFeatureFlagPayload()`, and `capture(['send_feature_flags' => true])` still work during the migration period, but they're deprecated. Prefer `evaluateFlags()` for new code. ### Step 2: Include feature flag information when capturing events @@ -985,11 +1005,31 @@ $flags = PostHog::evaluateFlags( ); ``` +### Optional evaluation parameters + +`evaluateFlags()` also accepts optional parameters for local evaluation and GeoIP behavior: + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags( + distinctId: 'distinct_id_of_your_user', + groups: ['company' => 'company_id_in_your_db'], + personProperties: ['plan' => 'pro'], + groupProperties: ['company' => ['employees' => 11]], + onlyEvaluateLocally: false, // Defaults to false. Set to true to avoid a remote fallback. + disableGeoip: false, // Defaults to false. Set to true to disable GeoIP enrichment during remote evaluation. + flagKeys: ['checkout-flow', 'new-dashboard'], +); +``` + ### Sending `$feature_flag_called` events Capturing `$feature_flag_called` events enables PostHog to know when a flag was accessed by a user and provide [analytics and insights](/docs/product-analytics/insights.md) on the flag. With `evaluateFlags()`, the SDK sends this event when you call `$flags->isEnabled()` or `$flags->getFlag()` for a flag. -The SDK deduplicates these events per `(distinct_id, flag, value)` in a local cache. If you reinitialize the PostHog client, the cache resets and `$feature_flag_called` events may be sent again. PostHog handles duplicates, so duplicate `$feature_flag_called` events don't affect your analytics. +The SDK deduplicates these events per `(flag key, distinct_id)` in a local cache. If you reinitialize the PostHog client, the cache resets and `$feature_flag_called` events may be sent again. PostHog handles duplicates, so duplicate `$feature_flag_called` events don't affect your analytics. `$flags->getFlagPayload()` doesn't send `$feature_flag_called` events and doesn't count as an access for `onlyAccessed()`. @@ -1108,7 +1148,7 @@ end `flags.get_flag()` returns the variant string for multivariate flags, `true` for enabled boolean flags, `false` for disabled flags, and `nil` when the flag wasn't returned by the evaluation. -> **Note:** `posthog.is_feature_enabled()`, `posthog.get_feature_flag()`, `posthog.get_feature_flag_payload()`, and `capture(send_feature_flags: true)` still work during the migration period, but they're deprecated. Prefer `evaluate_flags()` for new code. +> **Note:** `posthog.is_feature_enabled()`, `posthog.get_feature_flag()`, `posthog.get_feature_flag_result()`, `posthog.get_feature_flag_payload()`, and `capture({ ..., send_feature_flags: true })` still work during the migration period, but they're deprecated. Prefer `evaluate_flags()` for new code. ### Step 2: Include feature flag information when capturing events @@ -1197,6 +1237,36 @@ flags = posthog.evaluate_flags( ) ``` +### Evaluating locally only + +If you want to skip the remote `/flags` request and only use locally cached definitions, pass `only_evaluate_locally: true`: + +Ruby + +PostHog AI + +```ruby +flags = posthog.evaluate_flags( + 'distinct_id_of_your_user', + only_evaluate_locally: true, +) +``` + +### Disabling GeoIP for flag evaluation + +Pass `disable_geoip: true` to disable GeoIP lookup for remote flag evaluation: + +Ruby + +PostHog AI + +```ruby +flags = posthog.evaluate_flags( + 'distinct_id_of_your_user', + disable_geoip: true, +) +``` + ### Sending `$feature_flag_called` events Capturing `$feature_flag_called` events enables PostHog to know when a flag was accessed by a user and provide [analytics and insights](/docs/product-analytics/insights.md) on the flag. With `evaluate_flags()`, the SDK sends this event when you call `flags.enabled?()` or `flags.get_flag()` for a flag. diff --git a/skills/instrument-feature-flags/references/django.md b/skills/instrument-feature-flags/references/django.md new file mode 100644 index 0000000..af5d8ba --- /dev/null +++ b/skills/instrument-feature-flags/references/django.md @@ -0,0 +1,259 @@ +# Django - Docs + +PostHog makes it easy to get data about traffic and usage of your Django app. Integrating PostHog enables analytics, custom events capture, feature flags, error tracking, and more. + +This guide walks you through integrating PostHog into your Django app using the [Python SDK](/docs/libraries/python.md). + +## Beta: integration via LLM + +Install PostHog for Django in seconds with our wizard by running this prompt with [LLM coding agents](/blog/envoy-wizard-llm-agent.md) like Cursor and Bolt, or by running it in your terminal. + +`npx @posthog/wizard@latest` + +[Learn more](/wizard.md) + +Or, to integrate manually, continue with the rest of this guide. + +## Installation + +To start, run `pip install posthog` to install PostHog’s Python SDK. + +> **Note:** Version `7.x` of the PostHog Python SDK requires Python 3.10 or higher. + +Then, configure PostHog in your app config so it's initialized when Django starts: + +your\_app/apps.py + +PostHog AI + +```python +from django.apps import AppConfig +import posthog +class YourAppConfig(AppConfig): + name = 'your_app_name' + def ready(self): + posthog.api_key = '' + posthog.host = 'https://us.i.posthog.com' +``` + +Next, if you haven't done so already, add your `AppConfig` to `INSTALLED_APPS` in `settings.py`: + +settings.py + +PostHog AI + +```python +INSTALLED_APPS = [ + # ... other apps + 'your_app_name.apps.YourAppConfig', +] +``` + +You can find your project token and instance address in [your project settings](https://app.posthog.com/project/settings). + +To capture events from any file, import `posthog` and call the method you need. For example: + +Python + +PostHog AI + +```python +import posthog +from posthog import identify_context +def some_request(request): + with posthog.new_context(): + # Django includes request.user for anonymous visitors too. Only identify + # the context when the visitor is logged in. + if request.user.is_authenticated: + identify_context(str(request.user.pk)) + posthog.capture('event_name') +``` + +Events captured without a context or explicit `distinct_id` are sent as [anonymous events](/docs/data/anonymous-vs-identified-events.md) with an auto-generated `distinct_id`. See the [Python SDK docs](/docs/libraries/python.md#person-profiles-and-properties) for more details. + +## Identifying users + +> **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). +> +> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. + +## Django contexts middleware + +The Python SDK provides a Django middleware that automatically wraps all requests with a [context](/docs/libraries/python.md#contexts). This middleware extracts session and user information from each request and tags all events captured during that request with relevant metadata. + +### Basic setup + +Add the middleware to your Django settings. If your app uses Django authentication, place it after `django.contrib.auth.middleware.AuthenticationMiddleware` so the middleware can use the authenticated Django user as a distinct ID fallback and capture the user's email. + +Python + +PostHog AI + +```python +MIDDLEWARE = [ + # ... other middleware + 'posthog.integrations.django.PosthogContextMiddleware', + # ... other middleware +] +``` + +The middleware uses the globally configured `posthog` client by default, so you don't need to create or pass it a separate client instance. + +The middleware automatically extracts and uses: + +- **Session ID** from the `X-POSTHOG-SESSION-ID` header, if present +- **Distinct ID** from the `X-POSTHOG-DISTINCT-ID` header, if present, falling back to the authenticated Django user's `pk` (Django's primary-key alias, which works with custom user models) +- **User email** from the authenticated Django user's `email` as `email` +- **Current URL** as `$current_url` +- **Request method** as `$request_method` +- **Request path** as `$request_path` +- **Forwarded IP address** from `X-Forwarded-For` as `$ip` +- **User agent** from `User-Agent` as `$user_agent` + +The session and distinct ID headers are sanitized before use. Empty values are ignored, control characters are removed, values are trimmed, and values are capped at 1000 characters. + +All events captured during the request (including exceptions) include these properties and are associated with the extracted session and distinct ID. + +If you are using PostHog on your frontend, the JavaScript Web SDK will add the session and distinct ID headers automatically if you enable tracing headers. + +JavaScript + +PostHog AI + +```javascript +posthog.init('', { + __add_tracing_headers: ['your-backend-domain.com'] +}) +``` + +### Exception capture + +By default, the middleware captures exceptions and sends them to PostHog's error tracking using the globally configured `posthog` client. This includes Django view exceptions that Django converts into error responses. + +Disable this by setting: + +Python + +PostHog AI + +```python +# settings.py +POSTHOG_MW_CAPTURE_EXCEPTIONS = False +``` + +### Adding custom tags + +Use `POSTHOG_MW_EXTRA_TAGS` to add custom properties to all requests: + +Python + +PostHog AI + +```python +# settings.py +def add_user_tags(request): + # type: (HttpRequest) -> Dict[str, Any] + tags = {} + if hasattr(request, 'user') and request.user.is_authenticated: + # Use pk instead of id so this works with custom User primary keys. + tags['user_id'] = str(request.user.pk) + tags['email'] = request.user.email + return tags +POSTHOG_MW_EXTRA_TAGS = add_user_tags +``` + +#### Filtering requests + +Skip tracking for certain requests using `POSTHOG_MW_REQUEST_FILTER`: + +Python + +PostHog AI + +```python +# settings.py +def should_track_request(request): + # type: (HttpRequest) -> bool + # Don't track health checks or admin requests + if request.path.startswith('/health') or request.path.startswith('/admin'): + return False + return True +POSTHOG_MW_REQUEST_FILTER = should_track_request +``` + +### Modifying default tags + +Use `POSTHOG_MW_TAG_MAP` to modify or remove default tags: + +Python + +PostHog AI + +```python +# settings.py +def customize_tags(tags): + # type: (Dict[str, Any]) -> Dict[str, Any] + # Remove URL for privacy + tags.pop('$current_url', None) + # Add custom prefix to method + if '$request_method' in tags: + tags['http_method'] = tags.pop('$request_method') + return tags +POSTHOG_MW_TAG_MAP = customize_tags +``` + +### Complete configuration example + +Python + +PostHog AI + +```python +# settings.py +def add_request_context(request): + # type: (HttpRequest) -> Dict[str, Any] + tags = {} + if hasattr(request, 'user') and request.user.is_authenticated: + tags['user_type'] = 'authenticated' + # Use pk instead of id so this works with custom User primary keys. + tags['user_id'] = str(request.user.pk) + else: + tags['user_type'] = 'anonymous' + # Add request info + tags['user_agent'] = request.META.get('HTTP_USER_AGENT', '') + return tags +def filter_tracking(request): + # type: (HttpRequest) -> bool + # Skip internal endpoints + return not request.path.startswith(('/health', '/metrics', '/admin')) +def clean_tags(tags): + # type: (Dict[str, Any]) -> Dict[str, Any] + # Remove sensitive data + tags.pop('user_agent', None) + return tags +POSTHOG_MW_EXTRA_TAGS = add_request_context +POSTHOG_MW_REQUEST_FILTER = filter_tracking +POSTHOG_MW_TAG_MAP = clean_tags +POSTHOG_MW_CAPTURE_EXCEPTIONS = True +``` + +All events captured within the request context automatically include the configured tags and are associated with the session and user identified from the request headers or Django authentication. + +The middleware supports both sync (WSGI) and async (ASGI) Django applications. In async mode, it uses Django's `request.auser()` API when available to avoid synchronous user access. + +## Next steps + +For any technical questions for how to integrate specific PostHog features into Django (such as analytics, feature flags, A/B testing, etc.), have a look at our [Python SDK docs](/docs/libraries/python.md). + +Alternatively, the following tutorials can help you get started: + +- [Setting up Django analytics, feature flags, and more](/tutorials/django-analytics.md) +- [How to set up A/B tests in Django](/tutorials/django-ab-tests.md) + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/skills/instrument-feature-flags/references/flask.md b/skills/instrument-feature-flags/references/flask.md new file mode 100644 index 0000000..e8ec7bf --- /dev/null +++ b/skills/instrument-feature-flags/references/flask.md @@ -0,0 +1,113 @@ +# Flask - Docs + +PostHog makes it easy to get data about traffic and usage of your Flask app. Integrating PostHog enables analytics, custom events capture, feature flags, error tracking, and more. + +This guide walks you through integrating PostHog into your Flask app using the [Python SDK](/docs/libraries/python.md). + +## Installation + +To start, run `pip install posthog` to install PostHog’s Python SDK. + +> **Note:** Version `7.x` of the PostHog Python SDK requires Python 3.10 or higher. + +Then, initialize PostHog where you'd like to use it. For example, here's how to capture an event in a simple route: + +app.py + +PostHog AI + +```python +from flask import Flask +from posthog import Posthog +app = Flask(__name__) +posthog = Posthog( + '', + host='https://us.i.posthog.com', +) +@app.route('/api/dashboard', methods=['POST']) +def api_dashboard(): + posthog.capture( + 'dashboard_api_called', + distinct_id='distinct_id_of_your_user', + ) + return '', 204 +``` + +You can find your project token and instance address in [your project settings](https://app.posthog.com/project/settings). + +## Identifying users + +> **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). +> +> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. + +## Request contexts + +Use [contexts](/docs/libraries/python.md#contexts) to share identity, session IDs, and tags across multiple captures during a request: + +Python + +PostHog AI + +```python +from flask import request, session +from posthog import identify_context, set_context_session, tag +@app.route('/api/dashboard', methods=['POST']) +def api_dashboard(): + with posthog.new_context(): + distinct_id = request.headers.get('X-POSTHOG-DISTINCT-ID') or session.get('user_id') + if distinct_id: + identify_context(str(distinct_id)) + session_id = request.headers.get('X-POSTHOG-SESSION-ID') + if session_id: + set_context_session(session_id) + tag('$current_url', request.url) + tag('$request_method', request.method) + tag('$request_path', request.path) + posthog.capture('dashboard_api_called') + return '', 204 +``` + +Events captured without a context or explicit `distinct_id` are sent as [anonymous events](/docs/data/anonymous-vs-identified-events.md) with an auto-generated `distinct_id`. See the [Python SDK docs](/docs/libraries/python.md#person-profiles-and-properties) for more details. + +## Error tracking + +Flask has built-in error handlers. This means PostHog’s default exception autocapture won’t work and we need to manually capture errors instead using `capture_exception()`: + +Python + +PostHog AI + +```python +from flask import Flask, jsonify +from posthog import Posthog +app = Flask(__name__) +posthog = Posthog('', host='https://us.i.posthog.com') +@app.errorhandler(Exception) +def handle_exception(e): + # Capture methods, including capture_exception, return the UUID of the captured event, + # which you can use to find specific errors users encountered + event_id = posthog.capture_exception(e) + # You can show the event ID to your user, and ask them to include it in bug reports + response = jsonify({'message': str(e), 'error_id': event_id}) + response.status_code = 500 + return response +``` + +## Next steps + +For any technical questions for how to integrate specific PostHog features into Flask (such as analytics, feature flags, A/B testing, etc.), have a look at our [Python SDK docs](/docs/libraries/python.md). + +Alternatively, the following tutorials can help you get started: + +- [How to set up analytics in Python and Flask](/tutorials/python-analytics.md) +- [How to set up feature flags in Python and Flask](/tutorials/python-feature-flags.md) +- [How to set up A/B tests in Python and Flask](/tutorials/python-ab-testing.md) + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/skills/instrument-feature-flags/references/laravel.md b/skills/instrument-feature-flags/references/laravel.md new file mode 100644 index 0000000..022f900 --- /dev/null +++ b/skills/instrument-feature-flags/references/laravel.md @@ -0,0 +1,150 @@ +# Laravel - Docs + +PostHog integrates with Laravel through the [PostHog PHP SDK](/docs/libraries/php.md). This page covers Laravel-specific setup. For SDK features such as event capture, identifying users, feature flags, group analytics, and configuration options, see the [PHP SDK docs](/docs/libraries/php.md). + +## Installation + +Install the PHP SDK as described in the [PHP installation guide](/docs/libraries/php.md#installation), then add your project token and host to `.env`: + +.env + +PostHog AI + +```bash +POSTHOG_API_KEY= +POSTHOG_HOST=https://us.i.posthog.com +``` + +Add PostHog to Laravel's services config: + +config/services.php + +PostHog AI + +```php +'posthog' => [ + 'api_key' => env('POSTHOG_API_KEY'), + 'host' => env('POSTHOG_HOST', 'https://us.i.posthog.com'), +], +``` + +Initialize PostHog in the `boot` method of `app/Providers/AppServiceProvider.php`: + +app/Providers/AppServiceProvider.php + +PostHog AI + +```php + config('services.posthog.host'), + ] + ); + } +} +``` + +## Request context middleware + +Client SDKs such as [PostHog JS](/docs/libraries/js.md) can send tracing headers to your Laravel backend. The PHP SDK can read `X-PostHog-Distinct-Id` and `X-PostHog-Session-Id` headers and apply them to events captured during the request. Add middleware like this: + +app/Http/Middleware/PostHogRequestContext.php + +PostHog AI + +```php +headers->all()); + $context['properties'] = array_merge( + $context['properties'] ?? [], + array_filter([ + '$current_url' => $request->fullUrl(), + '$request_method' => $request->method(), + '$request_path' => $request->getPathInfo(), + '$user_agent' => $request->userAgent(), + '$ip' => $request->ip(), + ], static fn ($value): bool => $value !== null && $value !== '') + ); + return PostHog::withContext( + $context, + static fn (): Response => $next($request), + ['fresh' => true] + ); + } +} +``` + +Register this middleware using your Laravel version's normal middleware registration. + +## Error tracking in Laravel + +The PHP SDK supports [error tracking](/docs/libraries/php.md#error-tracking), but Laravel handles most request exceptions before they become uncaught PHP exceptions. Capture Laravel-reported exceptions explicitly. + +In Laravel 11 and later, add a report callback in `bootstrap/app.php`: + +bootstrap/app.php + +PostHog AI + +```php +use Illuminate\Foundation\Configuration\Exceptions; +use PostHog\PostHog; +use Throwable; +->withExceptions(function (Exceptions $exceptions): void { + $exceptions->report(function (Throwable $e): void { + if (! config('services.posthog.api_key')) { + return; + } + PostHog::captureException( + $e, + auth()->id() !== null ? (string) auth()->id() : null, + [ + '$current_url' => request()->fullUrl(), + '$request_method' => request()->method(), + ] + ); + }); +}) +``` + +For older Laravel versions, call `PostHog::captureException()` from your exception handler's `report` method. + +## Long-running processes + +In normal PHP request lifecycles, queued events flush when the client is destroyed. In long-running Laravel processes such as queue workers, Horizon, or Octane, call `PostHog::flush()` after capturing important events or at the end of a job/request. + +## Next steps + +See the [PHP SDK docs](/docs/libraries/php.md) for usage examples and the full API reference. + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/skills/instrument-feature-flags/references/react.md b/skills/instrument-feature-flags/references/react.md index 5e0244d..3a8e829 100644 --- a/skills/instrument-feature-flags/references/react.md +++ b/skills/instrument-feature-flags/references/react.md @@ -34,15 +34,15 @@ Required - Add your PostHog project token and host to your environment variables. For Vite-based React apps, use the `VITE_PUBLIC_` prefix: + Add your PostHog project token and host to your environment variables. For Vite-based React apps, use the `VITE_` prefix to expose them to the client: .env PostHog AI ```bash - VITE_PUBLIC_POSTHOG_PROJECT_TOKEN= - VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + VITE_POSTHOG_TOKEN= + VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. 3 @@ -64,12 +64,12 @@ import App from './App.jsx' import { PostHogProvider } from '@posthog/react' const options = { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', } as const createRoot(document.getElementById('root')).render( - + diff --git a/skills/instrument-feature-flags/references/ruby-on-rails.md b/skills/instrument-feature-flags/references/ruby-on-rails.md new file mode 100644 index 0000000..ad8cb5b --- /dev/null +++ b/skills/instrument-feature-flags/references/ruby-on-rails.md @@ -0,0 +1,595 @@ +# Ruby on Rails - Docs + +PostHog makes it easy to get data about traffic and usage of your Ruby on Rails app. Integrating PostHog enables analytics, custom event capture, feature flags, and automatic exception tracking. + +This guide walks you through integrating PostHog into your Rails app using the [posthog-rails gem](https://github.com/PostHog/posthog-ruby/tree/main/posthog-rails). + +## Beta: integration via LLM + +Install PostHog for Rails in seconds with our wizard by running this prompt with [LLM coding agents](/blog/envoy-wizard-llm-agent.md) like Cursor and Bolt, or by running it in your terminal. + +`npx @posthog/wizard@latest` + +[Learn more](/wizard.md) + +Or, to integrate manually, continue with the rest of this guide. + +## Features + +- **Automatic exception tracking** – Captures unhandled and rescued exceptions +- **ActiveJob instrumentation** – Tracks background job exceptions +- **User context** – Automatically associates exceptions with the current user +- **Smart filtering** – Excludes common Rails exceptions (404s, etc.) by default +- **Request context** – Adds request metadata and optional PostHog tracing header identity/session context to captured events +- **Rails 7.0+ error reporter** – Integrates with Rails' built-in error reporting + +## Installation + +Add both gems to your Gemfile: + +Gemfile + +PostHog AI + +```ruby +gem 'posthog-ruby' +gem 'posthog-rails' +``` + +Then run: + +Terminal + +PostHog AI + +```bash +bundle install +``` + +## Identifying users + +> **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). +> +> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. + +### Generate the initializer + +Run the install generator to create the PostHog initializer: + +Terminal + +PostHog AI + +```bash +rails generate posthog:install +``` + +This creates `config/initializers/posthog.rb` with sensible defaults and documentation. + +## Configuration + +`PostHog.init` creates a single client instance used across your app. Avoid creating multiple `PostHog::Client` instances with the same API key, as this can cause dropped events and inconsistent behavior. + +The generated initializer includes the most common options: + +config/initializers/posthog.rb + +PostHog AI + +```ruby +# Rails-specific configuration +PostHog::Rails.configure do |config| + config.auto_capture_exceptions = true # Enable automatic exception capture (default: false) + config.report_rescued_exceptions = true # Report exceptions Rails rescues (default: false) + config.auto_instrument_active_job = true # Instrument background jobs (default: false) + config.use_tracing_headers = true # Use PostHog tracing headers for identity/session context (default: true) + config.capture_user_context = true # Include authenticated user info in exceptions (default: true) + config.current_user_method = :current_user # Method to get current user (default: :current_user) + config.user_id_method = nil # Method to get ID from user object (default: auto-detect) + # Add additional exceptions to ignore + config.excluded_exceptions = ['MyCustomError'] +end +# Core PostHog client initialization +PostHog.init do |config| + # Required: Your PostHog project API key + config.api_key = '' + # Optional: Your PostHog instance URL + config.host = 'https://us.i.posthog.com' + # Optional: Personal API key for feature flags + config.personal_api_key = 'phx_xxxxxxxxx' + # Maximum number of events to queue before dropping (default: 10000) + config.max_queue_size = 10_000 + # Send events synchronously on the calling thread (default: false) + config.sync_mode = false + # Feature flags polling interval in seconds (default: 30) + config.feature_flags_polling_interval = 30 + # Feature flag request timeout in seconds (default: 3) + config.feature_flag_request_timeout_seconds = 3 + # Error callback to detect misconfiguration + config.on_error = proc { |status, msg| + Rails.logger.error("PostHog error: #{msg}") + } + # Before-send callback to modify or drop events + config.before_send = proc { |event| + event[:properties] ||= {} + event[:properties]['environment'] = Rails.env + event + } + # Disable network calls in test mode + config.test_mode = true if Rails.env.test? +end +``` + +You can find your project token and instance address in [your project settings](https://us.posthog.com/project/settings). + +> **Tip:** Use [`Rails.application.credentials`](https://guides.rubyonrails.org/security.html#custom-credentials) to avoid hardcoding API keys. First, add your keys and then reference them in your initializer: +> +> Terminal +> +> PostHog AI +> +> ```bash +> rails credentials:edit +> ``` +> +> config/credentials.yml.enc +> +> PostHog AI +> +> ```yaml +> posthog: +> api_key: +> host: https://us.i.posthog.com +> personal_api_key: phx_xxxxxxxxx +> ``` +> +> config/initializers/posthog.rb +> +> PostHog AI +> +> ```ruby +> config.api_key = Rails.application.credentials.posthog[:api_key] +> config.host = Rails.application.credentials.posthog[:host] +> config.personal_api_key = Rails.application.credentials.posthog[:personal_api_key] +> ``` + +## Capturing events + +Track custom events anywhere in your Rails app: + +Ruby + +PostHog AI + +```ruby +PostHog.capture({ + distinct_id: current_user.id, + event: 'post_created', + properties: { title: @post.title } +}) +``` + +Identify a user and set their person properties: + +Ruby + +PostHog AI + +```ruby +PostHog.identify({ + distinct_id: current_user.id, + properties: { + email: current_user.email, + plan: current_user.plan + } +}) +``` + +The Rails integration delegates methods like `capture`, `identify`, `alias`, `group_identify`, `evaluate_flags`, `capture_exception`, `flush`, and `shutdown` to the initialized `PostHog::Client`. + +## Request context + +PostHog Rails automatically applies request-scoped context to events captured during web requests. Request metadata such as `$current_url`, `$request_method`, `$request_path`, `$user_agent`, and `$ip` is added to event properties. + +When `use_tracing_headers` is enabled, PostHog tracing headers (`X-PostHog-Distinct-Id` and `X-PostHog-Session-Id`) are also used as default `distinct_id` and `$session_id` values. Explicit `distinct_id` and properties passed to `PostHog.capture` always take precedence. + +Disable tracing header identity/session capture if you do not want client-supplied tracing headers used for server-side events. Request metadata is still captured: + +Ruby + +PostHog AI + +```ruby +PostHog::Rails.config.use_tracing_headers = false +``` + +## Error tracking + +For full details on setting up error tracking with Rails, see our [Rails error tracking installation guide](/docs/error-tracking/installation/ruby-on-rails.md). + +### Automatic exception tracking + +When `auto_capture_exceptions` is enabled, exceptions are automatically captured: + +Ruby + +PostHog AI + +```ruby +class PostsController < ApplicationController + def show + @post = Post.find(params[:id]) + # Any exception here is automatically captured + end +end +``` + +`report_rescued_exceptions` controls whether exceptions Rails rescues (for example, exceptions rendered by Rails error pages) are captured. Enable it along with `auto_capture_exceptions` for complete error visibility, or leave it disabled to capture only unhandled exceptions. + +### Manual exception capture + +You can also manually capture exceptions: + +Ruby + +PostHog AI + +```ruby +PostHog.capture_exception( + exception, + current_user.id, + { custom_property: 'value' } +) +``` + +If you evaluated feature flags for the request, pass the same snapshot to include matching flag properties on the exception event: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +PostHog.capture_exception( + exception, + current_user.id, + { custom_property: 'value' }, + flags: flags +) +``` + +### Background job exceptions + +When `auto_instrument_active_job` is enabled, ActiveJob exceptions are automatically captured with job context: + +Ruby + +PostHog AI + +```ruby +class EmailJob < ApplicationJob + def perform(user_id) + user = User.find(user_id) + UserMailer.welcome(user).deliver_now + # Exceptions are automatically captured + end +end +``` + +#### Associating jobs with users + +By default, PostHog extracts a `distinct_id` from job arguments by looking for a `user_id` key in hash arguments: + +Ruby + +PostHog AI + +```ruby +# PostHog will automatically use options[:user_id] as the distinct_id +ProcessOrderJob.perform_later(order.id, user_id: current_user.id) +``` + +For more control, use the `posthog_distinct_id` class method. The proc or block receives the same arguments as `perform`: + +Ruby + +PostHog AI + +```ruby +class SendWelcomeEmailJob < ApplicationJob + posthog_distinct_id ->(user, _options) { user.id } + def perform(user, options = {}) + UserMailer.welcome(user).deliver_now + end +end +``` + +You can also use a block: + +Ruby + +PostHog AI + +```ruby +class ProcessOrderJob < ApplicationJob + posthog_distinct_id do |_order, notify_user_id| + notify_user_id + end + def perform(order, notify_user_id) + # Process the order... + end +end +``` + +### Rails 7.0+ error reporter + +PostHog integrates with Rails' built-in error reporting: + +Ruby + +PostHog AI + +```ruby +# These errors are automatically sent to PostHog +Rails.error.handle do + # Code that might raise an error +end +Rails.error.record(exception, context: { user_id: current_user.id }) +``` + +PostHog automatically extracts the user's distinct ID from `user_id` or `distinct_id` in the context hash. Other context keys are included as properties on the exception event. + +### User context + +PostHog Rails automatically captures authenticated user information from your controllers for exceptions. Authenticated Rails user context takes precedence over client-supplied tracing headers for exception identity. + +If your user method has a different name, configure it: + +Ruby + +PostHog AI + +```ruby +PostHog::Rails.config.current_user_method = :logged_in_user +``` + +#### User ID extraction + +By default, PostHog Rails auto-detects the user's distinct ID by trying these methods in order: + +1. `posthog_distinct_id` – Define this on your User model for full control +2. `distinct_id` – Common analytics convention +3. `id` – Standard ActiveRecord primary key +4. `pk` – Primary key alias +5. `uuid` – For UUID-based primary keys + +It also checks hash-like users for `id`, `pk`, and `uuid` keys. + +You can configure a specific method: + +Ruby + +PostHog AI + +```ruby +PostHog::Rails.config.user_id_method = :email +``` + +Or define a method on your User model: + +Ruby + +PostHog AI + +```ruby +class User < ApplicationRecord + def posthog_distinct_id + "user_#{id}" # or external_id, or any unique identifier + end +end +``` + +### Excluded exceptions + +The following exceptions are not reported by default (common 4xx errors): + +- `AbstractController::ActionNotFound` +- `ActionController::BadRequest` +- `ActionController::InvalidAuthenticityToken` +- `ActionController::InvalidCrossOriginRequest` +- `ActionController::MethodNotAllowed` +- `ActionController::NotImplemented` +- `ActionController::ParameterMissing` +- `ActionController::RoutingError` +- `ActionController::UnknownFormat` +- `ActionController::UnknownHttpMethod` +- `ActionDispatch::Http::Parameters::ParseError` +- `ActiveRecord::RecordNotFound` +- `ActiveRecord::RecordNotUnique` + +Add more with: + +Ruby + +PostHog AI + +```ruby +PostHog::Rails.config.excluded_exceptions = ['MyException'] +``` + +## Feature flags + +Evaluate flags once for the current user, then read values from the returned snapshot: + +Ruby + +PostHog AI + +```ruby +class PostsController < ApplicationController + def show + flags = PostHog.evaluate_flags(current_user.id) + if flags.enabled?('new-post-design') + render 'posts/show_new' + else + render 'posts/show' + end + end +end +``` + +For multivariate flags and experiments, use `get_flag`: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +variant = flags.get_flag('checkout-experiment') +if variant == 'test' + # Do something differently +end +``` + +When capturing an event after branching on a flag, pass the same `flags` snapshot so the event includes the exact flag values used by your code: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +PostHog.capture({ + distinct_id: current_user.id, + event: 'checkout_started', + flags: flags.only_accessed +}) +``` + +For local evaluation, ensure you've set `personal_api_key`: + +Ruby + +PostHog AI + +```ruby +config.personal_api_key = Rails.application.credentials.posthog[:personal_api_key] +``` + +See our [Ruby SDK docs](/docs/libraries/ruby.md#local-evaluation) for details on local evaluation with Puma and Unicorn servers. + +> **Note:** `PostHog.is_feature_enabled`, `PostHog.get_feature_flag`, `PostHog.get_feature_flag_result`, `PostHog.get_feature_flag_payload`, and `PostHog.capture({ ..., send_feature_flags: true })` still work during the migration period, but they're deprecated. Prefer `PostHog.evaluate_flags` for new code. + +## Testing + +In your test environment, disable network calls with test mode: + +config/environments/test.rb + +PostHog AI + +```ruby +PostHog.init do |config| + config.api_key = '' + config.test_mode = true +end +``` + +Or in your specs: + +spec/rails\_helper.rb + +PostHog AI + +```ruby +RSpec.configure do |config| + config.before(:each) do + allow(PostHog).to receive(:capture) + end +end +``` + +## Configuration reference + +### Core PostHog options + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| api_key | String | required | Your PostHog project token. | +| host | String | https://us.i.posthog.com | Fully qualified PostHog API host. | +| personal_api_key | String | nil | Personal API key for local feature flag evaluation and remote config payloads. | +| max_queue_size | Integer | 10000 | Maximum number of events to keep in the async queue before dropping new events. | +| test_mode | Boolean | false | Keep events queued and do not send them. Useful for tests. | +| sync_mode | Boolean | false | Send events synchronously on the calling thread. | +| on_error | Proc | no-op | Callback called as on_error.call(status, error). | +| feature_flags_polling_interval | Integer | 30 | Seconds between local feature flag definition polls. | +| feature_flag_request_timeout_seconds | Integer | 3 | Timeout, in seconds, for feature flag requests. | +| before_send | Proc | nil | Callback that receives the event hash before it is queued or sent. Return a modified event hash, or nil to drop the event. | + +The `PostHog.init` block supports the options above. Less common core options like `batch_size`, `disable_singleton_warning`, `skip_ssl_verification`, and the experimental `flag_definition_cache_provider` can be passed as an options hash to `PostHog.init(...)`; see the [Ruby SDK docs](/docs/libraries/ruby.md#configuration) for details. + +### Rails-specific options + +Configure these via `PostHog::Rails.configure` or `PostHog::Rails.config`: + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| auto_capture_exceptions | Boolean | false | Automatically capture exceptions. | +| report_rescued_exceptions | Boolean | false | Report exceptions Rails rescues. | +| auto_instrument_active_job | Boolean | false | Capture ActiveJob exceptions with job context. | +| excluded_exceptions | Array | [] | Additional exception class names to ignore. | +| use_tracing_headers | Boolean | true | Use X-PostHog-Distinct-Id and X-PostHog-Session-Id as request-scoped defaults. | +| capture_user_context | Boolean | true | Include authenticated user info in exceptions. | +| current_user_method | Symbol | :current_user | Controller method used to fetch the current user. | +| user_id_method | Symbol | nil | Method used to extract the distinct ID from the user object. Auto-detects when nil. | + +## Troubleshooting + +### Exceptions not being captured + +1. Verify PostHog is initialized: + + Ruby + + PostHog AI + + ```ruby + Rails.console + > PostHog.initialized? + => true + ``` + +2. Check your excluded exceptions list. + +3. Verify middleware is installed: + + Ruby + + PostHog AI + + ```ruby + Rails.application.middleware + ``` + +### User context not working + +1. Verify `current_user_method` matches your controller method. +2. Check that the user object responds to `posthog_distinct_id`, `distinct_id`, `id`, `pk`, or `uuid`. +3. If using a custom identifier, set `PostHog::Rails.config.user_id_method = :your_method`. + +### Feature flags not working + +Ensure you've set `personal_api_key` in your configuration. + +## Next steps + +For any technical questions for how to integrate specific PostHog features into Rails (such as analytics, feature flags, A/B testing, etc.), have a look at our [Ruby SDK docs](/docs/libraries/ruby.md). + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/skills/instrument-integration/SKILL.md b/skills/instrument-integration/SKILL.md index 66b3a62..0cf1192 100644 --- a/skills/instrument-integration/SKILL.md +++ b/skills/instrument-integration/SKILL.md @@ -12,7 +12,7 @@ metadata: Use this skill to add the PostHog SDK to an application. Use it when setting up PostHog for the first time, or reviewing PRs that need PostHog initialization. Covers SDK installation, provider setup, and basic configuration. Supports any framework or language. -Supported frameworks: Next.js, React, React Router, Vue, Nuxt, TanStack Start, SvelteKit, Astro, Angular, Django, Flask, FastAPI, Laravel, Ruby on Rails, Android, Swift, React Native, Expo, Node.js, and vanilla JavaScript. +Supported frameworks: Next.js, React, React Router, Vue, Nuxt, TanStack Start, SvelteKit, Astro, Angular, Django, Flask, FastAPI, Laravel, PHP, Ruby on Rails, Android, Swift, React Native, Expo, Node.js, and vanilla JavaScript. ## Instructions @@ -79,6 +79,7 @@ STEP 7: Verify and clean up. - `references/EXAMPLE-fastapi.md` - fastapi example project code - `references/EXAMPLE-python.md` - python example project code - `references/EXAMPLE-laravel.md` - laravel example project code +- `references/EXAMPLE-php.md` - php example project code - `references/EXAMPLE-ruby-on-rails.md` - ruby-on-rails example project code - `references/EXAMPLE-ruby.md` - ruby example project code - `references/EXAMPLE-android.md` - android example project code @@ -107,6 +108,7 @@ STEP 7: Verify and clean up. - `references/python.md` - Python - docs - `references/posthog-python.md` - PostHog python SDK - `references/laravel.md` - Laravel - docs +- `references/php.md` - Php - docs - `references/ruby-on-rails.md` - Ruby on rails - docs - `references/ruby.md` - Ruby - docs - `references/android.md` - Android - docs diff --git a/skills/instrument-integration/references/EXAMPLE-php.md b/skills/instrument-integration/references/EXAMPLE-php.md new file mode 100644 index 0000000..d530bd4 --- /dev/null +++ b/skills/instrument-integration/references/EXAMPLE-php.md @@ -0,0 +1,527 @@ +# PostHog php Example Project + +Repository: https://github.com/PostHog/context-mill +Path: basics/php + +--- + +## README.md + +# PostHog PHP Example - CLI Todo App + +A simple command-line todo application built with plain PHP (no framework) demonstrating PostHog integration for CLIs, scripts, data pipelines, and non-web PHP applications. + +## Purpose + +This example serves as: +- **Verification** that the context-mill wizard works for plain PHP projects +- **Reference implementation** of PostHog best practices for non-framework PHP code +- **Working example** you can run and modify + +## Features Demonstrated + +- **SDK initialization** - Uses `PostHog::init(...)` once with environment-based configuration +- **Event tracking** - Captures user actions with `distinctId` and properties +- **User identification** - Associates properties with users via `PostHog::identify(...)` +- **Error tracking** - Enables automatic PHP error tracking and manually captures handled exceptions +- **Proper flushing** - Calls `PostHog::flush()` before CLI exit + +## Quick Start + +### 1. Install Dependencies + +```bash +composer install +``` + +### 2. Configure PostHog + +```bash +# Copy environment template +cp .env.example .env + +# Edit .env and add your PostHog API key +# POSTHOG_API_KEY=phc_your_api_key_here +# POSTHOG_HOST=https://us.i.posthog.com +``` + +### 3. Run the App + +```bash +# Add a todo +php todo.php add "Buy groceries" + +# List all todos +php todo.php list + +# Complete a todo +php todo.php complete 1 + +# Delete a todo +php todo.php delete 1 + +# Show statistics +php todo.php stats +``` + +## What Gets Tracked + +The app tracks these events in PostHog: + +| Event | Properties | Purpose | +|-------|-----------|---------| +| `todo_added` | `todo_id`, `todo_length`, `total_todos` | When user adds a new todo | +| `todos_viewed` | `total_todos`, `completed_todos` | When user lists todos | +| `todo_completed` | `todo_id`, `time_to_complete_hours` | When user completes a todo | +| `todo_deleted` | `todo_id`, `was_completed` | When user deletes a todo | +| `stats_viewed` | `total_todos`, `completed_todos`, `pending_todos` | When user views stats | +| `$exception` | exception details and command context | When handled errors occur | + +## Code Structure + +``` +basics/php/ +├── todo.php # Main CLI application +├── composer.json # PHP dependencies +├── .env.example # Environment variable template +├── .gitignore # Git ignore rules +└── README.md # This file +``` + +## Key Implementation Patterns + +### 1. Initialize Once + +```php +PostHog::init($apiKey, [ + 'host' => $host, + 'error_tracking' => [ + 'enabled' => true, + ], +]); +``` + +### 2. Event Tracking Pattern + +```php +PostHog::capture([ + 'distinctId' => 'user_123', + 'event' => 'event_name', + 'properties' => ['key' => 'value'], +]); +``` + +### 3. Identifying Users + +```php +PostHog::identify([ + 'distinctId' => 'user_123', + 'properties' => ['app_language' => 'php'], +]); +``` + +### 4. Exception Tracking + +```php +try { + riskyOperation(); +} catch (Throwable $e) { + PostHog::captureException($e, 'user_123', [ + 'command' => 'example_command', + ]); +} +``` + +### 5. Flush Before CLI Exit + +```php +PostHog::flush(); +``` + +## Running Without PostHog + +The app works fine without PostHog configured - it simply won't track analytics. You'll see a warning message but the app continues to function normally. + +## Next Steps + +- Modify `todo.php` to experiment with PostHog tracking +- Add new commands and track their usage +- Explore feature flags: `PostHog::isFeatureEnabled('flag-name', 'user_id')` +- Check your PostHog dashboard to see tracked events + +## Learn More + +- [PostHog PHP SDK Documentation](https://posthog.com/docs/libraries/php) +- [PostHog PHP Error Tracking](https://posthog.com/docs/error-tracking/installation/php) +- [PostHog Product Analytics PHP installation](https://posthog.com/docs/product-analytics/installation/php) + +--- + +## .env.example + +```example +# PostHog configuration +POSTHOG_API_KEY=phc_your_api_key_here +POSTHOG_HOST=https://us.i.posthog.com + +``` + +--- + +## todo.php + +```php + getenv('POSTHOG_HOST') ?: 'https://us.i.posthog.com', + 'error_tracking' => [ + 'enabled' => true, + 'context_provider' => static function (array $payload): array { + return [ + 'distinctId' => getUserId(), + 'properties' => [ + 'app' => 'php_todo_cli', + 'runtime' => PHP_VERSION, + '$exception_source' => $payload['source'] ?? null, + ], + ]; + }, + ], + ]); + + return true; +} + +function getUserId(): string +{ + $path = dataFilePath(); + if (file_exists($path)) { + $data = json_decode((string) file_get_contents($path), true); + if (is_array($data) && isset($data['user_id'])) { + return (string) $data['user_id']; + } + } + + return 'user_' . bin2hex(random_bytes(4)); +} + +function loadTodos(): array +{ + $path = dataFilePath(); + if (!file_exists($path)) { + return ['user_id' => getUserId(), 'todos' => []]; + } + + $data = json_decode((string) file_get_contents($path), true); + if (!is_array($data)) { + return ['user_id' => getUserId(), 'todos' => []]; + } + + $data['todos'] = $data['todos'] ?? []; + $data['user_id'] = $data['user_id'] ?? getUserId(); + return $data; +} + +function saveTodos(array $data): void +{ + file_put_contents(dataFilePath(), json_encode($data, JSON_PRETTY_PRINT) . PHP_EOL); +} + +function identifyUser(bool $posthogEnabled): void +{ + if (!$posthogEnabled) { + return; + } + + PostHog::identify([ + 'distinctId' => getUserId(), + 'properties' => [ + 'app_language' => 'php', + 'app_type' => 'cli', + ], + ]); +} + +function trackEvent(bool $posthogEnabled, string $eventName, array $properties = []): void +{ + if (!$posthogEnabled) { + return; + } + + PostHog::capture([ + 'distinctId' => getUserId(), + 'event' => $eventName, + 'properties' => $properties, + ]); +} + +function cmdAdd(string $text, bool $posthogEnabled): void +{ + $data = loadTodos(); + + $todo = [ + 'id' => count($data['todos']) + 1, + 'text' => $text, + 'completed' => false, + 'created_at' => date(DATE_ATOM), + ]; + + $data['todos'][] = $todo; + saveTodos($data); + + echo "Added todo #{$todo['id']}: {$todo['text']}\n"; + + trackEvent($posthogEnabled, 'todo_added', [ + 'todo_id' => $todo['id'], + 'todo_length' => strlen($todo['text']), + 'total_todos' => count($data['todos']), + ]); +} + +function cmdList(bool $posthogEnabled): void +{ + $data = loadTodos(); + + if (count($data['todos']) === 0) { + echo "No todos yet! Add one with: php todo.php add 'Your task'\n"; + return; + } + + echo "\nYour Todos (" . count($data['todos']) . " total):\n\n"; + + foreach ($data['todos'] as $todo) { + $status = $todo['completed'] ? 'X' : ' '; + echo " [{$status}] #{$todo['id']}: {$todo['text']}\n"; + } + + echo "\n"; + + trackEvent($posthogEnabled, 'todos_viewed', [ + 'total_todos' => count($data['todos']), + 'completed_todos' => count(array_filter($data['todos'], static fn (array $todo): bool => (bool) $todo['completed'])), + ]); +} + +function cmdComplete(int $id, bool $posthogEnabled): void +{ + $data = loadTodos(); + + foreach ($data['todos'] as &$todo) { + if ((int) $todo['id'] !== $id) { + continue; + } + + if ($todo['completed']) { + echo "Todo #{$id} is already completed\n"; + return; + } + + $todo['completed'] = true; + $todo['completed_at'] = date(DATE_ATOM); + saveTodos($data); + + echo "Completed todo #{$todo['id']}: {$todo['text']}\n"; + + $timeToComplete = (strtotime($todo['completed_at']) - strtotime($todo['created_at'])) / 3600; + trackEvent($posthogEnabled, 'todo_completed', [ + 'todo_id' => $todo['id'], + 'time_to_complete_hours' => $timeToComplete, + ]); + return; + } + + echo "ERROR: Todo #{$id} not found\n"; +} + +function cmdDelete(int $id, bool $posthogEnabled): void +{ + $data = loadTodos(); + + foreach ($data['todos'] as $index => $todo) { + if ((int) $todo['id'] !== $id) { + continue; + } + + unset($data['todos'][$index]); + $data['todos'] = array_values($data['todos']); + saveTodos($data); + + echo "Deleted todo #{$id}\n"; + + trackEvent($posthogEnabled, 'todo_deleted', [ + 'todo_id' => $todo['id'], + 'was_completed' => $todo['completed'], + ]); + return; + } + + echo "ERROR: Todo #{$id} not found\n"; +} + +function cmdStats(bool $posthogEnabled): void +{ + $data = loadTodos(); + + $total = count($data['todos']); + $completed = count(array_filter($data['todos'], static fn (array $todo): bool => (bool) $todo['completed'])); + $pending = $total - $completed; + $rate = $total > 0 ? number_format($completed / $total * 100, 1) : '0.0'; + + echo "\nStats:\n\n"; + echo " Total todos: {$total}\n"; + echo " Completed: {$completed}\n"; + echo " Pending: {$pending}\n"; + echo " Completion rate: {$rate}%\n\n"; + + trackEvent($posthogEnabled, 'stats_viewed', [ + 'total_todos' => $total, + 'completed_todos' => $completed, + 'pending_todos' => $pending, + ]); +} + +function printUsage(): void +{ + echo << Mark todo as completed + php todo.php delete Delete a todo + php todo.php stats Show statistics +USAGE; +} + +$posthogEnabled = false; + +try { + $posthogEnabled = initializePostHog(); + identifyUser($posthogEnabled); + + $command = $argv[1] ?? null; + if (!$command) { + printUsage(); + exit(0); + } + + switch ($command) { + case 'add': + $text = $argv[2] ?? null; + if (!$text) { + echo "ERROR: Please provide todo text\n"; + echo "Usage: php todo.php add \"Your task\"\n"; + exit(1); + } + cmdAdd($text, $posthogEnabled); + break; + + case 'list': + cmdList($posthogEnabled); + break; + + case 'complete': + $id = (int) ($argv[2] ?? 0); + if ($id <= 0) { + echo "ERROR: Please provide a valid todo ID\n"; + echo "Usage: php todo.php complete \n"; + exit(1); + } + cmdComplete($id, $posthogEnabled); + break; + + case 'delete': + $id = (int) ($argv[2] ?? 0); + if ($id <= 0) { + echo "ERROR: Please provide a valid todo ID\n"; + echo "Usage: php todo.php delete \n"; + exit(1); + } + cmdDelete($id, $posthogEnabled); + break; + + case 'stats': + cmdStats($posthogEnabled); + break; + + default: + echo "ERROR: Unknown command '{$command}'\n"; + printUsage(); + exit(1); + } +} catch (Throwable $e) { + echo "ERROR: {$e->getMessage()}\n"; + + if ($posthogEnabled) { + PostHog::captureException($e, getUserId(), [ + 'command' => $argv[1] ?? null, + 'app' => 'php_todo_cli', + ]); + } + + exit(1); +} finally { + if ($posthogEnabled) { + PostHog::flush(); + } +} + +``` + +--- + diff --git a/skills/instrument-integration/references/django.md b/skills/instrument-integration/references/django.md index dc8a1af..af5d8ba 100644 --- a/skills/instrument-integration/references/django.md +++ b/skills/instrument-integration/references/django.md @@ -1,6 +1,6 @@ # Django - Docs -PostHog makes it easy to get data about traffic and usage of your Django app. Integrating PostHog enables analytics, custom events capture, feature flags, and more. +PostHog makes it easy to get data about traffic and usage of your Django app. Integrating PostHog enables analytics, custom events capture, feature flags, error tracking, and more. This guide walks you through integrating PostHog into your Django app using the [Python SDK](/docs/libraries/python.md). @@ -18,7 +18,9 @@ Or, to integrate manually, continue with the rest of this guide. To start, run `pip install posthog` to install PostHog’s Python SDK. -Then, set the PostHog API key and host in your `AppConfig` in your `your_app/apps.py` so that's it's available everywhere: +> **Note:** Version `7.x` of the PostHog Python SDK requires Python 3.10 or higher. + +Then, configure PostHog in your app config so it's initialized when Django starts: your\_app/apps.py @@ -28,15 +30,13 @@ PostHog AI from django.apps import AppConfig import posthog class YourAppConfig(AppConfig): - name = "your_app_name" + name = 'your_app_name' def ready(self): posthog.api_key = '' posthog.host = 'https://us.i.posthog.com' ``` -You can find your project token and instance address in [your project settings](https://us.posthog.com/project/settings). - -Next, if you haven't done so already, make sure you add your `AppConfig` to your `settings.py` under `INSTALLED_APPS`: +Next, if you haven't done so already, add your `AppConfig` to `INSTALLED_APPS` in `settings.py`: settings.py @@ -44,12 +44,14 @@ PostHog AI ```python INSTALLED_APPS = [ - # other apps - 'your_app_name.apps.MyAppConfig', # Add your app config + # ... other apps + 'your_app_name.apps.YourAppConfig', ] ``` -Lastly, to access PostHog in any file, simply `import posthog` and call the method you'd like. For example, to capture an event: +You can find your project token and instance address in [your project settings](https://app.posthog.com/project/settings). + +To capture events from any file, import `posthog` and call the method you need. For example: Python @@ -57,12 +59,18 @@ PostHog AI ```python import posthog +from posthog import identify_context def some_request(request): with posthog.new_context(): - posthog.identify_context(request.user.id) + # Django includes request.user for anonymous visitors too. Only identify + # the context when the visitor is logged in. + if request.user.is_authenticated: + identify_context(str(request.user.pk)) posthog.capture('event_name') ``` +Events captured without a context or explicit `distinct_id` are sent as [anonymous events](/docs/data/anonymous-vs-identified-events.md) with an auto-generated `distinct_id`. See the [Python SDK docs](/docs/libraries/python.md#person-profiles-and-properties) for more details. + ## Identifying users > **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). @@ -71,11 +79,11 @@ def some_request(request): ## Django contexts middleware -The Python SDK provides a Django middleware that automatically wraps all requests with a [context](/docs/libraries/python.md#contexts). This middleware extracts session and user information from request headers and tags all events captured during the request with relevant metadata. +The Python SDK provides a Django middleware that automatically wraps all requests with a [context](/docs/libraries/python.md#contexts). This middleware extracts session and user information from each request and tags all events captured during that request with relevant metadata. ### Basic setup -Add the middleware to your Django settings: +Add the middleware to your Django settings. If your app uses Django authentication, place it after `django.contrib.auth.middleware.AuthenticationMiddleware` so the middleware can use the authenticated Django user as a distinct ID fallback and capture the user's email. Python @@ -89,14 +97,22 @@ MIDDLEWARE = [ ] ``` +The middleware uses the globally configured `posthog` client by default, so you don't need to create or pass it a separate client instance. + The middleware automatically extracts and uses: - **Session ID** from the `X-POSTHOG-SESSION-ID` header, if present -- **Distinct ID** from the `X-POSTHOG-DISTINCT-ID` header, if present +- **Distinct ID** from the `X-POSTHOG-DISTINCT-ID` header, if present, falling back to the authenticated Django user's `pk` (Django's primary-key alias, which works with custom user models) +- **User email** from the authenticated Django user's `email` as `email` - **Current URL** as `$current_url` - **Request method** as `$request_method` +- **Request path** as `$request_path` +- **Forwarded IP address** from `X-Forwarded-For` as `$ip` +- **User agent** from `User-Agent` as `$user_agent` + +The session and distinct ID headers are sanitized before use. Empty values are ignored, control characters are removed, values are trimmed, and values are capped at 1000 characters. -All events captured during the request (including exceptions) will include these properties and be associated with the extracted session and distinct ID. +All events captured during the request (including exceptions) include these properties and are associated with the extracted session and distinct ID. If you are using PostHog on your frontend, the JavaScript Web SDK will add the session and distinct ID headers automatically if you enable tracing headers. @@ -112,7 +128,9 @@ posthog.init('', { ### Exception capture -By default, the middleware captures exceptions and sends them to PostHog's error tracking. Disable this by setting: +By default, the middleware captures exceptions and sends them to PostHog's error tracking using the globally configured `posthog` client. This includes Django view exceptions that Django converts into error responses. + +Disable this by setting: Python @@ -137,7 +155,8 @@ def add_user_tags(request): # type: (HttpRequest) -> Dict[str, Any] tags = {} if hasattr(request, 'user') and request.user.is_authenticated: - tags['user_id'] = request.user.id + # Use pk instead of id so this works with custom User primary keys. + tags['user_id'] = str(request.user.pk) tags['email'] = request.user.email return tags POSTHOG_MW_EXTRA_TAGS = add_user_tags @@ -196,7 +215,8 @@ def add_request_context(request): tags = {} if hasattr(request, 'user') and request.user.is_authenticated: tags['user_type'] = 'authenticated' - tags['user_id'] = str(request.user.id) + # Use pk instead of id so this works with custom User primary keys. + tags['user_id'] = str(request.user.pk) else: tags['user_type'] = 'anonymous' # Add request info @@ -217,7 +237,9 @@ POSTHOG_MW_TAG_MAP = clean_tags POSTHOG_MW_CAPTURE_EXCEPTIONS = True ``` -All events captured within the request context automatically include the configured tags and are associated with the session and user identified from the request headers. +All events captured within the request context automatically include the configured tags and are associated with the session and user identified from the request headers or Django authentication. + +The middleware supports both sync (WSGI) and async (ASGI) Django applications. In async mode, it uses Django's `request.auser()` API when available to avoid synchronous user access. ## Next steps diff --git a/skills/instrument-integration/references/flask.md b/skills/instrument-integration/references/flask.md index f631b4b..e8ec7bf 100644 --- a/skills/instrument-integration/references/flask.md +++ b/skills/instrument-integration/references/flask.md @@ -1,6 +1,6 @@ # Flask - Docs -PostHog makes it easy to get data about traffic and usage of your Flask app. Integrating PostHog enables analytics, custom events capture, feature flags, and more. +PostHog makes it easy to get data about traffic and usage of your Flask app. Integrating PostHog enables analytics, custom events capture, feature flags, error tracking, and more. This guide walks you through integrating PostHog into your Flask app using the [Python SDK](/docs/libraries/python.md). @@ -8,6 +8,8 @@ This guide walks you through integrating PostHog into your Flask app using the [ To start, run `pip install posthog` to install PostHog’s Python SDK. +> **Note:** Version `7.x` of the PostHog Python SDK requires Python 3.10 or higher. + Then, initialize PostHog where you'd like to use it. For example, here's how to capture an event in a simple route: app.py @@ -15,23 +17,23 @@ app.py PostHog AI ```python -package main -from flask import Flask, render_template, request, redirect, session, url_for +from flask import Flask from posthog import Posthog +app = Flask(__name__) posthog = Posthog( - '', - host='https://us.i.posthog.com' + '', + host='https://us.i.posthog.com', ) @app.route('/api/dashboard', methods=['POST']) def api_dashboard(): posthog.capture( - 'dashboard_api_called' + 'dashboard_api_called', distinct_id='distinct_id_of_your_user', ) return '', 204 ``` -You can find your project token and instance address in [your project settings](https://us.posthog.com/project/settings). +You can find your project token and instance address in [your project settings](https://app.posthog.com/project/settings). ## Identifying users @@ -39,6 +41,35 @@ You can find your project token and instance address in [your project settings]( > > See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. +## Request contexts + +Use [contexts](/docs/libraries/python.md#contexts) to share identity, session IDs, and tags across multiple captures during a request: + +Python + +PostHog AI + +```python +from flask import request, session +from posthog import identify_context, set_context_session, tag +@app.route('/api/dashboard', methods=['POST']) +def api_dashboard(): + with posthog.new_context(): + distinct_id = request.headers.get('X-POSTHOG-DISTINCT-ID') or session.get('user_id') + if distinct_id: + identify_context(str(distinct_id)) + session_id = request.headers.get('X-POSTHOG-SESSION-ID') + if session_id: + set_context_session(session_id) + tag('$current_url', request.url) + tag('$request_method', request.method) + tag('$request_path', request.path) + posthog.capture('dashboard_api_called') + return '', 204 +``` + +Events captured without a context or explicit `distinct_id` are sent as [anonymous events](/docs/data/anonymous-vs-identified-events.md) with an auto-generated `distinct_id`. See the [Python SDK docs](/docs/libraries/python.md#person-profiles-and-properties) for more details. + ## Error tracking Flask has built-in error handlers. This means PostHog’s default exception autocapture won’t work and we need to manually capture errors instead using `capture_exception()`: @@ -50,6 +81,7 @@ PostHog AI ```python from flask import Flask, jsonify from posthog import Posthog +app = Flask(__name__) posthog = Posthog('', host='https://us.i.posthog.com') @app.errorhandler(Exception) def handle_exception(e): diff --git a/skills/instrument-integration/references/js.md b/skills/instrument-integration/references/js.md index 50a7ad3..c674787 100644 --- a/skills/instrument-integration/references/js.md +++ b/skills/instrument-integration/references/js.md @@ -196,7 +196,6 @@ posthog.init('', { | ErrorTrackingExtensions | [Error Tracking](/docs/error-tracking.md) | | SurveysExtensions | [Surveys](/docs/surveys.md) | | ExperimentsExtensions | [Experiments](/docs/experiments.md) | -| ProductToursExtensions | [Product Tours](/docs/product-tours.md) | | SiteAppsExtensions | [Site apps](/docs/site-apps.md) | | TracingExtensions | Distributed tracing header injection | | ToolbarExtensions | [Toolbar](/docs/toolbar.md) | diff --git a/skills/instrument-integration/references/laravel.md b/skills/instrument-integration/references/laravel.md index 5038330..022f900 100644 --- a/skills/instrument-integration/references/laravel.md +++ b/skills/instrument-integration/references/laravel.md @@ -1,14 +1,34 @@ # Laravel - Docs -PostHog makes it easy to get data about traffic and usage of your Laravel app. Integrating PostHog enables analytics, custom events capture, feature flags, and more. - -This guide walks you through integrating PostHog into your Laravel app using the [PHP SDK](/docs/libraries/php.md). +PostHog integrates with Laravel through the [PostHog PHP SDK](/docs/libraries/php.md). This page covers Laravel-specific setup. For SDK features such as event capture, identifying users, feature flags, group analytics, and configuration options, see the [PHP SDK docs](/docs/libraries/php.md). ## Installation -First, ensure [Composer](https://getcomposer.org/) is installed. Then run `composer require posthog/posthog-php` to install PostHog’s PHP SDK. +Install the PHP SDK as described in the [PHP installation guide](/docs/libraries/php.md#installation), then add your project token and host to `.env`: + +.env + +PostHog AI + +```bash +POSTHOG_API_KEY= +POSTHOG_HOST=https://us.i.posthog.com +``` + +Add PostHog to Laravel's services config: -Next, initialize PostHog in the `boot` method of `app/Providers/AppServiceProvider.php`: +config/services.php + +PostHog AI + +```php +'posthog' => [ + 'api_key' => env('POSTHOG_API_KEY'), + 'host' => env('POSTHOG_HOST', 'https://us.i.posthog.com'), +], +``` + +Initialize PostHog in the `boot` method of `app/Providers/AppServiceProvider.php`: app/Providers/AppServiceProvider.php @@ -23,48 +43,103 @@ class AppServiceProvider extends ServiceProvider { public function boot(): void { + if (! config('services.posthog.api_key')) { + return; + } PostHog::init( - '', + config('services.posthog.api_key'), [ - 'host' => 'https://us.i.posthog.com' + 'host' => config('services.posthog.host'), ] ); } } ``` -You can find your project token and instance address in [your project settings](https://us.posthog.com/project/settings). +## Request context middleware -## Usage +Client SDKs such as [PostHog JS](/docs/libraries/js.md) can send tracing headers to your Laravel backend. The PHP SDK can read `X-PostHog-Distinct-Id` and `X-PostHog-Session-Id` headers and apply them to events captured during the request. Add middleware like this: -To access your PostHog client anywhere in your app, import `use PostHog\PostHog;` and call `PostHog::method_name`. For example, below is how to capture an event in a simple route: - -routes/web.php +app/Http/Middleware/PostHogRequestContext.php PostHog AI ```php 'distinct_id_of_your_user', - 'event' => 'route_called' - ]); - return view('welcome'); -}); +use Symfony\Component\HttpFoundation\Response; +final class PostHogRequestContext +{ + public function handle(Request $request, Closure $next): Response + { + if (! config('services.posthog.api_key')) { + return $next($request); + } + $context = PostHog::contextFromHeaders($request->headers->all()); + $context['properties'] = array_merge( + $context['properties'] ?? [], + array_filter([ + '$current_url' => $request->fullUrl(), + '$request_method' => $request->method(), + '$request_path' => $request->getPathInfo(), + '$user_agent' => $request->userAgent(), + '$ip' => $request->ip(), + ], static fn ($value): bool => $value !== null && $value !== '') + ); + return PostHog::withContext( + $context, + static fn (): Response => $next($request), + ['fresh' => true] + ); + } +} ``` -## Next steps +Register this middleware using your Laravel version's normal middleware registration. + +## Error tracking in Laravel + +The PHP SDK supports [error tracking](/docs/libraries/php.md#error-tracking), but Laravel handles most request exceptions before they become uncaught PHP exceptions. Capture Laravel-reported exceptions explicitly. + +In Laravel 11 and later, add a report callback in `bootstrap/app.php`: + +bootstrap/app.php + +PostHog AI + +```php +use Illuminate\Foundation\Configuration\Exceptions; +use PostHog\PostHog; +use Throwable; +->withExceptions(function (Exceptions $exceptions): void { + $exceptions->report(function (Throwable $e): void { + if (! config('services.posthog.api_key')) { + return; + } + PostHog::captureException( + $e, + auth()->id() !== null ? (string) auth()->id() : null, + [ + '$current_url' => request()->fullUrl(), + '$request_method' => request()->method(), + ] + ); + }); +}) +``` + +For older Laravel versions, call `PostHog::captureException()` from your exception handler's `report` method. -For any technical questions for how to integrate specific PostHog features into Laravel (such as analytics, feature flags, A/B testing, etc.), have a look at our [PHP SDK docs](/docs/libraries/php.md). +## Long-running processes -Alternatively, the following tutorials can help you get started: +In normal PHP request lifecycles, queued events flush when the client is destroyed. In long-running Laravel processes such as queue workers, Horizon, or Octane, call `PostHog::flush()` after capturing important events or at the end of a job/request. + +## Next steps -- [How to set up analytics in Laravel](/tutorials/laravel-analytics.md) -- [How to set up feature flags in Laravel](/tutorials/laravel-feature-flags.md) -- [How to set up A/B tests in Laravel](/tutorials/laravel-ab-tests.md) +See the [PHP SDK docs](/docs/libraries/php.md) for usage examples and the full API reference. ### Community questions diff --git a/skills/instrument-integration/references/node.md b/skills/instrument-integration/references/node.md index b673d7c..676fa79 100644 --- a/skills/instrument-integration/references/node.md +++ b/skills/instrument-integration/references/node.md @@ -812,9 +812,9 @@ export const handler() { This is also useful for shutting down a standard Node.js app. -## LLM analytics +## AI Observability -You can capture LLM usage and performance data by combining the `posthog-node` and `@posthog/ai` libraries. These work with LLM providers like OpenAI and Vercel's AI SDKs. Learn more in our [LLM analytics docs](/docs/llm-analytics.md). +You can capture LLM usage and performance data by combining the `posthog-node` and `@posthog/ai` libraries. These work with LLM providers like OpenAI and Vercel's AI SDKs. Learn more in our [AI Observability docs](/docs/ai-observability.md). ## Error tracking diff --git a/skills/instrument-integration/references/php.md b/skills/instrument-integration/references/php.md new file mode 100644 index 0000000..1c2e1fe --- /dev/null +++ b/skills/instrument-integration/references/php.md @@ -0,0 +1,629 @@ +# PHP - Docs + +This is an optional library you can install if you're working with PHP. It uses an internal queue to batch requests, flushes at the end of the request, and optionally does so in an async manner. + +## Installation + +Install the package with Composer: + +Terminal + +PostHog AI + +```bash +composer require posthog/posthog-php +``` + +In your app, set your project token before making any calls. + +PHP + +PostHog AI + +```php +PostHog\PostHog::init("", + ['host' => 'https://us.i.posthog.com'] +); +``` + +> **Note:** As a rule of thumb, we do not recommend having API keys or tokens in plaintext. Setting them as environment variables is best. The PHP SDK reads `POSTHOG_API_KEY` and `POSTHOG_HOST` when you omit the project token or host. + +You can find your project token and instance address in the [project settings](https://app.posthog.com/project/settings) page in PostHog. + +## Identifying users + +> **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). +> +> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. + +## Capturing events + +You can send custom events using `capture`: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id_of_the_user', + 'event' => 'user_signed_up' +]); +``` + +> **Tip:** We recommend using a `[object] [verb]` format for your event names, where `[object]` is the entity that the behavior relates to, and `[verb]` is the behavior itself. For example, `project created`, `user signed up`, or `invite sent`. + +### Setting event properties + +Optionally, you can include additional information with the event by including a [properties](/docs/data/events.md#event-properties) object: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id_of_the_user', + 'event' => 'user_signed_up', + 'properties' => [ + 'login_type' => 'email', + 'is_free_trial' => 'true' + ] +]); +``` + +### Sending page views + +If you're aiming for a backend-only implementation of PostHog and won't be capturing events from your frontend, you can send `pageviews` from your backend like so: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id_of_the_user', + 'event' => '$pageview', + 'properties' => [ + '$current_url' => 'https://example.com' + ] +]); +``` + +## Person profiles and properties + +The PHP SDK captures identified events by default. These create [person profiles](/docs/data/persons.md). To set [person properties](/docs/data/user-properties.md), call `identify` with the user's distinct ID and properties: + +PHP + +PostHog AI + +```php +PostHog::identify([ + 'distinctId' => 'distinct_id', + 'properties' => [ + 'email' => 'max@example.com', + 'name' => 'Max Hedgehog', + ], +]); +``` + +You can also include person properties when capturing an event: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id', + 'event' => 'event_name', + 'properties' => [ + '$set' => [ + 'name' => 'Max Hedgehog' + ], + '$set_once' => [ + 'initial_url' => '/blog' + ] + ] +]); +``` + +For more details on the difference between `$set` and `$set_once`, see our [person properties docs](/docs/data/user-properties.md#what-is-the-difference-between-set-and-set_once). + +To capture [anonymous events](/docs/data/anonymous-vs-identified-events.md) without person profiles, set the event's `$process_person_profile` property to `false`: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id', + 'event' => 'event_name', + 'properties' => [ + '$process_person_profile' => false + ] +]); +``` + +## Alias + +Sometimes, you want to assign multiple distinct IDs to a single user. This is helpful when your primary distinct ID is inaccessible. For example, if a distinct ID used on the frontend is not available in your backend. + +In this case, you can use `alias` to assign another distinct ID to the same user. + +PHP + +PostHog AI + +```php +PostHog::alias([ + 'distinctId' => 'distinct_id', + 'alias' => 'alias_id' +]); +``` + +We strongly recommend reading our docs on [alias](/docs/data/identify.md#alias-assigning-multiple-distinct-ids-to-the-same-user) to best understand how to correctly use this method. + +## Feature flags + +PostHog's [feature flags](/docs/feature-flags.md) enable you to safely deploy and roll back new features as well as target specific users and groups with them. + +There are two steps to implement feature flags in PHP: + +### Step 1: Evaluate flags once + +Call `PostHog::evaluateFlags()` once for the user, then read values from the returned snapshot. + +#### Boolean feature flags + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags('distinct_id_of_your_user'); +if ($flags->isEnabled('flag-key')) { + // Do something differently for this user + // Optional: fetch the payload + $matchedFlagPayload = $flags->getFlagPayload('flag-key'); +} +``` + +#### Multivariate feature flags + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags('distinct_id_of_your_user'); +$enabledVariant = $flags->getFlag('flag-key'); +if ($enabledVariant === 'variant-key') { // replace 'variant-key' with the key of your variant + // Do something differently for this user + // Optional: fetch the payload + $matchedFlagPayload = $flags->getFlagPayload('flag-key'); +} +``` + +`$flags->getFlag()` returns the variant string for multivariate flags, `true` for enabled boolean flags, `false` for disabled flags, and `null` when the flag wasn't returned by the evaluation. + +You can also call `$flags->getKeys()` to list the evaluated flag keys, or `$flags->getEventProperties()` to get the `$feature/` and `$active_feature_flags` properties that would be attached to a captured event. + +> **Note:** `PostHog::isFeatureEnabled()`, `PostHog::getFeatureFlag()`, `PostHog::getFeatureFlagPayload()`, and `capture(['send_feature_flags' => true])` still work during the migration period, but they're deprecated. Prefer `evaluateFlags()` for new code. + +### Step 2: Include feature flag information when capturing events + +If you want use your feature flag to breakdown or filter events in your [insights](/docs/product-analytics/insights.md), you'll need to include feature flag information in those events. This ensures that the feature flag value is attributed correctly to the event. + +> **Note:** This step is only required for events captured using our server-side SDKs or [API](/docs/api.md). + +There are two methods you can use to include feature flag information in your events: + +#### Method 1: Pass the evaluated flags snapshot to `capture()` + +Pass the same `flags` object that you used for branching. This attaches the exact flag values from that evaluation and doesn't make another `/flags` request. + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags('distinct_id_of_your_user'); +if ($flags->isEnabled('flag-key')) { + // Do something differently for this user +} +PostHog::capture([ + 'distinctId' => 'distinct_id_of_your_user', + 'event' => 'event_name', + 'flags' => $flags, +]); +``` + +By default, this attaches every flag in the snapshot using `$feature/` properties and `$active_feature_flags`. + +To reduce event property bloat, pass a filtered snapshot: + +PHP + +PostHog AI + +```php +// Attach only flags accessed with isEnabled() or getFlag() before this call +PostHog::capture([ + 'distinctId' => 'distinct_id_of_your_user', + 'event' => 'event_name', + 'flags' => $flags->onlyAccessed(), +]); +// Attach only specific flags +PostHog::capture([ + 'distinctId' => 'distinct_id_of_your_user', + 'event' => 'event_name', + 'flags' => $flags->only(['checkout-flow', 'new-dashboard']), +]); +``` + +`onlyAccessed()` is order-dependent. If you call it before accessing any flags with `isEnabled()` or `getFlag()`, no feature flag properties are attached. + +#### Method 2: Include the `$feature/feature_flag_name` property manually + +In the event properties, include `$feature/feature_flag_name: variant_key`: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id_of_your_user', + 'event' => 'event_name', + 'properties' => [ + // Replace feature-flag-key with your flag key and 'variant-key' with the key of your variant + '$feature/feature-flag-key' => 'variant-key', + ], +]); +``` + +### Evaluating only specific flags + +By default, `evaluateFlags()` evaluates every flag for the user. If you only need a few flags, pass `flagKeys` to request only those flags: + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags( + distinctId: 'distinct_id_of_your_user', + flagKeys: ['checkout-flow', 'new-dashboard'], +); +``` + +### Optional evaluation parameters + +`evaluateFlags()` also accepts optional parameters for local evaluation and GeoIP behavior: + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags( + distinctId: 'distinct_id_of_your_user', + groups: ['company' => 'company_id_in_your_db'], + personProperties: ['plan' => 'pro'], + groupProperties: ['company' => ['employees' => 11]], + onlyEvaluateLocally: false, // Defaults to false. Set to true to avoid a remote fallback. + disableGeoip: false, // Defaults to false. Set to true to disable GeoIP enrichment during remote evaluation. + flagKeys: ['checkout-flow', 'new-dashboard'], +); +``` + +### Sending `$feature_flag_called` events + +Capturing `$feature_flag_called` events enables PostHog to know when a flag was accessed by a user and provide [analytics and insights](/docs/product-analytics/insights.md) on the flag. With `evaluateFlags()`, the SDK sends this event when you call `$flags->isEnabled()` or `$flags->getFlag()` for a flag. + +The SDK deduplicates these events per `(flag key, distinct_id)` in a local cache. If you reinitialize the PostHog client, the cache resets and `$feature_flag_called` events may be sent again. PostHog handles duplicates, so duplicate `$feature_flag_called` events don't affect your analytics. + +`$flags->getFlagPayload()` doesn't send `$feature_flag_called` events and doesn't count as an access for `onlyAccessed()`. + +### Advanced: Overriding server properties + +Sometimes, you may want to evaluate feature flags using [person properties](/docs/product-analytics/person-properties.md), [groups](/docs/product-analytics/group-analytics.md), or group properties that haven't been ingested yet, or were set incorrectly earlier. + +You can provide properties to evaluate the flag with by using the `person properties`, `groups`, and `group properties` arguments. PostHog will then use these values to evaluate the flag, instead of any properties currently stored on your PostHog server. + +For example: + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags( + distinctId: 'distinct_id_of_the_user', + groups: [ + 'your_group_type' => 'your_group_id', + 'another_group_type' => 'your_group_id', + ], + personProperties: ['property_name' => 'value'], + groupProperties: [ + 'your_group_type' => ['group_property_name' => 'value'], + 'another_group_type' => ['group_property_name' => 'value'], + ], +); +if ($flags->isEnabled('flag-key')) { + // Do something differently for this user +} +``` + +### Overriding GeoIP properties + +By default, a user's GeoIP properties are set using the IP address they use to capture events on the frontend. You may want to override the these properties when evaluating feature flags. A common reason to do this is when you're not using PostHog on your frontend, so the user has no GeoIP properties. + +You can override GeoIP properties by including them in the `person_properties` parameter when evaluating feature flags. This is useful when you're evaluating flags on your backend and want to use the client's location instead of your server's location. + +The following GeoIP properties can be overridden: + +- `$geoip_country_code` +- `$geoip_country_name` +- `$geoip_city_name` +- `$geoip_city_confidence` +- `$geoip_continent_code` +- `$geoip_continent_name` +- `$geoip_latitude` +- `$geoip_longitude` +- `$geoip_postal_code` +- `$geoip_subdivision_1_code` +- `$geoip_subdivision_1_name` +- `$geoip_subdivision_2_code` +- `$geoip_subdivision_2_name` +- `$geoip_subdivision_3_code` +- `$geoip_subdivision_3_name` +- `$geoip_time_zone` + +Simply include any of these properties in the `person_properties` parameter alongside your other person properties when calling feature flags. + +### Request timeout + +You can configure the `feature_flag_request_timeout_ms` parameter when initializing your PostHog client to set a flag request timeout. This helps prevent your code from being blocked if PostHog's servers are too slow to respond. By default, this is set to 3 seconds. + +PHP + +PostHog AI + +```php +PostHog::init("", + [ + 'host' => 'https://us.i.posthog.com', + 'feature_flag_request_timeout_ms' => 3000, // Time in milliseconds. Defaults to 3000 (3 seconds). + ] +); +``` + +### Local Evaluation + +Evaluating feature flags requires making a request to PostHog for each flag. However, you can improve performance by evaluating flags locally. Instead of making a request for each flag, PostHog will periodically request and store feature flag definitions locally, enabling you to evaluate flags without making additional requests. + +It is best practice to use local evaluation flags when possible, since this enables you to resolve flags faster and with fewer API calls. + +To load feature flag definitions for local evaluation, initialize the SDK with your feature flags secure API key as `personalAPIKey`: + +PHP + +PostHog AI + +```php +PostHog::init( + '', + ['host' => 'https://us.i.posthog.com'], + personalAPIKey: 'your feature flags secure API key' +); +``` + +For details on how to implement local evaluation, see our [local evaluation guide](/docs/feature-flags/local-evaluation.md). + +### Experiments (A/B tests) + +Since [experiments](/docs/experiments/start-here.md) use feature flags, the code for running an experiment is very similar to the feature flags code: + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags('user_distinct_id'); +$variant = $flags->getFlag('experiment-feature-flag-key'); +if ($variant === 'variant-name') { + // Do something differently for this user +} +``` + +It's also possible to [run experiments without using feature flags](/docs/experiments/running-experiments-without-feature-flags.md). + +### Group analytics + +Group analytics allows you to associate an event with a group (e.g. teams, organizations, etc.). This feature requires version `2.1.0` or above of the PHP SDK. Read the [group analytics guide](/docs/product-analytics/group-analytics.md) for more information. + +> **Note:** This is a paid feature and is not available on the open-source or free cloud plan. Learn more on the [pricing page](/pricing.md). + +To create a group or update its properties, use `groupIdentify`: + +PHP + +PostHog AI + +```php +PostHog::groupIdentify([ + 'groupType' => 'company', + 'groupKey' => 'company_id_in_your_db', + 'properties' => [ + 'name' => 'Awesome Inc.', + 'employees' => 11, + ], + // Optional distinct ID to associate this event with an existing person. + // Requires posthog-php 4.4.0 or later. + 'distinctId' => 'user_distinct_id' +]); +``` + +`name` is a special property which is used in the PostHog UI for the name of the group. If you don't specify a `name` property, the group ID is used instead. + +If the optional `distinctId` parameter is not provided in the group identify call, it defaults to `${groupType}_${groupKey}` (e.g., `$company_company_id_in_your_db` in the example above). This default behavior results in each group appearing as a separate person in PostHog. To avoid this, use a consistent `distinctId`, such as `group_identifier`, or a real user distinct ID. + +Once a group is created, you can use the `capture` method and pass in the `groups` parameter to capture an event with group analytics. + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'user_distinct_id', + 'event' => 'some_event', + 'groups' => ['company' => 'company_id_in_your_db'] +]); +``` + +## Request context + +Use request context to apply a distinct ID, session ID, and common properties to all captures inside a callback. This is useful when connecting frontend activity to backend events, session replay, and error tracking. + +PHP + +PostHog AI + +```php +PostHog::withContext([ + 'distinctId' => 'user_distinct_id', + 'sessionId' => 'session_id_from_frontend', + 'properties' => [ + '$current_url' => 'https://example.com/account', + ], +], function () { + PostHog::capture([ + 'event' => 'backend_event', + ]); +}); +``` + +You can extract PostHog context from frontend tracing headers with `contextFromHeaders()`: + +PHP + +PostHog AI + +```php +$context = PostHog::contextFromHeaders($_SERVER); +PostHog::withContext($context, function () { + PostHog::capture([ + 'event' => 'backend_event', + ]); +}); +``` + +Call `PostHog::getContext()` to read the currently active context. Pass `['fresh' => true]` as the third argument to `withContext()` if you don't want to inherit any existing context. + +## Error tracking + +The PHP SDK supports both manual exception capture and opt-in automatic error tracking. + +To automatically capture uncaught exceptions, PHP errors, and fatal shutdown errors, enable `error_tracking` when initializing the client: + +PHP + +PostHog AI + +```php +PostHog::init( + '', + [ + 'host' => 'https://us.i.posthog.com', + 'error_tracking' => [ + 'enabled' => true, + ], + ], +); +``` + +You can also call `PostHog::captureException()` directly for manual capture. When source files are readable at runtime, PostHog includes surrounding source lines for in-app stack frames automatically. + +For the full setup guide, including `context_provider`, excluded exceptions, and verification steps, see the [PHP error tracking installation docs](/docs/error-tracking/installation/php.md). + +## Config options + +When calling `PostHog::init`, there are various configuration options you can set apart from the host. Pass them into your client initialisation like so: + +PHP + +PostHog AI + +```php +PostHog::init( + '', + [ + 'host' => 'https://us.i.posthog.com', + 'debug' => true, + 'ssl' => false, + // all options go here + ], +); +``` + +All possible options below: + +| Attribute | Description | +| --- | --- | +| hostType: StringDefault: us.i.posthog.com | URL of your PostHog instance. | +| sslType: BooleanDefault: true | Whether to use SSL for API requests or not. If host includes http:// or https://, the SDK infers this option unless you set it explicitly. | +| timeoutType: IntegerDefault: 10000 | Request timeout in milliseconds. | +| verify_batch_events_requestType: BooleanDefault: true | Whether to verify successful delivery of batch events (true, synchronous) or fire and forget (false, asynchronous) with the lib_curl consumer. | +| feature_flag_request_timeout_msType: IntegerDefault: 3000 | Request timeout for feature flags in milliseconds. | +| maximum_backoff_durationType: IntegerDefault: 10000 | Request retry backoff. Retries stop after this duration is hit. | +| consumerType: StringDefault: lib_curl | One of socket, file, lib_curl, and fork_curl. Determines what transport option to use for analytics capture. | +| debugType: BooleanDefault: false | Output debug logs or not. | +| max_queue_sizeType: IntegerDefault: 1000 | Maximum number of events to queue before rejecting new events. Applies to queued consumers. | +| batch_sizeType: IntegerDefault: 100 | Number of queued events to send in each batch. Applies to queued consumers. | +| compress_requestType: Boolean/StringDefault: false | Whether to gzip batch request payloads. | +| error_handlerType: CallableDefault: null | Callback invoked for SDK transport errors. | +| filenameType: StringDefault: sys_get_temp_dir() . '/posthog.log' | File path used when consumer is set to file. | +| error_trackingType: ArrayDefault: [] | Enables automatic error tracking. See the options below or the [PHP error tracking setup guide](/docs/error-tracking/installation/php.md). | + +### Error tracking options + +| Attribute | Description | +| --- | --- | +| enabledType: BooleanDefault: false | Enables automatic error tracking handlers. Manual captureException works regardless. | +| capture_errorsType: BooleanDefault: true | When enabled, captures PHP errors and fatal shutdown errors in addition to uncaught exceptions. | +| excluded_exceptionsType: Array of class stringsDefault: [] | Throwable classes to skip during automatic capture. | +| max_framesType: IntegerDefault: 20 | Maximum number of stack frames included in $exception_list. | +| context_providerType: Callable or nullDefault: null | Callback that returns distinctId and extra event properties for automatic captures. | + +## Debug mode + +PHP + +PostHog AI + +```php +PostHog::init( + '', + [ + 'host' => 'https://us.i.posthog.com', + 'debug' => true, + ], +); +``` + +## Thank you + +This library is largely based on the `analytics-php` package. + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/skills/instrument-integration/references/posthog-js.md b/skills/instrument-integration/references/posthog-js.md index 716a8e2..e0942c1 100644 --- a/skills/instrument-integration/references/posthog-js.md +++ b/skills/instrument-integration/references/posthog-js.md @@ -1,6 +1,6 @@ # PostHog JavaScript Web SDK -**SDK Version:** 1.373.4 +**SDK Version:** 1.376.2 Posthog-js allows you to automatically capture usage and send events to PostHog. diff --git a/skills/instrument-integration/references/posthog-node.md b/skills/instrument-integration/references/posthog-node.md index bfe628d..eb9f922 100644 --- a/skills/instrument-integration/references/posthog-node.md +++ b/skills/instrument-integration/references/posthog-node.md @@ -1,6 +1,6 @@ # PostHog Node.js SDK -**SDK Version:** 5.34.1 +**SDK Version:** 5.35.4 PostHog Node.js SDK allows you to capture events and send them to PostHog from your Node.js applications. diff --git a/skills/instrument-integration/references/posthog-python.md b/skills/instrument-integration/references/posthog-python.md index 50c8ae7..8c27cd2 100644 --- a/skills/instrument-integration/references/posthog-python.md +++ b/skills/instrument-integration/references/posthog-python.md @@ -1,6 +1,6 @@ # PostHog Python SDK -**SDK Version:** 7.14.2 +**SDK Version:** 7.15.4 Integrate PostHog into any python application. @@ -29,38 +29,38 @@ Initialize a new PostHog client instance. ### Parameters -- **`project_api_key?`** (`str`) - The project API key. -- **`host`** (`any`) - The host to use for the client. -- **`debug`** (`bool`) - Whether to enable debug mode. -- **`max_queue_size`** (`int`) -- **`send`** (`bool`) -- **`on_error`** (`any`) -- **`flush_at`** (`int`) -- **`flush_interval`** (`float`) -- **`gzip`** (`bool`) -- **`max_retries`** (`int`) -- **`sync_mode`** (`bool`) -- **`timeout`** (`int`) -- **`thread`** (`int`) -- **`poll_interval`** (`int`) -- **`personal_api_key`** (`any`) -- **`disabled`** (`bool`) -- **`disable_geoip`** (`bool`) -- **`historical_migration`** (`bool`) -- **`feature_flags_request_timeout_seconds`** (`int`) -- **`super_properties`** (`any`) -- **`enable_exception_autocapture`** (`bool`) -- **`log_captured_exceptions`** (`bool`) -- **`project_root`** (`any`) -- **`privacy_mode`** (`bool`) -- **`before_send`** (`any`) -- **`flag_fallback_cache_url`** (`any`) -- **`enable_local_evaluation`** (`bool`) -- **`flag_definition_cache_provider?`** (`FlagDefinitionCacheProvider`) -- **`capture_exception_code_variables`** (`bool`) -- **`code_variables_mask_patterns`** (`any`) -- **`code_variables_ignore_patterns`** (`any`) -- **`in_app_modules`** (`UnionType[list[str], any]`) +- **`project_api_key?`** (`str`) - PostHog project API key/token. +- **`host`** (`any`) - PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host. +- **`debug`** (`bool`) - Enable verbose SDK logging and re-raise errors from public API methods. +- **`max_queue_size`** (`int`) - Maximum number of events buffered before upload. +- **`send`** (`bool`) - If False, queueing succeeds but events are not sent. +- **`on_error`** (`any`) - Optional callback invoked by background consumers when an upload fails. +- **`flush_at`** (`int`) - Number of queued events that triggers a batch upload. +- **`flush_interval`** (`float`) - Maximum seconds a background consumer waits before flushing a partial batch. +- **`gzip`** (`bool`) - Whether to gzip event upload payloads. +- **`max_retries`** (`int`) - Number of upload retries for background consumers. +- **`sync_mode`** (`bool`) - If True, send each event synchronously instead of using background worker threads. +- **`timeout`** (`int`) - HTTP request timeout in seconds for event uploads. +- **`thread`** (`int`) - Number of background consumer threads. +- **`poll_interval`** (`int`) - Seconds between local feature flag definition refreshes. +- **`personal_api_key`** (`any`) - Personal API key used for local feature flag evaluation and remote config payloads. +- **`disabled`** (`bool`) - If True, disable captures and API requests. Useful in tests. +- **`disable_geoip`** (`bool`) - Whether to disable server-side GeoIP enrichment. Defaults to True. +- **`historical_migration`** (`bool`) - Mark events as historical migration imports. +- **`feature_flags_request_timeout_seconds`** (`int`) - Timeout in seconds for feature flag and remote config requests. +- **`super_properties`** (`any`) - Properties merged into every captured event. +- **`enable_exception_autocapture`** (`bool`) - Automatically capture uncaught exceptions. +- **`log_captured_exceptions`** (`bool`) - Also log exceptions captured by error tracking. +- **`project_root`** (`any`) - Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. +- **`privacy_mode`** (`bool`) - For AI observability, capture usage metadata without prompt inputs or outputs. +- **`before_send`** (`any`) - Optional callback that can modify or drop events before upload. Return ``None`` to drop an event. +- **`flag_fallback_cache_url`** (`any`) - Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL. +- **`enable_local_evaluation`** (`bool`) - Whether to poll feature flag definitions for local evaluation when a personal API key is configured. +- **`flag_definition_cache_provider?`** (`FlagDefinitionCacheProvider`) - Optional external cache provider for sharing feature flag definitions across workers. +- **`capture_exception_code_variables`** (`bool`) - Capture local variable values on exception stack frames. +- **`code_variables_mask_patterns`** (`any`) - Variable-name patterns to mask when capturing code variables. +- **`code_variables_ignore_patterns`** (`any`) - Variable-name patterns to omit when capturing code variables. +- **`in_app_modules`** (`UnionType[list[str], any]`) - Module/package prefixes treated as in-app frames in captured exceptions. ### Returns @@ -341,6 +341,18 @@ if is_my_flag_enabled: --- +#### feature_flag_definitions() + +**Release Tag:** public + +Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions. + +### Returns + +- `None` + +--- + #### get_all_flags() **Release Tag:** public @@ -575,6 +587,22 @@ decision = posthog.get_flags_decision('user123') --- +#### get_remote_config_payload() + +**Release Tag:** public + +Get the payload for a remote config feature flag. + +### Parameters + +- **`key?`** (`str`) - The remote config feature flag key. + +### Returns + +- `None` + +--- + #### load_feature_flags() **Release Tag:** public @@ -827,15 +855,8 @@ This will overwrite previous people property values. Generally operates similar ```python # Set person properties -from posthog import capture -capture( - 'distinct_id', - event='event_name', - properties={ - '$set': {'name': 'Max Hedgehog'}, - '$set_once': {'initial_url': '/blog'} - } -) +from posthog import set +set(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'}) ``` --- @@ -862,15 +883,8 @@ This will not overwrite previous people property values, unlike `set`. Otherwise ```python # Set property once -from posthog import capture -capture( - 'distinct_id', - event='event_name', - properties={ - '$set': {'name': 'Max Hedgehog'}, - '$set_once': {'initial_url': '/blog'} - } -) +from posthog import set_once +set_once(distinct_id='distinct_id', properties={'initial_url': '/blog'}) ``` --- @@ -957,7 +971,7 @@ Capture exception is idempotent - if it is called twice with the same exception ### Parameters -- **`exception`** (`BaseException`) - The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` +- **`exception`** (`BaseException`) - The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. - **`kwargs?`** (`Unpack[OptionalCaptureArgs]`) ### Returns @@ -1032,7 +1046,7 @@ You can call `posthog.load_feature_flags()` before to make sure you're not doing - **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally - **`send_feature_flag_events`** (`bool`) - Whether to send feature flag events - **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup -- **`device_id`** (`any`) +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags ### Returns @@ -1091,7 +1105,7 @@ Flags are key-value pairs where the key is the flag key and the value is the fla - **`group_properties`** (`any`) - Group properties - **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally - **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup -- **`device_id`** (`any`) +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags - **`flag_keys_to_evaluate`** (`any`) - Optional list of flag keys to evaluate (evaluates all if None) ### Returns @@ -1108,6 +1122,29 @@ get_all_flags('distinct_id_of_your_user') --- +#### get_all_flags_and_payloads() + +**Release Tag:** public + +Get all feature flag values and payloads for a user. + +### Parameters + +- **`distinct_id?`** (`any`) - The user's distinct ID. +- **`groups`** (`any`) - Mapping of group type to group key. +- **`person_properties`** (`any`) - Person properties to use for evaluation. +- **`group_properties`** (`any`) - Group properties keyed by group type. +- **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally. +- **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup. +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags. +- **`flag_keys_to_evaluate`** (`any`) - Optional list of flag keys to evaluate. Evaluates all flags when omitted. + +### Returns + +- `FlagsAndPayloads` + +--- + #### get_feature_flag() **Release Tag:** public @@ -1128,7 +1165,7 @@ Get feature flag variant for users. Used with experiments. - **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally - **`send_feature_flag_events`** (`bool`) - Whether to send feature flag events - **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup -- **`device_id`** (`any`) +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags ### Returns @@ -1146,6 +1183,31 @@ if enabled_variant == 'variant-key': --- +#### get_feature_flag_payload() + +**Release Tag:** public + +Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request. + +### Parameters + +- **`key?`** (`any`) - The feature flag key. +- **`distinct_id?`** (`any`) - The user's distinct ID. +- **`match_value`** (`any`) - Optional flag value to use when selecting a payload. +- **`groups`** (`any`) - Mapping of group type to group key. +- **`person_properties`** (`any`) - Person properties to use for evaluation. +- **`group_properties`** (`any`) - Group properties keyed by group type. +- **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally. +- **`send_feature_flag_events`** (`bool`) - Whether to send a $feature_flag_called event. +- **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup. +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags. + +### Returns + +- `Optional[str]` + +--- + #### load_feature_flags() **Release Tag:** public @@ -1230,19 +1292,19 @@ shutdown() **Release Tag:** public -Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled Example: ```python result = posthog.get_feature_flag_result('beta-feature', 'distinct_id') if result and result.enabled: # Use the variant and payload print(f"Variant: {result.variant}") print(f"Payload: {result.payload}") ``` +Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled ### Parameters -- **`key?`** (`any`) -- **`distinct_id?`** (`any`) -- **`groups`** (`any`) -- **`person_properties`** (`any`) -- **`group_properties`** (`any`) -- **`only_evaluate_locally`** (`bool`) -- **`send_feature_flag_events`** (`bool`) -- **`disable_geoip`** (`any`) -- **`device_id`** (`any`) +- **`key?`** (`any`) - The feature flag key. +- **`distinct_id?`** (`any`) - The user's distinct ID. +- **`groups`** (`any`) - Mapping of group type to group key. +- **`person_properties`** (`any`) - Person properties to use for evaluation. +- **`group_properties`** (`any`) - Group properties keyed by group type. +- **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally. +- **`send_feature_flag_events`** (`bool`) - Whether to send a $feature_flag_called event. +- **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup. +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags. ### Returns @@ -1266,120 +1328,120 @@ Get the payload for a remote config feature flag. --- -#### set_capture_exception_code_variables_context() - -**Release Tag:** public +### Contexts methods -Set whether code variables are captured for the current context. +#### get_tags() -### Parameters +**Release Tag:** public -- **`enabled?`** (`bool`) +Get all tags from the current context. Returns: Dict of all tags in the current context ### Returns -- `None` +- `dict[str, Any]` --- -#### set_code_variables_ignore_patterns_context() +#### new_context() **Release Tag:** public -Variable names matching these patterns will be ignored completely when capturing code variables. +Create a new context scope that will be active for the duration of the with block. ### Parameters -- **`ignore_patterns?`** (`list`) +- **`fresh`** (`bool`) - Whether to start with a fresh context (default: False) +- **`capture_exceptions`** (`bool`) - Whether to capture exceptions raised within the context (default: True) +- **`client?`** (`Client`) - Optional Posthog client instance to use for this context (default: None) ### Returns - `None` +### Examples + +```python +from posthog import new_context, tag, capture +with new_context(): + tag("request_id", "123") + capture("event_name", properties={"property": "value"}) +``` + --- -#### set_code_variables_mask_patterns_context() +#### scoped() **Release Tag:** public -Variable names matching these patterns will be masked with *** when capturing code variables. +Decorator that creates a new context for the function. ### Parameters -- **`mask_patterns?`** (`list`) +- **`fresh`** (`bool`) - Whether to start with a fresh context (default: False) +- **`capture_exceptions`** (`bool`) - Whether to capture and track exceptions with posthog error tracking (default: True) ### Returns - `None` ---- +### Examples -### Contexts methods +```python +from posthog import scoped, tag, capture +@scoped() +def process_payment(payment_id): + tag("payment_id", payment_id) + capture("payment_started") +``` -#### get_tags() +--- + +#### set_capture_exception_code_variables_context() **Release Tag:** public -Get all tags from the current context. Returns: Dict of all tags in the current context +Override code-variable capture for exceptions in the current context. + +### Parameters + +- **`enabled?`** (`bool`) - Whether exceptions captured in this context should include local variable values from stack frames. ### Returns -- `dict[str, Any]` +- `None` --- -#### new_context() +#### set_code_variables_ignore_patterns_context() **Release Tag:** public -Create a new context scope that will be active for the duration of the with block. +Override code-variable ignore patterns for exceptions in the current context. ### Parameters -- **`fresh`** (`bool`) - Whether to start with a fresh context (default: False) -- **`capture_exceptions`** (`bool`) - Whether to capture exceptions raised within the context (default: True) -- **`client?`** (`Client`) - Optional Posthog client instance to use for this context (default: None) +- **`ignore_patterns?`** (`list`) - Variable-name patterns that should be omitted entirely when code variables are captured. ### Returns - `None` -### Examples - -```python -from posthog import new_context, tag, capture -with new_context(): - tag("request_id", "123") - capture("event_name", properties={"property": "value"}) -``` - --- -#### scoped() +#### set_code_variables_mask_patterns_context() **Release Tag:** public -Decorator that creates a new context for the function. +Override code-variable mask patterns for exceptions in the current context. ### Parameters -- **`fresh`** (`bool`) - Whether to start with a fresh context (default: False) -- **`capture_exceptions`** (`bool`) - Whether to capture and track exceptions with posthog error tracking (default: True) +- **`mask_patterns?`** (`list`) - Variable-name patterns whose values should be replaced with ``***`` when code variables are captured. ### Returns - `None` -### Examples - -```python -from posthog import scoped, tag, capture -@scoped() -def process_payment(payment_id): - tag("payment_id", payment_id) - capture("payment_started") -``` - --- #### set_context_device_id() @@ -1450,4 +1512,18 @@ from posthog import tag tag("user_id", "123") ``` +--- + +### Initialization methods + +#### setup() + +**Release Tag:** public + +Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. Raises: ValueError: If ``api_key`` has not been configured. + +### Returns + +- `Client` + --- \ No newline at end of file diff --git a/skills/instrument-integration/references/python.md b/skills/instrument-integration/references/python.md index 166ab38..154b1cd 100644 --- a/skills/instrument-integration/references/python.md +++ b/skills/instrument-integration/references/python.md @@ -224,7 +224,7 @@ Python PostHog AI ```python -from posthog import new_context +from posthog import new_context, tag with new_context(fresh=True): tag("some-key", "value-2") # This event only has the property some-key="value-2" from the fresh context @@ -266,7 +266,7 @@ You can read more about identifying users in the [user identification documentat ### Contexts and sessions -Contexts can be associated with a session ID by calling `posthog.set_context_session`. Session IDs must be UUIDv7 strings. +Contexts can be associated with a session ID by calling `posthog.set_context_session`. When linking backend events to frontend sessions, use the session ID from the frontend SDK (PostHog session IDs are UUIDv7 strings). Python @@ -324,7 +324,7 @@ with new_context(capture_exceptions=False): ### Decorating functions -The SDK exposes a function decorator. It takes the same arguments as `new_context` and provides a handy way to mark a whole function as being in a new context. For example: +The SDK exposes a function decorator. It takes the same `fresh` and `capture_exceptions` arguments as `new_context` and provides a handy way to mark a whole function as being in a new context. For example: Python @@ -609,9 +609,9 @@ if variant == "variant-name": It's also possible to [run experiments without using feature flags](/docs/experiments/running-experiments-without-feature-flags.md). -## LLM analytics +## AI Observability -Our Python SDK includes a built-in LLM analytics feature. It enables you to capture LLM usage, performance, and more. Check out our [analytics docs](/docs/llm-analytics.md) for more details on setting it up. +Our Python SDK includes a built-in AI Observability feature. It enables you to capture LLM usage, performance, and more. Check out our [analytics docs](/docs/ai-observability.md) for more details on setting it up. ## Error tracking @@ -633,7 +633,7 @@ Python PostHog AI ```python -posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties) +posthog.capture_exception(e, distinct_id='user_distinct_id', properties=additional_properties) ``` Contexts automatically capture exceptions thrown inside them, unless disable it by passing `capture_exceptions=False` to `new_context()`. diff --git a/skills/instrument-integration/references/react-router-v6.md b/skills/instrument-integration/references/react-router-v6.md index b651e1e..291000d 100644 --- a/skills/instrument-integration/references/react-router-v6.md +++ b/skills/instrument-integration/references/react-router-v6.md @@ -42,15 +42,15 @@ This guide walks you through setting up PostHog for React Router V6. If you're u Required - Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, including `VITE_PUBLIC_` in their names ensures they are accessible in the frontend. + Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, prefixing variable names with `VITE_` ensures they are accessible in the frontend. .env.local PostHog AI ```shell - VITE_PUBLIC_POSTHOG_TOKEN= - VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + VITE_POSTHOG_TOKEN= + VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. 3 @@ -72,8 +72,8 @@ This guide walks you through setting up PostHog for React Router V6. If you're u import posthog from 'posthog-js'; import { PostHogErrorBoundary, PostHogProvider } from '@posthog/react' // Initialize PostHog - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', }); const root = document.getElementById("root"); @@ -341,8 +341,8 @@ This guide walks you through setting up PostHog for React Router V6. If you're u PostHog AI ```jsx - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', __add_tracing_headers: [ window.location.host, 'localhost' ], }); diff --git a/skills/instrument-integration/references/react-router-v7-data-mode.md b/skills/instrument-integration/references/react-router-v7-data-mode.md index 17d61a0..fa07c5b 100644 --- a/skills/instrument-integration/references/react-router-v7-data-mode.md +++ b/skills/instrument-integration/references/react-router-v7-data-mode.md @@ -42,15 +42,15 @@ This guide walks you through setting up PostHog for React Router V7 in data mode Required - Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, including `VITE_PUBLIC_` in their names ensures they are accessible in the frontend. + Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, prefixing variable names with `VITE_` ensures they are accessible in the frontend. .env.local PostHog AI ```shell - VITE_PUBLIC_POSTHOG_TOKEN= - VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + VITE_POSTHOG_TOKEN= + VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. 3 @@ -72,8 +72,8 @@ This guide walks you through setting up PostHog for React Router V7 in data mode import Root, { RootErrorBoundary } from "./app/root"; import posthog from 'posthog-js'; import { PostHogProvider } from '@posthog/react' - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', }); const router = createBrowserRouter([...]); @@ -329,8 +329,8 @@ This guide walks you through setting up PostHog for React Router V7 in data mode PostHog AI ```jsx - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', __add_tracing_headers: [ window.location.host, 'localhost' ], }); diff --git a/skills/instrument-integration/references/react-router-v7-declarative-mode.md b/skills/instrument-integration/references/react-router-v7-declarative-mode.md index f88d210..e13d45a 100644 --- a/skills/instrument-integration/references/react-router-v7-declarative-mode.md +++ b/skills/instrument-integration/references/react-router-v7-declarative-mode.md @@ -42,15 +42,15 @@ This guide walks you through setting up PostHog for React Router V7 in declarati Required - Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, including `VITE_PUBLIC_` in their names ensures they are accessible in the frontend. + Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, prefixing variable names with `VITE_` ensures they are accessible in the frontend. .env.local PostHog AI ```shell - VITE_PUBLIC_POSTHOG_TOKEN= - VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + VITE_POSTHOG_TOKEN= + VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. 3 @@ -72,8 +72,8 @@ This guide walks you through setting up PostHog for React Router V7 in declarati import posthog from 'posthog-js'; import { PostHogErrorBoundary, PostHogProvider } from '@posthog/react' // Initialize PostHog - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', }); const root = document.getElementById("root"); @@ -341,8 +341,8 @@ This guide walks you through setting up PostHog for React Router V7 in declarati PostHog AI ```jsx - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', __add_tracing_headers: [ window.location.host, 'localhost' ], }); diff --git a/skills/instrument-integration/references/react-router-v7-framework-mode.md b/skills/instrument-integration/references/react-router-v7-framework-mode.md index 2d92274..a2ee8ea 100644 --- a/skills/instrument-integration/references/react-router-v7-framework-mode.md +++ b/skills/instrument-integration/references/react-router-v7-framework-mode.md @@ -58,15 +58,15 @@ This guide walks you through setting up PostHog for React Router V7 in framework Required - Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, including `VITE_PUBLIC_` in their names ensures they are accessible in the frontend. + Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, prefixing variable names with `VITE_` ensures they are accessible in the frontend. .env.local PostHog AI ```shell - VITE_PUBLIC_POSTHOG_TOKEN= - VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + VITE_POSTHOG_TOKEN= + VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. 3 @@ -87,8 +87,8 @@ This guide walks you through setting up PostHog for React Router V7 in framework import { HydratedRouter } from "react-router/dom"; import posthog from 'posthog-js'; import { PostHogProvider } from '@posthog/react' - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', __add_tracing_headers: [ window.location.host, 'localhost' ], }); @@ -393,8 +393,8 @@ This guide walks you through setting up PostHog for React Router V7 in framework posthog?: PostHog; } export const posthogMiddleware: Route.MiddlewareFunction = async ({ request, context }, next) => { - const posthog = new PostHog(process.env.VITE_PUBLIC_POSTHOG_TOKEN!, { - host: process.env.VITE_PUBLIC_POSTHOG_HOST!, + const posthog = new PostHog(process.env.VITE_POSTHOG_TOKEN!, { + host: process.env.VITE_POSTHOG_HOST!, flushAt: 1, flushInterval: 0, }); diff --git a/skills/instrument-integration/references/react.md b/skills/instrument-integration/references/react.md index e2a5e4b..f680c9d 100644 --- a/skills/instrument-integration/references/react.md +++ b/skills/instrument-integration/references/react.md @@ -56,15 +56,15 @@ pnpm add posthog-js @posthog/react bun add posthog-js @posthog/react ``` -2. Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, include `VITE_PUBLIC_` in their names ensures they are accessible in the frontend. +2. Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, prefixing variable names with `VITE_` ensures they are accessible in the frontend. .env.local PostHog AI ```shell -VITE_PUBLIC_POSTHOG_TOKEN= -VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com +VITE_POSTHOG_TOKEN= +VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. Integrate PostHog at the root of your app (such as `main.jsx` for Vite apps and `root.tsx` for React Router V7). @@ -81,8 +81,8 @@ import './index.css' import App from './App.jsx' import posthog from 'posthog-js'; import { PostHogProvider } from '@posthog/react' -posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, +posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', }); createRoot(document.getElementById('root')).render( diff --git a/skills/instrument-integration/references/ruby-on-rails.md b/skills/instrument-integration/references/ruby-on-rails.md index f4b7119..ad8cb5b 100644 --- a/skills/instrument-integration/references/ruby-on-rails.md +++ b/skills/instrument-integration/references/ruby-on-rails.md @@ -1,6 +1,6 @@ # Ruby on Rails - Docs -PostHog makes it easy to get data about traffic and usage of your Ruby on Rails app. Integrating PostHog enables analytics, custom events capture, feature flags, and automatic exception tracking. +PostHog makes it easy to get data about traffic and usage of your Ruby on Rails app. Integrating PostHog enables analytics, custom event capture, feature flags, and automatic exception tracking. This guide walks you through integrating PostHog into your Rails app using the [posthog-rails gem](https://github.com/PostHog/posthog-ruby/tree/main/posthog-rails). @@ -20,6 +20,7 @@ Or, to integrate manually, continue with the rest of this guide. - **ActiveJob instrumentation** – Tracks background job exceptions - **User context** – Automatically associates exceptions with the current user - **Smart filtering** – Excludes common Rails exceptions (404s, etc.) by default +- **Request context** – Adds request metadata and optional PostHog tracing header identity/session context to captured events - **Rails 7.0+ error reporter** – Integrates with Rails' built-in error reporting ## Installation @@ -67,35 +68,55 @@ This creates `config/initializers/posthog.rb` with sensible defaults and documen ## Configuration -The generated initializer includes all available options: +`PostHog.init` creates a single client instance used across your app. Avoid creating multiple `PostHog::Client` instances with the same API key, as this can cause dropped events and inconsistent behavior. + +The generated initializer includes the most common options: config/initializers/posthog.rb PostHog AI ```ruby +# Rails-specific configuration +PostHog::Rails.configure do |config| + config.auto_capture_exceptions = true # Enable automatic exception capture (default: false) + config.report_rescued_exceptions = true # Report exceptions Rails rescues (default: false) + config.auto_instrument_active_job = true # Instrument background jobs (default: false) + config.use_tracing_headers = true # Use PostHog tracing headers for identity/session context (default: true) + config.capture_user_context = true # Include authenticated user info in exceptions (default: true) + config.current_user_method = :current_user # Method to get current user (default: :current_user) + config.user_id_method = nil # Method to get ID from user object (default: auto-detect) + # Add additional exceptions to ignore + config.excluded_exceptions = ['MyCustomError'] +end # Core PostHog client initialization PostHog.init do |config| - # Required: Your PostHog API key + # Required: Your PostHog project API key config.api_key = '' # Optional: Your PostHog instance URL config.host = 'https://us.i.posthog.com' # Optional: Personal API key for feature flags config.personal_api_key = 'phx_xxxxxxxxx' + # Maximum number of events to queue before dropping (default: 10000) + config.max_queue_size = 10_000 + # Send events synchronously on the calling thread (default: false) + config.sync_mode = false + # Feature flags polling interval in seconds (default: 30) + config.feature_flags_polling_interval = 30 + # Feature flag request timeout in seconds (default: 3) + config.feature_flag_request_timeout_seconds = 3 # Error callback to detect misconfiguration config.on_error = proc { |status, msg| Rails.logger.error("PostHog error: #{msg}") } -end -# Rails-specific configuration -PostHog::Rails.configure do |config| - config.auto_capture_exceptions = true # Enable automatic exception capture - config.report_rescued_exceptions = true # Report exceptions Rails rescues - config.auto_instrument_active_job = true # Instrument background jobs - config.capture_user_context = true # Include user info in exceptions - config.current_user_method = :current_user # Method to get current user - # Add additional exceptions to ignore - config.excluded_exceptions = ['MyCustomError'] + # Before-send callback to modify or drop events + config.before_send = proc { |event| + event[:properties] ||= {} + event[:properties]['environment'] = Rails.env + event + } + # Disable network calls in test mode + config.test_mode = true if Rails.env.test? end ``` @@ -141,20 +162,45 @@ Ruby PostHog AI ```ruby -# Track an event -PostHog.capture( +PostHog.capture({ distinct_id: current_user.id, event: 'post_created', properties: { title: @post.title } -) -# Identify a user -PostHog.identify( +}) +``` + +Identify a user and set their person properties: + +Ruby + +PostHog AI + +```ruby +PostHog.identify({ distinct_id: current_user.id, properties: { email: current_user.email, plan: current_user.plan } -) +}) +``` + +The Rails integration delegates methods like `capture`, `identify`, `alias`, `group_identify`, `evaluate_flags`, `capture_exception`, `flush`, and `shutdown` to the initialized `PostHog::Client`. + +## Request context + +PostHog Rails automatically applies request-scoped context to events captured during web requests. Request metadata such as `$current_url`, `$request_method`, `$request_path`, `$user_agent`, and `$ip` is added to event properties. + +When `use_tracing_headers` is enabled, PostHog tracing headers (`X-PostHog-Distinct-Id` and `X-PostHog-Session-Id`) are also used as default `distinct_id` and `$session_id` values. Explicit `distinct_id` and properties passed to `PostHog.capture` always take precedence. + +Disable tracing header identity/session capture if you do not want client-supplied tracing headers used for server-side events. Request metadata is still captured: + +Ruby + +PostHog AI + +```ruby +PostHog::Rails.config.use_tracing_headers = false ``` ## Error tracking @@ -178,6 +224,8 @@ class PostsController < ApplicationController end ``` +`report_rescued_exceptions` controls whether exceptions Rails rescues (for example, exceptions rendered by Rails error pages) are captured. Enable it along with `auto_capture_exceptions` for complete error visibility, or leave it disabled to capture only unhandled exceptions. + ### Manual exception capture You can also manually capture exceptions: @@ -194,6 +242,22 @@ PostHog.capture_exception( ) ``` +If you evaluated feature flags for the request, pass the same snapshot to include matching flag properties on the exception event: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +PostHog.capture_exception( + exception, + current_user.id, + { custom_property: 'value' }, + flags: flags +) +``` + ### Background job exceptions When `auto_instrument_active_job` is enabled, ActiveJob exceptions are automatically captured with job context: @@ -214,7 +278,7 @@ end #### Associating jobs with users -By default, PostHog extracts a `distinct_id` from job arguments by looking for a `user_id` key: +By default, PostHog extracts a `distinct_id` from job arguments by looking for a `user_id` key in hash arguments: Ruby @@ -225,7 +289,7 @@ PostHog AI ProcessOrderJob.perform_later(order.id, user_id: current_user.id) ``` -For more control, use the `posthog_distinct_id` class method: +For more control, use the `posthog_distinct_id` class method. The proc or block receives the same arguments as `perform`: Ruby @@ -233,13 +297,30 @@ PostHog AI ```ruby class SendWelcomeEmailJob < ApplicationJob - posthog_distinct_id ->(user, options) { user.id } + posthog_distinct_id ->(user, _options) { user.id } def perform(user, options = {}) UserMailer.welcome(user).deliver_now end end ``` +You can also use a block: + +Ruby + +PostHog AI + +```ruby +class ProcessOrderJob < ApplicationJob + posthog_distinct_id do |_order, notify_user_id| + notify_user_id + end + def perform(order, notify_user_id) + # Process the order... + end +end +``` + ### Rails 7.0+ error reporter PostHog integrates with Rails' built-in error reporting: @@ -256,11 +337,13 @@ end Rails.error.record(exception, context: { user_id: current_user.id }) ``` -PostHog automatically extracts the user's distinct ID from `user_id` or `distinct_id` in the context hash. +PostHog automatically extracts the user's distinct ID from `user_id` or `distinct_id` in the context hash. Other context keys are included as properties on the exception event. ### User context -PostHog Rails automatically captures user information from your controllers. If your user method has a different name, configure it: +PostHog Rails automatically captures authenticated user information from your controllers for exceptions. Authenticated Rails user context takes precedence over client-supplied tracing headers for exception identity. + +If your user method has a different name, configure it: Ruby @@ -272,11 +355,15 @@ PostHog::Rails.config.current_user_method = :logged_in_user #### User ID extraction -By default, PostHog Rails auto-detects the user's distinct ID by trying these methods: +By default, PostHog Rails auto-detects the user's distinct ID by trying these methods in order: 1. `posthog_distinct_id` – Define this on your User model for full control 2. `distinct_id` – Common analytics convention 3. `id` – Standard ActiveRecord primary key +4. `pk` – Primary key alias +5. `uuid` – For UUID-based primary keys + +It also checks hash-like users for `id`, `pk`, and `uuid` keys. You can configure a specific method: @@ -309,9 +396,16 @@ The following exceptions are not reported by default (common 4xx errors): - `AbstractController::ActionNotFound` - `ActionController::BadRequest` - `ActionController::InvalidAuthenticityToken` +- `ActionController::InvalidCrossOriginRequest` +- `ActionController::MethodNotAllowed` +- `ActionController::NotImplemented` +- `ActionController::ParameterMissing` - `ActionController::RoutingError` - `ActionController::UnknownFormat` +- `ActionController::UnknownHttpMethod` +- `ActionDispatch::Http::Parameters::ParseError` - `ActiveRecord::RecordNotFound` +- `ActiveRecord::RecordNotUnique` Add more with: @@ -325,7 +419,7 @@ PostHog::Rails.config.excluded_exceptions = ['MyException'] ## Feature flags -Use feature flags in your Rails app: +Evaluate flags once for the current user, then read values from the returned snapshot: Ruby @@ -334,7 +428,8 @@ PostHog AI ```ruby class PostsController < ApplicationController def show - if PostHog.is_feature_enabled('new-post-design', current_user.id) + flags = PostHog.evaluate_flags(current_user.id) + if flags.enabled?('new-post-design') render 'posts/show_new' else render 'posts/show' @@ -343,6 +438,35 @@ class PostsController < ApplicationController end ``` +For multivariate flags and experiments, use `get_flag`: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +variant = flags.get_flag('checkout-experiment') +if variant == 'test' + # Do something differently +end +``` + +When capturing an event after branching on a flag, pass the same `flags` snapshot so the event includes the exact flag values used by your code: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +PostHog.capture({ + distinct_id: current_user.id, + event: 'checkout_started', + flags: flags.only_accessed +}) +``` + For local evaluation, ensure you've set `personal_api_key`: Ruby @@ -355,9 +479,11 @@ config.personal_api_key = Rails.application.credentials.posthog[:personal_api_ke See our [Ruby SDK docs](/docs/libraries/ruby.md#local-evaluation) for details on local evaluation with Puma and Unicorn servers. +> **Note:** `PostHog.is_feature_enabled`, `PostHog.get_feature_flag`, `PostHog.get_feature_flag_result`, `PostHog.get_feature_flag_payload`, and `PostHog.capture({ ..., send_feature_flags: true })` still work during the migration period, but they're deprecated. Prefer `PostHog.evaluate_flags` for new code. + ## Testing -In your test environment, disable PostHog or use test mode: +In your test environment, disable network calls with test mode: config/environments/test.rb @@ -365,7 +491,8 @@ PostHog AI ```ruby PostHog.init do |config| - config.test_mode = true # Events are queued but not sent + config.api_key = '' + config.test_mode = true end ``` @@ -389,23 +516,33 @@ end | Option | Type | Default | Description | | --- | --- | --- | --- | -| api_key | String | required | Your PostHog project token | -| host | String | https://us.i.posthog.com | PostHog instance URL | -| personal_api_key | String | nil | For feature flag evaluation | -| test_mode | Boolean | false | Don't send events (for testing) | -| on_error | Proc | nil | Error callback | +| api_key | String | required | Your PostHog project token. | +| host | String | https://us.i.posthog.com | Fully qualified PostHog API host. | +| personal_api_key | String | nil | Personal API key for local feature flag evaluation and remote config payloads. | +| max_queue_size | Integer | 10000 | Maximum number of events to keep in the async queue before dropping new events. | +| test_mode | Boolean | false | Keep events queued and do not send them. Useful for tests. | +| sync_mode | Boolean | false | Send events synchronously on the calling thread. | +| on_error | Proc | no-op | Callback called as on_error.call(status, error). | +| feature_flags_polling_interval | Integer | 30 | Seconds between local feature flag definition polls. | +| feature_flag_request_timeout_seconds | Integer | 3 | Timeout, in seconds, for feature flag requests. | +| before_send | Proc | nil | Callback that receives the event hash before it is queued or sent. Return a modified event hash, or nil to drop the event. | + +The `PostHog.init` block supports the options above. Less common core options like `batch_size`, `disable_singleton_warning`, `skip_ssl_verification`, and the experimental `flag_definition_cache_provider` can be passed as an options hash to `PostHog.init(...)`; see the [Ruby SDK docs](/docs/libraries/ruby.md#configuration) for details. ### Rails-specific options +Configure these via `PostHog::Rails.configure` or `PostHog::Rails.config`: + | Option | Type | Default | Description | | --- | --- | --- | --- | -| auto_capture_exceptions | Boolean | false | Automatically capture exceptions | -| report_rescued_exceptions | Boolean | false | Report exceptions Rails rescues | -| auto_instrument_active_job | Boolean | false | Instrument ActiveJob | -| capture_user_context | Boolean | true | Include user info | -| current_user_method | Symbol | :current_user | Controller method for user | -| user_id_method | Symbol | nil | Method to extract ID from user object | -| excluded_exceptions | Array | [] | Additional exceptions to ignore | +| auto_capture_exceptions | Boolean | false | Automatically capture exceptions. | +| report_rescued_exceptions | Boolean | false | Report exceptions Rails rescues. | +| auto_instrument_active_job | Boolean | false | Capture ActiveJob exceptions with job context. | +| excluded_exceptions | Array | [] | Additional exception class names to ignore. | +| use_tracing_headers | Boolean | true | Use X-PostHog-Distinct-Id and X-PostHog-Session-Id as request-scoped defaults. | +| capture_user_context | Boolean | true | Include authenticated user info in exceptions. | +| current_user_method | Symbol | :current_user | Controller method used to fetch the current user. | +| user_id_method | Symbol | nil | Method used to extract the distinct ID from the user object. Auto-detects when nil. | ## Troubleshooting @@ -423,7 +560,7 @@ end => true ``` -2. Check your excluded exceptions list +2. Check your excluded exceptions list. 3. Verify middleware is installed: @@ -437,9 +574,9 @@ end ### User context not working -1. Verify `current_user_method` matches your controller method -2. Check that the user object responds to `posthog_distinct_id`, `distinct_id`, or `id` -3. If using a custom identifier, set `PostHog::Rails.config.user_id_method = :your_method` +1. Verify `current_user_method` matches your controller method. +2. Check that the user object responds to `posthog_distinct_id`, `distinct_id`, `id`, `pk`, or `uuid`. +3. If using a custom identifier, set `PostHog::Rails.config.user_id_method = :your_method`. ### Feature flags not working diff --git a/skills/instrument-integration/references/ruby.md b/skills/instrument-integration/references/ruby.md index 8a70dac..ab491dd 100644 --- a/skills/instrument-integration/references/ruby.md +++ b/skills/instrument-integration/references/ruby.md @@ -4,6 +4,8 @@ The `posthog-ruby` library provides tracking functionality on the server-side fo It uses an internal queue to make calls fast and non-blocking. It also batches requests and flushes asynchronously, making it perfect to use in any part of your web app or other server-side application that needs performance. +> **Use a single client instance (singleton)** — Create the PostHog client once and reuse it throughout your application. Multiple client instances with the same API key can cause dropped events and inconsistent behavior. The SDK logs a warning if it detects multiple instances. + ## Installation Add this to your `Gemfile`: @@ -33,12 +35,98 @@ posthog = PostHog::Client.new({ You can find your project token and instance address in the [project settings](https://app.posthog.com/project/settings) page in PostHog. +## Configuration + +Initialize the client with your project token before making any calls: + +Ruby + +PostHog AI + +```ruby +require 'posthog' +posthog = PostHog::Client.new({ + api_key: '', + host: 'https://us.i.posthog.com', + on_error: Proc.new { |status, msg| print msg } +}) +``` + +Available client options: + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| api_key | String | required | Your PostHog project token. | +| host | String | https://us.i.posthog.com | Fully qualified PostHog API host. Include the protocol, for example https://us.i.posthog.com or https://eu.i.posthog.com. | +| personal_api_key | String | nil | Personal API key. Required for local feature flag evaluation and remote config payloads. | +| max_queue_size | Integer | 10000 | Maximum number of events to keep in the async queue before dropping new events. | +| batch_size | Integer | 100 | Maximum number of events to send in one async batch. | +| test_mode | Boolean | false | Keep events queued and do not send them. Useful for tests. | +| sync_mode | Boolean | false | Send events synchronously on the calling thread. Useful in forking environments like Sidekiq and Resque. | +| on_error | Proc | no-op | Callback called as on_error.call(status, error) for API or serialization errors. | +| feature_flags_polling_interval | Integer | 30 | Seconds between local feature flag definition polls. | +| feature_flag_request_timeout_seconds | Integer | 3 | Timeout, in seconds, for feature flag requests. | +| before_send | Proc | nil | Callback that receives the event hash before it is queued or sent. Return a modified event hash, or nil to drop the event. | +| disable_singleton_warning | Boolean | false | Suppress warnings about multiple clients with the same API key. Use only when you intentionally need multiple clients. | +| skip_ssl_verification | Boolean | false | Disable SSL certificate verification. Intended only for local development or custom deployments. | +| flag_definition_cache_provider | Object | nil | Experimental provider for distributed feature flag definition caching. See [distributed flag definition caching](#distributed-flag-definition-caching). | + +### Filtering or modifying events before sending + +Use `before_send` to add, modify, or drop events immediately before the SDK queues or sends them: + +Ruby + +PostHog AI + +```ruby +posthog = PostHog::Client.new({ + api_key: '', + before_send: Proc.new do |event| + event[:properties] ||= {} + event[:properties]['environment'] = ENV['RACK_ENV'] + # Return nil to drop the event + event[:properties]['internal_user'] == true ? nil : event + end +}) +``` + +### Flushing and shutting down + +For short-lived scripts, call `flush` before the process exits. Call `shutdown` when your application is stopping to flush pending events and stop background resources. + +Ruby + +PostHog AI + +```ruby +posthog.capture({ distinct_id: 'user_123', event: 'script_finished' }) +posthog.flush +posthog.shutdown +``` + ## Identifying users > **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). > > See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. +Identify a user and set their person properties with `identify`: + +Ruby + +PostHog AI + +```ruby +posthog.identify({ + distinct_id: 'distinct_id_of_your_user', + properties: { + email: 'john@doe.com', + pro_user: false + } +}) +``` + ## Capturing events You can send custom events using `capture`: @@ -93,6 +181,20 @@ posthog.capture({ }) ``` +`capture` accepts these fields: + +| Field | Type | Description | +| --- | --- | --- | +| distinct_id | String | The user ID. If omitted, framework integrations can provide request context; otherwise the SDK generates a UUID and marks the event as personless. | +| event | String | Event name. Required. | +| properties | Hash | Event properties. | +| groups | Hash | Group analytics mapping from group type to group key. | +| timestamp | Time | When the event occurred. Defaults to the current time. | +| message_id | String | Optional message ID. | +| uuid | String | Optional event UUID used for deduplication. Must be a valid UUID. | +| flags | PostHog::FeatureFlagEvaluations | Snapshot returned by evaluate_flags. Adds $feature/ and $active_feature_flags properties without another /flags request. | +| send_feature_flags | Boolean, Hash, or PostHog::SendFeatureFlagsOptions | Deprecated. Prefer passing flags: from evaluate_flags. | + ## Person profiles and properties The Ruby SDK captures identified events by default. These create [person profiles](/docs/data/persons.md). To set [person properties](/docs/data/user-properties.md) in these profiles, include them when capturing an event: @@ -102,14 +204,14 @@ Ruby PostHog AI ```ruby -posthog.capture( +posthog.capture({ distinct_id: 'distinct_id', event: 'event_name', properties: { '$set': { name: 'Max Hedgehog' }, '$set_once': { initial_url: '/blog' } } -) +}) ``` For more details on the difference between `$set` and `$set_once`, see our [person properties docs](/docs/data/user-properties.md#what-is-the-difference-between-set-and-set_once). @@ -121,13 +223,13 @@ Ruby PostHog AI ```ruby -posthog.capture( +posthog.capture({ distinct_id: 'distinct_id', event: 'event_name', properties: { '$process_person_profile': false } -) +}) ``` ## Alias @@ -141,10 +243,10 @@ Ruby PostHog AI ```ruby -posthog.alias( - distinct_id: "distinct_id", - alias: "alias_id" -) +posthog.alias({ + distinct_id: 'distinct_id', + alias: 'alias_id' +}) ``` We strongly recommend reading our docs on [alias](/docs/data/identify.md#alias-assigning-multiple-distinct-ids-to-the-same-user) to best understand how to correctly use this method. @@ -192,7 +294,7 @@ end `flags.get_flag()` returns the variant string for multivariate flags, `true` for enabled boolean flags, `false` for disabled flags, and `nil` when the flag wasn't returned by the evaluation. -> **Note:** `posthog.is_feature_enabled()`, `posthog.get_feature_flag()`, `posthog.get_feature_flag_payload()`, and `capture(send_feature_flags: true)` still work during the migration period, but they're deprecated. Prefer `evaluate_flags()` for new code. +> **Note:** `posthog.is_feature_enabled()`, `posthog.get_feature_flag()`, `posthog.get_feature_flag_result()`, `posthog.get_feature_flag_payload()`, and `capture({ ..., send_feature_flags: true })` still work during the migration period, but they're deprecated. Prefer `evaluate_flags()` for new code. ### Step 2: Include feature flag information when capturing events @@ -281,6 +383,36 @@ flags = posthog.evaluate_flags( ) ``` +### Evaluating locally only + +If you want to skip the remote `/flags` request and only use locally cached definitions, pass `only_evaluate_locally: true`: + +Ruby + +PostHog AI + +```ruby +flags = posthog.evaluate_flags( + 'distinct_id_of_your_user', + only_evaluate_locally: true, +) +``` + +### Disabling GeoIP for flag evaluation + +Pass `disable_geoip: true` to disable GeoIP lookup for remote flag evaluation: + +Ruby + +PostHog AI + +```ruby +flags = posthog.evaluate_flags( + 'distinct_id_of_your_user', + disable_geoip: true, +) +``` + ### Sending `$feature_flag_called` events Capturing `$feature_flag_called` events enables PostHog to know when a flag was accessed by a user and provide [analytics and insights](/docs/product-analytics/insights.md) on the flag. With `evaluate_flags()`, the SDK sends this event when you call `flags.enabled?()` or `flags.get_flag()` for a flag. @@ -367,6 +499,18 @@ posthog = PostHog::Client.new({ }) ``` +### Legacy single-flag methods + +The following methods are still available during the migration period, but are deprecated. Prefer `evaluate_flags` for new code. + +| Method | Replacement | +| --- | --- | +| posthog.is_feature_enabled(flag_key, distinct_id, ...) | posthog.evaluate_flags(distinct_id, ...).enabled?(flag_key) | +| posthog.get_feature_flag(flag_key, distinct_id, ...) | posthog.evaluate_flags(distinct_id, ...).get_flag(flag_key) | +| posthog.get_feature_flag_payload(flag_key, distinct_id, ...) | posthog.evaluate_flags(distinct_id, ...).get_flag_payload(flag_key) | +| posthog.get_feature_flag_result(flag_key, distinct_id, ...) | posthog.evaluate_flags(distinct_id, ...) and read get_flag / get_flag_payload | +| posthog.capture({ ..., send_feature_flags: true }) | posthog.capture({ ..., flags: flags }) | + ### Local Evaluation Evaluating feature flags requires making a request to PostHog for each flag. However, you can improve performance by evaluating flags locally. Instead of making a request for each flag, PostHog will periodically request and store feature flag definitions locally, enabling you to evaluate flags without making additional requests. @@ -377,26 +521,26 @@ For details on how to implement local evaluation, see our [local evaluation guid #### Evaluating feature flags locally in unicorn server -If you have `preload_app true` in your unicorn config, you can use the [`after_fork`](https://www.rubydoc.info/gems/unicorn/Unicorn%2FConfigurator:after_fork) hook (which is part of the unicorn's configuration) to enable the feature flag cache to receive the updates from posthog dashboard. +If you have `preload_app true` in your unicorn config, you can use the [`after_fork`](https://www.rubydoc.info/gems/unicorn/Unicorn%2FConfigurator:after_fork) hook (which is part of the unicorn's configuration) to enable the feature flag cache to receive the updates from PostHog. Ruby PostHog AI ```ruby -after_fork do |server, worker| - $posthog = PostHog::Client.new( +after_fork do |_server, _worker| + $posthog = PostHog::Client.new({ api_key: '', - personal_api_key: '' + personal_api_key: '', host: 'https://us.i.posthog.com', on_error: Proc.new { |status, msg| print msg } - ) + }) end ``` #### Evaluating feature flags locally in a Puma server -If you use Puma with multiple workers, you can use the `on_worker_boot` hook (which is part of the Puma's configuration) to enable the feature flag cache to receive the updates from PostHog. +If you use Puma with multiple workers, you can use the `on_worker_boot` hook (which is part of Puma's configuration) to enable the feature flag cache to receive updates from PostHog. Ruby @@ -404,15 +548,50 @@ PostHog AI ```ruby on_worker_boot do - $posthog = PostHog::Client.new( + $posthog = PostHog::Client.new({ api_key: '', - personal_api_key: '' + personal_api_key: '', host: 'https://us.i.posthog.com', on_error: Proc.new { |status, msg| print msg } - ) + }) end ``` +### Distributed flag definition caching + +`flag_definition_cache_provider` is an experimental API for sharing locally evaluated feature flag definitions across multiple workers or processes. The provider object must implement: + +- `flag_definitions` – returns cached definitions as a hash with `:flags`, `:group_type_mapping`, and `:cohorts`, or `nil` if empty. +- `should_fetch_flag_definitions?` – returns `true` if this process should fetch fresh definitions from PostHog. +- `on_flag_definitions_received(data)` – stores freshly fetched definitions. +- `shutdown` – releases locks or other resources. + +Ruby + +PostHog AI + +```ruby +posthog = PostHog::Client.new({ + api_key: '', + personal_api_key: '', + flag_definition_cache_provider: my_cache_provider +}) +``` + +Because this API is experimental, it may change in future minor versions. + +### Remote config payloads + +Use `get_remote_config_payload` to fetch the decrypted remote config payload for a flag. This requires `personal_api_key`. + +Ruby + +PostHog AI + +```ruby +payload = posthog.get_remote_config_payload('flag-key') +``` + ## Experiments (A/B tests) Since [experiments](/docs/experiments/start-here.md) use feature flags, the code for running an experiment is very similar to the feature flags code: @@ -437,7 +616,7 @@ Group analytics allows you to associate an event with a group (e.g. teams, organ > **Note:** This is a paid feature and is not available on the open-source or free cloud plan. Learn more on the [pricing page](/pricing.md). -- Capture an event and associate it with a group +Capture an event and associate it with a group: Ruby @@ -450,34 +629,32 @@ posthog.capture({ properties: { movie_id: '123', category: 'romcom' - } + }, groups: { 'company': 'company_id_in_your_db' } }) ``` -- Update properties on a group +Update properties on a group: Ruby PostHog AI ```ruby -posthog.group_identify( - { - group_type: "company", - group_key: "company_id_in_your_db", - properties: { - name: "Awesome Inc." - } +posthog.group_identify({ + group_type: 'company', + group_key: 'company_id_in_your_db', + properties: { + name: 'Awesome Inc.' } -) +}) ``` The `name` is a special property which is used in the PostHog UI for the name of the group. If you don't specify a `name` property, the group ID will be used instead. -If the optional `distinct_id` is not provided in the group identify call, it defaults to `${groupType}_${groupKey}` (e.g., `$company_company_id_in_your_db` in the example above). This default behavior will result in each group appearing as a separate person in PostHog. To avoid this, it's often more practical to use a consistent `distinct_id`, such as `group_identifier`. +If the optional `distinct_id` is not provided in the group identify call, it defaults to `$#{group_type}_#{group_key}` (e.g., `$company_company_id_in_your_db` in the example above). This default behavior will result in each group appearing as a separate person in PostHog. To avoid this, it's often more practical to use a consistent `distinct_id`, such as `group_identifier`. ## Exception capture @@ -487,9 +664,7 @@ You can capture exceptions using the `posthog-ruby` library. This enables you to The [posthog-rails](/docs/libraries/ruby-on-rails.md) gem provides automatic exception capture, ActiveJob instrumentation, and user context out of the box. See our [Rails error tracking guide](/docs/error-tracking/installation/ruby-on-rails.md) for details. -For non-Rails Ruby applications, you can manually capture exceptions: - -To capture exceptions, use the `capture_exception` method: +For non-Rails Ruby applications, you can manually capture exceptions with `capture_exception`: Ruby @@ -498,12 +673,12 @@ PostHog AI ```ruby begin # Code that might raise an exception - raise StandardError, "Something went wrong" + raise StandardError, 'Something went wrong' rescue => e posthog.capture_exception( e, - distinct_id: 'user_distinct_id', - properties: { + 'user_distinct_id', + { custom_property: 'custom_value' } ) @@ -514,9 +689,10 @@ The `capture_exception` method accepts the following parameters: | Parameter | Type | Description | | --- | --- | --- | -| exception | Exception | The exception object to capture (required) | -| distinct_id | String | The distinct ID of the user (optional) | -| properties | Hash | Additional properties to attach to the exception event (optional) | +| exception | Exception, String, or exception-like object | The exception to capture. Required. | +| distinct_id | String | The distinct ID of the user. Optional; request context can provide a default, otherwise the SDK generates a UUID. | +| additional_properties | Hash | Additional properties to attach to the exception event. Optional. | +| flags | PostHog::FeatureFlagEvaluations | Optional keyword argument. Adds the same feature flag properties as capture({ flags: flags }). | You can also override the [fingerprint](/docs/error-tracking/fingerprints.md) to customize how exceptions are grouped into issues: @@ -527,8 +703,8 @@ PostHog AI ```ruby posthog.capture_exception( e, - distinct_id: 'user_distinct_id', - properties: { + 'user_distinct_id', + { '$exception_fingerprint': 'CustomExceptionGroup' } ) @@ -536,7 +712,41 @@ posthog.capture_exception( ## Debug mode -The Ruby SDK debug logs by default. The log level by default is set to `WARN`. You can change it to `DEBUG` if you want to debug the client by running `posthog.logger.level = Logger::DEBUG`, where `posthog` is your initialized `PostHog::Client` instance. +The Ruby SDK logs warnings by default. You can change the log level to `DEBUG` to debug the client: + +Ruby + +PostHog AI + +```ruby +posthog.logger.level = Logger::DEBUG +``` + +You can also replace the SDK logger globally: + +Ruby + +PostHog AI + +```ruby +PostHog::Logging.logger = Rails.logger +``` + +## Test helpers + +When `test_mode: true`, events remain queued. You can inspect and clear the queue in tests: + +Ruby + +PostHog AI + +```ruby +posthog = PostHog::Client.new({ api_key: '', test_mode: true }) +posthog.capture({ distinct_id: 'user_123', event: 'test_event' }) +posthog.queued_messages +posthog.dequeue_last_message +posthog.clear +``` ## Thank you diff --git a/skills/instrument-llm-analytics/SKILL.md b/skills/instrument-llm-analytics/SKILL.md index 4b92755..29c4390 100644 --- a/skills/instrument-llm-analytics/SKILL.md +++ b/skills/instrument-llm-analytics/SKILL.md @@ -55,41 +55,41 @@ STEP 6: Set up environment variables. ## Reference files -- `references/openai.md` - Openai llm analytics installation - docs -- `references/azure-openai.md` - Azure openai llm analytics installation - docs -- `references/anthropic.md` - Anthropic llm analytics installation - docs -- `references/google.md` - Google llm analytics installation - docs -- `references/cohere.md` - Cohere llm analytics installation - docs -- `references/mistral.md` - Mistral llm analytics installation - docs -- `references/perplexity.md` - Perplexity llm analytics installation - docs -- `references/deepseek.md` - Deepseek llm analytics installation - docs -- `references/groq.md` - Groq llm analytics installation - docs -- `references/together-ai.md` - Together ai llm analytics installation - docs -- `references/fireworks-ai.md` - Fireworks ai llm analytics installation - docs -- `references/xai.md` - Xai llm analytics installation - docs -- `references/cerebras.md` - Cerebras llm analytics installation - docs -- `references/hugging-face.md` - Hugging face llm analytics installation - docs -- `references/ollama.md` - Ollama llm analytics installation - docs -- `references/openrouter.md` - Openrouter llm analytics installation - docs -- `references/langchain.md` - Langchain llm analytics installation - docs -- `references/llamaindex.md` - Llamaindex llm analytics installation - docs -- `references/crewai.md` - Crewai llm analytics installation - docs -- `references/autogen.md` - Autogen llm analytics installation - docs -- `references/dspy.md` - Dspy llm analytics installation - docs -- `references/langgraph.md` - Langgraph llm analytics installation - docs -- `references/pydantic-ai.md` - Pydantic ai llm analytics installation - docs -- `references/vercel-ai.md` - Vercel ai SDK llm analytics installation - docs -- `references/litellm.md` - Litellm llm analytics installation - docs -- `references/instructor.md` - Instructor llm analytics installation - docs -- `references/semantic-kernel.md` - Semantic kernel llm analytics installation - docs -- `references/mirascope.md` - Mirascope llm analytics installation - docs -- `references/mastra.md` - Mastra llm analytics installation - docs -- `references/smolagents.md` - Smolagents llm analytics installation - docs -- `references/openai-agents.md` - Openai agents SDK llm analytics installation - docs -- `references/portkey.md` - Portkey llm analytics installation - docs -- `references/helicone.md` - Helicone llm analytics installation - docs -- `references/manual-capture.md` - Manual capture llm analytics installation - docs -- `references/basics.md` - Llm analytics basics - docs +- `references/openai.md` - Openai observability installation - docs +- `references/azure-openai.md` - Azure openai observability installation - docs +- `references/anthropic.md` - Anthropic ai observability installation - docs +- `references/google.md` - Google ai observability installation - docs +- `references/cohere.md` - Cohere ai observability installation - docs +- `references/mistral.md` - Mistral ai observability installation - docs +- `references/perplexity.md` - Perplexity ai observability installation - docs +- `references/deepseek.md` - Deepseek ai observability installation - docs +- `references/groq.md` - Groq ai observability installation - docs +- `references/together-ai.md` - Together ai observability installation - docs +- `references/fireworks-ai.md` - Fireworks ai observability installation - docs +- `references/xai.md` - Xai observability installation - docs +- `references/cerebras.md` - Cerebras ai observability installation - docs +- `references/hugging-face.md` - Hugging face ai observability installation - docs +- `references/ollama.md` - Ollama ai observability installation - docs +- `references/openrouter.md` - Openrouter ai observability installation - docs +- `references/langchain.md` - Langchain ai observability installation - docs +- `references/llamaindex.md` - Llamaindex ai observability installation - docs +- `references/crewai.md` - Crewai observability installation - docs +- `references/autogen.md` - Autogen ai observability installation - docs +- `references/dspy.md` - Dspy ai observability installation - docs +- `references/langgraph.md` - Langgraph ai observability installation - docs +- `references/pydantic-ai.md` - Pydantic ai observability installation - docs +- `references/vercel-ai.md` - Vercel ai SDK observability installation - docs +- `references/litellm.md` - Litellm ai observability installation - docs +- `references/instructor.md` - Instructor ai observability installation - docs +- `references/semantic-kernel.md` - Semantic kernel ai observability installation - docs +- `references/mirascope.md` - Mirascope ai observability installation - docs +- `references/mastra.md` - Mastra ai observability installation - docs +- `references/smolagents.md` - Smolagents ai observability installation - docs +- `references/openai-agents.md` - Openai agents SDK observability installation - docs +- `references/portkey.md` - Portkey ai observability installation - docs +- `references/helicone.md` - Helicone ai observability installation - docs +- `references/manual-capture.md` - Manual capture ai observability installation - docs +- `references/basics.md` - Ai observability basics - docs - `references/traces.md` - Traces - docs - `references/calculating-costs.md` - Calculating llm costs - docs diff --git a/skills/instrument-llm-analytics/references/anthropic.md b/skills/instrument-llm-analytics/references/anthropic.md index f41024f..581ccac 100644 --- a/skills/instrument-llm-analytics/references/anthropic.md +++ b/skills/instrument-llm-analytics/references/anthropic.md @@ -1,4 +1,4 @@ -# Anthropic LLM analytics installation - Docs +# Anthropic AI Observability installation - Docs 1. 1 @@ -139,7 +139,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -147,7 +147,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -159,15 +159,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/autogen.md b/skills/instrument-llm-analytics/references/autogen.md index 5a2a148..84310e2 100644 --- a/skills/instrument-llm-analytics/references/autogen.md +++ b/skills/instrument-llm-analytics/references/autogen.md @@ -1,4 +1,4 @@ -# AutoGen LLM analytics installation - Docs +# AutoGen AI Observability installation - Docs 1. 1 @@ -85,7 +85,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -93,7 +93,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -105,15 +105,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/azure-openai.md b/skills/instrument-llm-analytics/references/azure-openai.md index d09ebc5..4db1f7e 100644 --- a/skills/instrument-llm-analytics/references/azure-openai.md +++ b/skills/instrument-llm-analytics/references/azure-openai.md @@ -1,4 +1,4 @@ -# Azure OpenAI LLM analytics installation - Docs +# Azure OpenAI observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/basics.md b/skills/instrument-llm-analytics/references/basics.md index 113af5f..12b0ea6 100644 --- a/skills/instrument-llm-analytics/references/basics.md +++ b/skills/instrument-llm-analytics/references/basics.md @@ -1,10 +1,10 @@ -# LLM Analytics basics - Docs +# AI Observability basics - Docs -This page covers how your LLM calls become analytics in PostHog and defines key concepts for LLM Analytics. +This page covers how your LLM calls become analytics in PostHog and defines key concepts for AI Observability. ## How LLM calls become events -PostHog's LLM Analytics works by wrapping your existing LLM provider's SDK to capture requests and responses. Your API calls still go directly to your provider, but the wrapper extracts metadata from each call and sends it to PostHog as an event. +PostHog's AI Observability works by wrapping your existing LLM provider's SDK to capture requests and responses. Your API calls still go directly to your provider, but the wrapper extracts metadata from each call and sends it to PostHog as an event. graph LR Call\["LLM call"\] --> Wrapper\["Wrapped SDK"\] Wrapper --> Provider\["LLM provider API"\] Wrapper --> Event\["Event"\] Event --> PostHog\["PostHog"\] @@ -23,7 +23,7 @@ Generations are represented using the event name `$ai_generation`. Each generati PostHog automatically calculates costs by matching your model and provider against pricing data. We use OpenRouter's pricing as our primary source, with fallback to manually maintained pricing for additional models. -You can also [set custom pricing](/docs/llm-analytics/calculating-costs.md) if you have negotiated rates or use unsupported models. +You can also [set custom pricing](/docs/ai-observability/calculating-costs.md) if you have negotiated rates or use unsupported models. ### Message roles @@ -47,11 +47,11 @@ Here's a breakdown of this hierarchy: | Term | Definition | Example | | --- | --- | --- | -| [Session](/docs/llm-analytics/sessions.md) | Groups multiple traces together | A user's conversation thread | -| [Trace](/docs/llm-analytics/traces.md) | Contains generations and spans for a single request | One chatbot message and response | -| [Span](/docs/llm-analytics/spans.md) | Tracks an operation within a trace | A retrieval step or function call | -| [Generation](/docs/llm-analytics/generations.md) | An LLM call, tracked as $ai_generation events | Sending a prompt to Claude | -| [Embedding](/docs/llm-analytics/embeddings.md) | Converts text into vectors | Vectorizing documents for RAG | +| [Session](/docs/ai-observability/sessions.md) | Groups multiple traces together | A user's conversation thread | +| [Trace](/docs/ai-observability/traces.md) | Contains generations and spans for a single request | One chatbot message and response | +| [Span](/docs/ai-observability/spans.md) | Tracks an operation within a trace | A retrieval step or function call | +| [Generation](/docs/ai-observability/generations.md) | An LLM call, tracked as $ai_generation events | Sending a prompt to Claude | +| [Embedding](/docs/ai-observability/embeddings.md) | Converts text into vectors | Vectorizing documents for RAG | ### Community questions diff --git a/skills/instrument-llm-analytics/references/calculating-costs.md b/skills/instrument-llm-analytics/references/calculating-costs.md index 92e35bf..0b24036 100644 --- a/skills/instrument-llm-analytics/references/calculating-costs.md +++ b/skills/instrument-llm-analytics/references/calculating-costs.md @@ -51,7 +51,7 @@ You can override PostHog's automatic cost calculation by providing custom pricin ### Option 1: Custom price per token -If you know your pricing per token, you can set the following [custom properties](/docs/llm-analytics/custom-properties.md) when calling your LLM: +If you know your pricing per token, you can set the following [custom properties](/docs/ai-observability/custom-properties.md) when calling your LLM: - `$ai_input_token_price` (required): Price per input/prompt token - `$ai_output_token_price` (required): Price per output/completion token @@ -120,7 +120,7 @@ Both `$ai_input_token_price` and `$ai_output_token_price` must be provided for c ### Option 2: Pre-calculated costs -If you're [manually capturing](/docs/llm-analytics/installation/manual-capture.md) LLM events and have already calculated the total costs yourself, you can send them directly: +If you're [manually capturing](/docs/ai-observability/installation/manual-capture.md) LLM events and have already calculated the total costs yourself, you can send them directly: - `$ai_input_cost_usd`: Total cost for input/prompt tokens in USD - `$ai_output_cost_usd`: Total cost for output/completion tokens in USD diff --git a/skills/instrument-llm-analytics/references/cerebras.md b/skills/instrument-llm-analytics/references/cerebras.md index 6bb8b05..ceed9cc 100644 --- a/skills/instrument-llm-analytics/references/cerebras.md +++ b/skills/instrument-llm-analytics/references/cerebras.md @@ -1,4 +1,4 @@ -# Cerebras LLM analytics installation - Docs +# Cerebras AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/cohere.md b/skills/instrument-llm-analytics/references/cohere.md index c498f1e..aa09ff4 100644 --- a/skills/instrument-llm-analytics/references/cohere.md +++ b/skills/instrument-llm-analytics/references/cohere.md @@ -1,4 +1,4 @@ -# Cohere LLM analytics installation - Docs +# Cohere AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/crewai.md b/skills/instrument-llm-analytics/references/crewai.md index ee4d05b..243d4b9 100644 --- a/skills/instrument-llm-analytics/references/crewai.md +++ b/skills/instrument-llm-analytics/references/crewai.md @@ -1,4 +1,4 @@ -# CrewAI LLM analytics installation - Docs +# CrewAI observability installation - Docs 1. 1 @@ -88,7 +88,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 5. ## Verify traces and generations @@ -96,7 +96,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -108,15 +108,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/deepseek.md b/skills/instrument-llm-analytics/references/deepseek.md index df0cf32..286b617 100644 --- a/skills/instrument-llm-analytics/references/deepseek.md +++ b/skills/instrument-llm-analytics/references/deepseek.md @@ -1,4 +1,4 @@ -# DeepSeek LLM analytics installation - Docs +# DeepSeek AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/dspy.md b/skills/instrument-llm-analytics/references/dspy.md index eb6c157..49578ea 100644 --- a/skills/instrument-llm-analytics/references/dspy.md +++ b/skills/instrument-llm-analytics/references/dspy.md @@ -1,4 +1,4 @@ -# DSPy LLM analytics installation - Docs +# DSPy AI Observability installation - Docs 1. 1 @@ -86,7 +86,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 5. ## Verify traces and generations @@ -94,7 +94,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -106,15 +106,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/fireworks-ai.md b/skills/instrument-llm-analytics/references/fireworks-ai.md index 203d933..c0ec1b2 100644 --- a/skills/instrument-llm-analytics/references/fireworks-ai.md +++ b/skills/instrument-llm-analytics/references/fireworks-ai.md @@ -1,4 +1,4 @@ -# Fireworks AI LLM analytics installation - Docs +# Fireworks AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/google.md b/skills/instrument-llm-analytics/references/google.md index 68f46db..97c14a6 100644 --- a/skills/instrument-llm-analytics/references/google.md +++ b/skills/instrument-llm-analytics/references/google.md @@ -1,4 +1,4 @@ -# Google LLM analytics installation - Docs +# Google AI Observability installation - Docs 1. 1 @@ -135,7 +135,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. 4 @@ -171,7 +171,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -183,15 +183,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/groq.md b/skills/instrument-llm-analytics/references/groq.md index bc76fda..39dc918 100644 --- a/skills/instrument-llm-analytics/references/groq.md +++ b/skills/instrument-llm-analytics/references/groq.md @@ -1,4 +1,4 @@ -# Groq LLM analytics installation - Docs +# Groq AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/helicone.md b/skills/instrument-llm-analytics/references/helicone.md index c160878..43dca5a 100644 --- a/skills/instrument-llm-analytics/references/helicone.md +++ b/skills/instrument-llm-analytics/references/helicone.md @@ -1,4 +1,4 @@ -# Helicone LLM analytics installation - Docs +# Helicone AI Observability installation - Docs 1. 1 @@ -145,7 +145,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -153,7 +153,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -165,15 +165,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/hugging-face.md b/skills/instrument-llm-analytics/references/hugging-face.md index 81ea9b7..9d41a0d 100644 --- a/skills/instrument-llm-analytics/references/hugging-face.md +++ b/skills/instrument-llm-analytics/references/hugging-face.md @@ -1,4 +1,4 @@ -# Hugging Face LLM analytics installation - Docs +# Hugging Face AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/instructor.md b/skills/instrument-llm-analytics/references/instructor.md index 58c8012..3f17f4b 100644 --- a/skills/instrument-llm-analytics/references/instructor.md +++ b/skills/instrument-llm-analytics/references/instructor.md @@ -1,4 +1,4 @@ -# Instructor LLM analytics installation - Docs +# Instructor AI Observability installation - Docs 1. 1 @@ -147,7 +147,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -155,7 +155,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -167,15 +167,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/langchain.md b/skills/instrument-llm-analytics/references/langchain.md index 6c4e9a9..4a8e764 100644 --- a/skills/instrument-llm-analytics/references/langchain.md +++ b/skills/instrument-llm-analytics/references/langchain.md @@ -1,4 +1,4 @@ -# LangChain LLM analytics installation - Docs +# LangChain AI Observability installation - Docs 1. 1 @@ -139,7 +139,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | It also automatically creates a trace hierarchy based on how LangChain components are nested. @@ -149,7 +149,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -161,15 +161,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/langgraph.md b/skills/instrument-llm-analytics/references/langgraph.md index e180987..8362326 100644 --- a/skills/instrument-llm-analytics/references/langgraph.md +++ b/skills/instrument-llm-analytics/references/langgraph.md @@ -1,4 +1,4 @@ -# LangGraph LLM analytics installation - Docs +# LangGraph AI Observability installation - Docs 1. 1 @@ -152,7 +152,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -160,7 +160,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -172,15 +172,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/litellm.md b/skills/instrument-llm-analytics/references/litellm.md index 17312cf..fe68b10 100644 --- a/skills/instrument-llm-analytics/references/litellm.md +++ b/skills/instrument-llm-analytics/references/litellm.md @@ -1,4 +1,4 @@ -# LiteLLM LLM analytics installation - Docs +# LiteLLM AI Observability installation - Docs 1. 1 @@ -137,7 +137,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 5. 5 @@ -182,7 +182,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -194,15 +194,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/llamaindex.md b/skills/instrument-llm-analytics/references/llamaindex.md index 249214f..d2a55dd 100644 --- a/skills/instrument-llm-analytics/references/llamaindex.md +++ b/skills/instrument-llm-analytics/references/llamaindex.md @@ -1,4 +1,4 @@ -# LlamaIndex LLM analytics installation - Docs +# LlamaIndex AI Observability installation - Docs 1. 1 @@ -83,7 +83,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -91,7 +91,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -103,15 +103,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/manual-capture.md b/skills/instrument-llm-analytics/references/manual-capture.md index 55a32f2..9bb377a 100644 --- a/skills/instrument-llm-analytics/references/manual-capture.md +++ b/skills/instrument-llm-analytics/references/manual-capture.md @@ -1,4 +1,4 @@ -# Manual capture LLM analytics installation - Docs +# Manual capture AI Observability installation - Docs 1. 1 @@ -397,7 +397,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -409,15 +409,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/mastra.md b/skills/instrument-llm-analytics/references/mastra.md index 82bed04..6b1cfef 100644 --- a/skills/instrument-llm-analytics/references/mastra.md +++ b/skills/instrument-llm-analytics/references/mastra.md @@ -1,4 +1,4 @@ -# Mastra LLM analytics installation - Docs +# Mastra AI Observability installation - Docs 1. 1 @@ -93,7 +93,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -101,7 +101,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -113,15 +113,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/mirascope.md b/skills/instrument-llm-analytics/references/mirascope.md index e2149e7..275c41f 100644 --- a/skills/instrument-llm-analytics/references/mirascope.md +++ b/skills/instrument-llm-analytics/references/mirascope.md @@ -1,4 +1,4 @@ -# Mirascope LLM analytics installation - Docs +# Mirascope AI Observability installation - Docs 1. 1 @@ -78,7 +78,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -86,7 +86,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -98,15 +98,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/mistral.md b/skills/instrument-llm-analytics/references/mistral.md index 835ae1f..457b033 100644 --- a/skills/instrument-llm-analytics/references/mistral.md +++ b/skills/instrument-llm-analytics/references/mistral.md @@ -1,4 +1,4 @@ -# Mistral LLM analytics installation - Docs +# Mistral AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/ollama.md b/skills/instrument-llm-analytics/references/ollama.md index 5e7d847..5159af6 100644 --- a/skills/instrument-llm-analytics/references/ollama.md +++ b/skills/instrument-llm-analytics/references/ollama.md @@ -1,4 +1,4 @@ -# Ollama LLM analytics installation - Docs +# Ollama AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/openai-agents.md b/skills/instrument-llm-analytics/references/openai-agents.md index b1bcdf8..168a877 100644 --- a/skills/instrument-llm-analytics/references/openai-agents.md +++ b/skills/instrument-llm-analytics/references/openai-agents.md @@ -1,4 +1,4 @@ -# OpenAI Agents SDK LLM analytics installation - Docs +# OpenAI Agents SDK observability installation - Docs 1. 1 @@ -26,7 +26,7 @@ **Proxy note** - These SDKs **do not** proxy your calls. They only fire off an async call to PostHog in the background to send the data. You can also use LLM analytics with other SDKs or our API, but you will need to capture the data in the right format. See the schema in the [manual capture section](/docs/llm-analytics/installation/manual-capture.md) for more details. + These SDKs **do not** proxy your calls. They only fire off an async call to PostHog in the background to send the data. You can also use AI observability with other SDKs or our API, but you will need to capture the data in the right format. See the schema in the [manual capture section](/docs/ai-observability/installation/manual-capture.md) for more details. 3. 3 @@ -85,7 +85,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 5. 5 @@ -127,7 +127,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -139,15 +139,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/openai.md b/skills/instrument-llm-analytics/references/openai.md index 19603ab..0f4b857 100644 --- a/skills/instrument-llm-analytics/references/openai.md +++ b/skills/instrument-llm-analytics/references/openai.md @@ -1,4 +1,4 @@ -# OpenAI LLM analytics installation - Docs +# OpenAI observability installation - Docs 1. 1 @@ -162,7 +162,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -174,15 +174,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/openrouter.md b/skills/instrument-llm-analytics/references/openrouter.md index 6dba88c..d0bfcfc 100644 --- a/skills/instrument-llm-analytics/references/openrouter.md +++ b/skills/instrument-llm-analytics/references/openrouter.md @@ -1,4 +1,4 @@ -# OpenRouter LLM analytics installation - Docs +# OpenRouter AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/perplexity.md b/skills/instrument-llm-analytics/references/perplexity.md index 8153209..6f6d298 100644 --- a/skills/instrument-llm-analytics/references/perplexity.md +++ b/skills/instrument-llm-analytics/references/perplexity.md @@ -1,4 +1,4 @@ -# Perplexity LLM analytics installation - Docs +# Perplexity AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/portkey.md b/skills/instrument-llm-analytics/references/portkey.md index 04d7a8e..9cf4930 100644 --- a/skills/instrument-llm-analytics/references/portkey.md +++ b/skills/instrument-llm-analytics/references/portkey.md @@ -1,4 +1,4 @@ -# Portkey LLM analytics installation - Docs +# Portkey AI Observability installation - Docs 1. 1 @@ -147,7 +147,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -155,7 +155,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -167,15 +167,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/pydantic-ai.md b/skills/instrument-llm-analytics/references/pydantic-ai.md index a4b8190..cfddd6a 100644 --- a/skills/instrument-llm-analytics/references/pydantic-ai.md +++ b/skills/instrument-llm-analytics/references/pydantic-ai.md @@ -1,4 +1,4 @@ -# Pydantic AI LLM analytics installation - Docs +# Pydantic AI Observability installation - Docs 1. 1 @@ -80,7 +80,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -88,7 +88,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -100,15 +100,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/semantic-kernel.md b/skills/instrument-llm-analytics/references/semantic-kernel.md index aef8382..0b167ac 100644 --- a/skills/instrument-llm-analytics/references/semantic-kernel.md +++ b/skills/instrument-llm-analytics/references/semantic-kernel.md @@ -1,4 +1,4 @@ -# Semantic Kernel LLM analytics installation - Docs +# Semantic Kernel AI Observability installation - Docs 1. 1 @@ -86,7 +86,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -94,7 +94,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -106,15 +106,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/smolagents.md b/skills/instrument-llm-analytics/references/smolagents.md index a32db3e..8f18273 100644 --- a/skills/instrument-llm-analytics/references/smolagents.md +++ b/skills/instrument-llm-analytics/references/smolagents.md @@ -1,4 +1,4 @@ -# smolagents LLM analytics installation - Docs +# smolagents AI Observability installation - Docs 1. 1 @@ -78,7 +78,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -86,7 +86,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -98,15 +98,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/together-ai.md b/skills/instrument-llm-analytics/references/together-ai.md index 156d31e..f937359 100644 --- a/skills/instrument-llm-analytics/references/together-ai.md +++ b/skills/instrument-llm-analytics/references/together-ai.md @@ -1,4 +1,4 @@ -# Together AI LLM analytics installation - Docs +# Together AI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/traces.md b/skills/instrument-llm-analytics/references/traces.md index b311f2a..4c47ff4 100644 --- a/skills/instrument-llm-analytics/references/traces.md +++ b/skills/instrument-llm-analytics/references/traces.md @@ -1,13 +1,13 @@ # Traces - Docs -Traces are a collection of [generations](/docs/llm-analytics/generations.md) and [spans](/docs/llm-analytics/spans.md) that capture a full interaction between a user and an LLM. The [traces tab](https://app.posthog.com/llm-analytics/traces) lists them along with the properties autocaptured by PostHog like the person, total cost, total latency, and more. +Traces are a collection of [generations](/docs/ai-observability/generations.md) and [spans](/docs/ai-observability/spans.md) that capture a full interaction between a user and an LLM. The [traces tab](https://app.posthog.com/llm-analytics/traces) lists them along with the properties autocaptured by PostHog like the person, total cost, total latency, and more. ## Sessions vs Traces -- **Trace** (`$ai_trace_id`): Groups related generations and spans together. Required for all LLM analytics events. +- **Trace** (`$ai_trace_id`): Groups related generations and spans together. Required for all AI Observability events. - **Session** (`$ai_session_id`): Optional property that groups multiple traces together based on your chosen grouping strategy. -See the [Sessions](/docs/llm-analytics/sessions.md) documentation for more details on how to use `$ai_session_id`. +See the [Sessions](/docs/ai-observability/sessions.md) documentation for more details on how to use `$ai_session_id`. ## Trace timeline @@ -27,15 +27,15 @@ When viewing a trace, you can control how conversation messages are displayed us ## Tool calls -Traces display any [tools](/docs/llm-analytics/tools.md) called by the generations within them, shown as tags in the traces list. This makes it easy to see which conversations involved tool use at a glance. +Traces display any [tools](/docs/ai-observability/tools.md) called by the generations within them, shown as tags in the traces list. This makes it easy to see which conversations involved tool use at a glance. ## Sentiment classification -PostHog can classify the sentiment of user messages in a trace as negative, neutral, or positive. Sentiment is computed on-demand using a local model when you view a trace — no data is sent to third-party services. Each trace gets an overall sentiment label and score, with a per-generation and per-message breakdown. See [Sentiment classification](/docs/llm-analytics/sentiment.md) for more details. +PostHog can classify the sentiment of user messages in a trace as negative, neutral, or positive. Sentiment is computed on-demand using a local model when you view a trace — no data is sent to third-party services. Each trace gets an overall sentiment label and score, with a per-generation and per-message breakdown. See [Sentiment classification](/docs/ai-observability/sentiment.md) for more details. ## Search traces with PostHog AI -[PostHog AI](/docs/posthog-ai.md) can search and analyze your LLM traces using natural language. When you're on an [LLM Analytics page](https://app.posthog.com/llm-analytics), PostHog AI automatically switches to its LLM analytics mode, giving it access to tools for searching traces by date range, model, cost, error status, and other properties. +[PostHog AI](/docs/posthog-ai.md) can search and analyze your LLM traces using natural language. When you're on an [AI Observability page](https://app.posthog.com/llm-analytics), PostHog AI automatically switches to its AI Observability mode, giving it access to tools for searching traces by date range, model, cost, error status, and other properties. Example prompts you can try: diff --git a/skills/instrument-llm-analytics/references/vercel-ai.md b/skills/instrument-llm-analytics/references/vercel-ai.md index 7b592b4..1b15d52 100644 --- a/skills/instrument-llm-analytics/references/vercel-ai.md +++ b/skills/instrument-llm-analytics/references/vercel-ai.md @@ -1,4 +1,4 @@ -# Vercel AI SDK LLM analytics installation - Docs +# Vercel AI SDK observability installation - Docs 1. 1 @@ -78,7 +78,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -86,7 +86,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -98,15 +98,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-llm-analytics/references/xai.md b/skills/instrument-llm-analytics/references/xai.md index 16774bd..d0ecc91 100644 --- a/skills/instrument-llm-analytics/references/xai.md +++ b/skills/instrument-llm-analytics/references/xai.md @@ -1,4 +1,4 @@ -# xAI LLM analytics installation - Docs +# xAI Observability installation - Docs 1. 1 @@ -143,7 +143,7 @@ | $ai_output_choices | List of response choices from the LLM | | $ai_output_tokens | The number of tokens in the output (often found in response.usage) | | $ai_total_cost_usd | The total cost in USD (input + output) | - | [[...]](/docs/llm-analytics/generations.md#event-properties) | See [full list](/docs/llm-analytics/generations.md#event-properties) of properties | + | [[...]](/docs/ai-observability/generations.md#event-properties) | See [full list](/docs/ai-observability/generations.md#event-properties) of properties | 4. ## Verify traces and generations @@ -151,7 +151,7 @@ *Confirm LLM events are being sent to PostHog* - Let's make sure LLM events are being captured and sent to PostHog. Under **LLM analytics**, you should see rows of data appear in the **Traces** and **Generations** tabs. + Let's make sure LLM events are being captured and sent to PostHog. Under **AI Observability**, you should see rows of data appear in the **Traces** and **Generations** tabs. ![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syne_ecd0801880.png)![LLM generations in PostHog](https://res.cloudinary.com/dmukukwp6/image/upload/SCR_20250807_syjm_5baab36590.png) @@ -163,15 +163,15 @@ Recommended - Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform. + Now that you're capturing AI conversations, continue with the resources below to learn what else AI Observability enables within the PostHog platform. | Resource | Description | | --- | --- | - | [Basics](/docs/llm-analytics/basics.md) | Learn the basics of how LLM calls become events in PostHog. | - | [Generations](/docs/llm-analytics/generations.md) | Read about the $ai_generation event and its properties. | - | [Traces](/docs/llm-analytics/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | - | [Spans](/docs/llm-analytics/spans.md) | Review spans and their role in representing individual operations. | - | [Anaylze LLM performance](/docs/llm-analytics/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | + | [Basics](/docs/ai-observability/basics.md) | Learn the basics of how LLM calls become events in PostHog. | + | [Generations](/docs/ai-observability/generations.md) | Read about the $ai_generation event and its properties. | + | [Traces](/docs/ai-observability/traces.md) | Explore the trace hierarchy and how to use it to debug LLM calls. | + | [Spans](/docs/ai-observability/spans.md) | Review spans and their role in representing individual operations. | + | [Anaylze LLM performance](/docs/ai-observability/dashboard.md) | Learn how to create dashboards to analyze LLM performance. | ### Community questions diff --git a/skills/instrument-logs/references/search.md b/skills/instrument-logs/references/search.md index cda78b3..8f755d1 100644 --- a/skills/instrument-logs/references/search.md +++ b/skills/instrument-logs/references/search.md @@ -1,9 +1,10 @@ # Search logs - Docs -Filter logs from the filter bar at the top of the [logs page](https://us.posthog.com/logs). Pick a field, choose an operator, and enter a value. Add as many filters as you need — they're combined with AND. +Filter logs from the filter bar at the top of the [logs page](https://app.posthog.com/logs). Pick a field, choose an operator, and enter a value. Add as many filters as you need — they're combined with AND. -There are three kinds of fields you can filter on: +There are four kinds of fields you can filter on: +- **Logs** – top-level log properties: `severity_level`, `trace_id`, and `span_id` - **Message** – full-text search over the log body - **Resource attributes** – describe where the log came from, like `service.name`, `host.name`, or `k8s.container.name` - **Attributes** – custom key-value context attached to individual log events, like `user_id`, `endpoint`, or `status_code` @@ -20,6 +21,16 @@ To filter: For example, filter `service.name` equals `checkout-api` to scope to one service, then add `status_code` equals `500` to narrow to failed requests. +## Filter by severity, trace ID, and span ID + +The **Logs** group in the filter picker exposes three top-level fields. All three only support equals and not-equals operators. + +- **severity\_level** – filter by log severity using a dropdown. Available values: `trace`, `debug`, `info`, `warn`, `error`, `fatal`. +- **trace\_id** – filter logs by their OpenTelemetry trace correlation ID. Accepts hex or base64 format trace IDs +- **span\_id** – filter logs by their OpenTelemetry span ID. Accepts hex or base64, same as `trace_id`. + +For example, copy a `trace_id` from a trace URL and paste it into the filter to see every log emitted during that trace. + ## Full-text search on Message To search log bodies, pick the **Message** field from the filter bar. Message supports three operators, each with a negated variant for exclusion: diff --git a/skills/instrument-product-analytics/SKILL.md b/skills/instrument-product-analytics/SKILL.md index bdc82dd..083a2f0 100644 --- a/skills/instrument-product-analytics/SKILL.md +++ b/skills/instrument-product-analytics/SKILL.md @@ -12,7 +12,7 @@ metadata: Use this skill to add product analytics events (capture calls) that track meaningful user actions in new or changed code. Use it after implementing features or reviewing PRs to ensure key user behaviors are captured. If PostHog is not yet installed, this skill also covers initial SDK setup. Supports any framework or language. -Supported frameworks: Next.js, React Router, Nuxt, Vue, TanStack Start, SvelteKit, Astro, Angular, Django, Flask, FastAPI, Laravel, Ruby on Rails, Android, iOS, React Native, Expo, and more. +Supported frameworks: Next.js, React Router, Nuxt, Vue, TanStack Start, SvelteKit, Astro, Angular, Django, Flask, FastAPI, Laravel, PHP, Ruby on Rails, Android, iOS, React Native, Expo, and more. ## Instructions @@ -91,6 +91,7 @@ STEP 10: Verify and clean up. - `references/EXAMPLE-fastapi.md` - fastapi example project code - `references/EXAMPLE-python.md` - python example project code - `references/EXAMPLE-laravel.md` - laravel example project code +- `references/EXAMPLE-php.md` - php example project code - `references/EXAMPLE-ruby-on-rails.md` - ruby-on-rails example project code - `references/EXAMPLE-ruby.md` - ruby example project code - `references/EXAMPLE-android.md` - android example project code @@ -114,6 +115,7 @@ STEP 10: Verify and clean up. - `references/python.md` - Python - docs - `references/posthog-python.md` - PostHog python SDK - `references/laravel.md` - Laravel - docs +- `references/php.md` - Php - docs - `references/ruby-on-rails.md` - Ruby on rails - docs - `references/ruby.md` - Ruby - docs - `references/android.md` - Android - docs diff --git a/skills/instrument-product-analytics/references/EXAMPLE-php.md b/skills/instrument-product-analytics/references/EXAMPLE-php.md new file mode 100644 index 0000000..d530bd4 --- /dev/null +++ b/skills/instrument-product-analytics/references/EXAMPLE-php.md @@ -0,0 +1,527 @@ +# PostHog php Example Project + +Repository: https://github.com/PostHog/context-mill +Path: basics/php + +--- + +## README.md + +# PostHog PHP Example - CLI Todo App + +A simple command-line todo application built with plain PHP (no framework) demonstrating PostHog integration for CLIs, scripts, data pipelines, and non-web PHP applications. + +## Purpose + +This example serves as: +- **Verification** that the context-mill wizard works for plain PHP projects +- **Reference implementation** of PostHog best practices for non-framework PHP code +- **Working example** you can run and modify + +## Features Demonstrated + +- **SDK initialization** - Uses `PostHog::init(...)` once with environment-based configuration +- **Event tracking** - Captures user actions with `distinctId` and properties +- **User identification** - Associates properties with users via `PostHog::identify(...)` +- **Error tracking** - Enables automatic PHP error tracking and manually captures handled exceptions +- **Proper flushing** - Calls `PostHog::flush()` before CLI exit + +## Quick Start + +### 1. Install Dependencies + +```bash +composer install +``` + +### 2. Configure PostHog + +```bash +# Copy environment template +cp .env.example .env + +# Edit .env and add your PostHog API key +# POSTHOG_API_KEY=phc_your_api_key_here +# POSTHOG_HOST=https://us.i.posthog.com +``` + +### 3. Run the App + +```bash +# Add a todo +php todo.php add "Buy groceries" + +# List all todos +php todo.php list + +# Complete a todo +php todo.php complete 1 + +# Delete a todo +php todo.php delete 1 + +# Show statistics +php todo.php stats +``` + +## What Gets Tracked + +The app tracks these events in PostHog: + +| Event | Properties | Purpose | +|-------|-----------|---------| +| `todo_added` | `todo_id`, `todo_length`, `total_todos` | When user adds a new todo | +| `todos_viewed` | `total_todos`, `completed_todos` | When user lists todos | +| `todo_completed` | `todo_id`, `time_to_complete_hours` | When user completes a todo | +| `todo_deleted` | `todo_id`, `was_completed` | When user deletes a todo | +| `stats_viewed` | `total_todos`, `completed_todos`, `pending_todos` | When user views stats | +| `$exception` | exception details and command context | When handled errors occur | + +## Code Structure + +``` +basics/php/ +├── todo.php # Main CLI application +├── composer.json # PHP dependencies +├── .env.example # Environment variable template +├── .gitignore # Git ignore rules +└── README.md # This file +``` + +## Key Implementation Patterns + +### 1. Initialize Once + +```php +PostHog::init($apiKey, [ + 'host' => $host, + 'error_tracking' => [ + 'enabled' => true, + ], +]); +``` + +### 2. Event Tracking Pattern + +```php +PostHog::capture([ + 'distinctId' => 'user_123', + 'event' => 'event_name', + 'properties' => ['key' => 'value'], +]); +``` + +### 3. Identifying Users + +```php +PostHog::identify([ + 'distinctId' => 'user_123', + 'properties' => ['app_language' => 'php'], +]); +``` + +### 4. Exception Tracking + +```php +try { + riskyOperation(); +} catch (Throwable $e) { + PostHog::captureException($e, 'user_123', [ + 'command' => 'example_command', + ]); +} +``` + +### 5. Flush Before CLI Exit + +```php +PostHog::flush(); +``` + +## Running Without PostHog + +The app works fine without PostHog configured - it simply won't track analytics. You'll see a warning message but the app continues to function normally. + +## Next Steps + +- Modify `todo.php` to experiment with PostHog tracking +- Add new commands and track their usage +- Explore feature flags: `PostHog::isFeatureEnabled('flag-name', 'user_id')` +- Check your PostHog dashboard to see tracked events + +## Learn More + +- [PostHog PHP SDK Documentation](https://posthog.com/docs/libraries/php) +- [PostHog PHP Error Tracking](https://posthog.com/docs/error-tracking/installation/php) +- [PostHog Product Analytics PHP installation](https://posthog.com/docs/product-analytics/installation/php) + +--- + +## .env.example + +```example +# PostHog configuration +POSTHOG_API_KEY=phc_your_api_key_here +POSTHOG_HOST=https://us.i.posthog.com + +``` + +--- + +## todo.php + +```php + getenv('POSTHOG_HOST') ?: 'https://us.i.posthog.com', + 'error_tracking' => [ + 'enabled' => true, + 'context_provider' => static function (array $payload): array { + return [ + 'distinctId' => getUserId(), + 'properties' => [ + 'app' => 'php_todo_cli', + 'runtime' => PHP_VERSION, + '$exception_source' => $payload['source'] ?? null, + ], + ]; + }, + ], + ]); + + return true; +} + +function getUserId(): string +{ + $path = dataFilePath(); + if (file_exists($path)) { + $data = json_decode((string) file_get_contents($path), true); + if (is_array($data) && isset($data['user_id'])) { + return (string) $data['user_id']; + } + } + + return 'user_' . bin2hex(random_bytes(4)); +} + +function loadTodos(): array +{ + $path = dataFilePath(); + if (!file_exists($path)) { + return ['user_id' => getUserId(), 'todos' => []]; + } + + $data = json_decode((string) file_get_contents($path), true); + if (!is_array($data)) { + return ['user_id' => getUserId(), 'todos' => []]; + } + + $data['todos'] = $data['todos'] ?? []; + $data['user_id'] = $data['user_id'] ?? getUserId(); + return $data; +} + +function saveTodos(array $data): void +{ + file_put_contents(dataFilePath(), json_encode($data, JSON_PRETTY_PRINT) . PHP_EOL); +} + +function identifyUser(bool $posthogEnabled): void +{ + if (!$posthogEnabled) { + return; + } + + PostHog::identify([ + 'distinctId' => getUserId(), + 'properties' => [ + 'app_language' => 'php', + 'app_type' => 'cli', + ], + ]); +} + +function trackEvent(bool $posthogEnabled, string $eventName, array $properties = []): void +{ + if (!$posthogEnabled) { + return; + } + + PostHog::capture([ + 'distinctId' => getUserId(), + 'event' => $eventName, + 'properties' => $properties, + ]); +} + +function cmdAdd(string $text, bool $posthogEnabled): void +{ + $data = loadTodos(); + + $todo = [ + 'id' => count($data['todos']) + 1, + 'text' => $text, + 'completed' => false, + 'created_at' => date(DATE_ATOM), + ]; + + $data['todos'][] = $todo; + saveTodos($data); + + echo "Added todo #{$todo['id']}: {$todo['text']}\n"; + + trackEvent($posthogEnabled, 'todo_added', [ + 'todo_id' => $todo['id'], + 'todo_length' => strlen($todo['text']), + 'total_todos' => count($data['todos']), + ]); +} + +function cmdList(bool $posthogEnabled): void +{ + $data = loadTodos(); + + if (count($data['todos']) === 0) { + echo "No todos yet! Add one with: php todo.php add 'Your task'\n"; + return; + } + + echo "\nYour Todos (" . count($data['todos']) . " total):\n\n"; + + foreach ($data['todos'] as $todo) { + $status = $todo['completed'] ? 'X' : ' '; + echo " [{$status}] #{$todo['id']}: {$todo['text']}\n"; + } + + echo "\n"; + + trackEvent($posthogEnabled, 'todos_viewed', [ + 'total_todos' => count($data['todos']), + 'completed_todos' => count(array_filter($data['todos'], static fn (array $todo): bool => (bool) $todo['completed'])), + ]); +} + +function cmdComplete(int $id, bool $posthogEnabled): void +{ + $data = loadTodos(); + + foreach ($data['todos'] as &$todo) { + if ((int) $todo['id'] !== $id) { + continue; + } + + if ($todo['completed']) { + echo "Todo #{$id} is already completed\n"; + return; + } + + $todo['completed'] = true; + $todo['completed_at'] = date(DATE_ATOM); + saveTodos($data); + + echo "Completed todo #{$todo['id']}: {$todo['text']}\n"; + + $timeToComplete = (strtotime($todo['completed_at']) - strtotime($todo['created_at'])) / 3600; + trackEvent($posthogEnabled, 'todo_completed', [ + 'todo_id' => $todo['id'], + 'time_to_complete_hours' => $timeToComplete, + ]); + return; + } + + echo "ERROR: Todo #{$id} not found\n"; +} + +function cmdDelete(int $id, bool $posthogEnabled): void +{ + $data = loadTodos(); + + foreach ($data['todos'] as $index => $todo) { + if ((int) $todo['id'] !== $id) { + continue; + } + + unset($data['todos'][$index]); + $data['todos'] = array_values($data['todos']); + saveTodos($data); + + echo "Deleted todo #{$id}\n"; + + trackEvent($posthogEnabled, 'todo_deleted', [ + 'todo_id' => $todo['id'], + 'was_completed' => $todo['completed'], + ]); + return; + } + + echo "ERROR: Todo #{$id} not found\n"; +} + +function cmdStats(bool $posthogEnabled): void +{ + $data = loadTodos(); + + $total = count($data['todos']); + $completed = count(array_filter($data['todos'], static fn (array $todo): bool => (bool) $todo['completed'])); + $pending = $total - $completed; + $rate = $total > 0 ? number_format($completed / $total * 100, 1) : '0.0'; + + echo "\nStats:\n\n"; + echo " Total todos: {$total}\n"; + echo " Completed: {$completed}\n"; + echo " Pending: {$pending}\n"; + echo " Completion rate: {$rate}%\n\n"; + + trackEvent($posthogEnabled, 'stats_viewed', [ + 'total_todos' => $total, + 'completed_todos' => $completed, + 'pending_todos' => $pending, + ]); +} + +function printUsage(): void +{ + echo << Mark todo as completed + php todo.php delete Delete a todo + php todo.php stats Show statistics +USAGE; +} + +$posthogEnabled = false; + +try { + $posthogEnabled = initializePostHog(); + identifyUser($posthogEnabled); + + $command = $argv[1] ?? null; + if (!$command) { + printUsage(); + exit(0); + } + + switch ($command) { + case 'add': + $text = $argv[2] ?? null; + if (!$text) { + echo "ERROR: Please provide todo text\n"; + echo "Usage: php todo.php add \"Your task\"\n"; + exit(1); + } + cmdAdd($text, $posthogEnabled); + break; + + case 'list': + cmdList($posthogEnabled); + break; + + case 'complete': + $id = (int) ($argv[2] ?? 0); + if ($id <= 0) { + echo "ERROR: Please provide a valid todo ID\n"; + echo "Usage: php todo.php complete \n"; + exit(1); + } + cmdComplete($id, $posthogEnabled); + break; + + case 'delete': + $id = (int) ($argv[2] ?? 0); + if ($id <= 0) { + echo "ERROR: Please provide a valid todo ID\n"; + echo "Usage: php todo.php delete \n"; + exit(1); + } + cmdDelete($id, $posthogEnabled); + break; + + case 'stats': + cmdStats($posthogEnabled); + break; + + default: + echo "ERROR: Unknown command '{$command}'\n"; + printUsage(); + exit(1); + } +} catch (Throwable $e) { + echo "ERROR: {$e->getMessage()}\n"; + + if ($posthogEnabled) { + PostHog::captureException($e, getUserId(), [ + 'command' => $argv[1] ?? null, + 'app' => 'php_todo_cli', + ]); + } + + exit(1); +} finally { + if ($posthogEnabled) { + PostHog::flush(); + } +} + +``` + +--- + diff --git a/skills/instrument-product-analytics/references/django.md b/skills/instrument-product-analytics/references/django.md index dc8a1af..af5d8ba 100644 --- a/skills/instrument-product-analytics/references/django.md +++ b/skills/instrument-product-analytics/references/django.md @@ -1,6 +1,6 @@ # Django - Docs -PostHog makes it easy to get data about traffic and usage of your Django app. Integrating PostHog enables analytics, custom events capture, feature flags, and more. +PostHog makes it easy to get data about traffic and usage of your Django app. Integrating PostHog enables analytics, custom events capture, feature flags, error tracking, and more. This guide walks you through integrating PostHog into your Django app using the [Python SDK](/docs/libraries/python.md). @@ -18,7 +18,9 @@ Or, to integrate manually, continue with the rest of this guide. To start, run `pip install posthog` to install PostHog’s Python SDK. -Then, set the PostHog API key and host in your `AppConfig` in your `your_app/apps.py` so that's it's available everywhere: +> **Note:** Version `7.x` of the PostHog Python SDK requires Python 3.10 or higher. + +Then, configure PostHog in your app config so it's initialized when Django starts: your\_app/apps.py @@ -28,15 +30,13 @@ PostHog AI from django.apps import AppConfig import posthog class YourAppConfig(AppConfig): - name = "your_app_name" + name = 'your_app_name' def ready(self): posthog.api_key = '' posthog.host = 'https://us.i.posthog.com' ``` -You can find your project token and instance address in [your project settings](https://us.posthog.com/project/settings). - -Next, if you haven't done so already, make sure you add your `AppConfig` to your `settings.py` under `INSTALLED_APPS`: +Next, if you haven't done so already, add your `AppConfig` to `INSTALLED_APPS` in `settings.py`: settings.py @@ -44,12 +44,14 @@ PostHog AI ```python INSTALLED_APPS = [ - # other apps - 'your_app_name.apps.MyAppConfig', # Add your app config + # ... other apps + 'your_app_name.apps.YourAppConfig', ] ``` -Lastly, to access PostHog in any file, simply `import posthog` and call the method you'd like. For example, to capture an event: +You can find your project token and instance address in [your project settings](https://app.posthog.com/project/settings). + +To capture events from any file, import `posthog` and call the method you need. For example: Python @@ -57,12 +59,18 @@ PostHog AI ```python import posthog +from posthog import identify_context def some_request(request): with posthog.new_context(): - posthog.identify_context(request.user.id) + # Django includes request.user for anonymous visitors too. Only identify + # the context when the visitor is logged in. + if request.user.is_authenticated: + identify_context(str(request.user.pk)) posthog.capture('event_name') ``` +Events captured without a context or explicit `distinct_id` are sent as [anonymous events](/docs/data/anonymous-vs-identified-events.md) with an auto-generated `distinct_id`. See the [Python SDK docs](/docs/libraries/python.md#person-profiles-and-properties) for more details. + ## Identifying users > **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). @@ -71,11 +79,11 @@ def some_request(request): ## Django contexts middleware -The Python SDK provides a Django middleware that automatically wraps all requests with a [context](/docs/libraries/python.md#contexts). This middleware extracts session and user information from request headers and tags all events captured during the request with relevant metadata. +The Python SDK provides a Django middleware that automatically wraps all requests with a [context](/docs/libraries/python.md#contexts). This middleware extracts session and user information from each request and tags all events captured during that request with relevant metadata. ### Basic setup -Add the middleware to your Django settings: +Add the middleware to your Django settings. If your app uses Django authentication, place it after `django.contrib.auth.middleware.AuthenticationMiddleware` so the middleware can use the authenticated Django user as a distinct ID fallback and capture the user's email. Python @@ -89,14 +97,22 @@ MIDDLEWARE = [ ] ``` +The middleware uses the globally configured `posthog` client by default, so you don't need to create or pass it a separate client instance. + The middleware automatically extracts and uses: - **Session ID** from the `X-POSTHOG-SESSION-ID` header, if present -- **Distinct ID** from the `X-POSTHOG-DISTINCT-ID` header, if present +- **Distinct ID** from the `X-POSTHOG-DISTINCT-ID` header, if present, falling back to the authenticated Django user's `pk` (Django's primary-key alias, which works with custom user models) +- **User email** from the authenticated Django user's `email` as `email` - **Current URL** as `$current_url` - **Request method** as `$request_method` +- **Request path** as `$request_path` +- **Forwarded IP address** from `X-Forwarded-For` as `$ip` +- **User agent** from `User-Agent` as `$user_agent` + +The session and distinct ID headers are sanitized before use. Empty values are ignored, control characters are removed, values are trimmed, and values are capped at 1000 characters. -All events captured during the request (including exceptions) will include these properties and be associated with the extracted session and distinct ID. +All events captured during the request (including exceptions) include these properties and are associated with the extracted session and distinct ID. If you are using PostHog on your frontend, the JavaScript Web SDK will add the session and distinct ID headers automatically if you enable tracing headers. @@ -112,7 +128,9 @@ posthog.init('', { ### Exception capture -By default, the middleware captures exceptions and sends them to PostHog's error tracking. Disable this by setting: +By default, the middleware captures exceptions and sends them to PostHog's error tracking using the globally configured `posthog` client. This includes Django view exceptions that Django converts into error responses. + +Disable this by setting: Python @@ -137,7 +155,8 @@ def add_user_tags(request): # type: (HttpRequest) -> Dict[str, Any] tags = {} if hasattr(request, 'user') and request.user.is_authenticated: - tags['user_id'] = request.user.id + # Use pk instead of id so this works with custom User primary keys. + tags['user_id'] = str(request.user.pk) tags['email'] = request.user.email return tags POSTHOG_MW_EXTRA_TAGS = add_user_tags @@ -196,7 +215,8 @@ def add_request_context(request): tags = {} if hasattr(request, 'user') and request.user.is_authenticated: tags['user_type'] = 'authenticated' - tags['user_id'] = str(request.user.id) + # Use pk instead of id so this works with custom User primary keys. + tags['user_id'] = str(request.user.pk) else: tags['user_type'] = 'anonymous' # Add request info @@ -217,7 +237,9 @@ POSTHOG_MW_TAG_MAP = clean_tags POSTHOG_MW_CAPTURE_EXCEPTIONS = True ``` -All events captured within the request context automatically include the configured tags and are associated with the session and user identified from the request headers. +All events captured within the request context automatically include the configured tags and are associated with the session and user identified from the request headers or Django authentication. + +The middleware supports both sync (WSGI) and async (ASGI) Django applications. In async mode, it uses Django's `request.auser()` API when available to avoid synchronous user access. ## Next steps diff --git a/skills/instrument-product-analytics/references/flask.md b/skills/instrument-product-analytics/references/flask.md index f631b4b..e8ec7bf 100644 --- a/skills/instrument-product-analytics/references/flask.md +++ b/skills/instrument-product-analytics/references/flask.md @@ -1,6 +1,6 @@ # Flask - Docs -PostHog makes it easy to get data about traffic and usage of your Flask app. Integrating PostHog enables analytics, custom events capture, feature flags, and more. +PostHog makes it easy to get data about traffic and usage of your Flask app. Integrating PostHog enables analytics, custom events capture, feature flags, error tracking, and more. This guide walks you through integrating PostHog into your Flask app using the [Python SDK](/docs/libraries/python.md). @@ -8,6 +8,8 @@ This guide walks you through integrating PostHog into your Flask app using the [ To start, run `pip install posthog` to install PostHog’s Python SDK. +> **Note:** Version `7.x` of the PostHog Python SDK requires Python 3.10 or higher. + Then, initialize PostHog where you'd like to use it. For example, here's how to capture an event in a simple route: app.py @@ -15,23 +17,23 @@ app.py PostHog AI ```python -package main -from flask import Flask, render_template, request, redirect, session, url_for +from flask import Flask from posthog import Posthog +app = Flask(__name__) posthog = Posthog( - '', - host='https://us.i.posthog.com' + '', + host='https://us.i.posthog.com', ) @app.route('/api/dashboard', methods=['POST']) def api_dashboard(): posthog.capture( - 'dashboard_api_called' + 'dashboard_api_called', distinct_id='distinct_id_of_your_user', ) return '', 204 ``` -You can find your project token and instance address in [your project settings](https://us.posthog.com/project/settings). +You can find your project token and instance address in [your project settings](https://app.posthog.com/project/settings). ## Identifying users @@ -39,6 +41,35 @@ You can find your project token and instance address in [your project settings]( > > See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. +## Request contexts + +Use [contexts](/docs/libraries/python.md#contexts) to share identity, session IDs, and tags across multiple captures during a request: + +Python + +PostHog AI + +```python +from flask import request, session +from posthog import identify_context, set_context_session, tag +@app.route('/api/dashboard', methods=['POST']) +def api_dashboard(): + with posthog.new_context(): + distinct_id = request.headers.get('X-POSTHOG-DISTINCT-ID') or session.get('user_id') + if distinct_id: + identify_context(str(distinct_id)) + session_id = request.headers.get('X-POSTHOG-SESSION-ID') + if session_id: + set_context_session(session_id) + tag('$current_url', request.url) + tag('$request_method', request.method) + tag('$request_path', request.path) + posthog.capture('dashboard_api_called') + return '', 204 +``` + +Events captured without a context or explicit `distinct_id` are sent as [anonymous events](/docs/data/anonymous-vs-identified-events.md) with an auto-generated `distinct_id`. See the [Python SDK docs](/docs/libraries/python.md#person-profiles-and-properties) for more details. + ## Error tracking Flask has built-in error handlers. This means PostHog’s default exception autocapture won’t work and we need to manually capture errors instead using `capture_exception()`: @@ -50,6 +81,7 @@ PostHog AI ```python from flask import Flask, jsonify from posthog import Posthog +app = Flask(__name__) posthog = Posthog('', host='https://us.i.posthog.com') @app.errorhandler(Exception) def handle_exception(e): diff --git a/skills/instrument-product-analytics/references/laravel.md b/skills/instrument-product-analytics/references/laravel.md index 5038330..022f900 100644 --- a/skills/instrument-product-analytics/references/laravel.md +++ b/skills/instrument-product-analytics/references/laravel.md @@ -1,14 +1,34 @@ # Laravel - Docs -PostHog makes it easy to get data about traffic and usage of your Laravel app. Integrating PostHog enables analytics, custom events capture, feature flags, and more. - -This guide walks you through integrating PostHog into your Laravel app using the [PHP SDK](/docs/libraries/php.md). +PostHog integrates with Laravel through the [PostHog PHP SDK](/docs/libraries/php.md). This page covers Laravel-specific setup. For SDK features such as event capture, identifying users, feature flags, group analytics, and configuration options, see the [PHP SDK docs](/docs/libraries/php.md). ## Installation -First, ensure [Composer](https://getcomposer.org/) is installed. Then run `composer require posthog/posthog-php` to install PostHog’s PHP SDK. +Install the PHP SDK as described in the [PHP installation guide](/docs/libraries/php.md#installation), then add your project token and host to `.env`: + +.env + +PostHog AI + +```bash +POSTHOG_API_KEY= +POSTHOG_HOST=https://us.i.posthog.com +``` + +Add PostHog to Laravel's services config: -Next, initialize PostHog in the `boot` method of `app/Providers/AppServiceProvider.php`: +config/services.php + +PostHog AI + +```php +'posthog' => [ + 'api_key' => env('POSTHOG_API_KEY'), + 'host' => env('POSTHOG_HOST', 'https://us.i.posthog.com'), +], +``` + +Initialize PostHog in the `boot` method of `app/Providers/AppServiceProvider.php`: app/Providers/AppServiceProvider.php @@ -23,48 +43,103 @@ class AppServiceProvider extends ServiceProvider { public function boot(): void { + if (! config('services.posthog.api_key')) { + return; + } PostHog::init( - '', + config('services.posthog.api_key'), [ - 'host' => 'https://us.i.posthog.com' + 'host' => config('services.posthog.host'), ] ); } } ``` -You can find your project token and instance address in [your project settings](https://us.posthog.com/project/settings). +## Request context middleware -## Usage +Client SDKs such as [PostHog JS](/docs/libraries/js.md) can send tracing headers to your Laravel backend. The PHP SDK can read `X-PostHog-Distinct-Id` and `X-PostHog-Session-Id` headers and apply them to events captured during the request. Add middleware like this: -To access your PostHog client anywhere in your app, import `use PostHog\PostHog;` and call `PostHog::method_name`. For example, below is how to capture an event in a simple route: - -routes/web.php +app/Http/Middleware/PostHogRequestContext.php PostHog AI ```php 'distinct_id_of_your_user', - 'event' => 'route_called' - ]); - return view('welcome'); -}); +use Symfony\Component\HttpFoundation\Response; +final class PostHogRequestContext +{ + public function handle(Request $request, Closure $next): Response + { + if (! config('services.posthog.api_key')) { + return $next($request); + } + $context = PostHog::contextFromHeaders($request->headers->all()); + $context['properties'] = array_merge( + $context['properties'] ?? [], + array_filter([ + '$current_url' => $request->fullUrl(), + '$request_method' => $request->method(), + '$request_path' => $request->getPathInfo(), + '$user_agent' => $request->userAgent(), + '$ip' => $request->ip(), + ], static fn ($value): bool => $value !== null && $value !== '') + ); + return PostHog::withContext( + $context, + static fn (): Response => $next($request), + ['fresh' => true] + ); + } +} ``` -## Next steps +Register this middleware using your Laravel version's normal middleware registration. + +## Error tracking in Laravel + +The PHP SDK supports [error tracking](/docs/libraries/php.md#error-tracking), but Laravel handles most request exceptions before they become uncaught PHP exceptions. Capture Laravel-reported exceptions explicitly. + +In Laravel 11 and later, add a report callback in `bootstrap/app.php`: + +bootstrap/app.php + +PostHog AI + +```php +use Illuminate\Foundation\Configuration\Exceptions; +use PostHog\PostHog; +use Throwable; +->withExceptions(function (Exceptions $exceptions): void { + $exceptions->report(function (Throwable $e): void { + if (! config('services.posthog.api_key')) { + return; + } + PostHog::captureException( + $e, + auth()->id() !== null ? (string) auth()->id() : null, + [ + '$current_url' => request()->fullUrl(), + '$request_method' => request()->method(), + ] + ); + }); +}) +``` + +For older Laravel versions, call `PostHog::captureException()` from your exception handler's `report` method. -For any technical questions for how to integrate specific PostHog features into Laravel (such as analytics, feature flags, A/B testing, etc.), have a look at our [PHP SDK docs](/docs/libraries/php.md). +## Long-running processes -Alternatively, the following tutorials can help you get started: +In normal PHP request lifecycles, queued events flush when the client is destroyed. In long-running Laravel processes such as queue workers, Horizon, or Octane, call `PostHog::flush()` after capturing important events or at the end of a job/request. + +## Next steps -- [How to set up analytics in Laravel](/tutorials/laravel-analytics.md) -- [How to set up feature flags in Laravel](/tutorials/laravel-feature-flags.md) -- [How to set up A/B tests in Laravel](/tutorials/laravel-ab-tests.md) +See the [PHP SDK docs](/docs/libraries/php.md) for usage examples and the full API reference. ### Community questions diff --git a/skills/instrument-product-analytics/references/php.md b/skills/instrument-product-analytics/references/php.md new file mode 100644 index 0000000..1c2e1fe --- /dev/null +++ b/skills/instrument-product-analytics/references/php.md @@ -0,0 +1,629 @@ +# PHP - Docs + +This is an optional library you can install if you're working with PHP. It uses an internal queue to batch requests, flushes at the end of the request, and optionally does so in an async manner. + +## Installation + +Install the package with Composer: + +Terminal + +PostHog AI + +```bash +composer require posthog/posthog-php +``` + +In your app, set your project token before making any calls. + +PHP + +PostHog AI + +```php +PostHog\PostHog::init("", + ['host' => 'https://us.i.posthog.com'] +); +``` + +> **Note:** As a rule of thumb, we do not recommend having API keys or tokens in plaintext. Setting them as environment variables is best. The PHP SDK reads `POSTHOG_API_KEY` and `POSTHOG_HOST` when you omit the project token or host. + +You can find your project token and instance address in the [project settings](https://app.posthog.com/project/settings) page in PostHog. + +## Identifying users + +> **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). +> +> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. + +## Capturing events + +You can send custom events using `capture`: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id_of_the_user', + 'event' => 'user_signed_up' +]); +``` + +> **Tip:** We recommend using a `[object] [verb]` format for your event names, where `[object]` is the entity that the behavior relates to, and `[verb]` is the behavior itself. For example, `project created`, `user signed up`, or `invite sent`. + +### Setting event properties + +Optionally, you can include additional information with the event by including a [properties](/docs/data/events.md#event-properties) object: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id_of_the_user', + 'event' => 'user_signed_up', + 'properties' => [ + 'login_type' => 'email', + 'is_free_trial' => 'true' + ] +]); +``` + +### Sending page views + +If you're aiming for a backend-only implementation of PostHog and won't be capturing events from your frontend, you can send `pageviews` from your backend like so: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id_of_the_user', + 'event' => '$pageview', + 'properties' => [ + '$current_url' => 'https://example.com' + ] +]); +``` + +## Person profiles and properties + +The PHP SDK captures identified events by default. These create [person profiles](/docs/data/persons.md). To set [person properties](/docs/data/user-properties.md), call `identify` with the user's distinct ID and properties: + +PHP + +PostHog AI + +```php +PostHog::identify([ + 'distinctId' => 'distinct_id', + 'properties' => [ + 'email' => 'max@example.com', + 'name' => 'Max Hedgehog', + ], +]); +``` + +You can also include person properties when capturing an event: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id', + 'event' => 'event_name', + 'properties' => [ + '$set' => [ + 'name' => 'Max Hedgehog' + ], + '$set_once' => [ + 'initial_url' => '/blog' + ] + ] +]); +``` + +For more details on the difference between `$set` and `$set_once`, see our [person properties docs](/docs/data/user-properties.md#what-is-the-difference-between-set-and-set_once). + +To capture [anonymous events](/docs/data/anonymous-vs-identified-events.md) without person profiles, set the event's `$process_person_profile` property to `false`: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id', + 'event' => 'event_name', + 'properties' => [ + '$process_person_profile' => false + ] +]); +``` + +## Alias + +Sometimes, you want to assign multiple distinct IDs to a single user. This is helpful when your primary distinct ID is inaccessible. For example, if a distinct ID used on the frontend is not available in your backend. + +In this case, you can use `alias` to assign another distinct ID to the same user. + +PHP + +PostHog AI + +```php +PostHog::alias([ + 'distinctId' => 'distinct_id', + 'alias' => 'alias_id' +]); +``` + +We strongly recommend reading our docs on [alias](/docs/data/identify.md#alias-assigning-multiple-distinct-ids-to-the-same-user) to best understand how to correctly use this method. + +## Feature flags + +PostHog's [feature flags](/docs/feature-flags.md) enable you to safely deploy and roll back new features as well as target specific users and groups with them. + +There are two steps to implement feature flags in PHP: + +### Step 1: Evaluate flags once + +Call `PostHog::evaluateFlags()` once for the user, then read values from the returned snapshot. + +#### Boolean feature flags + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags('distinct_id_of_your_user'); +if ($flags->isEnabled('flag-key')) { + // Do something differently for this user + // Optional: fetch the payload + $matchedFlagPayload = $flags->getFlagPayload('flag-key'); +} +``` + +#### Multivariate feature flags + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags('distinct_id_of_your_user'); +$enabledVariant = $flags->getFlag('flag-key'); +if ($enabledVariant === 'variant-key') { // replace 'variant-key' with the key of your variant + // Do something differently for this user + // Optional: fetch the payload + $matchedFlagPayload = $flags->getFlagPayload('flag-key'); +} +``` + +`$flags->getFlag()` returns the variant string for multivariate flags, `true` for enabled boolean flags, `false` for disabled flags, and `null` when the flag wasn't returned by the evaluation. + +You can also call `$flags->getKeys()` to list the evaluated flag keys, or `$flags->getEventProperties()` to get the `$feature/` and `$active_feature_flags` properties that would be attached to a captured event. + +> **Note:** `PostHog::isFeatureEnabled()`, `PostHog::getFeatureFlag()`, `PostHog::getFeatureFlagPayload()`, and `capture(['send_feature_flags' => true])` still work during the migration period, but they're deprecated. Prefer `evaluateFlags()` for new code. + +### Step 2: Include feature flag information when capturing events + +If you want use your feature flag to breakdown or filter events in your [insights](/docs/product-analytics/insights.md), you'll need to include feature flag information in those events. This ensures that the feature flag value is attributed correctly to the event. + +> **Note:** This step is only required for events captured using our server-side SDKs or [API](/docs/api.md). + +There are two methods you can use to include feature flag information in your events: + +#### Method 1: Pass the evaluated flags snapshot to `capture()` + +Pass the same `flags` object that you used for branching. This attaches the exact flag values from that evaluation and doesn't make another `/flags` request. + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags('distinct_id_of_your_user'); +if ($flags->isEnabled('flag-key')) { + // Do something differently for this user +} +PostHog::capture([ + 'distinctId' => 'distinct_id_of_your_user', + 'event' => 'event_name', + 'flags' => $flags, +]); +``` + +By default, this attaches every flag in the snapshot using `$feature/` properties and `$active_feature_flags`. + +To reduce event property bloat, pass a filtered snapshot: + +PHP + +PostHog AI + +```php +// Attach only flags accessed with isEnabled() or getFlag() before this call +PostHog::capture([ + 'distinctId' => 'distinct_id_of_your_user', + 'event' => 'event_name', + 'flags' => $flags->onlyAccessed(), +]); +// Attach only specific flags +PostHog::capture([ + 'distinctId' => 'distinct_id_of_your_user', + 'event' => 'event_name', + 'flags' => $flags->only(['checkout-flow', 'new-dashboard']), +]); +``` + +`onlyAccessed()` is order-dependent. If you call it before accessing any flags with `isEnabled()` or `getFlag()`, no feature flag properties are attached. + +#### Method 2: Include the `$feature/feature_flag_name` property manually + +In the event properties, include `$feature/feature_flag_name: variant_key`: + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'distinct_id_of_your_user', + 'event' => 'event_name', + 'properties' => [ + // Replace feature-flag-key with your flag key and 'variant-key' with the key of your variant + '$feature/feature-flag-key' => 'variant-key', + ], +]); +``` + +### Evaluating only specific flags + +By default, `evaluateFlags()` evaluates every flag for the user. If you only need a few flags, pass `flagKeys` to request only those flags: + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags( + distinctId: 'distinct_id_of_your_user', + flagKeys: ['checkout-flow', 'new-dashboard'], +); +``` + +### Optional evaluation parameters + +`evaluateFlags()` also accepts optional parameters for local evaluation and GeoIP behavior: + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags( + distinctId: 'distinct_id_of_your_user', + groups: ['company' => 'company_id_in_your_db'], + personProperties: ['plan' => 'pro'], + groupProperties: ['company' => ['employees' => 11]], + onlyEvaluateLocally: false, // Defaults to false. Set to true to avoid a remote fallback. + disableGeoip: false, // Defaults to false. Set to true to disable GeoIP enrichment during remote evaluation. + flagKeys: ['checkout-flow', 'new-dashboard'], +); +``` + +### Sending `$feature_flag_called` events + +Capturing `$feature_flag_called` events enables PostHog to know when a flag was accessed by a user and provide [analytics and insights](/docs/product-analytics/insights.md) on the flag. With `evaluateFlags()`, the SDK sends this event when you call `$flags->isEnabled()` or `$flags->getFlag()` for a flag. + +The SDK deduplicates these events per `(flag key, distinct_id)` in a local cache. If you reinitialize the PostHog client, the cache resets and `$feature_flag_called` events may be sent again. PostHog handles duplicates, so duplicate `$feature_flag_called` events don't affect your analytics. + +`$flags->getFlagPayload()` doesn't send `$feature_flag_called` events and doesn't count as an access for `onlyAccessed()`. + +### Advanced: Overriding server properties + +Sometimes, you may want to evaluate feature flags using [person properties](/docs/product-analytics/person-properties.md), [groups](/docs/product-analytics/group-analytics.md), or group properties that haven't been ingested yet, or were set incorrectly earlier. + +You can provide properties to evaluate the flag with by using the `person properties`, `groups`, and `group properties` arguments. PostHog will then use these values to evaluate the flag, instead of any properties currently stored on your PostHog server. + +For example: + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags( + distinctId: 'distinct_id_of_the_user', + groups: [ + 'your_group_type' => 'your_group_id', + 'another_group_type' => 'your_group_id', + ], + personProperties: ['property_name' => 'value'], + groupProperties: [ + 'your_group_type' => ['group_property_name' => 'value'], + 'another_group_type' => ['group_property_name' => 'value'], + ], +); +if ($flags->isEnabled('flag-key')) { + // Do something differently for this user +} +``` + +### Overriding GeoIP properties + +By default, a user's GeoIP properties are set using the IP address they use to capture events on the frontend. You may want to override the these properties when evaluating feature flags. A common reason to do this is when you're not using PostHog on your frontend, so the user has no GeoIP properties. + +You can override GeoIP properties by including them in the `person_properties` parameter when evaluating feature flags. This is useful when you're evaluating flags on your backend and want to use the client's location instead of your server's location. + +The following GeoIP properties can be overridden: + +- `$geoip_country_code` +- `$geoip_country_name` +- `$geoip_city_name` +- `$geoip_city_confidence` +- `$geoip_continent_code` +- `$geoip_continent_name` +- `$geoip_latitude` +- `$geoip_longitude` +- `$geoip_postal_code` +- `$geoip_subdivision_1_code` +- `$geoip_subdivision_1_name` +- `$geoip_subdivision_2_code` +- `$geoip_subdivision_2_name` +- `$geoip_subdivision_3_code` +- `$geoip_subdivision_3_name` +- `$geoip_time_zone` + +Simply include any of these properties in the `person_properties` parameter alongside your other person properties when calling feature flags. + +### Request timeout + +You can configure the `feature_flag_request_timeout_ms` parameter when initializing your PostHog client to set a flag request timeout. This helps prevent your code from being blocked if PostHog's servers are too slow to respond. By default, this is set to 3 seconds. + +PHP + +PostHog AI + +```php +PostHog::init("", + [ + 'host' => 'https://us.i.posthog.com', + 'feature_flag_request_timeout_ms' => 3000, // Time in milliseconds. Defaults to 3000 (3 seconds). + ] +); +``` + +### Local Evaluation + +Evaluating feature flags requires making a request to PostHog for each flag. However, you can improve performance by evaluating flags locally. Instead of making a request for each flag, PostHog will periodically request and store feature flag definitions locally, enabling you to evaluate flags without making additional requests. + +It is best practice to use local evaluation flags when possible, since this enables you to resolve flags faster and with fewer API calls. + +To load feature flag definitions for local evaluation, initialize the SDK with your feature flags secure API key as `personalAPIKey`: + +PHP + +PostHog AI + +```php +PostHog::init( + '', + ['host' => 'https://us.i.posthog.com'], + personalAPIKey: 'your feature flags secure API key' +); +``` + +For details on how to implement local evaluation, see our [local evaluation guide](/docs/feature-flags/local-evaluation.md). + +### Experiments (A/B tests) + +Since [experiments](/docs/experiments/start-here.md) use feature flags, the code for running an experiment is very similar to the feature flags code: + +PHP + +PostHog AI + +```php +$flags = PostHog::evaluateFlags('user_distinct_id'); +$variant = $flags->getFlag('experiment-feature-flag-key'); +if ($variant === 'variant-name') { + // Do something differently for this user +} +``` + +It's also possible to [run experiments without using feature flags](/docs/experiments/running-experiments-without-feature-flags.md). + +### Group analytics + +Group analytics allows you to associate an event with a group (e.g. teams, organizations, etc.). This feature requires version `2.1.0` or above of the PHP SDK. Read the [group analytics guide](/docs/product-analytics/group-analytics.md) for more information. + +> **Note:** This is a paid feature and is not available on the open-source or free cloud plan. Learn more on the [pricing page](/pricing.md). + +To create a group or update its properties, use `groupIdentify`: + +PHP + +PostHog AI + +```php +PostHog::groupIdentify([ + 'groupType' => 'company', + 'groupKey' => 'company_id_in_your_db', + 'properties' => [ + 'name' => 'Awesome Inc.', + 'employees' => 11, + ], + // Optional distinct ID to associate this event with an existing person. + // Requires posthog-php 4.4.0 or later. + 'distinctId' => 'user_distinct_id' +]); +``` + +`name` is a special property which is used in the PostHog UI for the name of the group. If you don't specify a `name` property, the group ID is used instead. + +If the optional `distinctId` parameter is not provided in the group identify call, it defaults to `${groupType}_${groupKey}` (e.g., `$company_company_id_in_your_db` in the example above). This default behavior results in each group appearing as a separate person in PostHog. To avoid this, use a consistent `distinctId`, such as `group_identifier`, or a real user distinct ID. + +Once a group is created, you can use the `capture` method and pass in the `groups` parameter to capture an event with group analytics. + +PHP + +PostHog AI + +```php +PostHog::capture([ + 'distinctId' => 'user_distinct_id', + 'event' => 'some_event', + 'groups' => ['company' => 'company_id_in_your_db'] +]); +``` + +## Request context + +Use request context to apply a distinct ID, session ID, and common properties to all captures inside a callback. This is useful when connecting frontend activity to backend events, session replay, and error tracking. + +PHP + +PostHog AI + +```php +PostHog::withContext([ + 'distinctId' => 'user_distinct_id', + 'sessionId' => 'session_id_from_frontend', + 'properties' => [ + '$current_url' => 'https://example.com/account', + ], +], function () { + PostHog::capture([ + 'event' => 'backend_event', + ]); +}); +``` + +You can extract PostHog context from frontend tracing headers with `contextFromHeaders()`: + +PHP + +PostHog AI + +```php +$context = PostHog::contextFromHeaders($_SERVER); +PostHog::withContext($context, function () { + PostHog::capture([ + 'event' => 'backend_event', + ]); +}); +``` + +Call `PostHog::getContext()` to read the currently active context. Pass `['fresh' => true]` as the third argument to `withContext()` if you don't want to inherit any existing context. + +## Error tracking + +The PHP SDK supports both manual exception capture and opt-in automatic error tracking. + +To automatically capture uncaught exceptions, PHP errors, and fatal shutdown errors, enable `error_tracking` when initializing the client: + +PHP + +PostHog AI + +```php +PostHog::init( + '', + [ + 'host' => 'https://us.i.posthog.com', + 'error_tracking' => [ + 'enabled' => true, + ], + ], +); +``` + +You can also call `PostHog::captureException()` directly for manual capture. When source files are readable at runtime, PostHog includes surrounding source lines for in-app stack frames automatically. + +For the full setup guide, including `context_provider`, excluded exceptions, and verification steps, see the [PHP error tracking installation docs](/docs/error-tracking/installation/php.md). + +## Config options + +When calling `PostHog::init`, there are various configuration options you can set apart from the host. Pass them into your client initialisation like so: + +PHP + +PostHog AI + +```php +PostHog::init( + '', + [ + 'host' => 'https://us.i.posthog.com', + 'debug' => true, + 'ssl' => false, + // all options go here + ], +); +``` + +All possible options below: + +| Attribute | Description | +| --- | --- | +| hostType: StringDefault: us.i.posthog.com | URL of your PostHog instance. | +| sslType: BooleanDefault: true | Whether to use SSL for API requests or not. If host includes http:// or https://, the SDK infers this option unless you set it explicitly. | +| timeoutType: IntegerDefault: 10000 | Request timeout in milliseconds. | +| verify_batch_events_requestType: BooleanDefault: true | Whether to verify successful delivery of batch events (true, synchronous) or fire and forget (false, asynchronous) with the lib_curl consumer. | +| feature_flag_request_timeout_msType: IntegerDefault: 3000 | Request timeout for feature flags in milliseconds. | +| maximum_backoff_durationType: IntegerDefault: 10000 | Request retry backoff. Retries stop after this duration is hit. | +| consumerType: StringDefault: lib_curl | One of socket, file, lib_curl, and fork_curl. Determines what transport option to use for analytics capture. | +| debugType: BooleanDefault: false | Output debug logs or not. | +| max_queue_sizeType: IntegerDefault: 1000 | Maximum number of events to queue before rejecting new events. Applies to queued consumers. | +| batch_sizeType: IntegerDefault: 100 | Number of queued events to send in each batch. Applies to queued consumers. | +| compress_requestType: Boolean/StringDefault: false | Whether to gzip batch request payloads. | +| error_handlerType: CallableDefault: null | Callback invoked for SDK transport errors. | +| filenameType: StringDefault: sys_get_temp_dir() . '/posthog.log' | File path used when consumer is set to file. | +| error_trackingType: ArrayDefault: [] | Enables automatic error tracking. See the options below or the [PHP error tracking setup guide](/docs/error-tracking/installation/php.md). | + +### Error tracking options + +| Attribute | Description | +| --- | --- | +| enabledType: BooleanDefault: false | Enables automatic error tracking handlers. Manual captureException works regardless. | +| capture_errorsType: BooleanDefault: true | When enabled, captures PHP errors and fatal shutdown errors in addition to uncaught exceptions. | +| excluded_exceptionsType: Array of class stringsDefault: [] | Throwable classes to skip during automatic capture. | +| max_framesType: IntegerDefault: 20 | Maximum number of stack frames included in $exception_list. | +| context_providerType: Callable or nullDefault: null | Callback that returns distinctId and extra event properties for automatic captures. | + +## Debug mode + +PHP + +PostHog AI + +```php +PostHog::init( + '', + [ + 'host' => 'https://us.i.posthog.com', + 'debug' => true, + ], +); +``` + +## Thank you + +This library is largely based on the `analytics-php` package. + +### Community questions + +Ask a question + +### Was this page useful? + +HelpfulCould be better \ No newline at end of file diff --git a/skills/instrument-product-analytics/references/posthog-python.md b/skills/instrument-product-analytics/references/posthog-python.md index 50c8ae7..8c27cd2 100644 --- a/skills/instrument-product-analytics/references/posthog-python.md +++ b/skills/instrument-product-analytics/references/posthog-python.md @@ -1,6 +1,6 @@ # PostHog Python SDK -**SDK Version:** 7.14.2 +**SDK Version:** 7.15.4 Integrate PostHog into any python application. @@ -29,38 +29,38 @@ Initialize a new PostHog client instance. ### Parameters -- **`project_api_key?`** (`str`) - The project API key. -- **`host`** (`any`) - The host to use for the client. -- **`debug`** (`bool`) - Whether to enable debug mode. -- **`max_queue_size`** (`int`) -- **`send`** (`bool`) -- **`on_error`** (`any`) -- **`flush_at`** (`int`) -- **`flush_interval`** (`float`) -- **`gzip`** (`bool`) -- **`max_retries`** (`int`) -- **`sync_mode`** (`bool`) -- **`timeout`** (`int`) -- **`thread`** (`int`) -- **`poll_interval`** (`int`) -- **`personal_api_key`** (`any`) -- **`disabled`** (`bool`) -- **`disable_geoip`** (`bool`) -- **`historical_migration`** (`bool`) -- **`feature_flags_request_timeout_seconds`** (`int`) -- **`super_properties`** (`any`) -- **`enable_exception_autocapture`** (`bool`) -- **`log_captured_exceptions`** (`bool`) -- **`project_root`** (`any`) -- **`privacy_mode`** (`bool`) -- **`before_send`** (`any`) -- **`flag_fallback_cache_url`** (`any`) -- **`enable_local_evaluation`** (`bool`) -- **`flag_definition_cache_provider?`** (`FlagDefinitionCacheProvider`) -- **`capture_exception_code_variables`** (`bool`) -- **`code_variables_mask_patterns`** (`any`) -- **`code_variables_ignore_patterns`** (`any`) -- **`in_app_modules`** (`UnionType[list[str], any]`) +- **`project_api_key?`** (`str`) - PostHog project API key/token. +- **`host`** (`any`) - PostHog host. Defaults to the US ingestion endpoint when not set. App hosts such as ``https://us.posthog.com`` are mapped to the corresponding ingestion host. +- **`debug`** (`bool`) - Enable verbose SDK logging and re-raise errors from public API methods. +- **`max_queue_size`** (`int`) - Maximum number of events buffered before upload. +- **`send`** (`bool`) - If False, queueing succeeds but events are not sent. +- **`on_error`** (`any`) - Optional callback invoked by background consumers when an upload fails. +- **`flush_at`** (`int`) - Number of queued events that triggers a batch upload. +- **`flush_interval`** (`float`) - Maximum seconds a background consumer waits before flushing a partial batch. +- **`gzip`** (`bool`) - Whether to gzip event upload payloads. +- **`max_retries`** (`int`) - Number of upload retries for background consumers. +- **`sync_mode`** (`bool`) - If True, send each event synchronously instead of using background worker threads. +- **`timeout`** (`int`) - HTTP request timeout in seconds for event uploads. +- **`thread`** (`int`) - Number of background consumer threads. +- **`poll_interval`** (`int`) - Seconds between local feature flag definition refreshes. +- **`personal_api_key`** (`any`) - Personal API key used for local feature flag evaluation and remote config payloads. +- **`disabled`** (`bool`) - If True, disable captures and API requests. Useful in tests. +- **`disable_geoip`** (`bool`) - Whether to disable server-side GeoIP enrichment. Defaults to True. +- **`historical_migration`** (`bool`) - Mark events as historical migration imports. +- **`feature_flags_request_timeout_seconds`** (`int`) - Timeout in seconds for feature flag and remote config requests. +- **`super_properties`** (`any`) - Properties merged into every captured event. +- **`enable_exception_autocapture`** (`bool`) - Automatically capture uncaught exceptions. +- **`log_captured_exceptions`** (`bool`) - Also log exceptions captured by error tracking. +- **`project_root`** (`any`) - Root path used to determine in-app stack frames for captured exceptions. Defaults to the current working directory. +- **`privacy_mode`** (`bool`) - For AI observability, capture usage metadata without prompt inputs or outputs. +- **`before_send`** (`any`) - Optional callback that can modify or drop events before upload. Return ``None`` to drop an event. +- **`flag_fallback_cache_url`** (`any`) - Optional feature flag fallback cache URL, such as ``memory://local/?ttl=300&size=10000`` or a Redis URL. +- **`enable_local_evaluation`** (`bool`) - Whether to poll feature flag definitions for local evaluation when a personal API key is configured. +- **`flag_definition_cache_provider?`** (`FlagDefinitionCacheProvider`) - Optional external cache provider for sharing feature flag definitions across workers. +- **`capture_exception_code_variables`** (`bool`) - Capture local variable values on exception stack frames. +- **`code_variables_mask_patterns`** (`any`) - Variable-name patterns to mask when capturing code variables. +- **`code_variables_ignore_patterns`** (`any`) - Variable-name patterns to omit when capturing code variables. +- **`in_app_modules`** (`UnionType[list[str], any]`) - Module/package prefixes treated as in-app frames in captured exceptions. ### Returns @@ -341,6 +341,18 @@ if is_my_flag_enabled: --- +#### feature_flag_definitions() + +**Release Tag:** public + +Return feature flag definitions loaded for local evaluation. Returns: The currently loaded feature flag definitions, or ``None`` before local evaluation has loaded definitions. + +### Returns + +- `None` + +--- + #### get_all_flags() **Release Tag:** public @@ -575,6 +587,22 @@ decision = posthog.get_flags_decision('user123') --- +#### get_remote_config_payload() + +**Release Tag:** public + +Get the payload for a remote config feature flag. + +### Parameters + +- **`key?`** (`str`) - The remote config feature flag key. + +### Returns + +- `None` + +--- + #### load_feature_flags() **Release Tag:** public @@ -827,15 +855,8 @@ This will overwrite previous people property values. Generally operates similar ```python # Set person properties -from posthog import capture -capture( - 'distinct_id', - event='event_name', - properties={ - '$set': {'name': 'Max Hedgehog'}, - '$set_once': {'initial_url': '/blog'} - } -) +from posthog import set +set(distinct_id='distinct_id', properties={'name': 'Max Hedgehog'}) ``` --- @@ -862,15 +883,8 @@ This will not overwrite previous people property values, unlike `set`. Otherwise ```python # Set property once -from posthog import capture -capture( - 'distinct_id', - event='event_name', - properties={ - '$set': {'name': 'Max Hedgehog'}, - '$set_once': {'initial_url': '/blog'} - } -) +from posthog import set_once +set_once(distinct_id='distinct_id', properties={'initial_url': '/blog'}) ``` --- @@ -957,7 +971,7 @@ Capture exception is idempotent - if it is called twice with the same exception ### Parameters -- **`exception`** (`BaseException`) - The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` +- **`exception`** (`BaseException`) - The exception to capture. If not provided, the current exception is captured via `sys.exc_info()` **kwargs: Optional capture arguments including distinct_id, properties, timestamp, uuid, groups, flags, send_feature_flags, and disable_geoip. - **`kwargs?`** (`Unpack[OptionalCaptureArgs]`) ### Returns @@ -1032,7 +1046,7 @@ You can call `posthog.load_feature_flags()` before to make sure you're not doing - **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally - **`send_feature_flag_events`** (`bool`) - Whether to send feature flag events - **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup -- **`device_id`** (`any`) +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags ### Returns @@ -1091,7 +1105,7 @@ Flags are key-value pairs where the key is the flag key and the value is the fla - **`group_properties`** (`any`) - Group properties - **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally - **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup -- **`device_id`** (`any`) +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags - **`flag_keys_to_evaluate`** (`any`) - Optional list of flag keys to evaluate (evaluates all if None) ### Returns @@ -1108,6 +1122,29 @@ get_all_flags('distinct_id_of_your_user') --- +#### get_all_flags_and_payloads() + +**Release Tag:** public + +Get all feature flag values and payloads for a user. + +### Parameters + +- **`distinct_id?`** (`any`) - The user's distinct ID. +- **`groups`** (`any`) - Mapping of group type to group key. +- **`person_properties`** (`any`) - Person properties to use for evaluation. +- **`group_properties`** (`any`) - Group properties keyed by group type. +- **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally. +- **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup. +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags. +- **`flag_keys_to_evaluate`** (`any`) - Optional list of flag keys to evaluate. Evaluates all flags when omitted. + +### Returns + +- `FlagsAndPayloads` + +--- + #### get_feature_flag() **Release Tag:** public @@ -1128,7 +1165,7 @@ Get feature flag variant for users. Used with experiments. - **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally - **`send_feature_flag_events`** (`bool`) - Whether to send feature flag events - **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup -- **`device_id`** (`any`) +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags ### Returns @@ -1146,6 +1183,31 @@ if enabled_variant == 'variant-key': --- +#### get_feature_flag_payload() + +**Release Tag:** public + +Get the payload associated with a feature flag value. Deprecated for new code. Prefer ``evaluate_flags()`` and ``flags.get_flag_payload(key)`` so flag evaluation happens once per request. + +### Parameters + +- **`key?`** (`any`) - The feature flag key. +- **`distinct_id?`** (`any`) - The user's distinct ID. +- **`match_value`** (`any`) - Optional flag value to use when selecting a payload. +- **`groups`** (`any`) - Mapping of group type to group key. +- **`person_properties`** (`any`) - Person properties to use for evaluation. +- **`group_properties`** (`any`) - Group properties keyed by group type. +- **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally. +- **`send_feature_flag_events`** (`bool`) - Whether to send a $feature_flag_called event. +- **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup. +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags. + +### Returns + +- `Optional[str]` + +--- + #### load_feature_flags() **Release Tag:** public @@ -1230,19 +1292,19 @@ shutdown() **Release Tag:** public -Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled Example: ```python result = posthog.get_feature_flag_result('beta-feature', 'distinct_id') if result and result.enabled: # Use the variant and payload print(f"Variant: {result.variant}") print(f"Payload: {result.payload}") ``` +Get a FeatureFlagResult object which contains the flag result and payload. This method evaluates a feature flag and returns a FeatureFlagResult object containing: - enabled: Whether the flag is enabled - variant: The variant value if the flag has variants - payload: The payload associated with the flag (automatically deserialized from JSON) - key: The flag key - reason: Why the flag was enabled/disabled ### Parameters -- **`key?`** (`any`) -- **`distinct_id?`** (`any`) -- **`groups`** (`any`) -- **`person_properties`** (`any`) -- **`group_properties`** (`any`) -- **`only_evaluate_locally`** (`bool`) -- **`send_feature_flag_events`** (`bool`) -- **`disable_geoip`** (`any`) -- **`device_id`** (`any`) +- **`key?`** (`any`) - The feature flag key. +- **`distinct_id?`** (`any`) - The user's distinct ID. +- **`groups`** (`any`) - Mapping of group type to group key. +- **`person_properties`** (`any`) - Person properties to use for evaluation. +- **`group_properties`** (`any`) - Group properties keyed by group type. +- **`only_evaluate_locally`** (`bool`) - Whether to evaluate only locally. +- **`send_feature_flag_events`** (`bool`) - Whether to send a $feature_flag_called event. +- **`disable_geoip`** (`any`) - Whether to disable GeoIP lookup. +- **`device_id`** (`any`) - Optional device ID override for experience-continuity flags. ### Returns @@ -1266,120 +1328,120 @@ Get the payload for a remote config feature flag. --- -#### set_capture_exception_code_variables_context() - -**Release Tag:** public +### Contexts methods -Set whether code variables are captured for the current context. +#### get_tags() -### Parameters +**Release Tag:** public -- **`enabled?`** (`bool`) +Get all tags from the current context. Returns: Dict of all tags in the current context ### Returns -- `None` +- `dict[str, Any]` --- -#### set_code_variables_ignore_patterns_context() +#### new_context() **Release Tag:** public -Variable names matching these patterns will be ignored completely when capturing code variables. +Create a new context scope that will be active for the duration of the with block. ### Parameters -- **`ignore_patterns?`** (`list`) +- **`fresh`** (`bool`) - Whether to start with a fresh context (default: False) +- **`capture_exceptions`** (`bool`) - Whether to capture exceptions raised within the context (default: True) +- **`client?`** (`Client`) - Optional Posthog client instance to use for this context (default: None) ### Returns - `None` +### Examples + +```python +from posthog import new_context, tag, capture +with new_context(): + tag("request_id", "123") + capture("event_name", properties={"property": "value"}) +``` + --- -#### set_code_variables_mask_patterns_context() +#### scoped() **Release Tag:** public -Variable names matching these patterns will be masked with *** when capturing code variables. +Decorator that creates a new context for the function. ### Parameters -- **`mask_patterns?`** (`list`) +- **`fresh`** (`bool`) - Whether to start with a fresh context (default: False) +- **`capture_exceptions`** (`bool`) - Whether to capture and track exceptions with posthog error tracking (default: True) ### Returns - `None` ---- +### Examples -### Contexts methods +```python +from posthog import scoped, tag, capture +@scoped() +def process_payment(payment_id): + tag("payment_id", payment_id) + capture("payment_started") +``` -#### get_tags() +--- + +#### set_capture_exception_code_variables_context() **Release Tag:** public -Get all tags from the current context. Returns: Dict of all tags in the current context +Override code-variable capture for exceptions in the current context. + +### Parameters + +- **`enabled?`** (`bool`) - Whether exceptions captured in this context should include local variable values from stack frames. ### Returns -- `dict[str, Any]` +- `None` --- -#### new_context() +#### set_code_variables_ignore_patterns_context() **Release Tag:** public -Create a new context scope that will be active for the duration of the with block. +Override code-variable ignore patterns for exceptions in the current context. ### Parameters -- **`fresh`** (`bool`) - Whether to start with a fresh context (default: False) -- **`capture_exceptions`** (`bool`) - Whether to capture exceptions raised within the context (default: True) -- **`client?`** (`Client`) - Optional Posthog client instance to use for this context (default: None) +- **`ignore_patterns?`** (`list`) - Variable-name patterns that should be omitted entirely when code variables are captured. ### Returns - `None` -### Examples - -```python -from posthog import new_context, tag, capture -with new_context(): - tag("request_id", "123") - capture("event_name", properties={"property": "value"}) -``` - --- -#### scoped() +#### set_code_variables_mask_patterns_context() **Release Tag:** public -Decorator that creates a new context for the function. +Override code-variable mask patterns for exceptions in the current context. ### Parameters -- **`fresh`** (`bool`) - Whether to start with a fresh context (default: False) -- **`capture_exceptions`** (`bool`) - Whether to capture and track exceptions with posthog error tracking (default: True) +- **`mask_patterns?`** (`list`) - Variable-name patterns whose values should be replaced with ``***`` when code variables are captured. ### Returns - `None` -### Examples - -```python -from posthog import scoped, tag, capture -@scoped() -def process_payment(payment_id): - tag("payment_id", payment_id) - capture("payment_started") -``` - --- #### set_context_device_id() @@ -1450,4 +1512,18 @@ from posthog import tag tag("user_id", "123") ``` +--- + +### Initialization methods + +#### setup() + +**Release Tag:** public + +Create or return the global PostHog client configured by module settings. Most applications should either instantiate ``Posthog`` directly or set ``posthog.api_key``/other module settings before calling top-level helpers. ``setup()`` is called automatically by global APIs such as ``capture()``. Returns: The global ``Client`` instance. Raises: ValueError: If ``api_key`` has not been configured. + +### Returns + +- `Client` + --- \ No newline at end of file diff --git a/skills/instrument-product-analytics/references/python.md b/skills/instrument-product-analytics/references/python.md index 166ab38..154b1cd 100644 --- a/skills/instrument-product-analytics/references/python.md +++ b/skills/instrument-product-analytics/references/python.md @@ -224,7 +224,7 @@ Python PostHog AI ```python -from posthog import new_context +from posthog import new_context, tag with new_context(fresh=True): tag("some-key", "value-2") # This event only has the property some-key="value-2" from the fresh context @@ -266,7 +266,7 @@ You can read more about identifying users in the [user identification documentat ### Contexts and sessions -Contexts can be associated with a session ID by calling `posthog.set_context_session`. Session IDs must be UUIDv7 strings. +Contexts can be associated with a session ID by calling `posthog.set_context_session`. When linking backend events to frontend sessions, use the session ID from the frontend SDK (PostHog session IDs are UUIDv7 strings). Python @@ -324,7 +324,7 @@ with new_context(capture_exceptions=False): ### Decorating functions -The SDK exposes a function decorator. It takes the same arguments as `new_context` and provides a handy way to mark a whole function as being in a new context. For example: +The SDK exposes a function decorator. It takes the same `fresh` and `capture_exceptions` arguments as `new_context` and provides a handy way to mark a whole function as being in a new context. For example: Python @@ -609,9 +609,9 @@ if variant == "variant-name": It's also possible to [run experiments without using feature flags](/docs/experiments/running-experiments-without-feature-flags.md). -## LLM analytics +## AI Observability -Our Python SDK includes a built-in LLM analytics feature. It enables you to capture LLM usage, performance, and more. Check out our [analytics docs](/docs/llm-analytics.md) for more details on setting it up. +Our Python SDK includes a built-in AI Observability feature. It enables you to capture LLM usage, performance, and more. Check out our [analytics docs](/docs/ai-observability.md) for more details on setting it up. ## Error tracking @@ -633,7 +633,7 @@ Python PostHog AI ```python -posthog.capture_exception(e, 'user_distinct_id', properties=additional_properties) +posthog.capture_exception(e, distinct_id='user_distinct_id', properties=additional_properties) ``` Contexts automatically capture exceptions thrown inside them, unless disable it by passing `capture_exceptions=False` to `new_context()`. diff --git a/skills/instrument-product-analytics/references/react-router-v6.md b/skills/instrument-product-analytics/references/react-router-v6.md index b651e1e..291000d 100644 --- a/skills/instrument-product-analytics/references/react-router-v6.md +++ b/skills/instrument-product-analytics/references/react-router-v6.md @@ -42,15 +42,15 @@ This guide walks you through setting up PostHog for React Router V6. If you're u Required - Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, including `VITE_PUBLIC_` in their names ensures they are accessible in the frontend. + Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, prefixing variable names with `VITE_` ensures they are accessible in the frontend. .env.local PostHog AI ```shell - VITE_PUBLIC_POSTHOG_TOKEN= - VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + VITE_POSTHOG_TOKEN= + VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. 3 @@ -72,8 +72,8 @@ This guide walks you through setting up PostHog for React Router V6. If you're u import posthog from 'posthog-js'; import { PostHogErrorBoundary, PostHogProvider } from '@posthog/react' // Initialize PostHog - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', }); const root = document.getElementById("root"); @@ -341,8 +341,8 @@ This guide walks you through setting up PostHog for React Router V6. If you're u PostHog AI ```jsx - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', __add_tracing_headers: [ window.location.host, 'localhost' ], }); diff --git a/skills/instrument-product-analytics/references/react-router-v7-data-mode.md b/skills/instrument-product-analytics/references/react-router-v7-data-mode.md index 17d61a0..fa07c5b 100644 --- a/skills/instrument-product-analytics/references/react-router-v7-data-mode.md +++ b/skills/instrument-product-analytics/references/react-router-v7-data-mode.md @@ -42,15 +42,15 @@ This guide walks you through setting up PostHog for React Router V7 in data mode Required - Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, including `VITE_PUBLIC_` in their names ensures they are accessible in the frontend. + Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, prefixing variable names with `VITE_` ensures they are accessible in the frontend. .env.local PostHog AI ```shell - VITE_PUBLIC_POSTHOG_TOKEN= - VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + VITE_POSTHOG_TOKEN= + VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. 3 @@ -72,8 +72,8 @@ This guide walks you through setting up PostHog for React Router V7 in data mode import Root, { RootErrorBoundary } from "./app/root"; import posthog from 'posthog-js'; import { PostHogProvider } from '@posthog/react' - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', }); const router = createBrowserRouter([...]); @@ -329,8 +329,8 @@ This guide walks you through setting up PostHog for React Router V7 in data mode PostHog AI ```jsx - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', __add_tracing_headers: [ window.location.host, 'localhost' ], }); diff --git a/skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md b/skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md index f88d210..e13d45a 100644 --- a/skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +++ b/skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md @@ -42,15 +42,15 @@ This guide walks you through setting up PostHog for React Router V7 in declarati Required - Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, including `VITE_PUBLIC_` in their names ensures they are accessible in the frontend. + Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, prefixing variable names with `VITE_` ensures they are accessible in the frontend. .env.local PostHog AI ```shell - VITE_PUBLIC_POSTHOG_TOKEN= - VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + VITE_POSTHOG_TOKEN= + VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. 3 @@ -72,8 +72,8 @@ This guide walks you through setting up PostHog for React Router V7 in declarati import posthog from 'posthog-js'; import { PostHogErrorBoundary, PostHogProvider } from '@posthog/react' // Initialize PostHog - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', }); const root = document.getElementById("root"); @@ -341,8 +341,8 @@ This guide walks you through setting up PostHog for React Router V7 in declarati PostHog AI ```jsx - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', __add_tracing_headers: [ window.location.host, 'localhost' ], }); diff --git a/skills/instrument-product-analytics/references/react-router-v7-framework-mode.md b/skills/instrument-product-analytics/references/react-router-v7-framework-mode.md index 2d92274..a2ee8ea 100644 --- a/skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +++ b/skills/instrument-product-analytics/references/react-router-v7-framework-mode.md @@ -58,15 +58,15 @@ This guide walks you through setting up PostHog for React Router V7 in framework Required - Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, including `VITE_PUBLIC_` in their names ensures they are accessible in the frontend. + Add your environment variables to your `.env.local` file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token and host in [your project settings](https://us.posthog.com/settings/project). If you're using Vite, prefixing variable names with `VITE_` ensures they are accessible in the frontend. .env.local PostHog AI ```shell - VITE_PUBLIC_POSTHOG_TOKEN= - VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com + VITE_POSTHOG_TOKEN= + VITE_POSTHOG_HOST=https://us.i.posthog.com ``` 3. 3 @@ -87,8 +87,8 @@ This guide walks you through setting up PostHog for React Router V7 in framework import { HydratedRouter } from "react-router/dom"; import posthog from 'posthog-js'; import { PostHogProvider } from '@posthog/react' - posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_TOKEN, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, + posthog.init(import.meta.env.VITE_POSTHOG_TOKEN, { + api_host: import.meta.env.VITE_POSTHOG_HOST, defaults: '2026-01-30', __add_tracing_headers: [ window.location.host, 'localhost' ], }); @@ -393,8 +393,8 @@ This guide walks you through setting up PostHog for React Router V7 in framework posthog?: PostHog; } export const posthogMiddleware: Route.MiddlewareFunction = async ({ request, context }, next) => { - const posthog = new PostHog(process.env.VITE_PUBLIC_POSTHOG_TOKEN!, { - host: process.env.VITE_PUBLIC_POSTHOG_HOST!, + const posthog = new PostHog(process.env.VITE_POSTHOG_TOKEN!, { + host: process.env.VITE_POSTHOG_HOST!, flushAt: 1, flushInterval: 0, }); diff --git a/skills/instrument-product-analytics/references/ruby-on-rails.md b/skills/instrument-product-analytics/references/ruby-on-rails.md index f4b7119..ad8cb5b 100644 --- a/skills/instrument-product-analytics/references/ruby-on-rails.md +++ b/skills/instrument-product-analytics/references/ruby-on-rails.md @@ -1,6 +1,6 @@ # Ruby on Rails - Docs -PostHog makes it easy to get data about traffic and usage of your Ruby on Rails app. Integrating PostHog enables analytics, custom events capture, feature flags, and automatic exception tracking. +PostHog makes it easy to get data about traffic and usage of your Ruby on Rails app. Integrating PostHog enables analytics, custom event capture, feature flags, and automatic exception tracking. This guide walks you through integrating PostHog into your Rails app using the [posthog-rails gem](https://github.com/PostHog/posthog-ruby/tree/main/posthog-rails). @@ -20,6 +20,7 @@ Or, to integrate manually, continue with the rest of this guide. - **ActiveJob instrumentation** – Tracks background job exceptions - **User context** – Automatically associates exceptions with the current user - **Smart filtering** – Excludes common Rails exceptions (404s, etc.) by default +- **Request context** – Adds request metadata and optional PostHog tracing header identity/session context to captured events - **Rails 7.0+ error reporter** – Integrates with Rails' built-in error reporting ## Installation @@ -67,35 +68,55 @@ This creates `config/initializers/posthog.rb` with sensible defaults and documen ## Configuration -The generated initializer includes all available options: +`PostHog.init` creates a single client instance used across your app. Avoid creating multiple `PostHog::Client` instances with the same API key, as this can cause dropped events and inconsistent behavior. + +The generated initializer includes the most common options: config/initializers/posthog.rb PostHog AI ```ruby +# Rails-specific configuration +PostHog::Rails.configure do |config| + config.auto_capture_exceptions = true # Enable automatic exception capture (default: false) + config.report_rescued_exceptions = true # Report exceptions Rails rescues (default: false) + config.auto_instrument_active_job = true # Instrument background jobs (default: false) + config.use_tracing_headers = true # Use PostHog tracing headers for identity/session context (default: true) + config.capture_user_context = true # Include authenticated user info in exceptions (default: true) + config.current_user_method = :current_user # Method to get current user (default: :current_user) + config.user_id_method = nil # Method to get ID from user object (default: auto-detect) + # Add additional exceptions to ignore + config.excluded_exceptions = ['MyCustomError'] +end # Core PostHog client initialization PostHog.init do |config| - # Required: Your PostHog API key + # Required: Your PostHog project API key config.api_key = '' # Optional: Your PostHog instance URL config.host = 'https://us.i.posthog.com' # Optional: Personal API key for feature flags config.personal_api_key = 'phx_xxxxxxxxx' + # Maximum number of events to queue before dropping (default: 10000) + config.max_queue_size = 10_000 + # Send events synchronously on the calling thread (default: false) + config.sync_mode = false + # Feature flags polling interval in seconds (default: 30) + config.feature_flags_polling_interval = 30 + # Feature flag request timeout in seconds (default: 3) + config.feature_flag_request_timeout_seconds = 3 # Error callback to detect misconfiguration config.on_error = proc { |status, msg| Rails.logger.error("PostHog error: #{msg}") } -end -# Rails-specific configuration -PostHog::Rails.configure do |config| - config.auto_capture_exceptions = true # Enable automatic exception capture - config.report_rescued_exceptions = true # Report exceptions Rails rescues - config.auto_instrument_active_job = true # Instrument background jobs - config.capture_user_context = true # Include user info in exceptions - config.current_user_method = :current_user # Method to get current user - # Add additional exceptions to ignore - config.excluded_exceptions = ['MyCustomError'] + # Before-send callback to modify or drop events + config.before_send = proc { |event| + event[:properties] ||= {} + event[:properties]['environment'] = Rails.env + event + } + # Disable network calls in test mode + config.test_mode = true if Rails.env.test? end ``` @@ -141,20 +162,45 @@ Ruby PostHog AI ```ruby -# Track an event -PostHog.capture( +PostHog.capture({ distinct_id: current_user.id, event: 'post_created', properties: { title: @post.title } -) -# Identify a user -PostHog.identify( +}) +``` + +Identify a user and set their person properties: + +Ruby + +PostHog AI + +```ruby +PostHog.identify({ distinct_id: current_user.id, properties: { email: current_user.email, plan: current_user.plan } -) +}) +``` + +The Rails integration delegates methods like `capture`, `identify`, `alias`, `group_identify`, `evaluate_flags`, `capture_exception`, `flush`, and `shutdown` to the initialized `PostHog::Client`. + +## Request context + +PostHog Rails automatically applies request-scoped context to events captured during web requests. Request metadata such as `$current_url`, `$request_method`, `$request_path`, `$user_agent`, and `$ip` is added to event properties. + +When `use_tracing_headers` is enabled, PostHog tracing headers (`X-PostHog-Distinct-Id` and `X-PostHog-Session-Id`) are also used as default `distinct_id` and `$session_id` values. Explicit `distinct_id` and properties passed to `PostHog.capture` always take precedence. + +Disable tracing header identity/session capture if you do not want client-supplied tracing headers used for server-side events. Request metadata is still captured: + +Ruby + +PostHog AI + +```ruby +PostHog::Rails.config.use_tracing_headers = false ``` ## Error tracking @@ -178,6 +224,8 @@ class PostsController < ApplicationController end ``` +`report_rescued_exceptions` controls whether exceptions Rails rescues (for example, exceptions rendered by Rails error pages) are captured. Enable it along with `auto_capture_exceptions` for complete error visibility, or leave it disabled to capture only unhandled exceptions. + ### Manual exception capture You can also manually capture exceptions: @@ -194,6 +242,22 @@ PostHog.capture_exception( ) ``` +If you evaluated feature flags for the request, pass the same snapshot to include matching flag properties on the exception event: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +PostHog.capture_exception( + exception, + current_user.id, + { custom_property: 'value' }, + flags: flags +) +``` + ### Background job exceptions When `auto_instrument_active_job` is enabled, ActiveJob exceptions are automatically captured with job context: @@ -214,7 +278,7 @@ end #### Associating jobs with users -By default, PostHog extracts a `distinct_id` from job arguments by looking for a `user_id` key: +By default, PostHog extracts a `distinct_id` from job arguments by looking for a `user_id` key in hash arguments: Ruby @@ -225,7 +289,7 @@ PostHog AI ProcessOrderJob.perform_later(order.id, user_id: current_user.id) ``` -For more control, use the `posthog_distinct_id` class method: +For more control, use the `posthog_distinct_id` class method. The proc or block receives the same arguments as `perform`: Ruby @@ -233,13 +297,30 @@ PostHog AI ```ruby class SendWelcomeEmailJob < ApplicationJob - posthog_distinct_id ->(user, options) { user.id } + posthog_distinct_id ->(user, _options) { user.id } def perform(user, options = {}) UserMailer.welcome(user).deliver_now end end ``` +You can also use a block: + +Ruby + +PostHog AI + +```ruby +class ProcessOrderJob < ApplicationJob + posthog_distinct_id do |_order, notify_user_id| + notify_user_id + end + def perform(order, notify_user_id) + # Process the order... + end +end +``` + ### Rails 7.0+ error reporter PostHog integrates with Rails' built-in error reporting: @@ -256,11 +337,13 @@ end Rails.error.record(exception, context: { user_id: current_user.id }) ``` -PostHog automatically extracts the user's distinct ID from `user_id` or `distinct_id` in the context hash. +PostHog automatically extracts the user's distinct ID from `user_id` or `distinct_id` in the context hash. Other context keys are included as properties on the exception event. ### User context -PostHog Rails automatically captures user information from your controllers. If your user method has a different name, configure it: +PostHog Rails automatically captures authenticated user information from your controllers for exceptions. Authenticated Rails user context takes precedence over client-supplied tracing headers for exception identity. + +If your user method has a different name, configure it: Ruby @@ -272,11 +355,15 @@ PostHog::Rails.config.current_user_method = :logged_in_user #### User ID extraction -By default, PostHog Rails auto-detects the user's distinct ID by trying these methods: +By default, PostHog Rails auto-detects the user's distinct ID by trying these methods in order: 1. `posthog_distinct_id` – Define this on your User model for full control 2. `distinct_id` – Common analytics convention 3. `id` – Standard ActiveRecord primary key +4. `pk` – Primary key alias +5. `uuid` – For UUID-based primary keys + +It also checks hash-like users for `id`, `pk`, and `uuid` keys. You can configure a specific method: @@ -309,9 +396,16 @@ The following exceptions are not reported by default (common 4xx errors): - `AbstractController::ActionNotFound` - `ActionController::BadRequest` - `ActionController::InvalidAuthenticityToken` +- `ActionController::InvalidCrossOriginRequest` +- `ActionController::MethodNotAllowed` +- `ActionController::NotImplemented` +- `ActionController::ParameterMissing` - `ActionController::RoutingError` - `ActionController::UnknownFormat` +- `ActionController::UnknownHttpMethod` +- `ActionDispatch::Http::Parameters::ParseError` - `ActiveRecord::RecordNotFound` +- `ActiveRecord::RecordNotUnique` Add more with: @@ -325,7 +419,7 @@ PostHog::Rails.config.excluded_exceptions = ['MyException'] ## Feature flags -Use feature flags in your Rails app: +Evaluate flags once for the current user, then read values from the returned snapshot: Ruby @@ -334,7 +428,8 @@ PostHog AI ```ruby class PostsController < ApplicationController def show - if PostHog.is_feature_enabled('new-post-design', current_user.id) + flags = PostHog.evaluate_flags(current_user.id) + if flags.enabled?('new-post-design') render 'posts/show_new' else render 'posts/show' @@ -343,6 +438,35 @@ class PostsController < ApplicationController end ``` +For multivariate flags and experiments, use `get_flag`: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +variant = flags.get_flag('checkout-experiment') +if variant == 'test' + # Do something differently +end +``` + +When capturing an event after branching on a flag, pass the same `flags` snapshot so the event includes the exact flag values used by your code: + +Ruby + +PostHog AI + +```ruby +flags = PostHog.evaluate_flags(current_user.id) +PostHog.capture({ + distinct_id: current_user.id, + event: 'checkout_started', + flags: flags.only_accessed +}) +``` + For local evaluation, ensure you've set `personal_api_key`: Ruby @@ -355,9 +479,11 @@ config.personal_api_key = Rails.application.credentials.posthog[:personal_api_ke See our [Ruby SDK docs](/docs/libraries/ruby.md#local-evaluation) for details on local evaluation with Puma and Unicorn servers. +> **Note:** `PostHog.is_feature_enabled`, `PostHog.get_feature_flag`, `PostHog.get_feature_flag_result`, `PostHog.get_feature_flag_payload`, and `PostHog.capture({ ..., send_feature_flags: true })` still work during the migration period, but they're deprecated. Prefer `PostHog.evaluate_flags` for new code. + ## Testing -In your test environment, disable PostHog or use test mode: +In your test environment, disable network calls with test mode: config/environments/test.rb @@ -365,7 +491,8 @@ PostHog AI ```ruby PostHog.init do |config| - config.test_mode = true # Events are queued but not sent + config.api_key = '' + config.test_mode = true end ``` @@ -389,23 +516,33 @@ end | Option | Type | Default | Description | | --- | --- | --- | --- | -| api_key | String | required | Your PostHog project token | -| host | String | https://us.i.posthog.com | PostHog instance URL | -| personal_api_key | String | nil | For feature flag evaluation | -| test_mode | Boolean | false | Don't send events (for testing) | -| on_error | Proc | nil | Error callback | +| api_key | String | required | Your PostHog project token. | +| host | String | https://us.i.posthog.com | Fully qualified PostHog API host. | +| personal_api_key | String | nil | Personal API key for local feature flag evaluation and remote config payloads. | +| max_queue_size | Integer | 10000 | Maximum number of events to keep in the async queue before dropping new events. | +| test_mode | Boolean | false | Keep events queued and do not send them. Useful for tests. | +| sync_mode | Boolean | false | Send events synchronously on the calling thread. | +| on_error | Proc | no-op | Callback called as on_error.call(status, error). | +| feature_flags_polling_interval | Integer | 30 | Seconds between local feature flag definition polls. | +| feature_flag_request_timeout_seconds | Integer | 3 | Timeout, in seconds, for feature flag requests. | +| before_send | Proc | nil | Callback that receives the event hash before it is queued or sent. Return a modified event hash, or nil to drop the event. | + +The `PostHog.init` block supports the options above. Less common core options like `batch_size`, `disable_singleton_warning`, `skip_ssl_verification`, and the experimental `flag_definition_cache_provider` can be passed as an options hash to `PostHog.init(...)`; see the [Ruby SDK docs](/docs/libraries/ruby.md#configuration) for details. ### Rails-specific options +Configure these via `PostHog::Rails.configure` or `PostHog::Rails.config`: + | Option | Type | Default | Description | | --- | --- | --- | --- | -| auto_capture_exceptions | Boolean | false | Automatically capture exceptions | -| report_rescued_exceptions | Boolean | false | Report exceptions Rails rescues | -| auto_instrument_active_job | Boolean | false | Instrument ActiveJob | -| capture_user_context | Boolean | true | Include user info | -| current_user_method | Symbol | :current_user | Controller method for user | -| user_id_method | Symbol | nil | Method to extract ID from user object | -| excluded_exceptions | Array | [] | Additional exceptions to ignore | +| auto_capture_exceptions | Boolean | false | Automatically capture exceptions. | +| report_rescued_exceptions | Boolean | false | Report exceptions Rails rescues. | +| auto_instrument_active_job | Boolean | false | Capture ActiveJob exceptions with job context. | +| excluded_exceptions | Array | [] | Additional exception class names to ignore. | +| use_tracing_headers | Boolean | true | Use X-PostHog-Distinct-Id and X-PostHog-Session-Id as request-scoped defaults. | +| capture_user_context | Boolean | true | Include authenticated user info in exceptions. | +| current_user_method | Symbol | :current_user | Controller method used to fetch the current user. | +| user_id_method | Symbol | nil | Method used to extract the distinct ID from the user object. Auto-detects when nil. | ## Troubleshooting @@ -423,7 +560,7 @@ end => true ``` -2. Check your excluded exceptions list +2. Check your excluded exceptions list. 3. Verify middleware is installed: @@ -437,9 +574,9 @@ end ### User context not working -1. Verify `current_user_method` matches your controller method -2. Check that the user object responds to `posthog_distinct_id`, `distinct_id`, or `id` -3. If using a custom identifier, set `PostHog::Rails.config.user_id_method = :your_method` +1. Verify `current_user_method` matches your controller method. +2. Check that the user object responds to `posthog_distinct_id`, `distinct_id`, `id`, `pk`, or `uuid`. +3. If using a custom identifier, set `PostHog::Rails.config.user_id_method = :your_method`. ### Feature flags not working diff --git a/skills/instrument-product-analytics/references/ruby.md b/skills/instrument-product-analytics/references/ruby.md index 8a70dac..ab491dd 100644 --- a/skills/instrument-product-analytics/references/ruby.md +++ b/skills/instrument-product-analytics/references/ruby.md @@ -4,6 +4,8 @@ The `posthog-ruby` library provides tracking functionality on the server-side fo It uses an internal queue to make calls fast and non-blocking. It also batches requests and flushes asynchronously, making it perfect to use in any part of your web app or other server-side application that needs performance. +> **Use a single client instance (singleton)** — Create the PostHog client once and reuse it throughout your application. Multiple client instances with the same API key can cause dropped events and inconsistent behavior. The SDK logs a warning if it detects multiple instances. + ## Installation Add this to your `Gemfile`: @@ -33,12 +35,98 @@ posthog = PostHog::Client.new({ You can find your project token and instance address in the [project settings](https://app.posthog.com/project/settings) page in PostHog. +## Configuration + +Initialize the client with your project token before making any calls: + +Ruby + +PostHog AI + +```ruby +require 'posthog' +posthog = PostHog::Client.new({ + api_key: '', + host: 'https://us.i.posthog.com', + on_error: Proc.new { |status, msg| print msg } +}) +``` + +Available client options: + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| api_key | String | required | Your PostHog project token. | +| host | String | https://us.i.posthog.com | Fully qualified PostHog API host. Include the protocol, for example https://us.i.posthog.com or https://eu.i.posthog.com. | +| personal_api_key | String | nil | Personal API key. Required for local feature flag evaluation and remote config payloads. | +| max_queue_size | Integer | 10000 | Maximum number of events to keep in the async queue before dropping new events. | +| batch_size | Integer | 100 | Maximum number of events to send in one async batch. | +| test_mode | Boolean | false | Keep events queued and do not send them. Useful for tests. | +| sync_mode | Boolean | false | Send events synchronously on the calling thread. Useful in forking environments like Sidekiq and Resque. | +| on_error | Proc | no-op | Callback called as on_error.call(status, error) for API or serialization errors. | +| feature_flags_polling_interval | Integer | 30 | Seconds between local feature flag definition polls. | +| feature_flag_request_timeout_seconds | Integer | 3 | Timeout, in seconds, for feature flag requests. | +| before_send | Proc | nil | Callback that receives the event hash before it is queued or sent. Return a modified event hash, or nil to drop the event. | +| disable_singleton_warning | Boolean | false | Suppress warnings about multiple clients with the same API key. Use only when you intentionally need multiple clients. | +| skip_ssl_verification | Boolean | false | Disable SSL certificate verification. Intended only for local development or custom deployments. | +| flag_definition_cache_provider | Object | nil | Experimental provider for distributed feature flag definition caching. See [distributed flag definition caching](#distributed-flag-definition-caching). | + +### Filtering or modifying events before sending + +Use `before_send` to add, modify, or drop events immediately before the SDK queues or sends them: + +Ruby + +PostHog AI + +```ruby +posthog = PostHog::Client.new({ + api_key: '', + before_send: Proc.new do |event| + event[:properties] ||= {} + event[:properties]['environment'] = ENV['RACK_ENV'] + # Return nil to drop the event + event[:properties]['internal_user'] == true ? nil : event + end +}) +``` + +### Flushing and shutting down + +For short-lived scripts, call `flush` before the process exits. Call `shutdown` when your application is stopping to flush pending events and stop background resources. + +Ruby + +PostHog AI + +```ruby +posthog.capture({ distinct_id: 'user_123', event: 'script_finished' }) +posthog.flush +posthog.shutdown +``` + ## Identifying users > **Identifying users is required.** Backend events need a `distinct_id` that matches the ID your frontend uses when calling `posthog.identify()`. Without this, backend events are orphaned — they can't be linked to frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), or [error tracking](/docs/error-tracking.md). > > See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up. +Identify a user and set their person properties with `identify`: + +Ruby + +PostHog AI + +```ruby +posthog.identify({ + distinct_id: 'distinct_id_of_your_user', + properties: { + email: 'john@doe.com', + pro_user: false + } +}) +``` + ## Capturing events You can send custom events using `capture`: @@ -93,6 +181,20 @@ posthog.capture({ }) ``` +`capture` accepts these fields: + +| Field | Type | Description | +| --- | --- | --- | +| distinct_id | String | The user ID. If omitted, framework integrations can provide request context; otherwise the SDK generates a UUID and marks the event as personless. | +| event | String | Event name. Required. | +| properties | Hash | Event properties. | +| groups | Hash | Group analytics mapping from group type to group key. | +| timestamp | Time | When the event occurred. Defaults to the current time. | +| message_id | String | Optional message ID. | +| uuid | String | Optional event UUID used for deduplication. Must be a valid UUID. | +| flags | PostHog::FeatureFlagEvaluations | Snapshot returned by evaluate_flags. Adds $feature/ and $active_feature_flags properties without another /flags request. | +| send_feature_flags | Boolean, Hash, or PostHog::SendFeatureFlagsOptions | Deprecated. Prefer passing flags: from evaluate_flags. | + ## Person profiles and properties The Ruby SDK captures identified events by default. These create [person profiles](/docs/data/persons.md). To set [person properties](/docs/data/user-properties.md) in these profiles, include them when capturing an event: @@ -102,14 +204,14 @@ Ruby PostHog AI ```ruby -posthog.capture( +posthog.capture({ distinct_id: 'distinct_id', event: 'event_name', properties: { '$set': { name: 'Max Hedgehog' }, '$set_once': { initial_url: '/blog' } } -) +}) ``` For more details on the difference between `$set` and `$set_once`, see our [person properties docs](/docs/data/user-properties.md#what-is-the-difference-between-set-and-set_once). @@ -121,13 +223,13 @@ Ruby PostHog AI ```ruby -posthog.capture( +posthog.capture({ distinct_id: 'distinct_id', event: 'event_name', properties: { '$process_person_profile': false } -) +}) ``` ## Alias @@ -141,10 +243,10 @@ Ruby PostHog AI ```ruby -posthog.alias( - distinct_id: "distinct_id", - alias: "alias_id" -) +posthog.alias({ + distinct_id: 'distinct_id', + alias: 'alias_id' +}) ``` We strongly recommend reading our docs on [alias](/docs/data/identify.md#alias-assigning-multiple-distinct-ids-to-the-same-user) to best understand how to correctly use this method. @@ -192,7 +294,7 @@ end `flags.get_flag()` returns the variant string for multivariate flags, `true` for enabled boolean flags, `false` for disabled flags, and `nil` when the flag wasn't returned by the evaluation. -> **Note:** `posthog.is_feature_enabled()`, `posthog.get_feature_flag()`, `posthog.get_feature_flag_payload()`, and `capture(send_feature_flags: true)` still work during the migration period, but they're deprecated. Prefer `evaluate_flags()` for new code. +> **Note:** `posthog.is_feature_enabled()`, `posthog.get_feature_flag()`, `posthog.get_feature_flag_result()`, `posthog.get_feature_flag_payload()`, and `capture({ ..., send_feature_flags: true })` still work during the migration period, but they're deprecated. Prefer `evaluate_flags()` for new code. ### Step 2: Include feature flag information when capturing events @@ -281,6 +383,36 @@ flags = posthog.evaluate_flags( ) ``` +### Evaluating locally only + +If you want to skip the remote `/flags` request and only use locally cached definitions, pass `only_evaluate_locally: true`: + +Ruby + +PostHog AI + +```ruby +flags = posthog.evaluate_flags( + 'distinct_id_of_your_user', + only_evaluate_locally: true, +) +``` + +### Disabling GeoIP for flag evaluation + +Pass `disable_geoip: true` to disable GeoIP lookup for remote flag evaluation: + +Ruby + +PostHog AI + +```ruby +flags = posthog.evaluate_flags( + 'distinct_id_of_your_user', + disable_geoip: true, +) +``` + ### Sending `$feature_flag_called` events Capturing `$feature_flag_called` events enables PostHog to know when a flag was accessed by a user and provide [analytics and insights](/docs/product-analytics/insights.md) on the flag. With `evaluate_flags()`, the SDK sends this event when you call `flags.enabled?()` or `flags.get_flag()` for a flag. @@ -367,6 +499,18 @@ posthog = PostHog::Client.new({ }) ``` +### Legacy single-flag methods + +The following methods are still available during the migration period, but are deprecated. Prefer `evaluate_flags` for new code. + +| Method | Replacement | +| --- | --- | +| posthog.is_feature_enabled(flag_key, distinct_id, ...) | posthog.evaluate_flags(distinct_id, ...).enabled?(flag_key) | +| posthog.get_feature_flag(flag_key, distinct_id, ...) | posthog.evaluate_flags(distinct_id, ...).get_flag(flag_key) | +| posthog.get_feature_flag_payload(flag_key, distinct_id, ...) | posthog.evaluate_flags(distinct_id, ...).get_flag_payload(flag_key) | +| posthog.get_feature_flag_result(flag_key, distinct_id, ...) | posthog.evaluate_flags(distinct_id, ...) and read get_flag / get_flag_payload | +| posthog.capture({ ..., send_feature_flags: true }) | posthog.capture({ ..., flags: flags }) | + ### Local Evaluation Evaluating feature flags requires making a request to PostHog for each flag. However, you can improve performance by evaluating flags locally. Instead of making a request for each flag, PostHog will periodically request and store feature flag definitions locally, enabling you to evaluate flags without making additional requests. @@ -377,26 +521,26 @@ For details on how to implement local evaluation, see our [local evaluation guid #### Evaluating feature flags locally in unicorn server -If you have `preload_app true` in your unicorn config, you can use the [`after_fork`](https://www.rubydoc.info/gems/unicorn/Unicorn%2FConfigurator:after_fork) hook (which is part of the unicorn's configuration) to enable the feature flag cache to receive the updates from posthog dashboard. +If you have `preload_app true` in your unicorn config, you can use the [`after_fork`](https://www.rubydoc.info/gems/unicorn/Unicorn%2FConfigurator:after_fork) hook (which is part of the unicorn's configuration) to enable the feature flag cache to receive the updates from PostHog. Ruby PostHog AI ```ruby -after_fork do |server, worker| - $posthog = PostHog::Client.new( +after_fork do |_server, _worker| + $posthog = PostHog::Client.new({ api_key: '', - personal_api_key: '' + personal_api_key: '', host: 'https://us.i.posthog.com', on_error: Proc.new { |status, msg| print msg } - ) + }) end ``` #### Evaluating feature flags locally in a Puma server -If you use Puma with multiple workers, you can use the `on_worker_boot` hook (which is part of the Puma's configuration) to enable the feature flag cache to receive the updates from PostHog. +If you use Puma with multiple workers, you can use the `on_worker_boot` hook (which is part of Puma's configuration) to enable the feature flag cache to receive updates from PostHog. Ruby @@ -404,15 +548,50 @@ PostHog AI ```ruby on_worker_boot do - $posthog = PostHog::Client.new( + $posthog = PostHog::Client.new({ api_key: '', - personal_api_key: '' + personal_api_key: '', host: 'https://us.i.posthog.com', on_error: Proc.new { |status, msg| print msg } - ) + }) end ``` +### Distributed flag definition caching + +`flag_definition_cache_provider` is an experimental API for sharing locally evaluated feature flag definitions across multiple workers or processes. The provider object must implement: + +- `flag_definitions` – returns cached definitions as a hash with `:flags`, `:group_type_mapping`, and `:cohorts`, or `nil` if empty. +- `should_fetch_flag_definitions?` – returns `true` if this process should fetch fresh definitions from PostHog. +- `on_flag_definitions_received(data)` – stores freshly fetched definitions. +- `shutdown` – releases locks or other resources. + +Ruby + +PostHog AI + +```ruby +posthog = PostHog::Client.new({ + api_key: '', + personal_api_key: '', + flag_definition_cache_provider: my_cache_provider +}) +``` + +Because this API is experimental, it may change in future minor versions. + +### Remote config payloads + +Use `get_remote_config_payload` to fetch the decrypted remote config payload for a flag. This requires `personal_api_key`. + +Ruby + +PostHog AI + +```ruby +payload = posthog.get_remote_config_payload('flag-key') +``` + ## Experiments (A/B tests) Since [experiments](/docs/experiments/start-here.md) use feature flags, the code for running an experiment is very similar to the feature flags code: @@ -437,7 +616,7 @@ Group analytics allows you to associate an event with a group (e.g. teams, organ > **Note:** This is a paid feature and is not available on the open-source or free cloud plan. Learn more on the [pricing page](/pricing.md). -- Capture an event and associate it with a group +Capture an event and associate it with a group: Ruby @@ -450,34 +629,32 @@ posthog.capture({ properties: { movie_id: '123', category: 'romcom' - } + }, groups: { 'company': 'company_id_in_your_db' } }) ``` -- Update properties on a group +Update properties on a group: Ruby PostHog AI ```ruby -posthog.group_identify( - { - group_type: "company", - group_key: "company_id_in_your_db", - properties: { - name: "Awesome Inc." - } +posthog.group_identify({ + group_type: 'company', + group_key: 'company_id_in_your_db', + properties: { + name: 'Awesome Inc.' } -) +}) ``` The `name` is a special property which is used in the PostHog UI for the name of the group. If you don't specify a `name` property, the group ID will be used instead. -If the optional `distinct_id` is not provided in the group identify call, it defaults to `${groupType}_${groupKey}` (e.g., `$company_company_id_in_your_db` in the example above). This default behavior will result in each group appearing as a separate person in PostHog. To avoid this, it's often more practical to use a consistent `distinct_id`, such as `group_identifier`. +If the optional `distinct_id` is not provided in the group identify call, it defaults to `$#{group_type}_#{group_key}` (e.g., `$company_company_id_in_your_db` in the example above). This default behavior will result in each group appearing as a separate person in PostHog. To avoid this, it's often more practical to use a consistent `distinct_id`, such as `group_identifier`. ## Exception capture @@ -487,9 +664,7 @@ You can capture exceptions using the `posthog-ruby` library. This enables you to The [posthog-rails](/docs/libraries/ruby-on-rails.md) gem provides automatic exception capture, ActiveJob instrumentation, and user context out of the box. See our [Rails error tracking guide](/docs/error-tracking/installation/ruby-on-rails.md) for details. -For non-Rails Ruby applications, you can manually capture exceptions: - -To capture exceptions, use the `capture_exception` method: +For non-Rails Ruby applications, you can manually capture exceptions with `capture_exception`: Ruby @@ -498,12 +673,12 @@ PostHog AI ```ruby begin # Code that might raise an exception - raise StandardError, "Something went wrong" + raise StandardError, 'Something went wrong' rescue => e posthog.capture_exception( e, - distinct_id: 'user_distinct_id', - properties: { + 'user_distinct_id', + { custom_property: 'custom_value' } ) @@ -514,9 +689,10 @@ The `capture_exception` method accepts the following parameters: | Parameter | Type | Description | | --- | --- | --- | -| exception | Exception | The exception object to capture (required) | -| distinct_id | String | The distinct ID of the user (optional) | -| properties | Hash | Additional properties to attach to the exception event (optional) | +| exception | Exception, String, or exception-like object | The exception to capture. Required. | +| distinct_id | String | The distinct ID of the user. Optional; request context can provide a default, otherwise the SDK generates a UUID. | +| additional_properties | Hash | Additional properties to attach to the exception event. Optional. | +| flags | PostHog::FeatureFlagEvaluations | Optional keyword argument. Adds the same feature flag properties as capture({ flags: flags }). | You can also override the [fingerprint](/docs/error-tracking/fingerprints.md) to customize how exceptions are grouped into issues: @@ -527,8 +703,8 @@ PostHog AI ```ruby posthog.capture_exception( e, - distinct_id: 'user_distinct_id', - properties: { + 'user_distinct_id', + { '$exception_fingerprint': 'CustomExceptionGroup' } ) @@ -536,7 +712,41 @@ posthog.capture_exception( ## Debug mode -The Ruby SDK debug logs by default. The log level by default is set to `WARN`. You can change it to `DEBUG` if you want to debug the client by running `posthog.logger.level = Logger::DEBUG`, where `posthog` is your initialized `PostHog::Client` instance. +The Ruby SDK logs warnings by default. You can change the log level to `DEBUG` to debug the client: + +Ruby + +PostHog AI + +```ruby +posthog.logger.level = Logger::DEBUG +``` + +You can also replace the SDK logger globally: + +Ruby + +PostHog AI + +```ruby +PostHog::Logging.logger = Rails.logger +``` + +## Test helpers + +When `test_mode: true`, events remain queued. You can inspect and clear the queue in tests: + +Ruby + +PostHog AI + +```ruby +posthog = PostHog::Client.new({ api_key: '', test_mode: true }) +posthog.capture({ distinct_id: 'user_123', event: 'test_event' }) +posthog.queued_messages +posthog.dequeue_last_message +posthog.clear +``` ## Thank you diff --git a/skills/investigating-error-issue/SKILL.md b/skills/investigating-error-issue/SKILL.md new file mode 100644 index 0000000..c88021f --- /dev/null +++ b/skills/investigating-error-issue/SKILL.md @@ -0,0 +1,364 @@ +--- +name: investigating-error-issue +description: > + Investigates a single PostHog error tracking issue end-to-end. Use when + the user provides an issue ID or pastes an issue URL + (`/error_tracking/`) and wants to understand the error — who it + affects, what triggers it, when it started, whether it correlates with + a release, browser, OS, or feature flag, and what the next step should + be. Pulls aggregated metrics, sample exception events, segment + breakdowns, linked replays, and synthesizes a hypothesis-grade summary + in one pass. +--- + +# Investigating an error tracking issue + +When a user asks "what's going on with this error?" or pastes an issue URL, gather +the context they would otherwise have to assemble manually: who is hitting it, what +changed, where it happens, and whether a replay shows the cause. + +## Available tools + +| Tool | Purpose | +| ------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `posthog:query-error-tracking-issue` | Compact issue details (status, assignee, top frame, release, aggregates) | +| `posthog:query-error-tracking-issue-events` | Sampled `$exception` events with stack, URL, browser, `$session_id` | +| `posthog:execute-sql` | Breakdowns, release / flag correlations, surrounding events + console logs around the error | +| `posthog:query-logs` | OTEL log entries around the error timestamp for server-side issues | +| `posthog:query-session-recordings-list` | Linked replays (delegate ranking to `finding-replay-for-issue`) | +| `posthog:read-data-schema` | Confirm property keys before filtering on them | + +## Workflow + +### Step 1 — Establish the issue baseline + +Fetch the issue record with its compact aggregates and a sparkline: + +```json +posthog:query-error-tracking-issue +{ + "issueId": "", + "dateRange": { "date_from": "-30d" }, + "includeSparkline": true, + "volumeResolution": 12 +} +``` + +Capture: `name`, `description`, `status`, `first_seen`, `last_seen`, `assignee`, +total `occurrences` / `users` / `sessions`, top in-app frame, latest release +metadata, and the volume buckets. + +The sparkline tells you the shape — flat, spike, ramp, or recurring — and that +shape drives the rest of the investigation. If the user only asked a status +question, skip `includeSparkline` to save tokens. + +### Step 2 — Pull a sample exception event + +A captured event has the stack frames, URL, browser, and properties needed to +reason about cause. Pull a recent sample first, then an early one to compare. + +```json +posthog:query-error-tracking-issue-events +{ + "issueId": "", + "limit": 1, + "verbosity": "stack" +} +``` + +Use `verbosity: "raw"` only if the truncated stack hides the answer. The tool +defaults to `onlyAppFrames: true`, which strips vendor frames; flip to `false` +when the bug appears to live in a third-party library — or when the response +comes back with `stacktrace.type: "resolved"` but no frames at all (common for +minified bundles where every frame looks vendor-y to the resolver, e.g. React +production builds). + +For the earliest sample, narrow `dateRange` to a tight window around the +issue's `first_seen` (e.g. set `date_from` slightly before and `date_to` +slightly after) and pass `orderDirection: "ASC"` so you get the earliest +event in the window rather than the latest — the tool defaults to `DESC`, +which would return a recent event and silently duplicate the first call. +If recent and earliest events look materially different — different stack +root, different URL pattern — the issue may be a grouping mistake. Flag for +`grouping-noisy-errors` instead of continuing as if it were one bug. + +### Step 3 — Run breakdowns to isolate the cause + +Breakdowns aren't a typed tool — drop into `execute-sql`. Run only the +breakdowns the issue's shape suggests; each one costs a query and clutters the +synthesis. + +| Sparkline shape | First breakdown to try | +| ----------------- | ------------------------------------------------------------------------ | +| Spike from zero | By app version / release — almost always a deploy regression (see below) | +| Steady-state high | By browser / OS — rendering or platform-specific bug | +| Ramp | By geography or feature flag — gradual rollout exposure | +| Bursts then quiet | By time of day or `$current_url` — scheduled job or specific page | + +#### Picking the right version property + +PostHog emits three version-shaped fields. They mean different things and only +one of them answers "what version of the user's app introduced this?": + +| Property | What it is | Auto-captured by | Use for | +| --------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| `$exception_releases` | Cymbal-managed release map, keyed by release ID | Only when SDK publishes release metadata (e.g. sourcemap upload tied to a release) | Most precise release attribution **when present** | +| `$app_version` | The user's deployed app version | iOS (`CFBundleShortVersionString`), React Native (Expo / react-native-device-info) | "What deploy of my app introduced this?" — the question users care about | +| `$lib_version` | The PostHog SDK library version (e.g. posthog-js 1.298.0) | Every SDK on every event | The narrow "did upgrading the PostHog SDK introduce this?" question | + +`$lib_version` is on virtually every event, which makes it tempting — but it's +the PostHog library version, not the user's app version. A constant +`$lib_version` paired with a spike means the user shipped a regression in +their own code with the SDK unchanged, which is the common case. Reach for +`$lib_version` only when nothing else is populated and you're explicitly +asking "did upgrading PostHog cause this?". + +Web / server / Node / Java / Python projects do **not** auto-capture +`$app_version` — the customer has to set it (via `register`, a context +provider, or `before_send`). If the breakdown comes back with one +`$app_version` row of all-NULL, say so explicitly in the synthesis and +suggest the customer wire it up; falling back to `$exception_releases` or to +a per-day timeline by `first_seen` keeps the investigation moving. + +Example (`$app_version` — populated automatically on mobile, manually on +web / server): + +```sql +posthog:execute-sql +SELECT + properties.$app_version AS app_version, + count() AS occurrences, + uniq(person_id) AS users, + min(timestamp) AS first_seen, + max(timestamp) AS last_seen +FROM events +WHERE event = '$exception' + AND (issue_id = '' OR properties.$exception_issue_id = '') + AND timestamp > now() - INTERVAL 30 DAY +GROUP BY app_version +ORDER BY occurrences DESC +LIMIT 20 +``` + +The `(issue_id = ... OR properties.$exception_issue_id = ...)` pattern +mirrors the canonical `build_issue_where` clause from +`products/error_tracking/backend/api/query_utils.py`. `issue_id` is the +resolved virtual field on `events` (it follows fingerprint overrides so +merged/split issues route correctly); `properties.$exception_issue_id` is +the raw event property captured at ingestion. Filtering on only the property +silently undercounts events for issues that have been merged or split. + +If `first_seen` for one `app_version` is much later than the issue's overall +`first_seen`, that release introduced or worsened the bug — strong root-cause +signal. If every row is `NULL`, the SDK isn't reporting an app version on +this project (common on web / server) — switch to `$exception_releases` if +the customer ships releases, or fall back to a `toDate(timestamp)` timeline. + +When `$exception_releases` is populated, it's a JSON dict keyed by release +ID. There is no top-level `$release` property; query `$exception_releases` +directly when you need release attribution and the customer has it wired up. + +Repeat with `properties.$browser`, `properties.$os`, `properties.$current_url`, +or any feature flag the project tags errors with. + +### Step 4 — Check feature flag exposure + +If the user suspects an experiment or rollout, check whether affected users had +a flag enabled when the error fired. + +To enumerate which flags were evaluated on affected users, parse the +`$active_feature_flags` property — it is materialized as a JSON-encoded string in +ClickHouse, so `arrayJoin(properties.$active_feature_flags)` directly will fail; +`JSONExtract` is the working pattern: + +```sql +posthog:execute-sql +SELECT + arrayJoin(JSONExtract(toString(properties.$active_feature_flags), 'Array(String)')) AS flag, + count() AS occurrences, + uniq(person_id) AS users +FROM events +WHERE event = '$exception' + AND (issue_id = '' OR properties.$exception_issue_id = '') + AND timestamp > now() - INTERVAL 14 DAY + AND notEmpty(toString(properties.$active_feature_flags)) +GROUP BY flag +ORDER BY occurrences DESC +LIMIT 20 +``` + +Caveat: every event captures every evaluated flag key, so this enumeration often +returns identical counts across flags and **doesn't tell you which flag +correlates with the error** — only which were on the user. To actually test a +hypothesis, query the per-flag value column `properties.$feature/`, +which carries the evaluated value (`true`/`false`/variant name): + +```sql +posthog:execute-sql +SELECT + properties.`$feature/my-flag-key` AS variant, + count() AS occurrences, + uniq(person_id) AS users +FROM events +WHERE event = '$exception' + AND (issue_id = '' OR properties.$exception_issue_id = '') + AND timestamp > now() - INTERVAL 14 DAY +GROUP BY variant +ORDER BY occurrences DESC +``` + +Compare the variant split here to the project's overall exposure on the same +flag in the same window. Disproportionate representation of one variant +suggests the flag is involved in the cause — not a guarantee, but a strong +hypothesis. + +### Step 5 — Reconstruct what happened around the error + +Use the `$session_id` from the sample event in step 2 to pull the activity +surrounding the exception. Three sources stack on each other; run the ones +that make sense for the SDK that captured the error. + +#### 5a. Surrounding events (client SDKs by `$session_id`) + +Mirrors the ET frontend session timeline. Pulls custom events, page views, +and other exceptions captured under the same session within a ±1h window: + +```sql +posthog:execute-sql +SELECT + uuid, + event, + timestamp, + properties.$lib AS lib, + properties.$current_url AS url +FROM events +WHERE $session_id = '' + AND (event = '$exception' OR event = '$pageview' OR left(event, 1) != '$') + AND timestamp >= toDateTime('', 'UTC') - INTERVAL 1 HOUR + AND timestamp <= toDateTime('', 'UTC') + INTERVAL 1 HOUR +ORDER BY timestamp ASC +LIMIT 100 +``` + +The `left(event, 1) != '$'` clause drops PostHog autocapture / system events +while keeping every custom event. The `OR event = '$pageview'`/`'$exception'` +exceptions re-add the two system events worth seeing on the timeline. This is +the same filter the ET UI uses. + +Mixed `$lib` values in the output are a feature, not noise. When a server SDK +propagates `$session_id` from the client request (PostHog's own backend does +this), the timeline shows server-side activity inline with the browser side — +"both SDKs when available" for free. Skim the lib column to see how each row +was produced. + +The skill defaults to a ±1h window because that's what the UI uses; widen it +when an issue's actions are slow (long batch jobs, background workers) or +tighten it when only the seconds right before the throw matter. + +#### 5b. Console logs (web / React Native session replay) + +When session replay is enabled, the replay pipeline emits `console.*` calls +into the `log_entries` table tagged with the same session id. Pull them with +the matching window: + +```sql +posthog:execute-sql +SELECT timestamp, level, message +FROM log_entries +WHERE log_source = 'session_replay' + AND log_source_id = '' + AND timestamp >= toDateTime('', 'UTC') - INTERVAL 1 HOUR + AND timestamp <= toDateTime('', 'UTC') + INTERVAL 1 HOUR +ORDER BY timestamp ASC +LIMIT 200 +``` + +`log_source = 'session_replay'` is the discriminator — `log_entries` is shared +with other sources. Empty results are common: either replay isn't enabled, or +this specific session wasn't recorded. Mention that in the synthesis rather +than treating it as a failure. + +#### 5c. Server logs around the error (OTEL via `query-logs`) + +For server-side exceptions, correlate the exception timestamp with OTEL log +entries the customer ingests. Many projects don't ingest logs at all — if +`query-logs` returns nothing or errors, say so and move on. Discover available +services first with `logs-attribute-values-list` when you don't know which +service produced the error. + +```json +posthog:query-logs +{ + "query": { + "dateRange": { + "date_from": "", + "date_to": "" + }, + "severityLevels": ["error", "warn"], + "serviceNames": [""], + "limit": 50, + "orderBy": "earliest" + } +} +``` + +Caveats worth knowing before relying on this output: + +- Logs are ingested separately from events and typically have shorter retention. + Old exceptions may return empty even though the issue is still active. +- `trace_id` / `span_id` come back zero-padded (`"00000000..."`) when not set. + Trace-based correlation only works for explicitly instrumented requests, not + for every event. +- `service.name` is a resource attribute. Narrow with `serviceNames` rather + than a free-text `searchTerm` when you know the producer. + +#### 5d. Find a representative replay + +Hand off to `finding-replay-for-issue` when picking the _best_ session matters — +popular issues link hundreds of recordings, mostly short crash fragments or +idle-tab sessions, and that skill applies the duration / active-time / recency +ranking that finds the one most likely to show the cause. Hand off too when the +user asks for "a replay" without specifying which. + +Skip the hand-off and pull a recording inline via `query-session-recordings-list` +with `session_ids` from the sample exception events you already fetched in step 2 +when only a handful of sessions are linked, the user already named a specific +session, or any working example will do (e.g. proving the error reproduces). + +If neither path returns a recording, mention that session replay may not be +enabled for the affected users — useful context, not a failure. + +### Step 6 — Synthesize + +Present in this order: + +1. **What it is** — type, message, where in the stack +2. **Who it affects** — total users, sessions, and any segment breakdown that + stood out +3. **When it started** — `first_seen`, plus the release / version that + introduced it if a breakdown found one +4. **Likely cause** — one or two hypotheses backed by the breakdowns above +5. **Next step** — a concrete action: investigate the suspected release, watch + the linked replay, ping the assignee, or escalate + +Keep the synthesis tight. The user wants the answer, not a tour of the data. + +## Tips + +- The canonical join key from events to an issue is the resolved `issue_id` + virtual field, with `properties.$exception_issue_id` as fallback — see Step 3 + for the reason and the `build_issue_where` pattern. +- For a "what version introduced this?" breakdown, prefer `$app_version` (the + user's deployed app version, auto-captured on iOS / React Native and + manually set on web / server) or `$exception_releases` when populated. Avoid + `$lib_version` for this question — it's the PostHog SDK library version, not + the user's app. See the "Picking the right version property" subsection in + Step 3. +- If the issue spans more than 30 days, widen the date range explicitly. + Defaults often truncate the original `first_seen` event off the breakdown. +- Don't propose a fix in the synthesis unless the cause is obvious from the + sample stack. Hypotheses backed by data are more useful than confident + guesses. +- If `query-error-tracking-issue` returns an `external_issues` array, the issue + is already linked to a Linear / Jira / GitHub ticket. Mention the link in the + synthesis so the user doesn't open a duplicate. diff --git a/skills/querying-posthog-data/SKILL.md b/skills/querying-posthog-data/SKILL.md index c0c3fd3..ae16aa8 100644 --- a/skills/querying-posthog-data/SKILL.md +++ b/skills/querying-posthog-data/SKILL.md @@ -46,7 +46,7 @@ Schema reference for PostHog's core system models, organized by domain: - [Hog Flows](./references/models-hog-flows.md) - [Hog Functions](./references/models-hog-functions.md) - [Integrations](./references/models-integrations.md) -- [AI observability reviews](./references/models-llm-analytics-reviews.md) +- [AI observability reviews](./references/models-ai-observability-reviews.md) - [Logs (`logs` data plane + saved views and alerts)](./references/models-logs.md) - [Metrics (`posthog.metrics`)](./references/models-metrics.md) - [Notebooks](./references/models-notebooks.md) diff --git a/skills/querying-posthog-data/references/available-functions.md b/skills/querying-posthog-data/references/available-functions.md index 2d87f55..1ee494f 100644 --- a/skills/querying-posthog-data/references/available-functions.md +++ b/skills/querying-posthog-data/references/available-functions.md @@ -630,6 +630,8 @@ protocol quantile quantileExact quantiles +quantilesMerge +quantilesState queryString queryStringAndFragment radians diff --git a/skills/querying-posthog-data/references/example-error-tracking.md b/skills/querying-posthog-data/references/example-error-tracking.md index 85d6e94..7808e99 100644 --- a/skills/querying-posthog-data/references/example-error-tracking.md +++ b/skills/querying-posthog-data/references/example-error-tracking.md @@ -10,13 +10,13 @@ SELECT count(DISTINCT uuid) AS occurrences, count(DISTINCT nullIf($session_id, '')) AS sessions, count(DISTINCT coalesce(nullIf(toString(person_id), '00000000-0000-0000-0000-000000000000'), distinct_id)) AS users, - sumForEach(arrayMap(bin -> if(and(greater(timestamp, bin), lessOrEquals(dateDiff('seconds', bin, timestamp), divide(dateDiff('seconds', toDateTime(toDateTime('2026-05-21 20:25:41.236619')), toDateTime(toDateTime('2026-05-22 20:25:41.237155'))), 20))), 1, 0), arrayMap(i -> dateAdd(toDateTime(toDateTime('2026-05-21 20:25:41.236619')), toIntervalSecond(multiply(i, divide(dateDiff('seconds', toDateTime(toDateTime('2026-05-21 20:25:41.236619')), toDateTime(toDateTime('2026-05-22 20:25:41.237155'))), 20)))), range(0, 20)))) AS volumeRange, + sumForEach(arrayMap(bin -> if(and(greater(timestamp, bin), lessOrEquals(dateDiff('seconds', bin, timestamp), divide(dateDiff('seconds', toDateTime(toDateTime('2026-05-27 16:47:37.644270')), toDateTime(toDateTime('2026-05-28 16:47:37.644810'))), 20))), 1, 0), arrayMap(i -> dateAdd(toDateTime(toDateTime('2026-05-27 16:47:37.644270')), toIntervalSecond(multiply(i, divide(dateDiff('seconds', toDateTime(toDateTime('2026-05-27 16:47:37.644270')), toDateTime(toDateTime('2026-05-28 16:47:37.644810'))), 20)))), range(0, 20)))) AS volumeRange, argMin(tuple(uuid, distinct_id, timestamp, properties), timestamp) AS first_event, argMax(properties.$lib, timestamp) AS library FROM events AS e WHERE - and(equals(event, '$exception'), isNotNull(e.issue_id), equals(properties.tag, 'max_ai'), greaterOrEquals(timestamp, toDateTime(toDateTime('2026-05-21 20:25:41.236619'))), lessOrEquals(timestamp, toDateTime(toDateTime('2026-05-22 20:25:41.237155'))), or(greater(position(lower(properties.$exception_types), lower('constant')), 0), greater(position(lower(properties.$exception_values), lower('constant')), 0), greater(position(lower(properties.$exception_sources), lower('constant')), 0), greater(position(lower(properties.$exception_functions), lower('constant')), 0), greater(position(lower(properties.email), lower('constant')), 0), greater(position(lower(person.properties.email), lower('constant')), 0))) + and(equals(event, '$exception'), isNotNull(e.issue_id), equals(properties.tag, 'max_ai'), greaterOrEquals(timestamp, toDateTime(toDateTime('2026-05-27 16:47:37.644270'))), lessOrEquals(timestamp, toDateTime(toDateTime('2026-05-28 16:47:37.644810'))), or(greater(position(lower(properties.$exception_types), lower('constant')), 0), greater(position(lower(properties.$exception_values), lower('constant')), 0), greater(position(lower(properties.$exception_sources), lower('constant')), 0), greater(position(lower(properties.$exception_functions), lower('constant')), 0), greater(position(lower(properties.email), lower('constant')), 0), greater(position(lower(person.properties.email), lower('constant')), 0))) GROUP BY id ORDER BY diff --git a/skills/querying-posthog-data/references/example-funnel-trends.md b/skills/querying-posthog-data/references/example-funnel-trends.md index 5ce79d3..a839d31 100644 --- a/skills/querying-posthog-data/references/example-funnel-trends.md +++ b/skills/querying-posthog-data/references/example-funnel-trends.md @@ -26,7 +26,7 @@ FROM FROM events AS e WHERE - and(and(greaterOrEquals(e.timestamp, toDateTime('2025-12-03 00:00:00.000000')), lessOrEquals(e.timestamp, toDateTime('2025-12-10 23:59:59.999999'))), and(notEquals(aggregation_target, ''), notEquals(aggregation_target, NULL)))) + and(and(greaterOrEquals(e.timestamp, toDateTime('2025-12-03 00:00:00.000000')), lessOrEquals(e.timestamp, toDateTime('2025-12-10 23:59:59.999999'))), and(notEquals(toString(aggregation_target), ''), notEquals(aggregation_target, NULL)))) GROUP BY aggregation_target) AS data RIGHT OUTER JOIN (SELECT diff --git a/skills/querying-posthog-data/references/example-logs.md b/skills/querying-posthog-data/references/example-logs.md index 670cb62..dae7733 100644 --- a/skills/querying-posthog-data/references/example-logs.md +++ b/skills/querying-posthog-data/references/example-logs.md @@ -23,7 +23,7 @@ SELECT FROM logs WHERE - and(and(greaterOrEquals(toStartOfDay(time_bucket), toStartOfDay(assumeNotNull(toDateTime('2025-12-09 00:00:00')))), lessOrEquals(toStartOfDay(time_bucket), toStartOfDay(assumeNotNull(toDateTime('2025-12-10 00:00:00'))))), 1, greaterOrEquals(timestamp, toDateTime('2026-05-21 20:25:43.656852')), indexHint(like(lower(body), '%timeout%')), ilike(toString(body), '%timeout%'), in(severity_text, tuple('warn', 'error', 'fatal'))) + and(and(greaterOrEquals(toStartOfDay(time_bucket), toStartOfDay(assumeNotNull(toDateTime('2025-12-09 00:00:00')))), lessOrEquals(toStartOfDay(time_bucket), toStartOfDay(assumeNotNull(toDateTime('2025-12-10 00:00:00'))))), 1, greaterOrEquals(timestamp, toDateTime('2026-05-27 16:47:40.031178')), indexHint(like(lower(body), '%timeout%')), ilike(toString(body), '%timeout%'), in(severity_text, tuple('warn', 'error', 'fatal'))) ORDER BY timestamp DESC, uuid DESC diff --git a/skills/querying-posthog-data/references/example-session-replay.md b/skills/querying-posthog-data/references/example-session-replay.md index 7812b36..1b8c283 100644 --- a/skills/querying-posthog-data/references/example-session-replay.md +++ b/skills/querying-posthog-data/references/example-session-replay.md @@ -19,17 +19,17 @@ SELECT sum(s.console_error_count) AS console_error_count, max(s.retention_period_days) AS retention_period_days, plus(dateTrunc('DAY', start_time), toIntervalDay(coalesce(retention_period_days, 30))) AS expiry_time, - date_diff('DAY', toDateTime('2026-05-22 20:25:44.656339'), expiry_time) AS recording_ttl, - greaterOrEquals(max(s._timestamp), toDateTime('2026-05-22 20:20:44.655475')) AS ongoing, + date_diff('DAY', toDateTime('2026-05-28 16:47:41.023376'), expiry_time) AS recording_ttl, + greaterOrEquals(max(s._timestamp), toDateTime('2026-05-28 16:42:41.022574')) AS ongoing, round(multiply(divide(plus(plus(plus(divide(sum(s.active_milliseconds), 1000), sum(s.click_count)), sum(s.keypress_count)), sum(s.console_error_count)), plus(plus(plus(plus(sum(s.mouse_activity_count), dateDiff('SECOND', start_time, end_time)), sum(s.console_error_count)), sum(s.console_log_count)), sum(s.console_warn_count))), 100), 2) AS activity_score FROM raw_session_replay_events AS s WHERE - and(greaterOrEquals(s.min_first_timestamp, toDateTime('2026-05-19 00:00:00.000000')), lessOrEquals(s.min_first_timestamp, toDateTime('2026-05-22 20:25:44.655652'))) + and(greaterOrEquals(s.min_first_timestamp, toDateTime('2026-05-25 00:00:00.000000')), lessOrEquals(s.min_first_timestamp, toDateTime('2026-05-28 16:47:41.022737'))) GROUP BY session_id HAVING - and(greaterOrEquals(expiry_time, toDateTime('2026-05-22 20:25:44.656197')), equals(max(s.is_deleted), 0), greater(active_seconds, 5.0)) + and(greaterOrEquals(expiry_time, toDateTime('2026-05-28 16:47:41.023261')), equals(max(s.is_deleted), 0), greater(active_seconds, 5.0)) ORDER BY start_time DESC, session_id DESC diff --git a/skills/querying-posthog-data/references/example-sessions.md b/skills/querying-posthog-data/references/example-sessions.md index a57eac7..5f31e18 100644 --- a/skills/querying-posthog-data/references/example-sessions.md +++ b/skills/querying-posthog-data/references/example-sessions.md @@ -13,7 +13,7 @@ SELECT FROM sessions WHERE - and(less($start_timestamp, toDateTime('2026-05-22 20:25:50.007785')), greater($start_timestamp, toDateTime('2026-05-21 20:25:45.008598'))) + and(less($start_timestamp, toDateTime('2026-05-28 16:47:46.395043')), greater($start_timestamp, toDateTime('2026-05-27 16:47:41.395776'))) ORDER BY $start_timestamp DESC LIMIT 50000 diff --git a/skills/querying-posthog-data/references/models-llm-analytics-reviews.md b/skills/querying-posthog-data/references/models-ai-observability-reviews.md similarity index 100% rename from skills/querying-posthog-data/references/models-llm-analytics-reviews.md rename to skills/querying-posthog-data/references/models-ai-observability-reviews.md diff --git a/skills/suppressing-noisy-errors/SKILL.md b/skills/suppressing-noisy-errors/SKILL.md new file mode 100644 index 0000000..d22b7a8 --- /dev/null +++ b/skills/suppressing-noisy-errors/SKILL.md @@ -0,0 +1,341 @@ +--- +name: suppressing-noisy-errors +description: > + Create PostHog error tracking suppression rules to drop high-volume, + low-value errors at ingestion. Use when the user asks "stop capturing + this error", "drop browser extension errors", "ignore ResizeObserver + loops", "suppress bot-driven errors", or wants to reduce ingestion + cost from noisy unactionable errors. Identifies suppression + candidates, scopes the filter tightly, decides between full + suppression and sampling, and confirms the rule before creating it. + Suppressed errors are dropped permanently — this skill defaults to + caution. +--- + +# Suppressing noisy errors + +Suppression is destructive in spirit: matching events are dropped at ingestion and +never become issues. The wrong rule silently throws away real bugs. This skill +exists to make sure suppression is applied only to patterns that are genuinely +unactionable, with filters narrow enough to avoid swallowing unrelated errors. + +## When suppression is the right tool + +Suppression is the right tool when an error is: + +- **Unactionable from your code** — browser extensions, third-party scripts, ad + blockers, network beacons firing after navigation. You can't fix it because you + didn't write it. +- **Browser engine quirks** — `ResizeObserver loop limit exceeded`, + `Script error.`, `Non-Error promise rejection captured` with empty payloads. +- **Bot or crawler traffic** — errors firing only from headless browsers or known + crawler user agents. +- **Sampling already enough** — for high-volume but real errors, dampen with + `sampling_rate` instead of full suppression so you keep visibility without + paying full cost. + +Suppression is **not** the right tool when: + +- The error is unactionable _today_ but might become actionable after a fix — + use issue status `archived` or `resolved` instead so it surfaces if it returns. +- You only want to mute notifications — assign the issue to a user, change its + status, or use notification rules. +- The error is a duplicate of another — merge or create a grouping rule + (`grouping-noisy-errors`). + +## Available tools + +| Tool | Purpose | +| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `posthog:query-error-tracking-issues-list` | Find suppression candidates by volume and impact; dry-run a candidate filter via `filterGroup` | +| `posthog:query-error-tracking-issue-events` | Inspect sampled `$exception` events to confirm the pattern | +| `posthog:execute-sql` | Fallback dry-run for filters that need OR groups or operators outside the `filterGroup` allowed list | +| `posthog:error-tracking-suppression-rules-list` | Check existing suppression rules | +| `posthog:error-tracking-suppression-rules-create` | Create the suppression rule | +| `posthog:error-tracking-issues-partial-update` | Hide past data via issue status without dropping events at ingestion | + +## Workflow + +### Step 1 — Identify candidates + +High occurrences with low distinct users is the strongest noise signal — one +user (or one bot) producing many events. + +```json +posthog:query-error-tracking-issues-list +{ + "status": "active", + "orderBy": "occurrences", + "orderDirection": "DESC", + "dateRange": { "date_from": "-7d" }, + "limit": 30, + "volumeResolution": 0 +} +``` + +Look for: + +- High `occurrences`, low `users` ratio (e.g., 50,000 occurrences, 3 users → likely + bot or extension loop) +- Exception messages matching known noise patterns: `ResizeObserver loop`, + `Script error.`, extension namespaces (`chrome-extension://`, + `moz-extension://`, `safari-extension://`) +- Stack traces dominated by third-party domains the user doesn't control + +### Step 2 — Confirm the pattern + +For each candidate, pull a sample of `$exception` events and check that the +pattern matches what you intend to suppress: + +```json +posthog:query-error-tracking-issue-events +{ + "issueId": "", + "limit": 10, + "verbosity": "stack" +} +``` + +`onlyAppFrames` defaults to `true`, but for noise investigation you usually +want the third-party frames visible — pass `onlyAppFrames: false` so extension +URLs and vendor domains show up in the stack. + +Confirm: + +- The exception type or message text is consistent across the sample +- The URLs / user agents / browsers don't include real user traffic mixed in with + the noise +- Suppressing this pattern won't hide a future real bug that happens to share + the type + +If any sample doesn't match, narrow the filter or skip the candidate. + +### Step 3 — Scope the filter tightly + +Suppression rules are configured with the same filter shape as grouping rules. +The `error-tracking-suppression-rules-create` tool description warns explicitly: +do **not** create match-all rules and do **not** create overly broad rules. +Match on the most specific property combination you can: + +| Noise pattern | Recommended filter | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| Chrome extension errors | `$exception_sources icontains "chrome-extension://"` | +| Firefox extension errors | `$exception_sources icontains "moz-extension://"` | +| Safari extension errors | `$exception_sources icontains "safari-extension://"` | +| ResizeObserver loop | `$exception_values icontains "ResizeObserver loop"` (the message is specific; a type filter is optional) | +| Cross-origin "Script error." | `$exception_values icontains "Script error."` AND `$exception_types exact "Error"` | +| Bot user agents | `$raw_user_agent regex "(?i)bot"` for a single term; see the alternation pattern below for matching several bot/crawler markers in one rule | +| Third-party network beacon failures | `$exception_sources icontains ""` AND a type filter (e.g. `$exception_types exact "TypeError"`) | + +The canonical exception properties (`$exception_types`, `$exception_values`, +`$exception_sources`, `$exception_functions`) are arrays at capture time. The +property filter compiler [special-cases them](https://github.com/PostHog/posthog/blob/master/posthog/hogql/property.py#L904) — it parses the +JSON-materialized column and wraps the filter in +`arrayExists(v -> ..., JSONExtract(...))`, so all the standard operators +(`exact`, `is_not`, `icontains`, `not_icontains`, `regex`, `not_regex`) work +against individual elements with the bare value: `exact "TypeError"`, not +`exact '["TypeError"]'` or `regex '"TypeError"'`. + +The singular forms (`$exception_type`, `$exception_message`) and +`$exception_stack_trace_raw` are emitted on a fraction of a percent of events; +filtering on them produces a rule that silently never matches. + +Note that the `regex` operator on suppression and grouping rules compiles to +the HogVM `Operation::Regex`, which is **case-sensitive**. Use the `(?i)` +inline flag for case-insensitive matching (e.g. `(?i)headlesschrome`). + +For matching multiple bot or crawler terms, use bare pipes for alternation. +Pass this as the `value` field of the regex filter when calling the API +(`$raw_user_agent` is more reliable than the parsed `$user_agent`, which some +parsers normalize away from crawler markers): + +```text +(?i)(HeadlessChrome|bot|crawler|spider) +``` + +Whenever possible, AND together two or more conditions — type plus message, or +message plus URL pattern — so the rule is specific to the real noise. + +### Step 4 — Decide: suppress or sample + +If you want to keep some visibility, use `sampling_rate` between 0 and 1: + +- `sampling_rate: 1` — drop everything matching (full suppression) +- `sampling_rate: 0.95` — drop 95% of matching events, keep 5% as sentinel data +- `sampling_rate: 0.5` — half-rate, useful for high-volume but real errors + +Default to a non-1.0 sampling rate when there's any doubt that the pattern is +purely noise. You can tighten to 1.0 later once the data shows the rule isn't +catching real issues. + +### Step 5 — Dry-run the filter against live data + +Before asking for confirmation, run the candidate filter against the issues +list so you (and the user) can see exactly which issues the rule would have +caught over the last 7 days. `query-error-tracking-issues-list` accepts the +same property-filter shape suppression rules use via its `filterGroup` +parameter, so for a typical AND-only rule you can pass the rule's leaf +filters directly — no HogQL translation needed: + +```json +posthog:query-error-tracking-issues-list +{ + "filterGroup": [ + { "type": "event", "key": "$exception_types", "operator": "exact", "value": "Error" }, + { "type": "event", "key": "$exception_values", "operator": "icontains", "value": "ResizeObserver loop" } + ], + "dateRange": { "date_from": "-7d" }, + "status": "all", + "filterTestAccounts": false, + "orderBy": "occurrences", + "limit": 25 +} +``` + +Important defaults to override for suppression preview: + +- `status: "all"` — suppression applies regardless of issue status, so don't + let the default `active` filter hide already-archived noise. +- `filterTestAccounts: false` — the rule will not respect the test-account + toggle at ingestion. The preview should match production reality. + +Each row is one issue the rule would catch: `name` (exception type), +`description` (sample message), `source`, `library`, plus +`aggregations.occurrences` and `aggregations.users`. The issue list **is** +the per-issue breakdown — read every row. + +**The single most important safety check**: scan the result for any issue +whose `name` / `description` / `source` looks like a real bug the team +would want to fix, not noise. A filter that looks tight by message text +will routinely match unrelated issues that happen to share a phrase, and +this is the failure mode that silently destroys real data once the rule is +live. If you see anything suspicious, narrow the filter (step 3) and rerun +this step until only the genuine noise pattern is in the list. + +Add up `aggregations.occurrences` and `aggregations.users` across rows for +the blast-radius totals you'll surface to the user in step 6. If you need +exact totals across more than `limit` issues, paginate with `offset` or +fall back to the HogQL aggregate at the end of this step. + +For one or two concrete sample events with full stack traces, follow up on +the most suspicious-looking issue with `query-error-tracking-issue-events`: + +```json +posthog:query-error-tracking-issue-events +{ + "issueId": "", + "limit": 3, + "verbosity": "stack", + "onlyAppFrames": false +} +``` + +#### When you must fall back to execute-sql + +`filterGroup` is **flat AND only**. Drop into HogQL when: + +- The rule uses `type: "OR"` at the outer group or any nested OR. +- The rule uses operators not supported by `filterGroup` (e.g. `between`, + `in`, `semver_*`). +- You want a precise event-level count rather than per-issue aggregates. + +The HogQL shape mirrors what the suppression rule bytecode compiles to. +The materialized property column is nullable, so the `coalesce(..., '[]')` +wrapper is required — without it ClickHouse rejects the query with +"Nested type Array(String) cannot be inside Nullable type": + +```sql +SELECT + count() AS matched, + count(DISTINCT distinct_id) AS users, + count(DISTINCT properties.$exception_issue_id) AS issues +FROM events +WHERE event = '$exception' + AND timestamp > now() - INTERVAL 7 DAY + AND arrayExists( + v -> ifNull(ilike(v, ''), 0), + JSONExtract(coalesce(properties.$exception_values, '[]'), 'Array(String)') + ) +``` + +Use `ilike` for `icontains`, plain equality for `exact`, `match(v, +'')` for `regex`. The rule's `regex` is case-sensitive — add +`(?i)` inline if needed. + +### Step 6 — Confirm with the user before creating + +Suppression is destructive in spirit even though the API marks it +`destructive: false`. Show the user before creating: + +1. The exact filter you plan to send +2. The list of issues from step 5 with their `occurrences` and `users`, + plus the aggregate totals — call out any rows that look like real bugs +3. Whether it overlaps any existing suppression rules + (`posthog:error-tracking-suppression-rules-list` first) + +Wait for explicit confirmation. Then create: + +```json +posthog:error-tracking-suppression-rules-create +{ + "filters": { + "type": "AND", + "values": [ + { + "type": "event", + "key": "$exception_types", + "operator": "exact", + "value": "Error" + }, + { + "type": "event", + "key": "$exception_values", + "operator": "icontains", + "value": "ResizeObserver loop" + } + ] + }, + "sampling_rate": 0.95 +} +``` + +Start at `0.95` (drop 95%, keep 5% as sentinel data) so you can confirm the +rule isn't catching real errors before tightening to `1.0`. + +### Step 7 — Watch the rule for 24-48h + +After creating the rule: + +- Confirm matching events are no longer being captured by running the same + filter against a short window scoped to **after** the rule was created + (e.g. `WHERE timestamp > now() - INTERVAL 1 HOUR` once an hour has passed). + Don't re-run the 7-day estimate from step 5 — suppression only applies to + new events, so historical events in the window will still be there and the + count won't drop. +- Watch related active issues over the post-creation window — if their volume + drops while non-related issues hold steady, the rule was scoped correctly +- If a related real issue's volume drops too (false-positive), ask the user to + disable the rule via **Project settings → Error tracking → Suppression rules** + immediately and tighten the filter before re-creating it. The MCP tools to + edit or delete a rule (`error-tracking-suppression-rules-partial-update`, + `-destroy`) are not enabled — the agent has no way to recover programmatically. + +If you see signs of false positives (a real issue going quiet at the same time +the rule was created), prefer disabling the rule over deleting it — that +preserves the rule's configuration for forensic review. + +## Tips + +- Project settings → Error tracking → Suppression rules shows the same data; + mention this when the user asks where rules live in the UI. +- Suppression applies at ingestion. Existing issues from past events keep their + data; only new events are dropped. +- For a status-only change (don't drop the data, just hide it from the active + list), prefer `error-tracking-issues-partial-update` with `status: "suppressed"` + over a suppression rule. +- The schema explicitly warns the model not to create match-all rules. If the + user asks "suppress everything from extensions", still scope by stack trace or + URL — never leave `filters` empty. +- A suppression rule that turns out to be too narrow is harmless (some noise + leaks through). A rule that's too broad silently destroys real data — bias + toward narrow. diff --git a/skills/triaging-error-issues/SKILL.md b/skills/triaging-error-issues/SKILL.md new file mode 100644 index 0000000..628937f --- /dev/null +++ b/skills/triaging-error-issues/SKILL.md @@ -0,0 +1,155 @@ +--- +name: triaging-error-issues +description: > + Triage PostHog error tracking issues during a daily or on-call review. + Use when the user asks "what's broken?", "what new errors do we have?", + "show me top errors today", "what should I look at this morning", + or wants a prioritized list of active issues to work on. Surfaces new + and high-impact issues, ranks by users affected and recency, points at + linked replays, and proposes next actions (investigate, assign, suppress, + merge). +--- + +# Triaging error tracking issues + +When a user asks "what's broken?" or wants a daily error review, the goal is a short +prioritized list of issues worth a human's attention — not a dump of every active +issue. Most projects have hundreds of active issues; the few that matter are usually +new (first seen in the last 24-48h), spiking, or affecting many distinct users. + +## Available tools + +| Tool | Purpose | +| ------------------------------------------- | ------------------------------------------------------------------------- | +| `posthog:query-error-tracking-issues-list` | List + rank issues with aggregate metrics (occurrences, users, sessions) | +| `posthog:query-error-tracking-issue` | Compact details for a single issue (status, assignee, top frame, release) | +| `posthog:query-error-tracking-issue-events` | Sampled `$exception` events with stack, URL, browser, and `$session_id` | +| `posthog:query-session-recordings-list` | Find replays of users hitting an issue | +| `posthog:inbox-reports-list` | Pre-curated actionable signals if the project uses Inbox | + +## Workflow + +### Step 1 — Pick a window and a signal + +Read the time window from the user's wording. Defaults if unspecified: + +- "Today" / "this morning" / "right now" → `dateRange: { date_from: "-24h" }` +- "This week" / "since Monday" → `-7d` +- On-call shift handoff → `-24h` + +Pick what "matters" means: + +- **New issues** — `orderBy: "first_seen"`, `orderDirection: "DESC"`, tight window. + Catches regressions introduced by recent deploys. +- **High-impact** — `orderBy: "users"` ranks by distinct users affected. Better than + raw occurrences for severity (one bot loop produces many occurrences but one user). +- **Trending** — `orderBy: "occurrences"` over a short window vs a longer baseline + to spot spikes. + +### Step 2 — Pull the candidate list + +Start narrow and widen if too few issues come back: + +```json +posthog:query-error-tracking-issues-list +{ + "status": "active", + "orderBy": "users", + "orderDirection": "DESC", + "dateRange": { "date_from": "-24h" }, + "limit": 20, + "volumeResolution": 24 +} +``` + +Match `volumeResolution` to the window (24 buckets for `-24h`, 14 for `-14d`, etc.) +so each row's sparkline has enough resolution to show a spike vs flat steady state. +A single bucket only gives a total, not a shape. + +For new-issues-only, run a parallel query with `orderBy: "first_seen"`: + +```json +{ + "status": "active", + "orderBy": "first_seen", + "orderDirection": "DESC", + "dateRange": { "date_from": "-24h" }, + "limit": 10 +} +``` + +If a project mixes browser and server SDKs, the top-by-users list is usually drowned +by server-side errors (each invocation often gets a fresh `distinct_id`). Narrow with +the `library` filter — values match the SDK's `$lib`, not the npm package name, examples: + +- `web` — posthog-js (browser) +- `posthog-node`, `posthog-python`, `posthog-ruby`, `posthog-go`, `posthog-php`, `posthog-java`, `posthog-elixir` — server SDKs +- `posthog-edge` — Cloudflare Workers / edge runtime +- `posthog-ios`, `posthog-android`, `posthog-react-native`, `posthog-flutter` — mobile + +### Step 3 — Filter the noise + +The list will include known noise. Before presenting, drop or call out: + +- Issues whose volume is flat over the window — they're not new, the user already + lives with them. Surface them only if they're in the top by users. +- Bot-only issues — if all events come from headless browsers or crawler user agents, + flag for suppression (`suppressing-noisy-errors`) instead of triage. + +If unsure whether an issue is new vs. recurring, compare `first_seen` to the start +of the window: + +- `first_seen` inside the window → new, worth attention +- `first_seen` weeks ago but spiking now → regression worth attention +- `first_seen` weeks ago, flat volume → background noise + +### Step 4 — Add context for the top items + +For the top 3-5 candidates, pull a sample exception so the summary includes a stack +frame and URL, not just a title. Use `posthog:query-error-tracking-issue-events` rather than +raw SQL — it returns normalized fields (`$exception_types`, `$exception_values`, +`$current_url`, browser/OS, `$session_id`) and defaults to `onlyAppFrames: true` to +strip vendor noise from the stack: + +```json +posthog:query-error-tracking-issue-events +{ + "issueId": "", + "limit": 1, + "verbosity": "stack" +} +``` + +If the user wants to see what users were doing, hand off to `finding-replay-for-issue` +to pick the best linked recording. Don't fetch replays for every triaged issue — only +the ones the user asks to dig into. + +### Step 5 — Present the triage list + +Lead with a one-line headline ("3 new issues in last 24h, 1 spike, 5 active +high-impact"). Then a short table sorted by your chosen signal: + +| Issue | First seen | Users | Sessions | Sample message | Suggested action | +| ----- | ---------- | ----- | -------- | --------------------------------- | -------------------------- | +| ... | 2h ago | 142 | 198 | `TypeError ... at checkout.js:42` | Investigate | +| ... | spike | 67 | 89 | `Network request failed` | Watch — likely transient | +| ... | 3d ago | 12 | 12 | `chrome-extension:// timeout` | Suppress (extension noise) | + +For each, suggest one of: **investigate** (`investigating-error-issue`), **assign** +(`error-tracking-issues-partial-update`), **suppress** (`suppressing-noisy-errors`), +**merge** (`grouping-noisy-errors`), or **resolve** if it's already known fixed. + +## Tips + +- A single deploy often surfaces several related new issues. If multiple new issues + share a `properties.$lib_version` (or `properties.$exception_releases` when the + SDK is configured to populate it), present them grouped — a rollback decision + rests on the cluster, not any one issue. +- "Users" is the right severity proxy for user-facing apps. For backend services + without a real distinct_id concept, fall back to `sessions` or `occurrences`. +- Don't auto-assign or auto-resolve as part of triage. Present the list and let the + user decide. Bulk actions belong in dedicated skills. +- If the project uses Inbox (`posthog:inbox-reports-list`), check it first — PostHog + may have already curated the most actionable issues so you avoid re-deriving them. +- Provide the issue URL (`/error_tracking/`) for each row so the user can jump + straight to the issue page if they want to drill down themselves.