Skip to content

Commit d349822

Browse files
feat: Add Azure Monitor OpenTelemetry Profiler for container deployments (#1107)
## Summary Adds the [Azure Monitor OpenTelemetry Profiler](https://github.com/Azure/azuremonitor-opentelemetry-profiler-net) (`Azure.Monitor.OpenTelemetry.Profiler`) to enable Application Insights profiling and [Code Optimizations](https://learn.microsoft.com/en-us/azure/azure-monitor/optimization-insights/code-optimizations-profiler-overview) in production. ## Why this package (not `Microsoft.ApplicationInsights.Profiler.AspNetCore`) The app already uses `Azure.Monitor.OpenTelemetry.AspNetCore` (OTel distro). Microsoft's docs list two profiler paths: - **Old SDK path**: `Microsoft.ApplicationInsights.Profiler.AspNetCore` + `AddServiceProfiler()` — uses a native uploader binary, incompatible with chiseled containers - **New OTel path**: `Azure.Monitor.OpenTelemetry.Profiler` + `AddAzureMonitorProfiler()` ← **this PR** — uses EventPipe (pure in-process), compatible with chiseled images ## Changes - **`Directory.Packages.props`**: Add `Azure.Monitor.OpenTelemetry.Profiler 1.0.0-beta9` to Central Package Management (prerelease-only; TODO comment added to update at GA) - **`EssentialCSharp.Web.csproj`**: Reference the new package - **`Program.cs`**: Chain `.AddAzureMonitorProfiler()` onto `UseAzureMonitor()` — profiler only activates when `APPLICATIONINSIGHTS_CONNECTION_STRING` is set (production only, never local/OTLP) - **`appsettings.Development.json`**: Add `Debug` log level for profiler namespaces for local diagnostics ## Testing | Test | Result | |---|---| | `dotnet run` with fake connection string | ✅ Profiler agent loads, initializes, CPU sampling begins — fails only on invalid key (expected) | | Docker (`noble-chiseled-extra`) with fake key | ✅ Identical behavior — no native dependency errors, no shell-not-found failures | ## What this enables in production Once `APPLICATIONINSIGHTS_CONNECTION_STRING` is set in the ACA deployment config: - **Sampling trigger**: Profiles ~2 min/hour automatically - **CPU trigger**: Profiles when CPU >80% - **Memory trigger**: Profiles when memory >80% - **Code Optimizations**: AI-based perf insights in Azure portal (works because we use default storage, not BYOS) ## Notes - No BYOS (Bring Your Own Storage) — intentional; BYOS would disable Code Optimizations - No manual request tracking needed — ASP.NET Core auto-tracks requests - Trigger thresholds can be tuned in Azure portal → App Insights → Performance → Profiler → Triggers --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
1 parent 1f57837 commit d349822

4 files changed

Lines changed: 35 additions & 19 deletions

File tree

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.5.1" />
2323
<PackageVersion Include="Azure.Identity" Version="1.21.0" />
2424
<PackageVersion Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.5.0" />
25+
<!-- TODO: update to stable release when Azure.Monitor.OpenTelemetry.Profiler reaches GA -->
26+
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Profiler" Version="1.0.0-beta9" />
2527
<PackageVersion Include="TUnit" Version="1.40.5" />
2628
<PackageVersion Include="EssentialCSharp.Shared.Models" Version="$(ToolingPackagesVersion)" />
2729
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />

EssentialCSharp.Web/EssentialCSharp.Web.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Keys" />
3939
<PackageReference Include="Azure.Identity" />
4040
<PackageReference Include="Azure.Monitor.OpenTelemetry.AspNetCore" />
41+
<PackageReference Include="Azure.Monitor.OpenTelemetry.Profiler" />
4142
<PackageReference Include="EssentialCSharp.Shared.Models" />
4243
<PackageReference Include="HtmlAgilityPack" />
4344
<PackageReference Include="IntelliTect.Multitool" />

EssentialCSharp.Web/Program.cs

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Microsoft.AspNetCore.Identity.UI.Services;
2020
using Microsoft.AspNetCore.RateLimiting;
2121
using Azure.Monitor.OpenTelemetry.AspNetCore;
22+
using Azure.Monitor.OpenTelemetry.Profiler;
2223
using Microsoft.AspNetCore.DataProtection;
2324
using Microsoft.AspNetCore.Diagnostics;
2425
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
@@ -90,7 +91,7 @@ private static void Main(string[] args)
9091
});
9192

