// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; namespace EpicGames.Core { /// /// Class to apply a log indent for the lifetime of an object /// public interface ILoggerIndent { /// /// The indent to apply /// string Indent { get; } } /// /// Wrapper class for ILogger classes which supports LoggerStatusScope /// public class DefaultLoggerIndentHandler : ILogger { /// /// Scoped indent message /// class Scope : IDisposable { /// /// Owning object /// readonly DefaultLoggerIndentHandler _owner; /// /// The indent scope object /// public ILoggerIndent Indent { get; } /// /// Constructor /// public Scope(DefaultLoggerIndentHandler owner, ILoggerIndent indent) { _owner = owner; Indent = indent; lock (owner._scopes) { owner._scopes.Add(this); } } /// /// Remove this indent from the list /// public void Dispose() { lock (_owner._scopes) { _owner._scopes.Remove(this); } } } /// /// Struct to wrap a formatted set of log values with applied indent /// /// Arbitrary type parameter readonly struct FormattedLogValues : IEnumerable> { /// /// The indent to apply /// readonly string _indent; /// /// The inner state /// readonly TState _state; /// /// Formatter for the inner state /// readonly Func _formatter; /// /// Constructor /// /// The indent to apply /// The inner state /// Formatter for the inner state public FormattedLogValues(string indent, TState state, Func formatter) { _indent = indent; _state = state; _formatter = formatter; } /// public IEnumerator> GetEnumerator() { IEnumerable>? innerEnumerable = _state as IEnumerable>; if (innerEnumerable != null) { foreach (KeyValuePair pair in innerEnumerable) { if (pair.Key.Equals("{OriginalFormat}", StringComparison.Ordinal)) { yield return new KeyValuePair(pair.Key, _indent + pair.Value.ToString()); } else { yield return pair; } } } } /// IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } /// /// Formats an instance of this object /// /// The object instance /// The exception to format /// The formatted string public static string Format(FormattedLogValues values, Exception? exception) { return values._indent + values._formatter(values._state, exception); } } /// /// The internal logger /// readonly ILogger _inner; /// /// Current list of indents /// readonly List _scopes = []; /// /// The inner logger /// public ILogger Inner => _inner; /// /// The current indent text /// public string Indent { get; private set; } /// /// Constructor /// /// The logger to wrap public DefaultLoggerIndentHandler(ILogger inner) { _inner = inner; Indent = ""; } /// public IDisposable? BeginScope(TState state) where TState : notnull { ILoggerIndent? indent = state as ILoggerIndent; if (indent != null) { return new Scope(this, indent); } return _inner.BeginScope(state); } /// public bool IsEnabled(LogLevel logLevel) { return _inner.IsEnabled(logLevel); } /// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { if (_scopes.Count > 0) { string indent = String.Join("", _scopes.Select(x => x.Indent.Indent)); _inner.Log(logLevel, eventId, new FormattedLogValues(indent, state, formatter), exception, FormattedLogValues.Format); return; } _inner.Log(logLevel, eventId, state, exception, formatter); } } /// /// Extension methods for creating an indent /// public static class LoggerIndentExtensions { /// /// Class to apply a log indent for the lifetime of an object /// class LoggerIndent : ILoggerIndent { /// /// The previous indent /// public string Indent { get; } /// /// Constructor /// /// Indent to append to the existing indent public LoggerIndent(string indent) { Indent = indent; } } /// /// Create an indent /// /// Logger interface /// The indent to apply /// Disposable object public static IDisposable? BeginIndentScope(this ILogger logger, string indent) { return logger.BeginScope(new LoggerIndent(indent)); } } }