Skip to content

CAMS-709: Disable App Insights console auto-collection#2002

Draft
jamesobrooks wants to merge 8 commits intomainfrom
CAMS-709-add-tags
Draft

CAMS-709: Disable App Insights console auto-collection#2002
jamesobrooks wants to merge 8 commits intomainfrom
CAMS-709-add-tags

Conversation

@jamesobrooks
Copy link
Contributor

@jamesobrooks jamesobrooks commented Feb 28, 2026

Purpose

Resolve production issue where custom events and metrics were not appearing in Application Insights, and eliminate inconsistent ApplicationContext usage patterns across dataflows.

Major Changes

  • Explicit App Insights SDK initialization: getOrInitializeAppInsightsClient() calls setup().start() when defaultClient is null, resolving production telemetry failures
  • ExtraOutputs interface: Humble object abstraction for Azure Functions queue operations, replacing unknown type in ApplicationContext
  • Standardize dataflow handlers: Refactor all handlers in migrate-cases.ts to follow API function pattern (create logger early, create ApplicationContext once, use only appContext thereafter)
  • Refactor helper functions: Accept ApplicationContext instead of InvocationContext, eliminating multiple observability instances per invocation
  • Critical error logging: Add CRITICAL-level logging when telemetry fails to highlight business reporting impact

Testing/Validation

  • Deployed to dev (rg-cams-app-dev-686ed5), staging, and production
  • All environments validated: customEvents now appearing in Application Insights workbooks
  • All tests passing (1491 tests, 140 suites)
  • Production workbooks now accurately reflect case migration metrics

Notes

Historic customEvents from before this fix deployment will never be created, creating a permanent gap in pre-fix metrics. Post-fix data is accurate.

The ExtraOutputs interface and standardized ApplicationContext pattern should be applied to remaining dataflows not yet refactored.

Definition of Done:

  • Code refactored for clarity: Standardized patterns across all dataflow handlers
  • Dependency rule followed: ExtraOutputs interface inverts dependency on Azure Functions
  • Development debt eliminated: Inconsistent ApplicationContext usage patterns resolved
  • No regressions: All tests passing, deployed and validated across environments

Summary by Sourcery

Standardize observability and ApplicationContext usage in the migrate-cases dataflow while hardening Application Insights initialization and configuration to prevent duplicate logs and ensure telemetry is reliably captured.

New Features:

  • Introduce explicit Application Insights SDK initialization that creates a default client when missing, based on the configured connection string.

Bug Fixes:

  • Ensure Application Insights telemetry client is available in production by initializing the SDK when defaultClient is not present, restoring custom event and metric tracking.
  • Disable Application Insights console auto-collection in Azure Functions to prevent duplicate log entries in telemetry.

Enhancements:

  • Refine ApplicationContext to expose a typed ExtraOutputs interface for queue outputs instead of using an untyped value.
  • Refactor migrate-cases dataflow handlers to construct a single ApplicationContext per invocation, reuse observability and logger from it, and wrap handler logic in error-handling blocks with consistent logging.
  • Improve observability error handling by logging CRITICAL-level messages when telemetry cannot be sent due to an unavailable Application Insights client.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 28, 2026

Reviewer's Guide

Refactors migrate-cases dataflow handlers to use a standardized ApplicationContext and ExtraOutputs abstraction while adding robust, explicitly initialized Application Insights telemetry configuration that disables console auto-collection and improves critical error logging when telemetry is unavailable.

Sequence diagram for migrate-cases handler flow with ApplicationContext and ExtraOutputs

sequenceDiagram
  participant AF as AzureFunctionHost
  participant HC as handlePage
  participant ACC as ApplicationContextCreator
  participant AC as ApplicationContext
  participant MC as MigrateCases
  participant ELC as ExportAndLoadCase
  participant Q as AzureStorageQueues
  participant AI as ApplicationInsights

  AF->>HC: invoke handlePage(range, invocationContext)
  HC->>ACC: getApplicationContext(invocationContext, logger)
  ACC-->>HC: ApplicationContext AC

  HC->>AC: observability.startTrace(invocationId)
  HC->>MC: getPageOfCaseEvents(AC, start, end)
  MC-->>HC: CaseSyncEvent[] events

  HC->>ELC: exportAndLoad(AC, events)
  ELC-->>HC: processedEvents

  HC->>AC: extraOutputs.set(DLQ, failedEvents)
  HC->>AC: extraOutputs.set(...)

  HC->>AC: observability.trackEvent(dataflow metrics)
  AC->>AI: trackEvent(customEvent)

  HC-->>AF: complete
Loading

Sequence diagram for explicit Application Insights initialization and configuration

