-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Start Azure SDK log forwarder in AzMon OTel distro
- Loading branch information
Showing
5 changed files
with
275 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
...itor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/AzureEventSourceLogForwarder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 140 additions & 0 deletions
140
...try.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Tests/AzureSdkLoggingTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |