feat: 集成OpenTelemetry埋点与日志输出

This commit is contained in:
2025-12-02 13:27:43 +08:00
parent fcce44b767
commit 753c10c492
12 changed files with 343 additions and 27 deletions

View File

@@ -5,6 +5,9 @@ using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Serilog; using Serilog;
using TakeoutSaaS.Application.App.Extensions; using TakeoutSaaS.Application.App.Extensions;
using TakeoutSaaS.Application.Identity.Extensions; using TakeoutSaaS.Application.Identity.Extensions;
@@ -34,7 +37,12 @@ builder.Host.UseSerilog((context, _, configuration) =>
configuration configuration
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.WithProperty("Service", "AdminApi") .Enrich.WithProperty("Service", "AdminApi")
.WriteTo.Console(); .WriteTo.Console()
.WriteTo.File(
"logs/admin-api-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 7,
shared: true);
}); });
builder.Services.AddSharedWebCore(); builder.Services.AddSharedWebCore();
@@ -60,6 +68,59 @@ builder.Services.AddSmsApplication(builder.Configuration);
builder.Services.AddMessagingModule(builder.Configuration); builder.Services.AddMessagingModule(builder.Configuration);
builder.Services.AddMessagingApplication(); builder.Services.AddMessagingApplication();
builder.Services.AddSchedulerModule(builder.Configuration); builder.Services.AddSchedulerModule(builder.Configuration);
var otelSection = builder.Configuration.GetSection("Otel");
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(
serviceName: "TakeoutSaaS.AdminApi",
serviceVersion: "1.0.0",
serviceInstanceId: Environment.MachineName))
.WithTracing(tracing =>
{
tracing
.SetSampler(new ParentBasedSampler(new AlwaysOnSampler()))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation(options =>
{
options.SetDbStatementForText = false;
options.SetDbStatementForStoredProcedure = false;
});
if (!string.IsNullOrWhiteSpace(otelEndpoint))
{
tracing.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri(otelEndpoint);
});
}
if (useConsoleExporter)
{
tracing.AddConsoleExporter();
}
})
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
if (!string.IsNullOrWhiteSpace(otelEndpoint))
{
metrics.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri(otelEndpoint);
});
}
if (useConsoleExporter)
{
metrics.AddConsoleExporter();
}
});
var adminOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:Admin"); var adminOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:Admin");
builder.Services.AddCors(options => builder.Services.AddCors(options =>

View File

@@ -8,6 +8,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.0.0-rc9.10" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -60,7 +60,9 @@
"Tenancy": { "Tenancy": {
"TenantIdHeaderName": "X-Tenant-Id", "TenantIdHeaderName": "X-Tenant-Id",
"TenantCodeHeaderName": "X-Tenant-Code", "TenantCodeHeaderName": "X-Tenant-Code",
"IgnoredPaths": [ "/health" ], "IgnoredPaths": [
"/health"
],
"RootDomain": "" "RootDomain": ""
}, },
"Storage": { "Storage": {
@@ -95,11 +97,27 @@
}, },
"Security": { "Security": {
"MaxFileSizeBytes": 10485760, "MaxFileSizeBytes": 10485760,
"AllowedImageExtensions": [ ".jpg", ".jpeg", ".png", ".webp", ".gif" ], "AllowedImageExtensions": [
"AllowedFileExtensions": [ ".jpg", ".jpeg", ".png", ".webp", ".gif", ".pdf" ], ".jpg",
".jpeg",
".png",
".webp",
".gif"
],
"AllowedFileExtensions": [
".jpg",
".jpeg",
".png",
".webp",
".gif",
".pdf"
],
"DefaultUrlExpirationMinutes": 30, "DefaultUrlExpirationMinutes": 30,
"EnableRefererValidation": true, "EnableRefererValidation": true,
"AllowedReferers": [ "https://admin.example.com", "https://miniapp.example.com" ], "AllowedReferers": [
"https://admin.example.com",
"https://miniapp.example.com"
],
"AntiLeechTokenSecret": "ReplaceWithARandomToken" "AntiLeechTokenSecret": "ReplaceWithARandomToken"
} }
}, },
@@ -149,5 +167,10 @@
"WorkerCount": 5, "WorkerCount": 5,
"DashboardEnabled": false, "DashboardEnabled": false,
"DashboardPath": "/hangfire" "DashboardPath": "/hangfire"
},
"Otel": {
"Endpoint": "",
"Sampling": "ParentBasedAlwaysOn",
"UseConsoleExporter": true
} }
} }

