diff --git a/src/Serilog.Sinks.Loggly/LoggerConfigurationLogglyExtensions.cs b/src/Serilog.Sinks.Loggly/LoggerConfigurationLogglyExtensions.cs index 58e060f..a099461 100644 --- a/src/Serilog.Sinks.Loggly/LoggerConfigurationLogglyExtensions.cs +++ b/src/Serilog.Sinks.Loggly/LoggerConfigurationLogglyExtensions.cs @@ -48,6 +48,8 @@ public static class LoggerConfigurationLogglyExtensions /// The limit is soft in that it can be exceeded by any single error payload, but in that case only that single error /// payload will be retained. /// number of files to retain for the buffer. If defined, this also controls which records + /// Used to configure underlying LogglyClient programmaticaly. Otherwise use app.Config. + /// Decides if the sink should include specific properties in the log message /// in the buffer get sent to the remote Loggly instance /// Logger configuration, allowing configuration to continue. /// A required parameter is null. @@ -62,7 +64,9 @@ public static LoggerConfiguration Loggly( long? eventBodyLimitBytes = 1024 * 1024, LoggingLevelSwitch controlLevelSwitch = null, long? retainedInvalidPayloadsLimitBytes = null, - int? retainedFileCountLimit = null) + int? retainedFileCountLimit = null, + LogglyConfiguration logglyConfig = null, + LogIncludes includes = null) { if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration)); if (bufferFileSizeLimitBytes.HasValue && bufferFileSizeLimitBytes < 0) @@ -74,7 +78,7 @@ public static LoggerConfiguration Loggly( if (bufferBaseFilename == null) { - sink = new LogglySink(formatProvider, batchPostingLimit, defaultedPeriod); + sink = new LogglySink(formatProvider, batchPostingLimit, defaultedPeriod, logglyConfig, includes); } else { @@ -87,7 +91,9 @@ public static LoggerConfiguration Loggly( controlLevelSwitch, retainedInvalidPayloadsLimitBytes, retainedFileCountLimit, - formatProvider); + formatProvider, + logglyConfig, + includes); } return loggerConfiguration.Sink(sink, restrictedToMinimumLevel); diff --git a/src/Serilog.Sinks.Loggly/Properties/AssemblyInfo.cs b/src/Serilog.Sinks.Loggly/Properties/AssemblyInfo.cs index 5269bcb..ac76148 100644 --- a/src/Serilog.Sinks.Loggly/Properties/AssemblyInfo.cs +++ b/src/Serilog.Sinks.Loggly/Properties/AssemblyInfo.cs @@ -2,7 +2,7 @@ using System.Reflection; using System.Runtime.CompilerServices; -[assembly: AssemblyVersion("3.0.0.0")] +[assembly: AssemblyVersion("3.1.0.0")] [assembly: CLSCompliant(true)] diff --git a/src/Serilog.Sinks.Loggly/Serilog.Sinks.Loggly.csproj b/src/Serilog.Sinks.Loggly/Serilog.Sinks.Loggly.csproj index 1090cc6..c2f3c1d 100644 --- a/src/Serilog.Sinks.Loggly/Serilog.Sinks.Loggly.csproj +++ b/src/Serilog.Sinks.Loggly/Serilog.Sinks.Loggly.csproj @@ -2,7 +2,7 @@ Serilog sink for Loggly.com service - 5.1.1 + 5.2.0 Serilog Contributors;Michiel van Oudheusden net45;netstandard1.5 Serilog.Sinks.Loggly diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/DurableLogglySink.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/DurableLogglySink.cs index 4247754..cbb01b1 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/DurableLogglySink.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/DurableLogglySink.cs @@ -34,8 +34,9 @@ public DurableLogglySink( LoggingLevelSwitch levelControlSwitch, long? retainedInvalidPayloadsLimitBytes, int? retainedFileCountLimit = null, - IFormatProvider formatProvider = null - ) + IFormatProvider formatProvider = null, + LogglyConfiguration logglyConfiguration = null, + LogIncludes includes = null) { if (bufferBaseFilename == null) throw new ArgumentNullException(nameof(bufferBaseFilename)); @@ -51,12 +52,13 @@ public DurableLogglySink( levelControlSwitch, retainedInvalidPayloadsLimitBytes, encoding, - retainedFileCountLimit); + retainedFileCountLimit, + logglyConfiguration); //writes events to the file to support connection recovery _sink = new RollingFileSink( bufferBaseFilename + "-{Date}.json", - new LogglyFormatter(formatProvider), //serializes as LogglyEvent + new LogglyFormatter(formatProvider, includes), //serializes as LogglyEvent bufferFileSizeLimitBytes, retainedFileCountLimit, encoding); diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs index ef00ffe..01b82dc 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/HttpLogShipper.cs @@ -43,7 +43,8 @@ class HttpLogShipper : IDisposable readonly IFileSystemAdapter _fileSystemAdapter = new FileSystemAdapter(); readonly FileBufferDataProvider _bufferDataProvider; readonly InvalidPayloadLogger _invalidPayloadLogger; - + readonly LogglyConfigAdapter _adapter; + public HttpLogShipper( string bufferBaseFilename, int batchPostingLimit, @@ -52,7 +53,8 @@ public HttpLogShipper( LoggingLevelSwitch levelControlSwitch, long? retainedInvalidPayloadsLimitBytes, Encoding encoding, - int? retainedFileCountLimit) + int? retainedFileCountLimit, + LogglyConfiguration logglyConfiguration) { _batchPostingLimit = batchPostingLimit; _retainedFileCountLimit = retainedFileCountLimit; @@ -60,6 +62,12 @@ public HttpLogShipper( _controlledSwitch = new ControlledLevelSwitch(levelControlSwitch); _connectionSchedule = new ExponentialBackoffConnectionSchedule(period); + if (logglyConfiguration != null) + { + _adapter = new LogglyConfigAdapter(); + _adapter.ConfigureLogglyClient(logglyConfiguration); + } + _logglyClient = new LogglyClient(); //we'll use the loggly client instead of HTTP directly //create necessary path elements diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogEventConverter.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogEventConverter.cs index 1fe2ea2..29c17e8 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogEventConverter.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogEventConverter.cs @@ -16,10 +16,12 @@ namespace Serilog.Sinks.Loggly public class LogEventConverter { readonly IFormatProvider _formatProvider; + private readonly LogIncludes _includes; - public LogEventConverter(IFormatProvider formatProvider = null) + public LogEventConverter(IFormatProvider formatProvider = null, LogIncludes includes = null) { _formatProvider = formatProvider; + _includes = includes ?? new LogIncludes(); } public LogglyEvent CreateLogglyEvent(LogEvent logEvent) @@ -28,9 +30,11 @@ 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)); + + if (_includes.IncludeMessage) + { + logglyEvent.Data.AddIfAbsent("Message", logEvent.RenderMessage(_formatProvider)); + } foreach (var key in logEvent.Properties.Keys) { @@ -39,20 +43,20 @@ public LogglyEvent CreateLogglyEvent(LogEvent logEvent) logglyEvent.Data.AddIfAbsent(key, simpleValue); } - if (isHttpTransport) + if (isHttpTransport && _includes.IncludeLevel) { // syslog will capture these via the header logglyEvent.Data.AddIfAbsent("Level", logEvent.Level.ToString()); } - if (logEvent.Exception != null) + if (logEvent.Exception != null && _includes.IncludeExceptionWhenExists) { logglyEvent.Data.AddIfAbsent("Exception", GetExceptionInfo(logEvent.Exception)); } + return logglyEvent; } - static SyslogLevel ToSyslogLevel(LogEvent logEvent) { SyslogLevel syslogLevel; diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogIncludes.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogIncludes.cs new file mode 100644 index 0000000..288e4aa --- /dev/null +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogIncludes.cs @@ -0,0 +1,20 @@ +namespace Serilog.Sinks.Loggly +{ + public class LogIncludes + { + /// + /// Adds Serilog Level to all log events. Defaults to true. + /// + public bool IncludeLevel { get; set; } = true; + + /// + /// Adds Serilog Message to all log events. Defaults to true. + /// + public bool IncludeMessage { get; set; } = true; + + /// + /// Adds Serilog Exception to log events when an exception exists. Defaults to true. + /// + public bool IncludeExceptionWhenExists { get; set; } = true; + } +} \ No newline at end of file diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyConfigAdapter.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyConfigAdapter.cs new file mode 100644 index 0000000..ce4ea46 --- /dev/null +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyConfigAdapter.cs @@ -0,0 +1,41 @@ +using System; +using Loggly.Config; + +namespace Serilog.Sinks.Loggly +{ + class LogglyConfigAdapter + { + public void ConfigureLogglyClient(LogglyConfiguration logglyConfiguration) + { + var config = LogglyConfig.Instance; + + if (!string.IsNullOrWhiteSpace(logglyConfiguration.ApplicationName)) + config.ApplicationName = logglyConfiguration.ApplicationName; + + if (string.IsNullOrWhiteSpace(logglyConfiguration.CustomerToken)) + throw new ArgumentNullException("CustomerToken", "CustomerToken is required"); + + config.CustomerToken = logglyConfiguration.CustomerToken; + config.IsEnabled = logglyConfiguration.IsEnabled; + + foreach (var tag in logglyConfiguration.Tags) + { + config.TagConfig.Tags.Add(tag); + } + + config.ThrowExceptions = logglyConfiguration.ThrowExceptions; + + if (logglyConfiguration.LogTransport != TransportProtocol.Https) + config.Transport.LogTransport = (LogTransport)Enum.Parse(typeof(LogTransport), logglyConfiguration.LogTransport.ToString()); + + if (!string.IsNullOrWhiteSpace(logglyConfiguration.EndpointHostName)) + config.Transport.EndpointHostname = logglyConfiguration.EndpointHostName; + + if (logglyConfiguration.EndpointPort > 0 && logglyConfiguration.EndpointPort <= ushort.MaxValue) + config.Transport.EndpointPort = logglyConfiguration.EndpointPort; + + config.Transport.IsOmitTimestamp = logglyConfiguration.OmitTimestamp; + config.Transport = config.Transport.GetCoercedToValidConfig(); + } + } +} diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyConfiguration.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyConfiguration.cs new file mode 100644 index 0000000..ec1b4a5 --- /dev/null +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyConfiguration.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; + +namespace Serilog.Sinks.Loggly +{ + public class LogglyConfiguration + { + public string ApplicationName { get; set; } + public string CustomerToken { get; set; } + public List Tags { get; set; } + public bool IsEnabled { get; set; } = true; + public bool ThrowExceptions { get; set; } + + /// + /// Defaults to Https + /// + public TransportProtocol LogTransport { get; set; } + + /// + /// Defaults to logs-01.loggly.com + /// + public string EndpointHostName { get; set; } + + /// + /// Defaults to default port for selected LogTransport. + /// E.g. https is 443, SyslogTcp/-Udp is 514 and SyslogSecure is 6514. + /// + public int EndpointPort { get; set; } + + /// + /// Defines if timestamp should automatically be added to the json body when using Https + /// + public bool OmitTimestamp { get; set; } + } + + public enum TransportProtocol + { + /// + /// Https. + /// + Https, + + /// + /// SyslogSecure. + /// + SyslogSecure, + + /// + /// SyslogUdp. + /// + SyslogUdp, + + /// + /// SyslogTcp. + /// + SyslogTcp, + } +} diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyFormatter.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyFormatter.cs index 52dd9c9..903e327 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyFormatter.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglyFormatter.cs @@ -28,11 +28,11 @@ class LogglyFormatter : ITextFormatter readonly JsonSerializer _serializer = JsonSerializer.Create(); readonly LogEventConverter _converter; - public LogglyFormatter(IFormatProvider formatProvider) + public LogglyFormatter(IFormatProvider formatProvider, LogIncludes includes) { //the converter should receive the format provider used, in order to // handle dateTimes and dateTimeOffsets in a controlled manner - _converter = new LogEventConverter(formatProvider); + _converter = new LogEventConverter(formatProvider, includes); } public void Format(LogEvent logEvent, TextWriter output) { diff --git a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglySink.cs b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglySink.cs index 6cefa6f..149fc1b 100644 --- a/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglySink.cs +++ b/src/Serilog.Sinks.Loggly/Sinks/Loggly/LogglySink.cs @@ -29,6 +29,7 @@ public class LogglySink : PeriodicBatchingSink { readonly LogEventConverter _converter; readonly LogglyClient _client; + readonly LogglyConfigAdapter _adapter; /// /// A reasonable default for the number of events posted in @@ -41,17 +42,34 @@ public class LogglySink : PeriodicBatchingSink /// public static readonly TimeSpan DefaultPeriod = TimeSpan.FromSeconds(5); + /// + /// Construct a sink that saves logs to the specified storage account. Properties are being send as data and the level is used as tag. + /// + /// The maximum number of events to post in a single batch. + /// The time to wait between checking for event batches. + /// Supplies culture-specific formatting information, or null. + public LogglySink(IFormatProvider formatProvider, int batchSizeLimit, TimeSpan period) : this(formatProvider, batchSizeLimit, period, null, null) + { + } + /// /// Construct a sink that saves logs to the specified storage account. Properties are being send as data and the level is used as tag. /// /// The maximum number of events to post in a single batch. /// The time to wait between checking for event batches. /// Supplies culture-specific formatting information, or null. - public LogglySink(IFormatProvider formatProvider, int batchSizeLimit, TimeSpan period) + /// Used to configure underlying LogglyClient programmaticaly. Otherwise use app.Config. + /// Decides if the sink should include specific properties in the log message + public LogglySink(IFormatProvider formatProvider, int batchSizeLimit, TimeSpan period, LogglyConfiguration logglyConfig, LogIncludes includes) : base (batchSizeLimit, period) { + if (logglyConfig != null) + { + _adapter = new LogglyConfigAdapter(); + _adapter.ConfigureLogglyClient(logglyConfig); + } _client = new LogglyClient(); - _converter = new LogEventConverter(formatProvider); + _converter = new LogEventConverter(formatProvider, includes); } /// diff --git a/test/Serilog.Sinks.Loggly.Tests/ExceptionSerialization.cs b/test/Serilog.Sinks.Loggly.Tests/ExceptionSerialization.cs index 7e20b6a..81391b5 100644 --- a/test/Serilog.Sinks.Loggly.Tests/ExceptionSerialization.cs +++ b/test/Serilog.Sinks.Loggly.Tests/ExceptionSerialization.cs @@ -17,7 +17,7 @@ public class ExceptionSerialization public void ReturnFalseGivenValueOf1() { var writer = new StringWriter(); - var formatter = new LogglyFormatter(null); + var formatter = new LogglyFormatter(null, null); try { ThrowException(); diff --git a/test/Serilog.Sinks.Loggly.Tests/Serilog.Sinks.Loggly.Tests.csproj b/test/Serilog.Sinks.Loggly.Tests/Serilog.Sinks.Loggly.Tests.csproj index 1b67e3f..7cd58f0 100644 --- a/test/Serilog.Sinks.Loggly.Tests/Serilog.Sinks.Loggly.Tests.csproj +++ b/test/Serilog.Sinks.Loggly.Tests/Serilog.Sinks.Loggly.Tests.csproj @@ -32,10 +32,10 @@ - - - - + + + +