using Amazon.XRay.Recorder.Core;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
using System.Runtime.InteropServices;
namespace SampleWebApp.AppLogger;
///
/// Custom Console Logging Formatter to add AWS X-Ray TraceId as suffix for logs
///
public class XrayCustomFormatter : ConsoleFormatter, IDisposable
{
private bool isDisposed;
private readonly string _padding = " ";
private readonly IDisposable _optionsReloadToken;
private XrayCustomFormatterOptions _formatterOptions;
public XrayCustomFormatter(IOptionsMonitor options)
: base(nameof(XrayCustomFormatter)) =>
(_optionsReloadToken, _formatterOptions) =
(options.OnChange(ReloadLoggerOptions), options?.CurrentValue);
private void ReloadLoggerOptions(XrayCustomFormatterOptions options) =>
_formatterOptions = options;
public override void Write(
in LogEntry logEntry,
IExternalScopeProvider scopeProvider,
TextWriter textWriter)
{
string message =
logEntry.Formatter(
logEntry.State, logEntry.Exception);
if (message == null || textWriter == null)
{
return;
}
//textWriter.Write(DateTime.UtcNow.ToString(_formatterOptions.TimestampFormat) + " ");
textWriter.Write(logLevelString(logEntry.LogLevel));
textWriter.Write(":");
textWriter.Write(_padding);
textWriter.Write(message);
textWriter.Write(_padding);
WriteTraceIdSuffix(textWriter);
textWriter.Write(_padding);
// exception message
if (logEntry.Exception != null)
{
textWriter.Write("Exception: ");
string newMessage = logEntry.Exception
.ToString()?
.Replace(Environment.NewLine, " ", StringComparison.Ordinal);
textWriter.Write(newMessage);
}
textWriter.Write(Environment.NewLine);
}
private void WriteTraceIdSuffix(TextWriter textWriter)
{
if (_formatterOptions.EnableTraceIdInjection && AWSXRayRecorder.Instance.IsEntityPresent())
{
textWriter.Write($"TraceId: {AWSXRayRecorder.Instance?.GetEntity()?.TraceId}");
}
}
private static string logLevelString(LogLevel logLevel)
{
return logLevel switch
{
LogLevel.Trace => "trce",
LogLevel.Debug => "dbug",
LogLevel.Information => "info",
LogLevel.Warning => "warn",
LogLevel.Error => "fail",
LogLevel.Critical => "crit",
_ => throw new ArgumentOutOfRangeException(nameof(logLevel))
};
}
// Dispose() calls Dispose(true)
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// The bulk of the clean-up code is implemented in Dispose(bool)
protected virtual void Dispose(bool disposing)
{
if (isDisposed) return;
if (disposing)
{
_optionsReloadToken?.Dispose();
}
isDisposed = true;
}
}