View File

@@ -60,7 +60,9 @@
"Tenancy": { "Tenancy": {
"TenantIdHeaderName": "X-Tenant-Id", "TenantIdHeaderName": "X-Tenant-Id",
"TenantCodeHeaderName": "X-Tenant-Code", "TenantCodeHeaderName": "X-Tenant-Code",
"IgnoredPaths": [ "/health" ], "IgnoredPaths": [
"/health"
],
"RootDomain": "" "RootDomain": ""
}, },
"Storage": { "Storage": {
@@ -95,11 +97,27 @@
}, },
"Security": { "Security": {
"MaxFileSizeBytes": 10485760, "MaxFileSizeBytes": 10485760,
"AllowedImageExtensions": [ ".jpg", ".jpeg", ".png", ".webp", ".gif" ], "AllowedImageExtensions": [
"AllowedFileExtensions": [ ".jpg", ".jpeg", ".png", ".webp", ".gif", ".pdf" ], ".jpg",
".jpeg",
".png",
".webp",
".gif"
],
"AllowedFileExtensions": [
".jpg",
".jpeg",
".png",
".webp",
".gif",
".pdf"
],
"DefaultUrlExpirationMinutes": 30, "DefaultUrlExpirationMinutes": 30,
"EnableRefererValidation": true, "EnableRefererValidation": true,
"AllowedReferers": [ "https://admin.example.com", "https://miniapp.example.com" ], "AllowedReferers": [
"https://admin.example.com",
"https://miniapp.example.com"
],
"AntiLeechTokenSecret": "ReplaceWithARandomToken" "AntiLeechTokenSecret": "ReplaceWithARandomToken"
} }
}, },
@@ -149,5 +167,10 @@
"WorkerCount": 5, "WorkerCount": 5,
"DashboardEnabled": false, "DashboardEnabled": false,
"DashboardPath": "/hangfire" "DashboardPath": "/hangfire"
},
"Otel": {
"Endpoint": "",
"Sampling": "ParentBasedAlwaysOn",
"UseConsoleExporter": true
} }
} }

View File

