Skip to content

Commit ee73c97

Browse files
author
Hou Chi Chan
committed
Document Application Insights queries and operator monitoring setup
Add validated JavaScript service-event and Front Door KQL examples, cross-region workbooks, metric/log alert starting points, absence detection and diagnostic setup instructions. Explain sampling, privacy, evaluation-versus-live traffic and separate edge diagnostic configuration. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b5102217-1e24-4fd7-abc5-13fecb0dabaf
1 parent b81b9aa commit ee73c97

1 file changed

Lines changed: 269 additions & 0 deletions

File tree

‎docs/FRONTDOOR.md‎

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and leaves the source endpoint and authentication policy unchanged. Failover can
1313

1414
Run from a reviewed repository checkout. [frontdoor-regions.bicep](../setup/infra/frontdoor-regions.bicep)
1515
reuses the existing [Function module](../setup/infra/resources.bicep); single-region defaults stay unchanged.
16+
For operators, see [Application Insights queries](#application-insights-and-sample-queries) and
17+
[monitoring setup](#monitoring-setup-for-operators).
1618

1719
### Supported source and prerequisites
1820

@@ -359,10 +361,277 @@ or successful delivery to a phone.
359361
plans, and telemetry remain billable until appropriately decommissioned; respect Key Vault purge
360362
protection and retention.
361363

364+
### Application Insights and sample queries
365+
366+
Setup provisions **workspace-based Application Insights and Log Analytics per region**. The Function
367+
receives `APPLICATIONINSIGHTS_CONNECTION_STRING` and `APPLICATIONINSIGHTS_AUTHENTICATION_STRING`;
368+
its identity receives the Monitoring Metrics Publisher role. Local/key-based ingestion is disabled.
369+
The JavaScript handler emits the [structured service events](CONTRACT.md#application-logs), and
370+
readiness emits `request_completed` and, on failure, `request_failed` with `functionName=FrontDoorHealth`.
371+
No extra application logging code is needed for these events.
372+
373+
Open the Application Insights component's **linked Log Analytics workspace > Logs** to run the
374+
queries below. They target JavaScript JSON messages in `AppTraces`, not Python/.NET's provider-specific
375+
formatting. In an Application Insights resource-scoped query, the equivalent source is
376+
`traces | project TimeGenerated=timestamp, Message=message, AppRoleName=cloud_RoleName`.
377+
For both regions, replace the first `AppTraces` line with:
378+
379+
```kusto
380+
union workspace("<first-workspace-id>").AppTraces,
381+
workspace("<second-workspace-id>").AppTraces
382+
```
383+
384+
Both workspaces require query access. Filter by `AppRoleName` if the workspaces contain other apps.
385+
The default host configuration samples traces, so counts below are **observed records**, not
386+
guaranteed traffic totals. Keep Information-level application logs enabled; excluding `Request`
387+
from sampling does not exclude these trace events. Health probes can dominate ingestion volume:
388+
keep their charts separate from delivery traffic and review retention/sampling costs.
389+
390+
**Delivery/evaluation volume, failures and handler latency**
391+
392+
```kusto
393+
AppTraces
394+
| where TimeGenerated > ago(24h)
395+
| extend e = parse_json(Message)
396+
| where e.logType == "service" and e.functionName == "SendOtp"
397+
| where e.eventName == "request_completed"
398+
| summarize ObservedCompletions=count(),
399+
ObservedFailures=countif(toint(e.httpStatus) >= 400),
400+
P95HandlerMs=percentile(todouble(e.elapsedMs), 95)
401+
by bin(TimeGenerated, 15m), AppRoleName, Evaluation=tostring(e.evaluation)
402+
| order by TimeGenerated desc
403+
```
404+
405+
`Evaluation=true` is a non-delivering test; `false` is live mode. A blank value means the mode was
406+
not established, for example because envelope validation failed. Handler latency is not end-to-end
407+
Front Door latency or proof of delivery to a handset.
408+
409+
**Failure stages and reasons**
410+
411+
```kusto
412+
AppTraces
413+
| where TimeGenerated > ago(24h)
414+
| extend e = parse_json(Message)
415+
| where e.logType == "service" and e.functionName == "SendOtp"
416+
| where e.eventName == "request_failed"
417+
| summarize ObservedFailures=count(), LastSeen=max(TimeGenerated)
418+
by AppRoleName, Stage=tostring(e.failureStage),
419+
Reason=tostring(e.failureReason), HttpStatus=toint(e.httpStatus)
420+
| order by ObservedFailures desc
421+
```
422+
423+
**Follow one request across its service events**
424+
425+
```kusto
426+
let TraceId = "<function-request-id-or-microsoft-correlation-id>";
427+
AppTraces
428+
| where TimeGenerated > ago(24h)
429+
| extend e = parse_json(Message)
430+
| where e.logType == "service"
431+
| where tostring(e.functionRequestId) == TraceId
432+
or tostring(e["x-ms-correlation-id"]) == TraceId
433+
| project TimeGenerated, AppRoleName, Event=tostring(e.eventName),
434+
FunctionRequestId=tostring(e.functionRequestId),
435+
InvocationId=tostring(e.functionInvocationId),
436+
HttpStatus=toint(e.httpStatus), Stage=tostring(e.failureStage),
437+
Reason=tostring(e.failureReason), ElapsedMs=todouble(e.elapsedMs)
438+
| order by TimeGenerated asc
439+
```
440+
441+
Use `functionRequestId` to isolate one invocation; a Microsoft correlation ID can cover multiple
442+
attempts. Missing sampled events do not prove a stage never ran.
443+
444+
**Readiness by origin**
445+
446+
```kusto
447+
AppTraces
448+
| where TimeGenerated > ago(1h)
449+
| extend e = parse_json(Message)
450+
| where e.logType == "service" and e.functionName == "FrontDoorHealth"
451+
| where e.eventName == "request_completed"
452+
| summarize ObservedProbes=count(),
453+
NotReady=countif(toint(e.httpStatus) != 200), LastSeen=max(TimeGenerated)
454+
by AppRoleName, bin(TimeGenerated, 5m)
455+
| order by TimeGenerated desc
456+
```
457+
458+
No rows is **not** a healthy result: an unavailable Function cannot emit a readiness event.
459+
460+
**Provider outcomes for live sends**
461+
462+
```kusto
463+
AppTraces
464+
| where TimeGenerated > ago(24h)
465+
| extend e = parse_json(Message)
466+
| where e.logType == "service" and e.eventName == "provider_response_processed"
467+
| summarize ObservedResponses=count(),
468+
P95ProviderMs=percentile(todouble(e.providerElapsedMs), 95)
469+
by AppRoleName, Provider=tostring(e.providerName),
470+
Outcome=tostring(e.providerOutcome), ProviderHttpStatus=toint(e.providerHttpStatus),
471+
ProviderStatus=tostring(e.providerStatus), Reason=tostring(e.failureReason)
472+
| order by ObservedResponses desc
473+
```
474+
475+
Evaluation intentionally emits no provider events. A provider HTTP 200 is not necessarily a
476+
successful outcome; inspect the normalized outcome and reason.
477+
478+
If expected events are absent, check the Function's instrumentation settings, identity role,
479+
Information-level log filters, sampling, workspace scope/time range and ingestion delay.
480+
Front Door access/probe diagnostics are **not configured by this script**: enable the required
481+
categories under the Front Door profile's **Diagnostic settings**, targeting a Log Analytics
482+
workspace, when edge-level diagnostics are needed. Easy Auth and ingress rejections can happen
483+
before the handler; investigate platform/authentication and Front Door diagnostics rather than
484+
expecting a `request_failed` service event. Review diagnostic fields, access and retention before
485+
enabling collection; never add OTP bodies, plaintext delivery data, keys or tokens to application logs.
486+
487+
### Monitoring setup for operators
488+
489+
The following is a starting configuration, **not an SLA or a production threshold recommendation**.
490+
Tune it against normal traffic, ingestion delay, sampling and the on-call team's response policy.
491+
Creating diagnostics, alerts, availability tests and notification actions can incur charges.
492+
493+
1. In **Azure Monitor > Action groups**, create a group for the service owners and test its
494+
notification delivery. Give operators appropriate read/query access to the regional workspaces;
495+
alert creators also need permission on the monitored resources.
496+
2. In each regional Application Insights component, open **Logs** and run the delivery and readiness
497+
queries above. Save them to a shared **Workbook**; use an explicit cross-workspace union for both
498+
regions. Include failures by stage, latency, provider outcomes and last telemetry seen per origin.
499+
3. On the **Front Door profile > Diagnostic settings**, enable `FrontDoorAccessLog` and
500+
`FrontDoorHealthProbeLog` to a chosen workspace. The script does not create this setting.
501+
Front Door metrics are available separately without exporting `AllMetrics` to logs.
502+
Review workspace retention and access: platform access logs may contain client IPs and URLs
503+
even though the application service events exclude delivery bodies and credentials.
504+
4. In **Front Door > Alerts > Create alert rule**, select the metrics below, use the suggested
505+
dimensions to separate origins/endpoints, and attach the action group. Configure auto-resolution
506+
and a maintenance suppression policy appropriate for the service.
507+
5. In **Log Analytics workspace > Alerts > Create alert rule**, select **Custom log search** and
508+
paste one of the alert queries below. Evaluate every 5 minutes. Each query includes its own
509+
lookback and returns only candidate alert rows: measure **Table rows > 0**. Split by `AppRoleName`
510+
for per-origin signals. Ensure the rule's query time range covers the stated lookback.
511+
Test the notification path and recovery in nonproduction before enabling paging.
512+
513+
| Signal | Example condition | Scope / caveat |
514+
|---|---|---|
515+
| `OriginHealthPercentage` | Average below 100% over 5 minutes | Split by `Origin` and `OriginGroup`. Expect transitional noise; use this with readiness events and absence detection. |
516+
| `Percentage5XX` | Average above 1% over 5 minutes | Split by `Endpoint`; inspect request volume because small denominators exaggerate percentages. |
517+
| `OriginLatency` | Average above 1000 ms over 5 minutes | Split by `Origin` and `Endpoint`; replace with a workload-derived latency budget. |
518+
| `Percentage4XX` | Investigate a sustained rise above baseline | Includes deliberate negative tests and client/authentication failures; do not page on every 401/403. |
519+
| Application failures / readiness | Custom log queries below | Trace sampling means these are observed failures, not complete request counts. |
520+
521+
An Application Insights **Availability > Standard test** can send HTTPS GET to the public
522+
`/api/health/ready` route and expect 200, using multiple locations. For example, alert on failures
523+
from two locations over five minutes. Do not put an EPP bearer token in the test, and do not probe
524+
the protected SendOtp route anonymously as an availability test. A shared health URL proves neither
525+
every origin's health nor provider delivery; retain the per-origin metric alerts.
526+
527+
**Observed live-delivery server failures: example log alert**
528+
529+
```kusto
530+
AppTraces
531+
| where TimeGenerated > ago(15m)
532+
| extend e = parse_json(Message)
533+
| where e.logType == "service" and e.functionName == "SendOtp"
534+
| where e.eventName == "request_completed" and tobool(e.evaluation) == false
535+
| summarize ObservedRequests=count(),
536+
ObservedServerFailures=countif(toint(e.httpStatus) >= 500) by AppRoleName
537+
| extend ObservedFailurePct=100.0 * ObservedServerFailures / ObservedRequests
538+
| where ObservedServerFailures >= 3 and ObservedFailurePct >= 5.0
539+
```
540+
541+
This deliberately excludes evaluation traffic and failures before a request's mode is known.
542+
Use the failure-stage query and Front Door diagnostics for those cases.
543+
544+
**Readiness failures: example log alert**
545+
546+
```kusto
547+
AppTraces
548+
| where TimeGenerated > ago(5m)
549+
| extend e = parse_json(Message)
550+
| where e.logType == "service" and e.functionName == "FrontDoorHealth"
551+
| where e.eventName == "request_completed" and toint(e.httpStatus) != 200
552+
| summarize ObservedFailures=count() by AppRoleName
553+
| where ObservedFailures >= 3
554+
```
555+
556+
**Missing per-origin telemetry: example log alert**
557+
558+
Replace both names below with the deployed Function names, and replace the `AppTraces` source with
559+
the cross-workspace union when each region has its own workspace. Keep the full expected-origin
560+
list: building it from recent events would silently omit an origin that stopped reporting.
561+
562+
```kusto
563+
let Expected = datatable(AppRoleName:string)
564+
[
565+
"<prefix>-0-func",
566+
"<prefix>-1-func"
567+
];
568+
let Seen =
569+
AppTraces
570+
| where TimeGenerated > ago(15m)
571+
| extend e = parse_json(Message)
572+
| where e.logType == "service" and e.functionName == "FrontDoorHealth"
573+
| where e.eventName == "request_completed"
574+
| summarize LastSeen=max(TimeGenerated) by AppRoleName;
575+
Expected
576+
| join kind=leftouter Seen on AppRoleName
577+
| where isnull(LastSeen)
578+
| project AppRoleName, LastSeen, Signal="No readiness telemetry in the last 15 minutes"
579+
```
580+
581+
An absence alert can mean an outage, sampling/filtering, an ingestion problem, or a wrong query
582+
scope/name. It is a telemetry-gap signal, not proof of an application outage. Suppress during
583+
planned maintenance and correlate with edge health and deployment status.
584+
585+
**Front Door SendOtp status codes: monitoring query**
586+
587+
Run this in the workspace receiving the profile's diagnostics. Replace the profile resource ID.
588+
Front Door Standard/Premium exports these logs to `AzureDiagnostics`. The status conversion handles the string
589+
and numeric columns found in AzureDiagnostics. It does not display client IPs or full request URLs.
590+
591+
```kusto
592+
let ProfileId = "/subscriptions/<subscription-id>/resourceGroups/<global-rg>/providers/Microsoft.Cdn/profiles/<profile-name>";
593+
AzureDiagnostics
594+
| where TimeGenerated > ago(24h)
595+
| where _ResourceId =~ ProfileId and Category == "FrontDoorAccessLog"
596+
| where tostring(parse_url(requestUri_s).Path) =~ "/api/SendOtp"
597+
| extend HttpStatus=toint(coalesce(
598+
tostring(column_ifexists("httpStatusCode_s", "")),
599+
tostring(column_ifexists("httpStatusCode_d", real(null))),
600+
tostring(column_ifexists("httpStatus_d", real(null)))))
601+
| summarize ObservedRequests=count() by bin(TimeGenerated, 15m), HttpStatus
602+
| order by TimeGenerated desc
603+
```
604+
605+
Use this to spot 401/403 and 5xx responses that never reached the Function handler.
606+
A 403 alone does not identify whether Easy Auth, origin restrictions or an edge policy rejected it.
607+
`FrontDoorHealthProbeLog` is useful for failed probe diagnostics, **not a stream of all successful
608+
probes**; absence of those log rows does not establish health. Use `OriginHealthPercentage` for
609+
the per-origin health trend.
610+
611+
**Front Door failed probes by origin**
612+
613+
```kusto
614+
let ProfileId = "/subscriptions/<subscription-id>/resourceGroups/<global-rg>/providers/Microsoft.Cdn/profiles/<profile-name>";
615+
AzureDiagnostics
616+
| where TimeGenerated > ago(24h)
617+
| where _ResourceId =~ ProfileId and Category == "FrontDoorHealthProbeLog"
618+
| summarize FailedProbes=count(), LastSeen=max(TimeGenerated)
619+
by Origin=tostring(originName_s), Result=tostring(result_s),
620+
HttpStatus=toint(httpStatusCode_s)
621+
| order by FailedProbes desc
622+
```
623+
624+
Also monitor Key Vault certificate expiry (for example, a 30-day renewal warning), resolved
625+
Function key references, subscription budgets and telemetry ingestion volume. Never test a paging
626+
rule by disabling production authentication or sending unsolicited OTP messages.
627+
362628
## References
363629

364630
- [Front Door routing methods](https://learn.microsoft.com/azure/frontdoor/routing-methods)
365631
- [Health probes and all-origins-unhealthy behavior](https://learn.microsoft.com/azure/frontdoor/health-probes)
366632
- [Origin security and caller-header considerations](https://learn.microsoft.com/azure/frontdoor/origin-security)
367633
- [Front Door caching](https://learn.microsoft.com/azure/frontdoor/front-door-caching)
368634
- [Key Vault backup/restore constraints](https://learn.microsoft.com/azure/key-vault/general/backup)
635+
- [Azure Functions monitoring and sampling](https://learn.microsoft.com/azure/azure-functions/configure-monitoring)
636+
- [Front Door monitoring data reference](https://learn.microsoft.com/azure/frontdoor/monitor-front-door-reference)
637+
- [Log search alert rules](https://learn.microsoft.com/azure/azure-monitor/alerts/alerts-create-log-alert-rule)

0 commit comments

Comments
 (0)