Applied Module 12 · AI-Accelerated Government Development

Operational Telemetry

What you'll learn

~25 min
  • Integrate Azure Application Insights using Azure Government endpoints
  • Retrieve secrets from Key Vault via Managed Identity instead of environment variables
  • Implement structured logging that never captures PII or full request bodies

Why telemetry exists on this platform

You are running a platform that state employees depend on daily. When a module page takes 8 seconds to load, when an RBAC check silently denies access it should grant, when a Managed Identity token expires mid-session — you need to know before a help desk ticket tells you. That is what operational telemetry does. It is not analytics. It is not user tracking. It is the instrumentation that tells you whether the platform is healthy.

On the DS platform, telemetry has three components: Application Insights for metrics and traces, Key Vault for secrets management, and structured logging with strict rules about what never gets recorded.

Azure Government endpoints

Application Insights in Azure Gov uses dc.applicationinsights.us, not dc.applicationinsights.azure.com. If you copy telemetry setup from Microsoft docs or Stack Overflow without changing the endpoint, your telemetry data silently goes nowhere. There is no error — the SDK swallows connection failures. You will think telemetry is working until you check the portal and find nothing.


Key Vault for secrets management

Connection strings, API keys, third-party credentials — none of these belong in code, .env files committed to the repo, or Azure DevOps pipeline variables stored in plain text. Key Vault is the only acceptable location.

The DS platform accesses Key Vault through Managed Identity. No client secrets. No certificates you rotate manually. The Azure resource itself authenticates.

src/lib/telemetry/key-vault.ts
import { DefaultAzureCredential } from '@azure/identity';
import { SecretClient } from '@azure/keyvault-secrets';
const credential = new DefaultAzureCredential({
authorityHost: 'https://login.microsoftonline.us', // Gov, not .com
});
const vaultUrl = `https://${process.env.KEY_VAULT_NAME}.vault.usgovcloudapi.net`;
const client = new SecretClient(vaultUrl, credential);
export async function getSecret(name: string): Promise<string> {
const secret = await client.getSecret(name);
if (!secret.value) {
throw new Error(`Secret "${name}" exists but has no value`);
}
return secret.value;
}
💡Cache secrets in memory

Key Vault has rate limits. Fetch secrets once at application startup and cache them in a module-scoped variable. Do not call getSecret() on every request. The Next.js server process stays alive between requests — use that.


Application Insights integration

The prompt below generates the App Insights integration for the DS platform. It handles the government endpoint, structured custom events, and the safe-logging rules.

The prompt

Set up Azure Application Insights for our Next.js 15.5 platform on Azure Government.
Requirements:
1. Install @microsoft/applicationinsights-web for client-side and
applicationinsights for server-side (Node.js SDK)
2. Connection string comes from Key Vault, secret name "appinsights-connection"
3. Government ingestion endpoint: dc.applicationinsights.us
4. Server-side: initialize in src/lib/telemetry/app-insights.ts
- Track custom events for module page loads with moduleName and userRole
- Track dependency calls (Azure SQL, Key Vault) with duration
- Track exceptions with severity level
5. Client-side: initialize in a React provider that wraps the app
- Page view tracking with route name (not full URL with query params)
- No PII in any telemetry: strip email, userId, IP address
- Set cloud role name to "ds-platform-web"
6. Safe logging rules:
- NEVER log full request or response bodies
- NEVER log authorization headers, tokens, or cookies
- NEVER log user email addresses, names, or SSNs
- Log request method, path, status code, duration, and correlationId only
- Use severity levels: Verbose, Information, Warning, Error, Critical
7. Create a telemetryMiddleware that adds correlation IDs to requests
and logs request/response metadata (method, path, status, duration)

What the AI generates

The AI should produce three key files. Here is what to expect in the server-side initialization:

