Skip to content

Commit

Permalink
Add LogManager.Flush as a public API
Browse files Browse the repository at this point in the history
A similar feature was already present internally, this is an improved version which can be useful for external benchmarks.
  • Loading branch information
ltrzesniewski committed Jan 6, 2024
1 parent 4e22343 commit eff85fe
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/ZeroLog.Benchmarks/LatencyTests/LatencyBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void ZeroLog()

[IterationCleanup(Target = nameof(ZeroLog))]
public void CleanupZeroLogIteration()
=> LogManager.WaitUntilQueueIsEmpty();
=> LogManager.Flush();

//
// Serilog
Expand Down
19 changes: 16 additions & 3 deletions src/ZeroLog.Impl.Full/LogManager.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ public static IDisposable Initialize(ZeroLogConfiguration configuration)
public static void Shutdown()
=> _staticLogManager?.Dispose();

/// <summary>
/// Waits until all the messages logged so far have been processed by the appenders.
/// </summary>
/// <remarks>
/// <para>
/// This is mostly meant for benchmarking, to ensure all the enqueued data has been processed.
/// </para>
/// <para>
/// It could also be used for unit testing, but in that case it is recommended to set
/// the <see cref="ZeroLogConfiguration.AppendingStrategy"/> property in the configuration
/// to <see cref="AppendingStrategy.Synchronous"/> instead, which removes all threading-related issues.
/// </para>
/// </remarks>
public static void Flush()
=> _staticLogManager?._runner?.Flush();

/// <summary>
/// Registers an enum type.
/// Member names will be used when formatting the message (instead of numeric values).
Expand Down Expand Up @@ -202,7 +218,4 @@ private void UpdateAllLogConfigurations()

internal void WaitUntilNewConfigurationIsApplied() // For unit tests
=> _runner?.WaitUntilNewConfigurationIsApplied();

internal static void WaitUntilQueueIsEmpty() // For benchmarks
=> (_staticLogManager?._runner as AsyncRunner)?.WaitUntilQueueIsEmpty();
}
28 changes: 26 additions & 2 deletions src/ZeroLog.Impl.Full/Runner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ public void Dispose()
_appenders = [];

foreach (var appender in appenders)
{
appender.InternalFlush();
appender.Dispose();
}
}

public LogMessage AcquireLogMessage(LogMessagePoolExhaustionStrategy poolExhaustionStrategy)
Expand Down Expand Up @@ -116,6 +119,7 @@ public LogMessage AcquireLogMessage(LogMessagePoolExhaustionStrategy poolExhaust
public abstract void Submit(LogMessage message);

public abstract void UpdateConfiguration(ZeroLogConfiguration newConfig);
public abstract void Flush();
protected abstract void Stop();

internal virtual void WaitUntilNewConfigurationIsApplied() // For unit tests
Expand Down Expand Up @@ -173,6 +177,8 @@ protected void ApplyConfigurationUpdate(ZeroLogConfiguration newConfig)

internal sealed class AsyncRunner : Runner
{
private static readonly LogMessage _flushMessage = new(string.Empty);

private readonly ConcurrentQueue<LogMessage> _queue;
private readonly Thread _thread;

Expand Down Expand Up @@ -270,7 +276,11 @@ private bool TryToProcessQueue()
if (!_queue.TryDequeue(out var logMessage))
return false;

ProcessMessage(logMessage);
if (ReferenceEquals(logMessage, _flushMessage))
FlushAppenders();
else
ProcessMessage(logMessage);

return true;
}

Expand All @@ -285,9 +295,13 @@ private bool TryApplyConfigurationUpdate()
return true;
}

public void WaitUntilQueueIsEmpty()
public override void Flush()
{
if (!IsRunning)
return;

var spinWait = new SpinWait();
_queue.Enqueue(_flushMessage);

while (!_queue.IsEmpty)
spinWait.SpinOnce();
Expand Down Expand Up @@ -315,6 +329,16 @@ public override void UpdateConfiguration(ZeroLogConfiguration newConfig)
}
}

public override void Flush()
{
lock (_lock)
{
// An explicit flush should be a no-op with this runner,
// but the lock can cause an observable delay, so do it anyway.
FlushAppenders();
}
}

