mojmir.novak
mojmir.novak

Reputation: 3439

Serilog - how to make custom console output format?

I am using Serilog with serilog-sinks-console in my C# project and I am wondering how can I modify format of console output without creating whole new formatter. I just need to adjust one thing info java (slf4j/logback) like format.

From this:

00:19:49 [DBG] Microsoft.AspNetCore.Hosting.Internal.WebHost - App starting
00:19:49 [DBG] Microsoft.AspNetCore.Hosting.Internal.WebHost - App started

into this:

00:19:49 [DBG] m.a.h.i.WebHost - App starting
00:19:49 [DBG] m.a.h.i.WebHost - App started

or just this simple format:

00:19:49 [DBG] WebHost - App starting
00:19:49 [DBG] WebHost - App started

Upvotes: 9

Views: 7559

Answers (3)

CleanUp
CleanUp

Reputation: 420

For those who would like the abbreviated variant where Microsoft.AspNetCore.Hosting.Internal.WebHost becomes M.A.H.Internal.WebHost (depending on the configured target length) the original logback implementation can be found here: https://github.com/qos-ch/logback/blob/master/logback-classic/src/main/java/ch/qos/logback/classic/pattern/TargetLengthBasedClassNameAbbreviator.java

Directly converted to a Serilog enricher that would be:

public class AbbreviatedSourceContextEnricher : ILogEventEnricher {
  private readonly int _targetLength;

  public AbbreviatedSourceContextEnricher( int targetLength ) {
    _targetLength = targetLength;
  }

  public void Enrich( LogEvent logEvent, ILogEventPropertyFactory propertyFactory ) {
    var typeName = logEvent.Properties.GetValueOrDefault( "SourceContext" ).ToString();
    typeName = typeName.Substring( 1, typeName.Length - 2 );
    // If you prefer to not override the SourceContext property you can create a new property:
    // logEvent.AddOrUpdateProperty( propertyFactory.CreateProperty( "AbbrSourceContext", Abbreviate( typeName ) ) );
    logEvent.AddOrUpdateProperty( propertyFactory.CreateProperty( "SourceContext", Abbreviate( typeName ) ) );
  }

  private string Abbreviate( string fqClassName ) {
    if ( fqClassName == null ) {
      throw new ArgumentNullException( nameof(fqClassName) );
    }

    var inLen = fqClassName.Length;
    if ( inLen < _targetLength ) {
      return fqClassName;
    }

    var buf = new StringBuilder( inLen );

    var rightMostDotIndex = fqClassName.LastIndexOf( "." );

    if ( rightMostDotIndex == -1 )
      return fqClassName;

    // length of last segment including the dot
    var lastSegmentLength = inLen - rightMostDotIndex;

    var leftSegments_TargetLen = _targetLength - lastSegmentLength;
    if ( leftSegments_TargetLen < 0 )
      leftSegments_TargetLen = 0;

    var leftSegmentsLen = inLen - lastSegmentLength;

    // maxPossibleTrim denotes the maximum number of characters we aim to trim
    // the actual number of character trimmed may be higher since segments, when
    // reduced, are reduced to just one character
    var maxPossibleTrim = leftSegmentsLen - leftSegments_TargetLen;

    var trimmed = 0;
    var inDotState = true;

    var i = 0;
    for ( ; i < rightMostDotIndex; i++ ) {
      char c = fqClassName.ToCharArray()[i];
      if ( c == '.' ) {
        // if trimmed too many characters, let us stop
        if ( trimmed >= maxPossibleTrim )
          break;
        buf.Append( c );
        inDotState = true;
      }
      else {
        if ( inDotState ) {
          buf.Append( c );
          inDotState = false;
        }
        else {
          trimmed++;
        }
      }
    }

    // append from the position of i which may include the last seen DOT
    buf.Append( fqClassName[i..] );
    return buf.ToString();
  }
}

Upvotes: 0

Ruben Bartelink
Ruben Bartelink

Reputation: 61875

F# riff on Mojmir's answer (HT @Kostas Rontogiannis):

    /// Converts SourceContext which is the fully qualified type name to a short version, using just the type name.
    type SourceContextShortEnricher () =
        interface Serilog.Core.ILogEventEnricher with
            member __.Enrich(logEvent : Serilog.Events.LogEvent, lepf : Serilog.Core.ILogEventPropertyFactory) =
                match logEvent.Properties.TryGetValue "SourceContext" with
                | true, (:? Serilog.Events.ScalarValue as v) when v <> null && v.Value <> null ->
                    let typeName =
                        string v.Value
                        |> fun s -> match s.LastIndexOf("[[") with -1 -> s | pos -> s.Substring(0, pos)
                        |> fun s -> match s.LastIndexOf('.') with -1 -> s | idx when s.Length = idx - 1 -> s | idx -> s.Substring(idx + 1)
                    logEvent.AddPropertyIfAbsent(lepf.CreateProperty("SourceContextShort", typeName))
                    logEvent.RemovePropertyIfPresent "SourceContext"
                | _ -> ()

Upvotes: 0

mojmir.novak
mojmir.novak

Reputation: 3439

Thanks for the direction to @Ruben Bartelink. If anyone else will be wondering how to do such thing here is the simple example:

Enricher:

class SimpleClassEnricher : ILogEventEnricher
{
  public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
  {
    var typeName = logEvent.Properties.GetValueOrDefault("SourceContext").ToString();
    var pos = typeName.LastIndexOf('.');
    typeName = typeName.Substring(pos + 1, typeName.Length - pos - 2);
    logEvent.AddOrUpdateProperty(propertyFactory.CreateProperty("SourceContext", typeName));
  }
}

then usage:

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .Enrich.With(new SimpleClassEnricher())
    .WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}")
    .CreateLogger();

Upvotes: 11

Related Questions