12.3 Write KQL Queries to Analyze Logs
Key Takeaways
- KQL (Kusto Query Language) is the query language for Log Analytics—AI-200 expects practical operators for investigating AI solution logs.
- Core operators include where, project, summarize, join, and ago-based time filters on TimeGenerated.
- Application Insights tables such as AppRequests, AppTraces, AppExceptions, and AppDependencies are first-stop sources for correlated app logs.
- Container Apps and custom logs often appear in tables like ContainerAppConsoleLogs_CL—know when to query platform logs vs App* tables.
- Start from time range + failed requests, then join traces on operation_Id to reconstruct the narrative.
KQL in the AI-200 monitoring toolkit
After OpenTelemetry data lands in Azure Monitor, you investigate with Kusto Query Language (KQL) in a Log Analytics workspace. AI-200 does not require you to memorize every function, but you must write and interpret queries that filter time, select columns, aggregate, and join related tables.
Mental model: table → filter → shape → aggregate → join
Most troubleshooting queries follow the same pipeline:
- Pick a table (
AppRequests,AppTraces, …) - where to filter time and predicates
- project / extend to keep useful columns
- summarize to aggregate
- join when one table is not enough
| Operator | Purpose | Example instinct |
|---|---|---|
| where | Filter rows | Failed requests, specific URL, severity |
| ago() | Relative time window | TimeGenerated > ago(1h) |
| project | Select/rename columns | Keep name, resultCode, duration |
| extend | Compute new columns | bin(TimeGenerated, 5m) already via summarize, or custom flags |
| summarize | Aggregate | count(), avg(duration), percentile() |
| join | Combine tables | Requests with traces on operation_Id |
| order by | Sort | Slowest first |
| take | Limit output | First 50 rows while exploring |
Essential Application Insights tables
When using workspace-based Application Insights, queries often target the App* schema:
| Table | Contents | AI pipeline use |
|---|---|---|
| AppRequests | Inbound operations | API latency, result codes, success flags |
| AppDependencies | Outbound calls | Model endpoint, Postgres, Redis, Service Bus |
| AppTraces | Trace/log messages | Custom diagnostics, OTel-related log lines |
| AppExceptions | Exceptions | SDK failures, deserialization errors |
| AppEvents | Custom events | Business markers (retrieval_miss, safety_block) |
| AppMetrics | Metric samples | Token counts, queue lengths (metrics focus expands in 12.4) |
Classic names like requests / dependencies appear in some Application Insights query scopes; on exam stems, read the table names given and apply the same operators.
Container and platform logs
Not every clue lives in App*. Container stdout/stderr and platform logs matter when instrumentation is incomplete or the process crashes before exporting spans.
| Table (examples) | Typical signal |
|---|---|
| ContainerAppConsoleLogs_CL | Console output from Container Apps revisions |
| ContainerAppSystemLogs_CL | Platform/system events for Container Apps |
| AppServiceConsoleLogs / similar | App Service container console (when used) |
| AzureDiagnostics | Broad diagnostic settings output (service-dependent) |
Pattern: if Application Insights is empty but the container is restarting, query console logs for crash stacks; if App Insights has requests but dependencies look wrong, stay in AppDependencies and fix instrumentation.
Time filtering with ago
Always constrain time. Unbounded queries are slow and noisy.
AppRequests
| where TimeGenerated > ago(2h)
| where Success == false
| project TimeGenerated, Name, ResultCode, DurationMs, OperationId=operation_Id, Url
| order by TimeGenerated desc
| take 50
Notes for exam reading:
ago(2h)means “two hours before now”- Prefer
TimeGenerated(ingestion/event time field commonly used in Log Analytics) Success == falsequickly finds failing requests; also filterResultCode >= 500when Success semantics differ
project vs summarize
project shapes rows without collapsing them—ideal when you need individual failing operations.
summarize collapses rows—ideal for rates and top offenders:
AppRequests
| where TimeGenerated > ago(24h)
| summarize Failures=countif(Success == false), Total=count(), AvgDuration=avg(DurationMs) by Name
| extend FailureRate = todouble(Failures) / Total
| order by FailureRate desc
This answers “which operation name is hurting us?” rather than listing every row.
join on operation_Id
Correlation shines with joins:
let failed = AppRequests
| where TimeGenerated > ago(1h)
| where Success == false
| project operation_Id, ReqName=Name, ResultCode, DurationMs;
AppTraces
| where TimeGenerated > ago(1h)
| join kind=inner failed on operation_Id
| project TimeGenerated, ReqName, ResultCode, Message, SeverityLevel
| order by TimeGenerated asc
Interpretation skills:
- inner join keeps only matching IDs (good when you already have failed requests)
- leftouter keeps requests even when traces are missing (detects instrumentation gaps)
- Always project a lean column set so results stay readable
You can similarly join AppRequests to AppDependencies to see which outbound call failed inside a request.
Searching messages and custom dimensions
Traces often need text filters:
AppTraces
| where TimeGenerated > ago(6h)
| where Message has "rate limit" or Message has "429"
| summarize count() by bin(TimeGenerated, 15m)
has is a practical token search. For structured custom dimensions, use dynamic accessors as shown in portal samples (stems usually give the field path).
Scenario: finding a bad Container Apps revision
Asha deployed revision chat-api--v12. Error rates climbed. She runs:
AppRequestssummarize failures by cloud role / app version dimensions available in her schemaContainerAppConsoleLogs_CLfiltered to the revision name for panic/OOM lines- Join failed
operation_Idvalues toAppExceptions
She finds exceptions only on v12 referencing a missing environment variable for the model deployment name. Rollback to the prior revision restores health. KQL made the revision association explicit.
Query hygiene for exams and incidents
- Start narrow (time + failure), then widen
- Name columns clearly when joining (
ReqNamevsMessage) - Beware of high-cardinality
summarize byfields (raw URLs with IDs) - Confirm you are in the correct workspace connected to the Application Insights resource
- Remember legal/privacy limits: do not dump full prompts in shared query results if policy forbids it
Practice shapes to memorize
| Goal | Starting shape |
|---|---|
| Recent failures | AppRequests | where ago | where not Success | project … |
| Top slow calls | AppRequests | summarize percentile(DurationMs, 95) by Name |
| Logs for one operation | filter operation_Id == "…" across AppTraces/AppExceptions |
| Container crash text | ContainerAppConsoleLogs_CL | where ago | where Log_s has "Traceback" |
| Dependency errors | AppDependencies | where Success == false | summarize count() by Target, ResultCode |
Master these patterns before section 12.4, which applies the same operators to metrics-heavy and multi-stage AI troubleshooting scenarios.
Which KQL operator filters rows, such as keeping only failed requests in the last hour?
You need related AppTraces lines for failed AppRequests. Which approach matches standard Log Analytics correlation practice?
A Container Apps process crashes before spans export. Which table is the most direct place to read stdout/stderr crash text?