Skip to content

Commit

Permalink
Start Azure SDK log forwarder in AzMon OTel distro (#42374)
Browse files Browse the repository at this point in the history
* Start Azure SDK log forwarder in AzMon OTel distro
  • Loading branch information
lmolkova committed Mar 6, 2024
1 parent 289b568 commit a4a3108
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 38 deletions.
75 changes: 75 additions & 0 deletions sdk/core/Azure.Core/src/Shared/EventSourceEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Tracing;

#nullable enable

namespace Azure.Core.Shared
{
/// <summary>
/// Wraps <see cref="EventWrittenEventArgs"/> into <see cref="IReadOnlyList{T}"/> simplifying iterating over
/// payload properties and providing them to logging libraries in a structured way.
/// </summary>
internal readonly struct EventSourceEvent : IReadOnlyList<KeyValuePair<string, object?>>
{
/// <summary>
/// Gets underlying EventSource event.
/// </summary>
public EventWrittenEventArgs EventData { get; }

public EventSourceEvent(EventWrittenEventArgs eventData)
{
EventData = eventData;
}

/// <inheritdoc />
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator()
{
if (EventData.PayloadNames == null || EventData.Payload == null)
{
yield break;
}

for (int i = 0; i < Count; i++)
{
yield return new KeyValuePair<string, object?>(EventData.PayloadNames[i], EventData.Payload[i]);
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

/// <summary>
/// Returns the count of payload properties in the Eventsource event.
/// </summary>
public int Count => EventData.PayloadNames?.Count ?? 0;

/// <summary>
/// Formats EventSource event as a string including all payload properties.
/// </summary>
public string Format()
{
return EventSourceEventFormatting.Format(EventData);
}

/// <inheritdoc />
public KeyValuePair<string, object?> this[int index]
{
get
{
if (EventData.PayloadNames == null || EventData.Payload == null || index >= EventData.PayloadNames.Count || index < 0)
{
throw new IndexOutOfRangeException("Index was out of range.");
}

return new KeyValuePair<string, object?>(EventData.PayloadNames[index], EventData.Payload[index]);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using Azure.Core.Diagnostics;
using Azure.Core.Shared;
Expand Down Expand Up @@ -85,36 +83,6 @@ private static LogLevel MapLevel(EventLevel level)
}
}

private static string FormatMessage(EventSourceEvent eventSourceEvent, Exception exception)
{
return EventSourceEventFormatting.Format(eventSourceEvent.EventData);
}

private readonly struct EventSourceEvent: IReadOnlyList<KeyValuePair<string, object>>
{
public EventWrittenEventArgs EventData { get; }

public EventSourceEvent(EventWrittenEventArgs eventData)
{
EventData = eventData;
}

public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return new KeyValuePair<string, object>(EventData.PayloadNames[i], EventData.Payload[i]);
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

public int Count => EventData.PayloadNames.Count;

public KeyValuePair<string, object> this[int index] => new KeyValuePair<string, object>(EventData.PayloadNames[index], EventData.Payload[index]);
}
private static string FormatMessage(EventSourceEvent eventSourceEvent, Exception _) => eventSourceEvent.Format();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
</ItemGroup>

<ItemGroup>
<Compile Include="$(AzureCoreSharedSources)EventSourceEvent.cs" LinkBase="Shared\Core" />
<Compile Include="$(AzureCoreSharedSources)EventSourceEventFormatting.cs" LinkBase="Shared\Core" />
<Compile Include="$(AzureCoreSharedSources)TrimmingAttribute.cs" LinkBase="Shared\Core" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>An OpenTelemetry .NET distro that exports to Azure Monitor</Description>
<AssemblyTitle>AzureMonitor OpenTelemetry ASP.NET Core Distro</AssemblyTitle>
Expand Down Expand Up @@ -30,10 +30,12 @@
<!--<ProjectReference Include="..\..\Azure.Monitor.OpenTelemetry.Exporter\src\Azure.Monitor.OpenTelemetry.Exporter.csproj" />-->
<!--<ProjectReference Include="..\..\Azure.Monitor.OpenTelemetry.LiveMetrics\src\Azure.Monitor.OpenTelemetry.LiveMetrics.csproj" />-->
</ItemGroup>
<!-- Shared source from Exporter -->

<!-- Shared sources -->
<ItemGroup>
<Compile Include="..\..\Azure.Monitor.OpenTelemetry.Exporter\src\Internals\ExceptionExtensions.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)EventSourceEventFormatting.cs" LinkBase="Shared\Core" />
<Compile Include="$(AzureCoreSharedSources)EventSourceEvent.cs" LinkBase="Shared\Core" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics.Tracing;
using System.Runtime.CompilerServices;
using Azure.Monitor.OpenTelemetry.Exporter.Internals;
Expand Down Expand Up @@ -60,6 +59,15 @@ public void GetEnvironmentVariableFailed(string envVarName, Exception ex)
}
}

[NonEvent]
public void MapLogLevelFailed(EventLevel level)
{
if (IsEnabled(EventLevel.Warning))
{
MapLogLevelFailed(level.ToString());
}
}

[Event(1, Message = "Failed to configure AzureMonitorOptions using the connection string from environment variables due to an exception: {0}", Level = EventLevel.Error)]
public void ConfigureFailed(string exceptionMessage) => WriteEvent(1, exceptionMessage);

Expand All @@ -74,5 +82,11 @@ public void GetEnvironmentVariableFailed(string envVarName, Exception ex)

[Event(5, Message = "Failed to Read environment variable {0}, exception: {1}", Level = EventLevel.Error)]
public void GetEnvironmentVariableFailed(string envVarName, string exceptionMessage) => WriteEvent(5, envVarName, exceptionMessage);

[Event(6, Message = "Failed to map unknown EventSource log level in AzureEventSourceLogForwarder {0}", Level = EventLevel.Warning)]
public void MapLogLevelFailed(string level) => WriteEvent(6, level);

[Event(7, Message = "Found existing Microsoft.Extensions.Azure.AzureEventSourceLogForwarder registration.", Level = EventLevel.Informational)]
public void LogForwarderIsAlreadyRegistered() => WriteEvent(7);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Concurrent;
using System.Diagnostics.Tracing;
using Azure.Core.Diagnostics;
using Azure.Core.Shared;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Azure.Monitor.OpenTelemetry.AspNetCore.Internals.AzureSdkCompat
{
internal sealed class AzureEventSourceLogForwarder : IHostedService, IDisposable
{
internal static readonly AzureEventSourceLogForwarder Noop = new AzureEventSourceLogForwarder(null);
private readonly ILoggerFactory _loggerFactory;

private readonly ConcurrentDictionary<string, ILogger> _loggers = new ConcurrentDictionary<string, ILogger>();

private readonly Func<EventSourceEvent, Exception, string> _formatMessage = FormatMessage;

private AzureEventSourceListener _listener;

public AzureEventSourceLogForwarder(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}

private void LogEvent(EventWrittenEventArgs eventData)
{
var logger = _loggers.GetOrAdd(eventData.EventSource.Name, name => _loggerFactory!.CreateLogger(ToLoggerName(name)));
logger.Log(MapLevel(eventData.Level), new EventId(eventData.EventId, eventData.EventName), new EventSourceEvent(eventData), null, _formatMessage);
}

private static string ToLoggerName(string name)
{
return name.Replace('-', '.');
}

private static LogLevel MapLevel(EventLevel level)
{
switch (level)
{
case EventLevel.Critical:
return LogLevel.Critical;
case EventLevel.Error:
return LogLevel.Error;
case EventLevel.Informational:
return LogLevel.Information;
case EventLevel.Verbose:
return LogLevel.Debug;
case EventLevel.Warning:
return LogLevel.Warning;
case EventLevel.LogAlways:
return LogLevel.Information;
default:
AzureMonitorAspNetCoreEventSource.Log.MapLogLevelFailed(level);
return LogLevel.None;
}
}

private static string FormatMessage(EventSourceEvent eventSourceEvent, Exception _) => eventSourceEvent.Format();

public Task StartAsync(CancellationToken cancellationToken)
{
if (_loggerFactory != null)
{
_listener ??= new AzureEventSourceListener((e, s) => LogEvent(e), EventLevel.Verbose);
}

return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
_listener?.Dispose();
return Task.CompletedTask;
}

public void Dispose()
{
_listener?.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#nullable enable

using System.Reflection;
using Azure.Monitor.OpenTelemetry.AspNetCore.Internals.AzureSdkCompat;
using Azure.Monitor.OpenTelemetry.AspNetCore.Internals.Profiling;
using Azure.Monitor.OpenTelemetry.Exporter;
using Azure.Monitor.OpenTelemetry.LiveMetrics;
Expand Down Expand Up @@ -184,6 +185,21 @@ public static OpenTelemetryBuilder UseAzureMonitor(this OpenTelemetryBuilder bui
azureMonitorOptions.Get(Options.DefaultName).SetValueToLiveMetricsExporterOptions(exporterOptions);
});

// Register Azure SDK log forwarder in the case it was not registered by the user application.
builder.Services.AddHostedService(sp =>
{
var logForwarderType = Type.GetType("Microsoft.Extensions.Azure.AzureEventSourceLogForwarder, Microsoft.Extensions.Azure", false);
if (logForwarderType != null && sp.GetService(logForwarderType) != null)
{
AzureMonitorAspNetCoreEventSource.Log.LogForwarderIsAlreadyRegistered();
return AzureEventSourceLogForwarder.Noop;
}
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
return new AzureEventSourceLogForwarder(loggerFactory);
});

return builder;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Azure" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
Expand All @@ -17,6 +18,7 @@
</ItemGroup>

<ItemGroup>
<Compile Include="$(AzureCoreSharedSources)AzureEventSource.cs" LinkBase="SharedSource\Azure.Core" />
<Compile Include="..\..\..\Azure.Monitor.OpenTelemetry.Exporter\tests\Azure.Monitor.OpenTelemetry.Exporter.Tests\CommonTestFramework\EventSourceTestHelper.cs" LinkBase="CommonTestFramework" />
<Compile Include="..\..\..\Azure.Monitor.OpenTelemetry.Exporter\tests\Azure.Monitor.OpenTelemetry.Exporter.Tests\CommonTestFramework\TestEventListener.cs" LinkBase="CommonTestFramework" />
</ItemGroup>
Expand Down
Loading

0 comments on commit a4a3108

Please sign in to comment.