Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sure stdout-loglevel setting is honored through the whole actor system lifecycle #5251

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0289b2b
Make sure stdout-loglevel setting is honored through the whole actor …
Arkatufus Sep 3, 2021
3f962db
Merge branch 'dev' into Add_new_StandardOutLogger_features
Arkatufus Sep 3, 2021
a2e2505
Add settings spec for the new StandardOutLogger setting
Arkatufus Sep 3, 2021
b36f917
Update API Approver list
Arkatufus Sep 3, 2021
38b93b2
Test logger can still output logs
Arkatufus Sep 3, 2021
a444df7
Suppress LoggerInitialized from deadletter
Arkatufus Sep 6, 2021
135ec8b
Update API Approver list
Arkatufus Sep 6, 2021
ba1d0d4
Remove LogLevel.OffLevel for backward compatibility
Arkatufus Sep 6, 2021
16db579
Update API Approver list
Arkatufus Sep 6, 2021
086a2bf
Merge branch 'dev' into Add_new_StandardOutLogger_features
Aaronontheweb Sep 6, 2021
bdcb56d
Re-add OffLogLevel private const
Arkatufus Sep 6, 2021
bf5ea5a
Merge branch 'Add_new_StandardOutLogger_features' of github.com:Arkat…
Arkatufus Sep 6, 2021
926aab3
Merge branch 'dev' into Add_new_StandardOutLogger_features
Arkatufus Sep 6, 2021
42d5d59
Merge branch 'dev' into Add_new_StandardOutLogger_features
Arkatufus Sep 6, 2021
a09a755
Fix XML doc and previously hardwired StandardOutLogger type checking.
Arkatufus Sep 6, 2021
c1a739a
Add documentation to the new MinimalLogger implementation
Arkatufus Sep 6, 2021
fd223c3
Merge branch 'dev' into Add_new_StandardOutLogger_features
Arkatufus Sep 6, 2021
52d5b3c
Merge branch 'dev' into Add_new_StandardOutLogger_features
Arkatufus Sep 6, 2021
7babaf3
Merge branch 'dev' into Add_new_StandardOutLogger_features
Arkatufus Sep 7, 2021
541d9dd
Merge branch 'dev' into Add_new_StandardOutLogger_features
Aaronontheweb Sep 7, 2021
8e196c9
Change equality to inheritance check
Arkatufus Sep 7, 2021
2108dfe
Add MinimalLogger spec
Arkatufus Sep 7, 2021
783d192
Merge branch 'dev' into Add_new_StandardOutLogger_features
Arkatufus Sep 7, 2021
0d24266
Merge branch 'dev' into Add_new_StandardOutLogger_features
Arkatufus Sep 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/articles/utilities/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,28 @@ Akka.NET comes with two built in loggers.
* __StandardOutLogger__
* __BusLogging__

### StandardOutLogger
`StandardOutLogger` is considered as a minimal logger and implements the `MinimalLogger` abstract
class. Its job is simply to output all `LogEvent`s emitted by the `EventBus` onto the console.
Since it is not an actual actor, ie. it doesn't need the `ActorSystem` to operate, it is also
used to log other loggers activity at the very start and very end of the `ActorSystem` life cycle.
You can change the minimal logger start and end life cycle behaviour by changing the
`akka.stdout-loglevel` HOCON settings to `OFF` if you do not need these feature in your application.

### Advanced MinimalLogger Setup
You can also replace `StandardOutLogger` by making your own logger class with an empty constructor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need to do more showing rather than telling here, but this is good for now

that inherits/implements the `MinimalLogger` abstract class and passing the fully qualified class
name into the `akka.stdout-logger-class` HOCON settings.

> [!WARNING]
> Be aware that `MinimalLogger` implementations are __NOT__ real actors and will __NOT__ have any
> access to the `ActorSystem` and all of its extensions. All logging done inside a `MinimalLogger`
> have to be done in as simple as possible manner since it is used to log how other loggers are
> behaving at the very start and very end of the `ActorSystem` life cycle.
>
> Note that `MinimalLogger` are __NOT__ interchangeable with other Akka.NET loggers and there can
> only be one `MinimalLogger` registered with the `ActorSystem` in the HOCON settings.

