Skip to content

AI-200 · Domain 4: Secure, monitor, troubleshoot Azure solutions · 23% of the exam

Task 4.2: Monitor and troubleshoot Azure solutions

Tracing distributed systems with the OpenTelemetry-based Azure Monitor distro and Application Insights, and writing KQL queries over logs and metrics.

Study it

Sample questions

Try each one before opening the answer. Every option is explained, with the Microsoft documentation page that proves it.

Question 1 · choose 1

Two Python services send OpenTelemetry data to the same Application Insights resource, and Application Map shows them as a single node named after the resource. How do you make each service appear as its own node while both keep reporting to that one resource?

  1. ASet a distinct OTEL_SERVICE_NAME, or service.name resource attribute, for each service
  2. BGive each service its own Application Insights connection string
  3. CSet the service.instance.id attribute to a different value per service
  4. DTurn off sampling so both services send all telemetry
Show the answer and why
  • ASet a distinct OTEL_SERVICE_NAME, or service.name resource attribute, for each service

    Correct

    The cloud role name comes from service.namespace and service.name, falling back to service.name alone.

  • BGive each service its own Application Insights connection string

    Incorrect

    Separate resources would give separate nodes, but the services must keep reporting to the one shared resource.

  • CSet the service.instance.id attribute to a different value per service

    Incorrect

    service.instance.id sets the cloud role instance, not the role name shown on the map node.

  • DTurn off sampling so both services send all telemetry

    Incorrect

    Sampling affects volume, not how components are named on the map.

Without a configured role name, the cloud role name defaults to the Application Insights resource name. Set it per service through OpenTelemetry resource attributes, for example OTEL_SERVICE_NAME or OTEL_RESOURCE_ATTRIBUTES.

Question 2 · choose 1

You need the 95th percentile duration of the POST /chat operation in five-minute buckets over the last hour, from the AppRequests table in a Log Analytics workspace. Which query returns it?

  1. AAppRequests | where TimeGenerated > ago(1h) and Name == "POST /chat" | summarize percentile(DurationMs, 95) by bin(TimeGenerated, 5m)
  2. BAppRequests | where TimeGenerated > ago(1h) and Name == "POST /chat" | summarize avg(DurationMs) by bin(TimeGenerated, 5m)
  3. CAppRequests | summarize percentile(DurationMs, 95) by Name | where TimeGenerated > ago(1h)
  4. DAppRequests | where TimeGenerated > ago(1h) and Name == "POST /chat" | top 95 by DurationMs
Show the answer and why
  • AAppRequests | where TimeGenerated > ago(1h) and Name == "POST /chat" | summarize percentile(DurationMs, 95) by bin(TimeGenerated, 5m)

    Correct

    It filters first, then computes the percentile per five-minute bin.

  • BAppRequests | where TimeGenerated > ago(1h) and Name == "POST /chat" | summarize avg(DurationMs) by bin(TimeGenerated, 5m)

    Incorrect

    This returns the average duration, not the 95th percentile.

  • CAppRequests | summarize percentile(DurationMs, 95) by Name | where TimeGenerated > ago(1h)

    Incorrect

    After summarize only the grouping and aggregate columns remain, so TimeGenerated can't be filtered, and there are no time buckets.

  • DAppRequests | where TimeGenerated > ago(1h) and Name == "POST /chat" | top 95 by DurationMs

    Incorrect

    top returns the 95 slowest rows, not a percentile per bucket.

Filter with where and ago(), then aggregate with summarize. percentile() estimates a nearest-rank percentile, and bin() rounds TimeGenerated into fixed-size buckets for the by clause.

Question 3 · choose 1

Sampling is enabled for an app's telemetry. A dashboard query runs AppRequests | summarize count() by bin(TimeGenerated, 1h), and its totals are far lower than the traffic seen at the load balancer. How should the query count requests?

  1. AUse summarize sum(ItemCount) instead of count()
  2. BUse dcount(Id) instead of count()
  3. CMultiply count() by the sampling percentage
  4. DQuery the AppTraces table instead of AppRequests
Show the answer and why
  • AUse summarize sum(ItemCount) instead of count()

    Correct

    Each retained record's ItemCount says how many original items it represents, so summing it restores the totals.

  • BUse dcount(Id) instead of count()

    Incorrect

    A distinct count of retained IDs is still a count of sampled records.

  • CMultiply count() by the sampling percentage

    Incorrect

    The sampling rate can vary over time; ItemCount already carries the right weight per record.

  • DQuery the AppTraces table instead of AppRequests

    Incorrect

    Trace logs aren't requests, and they're also subject to sampling.

Sampling keeps a representative subset of telemetry and records on each kept item how many items it stands for. Log queries that count events must sum ItemCount rather than count rows.

Question 4 · choose 1

A Python background worker wraps each job in tracer.start_as_current_span("process-job"). In Application Insights the jobs appear as InProc dependencies, but the team wants each job counted as a request so it shows in the Performance and Failures views. What do you change?

  1. ARecord each job as a custom event instead of a span
  2. BAdd a span attribute named request with the value true
  3. CStart the span with kind=SpanKind.SERVER
  4. DLower the sampling rate so more spans reach requests
