< Summary

Information
Class: Elsa.Diagnostics.StructuredLogs.Logging.StructuredLogLogger
Assembly: Elsa.Diagnostics.StructuredLogs
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Diagnostics.StructuredLogs/Logging/StructuredLogLogger.cs
Line coverage
90%
Covered lines: 82
Uncovered lines: 9
Coverable lines: 91
Total lines: 161
Line coverage: 90.1%
Branch coverage
68%
Covered branches: 51
Total branches: 75
Branch coverage: 68%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
BeginScope(...)100%11100%
IsEnabled(...)50%44100%
Log(...)71.42%1414100%
ToStructuredLogLevel(...)28.57%13750%
ExtractMessageTemplate(...)66.66%66100%
ExtractProperties(...)80%111080%
ExtractScopes()100%22100%
AddScope(...)66.66%6685.71%
TryAddKeyValueScope(...)75%202092.3%
AddValue(...)50%4483.33%
GetContextValue(...)100%22100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Diagnostics.StructuredLogs/Logging/StructuredLogLogger.cs

#LineLine coverage
 1using System.Collections;
 2using System.Diagnostics;
 3using Elsa.Diagnostics.StructuredLogs.Contracts;
 4using Elsa.Diagnostics.StructuredLogs.Models;
 5using Elsa.Diagnostics.StructuredLogs.Options;
 6using Microsoft.Extensions.Logging;
 7
 8namespace Elsa.Diagnostics.StructuredLogs.Logging;
 9
 510public class StructuredLogLogger(
 511    string categoryName,
 512    IStructuredLogProvider logProvider,
 513    IStructuredLogRedactor redactor,
 514    IStructuredLogSourceRegistry sourceRegistry,
 515    StructuredLogsOptions options,
 516    Func<IExternalScopeProvider> getScopeProvider) : ILogger
 17{
 18    private long _sequence;
 19
 220    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => getScopeProvider().Push(state);
 21
 22    public bool IsEnabled(LogLevel logLevel)
 23    {
 524        return logLevel != LogLevel.None && (options.IncludeStructuredLogsInternalLogs || !categoryName.StartsWith("Elsa
 25    }
 26
 27    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Excepti
 28    {
 529        if (!IsEnabled(logLevel))
 130            return;
 31
 432        var now = DateTimeOffset.UtcNow;
 433        var currentActivity = Activity.Current;
 434        var properties = ExtractProperties(state);
 435        var scopes = ExtractScopes();
 436        var logEvent = new StructuredLogEvent
 437        {
 438            Sequence = Interlocked.Increment(ref _sequence),
 439            Timestamp = now,
 440            ReceivedAt = now,
 441            Level = ToStructuredLogLevel(logLevel),
 442            Category = categoryName,
 443            EventId = eventId.Id,
 444            EventName = eventId.Name,
 445            Message = formatter(state, exception),
 446            MessageTemplate = ExtractMessageTemplate(state),
 447            Exception = exception == null ? null : new StructuredLogException(exception.GetType().FullName ?? exception.
 448            TraceId = currentActivity?.TraceId.ToString(),
 449            SpanId = currentActivity?.SpanId.ToString(),
 450            CorrelationId = currentActivity?.RootId ?? GetContextValue(properties, scopes, nameof(StructuredLogEvent.Cor
 451            TenantId = GetContextValue(properties, scopes, nameof(StructuredLogEvent.TenantId)),
 452            WorkflowDefinitionId = GetContextValue(properties, scopes, nameof(StructuredLogEvent.WorkflowDefinitionId)),
 453            WorkflowInstanceId = GetContextValue(properties, scopes, nameof(StructuredLogEvent.WorkflowInstanceId)),
 454            SourceId = sourceRegistry.Current.Id,
 455            Scopes = scopes,
 456            Properties = properties
 457        };
 58
 459        var redacted = redactor.Redact(logEvent);
 460        _ = logProvider.PublishAsync(redacted);
 461    }
 62
 63    private static StructuredLogLevel ToStructuredLogLevel(LogLevel logLevel)
 64    {
 465        return logLevel switch
 466        {
 067            LogLevel.Trace => StructuredLogLevel.Trace,
 068            LogLevel.Debug => StructuredLogLevel.Debug,
 369            LogLevel.Information => StructuredLogLevel.Information,
 170            LogLevel.Warning => StructuredLogLevel.Warning,
 071            LogLevel.Error => StructuredLogLevel.Error,
 072            LogLevel.Critical => StructuredLogLevel.Critical,
 073            _ => StructuredLogLevel.None
 474        };
 75    }
 76
 77    private static string? ExtractMessageTemplate<TState>(TState state)
 78    {
 479        return state is IEnumerable<KeyValuePair<string, object?>> values
 780            ? values.FirstOrDefault(x => x.Key == "{OriginalFormat}").Value?.ToString()
 481            : null;
 82    }
 83
 84    private static Dictionary<string, string?> ExtractProperties<TState>(TState state)
 85    {
 486        if (state is not IEnumerable<KeyValuePair<string, object?>> values)
 087            return new();
 88
 489        return values
 790            .Where(x => x.Key != "{OriginalFormat}")
 1091            .ToDictionary(x => x.Key, x => x.Value?.ToString(), StringComparer.OrdinalIgnoreCase);
 92    }
 93
 94    private Dictionary<string, string?> ExtractScopes()
 95    {
 496        var scopes = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
 497        var scopeValues = new List<object?>();
 98
 699        getScopeProvider().ForEachScope((scope, state) => state.Add(scope), scopeValues);
 100
 12101        for (var i = 0; i < scopeValues.Count; i++)
 2102            AddScope(scopes, scopeValues[i], i);
 103
 4104        return scopes;
 105    }
 106
 107    private static void AddScope(IDictionary<string, string?> scopes, object? scope, int index)
 108    {
 2109        if (scope == null)
 0110            return;
 111
 2112        if (TryAddKeyValueScope(scopes, scope))
 1113            return;
 114
 1115        var key = index == 0 ? "Scope" : $"Scope{index + 1}";
 1116        AddValue(scopes, key, scope);
 1117    }
 118
 119    private static bool TryAddKeyValueScope(IDictionary<string, string?> scopes, object scope)
 120    {
 2121        if (scope is not IEnumerable values)
 0122            return false;
 123
 2124        var added = false;
 30125        foreach (var value in values)
 126        {
 13127            if (value == null)
 128                continue;
 129
 13130            var type = value.GetType();
 13131            if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(KeyValuePair<,>))
 132                continue;
 133
 2134            var key = type.GetProperty(nameof(KeyValuePair<string, object?>.Key))?.GetValue(value) as string;
 2135            if (key is null or "{OriginalFormat}")
 136                continue;
 137
 2138            var itemValue = type.GetProperty(nameof(KeyValuePair<string, object?>.Value))?.GetValue(value);
 2139            AddValue(scopes, key, itemValue);
 2140            added = true;
 141        }
 142
 2143        return added;
 144    }
 145
 146    private static void AddValue(IDictionary<string, string?> values, string key, object? value)
 147    {
 3148        var uniqueKey = key;
 3149        var suffix = 2;
 150
 3151        while (values.ContainsKey(uniqueKey))
 0152            uniqueKey = $"{key}{suffix++}";
 153
 3154        values[uniqueKey] = value?.ToString();
 3155    }
 156
 157    private static string? GetContextValue(IReadOnlyDictionary<string, string?> properties, IReadOnlyDictionary<string, 
 158    {
 16159        return properties.GetValueOrDefault(key) ?? scopes.GetValueOrDefault(key);
 160    }
 161}