## Contrib Loggers
These loggers are also available as separate nuget packages

Expand Down
33 changes: 29 additions & 4 deletions src/contrib/testkits/Akka.TestKit.Xunit/Internals/Loggers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// </copyright>
//-----------------------------------------------------------------------

using System;
using Akka.Actor;
using Akka.Event;
using Xunit.Abstractions;
Expand All @@ -16,20 +17,44 @@ namespace Akka.TestKit.Xunit.Internals
/// </summary>
public class TestOutputLogger : ReceiveActor
{
private readonly ITestOutputHelper _output;

/// <summary>
/// Initializes a new instance of the <see cref="TestOutputLogger"/> class.
/// </summary>
/// <param name="output">The provider used to write test output.</param>
public TestOutputLogger(ITestOutputHelper output)
{
Receive<Debug>(e => output.WriteLine(e.ToString()));
Receive<Info>(e => output.WriteLine(e.ToString()));
Receive<Warning>(e => output.WriteLine(e.ToString()));
Receive<Error>(e => output.WriteLine(e.ToString()));
_output = output;

Receive<Debug>(Write);
Receive<Info>(Write);
Receive<Warning>(Write);
Receive<Error>(Write);
Receive<InitializeLogger>(e =>
{
e.LoggingBus.Subscribe(Self, typeof (LogEvent));
});
}

private void Write(LogEvent e)
{
try
{
_output.WriteLine(e.ToString());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add missing format exception catcher to Akka.TestKit.Xunit that was added to Akka.TestKit.Xunit2

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

}
catch (FormatException ex)
{
if (e.Message is LogMessage msg)
{
var message =
$"Received a malformed formatted message. Log level: [{e.LogLevel()}], Template: [{msg.Format}], args: [{string.Join(",", msg.Args)}]";
if(e.Cause != null)
throw new AggregateException(message, ex, e.Cause);
throw new FormatException(message, ex);
}
throw;
}
}
}
}
19 changes: 12 additions & 7 deletions src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,7 @@ namespace Akka.Actor
public bool SerializeAllMessages { get; }
public Akka.Actor.Setup.ActorSystemSetup Setup { get; }
public string StdoutLogLevel { get; }
public Akka.Event.MinimalLogger StdoutLogger { get; }
public string SupervisorStrategyClass { get; }
public Akka.Actor.ActorSystem System { get; }
public System.TimeSpan UnstartedPushTimeout { get; }
Expand Down Expand Up @@ -3089,7 +3090,7 @@ namespace Akka.Event
public static string FromType(System.Type t, Akka.Actor.ActorSystem system) { }
public static System.Type SourceType(object o) { }
}
public class LoggerInitialized : Akka.Actor.INoSerializationVerificationNeeded
public class LoggerInitialized : Akka.Actor.INoSerializationVerificationNeeded, Akka.Event.IDeadLetterSuppression
{
public LoggerInitialized() { }
}
Expand All @@ -3099,7 +3100,6 @@ namespace Akka.Event
}
public class static Logging
{
public static readonly Akka.Event.StandardOutLogger StandardOutLogger;
public static System.Type ClassFor(this Akka.Event.LogLevel logLevel) { }
public static Akka.Event.ILoggingAdapter GetLogger(this Akka.Actor.IActorContext context, Akka.Event.ILogMessageFormatter logMessageFormatter = null) { }
public static Akka.Event.ILoggingAdapter GetLogger(Akka.Actor.ActorSystem system, object logSourceObj, Akka.Event.ILogMessageFormatter logMessageFormatter = null) { }
Expand Down Expand Up @@ -3152,6 +3152,14 @@ namespace Akka.Event
public void SetLogLevel(Akka.Event.LogLevel logLevel) { }
public void StartStdoutLogger(Akka.Actor.Settings config) { }
}
public abstract class MinimalLogger : Akka.Actor.MinimalActorRef
{
protected MinimalLogger() { }
public virtual Akka.Actor.ActorPath Path { get; }
public virtual Akka.Actor.IActorRefProvider Provider { get; }
protected abstract void Log(object message);
protected virtual void TellInternal(object message, Akka.Actor.IActorRef sender) { }
}
public sealed class NoLogger : Akka.Event.ILoggingAdapter
{
public static readonly Akka.Event.ILoggingAdapter Instance;
Expand All @@ -3172,18 +3180,15 @@ namespace Akka.Event
public void Warning(string format, params object[] args) { }
public void Warning(System.Exception cause, string format, params object[] args) { }
}
public class StandardOutLogger : Akka.Actor.MinimalActorRef
public class StandardOutLogger : Akka.Event.MinimalLogger
{
public StandardOutLogger() { }
public static System.ConsoleColor DebugColor { get; set; }
public static System.ConsoleColor ErrorColor { get; set; }
public static System.ConsoleColor InfoColor { get; set; }
public override Akka.Actor.ActorPath Path { get; }
public override Akka.Actor.IActorRefProvider Provider { get; }
public static bool UseColors { get; set; }
public static System.ConsoleColor WarningColor { get; set; }
public static void PrintLogEvent(Akka.Event.LogEvent logEvent) { }
protected override void TellInternal(object message, Akka.Actor.IActorRef sender) { }
protected override void Log(object message) { }
}
public class Subscription<TSubscriber, TClassifier>
{
Expand Down
94 changes: 51 additions & 43 deletions src/core/Akka.TestKit/EventFilter/TestEventListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,57 +30,65 @@ public class TestEventListener : DefaultLogger
/// <returns>TBD</returns>
protected override bool Receive(object message)
{
if(message is InitializeLogger)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change if...then...else structure to switch

switch (message)
{
var initLogger = (InitializeLogger)message;
var bus = initLogger.LoggingBus;
var self = Context.Self;
bus.Subscribe(self, typeof(Mute));
bus.Subscribe(self, typeof(Unmute));
bus.Subscribe(self, typeof(DeadLetter));
bus.Subscribe(self, typeof(UnhandledMessage));
Sender.Tell(new LoggerInitialized());
}
else if(message is Mute)
{
var mute = (Mute)message;
foreach(var filter in mute.Filters)
case InitializeLogger initLogger:
{
AddFilter(filter);
base.Receive(message);
var bus = initLogger.LoggingBus;
var self = Context.Self;
bus.Subscribe(self, typeof(Mute));
bus.Subscribe(self, typeof(Unmute));
bus.Subscribe(self, typeof(DeadLetter));
bus.Subscribe(self, typeof(UnhandledMessage));
Sender.Tell(new LoggerInitialized());
break;
}
}
else if(message is Unmute)
{
var unmute = (Unmute)message;
foreach(var filter in unmute.Filters)
case Mute mute:
{
RemoveFilter(filter);
foreach(var filter in mute.Filters)
{
AddFilter(filter);
}

break;
}
}
else if(message is LogEvent)
{
var logEvent = (LogEvent)message;
if(!ShouldFilter(logEvent))
case Unmute unmute:
{
Print(logEvent);
foreach(var filter in unmute.Filters)
{
RemoveFilter(filter);
}

break;
}
case LogEvent logEvent:
{
if(!ShouldFilter(logEvent))
{
Print(logEvent);
}

break;
}
case DeadLetter letter:
HandleDeadLetter(letter);
break;

case UnhandledMessage un:
{
var rcp = un.Recipient;
var warning = new Warning(rcp.Path.ToString(), rcp.GetType(), "Unhandled message from " + un.Sender + ": " + un.Message);
if(!ShouldFilter(warning))
Print(warning);
break;
}

default:
Print(new Debug(Context.System.Name,GetType(),message));
break;
}
else if(message is DeadLetter)
{
HandleDeadLetter((DeadLetter)message);
}
else if(message is UnhandledMessage)
{
var un = (UnhandledMessage) message;
var rcp = un.Recipient;
var warning = new Warning(rcp.Path.ToString(), rcp.GetType(), "Unhandled message from " + un.Sender + ": " + un.Message);
if(!ShouldFilter(warning))
Print(warning);
}
else
{
Print(new Debug(Context.System.Name,GetType(),message));
}

return true;
}

Expand Down
2 changes: 2 additions & 0 deletions src/core/Akka.Tests/Configuration/ConfigurationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public void The_default_configuration_file_contain_all_configuration_properties(
settings.LoggerStartTimeout.Seconds.ShouldBe(5);
settings.LogLevel.ShouldBe("INFO");
settings.StdoutLogLevel.ShouldBe("WARNING");
settings.StdoutLogger.Should().NotBeNull();
settings.StdoutLogger.Should().BeOfType<StandardOutLogger>();
settings.LogConfigOnStart.ShouldBeFalse();
settings.LogDeadLetters.ShouldBe(10);
settings.LogDeadLettersDuringShutdown.ShouldBeFalse();
Expand Down
24 changes: 24 additions & 0 deletions src/core/Akka.Tests/Loggers/LoggerSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Actor.Internal;
using Akka.Actor.Setup;
using Akka.Configuration;
using Akka.Event;
using Akka.TestKit;
Expand Down Expand Up @@ -142,5 +145,26 @@ private class FakeException : Exception
public FakeException(string message) : base(message)
{ }
}

[Fact]
public async Task ShouldBeAbleToReplaceStandardOutLoggerWithCustomMinimalLogger()
{
var config = ConfigurationFactory
.ParseString("akka.stdout-logger-class = \"Akka.Tests.Loggers.LoggerSpec+CustomLogger, Akka.Tests\"")
.WithFallback(ConfigurationFactory.Default());

var system = ActorSystem.Create("MinimalLoggerTest", config);
system.Settings.StdoutLogger.Should().BeOfType<CustomLogger>();
await system.Terminate();
}

public class CustomLogger : MinimalLogger
{
protected override void Log(object message)
{
Console.WriteLine(message);
}
}

}
}
84 changes: 84 additions & 0 deletions src/core/Akka.Tests/Loggers/ShutdownLoggerSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// //-----------------------------------------------------------------------
// // <copyright file="ShutdownLoggerSpec.cs" company="Akka.NET Project">
// // Copyright (C) 2009-2021 Lightbend Inc. <http://www.lightbend.com>
// // Copyright (C) 2013-2021 .NET Foundation <https://github.com/akkadotnet/akka.net>
// // </copyright>
// //-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Configuration;
using Akka.Event;
using Akka.TestKit;
using FluentAssertions;
using Xunit;
using Xunit.Abstractions;