// src/lib/telemetry/app-insights.ts (server-side)
import * as appInsights from 'applicationinsights';
import { getSecret } from './key-vault';
let initialized = false;
export async function initTelemetry() {
if (initialized) return;
const connectionString = await getSecret('appinsights-connection');
appInsights.setup(connectionString)
.setAutoCollectRequests(true)
.setAutoCollectPerformance(true, true)
.setAutoCollectExceptions(true)
.setAutoCollectDependencies(true)
.setAutoCollectConsole(false) // Do not auto-collect console.log
.setAutoDependencyCorrelation(true)
.setSendLiveMetrics(false) // Disable Live Metrics in gov
.start();
// Override endpoint for Azure Government
appInsights.defaultClient.config.endpointUrl =
'https://dc.applicationinsights.us/v2.1/track';
appInsights.defaultClient.context.tags[
appInsights.defaultClient.context.keys.cloudRole
] = 'ds-platform-api';
initialized = true;
}
setSendLiveMetrics(false)

Live Metrics Stream uses a separate WebSocket connection to rt.applicationinsights.azure.com. That commercial endpoint does not exist in Azure Gov. Setting it to false prevents a background connection attempt that logs confusing errors.

Custom event tracking

src/lib/telemetry/track.ts
import * as appInsights from 'applicationinsights';
export function trackModuleLoad(moduleName: string, userRole: string, durationMs: number) {
appInsights.defaultClient.trackEvent({
name: 'ModulePageLoad',
properties: {
moduleName,
userRole, // Role name only, never user ID
environment: process.env.NODE_ENV ?? 'unknown',
},
measurements: {
durationMs,
},
});
}
export function trackApiCall(
method: string,
path: string,
statusCode: number,
durationMs: number,
correlationId: string
) {
appInsights.defaultClient.trackRequest({
name: `${method} ${path}`,
url: path, // Path only, never full URL with query params
duration: durationMs,
resultCode: statusCode.toString(),
success: statusCode < 400,
properties: { correlationId },
});
}

Managed Identity diagnostics

When Managed Identity token acquisition fails, the error messages from @azure/identity are notoriously unhelpful. Here are the three failure modes you will actually encounter.

SymptomCauseFix
AADSTS90002: Tenant not foundCredential hitting login.microsoftonline.comSet authorityHost to https://login.microsoftonline.us
ManagedIdentityCredential: request timed outIMDS endpoint unreachableVerify the App Service has a system-assigned identity enabled in the Azure portal
No managed identity credential availableRunning locally, not on AzureUse DefaultAzureCredential chain — it falls back to Azure CLI auth locally
💡Local development fallback

DefaultAzureCredential tries Managed Identity first, then environment variables, then Azure CLI. For local dev, run az login --tenant YOUR_GOV_TENANT_ID once, and the credential chain handles the rest. No code changes between local and deployed.

Testing identity locally

Terminal window
# Verify Azure CLI is authenticated to the gov tenant
az account show --query '{name:name, tenantId:tenantId, cloud:environmentName}'
# Should show:
# {
# "name": "DS-Platform-Dev",
# "tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
# "cloud": "AzureUSGovernment"
# }

If cloud shows AzureCloud instead of AzureUSGovernment, you are authenticated to the wrong cloud. Run az cloud set --name AzureUSGovernment first, then az login again.


Safe logging rules

This is not optional. State platforms are subject to CJIS, HIPAA, and state audit requirements. Logging PII — even accidentally — creates a compliance finding.

Always log:

  • Request method, path, status code, duration
  • Correlation ID (for tracing a request across services)
  • Module name, operation name
  • Error messages (but sanitize first — see below)
  • Severity level (Verbose, Information, Warning, Error, Critical)

Never log:

  • Email addresses, user names, Social Security Numbers
  • Full request or response bodies (they may contain PII)
  • Authorization headers, Bearer tokens, cookies, session IDs
  • IP addresses (CJIS considers these PII in some contexts)
  • Query parameters (they may contain search terms with names)
// BAD: logs full request body which may contain PII
logger.info('Request received', { body: req.body });
// BAD: logs the authorization header
logger.info('Auth check', { auth: req.headers.authorization });
// GOOD: logs only structural metadata
logger.info('Request received', {
method: req.method,
path: req.nextUrl.pathname,
correlationId: req.headers.get('x-correlation-id'),
});
💬Sanitize error messages too

When an Azure SQL query fails, the error message sometimes includes the SQL statement, which may contain user-submitted values. Wrap error messages in a sanitizer that strips quoted strings before logging. Log the error code and the table name — never the full query text.


