Skip to content

Add usage telemetry for worker custom metrics and Azure Monitor diagnostic logging - #12034

Open
Rohit Ranjan (RohitRanjanMS) wants to merge 3 commits into
devfrom
rohitranjanms-special-eureka
Open

Rohit Ranjan (RohitRanjanMS) wants to merge 3 commits into
devfrom
rohitranjanms-special-eureka

Conversation

@RohitRanjanMS

@RohitRanjanMS Rohit Ranjan (RohitRanjanMS) commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

Issue describing the changes in this PR

resolves #12033

Summary

We had no way to tell whether worker custom metrics or the AzureMonitorDiagnosticLogger are actually used by customers. Neither emitted anything to the Kusto metrics table.

This adds one metric event per feature through the existing IMetricsLogger → MetricsEventManager pipeline, following the pattern already used by MetricEventNames.OpenTelemetryOtlpEnabled. These flush via LogFunctionMetricEvent, so usage becomes queryable per app/subscription.

Event When Emitted from
host.azuremonitor.enabled Once per app initialization, when a category is explicitly subscribed ScriptHost.PreInitialize()
host.worker.custommetric Once per worker channel, on the first custom metric received WorkerChannel.Log()

Why two different mechanisms

The two features have opposite detectability, so a single approach can't cover both:

  • Azure Monitor is decided at startup from configuration, so it can be emitted during host initialization.
  • Custom metrics are only observable at runtime, when a worker actually sends one over gRPC. There is no startup signal, so it needs first-use detection — a one-shot Interlocked.Exchange per channel.

The custom-metric check is placed before the _executingInvocations lookup in Log(). Metrics that arrive without a live invocation are dropped there, but they still represent a customer using the feature, so they should count.

The Azure Monitor overcounting problem

IEnvironment.IsAzureMonitorEnabled() returns true when WEBSITE_FUNCTIONS_AZUREMONITOR_CATEGORIES is absent, not only when a customer subscribed:

if (azureMonitorcategoriesSubscribed == null)
{
    return true;
}

Reusing it directly would sweep in every app where the platform never set the variable, which would not answer the question being asked. This PR adds IsAzureMonitorExplicitlyEnabled() next to it, requiring a non-null value plus an actual category match:

WEBSITE_FUNCTIONS_AZUREMONITOR_CATEGORIES IsAzureMonitorEnabled IsAzureMonitorExplicitlyEnabled
unset true false
None false false
Foo,Bar false false
FunctionAppLogs true true
Foo,FunctionAppLogs,Bar true true

IsAzureMonitorEnabled() is deliberately left untouched — it still gates real AzureMonitorDiagnosticLoggerProvider registration in WebScriptHostBuilderExtension, and narrowing it would change actual logging behaviour rather than just telemetry.

Interpreting the data

Both signals are presence indicators, not volume:

  • host.azuremonitor.enabled fires once per app init.
  • host.worker.custommetric fires once per worker channel, so a multi-worker app aggregates to Count = N. Treat Count as worker count, not usage volume.

Notes for reviewers

  • _customMetricUsageLogged is int rather than bool because Interlocked.Exchange has no bool overload. This is the standard one-shot pattern, not an oversight.
  • Placeholder-mode hosts also run PreInitialize, and MetricsEventManager.WriteMetricEvents reads AppServiceOptions at flush time, so an event queued before specialization can be stamped with the specialized app name. The existing ApplicationInsightsEnabled / OpenTelemetry events already behave this way, so this is consistent — worth knowing when writing the Kusto query.
  • The source change is additive only: 64 insertions, 0 deletions, no refactoring of existing code paths.

Pull request checklist

IMPORTANT: Currently, changes must be backported to the in-proc branch to be included in Core Tools and non-Flex deployments.

  • Backporting to the in-proc branch is not required
    • Otherwise: Link to backporting PR
  • My changes do not require documentation changes
    • Otherwise: Documentation issue linked to PR
  • My changes should not be added to the release notes for the next release
    • Otherwise: I've added my notes to release_notes.md
  • My changes do not need to be backported to a previous version
    • Otherwise: Backport tracked by issue/PR #issue_or_pr
  • My changes do not require diagnostic events changes
    • Otherwise: I have added/updated all related diagnostic events and their documentation (Documentation issue linked to PR)
  • I have added all required tests (Unit tests, E2E tests)

Additional information

Internal telemetry only — no customer-facing behaviour change.