@@ -1,4 +1,10 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.Configuration;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Serilog; using Serilog;
using TakeoutSaaS.Application.Messaging.Extensions; using TakeoutSaaS.Application.Messaging.Extensions;
using TakeoutSaaS.Application.Sms.Extensions; using TakeoutSaaS.Application.Sms.Extensions;
@@ -17,7 +23,12 @@ builder.Host.UseSerilog((_, _, configuration) =>
configuration configuration
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.WithProperty("Service", "MiniApi") .Enrich.WithProperty("Service", "MiniApi")
.WriteTo.Console(); .WriteTo.Console()
.WriteTo.File(
"logs/mini-api-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 7,
shared: true);
}); });
builder.Services.AddSharedWebCore(); builder.Services.AddSharedWebCore();
@@ -34,6 +45,59 @@ builder.Services.AddSmsModule(builder.Configuration);
builder.Services.AddSmsApplication(builder.Configuration); builder.Services.AddSmsApplication(builder.Configuration);
builder.Services.AddMessagingModule(builder.Configuration); builder.Services.AddMessagingModule(builder.Configuration);
builder.Services.AddMessagingApplication(); builder.Services.AddMessagingApplication();
var otelSection = builder.Configuration.GetSection("Otel");
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(
serviceName: "TakeoutSaaS.MiniApi",
serviceVersion: "1.0.0",
serviceInstanceId: Environment.MachineName))
.WithTracing(tracing =>
{
tracing
.SetSampler(new ParentBasedSampler(new AlwaysOnSampler()))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation(options =>
{
options.SetDbStatementForText = false;
options.SetDbStatementForStoredProcedure = false;
});
if (!string.IsNullOrWhiteSpace(otelEndpoint))
{
tracing.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri(otelEndpoint);
});
}
if (useConsoleExporter)
{
tracing.AddConsoleExporter();
}
})
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
if (!string.IsNullOrWhiteSpace(otelEndpoint))
{
metrics.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri(otelEndpoint);
});
}
if (useConsoleExporter)
{
metrics.AddConsoleExporter();
}
});
var miniOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:Mini"); var miniOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:Mini");
builder.Services.AddCors(options => builder.Services.AddCors(options =>

View File

@@ -9,6 +9,13 @@
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" /> <PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.0.0-rc9.10" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Web\TakeoutSaaS.Shared.Web.csproj" /> <ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Web\TakeoutSaaS.Shared.Web.csproj" />

View File

@@ -55,7 +55,9 @@
"Tenancy": { "Tenancy": {
"TenantIdHeaderName": "X-Tenant-Id", "TenantIdHeaderName": "X-Tenant-Id",
"TenantCodeHeaderName": "X-Tenant-Code", "TenantCodeHeaderName": "X-Tenant-Code",
"IgnoredPaths": [ "/health" ], "IgnoredPaths": [
"/health"
],
"RootDomain": "" "RootDomain": ""
}, },
"Storage": { "Storage": {
@@ -90,11 +92,27 @@
}, },
"Security": { "Security": {
"MaxFileSizeBytes": 10485760, "MaxFileSizeBytes": 10485760,
"AllowedImageExtensions": [ ".jpg", ".jpeg", ".png", ".webp", ".gif" ], "AllowedImageExtensions": [
"AllowedFileExtensions": [ ".jpg", ".jpeg", ".png", ".webp", ".gif", ".pdf" ], ".jpg",
".jpeg",
".png",
".webp",
".gif"
],
"AllowedFileExtensions": [
".jpg",
".jpeg",
".png",
".webp",
".gif",
".pdf"
],
"DefaultUrlExpirationMinutes": 30, "DefaultUrlExpirationMinutes": 30,
"EnableRefererValidation": true, "EnableRefererValidation": true,
"AllowedReferers": [ "https://admin.example.com", "https://miniapp.example.com" ], "AllowedReferers": [
"https://admin.example.com",
"https://miniapp.example.com"
],
"AntiLeechTokenSecret": "ReplaceWithARandomToken" "AntiLeechTokenSecret": "ReplaceWithARandomToken"
} }
}, },
@@ -138,5 +156,10 @@
"Exchange": "takeout.events", "Exchange": "takeout.events",
"ExchangeType": "topic", "ExchangeType": "topic",
"PrefetchCount": 20 "PrefetchCount": 20
},
"Otel": {
"Endpoint": "",
"Sampling": "ParentBasedAlwaysOn",
"UseConsoleExporter": true
} }
} }

View File

@@ -55,7 +55,9 @@
"Tenancy": { "Tenancy": {
"TenantIdHeaderName": "X-Tenant-Id", "TenantIdHeaderName": "X-Tenant-Id",
"TenantCodeHeaderName": "X-Tenant-Code", "TenantCodeHeaderName": "X-Tenant-Code",
"IgnoredPaths": [ "/health" ], "IgnoredPaths": [
"/health"
],
"RootDomain": "" "RootDomain": ""
}, },
"Storage": { "Storage": {
@@ -90,11 +92,27 @@
}, },
"Security": { "Security": {
"MaxFileSizeBytes": 10485760, "MaxFileSizeBytes": 10485760,
"AllowedImageExtensions": [ ".jpg", ".jpeg", ".png", ".webp", ".gif" ], "AllowedImageExtensions": [
"AllowedFileExtensions": [ ".jpg", ".jpeg", ".png", ".webp", ".gif", ".pdf" ], ".jpg",
".jpeg",
".png",
".webp",
".gif"
],
"AllowedFileExtensions": [
".jpg",
".jpeg",
".png",
".webp",
".gif",
".pdf"
],
"DefaultUrlExpirationMinutes": 30, "DefaultUrlExpirationMinutes": 30,
"EnableRefererValidation": true, "EnableRefererValidation": true,
"AllowedReferers": [ "https://admin.example.com", "https://miniapp.example.com" ], "AllowedReferers": [
"https://admin.example.com",
"https://miniapp.example.com"
],
"AntiLeechTokenSecret": "ReplaceWithARandomToken" "AntiLeechTokenSecret": "ReplaceWithARandomToken"
} }
}, },
@@ -138,5 +156,10 @@
"Exchange": "takeout.events", "Exchange": "takeout.events",
"ExchangeType": "topic", "ExchangeType": "topic",
"PrefetchCount": 20 "PrefetchCount": 20
},
"Otel": {
"Endpoint": "",
"Sampling": "ParentBasedAlwaysOn",
"UseConsoleExporter": true
} }
} }