sequenceDiagram
  participant DF as dataflows.ts
  participant ACC as ApplicationContextCreator
  participant CFG as configureAppInsights
  participant GCF as getOrInitializeAppInsightsClient
  participant AISDK as applicationinsightsSDK
  participant AI as ApplicationInsights
  participant LOG as LoggerImpl

  DF->>CFG: configureAppInsights()
  CFG->>AISDK: require(applicationinsights)
  alt SDK not initialized yet
    AISDK-->>CFG: defaultClient null
    CFG-->>DF: return (no console config)
  else SDK already initialized by host
    CFG->>AISDK: Configuration.setAutoCollectConsole(false)
    AISDK-->>CFG: console collection disabled
    CFG-->>DF: return
  end

  ACC->>GCF: getOrInitializeAppInsightsClient(LOG)
  GCF->>AISDK: require(applicationinsights)
  alt defaultClient exists
    AISDK-->>GCF: defaultClient
    GCF-->>ACC: TelemetryClient
  else defaultClient missing
    GCF->>AISDK: setup(connectionString).start()
    AISDK-->>GCF: defaultClient
    GCF-->>ACC: TelemetryClient or null
  end

  ACC->>AI: use TelemetryClient for traces/events
Loading

Class diagram for updated ApplicationContext, ExtraOutputs, and AppInsightsObservability

classDiagram
  class ExtraOutputs {
    +set(output unknown, value unknown) void
  }

  class ApplicationContext {
    +config ApplicationConfiguration
    +featureFlags FeatureFlagSet
    +observability ObservabilityGateway
    +logger LoggerImpl
    +invocationId string
    +request CamsHttpRequest
    +closables Closable[]
    +releasables Releasable[]
    +extraOutputs ExtraOutputs
  }

  class AppInsightsObservability {
    -clientFactory AppInsightsClientFactory
    -MODULE_NAME string
    +constructor(logger LoggerImpl, clientFactory AppInsightsClientFactory)
    +startTrace(invocationId string) ObservabilityTrace
    +trackEvent(eventName string, properties Record)
    +trackMetric(metricName string, value number, properties Record)
  }

  class TelemetryEvent {
    +name string
    +properties Record
  }

  class TelemetryMetric {
    +name string
    +value number
    +properties Record
  }

  class TelemetryClient {
    +trackEvent(event TelemetryEvent) void
    +trackMetric(metric TelemetryMetric) void
  }

  class LoggerImpl
  class ObservabilityGateway
  class AppInsightsClientFactory

  ApplicationContext --> ExtraOutputs : has
  ApplicationContext --> ObservabilityGateway : uses
  ApplicationContext --> LoggerImpl : uses
  AppInsightsObservability ..|> ObservabilityGateway
  AppInsightsObservability --> LoggerImpl : optional
  AppInsightsObservability --> TelemetryClient : via clientFactory
  AppInsightsObservability --> AppInsightsClientFactory : uses
Loading

File-Level Changes

Change Details Files
Standardize migrate-cases dataflow handlers around ApplicationContext and ExtraOutputs instead of direct InvocationContext usage.
  • Updated handleStart, handlePage, handleError, and handleRetry to create a logger once, obtain a single ApplicationContext per invocation, and use appContext for observability, logging, and extraOutputs.
  • Wrapped each handler body in try/catch to log failures with the function logger and rethrow errors.
  • Replaced direct InvocationContext.extraOutputs usage with appContext.extraOutputs.set for PAGE, DLQ, RETRY, and HARD_STOP outputs.
  • Refactored helper functions (loadMigrationTable, emptyMigrationTable, getCaseIdsToMigrate, storeRuntimeState) to accept an ApplicationContext instead of InvocationContext and to use its extraOutputs and observability.
backend/function-apps/dataflows/migrations/migrate-cases.ts
Introduce explicit Application Insights configuration that disables console auto-collection and centralizes SDK initialization.
  • Added configureAppInsights to configure the Application Insights SDK once, disabling console auto-collection while leaving custom events and metrics intact, guarded by an isConfigured singleton flag.
  • Modified getAppInsightsClient utility to only return a client when the SDK is already initialized and to use consistent, App Insights–specific log prefixes.
  • Ensured Application Insights is configured early in the Azure Functions host lifecycle by invoking configureAppInsights in application-context-creator and dataflows bootstrap code.
backend/function-apps/azure/app-insights.ts
backend/function-apps/azure/application-context-creator.ts
backend/function-apps/dataflows/dataflows.ts
Make observability more robust by explicitly initializing the Application Insights client when needed and surfacing critical telemetry failures in logs.
  • Replaced getAppInsightsClient with getOrInitializeAppInsightsClient that will explicitly call appInsights.setup(connectionString).start() when defaultClient is missing, logging critical errors if configuration is absent or initialization fails.
  • Updated AppInsightsObservability to use the new client factory and to emit CRITICAL-level error logs when telemetry cannot be sent for an event due to a missing client.
backend/lib/adapters/services/observability.ts
Introduce a typed ExtraOutputs abstraction on ApplicationContext for Azure Functions output bindings.
  • Added an ExtraOutputs interface with a typed set method and updated ApplicationContext.extraOutputs to use this interface instead of unknown, clarifying its intended usage for queue and other outputs.
  • Adjusted migrate-cases dataflow code to rely on the typed ExtraOutputs API throughout.
