Ana içeriğe geç

Request Sampling ve Log Filtreleme Teknikleri

Request sampling ve log filtreleme, telemetri maliyetini kontrol altında tutarken önemli verilerin kaybolmasını önler; yanlış yapılandırma ya aşırı veri ya da eksik izleme sorununa yol açar.


1. Tüm İstekleri Trace Etmek

Yanlış Kullanım: Production’da %100 sampling ile çalışmak.

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .SetSampler(new AlwaysOnSampler()));
// Yüksek trafik: günde milyonlarca trace, backend maliyeti çok yüksek

İdeal Kullanım: Akıllı sampling stratejisi uygulayın.

public class SmartSampler : Sampler
{
    private readonly TraceIdRatioBasedSampler _defaultSampler = new(0.01); // %1

    public override SamplingResult ShouldSample(in SamplingParameters parameters)
    {
        // Hatalı istekler her zaman sample'la
        if (parameters.Tags?.Any(t => t.Key == "error" && (bool)t.Value!) == true)
            return new SamplingResult(SamplingDecision.RecordAndSample);

        // Yavaş istekler her zaman sample'la (parent span'dan bilgi)
        if (parameters.Tags?.Any(t => t.Key == "slow_request") == true)
            return new SamplingResult(SamplingDecision.RecordAndSample);

        // Diğerleri %1 oranında
        return _defaultSampler.ShouldSample(parameters);
    }
}

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .SetSampler(new ParentBasedSampler(new SmartSampler())));

2. Health Check ve Metrics Endpoint’lerini Loglamak

Yanlış Kullanım: Altyapı endpoint’lerini loglara dahil etmek.

app.UseSerilogRequestLogging();
// /health, /metrics, /favicon.ico gibi istekler de loglanır
// Günde on binlerce gereksiz log satırı

İdeal Kullanım: Altyapı endpoint’lerini filtreleyin.

app.UseSerilogRequestLogging(options =>
{
    options.MessageTemplate = "{RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0}ms";

    options.GetLevel = (context, elapsed, ex) =>
    {
        if (ex != null) return LogEventLevel.Error;
        if (elapsed > 1000) return LogEventLevel.Warning;
        if (context.Response.StatusCode >= 500) return LogEventLevel.Error;
        return LogEventLevel.Information;
    };

    options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
    {
        diagnosticContext.Set("UserId", httpContext.User.FindFirst("sub")?.Value);
        diagnosticContext.Set("ClientIp", httpContext.Connection.RemoteIpAddress);
    };

    // Altyapı endpoint'lerini hariç tut
    options.ShouldLog = (context) =>
    {
        var path = context.Request.Path.Value;
        return !path!.StartsWith("/health") &&
               !path.StartsWith("/metrics") &&
               !path.StartsWith("/favicon");
    };
});

3. Log Enrichment Yapmamak

Yanlış Kullanım: Bağlamsız loglar yazmak.

_logger.LogError("Sipariş oluşturulamadı");
// Hangi kullanıcı? Hangi sipariş? Hangi ortam? Bilinmiyor

İdeal Kullanım: Her log satırına bağlam ekleyin.

builder.Host.UseSerilog((context, config) => config
    .Enrich.FromLogContext()
    .Enrich.WithMachineName()
    .Enrich.WithEnvironmentName()
    .Enrich.WithProperty("Application", "OrderService")
    .Enrich.WithProperty("Version", typeof(Program).Assembly.GetName().Version?.ToString()));

// Request bazlı enrichment
public class RequestContextMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var userId = context.User.FindFirst("sub")?.Value ?? "anonymous";
        var correlationId = context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
            ?? Activity.Current?.Id ?? Guid.NewGuid().ToString();

        using (LogContext.PushProperty("UserId", userId))
        using (LogContext.PushProperty("CorrelationId", correlationId))
        {
            context.Response.Headers["X-Correlation-Id"] = correlationId;
            await next(context);
        }
    }
}

4. Dynamic Log Level Kullanmamak

Yanlış Kullanım: Log seviyesini değiştirmek için yeniden deploy gerekmesi.

{
  "Logging": {
    "LogLevel": { "Default": "Warning" }
  }
}
// Debug bilgisi lazım → deploy gerekir → sorun geçer

İdeal Kullanım: Runtime’da log seviyesini değiştirin.

builder.Host.UseSerilog((context, config) => config
    .ReadFrom.Configuration(context.Configuration)
    .MinimumLevel.ControlledBy(new LoggingLevelSwitch(LogEventLevel.Information)));

// API ile log seviyesi değiştirme
app.MapPost("/admin/log-level", (string level, LoggingLevelSwitch levelSwitch) =>
{
    if (Enum.TryParse<LogEventLevel>(level, true, out var newLevel))
    {
        levelSwitch.MinimumLevel = newLevel;
        return TypedResults.Ok($"Log seviyesi değiştirildi: {newLevel}");
    }
    return TypedResults.BadRequest("Geçersiz seviye");
}).RequireAuthorization("Admin");

// Veya Serilog.Settings.Configuration ile hot-reload
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

5. Sensitive Data Filtreleme Yapmamak

Yanlış Kullanım: Request/response body’lerini olduğu gibi loglamak.

_logger.LogInformation("Request: {@Body}", requestBody);
// Şifre, kredi kartı, kişisel veri loglara yazılır

İdeal Kullanım: Hassas verileri otomatik maskeleyin.

public class SensitiveDataFilter : IDestructuringPolicy
{
    private static readonly HashSet<string> SensitiveKeys = new(StringComparer.OrdinalIgnoreCase)
    {
        "password", "token", "secret", "authorization",
        "cardNumber", "cvv", "ssn", "creditCard"
    };

    public bool TryDestructure(object value, ILogEventPropertyValueFactory factory,
        out LogEventPropertyValue? result)
    {
        if (value is IDictionary<string, object> dict)
        {
            var sanitized = dict.ToDictionary(
                kvp => kvp.Key,
                kvp => SensitiveKeys.Contains(kvp.Key)
                    ? (object)"***REDACTED***"
                    : kvp.Value);

            result = factory.CreatePropertyValue(sanitized, destructureObjects: true);
            return true;
        }

        result = null;
        return false;
    }
}

builder.Host.UseSerilog((context, config) => config
    .Destructure.With<SensitiveDataFilter>());