// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.SignalR.Tests { /// /// A logger factory that will prepend the current SignalR connection ID to the message. /// public class WrappingLoggerFactory : ILoggerFactory { private readonly ILoggerFactory _innerLoggerFactory; private readonly DummyProvider _provider; public WrappingLoggerFactory(ILoggerFactory innerLoggerFactory) { _innerLoggerFactory = innerLoggerFactory; _provider = new DummyProvider(); AddProvider(_provider); } public void Dispose() { _innerLoggerFactory.Dispose(); } public ILogger CreateLogger(string categoryName) { return new WrappingLogger(_provider, _innerLoggerFactory.CreateLogger(categoryName)); } public void AddProvider(ILoggerProvider provider) { _innerLoggerFactory.AddProvider(provider); } private class DummyProvider : ILoggerProvider, ISupportExternalScope { public IExternalScopeProvider ScopeProvider { get; private set; } public void Dispose() { } public ILogger CreateLogger(string categoryName) { return NullLogger.Instance; } public void SetScopeProvider(IExternalScopeProvider scopeProvider) { ScopeProvider = scopeProvider; } } private class WrappingLogger : ILogger { private readonly DummyProvider _provider; private readonly ILogger _logger; public WrappingLogger(DummyProvider provider, ILogger logger) { _provider = provider; _logger = logger; } public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { // Build the message outside of the formatter // Serilog doesn't appear to use the formatter and just writes the state var connectionId = GetConnectionId(); var sb = new StringBuilder(); if (connectionId != null) { sb.Append(connectionId + " - "); } sb.Append(formatter(state, exception)); var message = sb.ToString(); _logger.Log(logLevel, eventId, message, exception, (s, ex) => s); } public bool IsEnabled(LogLevel logLevel) { return _logger.IsEnabled(logLevel); } public IDisposable BeginScope(TState state) { return _logger.BeginScope(state); } private string GetConnectionId() { string connectionId = null; _provider.ScopeProvider?.ForEachScope((scope, s) => { if (scope is IReadOnlyList> logScope) { if (logScope.FirstOrDefault(kv => kv.Key == "TransportConnectionId" || kv.Key == "ClientConnectionId").Value is string id) { connectionId = id; } } }, null); return connectionId; } } } }