backend/lib/adapters/types/basic.d.ts
backend/function-apps/dataflows/migrations/migrate-cases.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In configureAppInsights, you set isConfigured = true when the module is falsy or throws, but not when defaultClient is missing, which means the function will keep retrying and logging on every call in that scenario; consider setting isConfigured there as well if repeated retries aren’t useful.
  • Since configureAppInsights is invoked at module load in both dataflows.ts and application-context-creator.ts, it might be worth clarifying in comments that the singleton isConfigured flag lives in app-insights.ts and ensures these multiple call sites won’t conflict or cause double configuration.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `configureAppInsights`, you set `isConfigured = true` when the module is falsy or throws, but not when `defaultClient` is missing, which means the function will keep retrying and logging on every call in that scenario; consider setting `isConfigured` there as well if repeated retries aren’t useful.
- Since `configureAppInsights` is invoked at module load in both `dataflows.ts` and `application-context-creator.ts`, it might be worth clarifying in comments that the singleton `isConfigured` flag lives in `app-insights.ts` and ensures these multiple call sites won’t conflict or cause double configuration.

## Individual Comments

### Comment 1
<location path="backend/function-apps/azure/app-insights.ts" line_range="45-46" />
<code_context>
+
+    // Disable console auto-collection to prevent duplicate logs
+    // The Functions host already captures logs via invocationContext.log()
+    if (appInsights.Configuration?.setAutoCollectConsole) {
+      appInsights.Configuration.setAutoCollectConsole(false);
+      console.log('[APP-INSIGHTS] Console auto-collection disabled to prevent duplicate logs');
+    }
</code_context>
<issue_to_address>
**question (bug_risk):** The `setAutoCollectConsole(false)` usage may not align with the Application Insights SDK API, which typically expects additional parameters.

In most versions of the Node.js SDK, `setAutoCollectConsole` is used on the config/setup chain with a signature like `setAutoCollectConsole(enabled: boolean, collectWarningsAndErrors?: boolean)`. Using `Configuration.setAutoCollectConsole(false)` directly may be a no-op depending on how this SDK version exposes configuration. Please:

- Verify that `appInsights.Configuration.setAutoCollectConsole` is a valid, supported entry point for your target SDK version, and
- Consider passing both arguments (e.g. `setAutoCollectConsole(false, false)`) or configuring via the documented chain (e.g. `appInsights.setup().setAutoCollectConsole(...)` or `defaultClient.config`).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

jamesobrooks and others added 4 commits March 5, 2026 10:02
Azure Functions host logging and App Insights SDK console
auto-collection were both capturing logs, causing duplicate
entries in Application Insights. Added configureAppInsights()
function to disable SDK console auto-collection at startup
while preserving custom event/metric tracking. This ensures
logs flow through only the Functions host path.

Changes:
- Created configureAppInsights() in app-insights.ts with
  singleton pattern to disable console auto-collection
- Initialized in dataflows.ts at module load time
- Initialized in application-context-creator.ts for API
  functions

Jira ticket: CAMS-709

Co-authored-by: Claude <noreply@anthropic.com>
Explicitly initialize Application Insights SDK if defaultClient
doesn't exist, resolving production issue where custom events and
metrics were not being sent. Add getOrInitializeAppInsightsClient()
that calls setup().start() when needed.

Create ExtraOutputs interface as humble object abstraction for Azure
Functions queue operations, replacing unknown type in ApplicationContext.

Standardize all dataflow handlers to follow API function pattern:
create logger early, create ApplicationContext once, use only
appContext thereafter. Refactor helper functions to accept
ApplicationContext instead of InvocationContext, eliminating multiple
observability instances per invocation.

Add CRITICAL error logging when telemetry fails to highlight business
reporting impact for stakeholders.

Jira ticket: CAMS-709

Co-authored-by: Prem Govinda <16512926+bityogi@users.noreply.github.com>
jamesobrooks and others added 4 commits March 5, 2026 14:46
Jira ticket: CAMS-709

Co-authored-by: Kelly D <41132296+diabagatekelly@users.noreply.github.com>
Extract repeated logger/context/trace initialization into withAppContextAndTrace helper function. Simplifies all four handlers (handleStart, handlePage, handleError, handleRetry) by removing ~60 lines of duplicated error handling and setup code.

Addresses Sourcery code review feedback.
Reverted previous SDK-level fixes and instead disable console auto-collection
via environment variable. This allows Azure Functions runtime to exclusively
handle console log forwarding to App Insights, eliminating duplication.

Changes:
- Reverted app-insights.ts and observability.ts to original implementations
- Added APPLICATIONINSIGHTS_ENABLE_CONSOLE_AUTO_COLLECTION=false to both
  API and dataflows Bicep templates

Jira ticket: CAMS-709
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants