Implementation and test of SdkSelfDiagnosticsEventListener, which emulates the SDK's own SelfDiagnosticsEventListener to capture all event sources that begin with the prefix 'OpenTelemetry-' (#458)

Co-authored-by: Paulo Janotti <pjanotti@splunk.com>
This commit is contained in:
Zach Montoya 2022-04-07 12:52:56 -07:00 committed by GitHub
parent d02924167f
commit 6588bbc567
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 214 additions and 1 deletions

View File

@ -0,0 +1,111 @@
// <copyright file="SdkSelfDiagnosticsEventListener.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
// Source originated from https://github.com/open-telemetry/opentelemetry-dotnet/blob/23609730ddd73c860553de847e67c9b2226cff94/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using OpenTelemetry.AutoInstrumentation.Logging;
namespace OpenTelemetry.AutoInstrumentation.Diagnostics;
/// <summary>
/// SdkSelfDiagnosticsEventListener class enables the events from OpenTelemetry event sources
/// and write the events to the OpenTelemetry.AutoInstrumentation logger
/// </summary>
internal class SdkSelfDiagnosticsEventListener : EventListener
{
private const string EventSourceNamePrefix = "OpenTelemetry-";
private static readonly ILogger Log = ConsoleLogger.Create(typeof(SdkSelfDiagnosticsEventListener));
private readonly object lockObj = new();
private readonly EventLevel logLevel;
private readonly List<EventSource> eventSourcesBeforeConstructor = new();
public SdkSelfDiagnosticsEventListener(EventLevel eventLevel)
{
logLevel = eventLevel;
List<EventSource> eventSources;
lock (lockObj)
{
eventSources = this.eventSourcesBeforeConstructor;
eventSourcesBeforeConstructor = null;
}
foreach (var eventSource in eventSources)
{
EnableEvents(eventSource, logLevel, EventKeywords.All);
}
}
protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name.StartsWith(EventSourceNamePrefix, StringComparison.Ordinal))
{
// If there are EventSource classes already initialized as of now, this method would be called from
// the base class constructor before the first line of code in SelfDiagnosticsEventListener constructor.
// In this case logLevel is always its default value, "LogAlways".
// Thus we should save the event source and enable them later, when code runs in constructor.
if (eventSourcesBeforeConstructor != null)
{
lock (lockObj)
{
if (eventSourcesBeforeConstructor != null)
{
eventSourcesBeforeConstructor.Add(eventSource);
return;
}
}
}
EnableEvents(eventSource, logLevel, EventKeywords.All);
}
base.OnEventSourceCreated(eventSource);
}
/// <summary>
/// This method records the events from event sources to a local file, which is provided as a stream object by
/// SelfDiagnosticsConfigRefresher class. The file size is bound to a upper limit. Once the write position
/// reaches the end, it will be reset to the beginning of the file.
/// </summary>
/// <param name="eventData">Data of the EventSource event.</param>
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
var payloadArray = new object[eventData.Payload.Count];
eventData.Payload.CopyTo(payloadArray, 0);
switch (eventData.Level)
{
case EventLevel.Critical:
case EventLevel.Error:
Log.Error("EventSource={0}, Message={1}", eventData.EventSource.Name, string.Format(eventData.Message, payloadArray));
break;
case EventLevel.Warning:
Log.Warning("EventSource={0}, Message={1}", eventData.EventSource.Name, string.Format(eventData.Message, payloadArray));
break;
case EventLevel.LogAlways:
case EventLevel.Informational:
Log.Information("EventSource={0}, Message={1}", eventData.EventSource.Name, string.Format(eventData.Message, payloadArray));
break;
case EventLevel.Verbose:
Log.Debug("EventSource={0}, Message={1}", eventData.EventSource.Name, string.Format(eventData.Message, payloadArray));
break;
}
}
}

View File

