diff --git a/Felfel.Logging/Felfel.Logging.csproj b/Felfel.Logging/Felfel.Logging.csproj index 4627125..594be2d 100644 --- a/Felfel.Logging/Felfel.Logging.csproj +++ b/Felfel.Logging/Felfel.Logging.csproj @@ -15,9 +15,9 @@ https://github.com/felfel/logging-dotnet https://github.com/felfel/logging-dotnet http://gravatar.com/avatar/2ac36575f81dd95ffbebc8a6eb0315e5?s=400 - 1.0.15 - 1.0.15.0 - 1.0.15.0 + 1.0.19 + 1.0.19.0 + 1.0.19.0 diff --git a/Felfel.Logging/ILogger.cs b/Felfel.Logging/ILogger.cs new file mode 100644 index 0000000..27bb669 --- /dev/null +++ b/Felfel.Logging/ILogger.cs @@ -0,0 +1,40 @@ +namespace Felfel.Logging +{ + + /// + /// Primary façade for structured logging. + /// + /// Used to infer the . + public interface ILogger : ILogger + { + } + + + /// + /// Primary façade for structured logging. + /// + public interface ILogger + { + /// + /// Logger context, which will be part of the serialized data unless explicitly + /// set through the property. Identifies messages + /// that belong together (along with the at finer + /// granularity). + /// + string Context { get; } + + /// + /// Concatenates the the and the + /// of a log entry + /// in order to ensure a qualified (and unique) payload type name. + /// + bool PrefixPayloadType { get; set; } + + /// + /// Schedules a new log entry for logging. You probably should use one of the convenience + /// overloads such as Warn, Info or Debug that can be found + /// in . + /// + void Log(LogEntry entry); + } +} \ No newline at end of file diff --git a/Felfel.Logging/LogEntry.cs b/Felfel.Logging/LogEntry.cs index 2a44ab8..20d424f 100644 --- a/Felfel.Logging/LogEntry.cs +++ b/Felfel.Logging/LogEntry.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Felfel.Logging { @@ -55,5 +56,10 @@ public class LogEntry /// Optional exception information, if any. /// public Exception Exception { get; set; } + + /// + /// Allows framework level enrichment of log entires with additional properties. + /// + public Dictionary ContextData { get; } = new Dictionary(); } } \ No newline at end of file diff --git a/Felfel.Logging/LogEntryDto.cs b/Felfel.Logging/LogEntryDto.cs index b5c9009..8bb67b1 100644 --- a/Felfel.Logging/LogEntryDto.cs +++ b/Felfel.Logging/LogEntryDto.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Newtonsoft.Json; namespace Felfel.Logging @@ -70,6 +71,13 @@ public class LogEntryDto /// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public ExceptionInfo ExceptionInfo { get; set; } + + /// + /// Optional exception information, if any. Not rendered in JSON if no + /// exception was registered. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public Dictionary ContextData { get; set; } } diff --git a/Felfel.Logging/LogEntryParser.cs b/Felfel.Logging/LogEntryParser.cs index 95c29a1..02dfa70 100644 --- a/Felfel.Logging/LogEntryParser.cs +++ b/Felfel.Logging/LogEntryParser.cs @@ -56,7 +56,8 @@ public static LogEntryDto ParseLogEntry(LogEntry entry, string appName, string e Message = String.IsNullOrEmpty(message) ? null : message, PayloadType = entry.PayloadType, Payload = payload, - ExceptionInfo = exceptionInfo + ExceptionInfo = exceptionInfo, + ContextData = entry.ContextData.Count == 0 ? null : entry.ContextData }; } } diff --git a/Felfel.Logging/LogEntrySink.cs b/Felfel.Logging/LogEntrySink.cs index 756ffb1..0457275 100644 --- a/Felfel.Logging/LogEntrySink.cs +++ b/Felfel.Logging/LogEntrySink.cs @@ -56,22 +56,30 @@ protected virtual LogEntryDto ExtractLogEntry(LogEvent logEvent) if (logEntry == null) { - //something went wrong + //no log entry to unwrap - assume just a regular Serilog message that didn't come through the custom API + var logLevel = ParseLevel(logEvent.Level); logEntry = new LogEntry { TimestampOverride = logEvent.Timestamp, - LogLevel = LogLevel.Fatal, + LogLevel = logLevel, + Message = logEvent.RenderMessage(), Exception = logEvent.Exception, - Context = $"{nameof(HttpSink)}.Error", - Payload = new - { - Error = "Could not unwrap log entry.", - Message = logEvent.RenderMessage(), - Level = logEvent.Level.ToString() - } + Context = $"{AppName}.{logLevel}" }; } + //extract all other properties and add them to the context object + var props = logEvent.Properties.Where(p => !p.Key.Equals(Logger.EntryPropertyName)); + foreach (var prop in props) + { + var scalarValue = prop.Value as ScalarValue; + if (scalarValue != null) + { + //insert (override duplicate keys) + logEntry.ContextData[prop.Key] = scalarValue.Value; + } + } + var dto = LogEntryParser.ParseLogEntry(logEntry, AppName, Environment); return dto; } @@ -81,6 +89,24 @@ protected virtual LogEntryDto ExtractLogEntry(LogEvent logEvent) } } + private LogLevel ParseLevel(LogEventLevel level) + { + switch (level) + { + case LogEventLevel.Debug: + case LogEventLevel.Verbose: + return LogLevel.Debug; + case LogEventLevel.Information: + return LogLevel.Info; + case LogEventLevel.Warning: + return LogLevel.Warning; + case LogEventLevel.Error: + return LogLevel.Error; + default: + return LogLevel.Fatal; + } + } + /// /// Wraps an exception that occurred during logging into @@ -100,4 +126,4 @@ protected virtual LogEntryDto ProcessLoggingException(Exception e) }; } } -} \ No newline at end of file +} diff --git a/Felfel.Logging/Logger.cs b/Felfel.Logging/Logger.cs index 288bc0e..0734da7 100644 --- a/Felfel.Logging/Logger.cs +++ b/Felfel.Logging/Logger.cs @@ -5,13 +5,29 @@ namespace Felfel.Logging { + public class Logger : Logger, ILogger + { + /// + /// Creates a new logger instance using the type name + /// as the . + /// + /// If true, the + /// will be automatically prefixed with the logger's in order + /// to create a qualified payload name, which reduces the risk of potential + /// duplicates across app/service. + /// + public Logger(bool prefixPayloadType = true) : base(typeof(T).Name, prefixPayloadType) + { + } + } + /// /// Primary façade for structured logging. /// Having static builder methods is not a good practice, but will do for now given that /// we do not leverate dependency injection on our legacy infrastructure. You probably should not /// duplicate this pattern. /// - public class Logger + public class Logger : ILogger { internal const string EntryPropertyName = nameof(LogEntry); @@ -31,9 +47,9 @@ public class Logger /// of a log entry /// in order to ensure a qualified (and unique) payload type name. /// - public bool PrefixPayloadType { get; } + public bool PrefixPayloadType { get; set; } - private Logger(string context, bool prefixPayloadType) + protected Logger(string context, bool prefixPayloadType) { Context = context; PrefixPayloadType = prefixPayloadType; @@ -50,9 +66,9 @@ private Logger(string context, bool prefixPayloadType) /// to create a qualified payload name, which reduces the risk of potential /// duplicates across app/service. /// - public static Logger Create(bool prefixPayloadType = true) + public static ILogger Create(bool prefixPayloadType = true) { - return Create(typeof(T), prefixPayloadType); + return new Logger(prefixPayloadType); } /// @@ -67,9 +83,9 @@ public static Logger Create(bool prefixPayloadType = true) /// to create a qualified payload name, which reduces the risk of potential /// duplicates across app/service. /// - public static Logger Create(Type contextType, bool prefixPayloadType = true) + public static ILogger Create(Type contextType, bool prefixPayloadType = true) { - return Create(contextType.Name); + return Create(contextType.Name, prefixPayloadType); } /// @@ -83,7 +99,7 @@ public static Logger Create(Type contextType, bool prefixPayloadType = true) /// to create a qualified payload name, which reduces the risk of potential /// duplicates across app/service. /// - public static Logger Create(string context = "", bool prefixPayloadType = true) + public static ILogger Create(string context = "", bool prefixPayloadType = true) { return new Logger(context, prefixPayloadType); } diff --git a/Felfel.Logging/LoggerExtensions.cs b/Felfel.Logging/LoggerExtensions.cs index e6a9c2f..3f709fb 100644 --- a/Felfel.Logging/LoggerExtensions.cs +++ b/Felfel.Logging/LoggerExtensions.cs @@ -7,8 +7,7 @@ namespace Felfel.Logging /// public static class LoggerExtensions { - private static void WriteEntry(Logger logger, LogLevel level, string payloadType, object data, - Exception exception, string message) + private static void WriteEntry(ILogger logger, LogLevel level, string payloadType, object data, Exception exception, string message) { LogEntry entry = new LogEntry { @@ -22,102 +21,102 @@ private static void WriteEntry(Logger logger, LogLevel level, string payloadType logger.Log(entry); } - public static void Debug(this Logger logger, string message) + public static void Debug(this ILogger logger, string message) { WriteEntry(logger, LogLevel.Debug, null, null, null, message); } - public static void Debug(this Logger logger, string payloadType, object data, string message = null) + public static void Debug(this ILogger logger, string payloadType, object data, string message = null) { WriteEntry(logger, LogLevel.Debug, payloadType, data, null, message); } - public static void Debug(this Logger logger, Exception exception, string message = null) + public static void Debug(this ILogger logger, Exception exception, string message = null) { WriteEntry(logger, LogLevel.Debug, "", null, exception, message); } - public static void Debug(this Logger logger, Exception exception, string payloadType, object data, string message = null) + public static void Debug(this ILogger logger, Exception exception, string payloadType, object data, string message = null) { WriteEntry(logger, LogLevel.Debug, payloadType, data, exception, message); } - public static void Information(this Logger logger, string message) + public static void Information(this ILogger logger, string message) { WriteEntry(logger, LogLevel.Info, null, null, null, message); } - public static void Information(this Logger logger, string payloadType, object data, string message = null) + public static void Information(this ILogger logger, string payloadType, object data, string message = null) { WriteEntry(logger, LogLevel.Info, payloadType, data, null, message); } - public static void Information(this Logger logger, Exception exception, string message = null) + public static void Information(this ILogger logger, Exception exception, string message = null) { WriteEntry(logger, LogLevel.Info, "", null, exception, message); } - public static void Information(this Logger logger, Exception exception, string payloadType, object data, string message = null) + public static void Information(this ILogger logger, Exception exception, string payloadType, object data, string message = null) { WriteEntry(logger, LogLevel.Info, payloadType, data, exception, message); } - public static void Warning(this Logger logger, string message) + public static void Warning(this ILogger logger, string message) { WriteEntry(logger, LogLevel.Warning, null, null, null, message); } - public static void Warning(this Logger logger, string payloadType, object data, string message = null) + public static void Warning(this ILogger logger, string payloadType, object data, string message = null) { WriteEntry(logger, LogLevel.Warning, payloadType, data, null, message); } - public static void Warning(this Logger logger, Exception exception, string message = null) + public static void Warning(this ILogger logger, Exception exception, string message = null) { WriteEntry(logger, LogLevel.Warning, "", null, exception, message); } - public static void Warning(this Logger logger, Exception exception, string payloadType, object data, string message = null) + public static void Warning(this ILogger logger, Exception exception, string payloadType, object data, string message = null) { WriteEntry(logger, LogLevel.Warning, payloadType, data, exception, message); } - public static void Error(this Logger logger, string message) + public static void Error(this ILogger logger, string message) { WriteEntry(logger, LogLevel.Error, null, null, null, message); } - public static void Error(this Logger logger, string payloadType, object data, string message = null) + public static void Error(this ILogger logger, string payloadType, object data, string message = null) { WriteEntry(logger, LogLevel.Error, payloadType, data, null, message); } - public static void Error(this Logger logger, Exception exception, string message = null) + public static void Error(this ILogger logger, Exception exception, string message = null) { WriteEntry(logger, LogLevel.Error, "", null, exception, message); } - public static void Error(this Logger logger, Exception exception, string payloadType, object data, string message = null) + public static void Error(this ILogger logger, Exception exception, string payloadType, object data, string message = null) { WriteEntry(logger, LogLevel.Error, payloadType, data, exception, message); } - public static void Fatal(this Logger logger, string message) + public static void Fatal(this ILogger logger, string message) { WriteEntry(logger, LogLevel.Fatal, null, null, null, message); } - public static void Fatal(this Logger logger, string payloadType, object data, string message = null) + public static void Fatal(this ILogger logger, string payloadType, object data, string message = null) { WriteEntry(logger, LogLevel.Fatal, payloadType, data, null, message); } - public static void Fatal(this Logger logger, Exception exception, string message = null) + public static void Fatal(this ILogger logger, Exception exception, string message = null) { WriteEntry(logger, LogLevel.Fatal, "", null, exception, message); } - public static void Fatal(this Logger logger, Exception exception, string payloadType, object data, string message = null) + public static void Fatal(this ILogger logger, Exception exception, string payloadType, object data, string message = null) { WriteEntry(logger, LogLevel.Fatal, payloadType, data, exception, message); }