Skip to content

Commit

Permalink
Merge pull request #62 from mattwcole/external-scopes
Browse files Browse the repository at this point in the history
Make logger provider implement ISupportExternalScope
  • Loading branch information
mattwcole authored Sep 8, 2021
2 parents 1ee9f92 + 866e5a7 commit 9eb5aee
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 139 deletions.
42 changes: 0 additions & 42 deletions src/Gelf.Extensions.Logging/GelfLogScope.cs

This file was deleted.

181 changes: 94 additions & 87 deletions src/Gelf.Extensions.Logging/GelfLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions.Internal;

namespace Gelf.Extensions.Logging
{
public class GelfLogger : ILogger
{
private static readonly Regex AdditionalFieldKeyRegex = new(@"^[\w\.\-]*$", RegexOptions.Compiled);

private static readonly HashSet<string> ReservedAdditionalFieldKeys = new()
{
"id",
Expand All @@ -21,9 +21,8 @@ public class GelfLogger : ILogger
"message_template"
};

private readonly GelfMessageProcessor _messageProcessor;

private readonly string _name;
private readonly GelfMessageProcessor _messageProcessor;
private readonly GelfLoggerOptions _options;

public GelfLogger(string name, GelfMessageProcessor messageProcessor, GelfLoggerOptions options)
Expand All @@ -33,6 +32,8 @@ public GelfLogger(string name, GelfMessageProcessor messageProcessor, GelfLogger
_options = options;
}

internal IExternalScopeProvider? ScopeProvider { get; set; }

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception? exception, Func<TState, Exception?, string> formatter)
{
Expand All @@ -45,19 +46,13 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
{
ShortMessage = formatter(state, exception),
Host = _options.LogSource,
Logger = _name,
Exception = exception?.ToString(),
Level = GetLevel(logLevel),
Timestamp = GetTimestamp(),
Logger = _name,
Exception = exception?.ToString()
AdditionalFields = GetAdditionalFields(logLevel, eventId, state, exception).ToArray()
};

var additionalFields = _options.AdditionalFields
.Concat(GetFactoryAdditionalFields(logLevel, eventId, exception))
.Concat(GetScopeAdditionalFields())
.Concat(GetStateAdditionalFields(state));

message.AdditionalFields = ValidateAdditionalFields(additionalFields).ToArray();

if (eventId != default)
{
message.EventId = eventId.Id;
Expand All @@ -72,72 +67,37 @@ public bool IsEnabled(LogLevel logLevel)
return logLevel != LogLevel.None;
}

public IDisposable BeginScope<TState>(TState state)
public IDisposable BeginScope<TState>(TState state) => ScopeProvider?.Push(state) ?? NullScope.Instance;

private static SyslogSeverity GetLevel(LogLevel logLevel)
{
return state switch
return logLevel switch
{
IEnumerable<KeyValuePair<string, object>> fields => GelfLogScope.Push(fields),
ValueTuple<string, string> field => BeginValueTupleScope(field),
ValueTuple<string, sbyte> field => BeginValueTupleScope(field),
ValueTuple<string, byte> field => BeginValueTupleScope(field),
ValueTuple<string, short> field => BeginValueTupleScope(field),
ValueTuple<string, ushort> field => BeginValueTupleScope(field),
ValueTuple<string, int> field => BeginValueTupleScope(field),
ValueTuple<string, uint> field => BeginValueTupleScope(field),
ValueTuple<string, long> field => BeginValueTupleScope(field),
ValueTuple<string, ulong> field => BeginValueTupleScope(field),
ValueTuple<string, float> field => BeginValueTupleScope(field),
ValueTuple<string, double> field => BeginValueTupleScope(field),
ValueTuple<string, decimal> field => BeginValueTupleScope(field),
ValueTuple<string, object> field => BeginValueTupleScope(field),
_ => new NoopDisposable()
LogLevel.Trace => SyslogSeverity.Debug,
LogLevel.Debug => SyslogSeverity.Debug,
LogLevel.Information => SyslogSeverity.Informational,
LogLevel.Warning => SyslogSeverity.Warning,
LogLevel.Error => SyslogSeverity.Error,
LogLevel.Critical => SyslogSeverity.Critical,
_ => throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, "Log level not supported.")
};

static IDisposable BeginValueTupleScope((string, object) field)
{
return GelfLogScope.Push(new[]
{
new KeyValuePair<string, object>(field.Item1, field.Item2)
});
}
}

private IEnumerable<KeyValuePair<string, object>> GetFactoryAdditionalFields(
LogLevel logLevel, EventId eventId, Exception? exception)
{
return _options.AdditionalFieldsFactory?.Invoke(logLevel, eventId, exception) ??
Enumerable.Empty<KeyValuePair<string, object>>();
}

private IEnumerable<KeyValuePair<string, object>> GetScopeAdditionalFields()
private static double GetTimestamp()
{
var additionalFields = Enumerable.Empty<KeyValuePair<string, object>>();

if (!_options.IncludeScopes)
{
return additionalFields;
}

var scope = GelfLogScope.Current;
while (scope != null)
{
additionalFields = additionalFields.Concat(scope.AdditionalFields);
scope = scope.Parent;
}

return additionalFields.Reverse();
var totalMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var totalSeconds = totalMilliseconds / 1000d;
return Math.Round(totalSeconds, 3);
}

private static IEnumerable<KeyValuePair<string, object>> GetStateAdditionalFields<TState>(TState state)
private IEnumerable<KeyValuePair<string, object>> GetAdditionalFields<TState>(
LogLevel logLevel, EventId eventId, TState state, Exception? exception)
{
return state is IEnumerable<KeyValuePair<string, object>> logValues
? logValues
: Enumerable.Empty<KeyValuePair<string, object>>();
}
var additionalFields = _options.AdditionalFields
.Concat(GetFactoryAdditionalFields(logLevel, eventId, exception))
.Concat(GetScopeAdditionalFields())
.Concat(GetStateAdditionalFields(state));

private IEnumerable<KeyValuePair<string, object>> ValidateAdditionalFields(
IEnumerable<KeyValuePair<string, object>> additionalFields)
{
foreach (var field in additionalFields)
{
if (field.Key != "{OriginalFormat}")
Expand All @@ -158,32 +118,79 @@ private IEnumerable<KeyValuePair<string, object>> ValidateAdditionalFields(
}
}

private static SyslogSeverity GetLevel(LogLevel logLevel)
private IEnumerable<KeyValuePair<string, object>> GetFactoryAdditionalFields(
LogLevel logLevel, EventId eventId, Exception? exception)
{
return logLevel switch
{
LogLevel.Trace => SyslogSeverity.Debug,
LogLevel.Debug => SyslogSeverity.Debug,
LogLevel.Information => SyslogSeverity.Informational,
LogLevel.Warning => SyslogSeverity.Warning,
LogLevel.Error => SyslogSeverity.Error,
LogLevel.Critical => SyslogSeverity.Critical,
_ => throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, "Log level not supported.")
};
return _options.AdditionalFieldsFactory?.Invoke(logLevel, eventId, exception) ??
Enumerable.Empty<KeyValuePair<string, object>>();
}

private static double GetTimestamp()
private IEnumerable<KeyValuePair<string, object>> GetScopeAdditionalFields()
{
var totalMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var totalSeconds = totalMilliseconds / 1000d;
return Math.Round(totalSeconds, 3);
if (!_options.IncludeScopes)
{
return Enumerable.Empty<KeyValuePair<string, object>>();
}

var additionalFields = new List<KeyValuePair<string, object>>();

ScopeProvider?.ForEachScope((scope, state) =>
{
switch (scope)
{
case IEnumerable<KeyValuePair<string, object>> fields:
state.AddRange(fields);
break;
case ValueTuple<string, string>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, int>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, long>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, short>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, decimal>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, double>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, float>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, uint>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, ulong>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, ushort>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, byte>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, sbyte>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
case ValueTuple<string, object>(var key, var value):
state.Add(new KeyValuePair<string, object>(key, value));
break;
}
}, additionalFields);

return additionalFields;
}

private class NoopDisposable : IDisposable
private static IEnumerable<KeyValuePair<string, object>> GetStateAdditionalFields<TState>(TState state)
{
public void Dispose()
{
}
return state is IEnumerable<KeyValuePair<string, object>> additionalFields
? additionalFields
: Enumerable.Empty<KeyValuePair<string, object>>();
}
}
}
21 changes: 19 additions & 2 deletions src/Gelf.Extensions.Logging/GelfLoggerProvider.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
using System;
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Gelf.Extensions.Logging
{
[ProviderAlias("GELF")]
public class GelfLoggerProvider : ILoggerProvider
public class GelfLoggerProvider : ILoggerProvider, ISupportExternalScope
{
private readonly GelfLoggerOptions _options;
private readonly GelfMessageProcessor _messageProcessor;
private readonly IGelfClient _gelfClient;
private readonly ConcurrentDictionary<string, GelfLogger> _loggers;

private IExternalScopeProvider? _scopeProvider;

public GelfLoggerProvider(IOptions<GelfLoggerOptions> options) : this(options.Value)
{
Expand All @@ -31,11 +35,15 @@ public GelfLoggerProvider(GelfLoggerOptions options)
_gelfClient = CreateGelfClient(_options);
_messageProcessor = new GelfMessageProcessor(_gelfClient);
_messageProcessor.Start();
_loggers = new ConcurrentDictionary<string, GelfLogger>();
}

public ILogger CreateLogger(string name)
{
return new GelfLogger(name, _messageProcessor, _options);
return _loggers.GetOrAdd(name, newName => new GelfLogger(newName, _messageProcessor, _options)
{
ScopeProvider = _scopeProvider
});
}

private static IGelfClient CreateGelfClient(GelfLoggerOptions options)
Expand All @@ -54,5 +62,14 @@ public void Dispose()
_messageProcessor.Stop();
_gelfClient.Dispose();
}

public void SetScopeProvider(IExternalScopeProvider scopeProvider)
{
_scopeProvider = scopeProvider;
foreach (var logger in _loggers)
{
logger.Value.ScopeProvider = _scopeProvider;
}
}
}
}
10 changes: 7 additions & 3 deletions test/Gelf.Extensions.Logging.Tests/Fixtures/LoggerFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ public class LoggerFixture : IDisposable
public LoggerFixture(GelfLoggerOptions options)
{
TestContext.TestId = Guid.NewGuid().ToString();

LoggerOptions = options;
_loggerFactory = new LoggerFactory();
_loggerFactory.AddGelf(LoggerOptions);
}

public GelfLoggerOptions LoggerOptions { get; }

public ILoggerFactory CreateLoggerFactory(GelfLoggerOptions options)
public ILoggerFactory CreateLoggerFactory(GelfLoggerOptions options,
ActivityTrackingOptions trackingOptions = ActivityTrackingOptions.None)
{
var loggerFactory = new LoggerFactory();
var loggerFactory = LoggerFactory.Create(builder => builder
.Configure(loggerOptions => loggerOptions
.ActivityTrackingOptions = trackingOptions));

loggerFactory.AddGelf(options);

var logger = loggerFactory.CreateLogger<LoggerFixture>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Bogus" Version="28.4.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Bogus" Version="33.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Expand Down
Loading

0 comments on commit 9eb5aee

Please sign in to comment.