Skip to content

Commit d70e27f

Browse files
austindrenskiaskpt
andauthored
chore: Use logging source generators (#189)
This PR updates `OpenFeature` to use the latest MELA source generators to produce compile-time logging delegates. The most obvious critique of this PR is likely to be that, much like #188, this PR changes the dependency graph by bumping our MELA dependency from `[2.0.0,) => [8.0.0,)`. To that^ critique, I continue to contend that depending on an ancient version of MELA is unlikely to aid many real-world consumers of this SDK, since new versions of MELA provide robust TFM coverage, as well as my personal disbelief that anyone looking to consume this library in 2024 is deploying an app that won't already have transitively referenced MELA `8.0.0` somewhere in its package graph. _(If you are a user or potential user of this SDK and have a real-world use case of a legacy app that __does not and cannot__ reference MELA `>= 8.0.0`, please ping back here! Would love to hear from you and adjust my disbelief accordingly!)_ _(Note: if this PR lands before #188, then I'll update #188 to remove its added dependency on `Microsoft.Bcl.AsyncInterfaces`, since it flows transitively from MELA `8.0.0`.)_ Upon request, I am happy to provide a soapbox diatribe on why I think we should care about source generators, hook perf, and incidental logging allocations, especially as an SDK that wants to be trusted and baked into all kinds of consumer apps, but eliding that for now in favor of some docs refs: - https://learn.microsoft.com/dotnet/core/extensions/high-performance-logging - https://learn.microsoft.com/dotnet/core/extensions/logger-message-generator Signed-off-by: Austin Drenski <[email protected]> Signed-off-by: André Silva <[email protected]> Co-authored-by: André Silva <[email protected]>
1 parent 949d53c commit d70e27f

File tree

6 files changed

+36
-22
lines changed

6 files changed

+36
-22
lines changed

Directory.Packages.props

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66

77
<ItemGroup Label="src">
88
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
9-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
9+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
1010
<PackageVersion Include="System.Collections.Immutable" Version="1.7.1" />
1111
<PackageVersion Include="System.Threading.Channels" Version="6.0.0" />
12+
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
1213
</ItemGroup>
1314

1415
<ItemGroup Label="test">

build/Common.props

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<ItemGroup>
2323
<PackageReference Include="System.Collections.Immutable" Condition="'$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0'" />
2424
<PackageReference Include="System.Threading.Channels" Condition="'$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0'" />
25+
<PackageReference Include="System.ValueTuple" Condition="'$(TargetFramework)' == 'net462'" />
2526
</ItemGroup>
2627

2728
<ItemGroup Condition="'$(OS)' == 'Unix'">

src/OpenFeature/Api.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ public void RemoveHandler(ProviderEventTypes type, EventHandlerDelegate handler)
280280
/// <param name="logger">The logger to be used</param>
281281
public void SetLogger(ILogger logger)
282282
{
283-
this._eventExecutor.Logger = logger;
283+
this._eventExecutor.SetLogger(logger);
284284
}
285285

286286
internal void AddClientHandler(string client, ProviderEventTypes eventType, EventHandlerDelegate handler)

src/OpenFeature/EventExecutor.cs

+10-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace OpenFeature
1212
{
13-
internal class EventExecutor : IAsyncDisposable
13+
internal sealed partial class EventExecutor : IAsyncDisposable
1414
{
1515
private readonly object _lockObj = new object();
1616
public readonly Channel<object> EventChannel = Channel.CreateBounded<object>(1);
@@ -21,17 +21,19 @@ internal class EventExecutor : IAsyncDisposable
2121
private readonly Dictionary<ProviderEventTypes, List<EventHandlerDelegate>> _apiHandlers = new Dictionary<ProviderEventTypes, List<EventHandlerDelegate>>();
2222
private readonly Dictionary<string, Dictionary<ProviderEventTypes, List<EventHandlerDelegate>>> _clientHandlers = new Dictionary<string, Dictionary<ProviderEventTypes, List<EventHandlerDelegate>>>();
2323

24-
internal ILogger Logger { get; set; }
24+
private ILogger _logger;
2525

2626
public EventExecutor()
2727
{
28-
this.Logger = new Logger<EventExecutor>(new NullLoggerFactory());
28+
this._logger = NullLogger<EventExecutor>.Instance;
2929
var eventProcessing = new Thread(this.ProcessEventAsync);
3030
eventProcessing.Start();
3131
}
3232

3333
public ValueTask DisposeAsync() => new(this.Shutdown());
3434

35+
internal void SetLogger(ILogger logger) => this._logger = logger;
36+
3537
internal void AddApiLevelHandler(ProviderEventTypes eventType, EventHandlerDelegate handler)
3638
{
3739
lock (this._lockObj)
@@ -209,7 +211,7 @@ private void EmitOnRegistration(FeatureProvider? provider, ProviderEventTypes ev
209211
}
210212
catch (Exception exc)
211213
{
212-
this.Logger.LogError(exc, "Error running handler");
214+
this.ErrorRunningHandler(exc);
213215
}
214216
}
215217
}
@@ -311,7 +313,7 @@ private void InvokeEventHandler(EventHandlerDelegate eventHandler, Event e)
311313
}
312314
catch (Exception exc)
313315
{
314-
this.Logger.LogError(exc, "Error running handler");
316+
this.ErrorRunningHandler(exc);
315317
}
316318
}
317319

@@ -321,6 +323,9 @@ public async Task Shutdown()
321323

322324
await this.EventChannel.Reader.Completion.ConfigureAwait(false);
323325
}
326+
327+
[LoggerMessage(100, LogLevel.Error, "Error running handler")]
328+
partial void ErrorRunningHandler(Exception exception);
324329
}
325330