Show the answer and why
  • ARecord each job as a custom event instead of a span

    Incorrect

    Custom events go to the customEvents table, not to requests.

  • BAdd a span attribute named request with the value true

    Incorrect

    An attribute adds a custom property; it doesn't change which table the span lands in.

  • CStart the span with kind=SpanKind.SERVER

    Correct

    Spans of kind SERVER are stored in the requests table; the default kind lands in dependencies as InProc.

  • DLower the sampling rate so more spans reach requests

    Incorrect

    Sampling changes how many spans are kept, not their telemetry type.

Manual spans created with the OpenTelemetry API default to internal spans, which Application Insights records as InProc dependencies. For background work that autoinstrumentation doesn't capture, set the span kind to SERVER so it appears as a request.

Question 5 · choose 2

You're adding OpenTelemetry tracing to a new Python API and want the traces, metrics and logs in Application Insights. Following Microsoft's current guidance for new apps, which two steps do you take? (Choose TWO.)

  1. AInstall the opencensus-ext-azure package and configure its exporter
  2. BDeploy an OpenTelemetry Collector, which the distro needs to reach Azure Monitor
  3. CAdd a separate SDK package for each signal and register three exporters by hand
  4. DInstall the microsoft-opentelemetry package and call use_microsoft_opentelemetry() with Azure Monitor export enabled at startup
  5. ESet APPLICATIONINSIGHTS_CONNECTION_STRING to the resource's connection string before the app starts
Show the answer and why
  • AInstall the opencensus-ext-azure package and configure its exporter

    Incorrect

    The OpenCensus Python SDK is retired; Microsoft directs users to OpenTelemetry instead.

  • BDeploy an OpenTelemetry Collector, which the distro needs to reach Azure Monitor

    Incorrect

    The distro exports directly to Azure Monitor; a collector isn't required.

  • CAdd a separate SDK package for each signal and register three exporters by hand

    Incorrect

    The distro bundles the instrumentations and exporters, so manual wiring per signal isn't needed.

  • DInstall the microsoft-opentelemetry package and call use_microsoft_opentelemetry() with Azure Monitor export enabled at startup

    Correct

    The Microsoft OpenTelemetry Distro is the recommended starting point for new Python apps and collects all three signals.

  • ESet APPLICATIONINSIGHTS_CONNECTION_STRING to the resource's connection string before the app starts

    Correct

    The connection string identifies the Application Insights resource that receives the telemetry.

For new .NET, Node.js and Python apps, Microsoft recommends the Microsoft OpenTelemetry Distro; apps already on the Azure Monitor OpenTelemetry Distro need no change. Point it at the resource with the connection string, usually through APPLICATIONINSIGHTS_CONNECTION_STRING.

Question 6 · choose 1

You search a large AppTraces table for messages that mention the term timeout as a whole word. Which filter is the efficient choice?

  1. Awhere Message contains "timeout"
  2. Bwhere Message == "timeout"
  3. Cwhere Message startswith "timeout"
  4. Dwhere Message has "timeout"
Show the answer and why
  • Awhere Message contains "timeout"

    Incorrect

    contains scans for a substring anywhere, which is slower and also matches inside longer words.

  • Bwhere Message == "timeout"

    Incorrect

    == matches only messages that are exactly the word timeout.

  • Cwhere Message startswith "timeout"

    Incorrect

    startswith matches only messages that begin with the word.

  • Dwhere Message has "timeout"

    Correct

    has matches whole indexed terms and is the preferred, faster operator for this kind of search.

KQL indexes terms of three or more characters. has looks them up in the term index, while contains must scan values; Microsoft's best practices prefer has over contains when you're looking for full terms.

Question 7 · choose 1

Security disables local authentication on an Application Insights resource so that only Microsoft Entra-authenticated telemetry is ingested. A Python app's managed identity is passed to the OpenTelemetry distro as its credential, but telemetry stops arriving. Which role assignment fixes it?

  1. AMonitoring Reader on the Application Insights resource
  2. BLog Analytics Reader on the linked workspace
  3. CMonitoring Metrics Publisher on the Application Insights resource
  4. DKey Vault Secrets User on a vault that holds the connection string
Show the answer and why
  • AMonitoring Reader on the Application Insights resource

    Incorrect

    A reader role lets an identity view monitoring data, not send it.

  • BLog Analytics Reader on the linked workspace

    Incorrect

    Reading the workspace doesn't authorize ingestion into Application Insights.

  • CMonitoring Metrics Publisher on the Application Insights resource

    Correct

    Despite its name, this role publishes all telemetry types to the resource.

  • DKey Vault Secrets User on a vault that holds the connection string

    Incorrect

    The connection string isn't a credential for Entra ingestion; the identity itself must be authorized on the resource.

When local authentication is off, the SDK must authenticate with a Microsoft Entra credential, and that identity needs the Monitoring Metrics Publisher role scoped to the Application Insights resource.

