{"$schema":"https://keren.run/.well-known/telemetry-contract.json","contractVersion":"50d2888a2f2f","generatedAt":"2026-06-27T22:08:43.670Z","product":{"name":"Keren Analytics","url":"https://keren.run","repository":"https://github.com/lionelgarnier/keren-analytics","description":"Plug-and-play analytics over Azure Application Insights telemetry. This contract tells coding agents which signals to emit, how to name custom dimensions, and which config values make telemetry render well."},"privacy":{"rawLogsLeaveTenant":false,"summary":"Keren stores only aggregates — no raw log rows, no PII. Telemetry you emit is queried in place via KQL; pseudonymize user ids and never send PII."},"scoring":{"maxScore":120,"grades":[{"grade":"A","minPercentage":90},{"grade":"B","minPercentage":75},{"grade":"C","minPercentage":55},{"grade":"D","minPercentage":35}],"note":"Score = sum of points for signals present. Aim for grade A (>=90%). Points and grades mirror the live readiness scorer."},"signals":[{"id":"pageViews","label":"Page Views","points":20,"category":"required","description":"Frontend page view tracking","appInsightsTable":"pageViews","howToEmit":"Application Insights JS SDK: enable enableAutoRouteTracking for SPAs, or call trackPageView() on each route/page load.","verifyKql":"pageViews | summarize count() by bin(timestamp, 1h)"},{"id":"requests","label":"Backend Requests","points":15,"category":"required","description":"Server-side request telemetry","appInsightsTable":"requests","howToEmit":"Server SDK request auto-collection (applicationinsights for Node, Microsoft.ApplicationInsights for .NET, the Java agent, opencensus-ext-azure for Python).","verifyKql":"requests | summarize count() by bin(timestamp, 1h)"},{"id":"sessionId","label":"Session Tracking","points":15,"category":"required","description":"Session identification","appInsightsField":"session_Id","howToEmit":"Auto-generated by the JS SDK — do not set disableSessionTracking. Server-only telemetry has no sessions; add a frontend SDK.","verifyKql":"pageViews | where isnotempty(session_Id) | count"},{"id":"userId","label":"User Identity","points":15,"category":"recommended","description":"Authenticated user tracking","appInsightsField":"user_AuthenticatedId","howToEmit":"Call setAuthenticatedUserContext(hashedId) right after login on both frontend and backend. Use a stable pseudonymous id — never raw email.","verifyKql":"pageViews | where isnotempty(user_AuthenticatedId) | summarize dcount(user_AuthenticatedId) by bin(timestamp, 1d)"},{"id":"browserTimings","label":"Frontend Perf","points":15,"category":"optional","description":"Browser timing metrics","appInsightsTable":"browserTimings","howToEmit":"JS SDK with enableAutoRouteTracking + autoTrackPageVisitTime; keep sampling moderate so timings aren't filtered out.","verifyKql":"browserTimings | summarize count() by bin(timestamp, 1h)"},{"id":"userAgent","label":"Device & Browser","points":10,"category":"recommended","description":"Client environment details","appInsightsFields":["client_Browser","client_OS","client_Type"],"howToEmit":"Auto-collected by the JS SDK from the User-Agent header.","verifyKql":"pageViews | where isnotempty(client_Browser) | count"},{"id":"geo","label":"Geo Location","points":10,"category":"optional","description":"Geographic enrichment","appInsightsField":"client_CountryOrRegion","howToEmit":"Server-side IP geo-enrichment by Azure. Behind a proxy/CDN, forward X-Forwarded-For so the client IP (not the proxy's) is resolved.","verifyKql":"pageViews | where isnotempty(client_CountryOrRegion) | summarize count() by client_CountryOrRegion"},{"id":"dependencies","label":"Dependencies","points":10,"category":"recommended","description":"Outbound call telemetry (SQL, HTTP, queues)","appInsightsTable":"dependencies","howToEmit":"Enable dependency auto-collection in the server SDK config.","verifyKql":"dependencies | summarize count() by type"},{"id":"exceptions","label":"Exceptions","points":10,"category":"recommended","description":"Server-side exception capture","appInsightsTable":"exceptions","howToEmit":"Server SDK exception auto-collection plus trackException() in a global error handler.","verifyKql":"exceptions | summarize count() by bin(timestamp, 1h)"}],"namingConventions":[{"canonicalField":"userId","builtinExpressions":["user_AuthenticatedId","user_Id"],"customDimensionExpression":"tostring(customDimensions[\"userId\"])","recommendedCustomDimensionName":"userId","recognizedNames":["userId","user_id","uid","userid","userHash","user_hash","visitorId","visitor_id","authenticatedUser","authenticated_user","accountId","account_id","memberId","member_id","profileId","profile_id","sub","subject"],"recognizedNamePattern":"^(user|visitor|member|account|profile|customer)[_-]?(id|hash|key|ref)$","note":"If you emit this as a custom dimension, name it one of recognizedNames (or match recognizedNamePattern) so Keren auto-maps it with no manual setup."},{"canonicalField":"sessionId","builtinExpressions":["session_Id","operation_Id"],"customDimensionExpression":"tostring(customDimensions[\"sessionId\"])","recommendedCustomDimensionName":"sessionId","recognizedNames":["sessionId","session_id","sid","sessionKey","session_key","visitId","visit_id","browsing_session"],"recognizedNamePattern":"^(session|visit|browsing)[_-]?(id|key|token|ref)$","note":"If you emit this as a custom dimension, name it one of recognizedNames (or match recognizedNamePattern) so Keren auto-maps it with no manual setup."},{"canonicalField":"pagePath","builtinExpressions":["tostring(parse_url(url).Path)","extract(\"(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|RSC GET|RSC POST)\\\\s+([^?\\\\s]+)\", 1, name)"],"customDimensionExpression":"tostring(customDimensions[\"page\"])","recommendedCustomDimensionName":"page","recognizedNames":["page","pagePath","page_path","pageName","page_name","pageRoute","page_route","route","path","urlPath","url_path","screen","screenName","screen_name","view"],"recognizedNamePattern":"^(page|screen|route|view)[_-]?(path|name|route|url)$","note":"If you emit this as a custom dimension, name it one of recognizedNames (or match recognizedNamePattern) so Keren auto-maps it with no manual setup."},{"canonicalField":"referrer","builtinExpressions":["tostring(customDimensions[\"refUri\"])","tostring(customDimensions[\"referrer\"])","tostring(customDimensions[\"http.request.header.referer\"])"],"customDimensionExpression":null,"recommendedCustomDimensionName":"refUri","recognizedNames":["refUri","referrer","ref_uri","referrerUrl","referrer_url","referer","httpReferer","http_referer","source","trafficSource","traffic_source","utm_source","campaign_source"],"recognizedNamePattern":"^(ref|referr?er|traffic|campaign)[_-]?(uri|url|source)$","note":"If you emit this as a custom dimension, name it one of recognizedNames (or match recognizedNamePattern) so Keren auto-maps it with no manual setup."},{"canonicalField":"browser","builtinExpressions":["client_Browser"],"customDimensionExpression":null,"recommendedCustomDimensionName":"browser","recognizedNames":["browser","client_browser","userBrowser","user_browser","ua_browser","browserName","browser_name"],"recognizedNamePattern":"^(client[_-]?)?(ua[_-]?)?browser([_-]?name)?$","note":"If you emit this as a custom dimension, name it one of recognizedNames (or match recognizedNamePattern) so Keren auto-maps it with no manual setup."},{"canonicalField":"os","builtinExpressions":["client_OS"],"customDimensionExpression":null,"recommendedCustomDimensionName":"os","recognizedNames":["os","client_os","operatingSystem","operating_system","platform","ua_os","osName","os_name"],"recognizedNamePattern":"^(client[_-]?)?(ua[_-]?)?(os|operating[_-]?system|platform)([_-]?name)?$","note":"If you emit this as a custom dimension, name it one of recognizedNames (or match recognizedNamePattern) so Keren auto-maps it with no manual setup."},{"canonicalField":"device","builtinExpressions":["client_Type"],"customDimensionExpression":null,"recommendedCustomDimensionName":"device","recognizedNames":["device","deviceType","device_type","client_type","formFactor","form_factor","deviceClass","device_class"],"recognizedNamePattern":"^(client[_-]?type|device([_-]?(type|class))?|form[_-]?factor)$","note":"If you emit this as a custom dimension, name it one of recognizedNames (or match recognizedNamePattern) so Keren auto-maps it with no manual setup."}],"instrumentationConfig":[{"id":"connection-string","title":"Connection string from env, never hardcoded","detail":"Read the App Insights connection string from APPLICATIONINSIGHTS_CONNECTION_STRING. Never commit it. The browser SDK uses a write-only ingestion key — safe to expose to the page."},{"id":"init-order","title":"Initialize the SDK first","detail":"Set up the server SDK before the web framework loads (first import) so the HTTP stack is patched and requests/dependencies are auto-collected."},{"id":"no-pii","title":"No PII in telemetry","detail":"Never emit email, full name, raw IP, or tokens. Pseudonymize user ids (hash). Keren scrubs known PII patterns from samples, but the contract is: don't send it."},{"id":"exclude-health-checks","title":"Exclude health-check endpoints","detail":"Filter probe routes (e.g. /healthz) out of request telemetry so they don't drown real traffic in the Technical view."},{"id":"flush-on-shutdown","title":"Flush on shutdown","detail":"Flush pending telemetry on SIGTERM so the last batch of events isn't lost when the container stops."},{"id":"cloud-role","title":"Set cloud_RoleName per service","detail":"Tag each service with a distinct cloud_RoleName so multi-service topologies stay distinguishable in the dashboard."},{"id":"sampling","title":"Sample, but not away your conversions","detail":"Use adaptive sampling to control cost, but keep it moderate enough that low-volume conversion events and browser timings survive."}],"stacks":[{"key":"react","name":"React (SPA)","recommendedSdk":"@microsoft/applicationinsights-react-js"},{"key":"angular","name":"Angular (SPA)","recommendedSdk":"@microsoft/applicationinsights-web"},{"key":"vue","name":"Vue.js (SPA)","recommendedSdk":"@microsoft/applicationinsights-web"},{"key":"node","name":"Node.js","recommendedSdk":"applicationinsights"},{"key":"dotnet","name":".NET / ASP.NET","recommendedSdk":"Microsoft.ApplicationInsights"},{"key":"python","name":"Python","recommendedSdk":"opencensus-ext-azure"},{"key":"java","name":"Java / Spring","recommendedSdk":"applicationinsights-agent"},{"key":"generic","name":"Web Application","recommendedSdk":"@microsoft/applicationinsights-web"}],"recipes":[{"signal":"pageViews","label":"Page View Tracking","prompt":"My Web Application application is connected to Azure Application Insights (resource: [your-resource-name]), but **no pageView telemetry is reaching Application Insights**.\n\n## What I need\nPage view events must appear in the Application Insights `pageViews` table, with the page URL path and page title.\n\n## Your approach\n1. **Audit** — Search this codebase for any existing Application Insights setup: SDK imports, configuration files, connection strings, `trackPageView` calls, or equivalent. Also check package.json / dependencies for `@microsoft/applicationinsights-web` or similar packages.\n2. **Gap analysis** — Based on what you find, identify the specific reason pageViews are missing. Common causes:\n   - SDK installed but pageView auto-tracking is disabled\n   - SDK configured but the connection string / instrumentation key is wrong or missing\n   - SDK not initialized early enough in the app lifecycle\n   - SDK not installed at all\n3. **Fix** — Make the minimal changes needed to close the gap. If the SDK is already installed, do NOT reinstall or reconfigure from scratch — only adjust what's missing. If nothing exists, then set up the SDK with pageView tracking enabled.\n\nDo NOT send any PII (no email, no full name) in telemetry."},{"signal":"requests","label":"Backend Request Tracking","prompt":"My Web Application application is connected to Azure Application Insights (resource: [your-resource-name]), but **no server-side request telemetry is reaching Application Insights**.\n\n## What I need\nHTTP requests must appear in the Application Insights `requests` table, including method, URL, status code, and duration.\n\n## Your approach\n1. **Audit** — Search this codebase for any existing Application Insights server-side setup: SDK imports (e.g. `@microsoft/applicationinsights-web`), initialization code, middleware, connection strings. Check package/dependency files.\n2. **Gap analysis** — Identify why requests are not being tracked. Common causes:\n   - SDK installed but not initialized before the web framework starts listening\n   - Auto-collection is disabled in configuration\n   - Connection string / instrumentation key is wrong or missing\n   - SDK not installed at all\n3. **Fix** — Make the minimal changes needed. If the SDK is already present, just adjust the configuration. If it's missing entirely, add server-side telemetry with request auto-collection enabled. Also consider:\n   - Dependency tracking for outbound calls (databases, APIs) — enable if not already\n   - Adaptive sampling to control costs"},{"signal":"userId","label":"User Identity","prompt":"My Web Application application is connected to Azure Application Insights (resource: [your-resource-name]), but **no authenticated user ID (`user_AuthenticatedId`) appears in the telemetry**.\n\n## What I need\nAfter a user logs in, all subsequent telemetry (pageViews, requests, custom events) must carry a pseudonymized user identifier in the `user_AuthenticatedId` field.\n\n## Your approach\n1. **Audit** — Search this codebase for:\n   - How authentication works (auth provider, session management, login flow)\n   - Any existing calls to `setAuthenticatedUserContext()`, `context.user`, or telemetry initializers that set user identity\n   - Where the Application Insights SDK is initialized (frontend and/or backend)\n2. **Gap analysis** — Identify why the user ID is missing. Common causes:\n   - Authentication exists but nobody wired it to Application Insights\n   - `setAuthenticatedUserContext()` is called with the wrong arguments or at the wrong time\n   - User ID is set on frontend but not propagated to backend, or vice versa\n3. **Fix** — Wire the authenticated user ID to Application Insights at the right point in the auth flow. Use a hashed or pseudonymized ID — never raw email or PII. Show changes for both frontend and backend if both exist."},{"signal":"userIdDegraded","label":"User Identity (degraded)","prompt":"My Web Application application is connected to Azure Application Insights (resource: [your-resource-name]). Users **do authenticate**, but the `user_AuthenticatedId` field is nearly empty — less than 10 % of telemetry carries it. As a result, unique-visitor counts fall back to anonymous IDs (`user_Id`) and are **significantly over-estimated**.\n\n## What I need\nAfter a user logs in, every subsequent telemetry item (pageViews, requests, custom events) must carry a stable, pseudonymized identifier in `user_AuthenticatedId`.\n\n## Your approach\n1. **Audit** — Search this codebase for:\n   - How authentication works (auth provider, session/token management, login flow)\n   - Where the Application Insights SDK is initialized — both frontend and backend\n   - Any existing calls to `setAuthenticatedUserContext()` or telemetry initializers that set `user_AuthenticatedId`\n   - Whether the user ID is available in a cookie, JWT claim, or server-side session after login\n2. **Gap analysis** — The SDK is collecting data, but the authenticated user ID is missing from almost all events. Common causes:\n   - `setAuthenticatedUserContext()` is never called after login\n   - It is called, but at the wrong time (before the auth token is available, or only on one page)\n   - The frontend sets it, but the backend SDK doesn't receive or propagate it\n   - The value passed is empty, null, or changes across sessions for the same user\n3. **Fix** — Wire the authenticated user ID at the earliest reliable point after login:\n   - **Frontend (JS SDK):** call `appInsights.setAuthenticatedUserContext(hashedUserId)` once the user session is established. Use a hashed or opaque ID — never raw email or PII.\n   - **Backend (.NET / Node / Java / Python):** set `TelemetryContext.User.AuthenticatedUserId` (or equivalent) via a telemetry initializer or middleware that reads the auth session.\n   - Ensure the same stable ID is used on both sides so frontend and backend telemetry can be correlated.\n\nDo NOT send any PII (no email, no full name) in telemetry."},{"signal":"sessionId","label":"Session Tracking","prompt":"My Web Application application is connected to Azure Application Insights (resource: [your-resource-name]), but **no `session_Id` appears in the telemetry**.\n\n## What I need\nEvery telemetry item must carry a `session_Id` that groups user activity into sessions (default 30-minute timeout is fine).\n\n## Your approach\n1. **Audit** — Search this codebase for the Application Insights SDK configuration. Check whether session tracking is explicitly disabled, or if a custom session mechanism overrides the default behavior.\n2. **Gap analysis** — Session IDs are normally auto-generated by the JavaScript SDK. Common reasons they're missing:\n   - SDK is configured with `disableSessionTracking: true` or equivalent\n   - A custom telemetry initializer strips the session ID\n   - The SDK is only server-side with no frontend component (server SDK doesn't auto-generate sessions)\n   - Cookie consent blocking prevents the session cookie from being set\n3. **Fix** — Make the minimal adjustment to restore session tracking. This is usually a configuration flag, not new code."},{"signal":"userAgent","label":"Device & Browser Info","prompt":"My Web Application application is connected to Azure Application Insights (resource: [your-resource-name]), but **device and browser information (`client_Browser`, `client_OS`) is missing from the telemetry**.\n\n## What I need\nTelemetry must include the user's browser name, OS, and device type — populated automatically from the user-agent header.\n\n## Your approach\n1. **Audit** — Search this codebase for the Application Insights JavaScript SDK setup. Check if it's loaded on all pages and whether any configuration disables user-agent collection.\n2. **Gap analysis** — This data is normally auto-collected by the JS SDK. Common reasons it's missing:\n   - No frontend JavaScript SDK (only server-side tracking)\n   - SDK loaded but user-agent parsing is disabled\n   - A telemetry initializer or proxy strips these fields\n3. **Fix** — If the JS SDK is already present, verify the configuration. If there's no frontend SDK, add the minimal JavaScript snippet to collect client-side context. This is typically a small configuration change, not a major implementation."},{"signal":"geo","label":"Geo Enrichment","prompt":"My Web Application application is connected to Azure Application Insights (resource: [your-resource-name]), but **no geographic information (country, city) appears in the telemetry**.\n\n## What I need\nTelemetry must include geo-location data so we can analyze usage by region.\n\n## Your approach\n1. **Audit** — Check how the application is deployed:\n   - Is it behind a reverse proxy, CDN, or load balancer?\n   - Is the Application Insights SDK sending telemetry directly from the client browser, or only from the server?\n   - Check the Azure Portal: is geo-enrichment enabled on the Application Insights resource?\n2. **Gap analysis** — Geo-enrichment is done server-side by Azure based on the client IP. Common reasons it fails:\n   - A reverse proxy or load balancer replaces the client IP with its own, and `X-Forwarded-For` is not configured\n   - All telemetry is server-side only, and the server's IP (not the user's) gets geo-resolved\n   - IP collection is explicitly disabled in the SDK configuration\n3. **Fix** — The fix depends on the root cause:\n   - If behind a proxy: ensure `X-Forwarded-For` is forwarded\n   - If no frontend SDK: add client-side telemetry or forward the real client IP\n   - If IP collection is disabled: re-enable it (note: Application Insights can use IP for geo without storing it)"},{"signal":"browserTimings","label":"Frontend Performance","prompt":"My Web Application application is connected to Azure Application Insights (resource: [your-resource-name]), but **no browser timing / page load performance data appears in the telemetry**.\n\n## What I need\nThe Application Insights `browserTimings` table must receive page load performance metrics: network time, DOM processing time, and ideally Web Vitals (LCP, FID, CLS).\n\n## Your approach\n1. **Audit** — Search this codebase for the Application Insights JavaScript SDK. Check the configuration for performance-related settings: `enableAutoRouteTracking`, `enableAjaxPerfTracking`, `maxAjaxCallsPerView`, and any custom performance tracking.\n2. **Gap analysis** — Browser timings require the JS SDK on the frontend. Common reasons they're missing:\n   - No frontend JavaScript SDK (server-side only)\n   - JS SDK present but auto-tracking of performance is disabled\n   - SDK initialized too late (after page load events have already fired)\n   - Sampling is too aggressive, filtering out performance data\n3. **Fix** — If the JS SDK is present, enable browser timing collection in the configuration. If no frontend SDK exists, add the minimal setup with performance tracking enabled. Consider:\n   - `enableAutoRouteTracking: true` for SPA navigation timing\n   - `enableAjaxPerfTracking: true` for XHR/fetch performance\n   - Reasonable sampling to control costs without losing performance visibility"}]}