326331
internal class Event

src/OpenFeature/OpenFeatureClient.cs

+21-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace OpenFeature
1515
/// <summary>
1616
///
1717
/// </summary>
18-
public sealed class FeatureClient : IFeatureClient
18+
public sealed partial class FeatureClient : IFeatureClient
1919
{
2020
private readonly ClientMetadata _metadata;
2121
private readonly ConcurrentStack<Hook> _hooks = new ConcurrentStack<Hook>();
@@ -76,7 +76,7 @@ public void SetContext(EvaluationContext? context)
7676
public FeatureClient(string? name, string? version, ILogger? logger = null, EvaluationContext? context = null)
7777
{
7878
this._metadata = new ClientMetadata(name, version);
79-
this._logger = logger ?? new Logger<Api>(new NullLoggerFactory());
79+
this._logger = logger ?? NullLogger<FeatureClient>.Instance;
8080
this._evaluationContext = context ?? EvaluationContext.Empty;
8181
}
8282

@@ -252,15 +252,14 @@ private async Task<FlagEvaluationDetails<T>> EvaluateFlag<T>(
252252
}
253253
catch (FeatureProviderException ex)
254254
{
255-
this._logger.LogError(ex, "Error while evaluating flag {FlagKey}. Error {ErrorType}", flagKey,
256-
ex.ErrorType.GetDescription());
255+
this.FlagEvaluationErrorWithDescription(flagKey, ex.ErrorType.GetDescription(), ex);
257256
evaluation = new FlagEvaluationDetails<T>(flagKey, defaultValue, ex.ErrorType, Reason.Error,
258257
string.Empty, ex.Message);
259258
await this.TriggerErrorHooks(allHooksReversed, hookContext, ex, options).ConfigureAwait(false);
260259
}
261260
catch (Exception ex)
262261
{
263-
this._logger.LogError(ex, "Error while evaluating flag {FlagKey}", flagKey);
262+
this.FlagEvaluationError(flagKey, ex);
264263
var errorCode = ex is InvalidCastException ? ErrorType.TypeMismatch : ErrorType.General;
265264
evaluation = new FlagEvaluationDetails<T>(flagKey, defaultValue, errorCode, Reason.Error, string.Empty, ex.Message);
266265
await this.TriggerErrorHooks(allHooksReversed, hookContext, ex, options).ConfigureAwait(false);
@@ -289,8 +288,7 @@ private async Task<HookContext<T>> TriggerBeforeHooks<T>(IReadOnlyList<Hook> hoo
289288
}
290289
else
291290
{
292-
this._logger.LogDebug("Hook {HookName} returned null, nothing to merge back into context",
293-
hook.GetType().Name);
291+
this.HookReturnedNull(hook.GetType().Name);
294292
}
295293
}
296294

@@ -317,7 +315,7 @@ private async Task TriggerErrorHooks<T>(IReadOnlyList<Hook> hooks, HookContext<T
317315
}
318316
catch (Exception e)
319317
{
320-
this._logger.LogError(e, "Error while executing Error hook {HookName}", hook.GetType().Name);
318+
this.ErrorHookError(hook.GetType().Name, e);
321319
}
322320
}
323321
}
@@ -337,5 +335,20 @@ private async Task TriggerFinallyHooks<T>(IReadOnlyList<Hook> hooks, HookContext
337335
}
338336
}
339337
}
338+
339+
[LoggerMessage(100, LogLevel.Debug, "Hook {HookName} returned null, nothing to merge back into context")]
340+
partial void HookReturnedNull(string hookName);
341+
342+
[LoggerMessage(101, LogLevel.Error, "Error while evaluating flag {FlagKey}")]
343+
partial void FlagEvaluationError(string flagKey, Exception exception);
344+
345+
[LoggerMessage(102, LogLevel.Error, "Error while evaluating flag {FlagKey}: {ErrorType}")]
346+
partial void FlagEvaluationErrorWithDescription(string flagKey, string errorType, Exception exception);
347+
348+
[LoggerMessage(103, LogLevel.Error, "Error while executing Error hook {HookName}")]
349+
partial void ErrorHookError(string hookName, Exception exception);
350+
351+
[LoggerMessage(104, LogLevel.Error, "Error while executing Finally hook {HookName}")]
352+
partial void FinallyHookError(string hookName, Exception exception);
340353
}
341354
}

test/OpenFeature.Tests/OpenFeatureClientTests.cs

+1-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using AutoFixture;
77
using FluentAssertions;
88
using Microsoft.Extensions.Logging;
9-
using Microsoft.Extensions.Logging.Internal;
109
using NSubstitute;
1110
using NSubstitute.ExceptionExtensions;
1211
using OpenFeature.Constant;
@@ -182,12 +181,7 @@ public async Task OpenFeatureClient_Should_Return_DefaultValue_When_Type_Mismatc
182181

183182
_ = mockedFeatureProvider.Received(1).ResolveStructureValue(flagName, defaultValue, Arg.Any<EvaluationContext>());
184183

185-
mockedLogger.Received(1).Log(
186-
LogLevel.Error,
187-
Arg.Any<EventId>(),
188-
Arg.Is<FormattedLogValues>(t => string.Equals($"Error while evaluating flag {flagName}", t.ToString(), StringComparison.InvariantCultureIgnoreCase)),
189-
Arg.Any<Exception>(),
190-
Arg.Any<Func<object, Exception, string>>());
184+
mockedLogger.Received(1).IsEnabled(LogLevel.Error);
191185
}
192186

193187
[Fact]

0 commit comments

Comments
 (0)