9293
if (useAzureMonitor)
93-
otel.UseAzureMonitor();
94+
otel.UseAzureMonitor().AddAzureMonitorProfiler();
9495
else if (useOtlp)
9596
otel.UseOtlpExporter();
9697

@@ -511,7 +512,7 @@ await McpJsonRpcResponseWriter.WriteErrorAsync(
511512
}
512513
app.UseStaticFiles();
513514

514-
app.UseRouting();
515+
app.UseRouting();
515516

516517
app.UseWhen(
517518
context => context.Request.Path.StartsWithSegments("/mcp"),
@@ -542,10 +543,10 @@ await McpJsonRpcResponseWriter.WriteErrorAsync(
542543
await next(context);
543544
}));
544545

545-
app.UseRateLimiter();
546-
547-
app.UseAuthorization();
548-
app.UseOutputCache();
546+
app.UseRateLimiter();
547+
548+
app.UseAuthorization();
549+
app.UseOutputCache();
549550

550551
app.UseMiddleware<ReferralMiddleware>();
551552

@@ -584,13 +585,23 @@ await McpJsonRpcResponseWriter.WriteErrorAsync(
584585
try
585586
{
586587
SitemapXmlHelpers.EnsureSitemapHealthy(siteMappingService.SiteMappings.ToList());
587-
LogSitemapValidationSucceeded(logger);
588-
}
589-
catch (Exception ex)
590-
{
591-
LogSitemapValidationFailed(logger, ex);
592-
// Continue startup even if sitemap validation fails
593-
}
588+
LogSitemapValidationSucceeded(logger);
589+
}
590+
catch (InvalidOperationException ex)
591+
{
592+
LogSitemapValidationFailed(logger, ex);
593+
// Continue startup even if sitemap validation fails
594+
}
595+
catch (ArgumentException ex)
596+
{
597+
LogSitemapValidationFailed(logger, ex);
598+
// Continue startup even if sitemap validation fails
599+
}
600+
catch (FormatException ex)
601+
{
602+
LogSitemapValidationFailed(logger, ex);
603+
// Continue startup even if sitemap validation fails
604+
}
594605

595606
app.Run();
596607
}
@@ -604,11 +615,11 @@ private static bool IsMcpTransportRequest(HttpRequest request) =>
604615
[LoggerMessage(Level = LogLevel.Error, Message = "Unhandled exception on {Path}")]
605616
private static partial void LogUnhandledException(ILogger<Program> logger, Exception? exception, PathString path);
606617

607-
[LoggerMessage(Level = LogLevel.Information, Message = "Sitemap validation completed successfully during application startup")]
608-
private static partial void LogSitemapValidationSucceeded(ILogger<Program> logger);
609-
610-
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to validate sitemap during application startup")]
611-
private static partial void LogSitemapValidationFailed(ILogger<Program> logger, Exception exception);
618+
[LoggerMessage(Level = LogLevel.Information, Message = "Sitemap validation completed successfully during application startup")]
619+
private static partial void LogSitemapValidationSucceeded(ILogger<Program> logger);
620+
621+
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to validate sitemap during application startup")]
622+
private static partial void LogSitemapValidationFailed(ILogger<Program> logger, Exception exception);
612623

613624
[LoggerMessage(Level = LogLevel.Warning, Message = "Ignoring invalid TryDotNet origin in CSP: {Origin}")]
614625
private static partial void LogIgnoringInvalidTryDotNetOrigin(ILogger logger, string origin);

EssentialCSharp.Web/appsettings.Development.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"Logging": {
44
"LogLevel": {
55
"Default": "Information",
6-
"Microsoft.AspNetCore": "Warning"
6+
"Microsoft.AspNetCore": "Warning",
7+
"Microsoft.ServiceProfiler": "Debug",
8+
"Azure.Monitor.OpenTelemetry.Profiler": "Debug"
79
}
810
},
911
"ConnectionStrings": {

0 commit comments

Comments
 (0)