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