Skip to content

Commit 4dfac7f

Browse files
wip
1 parent 9f9b3db commit 4dfac7f

5 files changed

Lines changed: 66 additions & 32 deletions

File tree

Directory.Build.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@
1111
<!-- https://aka.ms/vs-build-acceleration -->
1212
<AccelerateBuildsInVisualStudio>True</AccelerateBuildsInVisualStudio>
1313
<UserSecretsId>18e91e0d-ea2d-490f-b77e-ec008f9d09ec</UserSecretsId>
14+
<!-- Only fail the build for moderate-or-higher vulnerability advisories. Low-severity
15+
transitive/tooling advisories (e.g. NuGet.Packaging) should not block builds. -->
16+
<NuGetAuditLevel>moderate</NuGetAuditLevel>
1417
</PropertyGroup>
1518
</Project>

Directory.Packages.props

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.5.0" />
2424
<PackageVersion Include="Azure.Identity" Version="1.21.0" />
2525
<PackageVersion Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.4.0" />
26-
<PackageVersion Include="Microsoft.ApplicationInsights.Profiler.AspNetCore" Version="3.0.2" />
2726
<PackageVersion Include="TUnit" Version="1.33.0" />
2827
<PackageVersion Include="EssentialCSharp.Shared.Models" Version="$(ToolingPackagesVersion)" />
2928
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
@@ -52,12 +51,12 @@
5251
<PackageVersion Include="System.CommandLine" Version="2.0.5" />
5352
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
5453
<PackageVersion Include="Octokit" Version="14.0.0" />
55-
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.0" />
56-
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.15.0" />
57-
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.0" />
54+
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.2" />
55+
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.15.2" />
56+
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.1" />
5857
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.15.0" />
5958
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.0" />
60-
<PackageVersion Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.15.0" />
59+
<PackageVersion Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.15.1" />
6160
<PackageVersion Include="DotnetSitemapGenerator" Version="2.0.0" />
6261
</ItemGroup>
6362
</Project>

EssentialCSharp.Web/EssentialCSharp.Web.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
CA1873: Logging argument evaluation - suppress for now; affects 14+ scaffolded Identity pages.
66
TODO: Address by converting to LoggerMessage source generators in a follow-up.
77
-->
8-
<NoWarn>$(NoWarn);CA1873</NoWarn>
8+
<!-- CA1873: Logging argument evaluation - scaffolded Identity pages.
9+
LOGGEN036: LoggerMessage source generator - JsonResult lacks ToString/IConvertible (.NET 10). -->
10+
<NoWarn>$(NoWarn);CA1873;LOGGEN036</NoWarn>
911
</PropertyGroup>
1012

1113
<ItemGroup>

EssentialCSharp.Web/Program.cs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using Microsoft.EntityFrameworkCore;
1919
using Microsoft.Extensions.Diagnostics.HealthChecks;
2020
using OpenTelemetry;
21+
using OpenTelemetry.Instrumentation.AspNetCore;
2122
using OpenTelemetry.Metrics;
2223
using OpenTelemetry.Trace;
2324

@@ -33,37 +34,55 @@ private static void Main(string[] args)
3334
builder.Services.AddHealthChecks()
3435
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
3536

36-
// OpenTelemetry — Aspire injects OTEL_EXPORTER_OTLP_ENDPOINT when hosted.
37-
// Azure Monitor is enabled in production when APPLICATIONINSIGHTS_CONNECTION_STRING is set.
37+
// OpenTelemetry — two mutually exclusive export paths:
38+
// Production: Azure Monitor (Application Insights) via APPLICATIONINSIGHTS_CONNECTION_STRING
39+
// Local/Aspire: OTLP to Aspire Dashboard via OTEL_EXPORTER_OTLP_ENDPOINT
40+
// Never both simultaneously — that would cause duplicate telemetry in App Insights.
3841
bool useAzureMonitor = !string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]);
42+
bool useOtlp = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
43+
3944
builder.Logging.AddOpenTelemetry(logging =>
4045
{
4146
logging.IncludeFormattedMessage = true;
4247
logging.IncludeScopes = true;
4348
});
44-
builder.Services.AddOpenTelemetry()
45-
.WithMetrics(metrics => metrics
46-
.AddAspNetCoreInstrumentation()
47-
.AddHttpClientInstrumentation()
48-
.AddRuntimeInstrumentation())
49+
50+
// Health probe paths excluded from tracing unconditionally — applies to both
51+
// manual instrumentation and Azure Monitor's auto-instrumentation.
52+
builder.Services.Configure<AspNetCoreTraceInstrumentationOptions>(options =>
53+
options.Filter = ctx =>
54+
!ctx.Request.Path.StartsWithSegments("/health")
55+
&& !ctx.Request.Path.StartsWithSegments("/alive"));
56+
57+
var otel = builder.Services.AddOpenTelemetry()
58+
.WithMetrics(metrics =>
59+
{
60+
// Azure Monitor auto-instruments ASP.NET Core + HttpClient metrics; only add
61+
// them manually when using OTLP so we don't register duplicate meter listeners.
62+
if (!useAzureMonitor)
63+
{
64+
metrics.AddAspNetCoreInstrumentation()
65+
.AddHttpClientInstrumentation();
66+
}
67+
// Runtime metrics are not included in the Azure Monitor distro.
68+
metrics.AddRuntimeInstrumentation();
69+
})
4970
.WithTracing(tracing =>
5071
{
5172
tracing.AddSource(builder.Environment.ApplicationName);
73+
// Azure Monitor distro auto-instruments tracing; add manually only for OTLP path.
5274
if (!useAzureMonitor)
5375
{
54-
tracing
55-
.AddAspNetCoreInstrumentation(t =>
56-
t.Filter = ctx =>
57-
!ctx.Request.Path.StartsWithSegments("/health")
58-
&& !ctx.Request.Path.StartsWithSegments("/alive"))
59-
.AddHttpClientInstrumentation()
60-
.AddSqlClientInstrumentation();
76+
tracing.AddAspNetCoreInstrumentation()
77+
.AddHttpClientInstrumentation()
78+
.AddSqlClientInstrumentation();
6179
}
6280
});
63-
if (!string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]))
64-
builder.Services.AddOpenTelemetry().UseOtlpExporter();
81+
6582
if (useAzureMonitor)
66-
builder.Services.AddOpenTelemetry().UseAzureMonitor();
83+
otel.UseAzureMonitor();
84+
else if (useOtlp)
85+
otel.UseOtlpExporter();
6786

