No theory. Each entry = a real question → the query → what the result looks like → how to read it. Open Logs in your App Insights resource and paste.
Quick vocab you'll see in every query: requests = calls coming in (APIM operation hit, Logic App run, Function trigger). dependencies = calls going out (NetSuite, Greenhouse, SQL, Key Vault, LLM). exceptions = errors with stack traces. operation_Id = the ID that ties one end-to-end action together across all services. cloud_RoleName = which service emitted the row.
A. "Is it even working?"
1. Did my endpoint get called at all in the last hour?
requests
| where timestamp > ago(1h)
| where name has "CreateByCustomAttributes"
| project timestamp, name, resultCode, success, duration, cloud_RoleName
| order by timestamp desc
Sample output:
| timestamp | name | resultCode | success | duration | cloud_RoleName |
|---|---|---|---|---|---|
| 10:42:11 | POST /v1/Custom/CreateByCustomAttributes | 404 | false | 38 ms | oiboomi-prod |
| 10:41:55 | POST /v1/Custom/CreateByCustomAttributes | 404 | false | 41 ms | oiboomi-prod |
Read it: rows exist → the call is reaching APIM. Empty result → the caller never got to the gateway (DNS, network, wrong URL) — the problem is upstream of App Insights, not in your API.
2. Success vs failure count for one operation today
requests
| where timestamp > ago(24h)
| where name has "CreateByCustomAttributes"
| summarize total=count(), failed=countif(success==false) by name
| extend failRate=round(100.0*failed/total,1)
| name | total | failed | failRate |
|---|---|---|---|
| POST …/CreateByCustomAttributes | 214 | 214 | 100 |
Read it: 100% fail = config/routing, not intermittent. A number like 3% = flaky (timeouts, race conditions) — different hunt entirely.
B. The 404 — full debug, the way you'd actually do it
3. Grab a failing call and get its operation_Id
requests
| where timestamp > ago(1d)
| where name has "CreateByCustomAttributes" and success == false
| project timestamp, resultCode, operation_Id, url
| take 1
| timestamp | resultCode | operation_Id | url |
|---|---|---|---|
| 10:42:11 | 404 | a1b2c3d4e5f6... |
https://…/v1/Custom/CreateByCustomAttributes |
4. ⭐ The key query — is the 404 from APIM or from the backend?
Paste the operation_Id from above:
let opId = "a1b2c3d4e5f6...";
union requests, dependencies, exceptions, traces
| where operation_Id == opId
| project timestamp, itemType, name, target, resultCode, success, message=outerMessage
| order by timestamp asc
Case 1 — no dependency row:
| timestamp | itemType | name | target | resultCode |
|---|---|---|---|---|
| 10:42:11.100 | request | POST …/CreateByCustomAttributes | | 404 |
→ APIM returned 404 without ever calling the backend. Meaning: the operation/route isn't matched in oiboomi-prod — wrong API path, operation not added to the product, or the URL template doesn't match. Fix is in APIM, not the API.
Case 2 — dependency row present, also 404:
| timestamp | itemType | name | target | resultCode |
|---|---|---|---|---|
| 10:42:11.100 | request | POST …/CreateByCustomAttributes | | 404 |
| 10:42:11.118 | dependency | POST /Custom/CreateByCustomAttributes | ConciousIntegrationAPIService | 404 |
→ APIM did forward it and the backend ConciousIntegrationAPIService returned 404. Meaning: the backend route genuinely doesn't exist at that path — check the backend base URL / rewrite policy / actual controller route.
This one query settles the argument. That "is there a dependency row or not" distinction is 90% of the value of App Insights for you.
5. See the full URL + rewritten path APIM actually sent
let opId = "a1b2c3d4e5f6...";
dependencies
| where operation_Id == opId
| project timestamp, target, name, data, resultCode
data shows the exact URL APIM hit downstream — compare it against what the backend expects. Nine times out of ten a /api prefix or version segment is missing here.
C. Performance / "why is it slow?"
6. Slowest operations (P50/P95/P99)
requests
| where timestamp > ago(24h)
| summarize p50=percentile(duration,50), p95=percentile(duration,95), p99=percentile(duration,99), calls=count() by name
| order by p95 desc
| name | p50 | p95 | p99 | calls |
|---|---|---|---|---|
| POST /Pulse/TimeCharge/Sync | 240 | 4100 | 9800 | 1,320 |
| GET /Nexus/Approvals | 60 | 180 | 320 | 8,900 |
Read it: P50 fine but P95/P99 huge = a slow tail (some calls hit a cold backend or a big payload), not a broadly slow endpoint. Chase the tail, not the average.
7. Which backend is dragging — NetSuite? Greenhouse? SQL?
dependencies
| where timestamp > ago(24h)
| summarize p95=percentile(duration,95), calls=count(), fails=countif(success==false) by target, type
| order by p95 desc
| target | type | p95 | calls | fails |
|---|---|---|---|---|
| netsuite.suitetalk.api | HTTP | 6,200 | 540 | 12 |
| yourserver.database | SQL | 210 | 40,100 | 0 |
| harvest.greenhouse.io | HTTP | 380 | 210 | 0 |
Read it: NetSuite is your latency source. type tells you HTTP vs SQL vs Azure Service Bus etc. without opening any code.
8. Pull the slowest individual traces to drill into
requests
| where timestamp > ago(6h) and name has "TimeCharge"
| top 10 by duration desc
| project timestamp, duration, resultCode, operation_Id
Take an operation_Id, run query #4 on it → you'll see which dependency ate the time inside that specific slow run.
D. Errors / exceptions
9. Top exceptions this week (by volume)
exceptions
| where timestamp > ago(7d)
| summarize count() by type, outerMessage
| order by count_ desc
| take 15
| type | outerMessage | count_ |
|---|---|---|
| SqlException | Timeout expired | 91 |
| HttpRequestException | 401 Unauthorized (NetSuite) | 44 |
Read it: ranks your real pain. The 401 pattern is exactly your OAuth-token-invalidation race — you'd catch it here without anyone reporting it.
10. Exceptions for one operation, with stack trace
exceptions
| where timestamp > ago(1d) and operation_Name has "TimeCharge"
| project timestamp, type, outerMessage, method, details
| order by timestamp desc
details holds the parsed stack frames — the method + line where it blew up.
11. Which request caused this exception?
let ex = exceptions | where timestamp > ago(1d) | take 1;
ex | join kind=inner (requests) on operation_Id
| project exType=type, exMessage=outerMessage, reqName=name, reqResultCode=resultCode, url
Read it: ties the crash back to the exact incoming call + URL that triggered it. No more guessing which input broke it.
E. Distributed tracing — the feature you're really buying
12. Follow one action across every service
let opId = "<any operation_Id>";
union requests, dependencies, exceptions
| where operation_Id == opId
| project timestamp, cloud_RoleName, itemType, name, target, duration, resultCode
| order by timestamp asc
| timestamp | cloud_RoleName | itemType | name | target | duration | resultCode |
|---|---|---|---|---|---|---|
| .100 | oiboomi-prod | request | POST /TimeCharge/Sync | 4200 | 200 | |
| .140 | oiboomi-prod | dependency | HTTP netsuite | netsuite… | 3900 | 200 |
| .150 | pulse-api | request | POST /billingclass | 120 | 200 |
Read it: a full waterfall of one user action across APIM → NetSuite → Pulse. The .140 → 3900ms line is your bottleneck, instantly.
13. Build a mini application map with KQL (who calls whom)
dependencies
| where timestamp > ago(1d)
| summarize calls=count(), fails=countif(success==false) by from=cloud_RoleName, to=target
| order by calls desc
Gives you the edges of your architecture (service → backend) with failure counts. The portal's Application Map draws this visually — this is the raw version.
F. Your AI agents (cost + cache — plugs into your ₹3,096/mo model)
Assumes you emit a customEvent / customMetric per LLM call. If not yet, this is the reason to.
14. Per-call LLM latency + volume by day
dependencies
| where timestamp > ago(7d) and target has "openai" or name has "responses"
| summarize calls=count(), p95ms=percentile(duration,95) by bin(timestamp,1d)
| render timechart
15. Cache-hit rate (if you tag calls with a cacheHit dimension)
customEvents
| where timestamp > ago(1d) and name == "LLMCall"
| extend hit = tostring(customDimensions.cacheHit)
| summarize total=count(), hits=countif(hit=="true")
| extend hitRatePct = round(100.0*hits/total,1)
| total | hits | hitRatePct |
|---|---|---|
| 148,200 | 139,300 | 94.0 |
Read it: your 94% assumption becomes a live measured number. If it drifts to 80%, your monthly cost estimate is wrong and you'll see it here first.
16. Token volume trend (cost driver) if you emit tokens as a custom measurement
customEvents
| where timestamp > ago(30d) and name == "LLMCall"
| summarize totalTokens=sum(toreal(customMeasurements.totalTokens)) by bin(timestamp,1d)
| render columnchart
G. Logic Apps — mind the split
17. Consumption Logic App run failures (no native App Insights — via diagnostic settings → Log Analytics)
AzureDiagnostics
| where TimeGenerated > ago(1d)
| where ResourceProvider == "MICROSOFT.LOGIC" and Category == "WorkflowRuntime"
| where status_s == "Failed"
| summarize failures=count() by resource_workflowName_s, OperationName
| order by failures desc
| resource_workflowName_s | OperationName | failures |
|---|---|---|
| GreenhouseJobSync | workflowRunCompleted | 7 |
Read it: for your 140+ Consumption apps this is how you get run-level failure counts. It's run history, not true traces — that's the limitation.
18. Standard Logic App — action-level detail (native App Insights)
requests
| where timestamp > ago(1d) and cloud_RoleName has "your-standard-logicapp"
| summarize runs=count(), fails=countif(success==false) by name
| order by fails desc
Read it: Standard gives you per-workflow/action telemetry as normal requests/dependencies — the richer observability you don't get on Consumption. Real bullet point for your migration break-even.
H. Alerts you'd actually set
19. Log alert: any 404 on that endpoint in the last 15 min
Save this as a scheduled log alert, fire if result count > 0:
requests
| where timestamp > ago(15m)
| where name has "CreateByCustomAttributes" and resultCode == "404"
So the next time that route breaks after a deploy, it pages you instead of a user reporting it.
20. NetSuite dependency failure spike
dependencies
| where timestamp > ago(15m) and target has "netsuite"
| where success == false
| summarize fails=count()
Alert when fails > 5. Catches your OAuth token-invalidation race the moment it starts.
21. Live Metrics (not KQL) — during a deploy
Open the Live Metrics blade while you deploy Pulse Time Charge. Sub-second stream of incoming requests, failures, and CPU. If failure rate spikes the instant traffic hits the new version, you roll back before it spreads. This is your deploy safety net.
I. Cost control (ties into your optimization work)
22. What's actually costing me ingestion (last 7 days)
union withsource=Table *
| where timestamp > ago(7d)
| summarize items=count(), gb=round(sum(_BilledSize)/1024/1024/1024, 2) by Table
| order by gb desc
| Table | items | gb |
|---|---|---|
| dependencies | 12,400,000 | 8.9 |
| requests | 3,100,000 | 2.1 |
| traces | 40,000,000 | 14.2 |
Read it: traces (verbose logging) is usually the silent bill-driver. Turn down trace verbosity or sampling there first — it rarely costs you debugging power.
23. Set the levers
- Sampling on APIM: crank to 100% while chasing a bug, drop to ~20–50% for steady state.
- Daily cap on the workspace so a runaway loop can't bankrupt you.
- Retention: keep 30–90 days interactive, archive the rest.
The 6 queries to memorise
-
requests | where name has "X" | project timestamp, resultCode, success, operation_Id— is it working -
union requests, dependencies, exceptions | where operation_Id == "…"— the full trace (your #1 debug tool) -
requests | summarize percentile(duration,95) by name— what's slow -
dependencies | summarize percentile(duration,95), countif(success==false) by target— which backend -
exceptions | summarize count() by type, outerMessage— what's breaking -
union withsource=T * | summarize sum(_BilledSize) by T— what's costing
Everything else is a variation on these.
Top comments (0)