protected override void Stop()
{
}
Expand Down
4 changes: 2 additions & 2 deletions src/ZeroLog.Tests/AllocationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void should_not_allocate_using_all_formats_and_file_appender_builder()
{
if (i == warmupEvents)
{
LogManager.WaitUntilQueueIsEmpty();
LogManager.Flush();

allocationsOnLoggingThread = GC.GetAllocatedBytesForCurrentThread();
allocationsOnAppenderThread = _awaitableAppender.AllocatedBytesOnAppenderThread;
Expand Down Expand Up @@ -156,7 +156,7 @@ public void should_not_allocate_using_all_formats_and_file_appender_builder()
}

// Give the appender some time to finish writing to file
LogManager.WaitUntilQueueIsEmpty();
LogManager.Flush();

allocationsOnLoggingThread = GC.GetAllocatedBytesForCurrentThread() - allocationsOnLoggingThread;
allocationsOnAppenderThread = _awaitableAppender.AllocatedBytesOnAppenderThread - allocationsOnAppenderThread;
Expand Down
10 changes: 10 additions & 0 deletions src/ZeroLog.Tests/LogManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ public void should_log_all_remaining_messages_on_shutdown()
_testAppender.LoggedMessages.ShouldHaveSingleItem().ShouldEqual("test");
}

[Test]
public void should_flush()
{
var log = LogManager.GetLogger<LogManagerTests>();
log.Info("test");
LogManager.Flush();

_testAppender.LoggedMessages.ShouldHaveSingleItem().ShouldEqual("test");
}

[Test]
public void should_prevent_initializing_already_initialized_log_manager()
{
Expand Down
21 changes: 20 additions & 1 deletion src/ZeroLog.Tests/RunnerTests.Async.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using ZeroLog.Configuration;
using ZeroLog.Tests.Support;
Expand Down Expand Up @@ -61,6 +62,24 @@ public void should_flush_appenders_when_not_logging_messages()
Wait.Until(() => _testAppender.FlushCount == 2, TimeSpan.FromSeconds(1));
}

[Test]
public void should_flush_appenders_explicitly()
{
_testAppender.WaitOnWriteEvent = new ManualResetEventSlim(false);

_log.Info("Foo");
_log.Info("Bar");
_log.Info("Baz");

var flushTask = Task.Factory.StartNew(() => _runner.Flush(), TaskCreationOptions.LongRunning);
flushTask.Wait(TimeSpan.FromSeconds(1)).ShouldBeFalse();

_testAppender.WaitOnWriteEvent.Set();

flushTask.Wait(TimeSpan.FromSeconds(1)).ShouldBeTrue();
_testAppender.FlushCount.ShouldEqual(1);
}

[Test]
public void should_apply_configuration_updates()
{
Expand Down Expand Up @@ -91,7 +110,7 @@ public void should_not_log_pool_exhaustion_message_twice_in_a_row()
_runner.AcquireLogMessage(LogMessagePoolExhaustionStrategy.DropLogMessageAndNotifyAppenders).ShouldBeTheSameAs(LogMessage.Empty);

_runner.Submit(firstMessage);
_runner.WaitUntilQueueIsEmpty();
_runner.Flush();
Thread.Sleep(500);

_runner.AcquireLogMessage(LogMessagePoolExhaustionStrategy.DropLogMessageAndNotifyAppenders).ConstantMessage.ShouldBeNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@ namespace ZeroLog
{
public static ZeroLog.Configuration.ZeroLogConfiguration? Configuration { get; }
public void Dispose() { }
public static void Flush() { }
public static ZeroLog.Log GetLogger(string name) { }
public static ZeroLog.Log GetLogger(System.Type type) { }
public static ZeroLog.Log GetLogger<T>() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@ namespace ZeroLog
{
public static ZeroLog.Configuration.ZeroLogConfiguration? Configuration { get; }
public void Dispose() { }
public static void Flush() { }
public static ZeroLog.Log GetLogger(string name) { }
public static ZeroLog.Log GetLogger(System.Type type) { }
public static ZeroLog.Log GetLogger<T>() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@ namespace ZeroLog
{
public static ZeroLog.Configuration.ZeroLogConfiguration? Configuration { get; }
public void Dispose() { }
public static void Flush() { }
public static ZeroLog.Log GetLogger(string name) { }
public static ZeroLog.Log GetLogger(System.Type type) { }
public static ZeroLog.Log GetLogger<T>() { }
Expand Down

0 comments on commit eff85fe

Please sign in to comment.