Skip to content

Copilot Analytics

Operational dashboards for the Copilot chat widget and the docs site. All metrics flow into a single Application Insights resource: appi-csa-inabox-copilot-fg (East US 2). Raw chat content lives separately in Cosmos DB: cosmos-csa-inabox-copilot-fgcopilot database.

Where to look

What Where URL
Live request stream Application Insights → Live Metrics Open
Custom event explorer Application Insights → Logs (Analytics) Same resource → "Logs"
Saved queries / workbook Application Insights → Workbooks → "Copilot Analytics" (created from this doc)
Raw chat conversations Cosmos DB → copilot/conversations Data Explorer
Auto-filed backlog issues GitHub Issues with the from-copilot label Filtered list
Live function logs Application Insights → Transaction search Same resource → "Search"

Pre-built KQL queries

Paste these into Application Insights → Logs to get instant answers. Each runs in seconds against the last 24 hours; change ago(24h) to widen the window.

1. Chat health overview (last 24h)

customEvents
| where timestamp > ago(24h)
| where name startswith "chat."
| summarize count() by name, bin(timestamp, 1h)
| render timechart

What you'll see: a stacked time-chart of chat.request, chat.feedback, chat.backlog_submission, chat.rejected, chat.error events binned by hour.

2. Chat success / failure ratio

customEvents
| where timestamp > ago(24h) and name in ("chat.request", "chat.error", "chat.rejected")
| summarize requests = countif(name == "chat.request"),
            errors   = countif(name == "chat.error"),
            rejected = countif(name == "chat.rejected")
| extend success_rate = round(100.0 * requests / (requests + errors + rejected), 1)

3. Latency percentiles

customEvents
| where timestamp > ago(24h) and name == "chat.request"
| extend latency_ms = toint(customDimensions.latency_ms)
| summarize p50 = percentile(latency_ms, 50),
            p90 = percentile(latency_ms, 90),
            p99 = percentile(latency_ms, 99),
            avg = round(avg(latency_ms), 0)
            by bin(timestamp, 1h)
| render timechart

4. Uncovered-question rate

How often does the Copilot fall back to "off-topic refusal" or zero grounding hits? High rates point at documentation gaps.

customEvents
| where timestamp > ago(7d) and name == "chat.request"
| summarize total = count(),
            uncovered = countif(tostring(customDimensions.uncovered) == "True")
| extend uncovered_pct = round(100.0 * uncovered / total, 1)

5. Top documentation pages driving chat

Which pages send users to ask the Copilot? Useful for finding pages that aren't self-explanatory.

customEvents
| where timestamp > ago(7d) and name == "chat.request"
| summarize chats = count() by page = tostring(customDimensions.page_url)
| where chats > 1
| order by chats desc
| take 20

6. Feedback summary (thumbs up/down)

customEvents
| where timestamp > ago(30d) and name == "chat.feedback"
| summarize count() by rating = tostring(customDimensions.rating)
| render piechart

7. Top distinct sessions (by chat volume)

customEvents
| where timestamp > ago(7d) and name == "chat.request"
| summarize chats = count(), first = min(timestamp), last = max(timestamp)
            by session = tostring(customDimensions.session_id)
| order by chats desc
| take 20

8. Token-budget burndown

Tracks the tokens_used dimension to forecast spend.

customEvents
| where timestamp > ago(30d) and name == "chat.request"
| extend tokens = toint(customDimensions.tokens_used)
| summarize total_tokens = sum(tokens), avg_per_chat = avg(tokens) by bin(timestamp, 1d)
| render timechart

9. Docs-site page-views (page-load analytics)

pageViews
| where timestamp > ago(7d)
| where customDimensions.site == "docs"  // exclude future apps that share this AI
| summarize views = count() by tostring(name), bin(timestamp, 1d)
| render columnchart

10. Top docs pages by traffic

pageViews
| where timestamp > ago(30d) and customDimensions.site == "docs"
| summarize views = count(), users = dcount(user_Id) by name
| order by views desc
| take 20

11. Geographic distribution

pageViews
| where timestamp > ago(30d) and customDimensions.site == "docs"
| summarize views = count() by client_CountryOrRegion, client_StateOrProvince
| order by views desc

12. Multi-page user flows

pageViews
| where timestamp > ago(7d) and customDimensions.site == "docs"
| summarize pages_viewed = dcount(name), session_length = max(timestamp) - min(timestamp)
            by user_Id
| where pages_viewed > 1
| summarize bins = count() by pages_viewed
| order by pages_viewed asc

13. Bounce rate (single-page sessions)

pageViews
| where timestamp > ago(7d) and customDimensions.site == "docs"
| summarize page_count = dcount(name) by user_Id
| summarize bounces = countif(page_count == 1), total = count()
| extend bounce_rate = round(100.0 * bounces / total, 1)

14. Slow page loads (P90 over 3s)

pageViews
| where timestamp > ago(7d) and customDimensions.site == "docs"
| summarize p90_load_ms = percentile(duration, 90) by name
| where p90_load_ms > 3000
| order by p90_load_ms desc

Cosmos DB queries (raw chat content)

The App Insights customEvents table holds dimensions only — no message bodies. For full-text analysis (e.g. clustering uncovered questions), pull from the Cosmos conversations container:

-- Cosmos SQL (NOT KQL). Run via Azure Portal → Cosmos DB →
-- Data Explorer → conversations → New SQL Query.

-- The 50 most recent uncovered questions
SELECT TOP 50 c.user_message, c.ts, c.session_id, c.page_url
FROM c
WHERE c.uncovered = true
ORDER BY c.ts DESC

-- Chat-feedback paired with the original question
SELECT c.session_id, c.conversation_id, c.user_message, c.assistant_reply
FROM c
WHERE c.conversation_id IN (
    SELECT VALUE f.conversation_id
    FROM feedback f
    WHERE f.rating = "down"
)

(Cross-container queries aren't natively supported in Cosmos SQL — for ad-hoc analysis, export both containers via the SDK and merge in Python / pandas.)

Building a workbook

Application Insights workbooks let you save these queries as a single dashboard. To bootstrap one:

  1. Open the AI resource → Workbooks+ New
  2. Pick "Empty"
  3. Add Query sections for each KQL block above
  4. Save as "Copilot Analytics" — pin to the resource

The first time you save, the workbook lives at the resource scope. You can share its URL with the team for read-only access (RBAC at the resource level: viewers need Reader on the AI resource).

Operational signals to watch

Signal Threshold What it usually means
chat.error rate > 1% red Azure OpenAI throttling, AOAI key/MI auth failure, timeout
Latency P99 > 30s red OpenAI degradation, network egress issue
chat.rejected reason=injection spiking yellow Either a determined attacker or false positives — review the messages
Uncovered rate > 50% over 7d yellow Doc corpus is missing material; review the top uncovered queries from Cosmos
Backlog status=open count growing yellow Drain workflow stalled — check Copilot Backlog Drain workflow runs
Storage / telemetry pipeline enabled=false red App Insights or Cosmos misconfigured — function still serves chat but you're flying blind

Privacy notes

Page analytics honor Do Not Track and the chat opt-out — see Copilot Privacy Notice. Aggregate metrics remain useful even with opt-out users excluded; the analytics shouldn't be used as a definitive traffic counter.