Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start Azure SDK log forwarder in AzMon OTel distro #42374

Merged
merged 9 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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?>>
lmolkova marked this conversation as resolved.
Show resolved Hide resolved
{
/// <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());
}
}

TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
[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)));
lmolkova marked this conversation as resolved.
Show resolved Hide resolved
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);
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading