diff --git a/src/OpenFeature/Hooks/LoggingHook.cs b/src/OpenFeature/Hooks/LoggingHook.cs
new file mode 100644
index 00000000..0a78e5fe
--- /dev/null
+++ b/src/OpenFeature/Hooks/LoggingHook.cs
@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using OpenFeature.Model;
+
+namespace OpenFeature.Hooks
+{
+ ///
+ /// The logging hook is a hook which logs messages during the flag evaluation life-cycle.
+ ///
+ public sealed partial class LoggingHook : Hook
+ {
+ private readonly ILogger _logger;
+ private readonly bool _includeContext;
+
+ ///
+ /// Initialise a with a and optional Evaluation Context. will
+ /// include properties in the to the generated logs.
+ ///
+ public LoggingHook(ILogger logger, bool includeContext = false)
+ {
+ this._logger = logger;
+ this._includeContext = includeContext;
+ }
+
+ ///
+ public override ValueTask BeforeAsync(HookContext context, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default)
+ {
+ var evaluationContext = this._includeContext ? context.EvaluationContext : null;
+
+ var beforeLogContent = new LoggingHookContent(
+ context.ClientMetadata.Name,
+ context.ProviderMetadata.Name,
+ context.FlagKey,
+ context.DefaultValue?.ToString(),
+ evaluationContext);
+
+ this.HookBeforeStageExecuted(beforeLogContent);
+
+ return base.BeforeAsync(context, hints, cancellationToken);
+ }
+
+ ///
+ public override ValueTask ErrorAsync(HookContext context, Exception error, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default)
+ {
+ var evaluationContext = this._includeContext ? context.EvaluationContext : null;
+
+ var beforeLogContent = new LoggingHookContent(
+ context.ClientMetadata.Name,
+ context.ProviderMetadata.Name,
+ context.FlagKey,
+ context.DefaultValue?.ToString(),
+ evaluationContext);
+
+ this.HookErrorStageExecuted(beforeLogContent);
+
+ return base.ErrorAsync(context, error, hints, cancellationToken);
+ }
+
+ ///
+ public override ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default)
+ {
+ var evaluationContext = this._includeContext ? context.EvaluationContext : null;
+
+ var beforeLogContent = new LoggingHookContent(
+ context.ClientMetadata.Name,
+ context.ProviderMetadata.Name,
+ context.FlagKey,
+ context.DefaultValue?.ToString(),
+ evaluationContext);
+
+ this.HookAfterStageExecuted(beforeLogContent);
+
+ return base.AfterAsync(context, details, hints, cancellationToken);
+ }
+
+ [LoggerMessage(
+ EventId = 0,
+ Level = LogLevel.Debug,
+ Message = "Before Flag Evaluation {Content}")]
+ partial void HookBeforeStageExecuted(LoggingHookContent content);
+
+ [LoggerMessage(
+ EventId = 0,
+ Level = LogLevel.Error,
+ Message = "Error during Flag Evaluation {Content}")]
+ partial void HookErrorStageExecuted(LoggingHookContent content);
+
+ [LoggerMessage(
+ EventId = 0,
+ Level = LogLevel.Debug,
+ Message = "After Flag Evaluation {Content}")]
+ partial void HookAfterStageExecuted(LoggingHookContent content);
+
+ ///
+ /// Generates a log string with contents provided by the .
+ ///
+ /// Specification for log contents found at https://github.com/open-feature/spec/blob/d261f68331b94fd8ed10bc72bc0485cfc72a51a8/specification/appendix-a-included-utilities.md#logging-hook
+ ///
+ ///
+ internal class LoggingHookContent
+ {
+ private readonly string _domain;
+ private readonly string _providerName;
+ private readonly string _flagKey;
+ private readonly string _defaultValue;
+ private readonly EvaluationContext? _evaluationContext;
+
+ public LoggingHookContent(string? domain, string? providerName, string flagKey, string? defaultValue, EvaluationContext? evaluationContext = null)
+ {
+ this._domain = string.IsNullOrEmpty(domain) ? "missing" : domain!;
+ this._providerName = string.IsNullOrEmpty(providerName) ? "missing" : providerName!;
+ this._flagKey = flagKey;
+ this._defaultValue = string.IsNullOrEmpty(defaultValue) ? "missing" : defaultValue!;
+ this._evaluationContext = evaluationContext;
+ }
+
+ public override string ToString()
+ {
+ var stringBuilder = new StringBuilder();
+
+ stringBuilder.Append("Domain:");
+ stringBuilder.Append(this._domain);
+ stringBuilder.Append(Environment.NewLine);
+
+ stringBuilder.Append("ProviderName:");
+ stringBuilder.Append(this._providerName);
+ stringBuilder.Append(Environment.NewLine);
+
+ stringBuilder.Append("FlagKey:");
+ stringBuilder.Append(this._flagKey);
+ stringBuilder.Append(Environment.NewLine);
+
+ stringBuilder.Append("DefaultValue:");
+ stringBuilder.Append(this._defaultValue);
+ stringBuilder.Append(Environment.NewLine);
+
+ if (this._evaluationContext != null)
+ {
+ stringBuilder.Append("Context:");
+ stringBuilder.Append(Environment.NewLine);
+ foreach (var kvp in this._evaluationContext.AsDictionary())
+ {
+ stringBuilder.Append('\t');
+ stringBuilder.Append(kvp.Key);
+ stringBuilder.Append(':');
+ stringBuilder.Append(GetValueString(kvp.Value) ?? "missing");
+ stringBuilder.Append(Environment.NewLine);
+ }
+ }
+
+ return stringBuilder.ToString();
+ }
+
+ static string? GetValueString(Value value)
+ {
+ if (value.IsNull)
+ return string.Empty;
+
+ if (value.IsString)
+ return value.AsString;
+
+ if (value.IsBoolean)
+ return value.AsBoolean.ToString();
+
+ if (value.IsDateTime)
+ return value.AsDateTime?.ToString("O");
+
+ return value.ToString();
+ }
+ }
+ }
+}
diff --git a/src/OpenFeature/LoggingHook.cs b/src/OpenFeature/LoggingHook.cs
deleted file mode 100644
index 726944bc..00000000
--- a/src/OpenFeature/LoggingHook.cs
+++ /dev/null
@@ -1,209 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using OpenFeature.Model;
-
-namespace OpenFeature
-{
- ///
- ///
- ///
- public sealed partial class LoggingHook : Hook
- {
- private readonly ILogger _logger;
- private readonly bool _includeContext;
-
- ///
- ///
- ///
- public LoggingHook(ILogger logger, bool includeContext = false)
- {
- this._logger = logger;
- this._includeContext = includeContext;
- }
-
- ///
- public override ValueTask BeforeAsync(HookContext context, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default)
- {
- if (this._includeContext)
- {
- var beforeLogContent = new LoggingHookContent(
- context.ClientMetadata.Name,
- context.ProviderMetadata.Name,
- context.FlagKey,
- context.DefaultValue?.ToString(),
- context.EvaluationContext);
-
- this.HookBeforeStageExecuted(beforeLogContent);
- }
- else
- {
- var beforeLogContent = new LoggingHookContent(
- context.ClientMetadata.Name,
- context.ProviderMetadata.Name,
- context.FlagKey,
- context.DefaultValue?.ToString());
-
- this.HookBeforeStageExecuted(beforeLogContent);
- }
-
- return base.BeforeAsync(context, hints, cancellationToken);
- }
-
- ///
- public override ValueTask ErrorAsync(HookContext context, Exception error, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default)
- {
- if (this._includeContext)
- {
- var beforeLogContent = new LoggingHookContent(
- context.ClientMetadata.Name,
- context.ProviderMetadata.Name,
- context.FlagKey,
- context.DefaultValue?.ToString(),
- context.EvaluationContext);
-
- this.HookErrorStageExecuted(beforeLogContent);
- }
- else
- {
- var beforeLogContent = new LoggingHookContent(
- context.ClientMetadata.Name,
- context.ProviderMetadata.Name,
- context.FlagKey,
- context.DefaultValue?.ToString());
-
- this.HookErrorStageExecuted(beforeLogContent);
- }
-
- return base.ErrorAsync(context, error, hints, cancellationToken);
- }
-
- ///
- public override ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default)
- {
- if (this._includeContext)
- {
- var beforeLogContent = new LoggingHookContent(
- context.ClientMetadata.Name,
- context.ProviderMetadata.Name,
- context.FlagKey,
- context.DefaultValue?.ToString(),
- context.EvaluationContext);
-
- this.HookAfterStageExecuted(beforeLogContent);
- }
- else
- {
- var beforeLogContent = new LoggingHookContent(
- context.ClientMetadata.Name,
- context.ProviderMetadata.Name,
- context.FlagKey,
- context.DefaultValue?.ToString());
-
- this.HookAfterStageExecuted(beforeLogContent);
- }
-
- return base.AfterAsync(context, details, hints, cancellationToken);
- }
-
- [LoggerMessage(
- EventId = 0,
- Level = LogLevel.Debug,
- Message = "Before Flag Evaluation {Content}")]
- partial void HookBeforeStageExecuted(LoggingHookContent content);
-
- [LoggerMessage(
- EventId = 0,
- Level = LogLevel.Error,
- Message = "Error during Flag Evaluation {Content}")]
- partial void HookErrorStageExecuted(LoggingHookContent content);
-
- [LoggerMessage(
- EventId = 0,
- Level = LogLevel.Debug,
- Message = "After Flag Evaluation {Content}")]
- partial void HookAfterStageExecuted(LoggingHookContent content);
-
- internal class LoggingHookContent
- {
- private readonly string _domain;
- private readonly string _providerName;
- private readonly string _flagKey;
- private readonly string _defaultValue;
- private readonly EvaluationContext? _evaluationContext;
-
- private string? _cachedToString;
-
- public LoggingHookContent(string? domain, string? providerName, string flagKey, string? defaultValue, EvaluationContext? evaluationContext = null)
- {
- this._domain = string.IsNullOrEmpty(domain) ? "missing" : domain!;
- this._providerName = string.IsNullOrEmpty(providerName) ? "missing" : providerName!;
- this._flagKey = flagKey;
- this._defaultValue = string.IsNullOrEmpty(defaultValue) ? "missing" : defaultValue!;
- this._evaluationContext = evaluationContext;
- }
-
- public override string ToString()
- {
- if (this._cachedToString == null)
- {
- var stringBuilder = new StringBuilder();
-
- stringBuilder.Append("Domain:");
- stringBuilder.Append(this._domain);
- stringBuilder.Append(Environment.NewLine);
-
- stringBuilder.Append("ProviderName:");
- stringBuilder.Append(this._providerName);
- stringBuilder.Append(Environment.NewLine);
-
- stringBuilder.Append("FlagKey:");
- stringBuilder.Append(this._flagKey);
- stringBuilder.Append(Environment.NewLine);
-
- stringBuilder.Append("DefaultValue:");
- stringBuilder.Append(this._defaultValue);
- stringBuilder.Append(Environment.NewLine);
-
- if (this._evaluationContext != null)
- {
- stringBuilder.Append("Context:");
- stringBuilder.Append(Environment.NewLine);
- foreach (var kvp in this._evaluationContext.AsDictionary())
- {
- stringBuilder.Append('\t');
- stringBuilder.Append(kvp.Key);
- stringBuilder.Append(':');
- stringBuilder.Append(GetValueString(kvp.Value) ?? "missing");
- stringBuilder.Append(Environment.NewLine);
- }
- }
-
- this._cachedToString = stringBuilder.ToString();
- }
-
- return this._cachedToString;
- }
-
- static string? GetValueString(Value value)
- {
- if (value.IsNull)
- return string.Empty;
-
- if (value.IsString)
- return value.AsString;
-
- if (value.IsBoolean)
- return value.AsBoolean.ToString();
-
- if (value.IsDateTime)
- return value.AsDateTime?.ToString("O");
-
- return value.ToString();
- }
- }
- }
-}