| File: XunitLoggerFactoryExtensions.cs | Web Access |
| Project: src\test\Libraries\Microsoft.Extensions.ServiceDiscovery.Dns.Tests\Microsoft.Extensions.ServiceDiscovery.Dns.Tests.csproj (Microsoft.Extensions.ServiceDiscovery.Dns.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Xunit.Abstractions; namespace Microsoft.Extensions.ServiceDiscovery.Dns.Tests; internal static class XunitLoggerFactoryExtensions { public static ILoggingBuilder AddXunit(this ILoggingBuilder builder, ITestOutputHelper output) { builder.Services.AddSingleton<ILoggerProvider>(new XunitLoggerProvider(output)); return builder; } public static IServiceCollection AddXunitLogging(this IServiceCollection services, ITestOutputHelper output) => services.AddLogging(b => b.AddXunit(output)); } internal class XunitLoggerProvider : ILoggerProvider { private readonly ITestOutputHelper _output; private readonly LogLevel _minLevel; private readonly DateTimeOffset? _logStart; public XunitLoggerProvider(ITestOutputHelper output) : this(output, LogLevel.Trace) { } public XunitLoggerProvider(ITestOutputHelper output, LogLevel minLevel) : this(output, minLevel, null) { } public XunitLoggerProvider(ITestOutputHelper output, LogLevel minLevel, DateTimeOffset? logStart) { _output = output; _minLevel = minLevel; _logStart = logStart; } public ILogger CreateLogger(string categoryName) { return new XunitLogger(_output, categoryName, _minLevel, _logStart); } public void Dispose() { } } internal class XunitLogger : ILogger { private static readonly string[] s_newLineChars = new[] { Environment.NewLine }; private readonly string _category; private readonly LogLevel _minLogLevel; private readonly ITestOutputHelper _output; private readonly DateTimeOffset? _logStart; public XunitLogger(ITestOutputHelper output, string category, LogLevel minLogLevel, DateTimeOffset? logStart) { _minLogLevel = minLogLevel; _category = category; _output = output; _logStart = logStart; } public void Log<TState>( LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) { if (!IsEnabled(logLevel)) { return; } // Buffer the message into a single string in order to avoid shearing the message when running across multiple threads. var messageBuilder = new StringBuilder(); var timestamp = _logStart.HasValue ? $"{(DateTimeOffset.UtcNow - _logStart.Value).TotalSeconds.ToString("N3", CultureInfo.InvariantCulture)}s" : DateTimeOffset.UtcNow.ToString("s", CultureInfo.InvariantCulture); var firstLinePrefix = $"| [{timestamp}] {_category} {logLevel}: "; var lines = formatter(state, exception).Split(s_newLineChars, StringSplitOptions.RemoveEmptyEntries); messageBuilder.AppendLine(firstLinePrefix + lines.FirstOrDefault() ?? string.Empty); var additionalLinePrefix = "|" + new string(' ', firstLinePrefix.Length - 1); foreach (var line in lines.Skip(1)) { messageBuilder.AppendLine(additionalLinePrefix + line); } if (exception != null) { lines = exception.ToString().Split(s_newLineChars, StringSplitOptions.RemoveEmptyEntries); additionalLinePrefix = "| "; foreach (var line in lines) { messageBuilder.AppendLine(additionalLinePrefix + line); } } // Remove the last line-break, because ITestOutputHelper only has WriteLine. var message = messageBuilder.ToString(); if (message.EndsWith(Environment.NewLine, StringComparison.Ordinal)) { message = message.Substring(0, message.Length - Environment.NewLine.Length); } try { _output.WriteLine(message); } catch (Exception) { // We could fail because we're on a background thread and our captured ITestOutputHelper is // busted (if the test "completed" before the background thread fired). // So, ignore this. There isn't really anything we can do but hope the // caller has additional loggers registered } } public bool IsEnabled(LogLevel logLevel) => logLevel >= _minLogLevel; public IDisposable BeginScope<TState>(TState state) where TState : notnull => new NullScope(); private sealed class NullScope : IDisposable { public void Dispose() { } } }