From ff79c9eeaac9ed8c9344531b5cdf07bb117f1c11 Mon Sep 17 00:00:00 2001 From: Miguel Alho Date: Tue, 4 Apr 2017 17:29:10 +0100 Subject: [PATCH 1/3] support formatProvider in DurableLogglySink This is especially important for DateSerialization (and pretty much only affects Date Serialization for now Sample has been updated to support this. Resharper based code inspection cleanup performed --- sample/sampleDurableLogger/Program.cs | 38 ++++++++++++++++--- .../Properties/AssemblyInfo.cs | 1 - .../LoggerConfigurationLogglyExtensions.cs | 5 ++- .../Sinks/Loggly/DurableLogglySink.cs | 5 ++- .../Sinks/Loggly/HttpLogShipper.cs | 2 +- .../Sinks/Loggly/LogEventConverter.cs | 3 +- .../Sinks/Loggly/LogglyFormatter.cs | 9 ++++- .../Sinks/Loggly/LogglyPropertyFormatter.cs | 29 +++++++++----- 8 files changed, 69 insertions(+), 23 deletions(-) diff --git a/sample/sampleDurableLogger/Program.cs b/sample/sampleDurableLogger/Program.cs index 93412ac..916e275 100644 --- a/sample/sampleDurableLogger/Program.cs +++ b/sample/sampleDurableLogger/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Loggly; using Loggly.Config; using Serilog; @@ -25,7 +26,7 @@ public static void Main(string[] args) Console.ReadLine(); logger.Information("Second test message"); - logger.Warning("Second test message with {@Data}", new {P1 = "sample2", P2 = DateTime.Now}); + logger.Warning("Second test message with {@Data}", new {P1 = "sample2", P2 = DateTime.Now, P3 = DateTime.UtcNow, P4 = DateTimeOffset.Now, P5 = DateTimeOffset.UtcNow}); Console.WriteLine( @@ -54,15 +55,17 @@ static Logger CreateLogger(string logFilePath) .Enrich.With(new PropertyEnricher("Environment", "development")) //Add sinks .WriteTo.Async(s => s.Loggly( - bufferBaseFilename: logFilePath + "buffer") + bufferBaseFilename: logFilePath + "buffer", + formatProvider: CreateLoggingCulture()) .MinimumLevel.Information() ) .WriteTo.Async(s => s.RollingFileAlternate( logFilePath, outputTemplate: - "[{ProcessId}] {Timestamp:yyyy-MM-dd HH:mm:ss.fff K} [{ThreadId}] [{Level}] [{SourceContext}] [{Category}] {Message}{NewLine}{Exception}", + "[{ProcessId}] {Timestamp} [{ThreadId}] [{Level}] [{SourceContext}] [{Category}] {Message}{NewLine}{Exception}", fileSizeLimitBytes: 10 * 1024 * 1024, - retainedFileCountLimit: 100) + retainedFileCountLimit: 100, + formatProvider: CreateLoggingCulture()) .MinimumLevel.Debug() ) .CreateLogger(); @@ -71,7 +74,7 @@ static Logger CreateLogger(string logFilePath) static void SetupLogglyConfiguration() { - ///CHANGE THESE TWO TO YOUR LOGGLY ACCOUNT: DO NOT COMMIT TO Source control!!! + //CHANGE THESE TWO TO YOUR LOGGLY ACCOUNT: DO NOT COMMIT TO Source control!!! const string appName = "AppNameHere"; const string customerToken = "yourkeyhere"; @@ -93,5 +96,30 @@ static void SetupLogglyConfiguration() new HostnameTag { Formatter = "host-{0}" } }); } + + static CultureInfo CreateLoggingCulture() + { + var loggingCulture = new CultureInfo(""); + + //with this DateTime and DateTimeOffset string representations will be sortable. By default, + // serialization without a culture or formater will use InvariantCulture. This may or may not be + // desirable, depending on the sorting needs you require or even the region your in. In this sample + // the invariant culture is used as a base, but the DateTime format is changed to a specific representation. + // Instead of the dd/MM/yyyy hh:mm:ss, we'll force yyyy-MM-dd HH:mm:ss.fff which is sortable and obtainable + // by overriding ShortDatePattern and LongTimePattern. + // + //Do note that they don't include the TimeZone by default, so a datetime will not have the TZ + // while a DateTimeOffset will in it's string representation. + // Both use the longTimePattern for time formatting, but including the time zone in the + // pattern will duplicate the TZ representation when using DateTimeOffset which serilog does + // for the timestamp. + // + //If you do not require specific formats, this method will not be required. Just pass in null (the default) + // for IFormatProvider in the Loggly() sink configuration method. + loggingCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-dd"; + loggingCulture.DateTimeFormat.LongTimePattern = "HH:mm:ss.fff"; + + return loggingCulture; + } } } diff --git a/sample/sampleDurableLogger/Properties/AssemblyInfo.cs b/sample/sampleDurableLogger/Properties/AssemblyInfo.cs index c15a5f1..7578df7 100644 --- a/sample/sampleDurableLogger/Properties/AssemblyInfo.cs +++ b/sample/sampleDurableLogger/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/Serilog.Sinks.Loggly/LoggerConfigurationLogglyExtensions.cs b/src/Serilog.Sinks.Loggly/LoggerConfigurationLogglyExtensions.cs index 2662ace..b3a2d87 100644 --- a/src/Serilog.Sinks.Loggly/LoggerConfigurationLogglyExtensions.cs +++ b/src/Serilog.Sinks.Loggly/LoggerConfigurationLogglyExtensions.cs @@ -61,7 +61,7 @@ public static LoggerConfiguration Loggly( LoggingLevelSwitch controlLevelSwitch = null, long? retainedInvalidPayloadsLimitBytes = null) { - if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration"); + if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration)); if (bufferFileSizeLimitBytes.HasValue && bufferFileSizeLimitBytes < 0) throw new ArgumentOutOfRangeException(nameof(bufferFileSizeLimitBytes), "Negative value provided; file size limit must be non-negative."); @@ -82,7 +82,8 @@ public static LoggerConfiguration Loggly( bufferFileSizeLimitBytes, eventBodyLimitBytes, controlLevelSwitch, - retainedInvalidPayloadsLimitBytes); + retainedInvalidPayloadsLimitBytes, + formatProvider); } return loggerConfiguration.Sink(sink, restrictedToMinimumLevel); diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/DurableLogglySink.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/DurableLogglySink.cs index 5b02591..6b619df 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/DurableLogglySink.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/DurableLogglySink.cs @@ -32,7 +32,8 @@ public DurableLogglySink( long? bufferFileSizeLimitBytes, long? eventBodyLimitBytes, LoggingLevelSwitch levelControlSwitch, - long? retainedInvalidPayloadsLimitBytes) + long? retainedInvalidPayloadsLimitBytes, + IFormatProvider formatProvider = null) { if (bufferBaseFilename == null) throw new ArgumentNullException(nameof(bufferBaseFilename)); @@ -48,7 +49,7 @@ public DurableLogglySink( //writes events to the file to support connection recovery _sink = new RollingFileSink( bufferBaseFilename + "-{Date}.json", - new LogglyFormatter(), //serializes as LogglyEvent + new LogglyFormatter(formatProvider), //serializes as LogglyEvent bufferFileSizeLimitBytes, null, Encoding.UTF8); diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs index 7860442..b0114e0 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs @@ -47,7 +47,7 @@ class HttpLogShipper : IDisposable readonly object _stateLock = new object(); readonly PortableTimer _timer; - ControlledLevelSwitch _controlledSwitch; + readonly ControlledLevelSwitch _controlledSwitch; DateTime _nextRequiredLevelCheckUtc = DateTime.UtcNow.Add(RequiredLevelCheckInterval); volatile bool _unloading; diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogEventConverter.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogEventConverter.cs index 3d6acf2..b45e249 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogEventConverter.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogEventConverter.cs @@ -27,12 +27,13 @@ public LogglyEvent CreateLogglyEvent(LogEvent logEvent) var isHttpTransport = LogglyConfig.Instance.Transport.LogTransport == LogTransport.Https; logglyEvent.Syslog.Level = ToSyslogLevel(logEvent); + logglyEvent.Data.AddIfAbsent("Message", logEvent.RenderMessage(_formatProvider)); foreach (var key in logEvent.Properties.Keys) { var propertyValue = logEvent.Properties[key]; - var simpleValue = LogglyPropertyFormatter.Simplify(propertyValue); + var simpleValue = LogglyPropertyFormatter.Simplify(propertyValue, _formatProvider); logglyEvent.Data.AddIfAbsent(key, simpleValue); } diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyFormatter.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyFormatter.cs index 366eac8..52dd9c9 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyFormatter.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyFormatter.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.IO; using Newtonsoft.Json; using Serilog.Events; @@ -25,8 +26,14 @@ namespace Serilog.Sinks.Loggly class LogglyFormatter : ITextFormatter { readonly JsonSerializer _serializer = JsonSerializer.Create(); - readonly LogEventConverter _converter = new LogEventConverter(null); + readonly LogEventConverter _converter; + public LogglyFormatter(IFormatProvider formatProvider) + { + //the converter should receive the format provider used, in order to + // handle dateTimes and dateTimeOffsets in a controlled manner + _converter = new LogEventConverter(formatProvider); + } public void Format(LogEvent logEvent, TextWriter output) { //Serializing the LogglyEvent means we can work with it from here on out and diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyPropertyFormatter.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyPropertyFormatter.cs index 8d07a92..bf50891 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyPropertyFormatter.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyPropertyFormatter.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Serilog.Debugging; using Serilog.Events; @@ -32,19 +33,20 @@ static class LogglyPropertyFormatter typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal), typeof(byte[]) - }; + }; /// /// Simplify the object so as to make handling the serialized /// representation easier. /// /// The value to simplify (possibly null). + /// A formatProvider to format DateTime values if a specific format is required. /// A simplified representation. - public static object Simplify(LogEventPropertyValue value) + public static object Simplify(LogEventPropertyValue value, IFormatProvider formatProvider) { var scalar = value as ScalarValue; if (scalar != null) - return SimplifyScalar(scalar.Value); + return SimplifyScalar(scalar.Value, formatProvider); var dict = value as DictionaryValue; if (dict != null) @@ -52,30 +54,30 @@ public static object Simplify(LogEventPropertyValue value) var result = new Dictionary(); foreach (var element in dict.Elements) { - var key = SimplifyScalar(element.Key.Value); + var key = SimplifyScalar(element.Key.Value, formatProvider); if (result.ContainsKey(key)) { SelfLog.WriteLine("The key {0} is not unique in the provided dictionary after simplification to {1}.", element.Key, key); return dict.Elements.Select(e => new Dictionary { - { "Key", SimplifyScalar(e.Key.Value) }, - { "Value", Simplify(e.Value) } + { "Key", SimplifyScalar(e.Key.Value, formatProvider) }, + { "Value", Simplify(e.Value, formatProvider) } }) .ToArray(); } - result.Add(key, Simplify(element.Value)); + result.Add(key, Simplify(element.Value, formatProvider)); } return result; } var seq = value as SequenceValue; if (seq != null) - return seq.Elements.Select(Simplify).ToArray(); + return seq.Elements.Select(propertyValue => Simplify(propertyValue, formatProvider)).ToArray(); var str = value as StructureValue; if (str != null) { - var props = str.Properties.ToDictionary(p => p.Name, p => Simplify(p.Value)); + var props = str.Properties.ToDictionary(p => p.Name, p => Simplify(p.Value, formatProvider)); if (str.TypeTag != null) props["$typeTag"] = str.TypeTag; return props; @@ -84,13 +86,20 @@ public static object Simplify(LogEventPropertyValue value) return null; } - static object SimplifyScalar(object value) + static object SimplifyScalar(object value, IFormatProvider formatProvider) { if (value == null) return null; var valueType = value.GetType(); if (LogglyScalars.Contains(valueType)) return value; + //handle dateTimes with the format provider + if (value is DateTime) + return ((DateTime)value).ToString(formatProvider ?? CultureInfo.InvariantCulture); + + if (value is DateTimeOffset) + return ((DateTimeOffset)value).ToString(formatProvider ?? CultureInfo.InvariantCulture); + return value.ToString(); } } From 786fd15fbc76d40ff01b6995c0bb0926505a57e0 Mon Sep 17 00:00:00 2001 From: Miguel Alho Date: Wed, 5 Apr 2017 09:55:09 +0100 Subject: [PATCH 2/3] fix HttpLogShipper build errors --- src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs index f2bcde3..3af70a8 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs @@ -42,13 +42,9 @@ class HttpLogShipper : IDisposable readonly string _candidateSearchPath; readonly ExponentialBackoffConnectionSchedule _connectionSchedule; readonly long? _retainedInvalidPayloadsLimitBytes; - readonly object _stateLock = new object(); readonly PortableTimer _timer; - readonly ControlledLevelSwitch _controlledSwitch; - DateTime _nextRequiredLevelCheckUtc = DateTime.UtcNow.Add(RequiredLevelCheckInterval); - volatile bool _unloading; readonly LogglyClient _logglyClient; From 9291086ed3f678cd5957eadc957aefe61dbc2b2c Mon Sep 17 00:00:00 2001 From: Miguel Alho Date: Tue, 11 Apr 2017 10:03:39 +0100 Subject: [PATCH 3/3] bump version to 4.0.1 --- src/Serilog.Sinks.Loggly/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.Loggly/project.json b/src/Serilog.Sinks.Loggly/project.json index 86e0741..9430571 100644 --- a/src/Serilog.Sinks.Loggly/project.json +++ b/src/Serilog.Sinks.Loggly/project.json @@ -1,5 +1,5 @@ { - "version": "4.0.0-*", + "version": "4.0.1-*", "description": "Serilog sink for Loggly.com service", "authors": [ "Serilog Contributors", "Michiel van Oudheusden" ], "packOptions": {