View File

@@ -1,4 +1,10 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.Configuration;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Serilog; using Serilog;
using TakeoutSaaS.Module.Tenancy.Extensions; using TakeoutSaaS.Module.Tenancy.Extensions;
using TakeoutSaaS.Shared.Web.Extensions; using TakeoutSaaS.Shared.Web.Extensions;
@@ -11,7 +17,12 @@ builder.Host.UseSerilog((_, _, configuration) =>
configuration configuration
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.WithProperty("Service", "UserApi") .Enrich.WithProperty("Service", "UserApi")
.WriteTo.Console(); .WriteTo.Console()
.WriteTo.File(
"logs/user-api-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 7,
shared: true);
}); });
builder.Services.AddSharedWebCore(); builder.Services.AddSharedWebCore();
@@ -22,6 +33,59 @@ builder.Services.AddSharedSwagger(options =>
options.EnableAuthorization = true; options.EnableAuthorization = true;
}); });
builder.Services.AddTenantResolution(builder.Configuration); builder.Services.AddTenantResolution(builder.Configuration);
var otelSection = builder.Configuration.GetSection("Otel");
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(
serviceName: "TakeoutSaaS.UserApi",
serviceVersion: "1.0.0",
serviceInstanceId: Environment.MachineName))
.WithTracing(tracing =>
{
tracing
.SetSampler(new ParentBasedSampler(new AlwaysOnSampler()))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation(options =>
{
options.SetDbStatementForText = false;
options.SetDbStatementForStoredProcedure = false;
});
if (!string.IsNullOrWhiteSpace(otelEndpoint))
{
tracing.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri(otelEndpoint);
});
}
if (useConsoleExporter)
{
tracing.AddConsoleExporter();
}
})
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
if (!string.IsNullOrWhiteSpace(otelEndpoint))
{
metrics.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri(otelEndpoint);
});
}
if (useConsoleExporter)
{
metrics.AddConsoleExporter();
}
});
var userOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:User"); var userOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:User");
builder.Services.AddCors(options => builder.Services.AddCors(options =>

View File

@@ -8,6 +8,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.0.0-rc9.10" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Web\TakeoutSaaS.Shared.Web.csproj" /> <ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Web\TakeoutSaaS.Shared.Web.csproj" />

View File

@@ -55,7 +55,14 @@
"Tenancy": { "Tenancy": {
"TenantIdHeaderName": "X-Tenant-Id", "TenantIdHeaderName": "X-Tenant-Id",
"TenantCodeHeaderName": "X-Tenant-Code", "TenantCodeHeaderName": "X-Tenant-Code",
"IgnoredPaths": [ "/health" ], "IgnoredPaths": [
"/health"
],
"RootDomain": "" "RootDomain": ""
},
"Otel": {
"Endpoint": "",
"Sampling": "ParentBasedAlwaysOn",
"UseConsoleExporter": true
} }
} }

View File

@@ -55,7 +55,14 @@
"Tenancy": { "Tenancy": {
"TenantIdHeaderName": "X-Tenant-Id", "TenantIdHeaderName": "X-Tenant-Id",
"TenantCodeHeaderName": "X-Tenant-Code", "TenantCodeHeaderName": "X-Tenant-Code",
"IgnoredPaths": [ "/health" ], "IgnoredPaths": [
"/health"
],
"RootDomain": "" "RootDomain": ""
},
"Otel": {
"Endpoint": "",
"Sampling": "ParentBasedAlwaysOn",
"UseConsoleExporter": true
} }
} }