See it in action

Claude Code — AI-assisted
/home/user $ Set up App Insights for our Next.js platform. Connection string is in Key Vault secret 'appinsights-connection'. This is Azure Government.
I'll set up Application Insights with government endpoints and Key Vault integration. Created: - src/lib/telemetry/key-vault.ts — Key Vault client with gov endpoint - src/lib/telemetry/app-insights.ts — Server-side SDK init - src/lib/telemetry/track.ts — Custom event helpers - src/lib/telemetry/sanitize.ts — PII stripping for log messages - src/components/providers/TelemetryProvider.tsx — Client-side page view tracking Key decisions: - Ingestion endpoint: dc.applicationinsights.us (gov) - Live Metrics disabled (not available in gov) - Auto-collect console disabled (prevents accidental PII logging) - Cloud role name: ds-platform-api (server), ds-platform-web (client) - All custom events use role name, never user ID or email
/home/user $ I'm getting AADSTS90002 when the telemetry initializes. The Key Vault fetch is failing.
That error means the credential is hitting the commercial Azure AD endpoint instead of government. The issue is in key-vault.ts. Checking... the authorityHost is set correctly to login.microsoftonline.us. But the @azure/identity package also needs the AZURE_AUTHORITY_HOST environment variable for the DefaultAzureCredential chain. Add this to your App Service configuration: AZURE_AUTHORITY_HOST=https://login.microsoftonline.us This ensures all credential types in the chain (Managed Identity, environment, CLI) use the government authority. Without it, some credential types in the chain fall back to commercial before reaching the one that would work.
/home/user $ Add a custom event that tracks when users switch between density views (executive, operational, technical).
Added trackDensitySwitch to src/lib/telemetry/track.ts: ```typescript export function trackDensitySwitch( fromView: string, toView: string, moduleName: string ) { appInsights.defaultClient.trackEvent({ name: 'DensityViewSwitch', properties: { fromView, toView, moduleName, environment: process.env.NODE_ENV ?? 'unknown', }, }); } ``` Note: No user ID or role in this event. The event tracks feature usage patterns, not individual users. If you need per-role analytics, add userRole (the role name, never the user's identity).
/home/user $

Verifying telemetry is working

After deploying, open the Azure Government portal (portal.azure.us) and navigate to your Application Insights resource. Check these three things:

  1. Live Metrics — if you enabled it (gov regions added support in 2025), you should see requests flowing. If you disabled it, skip this.
  2. Transaction search — search for your custom event name (ModulePageLoad). If you see events, server-side telemetry is working.
  3. Page views — if the client-side provider is working, you will see page view events with route names (not full URLs).

If the portal shows no data after 5 minutes, the connection string is wrong or the endpoint is hitting commercial. Check the Key Vault secret value — it should contain IngestionEndpoint=https://dc.applicationinsights.us/.


KNOWLEDGE CHECK

Your team discovers that the Application Insights logs contain user email addresses from failed login attempts. The error message includes 'Authentication failed for user: jane.doe@state.gov'. What is the correct remediation?


Key takeaways

  • Application Insights in Azure Gov uses dc.applicationinsights.us for ingestion. Commercial endpoints silently fail — no error, just missing data.
  • Key Vault via Managed Identity is the only acceptable way to store connection strings and API keys. Never commit secrets to .env files in the repo. Never store them in pipeline variables as plain text.
  • Managed Identity failures usually trace back to the authority host targeting commercial Azure AD. Set AZURE_AUTHORITY_HOST=https://login.microsoftonline.us in your App Service config.
  • Safe logging is a compliance requirement, not a suggestion. Log structural metadata (method, path, status, duration, correlation ID). Never log PII, tokens, request bodies, or full error messages containing user-submitted data.
  • Custom events track feature usage, not users. Use role names, module names, and operation names — never user IDs or email addresses.

What’s next

In the next lesson, you will set up the Azure DevOps git workflow: feature branches, PAT authentication, and creating pull requests through the ADO REST API. The telemetry you just configured will help you monitor the platform once it is deployed through that pipeline.