< Summary

Information
Class: OzonOrdersWeb.Observability.SlowDbCommandInterceptor
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Observability/SlowDbCommandInterceptor.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 39
Coverable lines: 39
Total lines: 110
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 8
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
ReaderExecuted(...)100%210%
ReaderExecutedAsync(...)100%210%
NonQueryExecuted(...)100%210%
NonQueryExecutedAsync(...)100%210%
ScalarExecuted(...)100%210%
ScalarExecutedAsync(...)100%210%
CommandFailed(...)100%210%
CommandFailedAsync(...)100%210%
LogIfSlow(...)0%2040%
LogFailure(...)0%620%
SanitizeCommand(...)0%620%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Observability/SlowDbCommandInterceptor.cs

#LineLine coverage
 1using System.Data.Common;
 2using System.Text.RegularExpressions;
 3using Microsoft.EntityFrameworkCore.Diagnostics;
 4using Serilog;
 5
 6namespace OzonOrdersWeb.Observability;
 7
 08public sealed class SlowDbCommandInterceptor(int slowMilliseconds) : DbCommandInterceptor
 9{
 10    public override DbDataReader ReaderExecuted(
 11        DbCommand command,
 12        CommandExecutedEventData eventData,
 13        DbDataReader result)
 14    {
 015        LogIfSlow(command, eventData.Duration);
 016        return result;
 17    }
 18
 19    public override ValueTask<DbDataReader> ReaderExecutedAsync(
 20        DbCommand command,
 21        CommandExecutedEventData eventData,
 22        DbDataReader result,
 23        CancellationToken cancellationToken = default)
 24    {
 025        LogIfSlow(command, eventData.Duration);
 026        return ValueTask.FromResult(result);
 27    }
 28
 29    public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result)
 30    {
 031        LogIfSlow(command, eventData.Duration);
 032        return result;
 33    }
 34
 35    public override ValueTask<int> NonQueryExecutedAsync(
 36        DbCommand command,
 37        CommandExecutedEventData eventData,
 38        int result,
 39        CancellationToken cancellationToken = default)
 40    {
 041        LogIfSlow(command, eventData.Duration);
 042        return ValueTask.FromResult(result);
 43    }
 44
 45    public override object? ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object? result)
 46    {
 047        LogIfSlow(command, eventData.Duration);
 048        return result;
 49    }
 50
 51    public override ValueTask<object?> ScalarExecutedAsync(
 52        DbCommand command,
 53        CommandExecutedEventData eventData,
 54        object? result,
 55        CancellationToken cancellationToken = default)
 56    {
 057        LogIfSlow(command, eventData.Duration);
 058        return ValueTask.FromResult(result);
 59    }
 60
 61    public override void CommandFailed(DbCommand command, CommandErrorEventData eventData)
 62    {
 063        LogFailure(command, eventData.Duration, eventData.Exception);
 064    }
 65
 66    public override Task CommandFailedAsync(
 67        DbCommand command,
 68        CommandErrorEventData eventData,
 69        CancellationToken cancellationToken = default)
 70    {
 071        LogFailure(command, eventData.Duration, eventData.Exception);
 072        return Task.CompletedTask;
 73    }
 74
 75    private void LogIfSlow(DbCommand command, TimeSpan duration)
 76    {
 077        if (duration.TotalMilliseconds < slowMilliseconds)
 78        {
 079            return;
 80        }
 81
 082        Log.ForContext("EventKind", "SlowDatabaseCommand")
 083            .ForContext("EventCategory", "database")
 084            .ForContext("ElapsedMilliseconds", duration.TotalMilliseconds)
 085            .ForContext("Database", command.Connection?.Database)
 086            .ForContext("CommandType", command.CommandType)
 087            .ForContext("CommandText", SanitizeCommand(command.CommandText))
 088            .Warning("Медленная команда БД выполнялась {ElapsedMilliseconds:0} мс", duration.TotalMilliseconds);
 089    }
 90
 91    private static void LogFailure(DbCommand command, TimeSpan duration, Exception exception)
 92    {
 093        Log.ForContext("EventKind", "DatabaseCommandFailed")
 094            .ForContext("EventCategory", "database")
 095            .ForContext("ElapsedMilliseconds", duration.TotalMilliseconds)
 096            .ForContext("Database", command.Connection?.Database)
 097            .ForContext("CommandType", command.CommandType)
 098            .ForContext("CommandText", SanitizeCommand(command.CommandText))
 099            .Error(exception, "Ошибка выполнения команды БД через {ElapsedMilliseconds:0} мс", duration.TotalMillisecond
 0100    }
 101
 102    private static string SanitizeCommand(string commandText)
 103    {
 104        const int maxLength = 4_096;
 0105        var normalized = string.Join(' ', commandText.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries));
 0106        normalized = Regex.Replace(normalized, "'(?:''|[^'])*'", "'?'", RegexOptions.CultureInvariant);
 0107        normalized = Regex.Replace(normalized, "\"(?:\"\"|[^\"])*\"", "\"?\"", RegexOptions.CultureInvariant);
 0108        return normalized.Length <= maxLength ? normalized : string.Concat(normalized.AsSpan(0, maxLength), "…");
 109    }
 110}