| name | dotnet-production-readiness |
| description | Checklist de prontidao para producao .NET C# / ASP.NET Core: configuracao de logging/tracing com OpenTelemetry (OTLP padrao oficial), formato de logs estruturados JSON, sanitizacao de dados sensiveis (CPF, email, telefone), niveis de log por ambiente, correlacao via TraceId, exportacao OTLP, checklist consolidado de deploy. Usar quando: preparar servico para producao; configurar OpenTelemetry; revisar logs; sanitizar dados; validar deploy; garantir observabilidade completa. |
Prontidao para Producao โ .NET C# / ASP.NET Core
Documento normativo e checklist consolidado.
Bloqueia deploy que nao atenda aos requisitos minimos.
Indice
- Logging e Tracing com OpenTelemetry
- Formato de Logs
- Boas Praticas de Logging
- Sanitizacao de Dados Sensiveis
- Niveis de Log por Ambiente
- Checklist de Producao
Logging e Tracing com OpenTelemetry
OpenTelemetry (OTLP) e o padrao oficial.
Nao usar Serilog + ECS em novos servicos.
Pacotes Necessarios
<PackageReference Include="OpenTelemetry" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Api" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.7.0" />
Configuracao em Program.cs
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
var serviceName = builder.Configuration["ServiceName"] ?? "meu-servico";
var serviceVersion = typeof(Program).Assembly.GetName().Version?.ToString() ?? "1.0.0";
var resourceBuilder = ResourceBuilder.CreateDefault()
.AddService(serviceName, serviceVersion: serviceVersion)
.AddAttributes(new Dictionary<string, object>
{
["deployment.environment"] = builder.Environment.EnvironmentName,
["host.name"] = Environment.MachineName
});
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.SetResourceBuilder(resourceBuilder)
.AddAspNetCoreInstrumentation(opts =>
{
opts.RecordException = true;
opts.Filter = ctx => !ctx.Request.Path.StartsWithSegments("/health");
})
.AddHttpClientInstrumentation(opts =>
{
opts.RecordException = true;
})
.AddSource(serviceName)
.AddOtlpExporter(opts =>
{
opts.Endpoint = new Uri(
builder.Configuration["OpenTelemetry:OtlpEndpoint"] ?? "http://localhost:4317");
}));
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.SetResourceBuilder(resourceBuilder)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter());
builder.Logging.ClearProviders();
builder.Logging.AddOpenTelemetry(logging =>
{
logging.SetResourceBuilder(resourceBuilder);
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
logging.AddOtlpExporter(opts =>
{
opts.Endpoint = new Uri(
builder.Configuration["OpenTelemetry:OtlpEndpoint"] ?? "http://localhost:4317");
});
});
Configuracao em appsettings.json
{
"ServiceName": "meu-servico-api",
"OpenTelemetry": {
"OtlpEndpoint": "http://otel-collector:4317"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning",
"System.Net.Http.HttpClient": "Warning"
}
}
}
ActivitySource para Tracing Manual
using System.Diagnostics;
public class ServicoPedido
{
private static readonly ActivitySource ActivitySource = new("meu-servico");
private readonly ILogger<ServicoPedido> _logger;
public ServicoPedido(ILogger<ServicoPedido> logger)
{
_logger = logger;
}
public async Task<Pedido> CriarPedidoAsync(SolicitacaoCriarPedido solicitacao, CancellationToken cancellationToken)
{
using var activity = ActivitySource.StartActivity("CriarPedido");
activity?.SetTag("pedido.cliente_id", solicitacao.IdCliente);
activity?.SetTag("pedido.total_itens", solicitacao.Itens.Count);
_logger.LogInformation(
"Criando pedido para cliente {ClienteId} com {TotalItens} itens",
solicitacao.IdCliente,
solicitacao.Itens.Count);
try
{
var pedido = await ProcessarPedidoAsync(solicitacao, cancellationToken);
activity?.SetTag("pedido.id", pedido.Id);
activity?.SetStatus(ActivityStatusCode.Ok);
_logger.LogInformation(
"Pedido {PedidoId} criado com sucesso para cliente {ClienteId}",
pedido.Id,
solicitacao.IdCliente);
return pedido;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
_logger.LogError(ex,
"Erro ao criar pedido para cliente {ClienteId}",
solicitacao.IdCliente);
throw;
}
}
}
Formato de Logs
Estrutura JSON Padrao
{
"timestamp": "2024-01-15T10:30:00.000Z",
"level": "Information",
"message": "Pedido criado com sucesso",
"service": "pedidos-api",
"traceId": "abc123def456",
"spanId": "789ghi012",
"context": {
"pedidoId": 12345,
"clienteId": 67890,
"totalItens": 3
},
"error": null
}
Templates Estruturados (OBRIGATORIO)
_logger.LogInformation(
"Pedido {PedidoId} criado para cliente {ClienteId} com valor {Valor:C}",
pedido.Id, pedido.ClienteId, pedido.Valor);
_logger.LogInformation($"Pedido {pedido.Id} criado para cliente {pedido.ClienteId}");
_logger.LogInformation("Pedido " + pedido.Id + " criado");
Log Scopes para Correlacao
public async Task ProcessarPedidoAsync(int pedidoId, CancellationToken cancellationToken)
{
using (_logger.BeginScope(new Dictionary<string, object>
{
["PedidoId"] = pedidoId,
["Operacao"] = "ProcessamentoPedido",
["CorrelationId"] = Activity.Current?.TraceId.ToString() ?? Guid.NewGuid().ToString()
}))
{
_logger.LogInformation("Inicio do processamento");
await ValidarEstoqueAsync(pedidoId, cancellationToken);
await ProcessarPagamentoAsync(pedidoId, cancellationToken);
await EnviarConfirmacaoAsync(pedidoId, cancellationToken);
_logger.LogInformation("Processamento concluido");
}
}
Boas Praticas de Logging
Niveis de Log โ Quando Usar
| Nivel | Quando Usar | Exemplo |
|---|
Trace | Detalhes internos (debug profundo) | Valores de variaveis internas |
Debug | Fluxo de desenvolvimento | Entrada/saida de metodos |
Information | Eventos de negocio relevantes | Pedido criado, usuario logou |
Warning | Situacao inesperada nao-fatal | Retry acionado, cache miss |
Error | Erro tratavel | Falha de validacao, timeout de API |
Critical | Falha irrecuperavel | Banco indisponivel, corrupcao de dados |
Regras de Ouro
_logger.LogInformation("Processado {Quantidade} itens em {Duracao}ms", qtd, ms);
_logger.LogError(ex, "Falha ao processar pedido {PedidoId} do cliente {ClienteId}", pedidoId, clienteId);
public async Task ProcessarAsync(int id, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
}
_logger.LogInformation("Processados {Total} registros com {Erros} erros", total, erros);
Sanitizacao de Dados Sensiveis
Dados Proibidos em Logs
| Dado | Tratamento | Exemplo |
|---|
| CPF | Mascarar | ***.***.***-34 |
| CNPJ | Mascarar | **.***.***/**34-** |
| Email | Mascarar | t***@e***.com |
| Telefone | Mascarar | (**) ****-5678 |
| Senha | NUNCA logar | โ |
| Token/API Key | NUNCA logar | โ |
| Numero cartao | NUNCA logar | โ |
| Dados medicos | NUNCA logar | โ |
Implementacao de Sanitizador
public static class LogSanitizer
{
public static string MaskCpf(string cpf)
{
if (string.IsNullOrEmpty(cpf) || cpf.Length < 11)
return "***";
return $"***.***.***-{cpf[^2..]}";
}
public static string MaskEmail(string email)
{
if (string.IsNullOrEmpty(email))
return "***";
var parts = email.Split('@');
if (parts.Length != 2) return "***";
return $"{parts[0][0]}***@{parts[1][0]}***.{parts[1].Split('.').Last()}";
}
public static string MaskPhone(string phone)
{
if (string.IsNullOrEmpty(phone) || phone.Length < 8)
return "***";
return $"(***) ****-{phone[^4..]}";
}
}
_logger.LogInformation(
"Cadastro do cliente CPF {Cpf} email {Email}",
LogSanitizer.MaskCpf(cliente.Cpf),
LogSanitizer.MaskEmail(cliente.Email));
Niveis de Log por Ambiente
Configuracao Recomendada
| Namespace | Development | Staging | Production |
|---|
| Default | Debug | Information | Information |
| Microsoft.AspNetCore | Information | Warning | Warning |
| Microsoft.EFCore | Information | Warning | Warning |
| System.Net.Http | Information | Warning | Error |
| HealthChecks | Debug | Information | Warning |
appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning",
"System.Net.Http.HttpClient": "Error",
"Microsoft.Extensions.Diagnostics.HealthChecks": "Warning"
}
}
}
Checklist de Producao
Logging e Tracing
Observabilidade
Resiliencia
Performance
Seguranca
Deploy