Skip to content

Commit

Permalink
Start Azure SDK log forwarder in AzMon OTel distro
Browse files Browse the repository at this point in the history
  • Loading branch information
lmolkova committed Mar 4, 2024
1 parent b48a30e commit a8fd665
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@
<!--<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" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,19 @@ public static OpenTelemetryBuilder UseAzureMonitor(this OpenTelemetryBuilder bui
azureMonitorOptions.Get(Options.DefaultName).SetValueToLiveMetricsExporterOptions(exporterOptions);
});

builder.Services.AddHostedService(sp =>
{
Type logForwarderType = Type.GetType("Microsoft.Extensions.Azure.AzureEventSourceLogForwarder, Microsoft.Extensions.Azure", false);
if (logForwarderType == null || sp.GetService(logForwarderType) == null)
{
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
return new AzureEventSourceLogForwarder(loggerFactory);
}
return AzureEventSourceLogForwarder.Noop;
});

return builder;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections;
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
{
internal sealed class AzureEventSourceLogForwarder : IHostedService
{
internal static readonly AzureEventSourceLogForwarder Noop = new AzureEventSourceLogForwarder();
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;

private AzureEventSourceLogForwarder()
{
_loggerFactory = null;
_listener = null;
}

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:
throw new ArgumentOutOfRangeException(nameof(level), level, null);
}
}

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

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;
}

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]);
}
}
}
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#if NET6_0_OR_GREATER
using System;
using System.Diagnostics.Tracing;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core.Diagnostics;
using Azure.Core.TestFramework;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Xunit;

namespace Azure.Monitor.OpenTelemetry.AspNetCore.Tests
{
public class AzureSdkLoggingTests
{
[Fact]
public async Task LogForwarderIsAddedLevelEnabled()
{
var builder = WebApplication.CreateBuilder();
builder.Logging.ClearProviders();
builder.Logging.AddFilter((name, level) =>
{
if (name != null && name.StartsWith("Azure"))
{
return level >= LogLevel.Information;
}
return false;
});

MockTransport transport = new MockTransport(new MockResponse(200).SetContent("ok"));
builder.Services.AddOpenTelemetry().UseAzureMonitor(config =>
{
config.Transport = transport;
config.ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000";
});

using var app = builder.Build();
await app.StartAsync();

using TestEventSource source = new TestEventSource();
try
{
Assert.True(source.IsEnabled());
source.LogTestInfoEvent("hello");

WaitForRequest(transport);
}
finally
{
await app.StopAsync();
}

Assert.True(transport.Requests.Count > 0);
}

[Fact]
public async Task PublicLogForwarderIsAdded()
{
var builder = WebApplication.CreateBuilder();
builder.Logging.ClearProviders();
builder.Logging.AddFilter((name, level) =>
{
if (name != null && name.StartsWith("Azure"))
{
return level >= LogLevel.Information;
}
return false;
});

builder.Services.TryAddSingleton<Microsoft.Extensions.Azure.AzureEventSourceLogForwarder>();
MockTransport transport = new MockTransport(new MockResponse(200).SetContent("ok"));
builder.Services.AddOpenTelemetry().UseAzureMonitor(config =>
{
config.Transport = transport;
config.ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000";
});

using var app = builder.Build();
Microsoft.Extensions.Azure.AzureEventSourceLogForwarder publicLogForwarder =
app.Services.GetRequiredService<Microsoft.Extensions.Azure.AzureEventSourceLogForwarder>();
Assert.NotNull(publicLogForwarder);
publicLogForwarder.Start();
await app.StartAsync();

using TestEventSource source = new TestEventSource();
try
{
Assert.True(source.IsEnabled());
source.LogTestInfoEvent("hello");

WaitForRequest(transport);
}
finally
{
await app.StopAsync();
}

Assert.True(transport.Requests.Count > 0);
}

private void WaitForRequest(MockTransport transport)
{
SpinWait.SpinUntil(
condition: () =>
{
Thread.Sleep(10);
return transport.Requests.Count > 0;
},
timeout: TimeSpan.FromSeconds(10));
}

internal class TestEventSource : AzureEventSource
{
private const string EventSourceName = "Azure-Test";
internal const int TestInfoEvent = 1;
internal const int TestTraceEvent = 2;
public TestEventSource() : base(EventSourceName)
{
}

[Event(TestInfoEvent, Level = EventLevel.Informational, Message = "TestInfoEvent: = {0}")]
public void LogTestInfoEvent(string message)
{
WriteEvent(TestInfoEvent, message);
}

[Event(TestTraceEvent, Level = EventLevel.Informational, Message = "TestTraceEvent: = {0}")]
public void LogTestTraceEvent(string message)
{
WriteEvent(TestTraceEvent, message);
}
}
}
}
#endif

0 comments on commit a8fd665

Please sign in to comment.