-
Notifications
You must be signed in to change notification settings - Fork 0
feat(observability): expandir AdicionarObservabilidade + OTLP sink Serilog + correlation_id span tag #367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat(observability): expandir AdicionarObservabilidade + OTLP sink Serilog + correlation_id span tag #367
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
82 changes: 80 additions & 2 deletions
82
src/shared/Unifesspa.UniPlus.Infrastructure.Core/Logging/SerilogConfiguration.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,102 @@ | ||
| namespace Unifesspa.UniPlus.Infrastructure.Core.Logging; | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Globalization; | ||
|
|
||
| using Microsoft.Extensions.Configuration; | ||
|
|
||
| using Serilog; | ||
| using Serilog.Events; | ||
| using Serilog.Sinks.OpenTelemetry; | ||
|
|
||
| using Unifesspa.UniPlus.Infrastructure.Core.Observability; | ||
|
|
||
| /// <summary> | ||
| /// Configuração canônica do pipeline Serilog Uni+: lê <c>appsettings</c>, override | ||
| /// de Microsoft/EF para Warning, <c>FromLogContext</c>, <see cref="PiiMaskingEnricher"/> | ||
| /// (ADR-0011), Console sink. Quando <c>Observability:Enabled</c> não está desligado, | ||
| /// adiciona o sink OTLP gRPC (ADR-0018) — logs fluem ao Collector → Loki com correlação | ||
| /// <c>traceId</c>/<c>spanId</c> automática por evento. | ||
| /// </summary> | ||
| public static class SerilogConfiguration | ||
| { | ||
| public static LoggerConfiguration ConfigurarSerilog(this LoggerConfiguration loggerConfiguration, IConfiguration configuration) | ||
| /// <summary> | ||
| /// Sobrecarga sem nome de serviço — mantida para callers que ainda não precisam | ||
| /// rotular logs com <c>service.name</c>/<c>service.namespace</c> no Loki. | ||
| /// O sink OTLP é registrado mesmo assim (quando o toggle está ativo); apenas | ||
| /// não popula <c>ResourceAttributes</c>, deixando o sink usar suas próprias | ||
| /// inferências (env vars padrão OTel, se presentes). | ||
| /// </summary> | ||
| public static LoggerConfiguration ConfigurarSerilog( | ||
| this LoggerConfiguration loggerConfiguration, | ||
| IConfiguration configuration) | ||
| => loggerConfiguration.ConfigurarSerilog(configuration, nomeServico: null); | ||
|
|
||
| /// <summary> | ||
| /// Configura o pipeline com <c>service.name</c>/<c>service.namespace</c> aplicados | ||
| /// ao Resource do sink OTLP — habilita queries LogQL como | ||
| /// <c>{service_name="uniplus-selecao"}</c>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para>O Console sink é sempre preservado para que <c>docker logs</c> continue | ||
| /// útil em bootstrap debugging.</para> | ||
| /// <para><see cref="PiiMaskingEnricher"/> fica antes dos sinks por construção: | ||
| /// enrichers executam antes dos sinks no pipeline Serilog. CPF é mascarado | ||
| /// antes de qualquer egress (Console ou OTLP) — ADR-0011.</para> | ||
| /// <para>Endpoint OTLP é lido pela env var <c>OTEL_EXPORTER_OTLP_ENDPOINT</c> | ||
| /// (default <c>http://localhost:4317</c>). Os atributos <c>service.*</c> são | ||
| /// injetados aqui porque o pipeline Serilog→OTLP é independente do pipeline | ||
| /// OTel SDK (<see cref="OpenTelemetryConfiguration.AdicionarObservabilidade"/>) — | ||
| /// duplicação intencional para evitar drift entre logs e traces no Loki/Tempo.</para> | ||
| /// </remarks> | ||
| /// <param name="loggerConfiguration">Configuração base do Serilog.</param> | ||
| /// <param name="configuration">Configuração da aplicação — fornece toggle | ||
| /// <see cref="OpenTelemetryConfiguration.EnabledConfigurationKey"/> e overrides | ||
| /// de level via <c>Serilog</c> section.</param> | ||
| /// <param name="nomeServico">Nome canônico do serviço para rotular o sink OTLP | ||
| /// (<c>service.name</c> + <c>service.namespace</c>). Quando <c>null</c>, sink | ||
| /// é registrado sem ResourceAttributes.</param> | ||
| public static LoggerConfiguration ConfigurarSerilog( | ||
| this LoggerConfiguration loggerConfiguration, | ||
| IConfiguration configuration, | ||
| string? nomeServico) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(loggerConfiguration); | ||
| ArgumentNullException.ThrowIfNull(configuration); | ||
|
|
||
| return loggerConfiguration | ||
| loggerConfiguration | ||
| .ReadFrom.Configuration(configuration) | ||
| .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) | ||
| .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) | ||
| .Enrich.FromLogContext() | ||
| .Enrich.With<PiiMaskingEnricher>() | ||
| .WriteTo.Console(formatProvider: CultureInfo.InvariantCulture); | ||
|
|
||
| bool observabilidadeAtivada = configuration.GetValue( | ||
| OpenTelemetryConfiguration.EnabledConfigurationKey, | ||
| defaultValue: true); | ||
|
|
||
| if (observabilidadeAtivada) | ||
| { | ||
| loggerConfiguration.WriteTo.OpenTelemetry(options => | ||
| { | ||
| options.Protocol = OtlpProtocol.Grpc; | ||
| options.IncludedData = | ||
| IncludedData.TraceIdField | ||
| | IncludedData.SpanIdField | ||
| | IncludedData.MessageTemplateTextAttribute; | ||
|
|
||
| if (!string.IsNullOrWhiteSpace(nomeServico)) | ||
| { | ||
| options.ResourceAttributes = new Dictionary<string, object> | ||
| { | ||
| ["service.name"] = nomeServico, | ||
| ["service.namespace"] = OpenTelemetryConfiguration.ServiceNamespaceResourceValue, | ||
| }; | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| return loggerConfiguration; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 135 additions & 3 deletions
138
src/shared/Unifesspa.UniPlus.Infrastructure.Core/Observability/OpenTelemetryConfiguration.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,152 @@ | ||
| namespace Unifesspa.UniPlus.Infrastructure.Core.Observability; | ||
|
|
||
| using System.Collections.Generic; | ||
|
|
||
| using Microsoft.Extensions.Configuration; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Hosting; | ||
|
|
||
| using OpenTelemetry.Metrics; | ||
| using OpenTelemetry.Resources; | ||
| using OpenTelemetry.Trace; | ||
|
|
||
| /// <summary> | ||
| /// Registra OpenTelemetry com instrumentações canônicas Uni+ (tracing + metrics) | ||
| /// e exporter OTLP para a stack LGTM institucional. ADR-0018. | ||
| /// </summary> | ||
| public static class OpenTelemetryConfiguration | ||
| { | ||
| public static IServiceCollection AdicionarObservabilidade(this IServiceCollection services, string nomeServico) | ||
| /// <summary> | ||
| /// Nome canônico do <see cref="System.Diagnostics.ActivitySource"/> emitido | ||
| /// pelo Wolverine. Registrado tanto em <c>WithTracing.AddSource</c> quanto em | ||
| /// <c>WithMetrics.AddMeter</c> para que command/handler/outbox executions | ||
| /// apareçam em Tempo (traces) e Prometheus (métricas) — ADR-0018. | ||
| /// </summary> | ||
| public const string WolverineActivityAndMeterName = "Wolverine"; | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// Toggle de configuração para observabilidade. Default <c>true</c>; quando | ||
| /// <c>false</c>, nenhum <see cref="TracerProvider"/> ou <see cref="MeterProvider"/> | ||
| /// é registrado. Cenário de uso: suites de teste HTTP-only sem Collector | ||
| /// provisionado, troubleshooting onde a stack LGTM está fora do ar | ||
| /// (degradação controlada). Mesma chave usada por <see cref="Logging.SerilogConfiguration"/> | ||
| /// para condicionar o sink OTLP de logs. | ||
| /// </summary> | ||
| public const string EnabledConfigurationKey = "Observability:Enabled"; | ||
|
|
||
| /// <summary> | ||
| /// Atributo Resource canônico do OTel para particionar telemetria entre | ||
| /// Development/Staging/Production nos dashboards Grafana. | ||
| /// </summary> | ||
| public const string DeploymentEnvironmentResourceAttribute = "deployment.environment"; | ||
|
|
||
| /// <summary> | ||
| /// Namespace canônico Uni+ — todas as APIs do projeto compartilham. Permite | ||
| /// queries cross-service em PromQL/LogQL/TraceQL via | ||
| /// <c>service_namespace="uniplus"</c>. | ||
| /// </summary> | ||
| public const string ServiceNamespaceResourceValue = "uniplus"; | ||
|
|
||
| /// <summary> | ||
| /// Sampling ratio head-based para ambientes não-Development. 10% conforme | ||
| /// ADR-0018; tail-based 100% para erros e latência alta é responsabilidade | ||
| /// do <c>tail_sampling_processor</c> no Collector, não da API. | ||
| /// </summary> | ||
| public const double ProductionSamplingRatio = 0.1; | ||
|
|
||
| /// <summary> | ||
| /// Registra OpenTelemetry com instrumentações canônicas Uni+ e exporter OTLP. | ||
| /// Endpoint OTLP é lido automaticamente pela env var | ||
| /// <c>OTEL_EXPORTER_OTLP_ENDPOINT</c> (default <c>http://localhost:4317</c>, gRPC). | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para><b>Tracing:</b> AspNetCore + EF Core + HttpClient + ActivitySource nominal | ||
| /// (<paramref name="nomeServico"/>).</para> | ||
| /// <para><b>Metrics:</b> AspNetCore + Runtime + HttpClient + Meter nominal + | ||
| /// Wolverine (instrumentação built-in via <c>System.Diagnostics.Metrics.Meter</c> | ||
| /// nativo do framework — ADR-0018).</para> | ||
| /// <para><b>Sampler:</b> <see cref="AlwaysOnSampler"/> em Development, | ||
| /// <see cref="ParentBasedSampler"/> com <see cref="TraceIdRatioBasedSampler"/> | ||
| /// (10%) nos demais.</para> | ||
| /// </remarks> | ||
| /// <param name="services">A coleção de serviços.</param> | ||
| /// <param name="nomeServico">Nome canônico do serviço (ex.: <c>uniplus-selecao</c>), | ||
| /// usado como <c>service.name</c> no Resource e como nome da | ||
| /// <c>ActivitySource</c>/<c>Meter</c> nominal.</param> | ||
| /// <param name="configuration">Configuração da aplicação.</param> | ||
| /// <param name="environment">Ambiente de hosting — define o sampler e popula | ||
| /// <c>deployment.environment</c> no Resource.</param> | ||
| /// <returns>A própria <paramref name="services"/> para encadeamento fluente.</returns> | ||
| public static IServiceCollection AdicionarObservabilidade( | ||
| this IServiceCollection services, | ||
| string nomeServico, | ||
| IConfiguration configuration, | ||
| IHostEnvironment environment) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(services); | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(nomeServico); | ||
| ArgumentNullException.ThrowIfNull(configuration); | ||
| ArgumentNullException.ThrowIfNull(environment); | ||
|
|
||
| bool enabled = configuration.GetValue(EnabledConfigurationKey, defaultValue: true); | ||
| if (!enabled) | ||
| { | ||
| return services; | ||
| } | ||
|
|
||
| Sampler sampler = SelecionarSampler(environment); | ||
|
|
||
| IEnumerable<KeyValuePair<string, object>> resourceAttributes = new[] | ||
| { | ||
| new KeyValuePair<string, object>( | ||
| DeploymentEnvironmentResourceAttribute, | ||
| environment.EnvironmentName), | ||
| }; | ||
|
|
||
| services.AddOpenTelemetry() | ||
| .ConfigureResource(resource => resource.AddService(nomeServico)) | ||
| .ConfigureResource(resource => resource | ||
| .AddService(serviceName: nomeServico, serviceNamespace: ServiceNamespaceResourceValue) | ||
| .AddAttributes(resourceAttributes)) | ||
| .WithTracing(tracing => tracing | ||
| .SetSampler(sampler) | ||
| .AddSource(nomeServico) | ||
| .AddSource(WolverineActivityAndMeterName) | ||
| .AddAspNetCoreInstrumentation() | ||
| .AddHttpClientInstrumentation() | ||
| .AddEntityFrameworkCoreInstrumentation() | ||
| .AddOtlpExporter()) | ||
| .WithMetrics(metrics => metrics | ||
| .AddMeter(nomeServico) | ||
| .AddMeter(WolverineActivityAndMeterName) | ||
| .AddAspNetCoreInstrumentation() | ||
| .AddSource(nomeServico)); | ||
| .AddHttpClientInstrumentation() | ||
| .AddRuntimeInstrumentation() | ||
| .AddOtlpExporter()); | ||
|
|
||
| return services; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Seleciona o sampler conforme ambiente: <see cref="AlwaysOnSampler"/> | ||
| /// em <c>Development</c> (debugging local — todos os spans), e | ||
| /// <see cref="ParentBasedSampler"/> com <see cref="TraceIdRatioBasedSampler"/> | ||
| /// em <see cref="ProductionSamplingRatio"/> (10%) nos demais ambientes — | ||
| /// ADR-0018 head-based sampling. Tail-based 100% para erro/latência alta | ||
| /// é responsabilidade do <c>tail_sampling_processor</c> no Collector. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Extraído como <c>internal static</c> exatamente para tornar a regra de | ||
| /// seleção testável sem precisar inspecionar o <c>TracerProvider</c> via | ||
| /// reflection — <c>InternalsVisibleTo</c> em <c>Infrastructure.Core.csproj</c> | ||
| /// expõe esta API para <c>Unifesspa.UniPlus.Infrastructure.Core.UnitTests</c>. | ||
| /// </remarks> | ||
| internal static Sampler SelecionarSampler(IHostEnvironment environment) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(environment); | ||
|
|
||
| return environment.IsDevelopment() | ||
| ? new AlwaysOnSampler() | ||
| : new ParentBasedSampler(new TraceIdRatioBasedSampler(ProductionSamplingRatio)); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.