Question 8 · choose 1

You chart the percentage of failed requests per 15 minutes from AppRequests. Which summarize clause computes it?

  1. Asummarize ErrorRate = countif(Success == false) by bin(TimeGenerated, 15m)
  2. Bsummarize ErrorRate = 100 * count() / countif(Success == false) by bin(TimeGenerated, 15m)
  3. Csummarize ErrorRate = dcount(Success) by bin(TimeGenerated, 15m)
  4. Dsummarize ErrorRate = 100.0 * countif(Success == false) / count() by bin(TimeGenerated, 15m)
Show the answer and why
  • Asummarize ErrorRate = countif(Success == false) by bin(TimeGenerated, 15m)

    Incorrect

    This is the number of failures per bin, not a percentage.

  • Bsummarize ErrorRate = 100 * count() / countif(Success == false) by bin(TimeGenerated, 15m)

    Incorrect

    The ratio is inverted, dividing all requests by failures.

  • Csummarize ErrorRate = dcount(Success) by bin(TimeGenerated, 15m)

    Incorrect

    dcount returns the number of distinct values of Success, at most two.

  • Dsummarize ErrorRate = 100.0 * countif(Success == false) / count() by bin(TimeGenerated, 15m)

    Correct

    countif counts failed rows and count() counts all rows in each bin, giving a percentage.

countif() counts the rows for which a predicate is true. Dividing it by count() per time bin, with a decimal literal to avoid integer arithmetic, gives the failure percentage for each bucket.

Question 9 · choose 1

A custom table records every status change of each deployment. You need one row per DeploymentId with all columns from its most recent record. Which query does that?

  1. ADeploymentStatus | summarize max(TimeGenerated) by DeploymentId, Status
  2. BDeploymentStatus | top 1 by TimeGenerated
  3. CDeploymentStatus | summarize arg_max(TimeGenerated, *) by DeploymentId
  4. DDeploymentStatus | distinct DeploymentId
Show the answer and why
  • ADeploymentStatus | summarize max(TimeGenerated) by DeploymentId, Status

    Incorrect

    This returns the latest timestamp per ID and status pair, not the latest full row per deployment.

  • BDeploymentStatus | top 1 by TimeGenerated

    Incorrect

    This returns the single latest row across all deployments.

  • CDeploymentStatus | summarize arg_max(TimeGenerated, *) by DeploymentId

    Correct

    arg_max returns the row with the highest TimeGenerated in each group, with all its columns.

  • DDeploymentStatus | distinct DeploymentId

    Incorrect

    This lists the IDs without any of their status columns.

arg_max(ExprToMaximize, *) finds the row that maximizes an expression in each group and returns the requested columns, here all of them.

Question 10 · choose 1

During a deployment you want to watch request rates, failures and exceptions from a Python API second by second, without waiting for ingestion. Which Application Insights feature fits?

  1. ALog Analytics queries on AppRequests
  2. BLive metrics
  3. CApplication map
  4. DMetrics explorer with a one-hour aggregation
Show the answer and why
  • ALog Analytics queries on AppRequests

    Incorrect

    Log data is aggregated and ingested over minutes, not shown within a second.

  • BLive metrics

    Correct

    Live metrics shows data within about a second while the pane is open, and it's enabled by default for Python.

  • CApplication map

    Incorrect

    Application map shows components and dependencies, not a second-by-second feed.

  • DMetrics explorer with a one-hour aggregation

    Incorrect

    Metrics explorer aggregates over minutes or more.

Live metrics streams selected metrics and sampled failures to the portal in near real time. Nothing is retained after it leaves the chart, and there's no charge for the stream.

Question 11 · choose 1

A Python API instrumented with the Microsoft OpenTelemetry Distro handles about 200 requests per second, yet Application Insights holds far fewer traces than that. Nothing about sampling is configured. Why?

  1. AThe distro defaults to rate-limited sampling at five traces per second
  2. BApplication Insights drops all traces beyond 50 per second on ingestion
  3. CThe distro samples a fixed 10 percent of traces by default
  4. DMetrics are sampled at the same rate, so request counts are wrong too
Show the answer and why
  • AThe distro defaults to rate-limited sampling at five traces per second

    Correct

    That's the default for Azure Monitor export in the .NET, Node.js and Python distros.

  • BApplication Insights drops all traces beyond 50 per second on ingestion

    Incorrect

    The reduction comes from the distro's sampler, not from a fixed ingestion cap.

  • CThe distro samples a fixed 10 percent of traces by default

    Incorrect

    Fixed-rate sampling is an option you configure; the default is rate-limited.

  • DMetrics are sampled at the same rate, so request counts are wrong too

    Incorrect

    Metrics aren't sampled, which is why they're recommended for alerting.

Sampling lowers telemetry volume and cost. You can switch to a fixed ratio or a different traces-per-second rate; the Application Insights sampler keeps traces complete and stays compatible with Live Metrics.

Practise domain 4 →Practise all domains →