namespace Akka.Tests.Loggers
{
public class ShutdownLoggerSpec: AkkaSpec
{
private static readonly Config Config = ConfigurationFactory.ParseString(@"
akka.loglevel = OFF
akka.stdout-loglevel = OFF
akka.stdout-logger-class = ""Akka.Tests.Loggers.ThrowingLogger, Akka.Tests""");

public ShutdownLoggerSpec(ITestOutputHelper output) : base(Config, output)
{
}

[Fact(DisplayName = "StandardOutLogger should not be called on shutdown when stdout-loglevel is set to OFF")]
public async Task StandardOutLoggerShouldNotBeCalled()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

{
Sys.Settings.StdoutLogger.Should().BeOfType<ThrowingLogger>();

var probeRef = Sys.ActorOf(Props.Create(() => new LogProbe()));
probeRef.Tell(new InitializeLogger(Sys.EventStream));
var probe = await probeRef.Ask<LogProbe>("hey");

await Sys.Terminate();

await Task.Delay(RemainingOrDefault);
if (probe.Events.Any(o => o is Error err && err.Cause is ShutdownLogException))
throw new Exception("Test failed, log should not be called after shutdown.");
}
}

internal class LogProbe : ReceiveActor
{
public List<LogEvent> Events { get; } = new List<LogEvent>();

public LogProbe()
{
Receive<string>(msg => Sender.Tell(this));
Receive<LogEvent>(Events.Add);
Receive<InitializeLogger>(e =>
{
e.LoggingBus.Subscribe(Self, typeof (LogEvent));
Sender.Tell(new LoggerInitialized());
});

}
}

internal class ShutdownLogException : Exception
{
public ShutdownLogException()
{ }

public ShutdownLogException(string msg) : base(msg)
{ }
}

internal class ThrowingLogger : MinimalLogger
{
protected override void Log(object message)
{
throw new ShutdownLogException("This logger should never be called.");
}
}
}
Loading