diff --git a/context/commandments.yaml b/context/commandments.yaml index 8c06db3c..6325aea3 100644 --- a/context/commandments.yaml +++ b/context/commandments.yaml @@ -73,6 +73,7 @@ commandments: - 'In CLIs and scripts: MUST call posthog.shutdown() before exit or all events are lost' - Always use the Posthog() class constructor (instance-based API) instead of module-level posthog.api_key config - Always include enable_exception_autocapture=True in the Posthog() constructor to automatically track exceptions + - PosthogFlaskIntegration (posthog.integrations.flask), PosthogASGIMiddleware (posthog.integrations.asgi), and the Django REST Framework exception handler (posthog.integrations.drf) need posthog 7.69.0 or later - when you add one, require at least that version - NEVER send PII in capture() event properties — no emails, full names, phone numbers, physical addresses, IP addresses, or user-generated content - PII belongs in identify() person properties, NOT in capture() event properties. Safe event properties are metadata like message_length, form_type, boolean flags. - Register posthog_client.shutdown with atexit.register() to ensure all events are flushed on exit @@ -141,19 +142,20 @@ commandments: django: - Add 'posthog.integrations.django.PosthogContextMiddleware' to MIDDLEWARE, after AuthenticationMiddleware, it auto-extracts tracing headers and captures exceptions - - Initialize PostHog in AppConfig.ready() with api_key and host from environment variables + - Pass the Posthog() client to the middleware with the POSTHOG_MW_CLIENT setting, as the Django docs show - do not assign django.conf.settings at runtime, such as in AppConfig.ready() - The middleware identifies the request context from the X-POSTHOG-DISTINCT-ID header or the authenticated user's pk, so in a view where the user is already logged in a plain capture() is already attributed - do not wrap it in a context of its own - The middleware reads the user once, before the view runs, so a login request's context is identified as whoever the user was beforehand - nobody - and calling login() does not update it. A bare capture there is personless. Hook Django's user_logged_in signal and call identify_context(str(user.pk)) inside it: the signal runs inside the login request, so it fixes the ambient context and every later capture in that request is attributed. Logout views need no special handling, the user is still authenticated when the middleware runs - capture before calling logout() - Do NOT create custom middleware, distinct_id helpers, or conditional checks - the SDK handles these flask: - - Initialize PostHog globally in create_app() using posthog.api_key and posthog.host (NOT per-request) - - Manually capture exceptions with `posthog.capture_exception(e)` for error tracking since Flask has built-in error handlers + - Register PosthogFlaskIntegration with one Posthog() client, as the Flask docs show (NOT per-request) + - Do NOT add an @app.errorhandler(Exception) to call capture_exception() - the integration already captures unhandled route exceptions - Blueprint registration happens AFTER PostHog initialization in create_app() fastapi: - - Initialize PostHog in the lifespan context manager on startup using posthog.api_key and posthog.host + - Add PosthogASGIMiddleware, as the ASGI section of the Python docs shows. Create the client where you build the app, not in the lifespan - middleware cannot be added once the app starts - Call posthog.flush() in the lifespan shutdown to ensure all events are sent before the app exits + - Do NOT call capture_exception() in an @app.exception_handler(Exception) catch-all - the middleware already captures those exceptions - Use Pydantic Settings with @lru_cache decorator on get_settings() for caching and easy test overrides - Use FastAPI dependency injection (Depends) for accessing current_user and settings in route handlers - Use the same context API pattern as Flask/Django (with new_context(), identify_context(user_id), then capture()) diff --git a/context/skills/posthog-best-practices/description.md b/context/skills/posthog-best-practices/description.md index 2d5e21f6..5c729fa6 100644 --- a/context/skills/posthog-best-practices/description.md +++ b/context/skills/posthog-best-practices/description.md @@ -160,7 +160,8 @@ If the project uses Django: context identified as whoever they were beforehand, which is nobody on a login. There, open a fresh context and identify it after the auth state changes, or the event is attributed to the wrong person. -- Initialize PostHog in `AppConfig.ready()` from env vars. +- Pass the `Posthog()` client to the middleware with `POSTHOG_MW_CLIENT`, as the + Django docs show. - Do not write custom middleware or `distinct_id` helpers first. - For management commands or other short-lived processes, call the underlying Python client's `posthog.shutdown()` before exit. - If this applies, consult [the Django docs](https://posthog.com/docs/libraries/django). @@ -169,11 +170,10 @@ If the project uses Django: If the project uses Flask: -- Initialize PostHog globally in `create_app()` before blueprint registration. -- No middleware ships for Flask, so nothing binds identity for you: open a fresh - context at the top of each route and identify it there, preferring the - authenticated user id and falling back to the incoming `X-POSTHOG-DISTINCT-ID`. -- Call `posthog.capture_exception(e)` from centralized error handlers. +- Initialize PostHog in `create_app()` before blueprint registration, and + register `PosthogFlaskIntegration`, as the Flask docs show. It opens a context + for each request and captures route exceptions. Identify the user with + `identify_context(...)` in the route. - For CLI commands, jobs, or other short-lived processes, call the underlying Python client's `posthog.shutdown()` before exit. - If this applies, consult [the Flask docs](https://posthog.com/docs/libraries/flask). @@ -181,10 +181,10 @@ If the project uses Flask: If the project uses FastAPI: -- Initialize PostHog in lifespan startup and call `posthog.shutdown()` in lifespan shutdown. -- No middleware ships for FastAPI either — bind identity per request yourself, in - a dependency or middleware that opens a context and identifies it, rather than - at each capture. +- Add `PosthogASGIMiddleware`, as the Python docs show, and call + `posthog.shutdown()` in lifespan shutdown. The middleware opens a context for + each request and captures route exceptions. Identify the user in a dependency + rather than at each capture. - Prefer Pydantic Settings with `@lru_cache`. - Use `Depends` for current user and settings in handlers. - If this applies, consult [the Python docs](https://posthog.com/docs/libraries/python).