diff --git a/src/Observability/Runtime/Etw/EtwEventSource.cs b/src/Observability/Runtime/Etw/EtwEventSource.cs
index 7eaca46e..b2921a35 100644
--- a/src/Observability/Runtime/Etw/EtwEventSource.cs
+++ b/src/Observability/Runtime/Etw/EtwEventSource.cs
@@ -6,93 +6,67 @@
namespace Microsoft.Agents.A365.Observability.Runtime.Etw
{
///
- /// ETW Event Source for Observability
+ /// ETW Event Source for Observability.
+ /// Call once at startup to configure the singleton,
+ /// then access it via . If is accessed
+ /// before , a default instance (with throw on errors) is created.
///
[EventSource(Name = "A365-O11y-EventSource")]
public class EtwEventSource : EventSource
{
- private static readonly object _lock = new object();
- private static bool _throwOnEventWriteErrors = false;
- private static Lazy _lazy =
- new Lazy(CreateInstance);
-
- private static EtwEventSource CreateInstance() =>
- _throwOnEventWriteErrors
- ? new EtwEventSource(EventSourceSettings.ThrowOnEventWriteErrors)
- : new EtwEventSource();
+ private static EtwEventSource? _instance;
///
- /// Singleton instance of the EtwEventSource.
+ /// Gets the singleton instance. Creates a default instance (with
+ /// ) if
+ /// has not been called.
///
- public static EtwEventSource Log
- {
- get
- {
- if (_lazy.IsValueCreated)
- {
- return _lazy.Value;
- }
-
- lock (_lock)
- {
- return _lazy.Value;
- }
- }
- }
+ public static EtwEventSource Log => _instance ??= new EtwEventSource(EventSourceSettings.ThrowOnEventWriteErrors);
private EtwEventSource() : base() { }
private EtwEventSource(EventSourceSettings settings) : base(settings) { }
///
- /// Configures the singleton before it is first used.
- /// Must be called before accessing .
+ /// Initializes the singleton with the specified settings.
+ /// Must be called before the first access of .
///
- ///
- /// When , the underlying will be created with
- /// .
+ ///
+ /// When , the underlying will be created without
+ /// . By default, throw on errors is enabled.
///
///
- /// Thrown if the singleton has already been created (i.e. has been accessed).
+ /// Thrown if the singleton has already been created.
///
- public static void Configure(bool throwOnEventWriteErrors)
+ public static void Initialize(bool suppressThrowOnEventWriteErrors = false)
{
- lock (_lock)
+ if (_instance != null)
{
- if (_lazy.IsValueCreated)
- {
- throw new InvalidOperationException(
- "EtwEventSource has already been created. Configure() must be called before the first access of Log.");
- }
-
- _throwOnEventWriteErrors = throwOnEventWriteErrors;
+ throw new InvalidOperationException(
+ "EtwEventSource has already been initialized. Initialize() must be called before the first access of Log.");
}
+
+ _instance = suppressThrowOnEventWriteErrors
+ ? new EtwEventSource()
+ : new EtwEventSource(EventSourceSettings.ThrowOnEventWriteErrors);
}
///
- /// Resets the singleton so tests can exercise on a fresh instance.
+ /// Resets the singleton so tests can exercise on a fresh instance.
///
internal static void ResetForTesting()
{
- lock (_lock)
- {
- if (_lazy.IsValueCreated)
- {
- _lazy.Value.Dispose();
- }
-
- _throwOnEventWriteErrors = false;
- _lazy = new Lazy(CreateInstance);
- }
+ _instance?.Dispose();
+ _instance = null;
}
///
/// Handler for stopping a span.
/// Writes an ETW event with the necessary information from the span.
///
- [Event(1000,
- Level = EventLevel.Informational,
- Opcode = EventOpcode.Stop,
+ [Event(1000,
+ Level = EventLevel.Informational,
+ Opcode = EventOpcode.Stop,
Message = "A365 Otel span: Name={0} Id={1} Body={4}")]
public void SpanStop(string name, string spanId, string traceId, string parentSpanId, string content) =>
WriteEvent(1000, name, spanId, traceId, parentSpanId, content);
@@ -101,8 +75,8 @@ public void SpanStop(string name, string spanId, string traceId, string parentSp
/// Handler for logging JSON messages.
/// Writes an ETW event with the provided JSON message.
///
- [Event(2000,
- Level = EventLevel.Informational,
+ [Event(2000,
+ Level = EventLevel.Informational,
Message = "{0}")]
public void LogJson(string message) =>
WriteEvent(2000, message);
diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Etw/EtwEventSourceConfigureTests.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Etw/EtwEventSourceConfigureTests.cs
index 355426f6..a39dd584 100644
--- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Etw/EtwEventSourceConfigureTests.cs
+++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Etw/EtwEventSourceConfigureTests.cs
@@ -22,10 +22,10 @@ public void Cleanup()
}
[TestMethod]
- public void Configure_WithTrue_BeforeLogAccess_CreatesInstanceWithThrowOnErrors()
+ public void Initialize_Default_CreatesInstanceWithThrowOnErrors()
{
// Arrange & Act
- EtwEventSource.Configure(throwOnEventWriteErrors: true);
+ EtwEventSource.Initialize();
var log = EtwEventSource.Log;
// Assert
@@ -33,10 +33,10 @@ public void Configure_WithTrue_BeforeLogAccess_CreatesInstanceWithThrowOnErrors(
}
[TestMethod]
- public void Configure_WithFalse_BeforeLogAccess_CreatesInstanceWithoutThrowOnErrors()
+ public void Initialize_WithSuppressTrue_CreatesInstanceWithoutThrowOnErrors()
{
// Arrange & Act
- EtwEventSource.Configure(throwOnEventWriteErrors: false);
+ EtwEventSource.Initialize(suppressThrowOnEventWriteErrors: true);
var log = EtwEventSource.Log;
// Assert
@@ -44,27 +44,40 @@ public void Configure_WithFalse_BeforeLogAccess_CreatesInstanceWithoutThrowOnErr
}
[TestMethod]
- public void Log_WithoutConfigure_CreatesInstanceWithoutThrowOnErrors()
+ public void Log_WithoutInitialize_CreatesDefaultInstanceWithThrowOnErrors()
{
// Act
var log = EtwEventSource.Log;
// Assert
- Assert.IsFalse(log.Settings.HasFlag(EventSourceSettings.ThrowOnEventWriteErrors));
+ Assert.IsTrue(log.Settings.HasFlag(EventSourceSettings.ThrowOnEventWriteErrors));
}
[TestMethod]
- public void Configure_AfterLogAccess_ThrowsInvalidOperationException()
+ public void Initialize_AfterLogAccess_ThrowsInvalidOperationException()
{
// Arrange - force singleton creation
_ = EtwEventSource.Log;
// Act & Assert
var ex = Assert.ThrowsException(() =>
- EtwEventSource.Configure(throwOnEventWriteErrors: true));
+ EtwEventSource.Initialize());
- StringAssert.Contains(ex.Message, "Configure()");
+ StringAssert.Contains(ex.Message, "Initialize()");
StringAssert.Contains(ex.Message, "before the first access");
}
+
+ [TestMethod]
+ public void Initialize_CalledTwice_ThrowsInvalidOperationException()
+ {
+ // Arrange
+ EtwEventSource.Initialize();
+
+ // Act & Assert
+ var ex = Assert.ThrowsException(() =>
+ EtwEventSource.Initialize());
+
+ StringAssert.Contains(ex.Message, "already been initialized");
+ }
}
}