Ana içeriğe geç

Event-Driven Logging ve Alerting Mekanizmaları

Event-driven logging, kritik olayları tespit ederek otomatik uyarı gönderir; yanlış yapılandırma önemli olayların kaçırılmasına veya alert yorgunluğuna yol açar.


1. Alert Olmadan Çalışmak

Yanlış Kullanım: Logları sadece kaydetmek, uyarı kurmamak.

_logger.LogError(ex, "Ödeme işlemi başarısız: {OrderId}", orderId);
// Log yazıldı ama kimse fark etmiyor, müşteri mağdur

İdeal Kullanım: Kritik olaylarda otomatik uyarı gönderin.

public class AlertingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IAlertService _alertService;
    private readonly ILogger<AlertingMiddleware> _logger;

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "İşlenmeyen hata: {Path}", context.Request.Path);

            if (IsCritical(ex))
                await _alertService.SendAsync(new Alert
                {
                    Severity = AlertSeverity.Critical,
                    Title = $"Kritik hata: {ex.GetType().Name}",
                    Message = ex.Message,
                    Source = context.Request.Path
                });

            throw;
        }
    }

    private static bool IsCritical(Exception ex) =>
        ex is PaymentException or DatabaseException or SecurityException;
}

2. Her Hatada Alert Göndermek

Yanlış Kullanım: Tüm hatalarda bildirim göndermek.

public class AlertService
{
    public async Task HandleErrorAsync(Exception ex)
    {
        await _slackClient.SendAsync($"HATA: {ex.Message}");
        // Günde yüzlerce mesaj, ekip uyarıları görmezden gelir
    }
}

İdeal Kullanım: Throttling ve deduplication ile akıllı alerting yapın.

public class SmartAlertService : IAlertService
{
    private readonly IDistributedCache _cache;
    private readonly ISlackClient _slack;

    public async Task SendAsync(Alert alert)
    {
        var deduplicationKey = $"alert:{alert.Title}:{alert.Source}";

        // Son 5 dakikada aynı alert gönderildi mi?
        var existing = await _cache.GetStringAsync(deduplicationKey);
        if (existing != null)
        {
            var count = int.Parse(existing) + 1;
            await _cache.SetStringAsync(deduplicationKey, count.ToString(),
                new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) });
            return; // Tekrar gönderme
        }

        await _cache.SetStringAsync(deduplicationKey, "1",
            new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) });

        await _slack.SendAsync(FormatAlert(alert));
    }
}

3. Alert Severity Tanımlamamak

Yanlış Kullanım: Tüm uyarıları aynı seviyede göndermek.

await _slack.SendAsync($"Alert: {message}");
// 404 hatası ile veritabanı çökmesi aynı kanalda, aynı formatta

İdeal Kullanım: Severity bazlı routing ve escalation yapın.

public enum AlertSeverity { Info, Warning, Critical, Fatal }

public class AlertRouter
{
    public async Task RouteAsync(Alert alert)
    {
        switch (alert.Severity)
        {
            case AlertSeverity.Info:
                await _slack.SendToChannelAsync("#monitoring-info", alert);
                break;

            case AlertSeverity.Warning:
                await _slack.SendToChannelAsync("#monitoring-warnings", alert);
                break;

            case AlertSeverity.Critical:
                await _slack.SendToChannelAsync("#incidents", alert);
                await _pagerDuty.CreateIncidentAsync(alert);
                break;

            case AlertSeverity.Fatal:
                await _slack.SendToChannelAsync("#incidents", alert);
                await _pagerDuty.CreateIncidentAsync(alert, urgency: "high");
                await _sms.SendToOnCallAsync(alert.Message);
                break;
        }
    }
}

4. Business Event’leri Loglamamak

Yanlış Kullanım: Sadece teknik hataları loglamak.

// Sadece exception loglanıyor
// Sipariş iptal oranı arttı? Ödeme başarısızlığı arttı? Bilinmiyor

İdeal Kullanım: İş olaylarını da loglayın ve izleyin.

public class OrderService
{
    public async Task<Result> CancelOrderAsync(int orderId, string reason)
    {
        var order = await _repository.GetByIdAsync(orderId);
        order.Cancel(reason);
        await _repository.SaveAsync();

        _logger.LogInformation("İş olayı: Siparişİptal {OrderId} {Reason} {CustomerId} {Amount}",
            orderId, reason, order.CustomerId, order.TotalAmount);

        _metrics.RecordBusinessEvent("order_cancelled", new()
        {
            ["reason"] = reason,
            ["customer_tier"] = order.CustomerTier
        });

        // Anormal iptal oranı kontrolü
        var cancelRate = await _metrics.GetCancelRateAsync(TimeSpan.FromHours(1));
        if (cancelRate > 0.1) // %10'dan fazla iptal
            await _alertService.SendAsync(new Alert
            {
                Severity = AlertSeverity.Warning,
                Title = "Yüksek sipariş iptal oranı",
                Message = $"Son 1 saatte iptal oranı: {cancelRate:P1}"
            });

        return Result.Success();
    }
}

5. Audit Log Tutmamak

Yanlış Kullanım: Kullanıcı eylemlerini izlememek.

public async Task DeleteUserAsync(int userId)
{
    var user = await _context.Users.FindAsync(userId);
    _context.Users.Remove(user);
    await _context.SaveChangesAsync();
    // Kim sildi? Ne zaman? Neden? Bilinmiyor
}

İdeal Kullanım: Audit log ile tüm kritik eylemleri kaydedin.

public class AuditLogService
{
    public async Task LogAsync(AuditEntry entry)
    {
        await _context.AuditLogs.AddAsync(new AuditLog
        {
            UserId = entry.UserId,
            Action = entry.Action,
            EntityType = entry.EntityType,
            EntityId = entry.EntityId,
            OldValues = JsonSerializer.Serialize(entry.OldValues),
            NewValues = JsonSerializer.Serialize(entry.NewValues),
            Timestamp = DateTimeOffset.UtcNow,
            IpAddress = entry.IpAddress
        });
        await _context.SaveChangesAsync();
    }
}

// EF Core interceptor ile otomatik audit
public class AuditSaveChangesInterceptor : SaveChangesInterceptor
{
    public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData, InterceptionResult<int> result, CancellationToken ct)
    {
        var context = eventData.Context!;
        var auditEntries = context.ChangeTracker.Entries()
            .Where(e => e.State is EntityState.Modified or EntityState.Deleted)
            .Select(e => new AuditEntry
            {
                EntityType = e.Entity.GetType().Name,
                Action = e.State.ToString(),
                OldValues = e.OriginalValues.Properties
                    .ToDictionary(p => p.Name, p => e.OriginalValues[p])
            }).ToList();

        // Audit kayıtlarını sakla
        return await base.SavingChangesAsync(eventData, result, ct);
    }
}