6887
// HttpClient defaults — standard retry/circuit breaker for all named clients.
6988
builder.Services.ConfigureHttpClientDefaults(http => http.AddStandardResilienceHandler());

EssentialCSharp.Web/Services/ListingSourceCodeService.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,32 @@ namespace EssentialCSharp.Web.Services;
77

88
public partial class ListingSourceCodeService : IListingSourceCodeService
99
{
10-
private readonly IWebHostEnvironment _WebHostEnvironment;
10+
private readonly IFileProvider _FileProvider;
11+
private readonly string _ChapterDirectoryPrefix;
1112
private readonly ILogger<ListingSourceCodeService> _Logger;
1213

13-
public ListingSourceCodeService(IWebHostEnvironment webHostEnvironment, ILogger<ListingSourceCodeService> logger)
14+
public ListingSourceCodeService(IWebHostEnvironment webHostEnvironment, ILogger<ListingSourceCodeService> logger, IConfiguration configuration)
1415
{
15-
_WebHostEnvironment = webHostEnvironment;
1616
_Logger = logger;
17+
18+
string? listingSourceCodePath = configuration["LISTING_SOURCE_CODE_PATH"];
19+
if (!string.IsNullOrEmpty(listingSourceCodePath) && Directory.Exists(listingSourceCodePath))
20+
{
21+
_FileProvider = new PhysicalFileProvider(listingSourceCodePath);
22+
_ChapterDirectoryPrefix = "src";
23+
_Logger.LogInformation("Using listing source code from: {Path}", listingSourceCodePath);
24+
}
25+
else
26+
{
27+
_FileProvider = webHostEnvironment.ContentRootFileProvider;
28+
_ChapterDirectoryPrefix = "ListingSourceCode/src";
29+
}
1730
}
1831

1932
public async Task<ListingSourceCodeResponse?> GetListingAsync(int chapterNumber, int listingNumber)
2033
{
21-
string chapterDirectory = $"ListingSourceCode/src/Chapter{chapterNumber:D2}";
22-
IFileProvider fileProvider = _WebHostEnvironment.ContentRootFileProvider;
23-
IDirectoryContents directoryContents = fileProvider.GetDirectoryContents(chapterDirectory);
34+
string chapterDirectory = $"{_ChapterDirectoryPrefix}/Chapter{chapterNumber:D2}";
35+
IDirectoryContents directoryContents = _FileProvider.GetDirectoryContents(chapterDirectory);
2436

2537
if (!directoryContents.Exists)
2638
{
@@ -52,9 +64,8 @@ public ListingSourceCodeService(IWebHostEnvironment webHostEnvironment, ILogger<
5264

5365
public async Task<IReadOnlyList<ListingSourceCodeResponse>> GetListingsByChapterAsync(int chapterNumber)
5466
{
55-
string chapterDirectory = $"ListingSourceCode/src/Chapter{chapterNumber:D2}";
56-
IFileProvider fileProvider = _WebHostEnvironment.ContentRootFileProvider;
57-
IDirectoryContents directoryContents = fileProvider.GetDirectoryContents(chapterDirectory);
67+
string chapterDirectory = $"{_ChapterDirectoryPrefix}/Chapter{chapterNumber:D2}";
68+
IDirectoryContents directoryContents = _FileProvider.GetDirectoryContents(chapterDirectory);
5869

5970
if (!directoryContents.Exists)
6071
{

0 commit comments

Comments
 (0)