diff --git a/.idea/.idea.Splunk.Logging/.idea/.name b/.idea/.idea.Splunk.Logging/.idea/.name new file mode 100644 index 0000000..8216a96 --- /dev/null +++ b/.idea/.idea.Splunk.Logging/.idea/.name @@ -0,0 +1 @@ +Splunk.Logging \ No newline at end of file diff --git a/.idea/.idea.Splunk.Logging/.idea/indexLayout.xml b/.idea/.idea.Splunk.Logging/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.Splunk.Logging/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.Splunk.Logging/.idea/projectSettingsUpdater.xml b/.idea/.idea.Splunk.Logging/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000..4bb9f4d --- /dev/null +++ b/.idea/.idea.Splunk.Logging/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.Splunk.Logging/.idea/vcs.xml b/.idea/.idea.Splunk.Logging/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/.idea.Splunk.Logging/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/.idea.Splunk.Logging/.idea/workspace.xml b/.idea/.idea.Splunk.Logging/.idea/workspace.xml new file mode 100644 index 0000000..19eaa71 --- /dev/null +++ b/.idea/.idea.Splunk.Logging/.idea/workspace.xml @@ -0,0 +1,201 @@ + + + + examples/examples.csproj + standalone-test/standalone-test.csproj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "keyToString": { + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "WebServerToolWindowFactoryState": "false", + "XThreadsFramesViewSplitterKey": "0.53677934", + "nodejs_package_manager_path": "npm", + "settings.editor.selected.configurable": "SolutionBuilderGeneralOptionsPage", + "vue.rearranger.settings.migration": "true" + }, + "keyToStringList": { + "rider.external.source.directories": [ + "C:\\Users\\graemefoster\\AppData\\Roaming\\JetBrains\\Rider2022.3\\resharper-host\\DecompilerCache", + "C:\\Users\\graemefoster\\AppData\\Roaming\\JetBrains\\Rider2022.3\\resharper-host\\SourcesCache", + "C:\\Users\\graemefoster\\AppData\\Local\\Symbols\\src" + ] + } +} + + + + + + + + + + + 1680251006425 + + + 1680507316489 + + + 1680507400298 + + + 1680513705888 + + + 1680523264561 + + + + + + + + + + + + + file://$PROJECT_DIR$/test/unit-tests/TestHttpEventCollector.cs + 534 + + + + + + + + + file://$PROJECT_DIR$/test/unit-tests/TestHttpEventCollector.cs + 136 + + + + + + + + + file://$PROJECT_DIR$/test/unit-tests/TestHttpEventCollector.cs + 64 + + + + + + + + + + + \ No newline at end of file diff --git a/examples/examples.csproj b/examples/examples.csproj index 7ac5b4d..c43378e 100644 --- a/examples/examples.csproj +++ b/examples/examples.csproj @@ -8,7 +8,7 @@ Exe examples examples - v4.5 + v4.5.2 512 true diff --git a/src/Splunk.Logging.Common/BatchBuffers/IBuffer.cs b/src/Splunk.Logging.Common/BatchBuffers/IBuffer.cs new file mode 100644 index 0000000..979fd9d --- /dev/null +++ b/src/Splunk.Logging.Common/BatchBuffers/IBuffer.cs @@ -0,0 +1,13 @@ +using System; +using System.Net.Http; + +namespace Splunk.Logging.BatchBuffers +{ + public interface IBuffer : IDisposable + { + void Append(HttpEventCollectorEventInfo serializedEventInfo); + long Length { get; } + HttpContent BuildHttpContent(string mediaType); + void SupportOriginalBehaviour(); + } +} \ No newline at end of file diff --git a/src/Splunk.Logging.Common/BatchBuffers/PushStreamInMemoryBuffer.cs b/src/Splunk.Logging.Common/BatchBuffers/PushStreamInMemoryBuffer.cs new file mode 100644 index 0000000..6c7ae6d --- /dev/null +++ b/src/Splunk.Logging.Common/BatchBuffers/PushStreamInMemoryBuffer.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace Splunk.Logging.BatchBuffers +{ + public class PushStreamInMemoryBuffer : IBuffer + { + private readonly List serializedItems = new List(); + + public void Append(HttpEventCollectorEventInfo serializedEventInfo) + { + serializedItems.Add( Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(serializedEventInfo))); + } + + public long Length => serializedItems.Sum(x => x.Length); + + public HttpContent BuildHttpContent(string mediaType) + { + return new PushStreamContent(async s => + { + foreach (var entry in serializedItems) + { + await s.WriteAsync(entry, 0, entry.Length); + } + }, mediaType, Length); + } + + public void SupportOriginalBehaviour() + { + } + + public void Dispose() + { + } + + private class PushStreamContent : HttpContent + { + private readonly Func writeContent; + private readonly long knownLength; + + public PushStreamContent(Func writeContent, string mediaType, long knownLength) + { + this.writeContent = writeContent; + this.knownLength = knownLength; + Headers.ContentType = new MediaTypeHeaderValue(mediaType) + { + CharSet = "utf-8" + }; + } + + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + await writeContent(stream); + await stream.FlushAsync(); + } + + protected override bool TryComputeLength(out long length) + { + length = this.knownLength; + return true; + } + } + } +} \ No newline at end of file diff --git a/src/Splunk.Logging.Common/BatchBuffers/StringBuilderBuffer.cs b/src/Splunk.Logging.Common/BatchBuffers/StringBuilderBuffer.cs new file mode 100644 index 0000000..9560cee --- /dev/null +++ b/src/Splunk.Logging.Common/BatchBuffers/StringBuilderBuffer.cs @@ -0,0 +1,31 @@ +using System.Net.Http; +using System.Text; +using Newtonsoft.Json; + +namespace Splunk.Logging.BatchBuffers +{ + public class StringBuilderBuffer : IBuffer + { + private readonly StringBuilder builder = new StringBuilder(); + + public void Append(HttpEventCollectorEventInfo serializedEventInfo) + { + builder.Append(JsonConvert.SerializeObject(serializedEventInfo)); + } + + public long Length => builder.Length; + + public HttpContent BuildHttpContent(string mediaType) + { + return new StringContent(builder.ToString(), Encoding.UTF8, mediaType); + } + + public void SupportOriginalBehaviour() + { + } + + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/src/Splunk.Logging.Common/BatchBuffers/TemporaryFileBatchBuffer.cs b/src/Splunk.Logging.Common/BatchBuffers/TemporaryFileBatchBuffer.cs new file mode 100644 index 0000000..99b0d25 --- /dev/null +++ b/src/Splunk.Logging.Common/BatchBuffers/TemporaryFileBatchBuffer.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using Newtonsoft.Json; + +namespace Splunk.Logging.BatchBuffers +{ + public class TemporaryFileBatchBuffer : IBuffer + { + private static readonly string TempDir = Path.GetTempPath(); + private readonly string filePath; + private readonly JsonSerializer serializer; + private readonly TextWriter writer; + private readonly FileStream fileStream; + + public TemporaryFileBatchBuffer() + { + filePath = Path.GetFileName(Path.GetTempFileName()); + serializer = JsonSerializer.Create(); + fileStream = File.OpenWrite($"{TempDir}{filePath}"); + writer = new StreamWriter(fileStream); + } + + public void Append(HttpEventCollectorEventInfo serializedEventInfo) + { + serializer.Serialize(writer, serializedEventInfo); + writer.Flush(); + } + + public long Length => fileStream.Length; + + public HttpContent BuildHttpContent(string mediaType) + { + writer.Flush(); + writer.Close(); + var mediaTypeHeaderValue = new MediaTypeHeaderValue(mediaType) + { + CharSet = "utf-8" + }; + return new StreamContent(File.OpenRead($"{TempDir}{filePath}")) + { + Headers = + { + ContentType = mediaTypeHeaderValue + } + }; + } + + public void SupportOriginalBehaviour() + { + } + + public void Dispose() + { + writer?.Dispose(); + fileStream?.Dispose(); + try + { + File.Delete($"{TempDir}{filePath}"); + } + catch (Exception) + { + //Ignore + } + } + } +} \ No newline at end of file diff --git a/src/Splunk.Logging.Common/HttpEventCollectorSender.cs b/src/Splunk.Logging.Common/HttpEventCollectorSender.cs index e97f719..120787f 100644 --- a/src/Splunk.Logging.Common/HttpEventCollectorSender.cs +++ b/src/Splunk.Logging.Common/HttpEventCollectorSender.cs @@ -20,12 +20,14 @@ using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; +using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; +using Splunk.Logging.BatchBuffers; namespace Splunk.Logging { @@ -99,6 +101,18 @@ public enum SendMode Sequential }; + /// + /// Where to buffer log messages before sending. Default is to use InMemory, but + /// to save memory use at the expense of slightly slower sends, TempFiles will write to the + /// file system, and delete the files on successful send. + /// + public enum BufferMode + { + StringBuilderBuffer, + PushStreamInMemoryBuffer, + TempFilesBuffer + }; + private const string HttpContentTypeMedia = "application/json"; private const string HttpEventCollectorPath = "/services/collector/event/1.0"; private const string AuthorizationHeaderScheme = "Splunk"; @@ -114,8 +128,8 @@ public enum SendMode private SendMode sendMode = SendMode.Parallel; private Task activePostTask = null; private object eventsBatchLock = new object(); - private List eventsBatch = new List(); - private StringBuilder serializedEventsBatch = new StringBuilder(); + private List eventsBatch; + private IBuffer serializedEventsBatch; private Timer timer; private HttpClient httpClient = null; @@ -148,7 +162,8 @@ public HttpEventCollectorSender( SendMode sendMode, int batchInterval, int batchSizeBytes, int batchSizeCount, HttpEventCollectorMiddleware middleware, - HttpEventCollectorFormatter formatter = null) + HttpEventCollectorFormatter formatter = null, + BufferMode bufferMode = BufferMode.StringBuilderBuffer) { this.serializer = new JsonSerializer(); serializer.NullValueHandling = NullValueHandling.Ignore; @@ -163,6 +178,9 @@ public HttpEventCollectorSender( this.token = token; this.middleware = middleware; this.formatter = formatter; + this.bufferMode = bufferMode; + + BuildBuffers(); // special case - if batch interval is specified without size and count // they are set to "infinity", i.e., batch may have any size @@ -243,16 +261,10 @@ public void Send( private void DoSerialization(HttpEventCollectorEventInfo ei) { - string serializedEventInfo; - if (formatter == null) - { - serializedEventInfo = SerializeEventInfo(ei); - } - else + if (formatter != null) { var formattedEvent = formatter(ei); ei.Event = formattedEvent; - serializedEventInfo = JsonConvert.SerializeObject(ei); } // we use lock serializedEventsBatch to synchronize both @@ -260,7 +272,7 @@ private void DoSerialization(HttpEventCollectorEventInfo ei) lock (eventsBatchLock) { eventsBatch.Add(ei); - serializedEventsBatch.Append(serializedEventInfo); + serializedEventsBatch.Append(ei); if (eventsBatch.Count >= batchSizeCount || serializedEventsBatch.Length >= batchSizeBytes) { @@ -291,20 +303,9 @@ public void FlushSync() /// public Task FlushAsync() { - return new Task(() => - { - FlushSync(); - }); - } - - /// - /// Serialize event info into a json string - /// - /// - /// - public static string SerializeEventInfo(HttpEventCollectorEventInfo eventInfo) - { - return JsonConvert.SerializeObject(eventInfo); + var task = new Task(FlushSync); + task.Start(); + return task; } /// @@ -327,46 +328,58 @@ private void FlushInternal() return; // there is nothing to send // flush events according to the system operation mode + this.serializedEventsBatch.SupportOriginalBehaviour(); + + // post data and update tasks counter + Interlocked.Increment(ref activeAsyncTasksCount); + if (this.sendMode == SendMode.Sequential) - FlushInternalSequentialMode(this.eventsBatch, this.serializedEventsBatch.ToString()); + FlushInternalSequentialMode(this.eventsBatch, this.serializedEventsBatch); else - FlushInternalSingleBatch(this.eventsBatch, this.serializedEventsBatch.ToString()); + FlushInternalSingleBatch(this.eventsBatch, this.serializedEventsBatch); // we explicitly create new objects instead to clear and reuse // the old ones because Flush works in async mode // and can use "previous" containers - this.serializedEventsBatch = new StringBuilder(); + BuildBuffers(); + } + + private void BuildBuffers() + { this.eventsBatch = new List(); + this.serializedEventsBatch = bufferMode == BufferMode.StringBuilderBuffer + ? new StringBuilderBuffer() + : bufferMode == BufferMode.PushStreamInMemoryBuffer + ? (IBuffer)new PushStreamInMemoryBuffer() + : new TemporaryFileBatchBuffer(); } private void FlushInternalSequentialMode( List events, - String serializedEvents) + IBuffer serializedEventsBuilder) { // post events only after the current post task is done if (this.activePostTask == null) { this.activePostTask = Task.Factory.StartNew(() => { - FlushInternalSingleBatch(events, serializedEvents).Wait(); + FlushInternalSingleBatch(events, serializedEventsBuilder).Wait(); }); } else { this.activePostTask = this.activePostTask.ContinueWith((_) => { - FlushInternalSingleBatch(events, serializedEvents).Wait(); + FlushInternalSingleBatch(events, serializedEventsBuilder).Wait(); }); } } private Task FlushInternalSingleBatch( List events, - String serializedEvents) + IBuffer serializedEventsBuilder) { - // post data and update tasks counter - Interlocked.Increment(ref activeAsyncTasksCount); - Task task = PostEvents(events, serializedEvents); + Task task = PostEvents(events, serializedEventsBuilder); task.ContinueWith((_) => { Interlocked.Decrement(ref activeAsyncTasksCount); @@ -376,7 +389,7 @@ private Task FlushInternalSingleBatch( private async Task PostEvents( List events, - String serializedEvents) + IBuffer serializedEventsBuilder) { // encode data HttpResponseMessage response = null; @@ -385,18 +398,26 @@ private async Task PostEvents( try { // post data - HttpEventCollectorHandler next = (t, e) => + HttpEventCollectorHandler next = async (t, e) => { - HttpContent content = new StringContent(serializedEvents, Encoding.UTF8, HttpContentTypeMedia); - return httpClient.PostAsync(httpEventCollectorEndpointUri, content); + using (var content = serializedEventsBuilder.BuildHttpContent(HttpContentTypeMedia)) + { + var postResult = await httpClient.PostAsync(httpEventCollectorEndpointUri, content); + return postResult; + } }; HttpEventCollectorHandler postEvents = (t, e) => { - return middleware == null ? - next(t, e) : middleware(t, e, next); + return middleware == null ? next(t, e) : middleware(t, e, next); }; response = await postEvents(token, events); responseCode = response.StatusCode; + + if (response.IsSuccessStatusCode) + { + serializedEventsBuilder.Dispose(); + } + if (responseCode != HttpStatusCode.OK && response.Content != null) { // record server reply @@ -416,7 +437,7 @@ private async Task PostEvents( OnError(e); } catch (Exception e) - { + { OnError(new HttpEventCollectorException( code: responseCode, webException: e, @@ -425,6 +446,7 @@ private async Task PostEvents( events: events )); } + return responseCode; } @@ -436,6 +458,7 @@ private void OnTimer(object state) #region HttpClientHandler.IDispose private bool disposed = false; + private readonly BufferMode bufferMode; public void Dispose() { diff --git a/src/Splunk.Logging.Common/Splunk.Logging.Common.csproj b/src/Splunk.Logging.Common/Splunk.Logging.Common.csproj index d0e6016..9853e81 100644 --- a/src/Splunk.Logging.Common/Splunk.Logging.Common.csproj +++ b/src/Splunk.Logging.Common/Splunk.Logging.Common.csproj @@ -1,12 +1,13 @@ - net45 + net452;net6.0 Splunk, Inc. Copyright © Splunk, Inc. 2015 1.7.2.0 1.7.2.0 1.7.2 + Splunk.Logging diff --git a/src/Splunk.Logging.SLAB/Splunk.Logging.SLAB.csproj b/src/Splunk.Logging.SLAB/Splunk.Logging.SLAB.csproj index 95b5609..17e2be5 100644 --- a/src/Splunk.Logging.SLAB/Splunk.Logging.SLAB.csproj +++ b/src/Splunk.Logging.SLAB/Splunk.Logging.SLAB.csproj @@ -1,12 +1,12 @@  - net45 Splunk, Inc. Copyright © Splunk, Inc. 2015 1.7.2.0 1.7.2.0 1.7.2 + net452 diff --git a/src/Splunk.Logging.TraceListener/Splunk.Logging.TraceListener.csproj b/src/Splunk.Logging.TraceListener/Splunk.Logging.TraceListener.csproj index 7ea91f7..6e135d6 100644 --- a/src/Splunk.Logging.TraceListener/Splunk.Logging.TraceListener.csproj +++ b/src/Splunk.Logging.TraceListener/Splunk.Logging.TraceListener.csproj @@ -1,12 +1,12 @@  - net45 Splunk, Inc. Copyright © Splunk, Inc. 2015 1.7.2.0 1.7.2.0 1.7.2 + net452 diff --git a/standalone-test/Program.cs b/standalone-test/Program.cs index 1769fd5..463fdf1 100644 --- a/standalone-test/Program.cs +++ b/standalone-test/Program.cs @@ -1,6 +1,7 @@ using Splunk.Logging; using System; using System.Net; +using System.Threading.Tasks; namespace standalone_test { @@ -9,13 +10,13 @@ namespace standalone_test /// class Program { - static void Main(string[] args) + static async Task Main(string[] args) { - DoIt(); - Console.ReadLine(); + await DoIt(); + Console.WriteLine("Done"); } - static async void DoIt() + static async Task DoIt() { ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => { @@ -23,12 +24,43 @@ static async void DoIt() }; var middleware = new HttpEventCollectorResendMiddleware(100); - var ecSender = new HttpEventCollectorSender(new Uri("https://localhost:8088"), "92A93306-354C-46A5-9790-055C688EB0C4", null, HttpEventCollectorSender.SendMode.Sequential, 5000, 0, 0, middleware.Plugin); + var ecSender = new HttpEventCollectorSender( + new Uri("https://localhost:8088"), + "92A93306-354C-46A5-9790-055C688EB0C4", + null, + HttpEventCollectorSender.SendMode.Sequential, + 1000, + 0, + 1000, + middleware.Plugin, + bufferMode: HttpEventCollectorSender.BufferMode.StringBuilderBuffer); + ecSender.OnError += o => Console.WriteLine(o.Message); - ecSender.Send(DateTime.UtcNow.AddDays(-1), Guid.NewGuid().ToString(), "INFO", null, - new { Foo = "Bar", test2 = "Testit2", time = ConvertToEpoch(DateTime.UtcNow.AddHours(-2)).ToString(), anotherkey="anothervalue" }); - ecSender.Send(Guid.NewGuid().ToString(), "INFO", null, - new { Foo = "Bar", test2 = "Testit2", time = ConvertToEpoch(DateTime.UtcNow.AddHours(-2)).ToString(), anotherkey = "anothervalue!!" }); + + var rnd = new Random(Environment.TickCount); + for (var i = 0; i < 5000; i++) + { + for (var j = 0; j < rnd.Next(30, 50); j++) + { + ecSender.Send(DateTime.UtcNow.AddDays(-1), Guid.NewGuid().ToString(), "INFO", null, + new + { + Foo = "Bar", test2 = "Testit2", + time = ConvertToEpoch(DateTime.UtcNow.AddHours(-2)).ToString(), + anotherkey = "anothervalue" + }); + ecSender.Send(Guid.NewGuid().ToString(), "INFO", null, + new + { + Foo = "Bar", test2 = "Testit2", + time = ConvertToEpoch(DateTime.UtcNow.AddHours(-2)).ToString(), + anotherkey = "anothervalue!!" + }); + } + + Console.WriteLine(i.ToString()); + } + await ecSender.FlushAsync(); } @@ -44,4 +76,4 @@ private static void EcSender_OnError(HttpEventCollectorException obj) throw new NotImplementedException(); } } -} +} \ No newline at end of file diff --git a/standalone-test/standalone-test.csproj b/standalone-test/standalone-test.csproj index e1845ea..4f26ed8 100644 --- a/standalone-test/standalone-test.csproj +++ b/standalone-test/standalone-test.csproj @@ -9,7 +9,7 @@ Properties standalone_test standalone-test - v4.5 + v4.5.2 512 true diff --git a/test/unit-tests/TestHttpEventCollector.cs b/test/unit-tests/TestHttpEventCollector.cs index 5e179e7..986ea78 100644 --- a/test/unit-tests/TestHttpEventCollector.cs +++ b/test/unit-tests/TestHttpEventCollector.cs @@ -9,13 +9,14 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; using Xunit; namespace Splunk.Logging { public class TestHttpEventCollector { - private readonly Uri uri = new Uri("http://localhost:8089"); // a dummy uri + private readonly Uri uri = new Uri("http://localhost:5678"); // a dummy uri private const string token = "TOKEN-GUID"; #region Trace listener interceptor that replaces a real Splunk server for testing. @@ -271,7 +272,7 @@ public void HttpEventCollectorCoreTest() [Trait("integration-tests", "Splunk.Logging.HttpEventCollectorSerializationTest")] [Fact] - public void HttpEventCollectorSerializationTest() + public async Task HttpEventCollectorSerializationTest() { Func, Response> noopHandler = (token, events) => { @@ -337,7 +338,7 @@ public void HttpEventCollectorSerializationTest() trace.TraceInformation("hello2"); trace.TraceInformation("hello3"); - (trace.Listeners[trace.Listeners.Count - 1] as HttpEventCollectorTraceListener).FlushAsync().RunSynchronously(); + await (trace.Listeners[trace.Listeners.Count - 1] as HttpEventCollectorTraceListener).FlushAsync(); trace.Close(); Assert.Equal(numFormattedEvents, 3); @@ -394,7 +395,7 @@ public void HttpEventCollectorBatchingSizeTest() // estimate serialized event size HttpEventCollectorEventInfo ei = new HttpEventCollectorEventInfo(null, TraceEventType.Information.ToString(), "info ?", null, null); - int size = HttpEventCollectorSender.SerializeEventInfo(ei).Length; + int size = JsonConvert.SerializeObject(ei).Length; var trace = Trace( handler: (token, events) => @@ -557,7 +558,7 @@ public void HttpEventCollectorSinkBatchingTest() [Trait("integration-tests", "Splunk.Logging.HttpEventCollectorAsyncFlushTest")] [Fact] - public void HttpEventCollectorAsyncFlushTest() + public async Task HttpEventCollectorAsyncFlushTest() { var trace = Trace( handler: (token, events) => @@ -576,7 +577,7 @@ public void HttpEventCollectorAsyncFlushTest() trace.TraceInformation("info 3"); trace.TraceInformation("info 4"); HttpEventCollectorTraceListener listener = trace.Listeners[1] as HttpEventCollectorTraceListener; - listener.FlushAsync().RunSynchronously(); + await listener.FlushAsync(); } [Trait("integration-tests", "Splunk.Logging.HttpEventCollectorSeqModeTest")] diff --git a/test/unit-tests/unit-tests.csproj b/test/unit-tests/unit-tests.csproj index a826d43..9146710 100644 --- a/test/unit-tests/unit-tests.csproj +++ b/test/unit-tests/unit-tests.csproj @@ -1,7 +1,7 @@  - net45 + net452