Tests added:

  • EnvironmentExtensionsTests.IsAzureMonitorExplicitlyEnabled_ReturnsExpectedResult — 6-case theory covering unset / empty / None / matching / non-matching / multi-category, mirroring the existing IsAzureMonitorEnabled theory beside it.
  • GrpcWorkerChannelTests.Log_CustomMetric_LogsUsageMetricOncePerChannel — verifies the one-shot guarantee across repeated custom metrics and that a User-category log does not trigger it.

Verified: ScriptHostTests, GrpcWorkerChannelTests, EnvironmentExtensionsTests, UtilityTests — 566 passed, 0 failed.

We had no way to tell whether worker custom metrics or the
AzureMonitorDiagnosticLogger are actually used by customers, which made it
risky to change or deprecate either. Neither emitted anything to the Kusto
metrics table.

Emit a metric event for each through the existing IMetricsLogger pipeline,
following the pattern already used for OpenTelemetryOtlpEnabled:

- host.azuremonitor.enabled: once per app init, from ScriptHost.PreInitialize
- host.worker.custommetric: once per worker channel, on first custom metric

The two features have opposite detectability, so one mechanism cannot cover
both. Azure Monitor is decided at startup from configuration. Custom metrics
are only observable at runtime, so they use a one-shot Interlocked flag placed
before the _executingInvocations lookup, ensuring metrics that arrive outside
an invocation (and are dropped) still register as usage.

IsAzureMonitorEnabled() returns true when the AzureMonitor categories variable
is absent, so it overcounts and cannot answer the usage question. Add
IsAzureMonitorExplicitlyEnabled() alongside it, which requires an explicit
category subscription. The original method is left untouched so the real
provider registration gate is unaffected.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4b780f55-8b0d-4544-b99d-a07dab44ee80
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4b780f55-8b0d-4544-b99d-a07dab44ee80
Resolves a conflict in WorkerChannel.Log: dev changed the signature from
Log(GrpcEvent) to Log(StreamingMessage), so rpcLog is now read from
msg.RpcLog instead of msg.Message.RpcLog. Kept the custom metric usage
check and adapted it to the new accessor. Updated the CreateRpcLogEvent
test helper to return StreamingMessage to match.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4b780f55-8b0d-4544-b99d-a07dab44ee80
@RohitRanjanMS
Rohit Ranjan (RohitRanjanMS) marked this pull request as ready for review September 21, 2026 17:51
Copilot AI lite review requested due to automatic review settings September 21, 2026 17:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Add ScriptHost initialization coverage and correct the release-note issue reference.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds telemetry for Azure Monitor diagnostic logging and worker custom metrics.

Changes:

  • Detects explicit Azure Monitor subscriptions.
  • Records first custom metric usage per worker channel.
  • Adds tests and release notes.
File Summary
test/​WebJobs.Script.Tests/​Workers/​Rpc/​GrpcWorkerChannelTests.cs Tests one-shot worker metric telemetry.
test/​WebJobs.Script.Tests/​Extensions/​EnvironmentExtensionsTests.cs Tests Azure Monitor detection.
src/​WebJobs.Script/​Host/​ScriptHost.cs Emits Azure Monitor usage telemetry.
src/​WebJobs.Script/​Environment/​EnvironmentExtensions.cs Adds explicit subscription detection.
src/​WebJobs.Script/​Diagnostics/​MetricEventNames.cs Defines telemetry event names.
src/​WebJobs.Script.Grpc/​Channel/​WorkerChannel.cs Tracks first custom metric per channel.
release_notes.md Documents the telemetry change.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +479 to +481
if (_environment.IsAzureMonitorExplicitlyEnabled())
{
_metricsLogger.LogEvent(MetricEventNames.AzureMonitorEnabled);

// Record that this app uses worker custom metrics. Emitted once per channel, before the
// invocation lookup below, so metrics arriving outside an invocation are still counted.
if (rpcLog.LogCategory == RpcLogCategory.CustomMetric && Interlocked.Exchange(ref _customMetricUsageLogged, 1) == 0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor improvement so we don't have to call Exchange every time on this hot path:

if (rpcLog.LogCategory == RpcLogCategory.CustomMetric &&
    _customMetricUsageLogged == 0 &&
    Interlocked.Exchange(ref _customMetricUsageLogged, 1) == 0)
{
    _metricsLogger.LogEvent(MetricEventNames.WorkerCustomMetric);
}

A small benchmark shows this is more efficient after _customMetricLogged is set.

Scenario No tracking Current condition Read before exchange
One thread 1.6 ns/op 2.5 ns/op 1.9 ns/op
Four threads sharing one flag 0.4 ns/op 18–19 ns/op 0.5 ns/op

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add usage telemetry for worker custom metrics and Azure Monitor diagnostic logging

3 participants