@ -16,8 +16,10 @@
using System;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Threading;
using OpenTelemetry.AutoInstrumentation.Configuration;
using OpenTelemetry.AutoInstrumentation.Diagnostics;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Shims.OpenTracing;
using OpenTelemetry.Trace;
@ -32,6 +34,7 @@ public static class Instrumentation
private static readonly Process _process = Process.GetCurrentProcess();
private static int _firstInitialization = 1;
private static int _isExiting = 0;
private static SdkSelfDiagnosticsEventListener _sdkEventListener;
private static TracerProvider _tracerProvider;
@ -74,6 +77,9 @@ public static class Instrumentation
{
if (TracerSettings.LoadTracerAtStartup)
{
// Initialize SdkSelfDiagnosticsEventListener to create an EventListener for the OpenTelemetry SDK
_sdkEventListener = new(EventLevel.Warning);
var builder = Sdk
.CreateTracerProviderBuilder()
.UseEnvironmentVariables(TracerSettings)
@ -133,6 +139,7 @@ public static class Instrumentation
try
{
_tracerProvider.Dispose();
_sdkEventListener.Dispose();
Log("OpenTelemetry tracer exit.");
}

View File

@ -206,7 +206,7 @@ internal class ConsoleLogger : ILogger
{
try
{
Console.WriteLine($"[{_name}] {level}:{string.Format(messageTemplate, args)}");
Console.WriteLine($"[{_name}] {level}: {string.Format(messageTemplate, args)}");
if (exception != null)
{

View File

@ -0,0 +1,95 @@
// <copyright file="SdkSelfDiagnosticsEventListenerTests.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
// Source originated from https://github.com/open-telemetry/opentelemetry-dotnet/blob/23609730ddd73c860553de847e67c9b2226cff94/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs
using System;
using System.Diagnostics.Tracing;
using System.IO;
using FluentAssertions;
using OpenTelemetry.AutoInstrumentation.Diagnostics;
using Xunit;
public class SdkSelfDiagnosticsEventListenerTests
{
[Fact]
public void EventSourceSetup_LowerSeverity()
{
var originalConsoleOut = Console.Out; // preserve the original stream
using var writer = new StringWriter();
var listener = new SdkSelfDiagnosticsEventListener(EventLevel.Error);
// Emitting a Verbose event. Or any EventSource event with lower severity than Error.
AspNetTelemetryEventSource.Log.ActivityRestored("123");
OpenTelemetrySdkEventSource.Log.ActivityStarted("Activity started", "1");
// Prepare the output for assertion
writer.Flush();
var outputString = writer.GetStringBuilder().ToString();
outputString.Should().NotContain("EventSource=OpenTelemetry-Instrumentation-AspNet-Telemetry, Message=Activity restored, Id='123'");
outputString.Should().NotContain("EventSource=OpenTelemetry-Sdk, Message=Activity started.");
// Cleanup
Console.SetOut(originalConsoleOut);
}
[Fact]
public void EventSourceSetup_HigherSeverity()
{
// Redirect the ConsoleLogger
var originalConsoleOut = Console.Out;
using var writer = new StringWriter();
Console.SetOut(writer);
var listener = new SdkSelfDiagnosticsEventListener(EventLevel.Verbose);
// Emitting a Verbose event. Or any EventSource event with lower severity than Error.
AspNetTelemetryEventSource.Log.ActivityRestored("123");
OpenTelemetrySdkEventSource.Log.ActivityStarted("Activity started", "1");
// Prepare the output for assertion
writer.Flush();
var outputString = writer.GetStringBuilder().ToString();
outputString.Should().Contain("EventSource=OpenTelemetry-Instrumentation-AspNet-Telemetry, Message=Activity restored, Id='123'");
outputString.Should().Contain("EventSource=OpenTelemetry-Sdk, Message=Activity started.");
// Cleanup
Console.SetOut(originalConsoleOut);
}
[EventSource(Name = "OpenTelemetry-Instrumentation-AspNet-Telemetry", Guid = "1de158cc-f7ce-4293-bd19-2358c93c8186")]
internal sealed class AspNetTelemetryEventSource : EventSource
{
public static readonly AspNetTelemetryEventSource Log = new();
[Event(4, Message = "Activity restored, Id='{0}'", Level = EventLevel.Informational)]
public void ActivityRestored(string id)
{
this.WriteEvent(4, id);
}
}
[EventSource(Name = "OpenTelemetry-Sdk")]
internal class OpenTelemetrySdkEventSource : EventSource
{
public static readonly OpenTelemetrySdkEventSource Log = new();
[Event(24, Message = "Activity started. OperationName = '{0}', Id = '{1}'.", Level = EventLevel.Verbose)]
public void ActivityStarted(string operationName, string id)
{
this.WriteEvent(24, operationName, id);
}
}
}