diff --git a/.travis.yml b/.travis.yml index ba8d764..79d1441 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ matrix: - os: linux # Ubuntu 14.04 dist: trusty sudo: required - dotnet: 1.0.4 + dotnet: 2.0.0 script: - ./build.sh \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 2d917bb..9434222 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,20 +1,33 @@ -##2.2.0 -- [#47] Tooling updates to VS2017 +## 2.4.0 +- [#62](https://github.com/serilog/serilog-sinks-splunk/issues/62) Default fields added by serilog to splunk +- [#63](https://github.com/serilog/serilog-sinks-splunk/issues/63) Possible thread leak when ILogger instances are disposed -##2.1.2 -- [#43](https://github.com/serilog/serilog-sinks-splunk/pull/43) - Extend sink & static configuration to allow for custom JSON formatter. +## 2.3.0 +- [#59](https://github.com/serilog/serilog-sinks-splunk/issues/59) Added ability to use custom fields with HEC. See http://dev.splunk.com/view/event-collector/SP-CAAAFB6. -##2.1.1 +## 2.2.1 +- [#47](https://github.com/serilog/serilog-sinks-splunk/issues/47) Tooling updates to VS2017 +- [#48](https://github.com/serilog/serilog-sinks-splunk/issues/48) +- [#49](https://github.com/serilog/serilog-sinks-splunk/issues/49) +- [#52](https://github.com/serilog/serilog-sinks-splunk/issues/52) + +## 2.1.3 +- [#45](https://github.com/serilog/serilog-sinks-splunk/issues/45) - Deadlock fix on UI thread. + +## 2.1.2 +- [#43](https://github.com/serilog/serilog-sinks-splunk/issues/43) - Extend sink & static configuration to allow for custom JSON formatter. + +## 2.1.1 - [#38](https://github.com/serilog/serilog-sinks-splunk/issues/38) - Fix for HttpEventlogCollector and sourceType - Clean up of sample app using examples of host, sourcetype, source override -##2.1.0 +## 2.1.0 * Change to use a standalone formatter -* Resolves #32 & #26 by exposing `HttpMessageHandler` -* Resolves #30 by ignoring OSX build and including tests in `build.sh` for TravisCI +* Resolves - [#32](https://github.com/serilog/serilog-sinks-splunk/issues/32) & - [#26](https://github.com/serilog/serilog-sinks-splunk/issues/26) by exposing `HttpMessageHandler` +* Resolves - [#30](https://github.com/serilog/serilog-sinks-splunk/issues/30) by ignoring OSX build and including tests in `build.sh` for TravisCI -##2.0 +## 2.0 - Support for DotNet Core - Event Collector fluent interface changed to `.WriteTo.EventCollector` - Event Collector Sink targeting core @@ -22,22 +35,22 @@ - Updated Event Collector HTTP Client to add URI endpoint to host: "services/collector" if not included. - Event Collector changed to use epoch time [#15](https://github.com/serilog/serilog-sinks-splunk/pull/15) -##1.8 +## 1.8 - Event Collector changed to use epoch time [#15](https://github.com/serilog/serilog-sinks-splunk/pull/15) -##1.7 +## 1.7 - Better support for formatting including [#578](https://github.com/serilog/serilog/issues/578) - Cleanup on Event Collector -##1.6.50 +## 1.6.50 - Streaming support for Event Collector -##1.6.42 +## 1.6.42 - Added support for Splunk 6.3 Event Collector - Deprecated Splunk HTTP Sink using Management Port/API -##1.5.30 +## 1.5.30 - Added switch for template rendering - ##1.5.0 +## 1.5.0 - Moved the sink from its [original location](https://github.com/serilog/serilog) diff --git a/build.sh b/build.sh index 69a9e8d..9c61ab2 100755 --- a/build.sh +++ b/build.sh @@ -5,10 +5,11 @@ dotnet restore for path in src/**/*.csproj; do dotnet build -f netstandard1.1 -c Release ${path} + dotnet build -f netstandard1.3 -c Release ${path} done for path in test/*.Tests/*.csproj; do - dotnet test -f netcoreapp1.0 -c Release ${path} + dotnet test -f netcoreapp2.0 -c Release ${path} done -dotnet build -f netcoreapp1.0 -c Release sample/Sample/Sample.csproj \ No newline at end of file +dotnet build -f netcoreapp2.0 -c Release sample/Sample/Sample.csproj \ No newline at end of file diff --git a/sample/Sample/Program.cs b/sample/Sample/Program.cs index fff3420..f07930c 100644 --- a/sample/Sample/Program.cs +++ b/sample/Sample/Program.cs @@ -47,7 +47,7 @@ private static void WithCompactSplunkFormatter(int eventsToCreate) // Vanilla Test with full uri specified Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.LiterateConsole() + .WriteTo.Console() .WriteTo.EventCollector( SPLUNK_FULL_ENDPOINT, Program.EventCollectorToken, new CompactSplunkJsonFormatter()) @@ -70,7 +70,7 @@ public static void OverridingSource(int eventsToCreate) // Override Source Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.LiterateConsole() + .WriteTo.Console() .WriteTo.EventCollector( SPLUNK_ENDPOINT, Program.EventCollectorToken, @@ -92,7 +92,7 @@ public static void OverridingSourceType(int eventsToCreate) // Override Source Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.LiterateConsole() + .WriteTo.Console() .WriteTo.EventCollector( SPLUNK_ENDPOINT, Program.EventCollectorToken, @@ -114,7 +114,7 @@ public static void OverridingHost(int eventsToCreate) // Override Host Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.LiterateConsole() + .WriteTo.Console() .WriteTo.EventCollector( SPLUNK_ENDPOINT, Program.EventCollectorToken, @@ -136,7 +136,7 @@ public static void UsingFullUri(int eventsToCreate) // Vanilla Test with full uri specified Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.LiterateConsole() + .WriteTo.Console() .WriteTo.EventCollector( SPLUNK_FULL_ENDPOINT, Program.EventCollectorToken) @@ -158,7 +158,7 @@ public static void UsingHostOnly(int eventsToCreate) // Vanilla Tests just host Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.LiterateConsole() + .WriteTo.Console() .WriteTo.EventCollector( SPLUNK_ENDPOINT, Program.EventCollectorToken) @@ -179,7 +179,7 @@ public static void WithNoTemplate(int eventsToCreate) // No Template Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.LiterateConsole() + .WriteTo.Console() .WriteTo.EventCollector( SPLUNK_ENDPOINT, Program.EventCollectorToken, @@ -201,7 +201,7 @@ public static void UsingSSL(int eventsToCreate) // SSL Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.LiterateConsole() + .WriteTo.Console() .WriteTo.EventCollector( SPLUNK_ENDPOINT, Program.EventCollectorToken) @@ -228,7 +228,7 @@ public static void AddCustomFields(int eventsToCreate) // Override Source Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() - .WriteTo.LiterateConsole() + .WriteTo.Console() .WriteTo.EventCollector( splunkHost: SPLUNK_ENDPOINT , eventCollectorToken: SPLUNK_HEC_TOKEN diff --git a/sample/Sample/Sample.csproj b/sample/Sample/Sample.csproj index ea50423..541aaa2 100644 --- a/sample/Sample/Sample.csproj +++ b/sample/Sample/Sample.csproj @@ -2,12 +2,12 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>netcoreapp1.0</TargetFramework> + <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> <ItemGroup> - <PackageReference Include="Serilog" Version="2.4.0" /> - <PackageReference Include="Serilog.Sinks.Literate" Version="2.1.0" /> + <PackageReference Include="Serilog" Version="2.5.0" /> + <PackageReference Include="Serilog.Sinks.Console" Version="3.0.1" /> </ItemGroup> <ItemGroup> diff --git a/src/Serilog.Sinks.Splunk/Serilog.Sinks.Splunk.csproj b/src/Serilog.Sinks.Splunk/Serilog.Sinks.Splunk.csproj index a73a5aa..322d1c9 100644 --- a/src/Serilog.Sinks.Splunk/Serilog.Sinks.Splunk.csproj +++ b/src/Serilog.Sinks.Splunk/Serilog.Sinks.Splunk.csproj @@ -2,9 +2,9 @@ <PropertyGroup> <Description>The Splunk Sink for Serilog</Description> - <VersionPrefix>2.3.0</VersionPrefix> + <VersionPrefix>2.3.1</VersionPrefix> <Authors>Matthew Erbs, Serilog Contributors</Authors> - <TargetFrameworks>net45;netstandard1.1</TargetFrameworks> + <TargetFrameworks>net45;netstandard1.1;netstandard1.3</TargetFrameworks> <GenerateDocumentationFile>true</GenerateDocumentationFile> <AssemblyName>Serilog.Sinks.Splunk</AssemblyName> <PackageId>Serilog.Sinks.Splunk</PackageId> @@ -27,15 +27,17 @@ </PropertyGroup> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.1' "> - <PackageReference Include="System.Collections" Version="4.0.11" /> - <PackageReference Include="System.Collections.Concurrent" Version="4.0.12" /> - <PackageReference Include="System.Runtime" Version="4.1.0" /> - <PackageReference Include="System.Threading" Version="4.0.11" /> - <PackageReference Include="System.Net.Http" Version="4.1.1" /> + <PackageReference Include="System.Net.Http" Version="4.3.0" /> + </ItemGroup> + + <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' "> + <PackageReference Include="System.Net.Http" Version="4.3.0" /> </ItemGroup> <ItemGroup> - <PackageReference Include="Serilog" Version="2.4.0" /> + <PackageReference Include="Serilog" Version="2.5.0" /> + <PackageReference Include="Serilog.Sinks.PeriodicBatching" Version="2.1.1" /> + <PackageReference Include="System.Net.Http" Version="4.3.2" /> </ItemGroup> </Project> diff --git a/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorSink.cs b/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorSink.cs index 647fe6b..cdfe05f 100644 --- a/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorSink.cs +++ b/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorSink.cs @@ -13,33 +13,30 @@ // limitations under the License. using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Threading; using System.Threading.Tasks; -using Serilog.Core; using Serilog.Debugging; using Serilog.Events; using Serilog.Formatting; +using Serilog.Sinks.PeriodicBatching; namespace Serilog.Sinks.Splunk { /// <summary> /// A sink to log to the Event Collector available in Splunk 6.3 /// </summary> - public class EventCollectorSink : ILogEventSink, IDisposable + public class EventCollectorSink : PeriodicBatchingSink { private readonly string _splunkHost; private readonly string _uriPath; - private readonly int _batchSizeLimitLimit; private readonly ITextFormatter _jsonFormatter; - private readonly ConcurrentQueue<LogEvent> _queue; private readonly EventCollectorClient _httpClient; + /// <summary> /// Taken from Splunk.Logging.Common /// </summary> @@ -115,6 +112,7 @@ public EventCollectorSink( messageHandler) { } + /// <summary> /// Creates a new instance of the sink with Customfields /// </summary> @@ -152,12 +150,11 @@ public EventCollectorSink( uriPath, batchIntervalInSeconds, batchSizeLimit, - new SplunkJsonFormatter(renderTemplate, formatProvider, source, sourceType, host, index,fields), + new SplunkJsonFormatter(renderTemplate, formatProvider, source, sourceType, host, index, fields), messageHandler) { } - /// <summary> /// Creates a new instance of the sink /// </summary> @@ -176,67 +173,25 @@ public EventCollectorSink( int batchSizeLimit, ITextFormatter jsonFormatter, HttpMessageHandler messageHandler = null) + : base(batchSizeLimit, TimeSpan.FromSeconds(batchIntervalInSeconds)) { _uriPath = uriPath; _splunkHost = splunkHost; - _queue = new ConcurrentQueue<LogEvent>(); _jsonFormatter = jsonFormatter; - _batchSizeLimitLimit = batchSizeLimit; - var batchInterval = TimeSpan.FromSeconds(batchIntervalInSeconds); _httpClient = messageHandler != null ? new EventCollectorClient(eventCollectorToken, messageHandler) : new EventCollectorClient(eventCollectorToken); - - var cancellationToken = new CancellationToken(); - - RepeatAction.OnInterval( - batchInterval, - async () => await ProcessQueue(), - cancellationToken); } /// <summary> - /// Emits the provided log event from a sink + /// Emit a batch of log events, running asynchronously. /// </summary> - /// <param name="logEvent"></param> - public void Emit(LogEvent logEvent) - { - if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); - - _queue.Enqueue(logEvent); - } - - private async Task ProcessQueue() - { - try - { - do - { - var count = 0; - var events = new Queue<LogEvent>(); - LogEvent next; - - while (count < _batchSizeLimitLimit && _queue.TryDequeue(out next)) - { - count++; - events.Enqueue(next); - } - - if (events.Count == 0) - return; - - await Send(events); - - } while (true); - } - catch (Exception ex) - { - SelfLog.WriteLine("Exception while emitting batch from {0}: {1}", this, ex); - } - } - - private async Task Send(IEnumerable<LogEvent> events) + /// <param name="events">The events to emit.</param> + /// <remarks> + /// Override either <see cref="PeriodicBatchingSink.EmitBatch" /> or <see cref="PeriodicBatchingSink.EmitBatchAsync" />, not both. + /// </remarks> + protected override async Task EmitBatchAsync(IEnumerable<LogEvent> events) { var allEvents = new StringWriter(); @@ -248,56 +203,22 @@ private async Task Send(IEnumerable<LogEvent> events) var request = new EventCollectorRequest(_splunkHost, allEvents.ToString(), _uriPath); var response = await _httpClient.SendAsync(request).ConfigureAwait(false); - if (response.IsSuccessStatusCode) - { - //Do Nothing? - } - else + if (!response.IsSuccessStatusCode) { //Application Errors sent via HTTP Event Collector if (HttpEventCollectorApplicationErrors.Any(x => x == response.StatusCode)) { + // By not throwing an exception here the PeriodicBatchingSink will assume the batch succeeded and not send it again. SelfLog.WriteLine( "A status code of {0} was received when attempting to send to {1}. The event has been discarded and will not be placed back in the queue.", response.StatusCode.ToString(), _splunkHost); } else { - //Put the item back in the queue & retry on next go - SelfLog.WriteLine( - "A status code of {0} was received when attempting to send to {1}. The event has been placed back in the queue", - response.StatusCode.ToString(), _splunkHost); - - foreach (var logEvent in events) - { - _queue.Enqueue(logEvent); - } + // EnsureSuccessStatusCode will throw an exception and the PeriodicBatchingSink will catch/log the exception and retry the batch. + response.EnsureSuccessStatusCode(); } } } - - /// <inheritdoc/> - public void Dispose() - { - Dispose(true); - } - - /// <inheritdoc/> - protected virtual void Dispose(bool disposing) - { - if (!disposing) return; - - var remainingEvents = new List<LogEvent>(); - - while (!_queue.IsEmpty) - { - LogEvent next; - _queue.TryDequeue(out next); - remainingEvents.Add(next); - } - - Send(remainingEvents).Wait(); - _httpClient.Dispose(); - } } } diff --git a/src/Serilog.Sinks.Splunk/Sinks/Splunk/RepeatAction.cs b/src/Serilog.Sinks.Splunk/Sinks/Splunk/RepeatAction.cs deleted file mode 100644 index ee39d6f..0000000 --- a/src/Serilog.Sinks.Splunk/Sinks/Splunk/RepeatAction.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2016 Serilog Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Serilog.Sinks.Splunk -{ - internal static class RepeatAction - { - public static Task OnInterval(TimeSpan pollInterval, Action action, CancellationToken token, - TaskCreationOptions taskCreationOptions, TaskScheduler taskScheduler) - { - return Task.Factory.StartNew(() => - { - for (;;) - { - if (token.WaitCancellationRequested(pollInterval)) - break; - action(); - } - }, token, taskCreationOptions, taskScheduler); - } - - public static Task OnInterval(TimeSpan pollInterval, Action action, CancellationToken token) - { - return OnInterval(pollInterval, action, token, TaskCreationOptions.LongRunning, TaskScheduler.Default); - } - - public static bool WaitCancellationRequested(this CancellationToken token, TimeSpan timeout) - { - return token.WaitHandle.WaitOne(timeout); - } - } -} \ No newline at end of file diff --git a/src/Serilog.Sinks.Splunk/Sinks/Splunk/SplunkJsonFormatter.cs b/src/Serilog.Sinks.Splunk/Sinks/Splunk/SplunkJsonFormatter.cs index cc6b64f..55b197e 100644 --- a/src/Serilog.Sinks.Splunk/Sinks/Splunk/SplunkJsonFormatter.cs +++ b/src/Serilog.Sinks.Splunk/Sinks/Splunk/SplunkJsonFormatter.cs @@ -22,6 +22,7 @@ namespace Serilog.Sinks.Splunk { + /// <inheritdoc /> /// <summary> /// Renders log events into a default JSON format for consumption by Splunk. /// </summary> @@ -29,12 +30,13 @@ public class SplunkJsonFormatter : ITextFormatter { static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter(); - readonly bool _renderTemplate; - readonly IFormatProvider _formatProvider; - readonly string _suffix; + private readonly bool _renderTemplate; + private readonly IFormatProvider _formatProvider; + private readonly string _suffix; + /// <inheritdoc /> /// <summary> - /// Construct a <see cref="SplunkJsonFormatter"/>. + /// Construct a <see cref="T:Serilog.Sinks.Splunk.SplunkJsonFormatter" />. /// </summary> /// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param> /// <param name="renderTemplate">If true, the template used will be rendered and written to the output as a property named MessageTemplate</param> diff --git a/test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj b/test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj index 93f3e28..40da3da 100644 --- a/test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj +++ b/test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFrameworks>net452;netcoreapp1.0</TargetFrameworks> + <TargetFrameworks>net452;netcoreapp2.0</TargetFrameworks> <AssemblyName>Serilog.Sinks.Splunk.Tests</AssemblyName> <PackageId>Serilog.Sinks.Splunk.Tests</PackageId> <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> @@ -25,8 +25,12 @@ <ProjectReference Include="..\..\src\Serilog.Sinks.Splunk\Serilog.Sinks.Splunk.csproj" /> </ItemGroup> - <ItemGroup> - <Reference Include="System.Net.Http" /> + <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.1' "> + <PackageReference Include="System.Net.Http" Version="4.3.0" /> + </ItemGroup> + + <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' "> + <PackageReference Include="System.Net.Http" Version="4.3.0" /> </ItemGroup> <ItemGroup>