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.
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.
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;}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.us4. 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 level5. 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, Critical7. 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;}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
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.
| Symptom | Cause | Fix |
|---|---|---|
AADSTS90002: Tenant not found | Credential hitting login.microsoftonline.com | Set authorityHost to https://login.microsoftonline.us |
ManagedIdentityCredential: request timed out | IMDS endpoint unreachable | Verify the App Service has a system-assigned identity enabled in the Azure portal |
No managed identity credential available | Running locally, not on Azure | Use DefaultAzureCredential chain — it falls back to Azure CLI auth locally |
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
# Verify Azure CLI is authenticated to the gov tenantaz 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 PIIlogger.info('Request received', { body: req.body });
// BAD: logs the authorization headerlogger.info('Auth check', { auth: req.headers.authorization });
// GOOD: logs only structural metadatalogger.info('Request received', { method: req.method, path: req.nextUrl.pathname, correlationId: req.headers.get('x-correlation-id'),});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
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:
- Live Metrics — if you enabled it (gov regions added support in 2025), you should see requests flowing. If you disabled it, skip this.
- Transaction search — search for your custom event name (
ModulePageLoad). If you see events, server-side telemetry is working. - 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/.
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.usfor 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
.envfiles 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.usin 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.