From 8279ed4f71df3bdb7a52fe57facad0e2b01cee9e Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Wed, 8 Sep 2021 22:09:13 +0700 Subject: [PATCH] Make sure stdout-loglevel setting is honored through the whole actor system lifecycle (#5251) * Make sure stdout-loglevel setting is honored through the whole actor system lifecycle * Add settings spec for the new StandardOutLogger setting * Update API Approver list * Test logger can still output logs * Suppress LoggerInitialized from deadletter * Update API Approver list * Remove LogLevel.OffLevel for backward compatibility * Update API Approver list * Re-add OffLogLevel private const * Fix XML doc and previously hardwired StandardOutLogger type checking. * Add documentation to the new MinimalLogger implementation * Change equality to inheritance check * Add MinimalLogger spec Co-authored-by: Aaron Stannard --- docs/articles/utilities/logging.md | 22 +++++ .../Akka.TestKit.Xunit/Internals/Loggers.cs | 33 ++++++- .../CoreAPISpec.ApproveCore.approved.txt | 19 ++-- .../EventFilter/TestEventListener.cs | 94 ++++++++++--------- .../Configuration/ConfigurationSpec.cs | 2 + src/core/Akka.Tests/Loggers/LoggerSpec.cs | 24 +++++ .../Akka.Tests/Loggers/ShutdownLoggerSpec.cs | 84 +++++++++++++++++ src/core/Akka/Actor/Settings.cs | 31 ++++++ src/core/Akka/Configuration/Pigeon.conf | 4 + src/core/Akka/Event/DefaultLogger.cs | 26 +++-- src/core/Akka/Event/LoggerInitialized.cs | 2 +- src/core/Akka/Event/LoggerMailbox.cs | 8 +- src/core/Akka/Event/Logging.cs | 5 - src/core/Akka/Event/LoggingBus.cs | 22 ++--- src/core/Akka/Event/StandardOutLogger.cs | 76 ++++++++------- 15 files changed, 335 insertions(+), 117 deletions(-) create mode 100644 src/core/Akka.Tests/Loggers/ShutdownLoggerSpec.cs diff --git a/docs/articles/utilities/logging.md b/docs/articles/utilities/logging.md index bbe33664d2e..097212a5499 100644 --- a/docs/articles/utilities/logging.md +++ b/docs/articles/utilities/logging.md @@ -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 +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 diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/Loggers.cs b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/Loggers.cs index 66244c36e28..6b64b006915 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/Internals/Loggers.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Internals/Loggers.cs @@ -5,6 +5,7 @@ // //----------------------------------------------------------------------- +using System; using Akka.Actor; using Akka.Event; using Xunit.Abstractions; @@ -16,20 +17,44 @@ namespace Akka.TestKit.Xunit.Internals /// public class TestOutputLogger : ReceiveActor { + private readonly ITestOutputHelper _output; + /// /// Initializes a new instance of the class. /// /// The provider used to write test output. public TestOutputLogger(ITestOutputHelper output) { - Receive(e => output.WriteLine(e.ToString())); - Receive(e => output.WriteLine(e.ToString())); - Receive(e => output.WriteLine(e.ToString())); - Receive(e => output.WriteLine(e.ToString())); + _output = output; + + Receive(Write); + Receive(Write); + Receive(Write); + Receive(Write); Receive(e => { e.LoggingBus.Subscribe(Self, typeof (LogEvent)); }); } + + private void Write(LogEvent e) + { + try + { + _output.WriteLine(e.ToString()); + } + 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; + } + } } } diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt index a717c1e2fbc..32a68ea2cd0 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt @@ -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; } @@ -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() { } } @@ -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) { } @@ -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; @@ -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 { diff --git a/src/core/Akka.TestKit/EventFilter/TestEventListener.cs b/src/core/Akka.TestKit/EventFilter/TestEventListener.cs index a959d8c9da3..a42ca7b2fd5 100644 --- a/src/core/Akka.TestKit/EventFilter/TestEventListener.cs +++ b/src/core/Akka.TestKit/EventFilter/TestEventListener.cs @@ -30,57 +30,65 @@ public class TestEventListener : DefaultLogger /// TBD protected override bool Receive(object message) { - if(message is InitializeLogger) + 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; } diff --git a/src/core/Akka.Tests/Configuration/ConfigurationSpec.cs b/src/core/Akka.Tests/Configuration/ConfigurationSpec.cs index 7074746fa98..18654b6f9f9 100644 --- a/src/core/Akka.Tests/Configuration/ConfigurationSpec.cs +++ b/src/core/Akka.Tests/Configuration/ConfigurationSpec.cs @@ -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(); settings.LogConfigOnStart.ShouldBeFalse(); settings.LogDeadLetters.ShouldBe(10); settings.LogDeadLettersDuringShutdown.ShouldBeFalse(); diff --git a/src/core/Akka.Tests/Loggers/LoggerSpec.cs b/src/core/Akka.Tests/Loggers/LoggerSpec.cs index 72681e225a6..d7079a1de5a 100644 --- a/src/core/Akka.Tests/Loggers/LoggerSpec.cs +++ b/src/core/Akka.Tests/Loggers/LoggerSpec.cs @@ -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; @@ -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(); + await system.Terminate(); + } + + public class CustomLogger : MinimalLogger + { + protected override void Log(object message) + { + Console.WriteLine(message); + } + } + } } diff --git a/src/core/Akka.Tests/Loggers/ShutdownLoggerSpec.cs b/src/core/Akka.Tests/Loggers/ShutdownLoggerSpec.cs new file mode 100644 index 00000000000..796a4404d26 --- /dev/null +++ b/src/core/Akka.Tests/Loggers/ShutdownLoggerSpec.cs @@ -0,0 +1,84 @@ +// //----------------------------------------------------------------------- +// // +// // Copyright (C) 2009-2021 Lightbend Inc. +// // Copyright (C) 2013-2021 .NET Foundation +// // +// //----------------------------------------------------------------------- + +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() + { + Sys.Settings.StdoutLogger.Should().BeOfType(); + + var probeRef = Sys.ActorOf(Props.Create(() => new LogProbe())); + probeRef.Tell(new InitializeLogger(Sys.EventStream)); + var probe = await probeRef.Ask("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 Events { get; } = new List(); + + public LogProbe() + { + Receive(msg => Sender.Tell(this)); + Receive(Events.Add); + Receive(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."); + } + } +} \ No newline at end of file diff --git a/src/core/Akka/Actor/Settings.cs b/src/core/Akka/Actor/Settings.cs index 906c016e3a5..1ecf387cbdc 100644 --- a/src/core/Akka/Actor/Settings.cs +++ b/src/core/Akka/Actor/Settings.cs @@ -11,6 +11,7 @@ using Akka.Actor.Setup; using Akka.Configuration; using Akka.Dispatch; +using Akka.Event; using Akka.Routing; using ConfigurationFactory = Akka.Configuration.ConfigurationFactory; @@ -109,6 +110,31 @@ public Settings(ActorSystem system, Config config, ActorSystemSetup setup) LogLevel = Config.GetString("akka.loglevel", null); StdoutLogLevel = Config.GetString("akka.stdout-loglevel", null); + + var stdoutClassName = Config.GetString("akka.stdout-logger-class", null); + if (string.IsNullOrWhiteSpace(stdoutClassName)) + { + StdoutLogger = new StandardOutLogger(); + } + else + { + var stdoutLoggerType = Type.GetType(stdoutClassName); + if (stdoutLoggerType == null) + throw new ArgumentException($"Could not load type of {stdoutClassName} for standard out logger."); + if(!typeof(MinimalLogger).IsAssignableFrom(stdoutLoggerType)) + throw new ArgumentException("Standard out logger type must inherit from the MinimalLogger abstract class."); + + try + { + StdoutLogger = (MinimalLogger)Activator.CreateInstance(stdoutLoggerType); + } + catch (MissingMethodException) + { + throw new MissingMethodException( + "Standard out logger type must inherit from the MinimalLogger abstract class and have an empty constructor."); + } + } + Loggers = Config.GetStringList("akka.loggers", new string[] { }); LoggersDispatcher = Config.GetString("akka.loggers-dispatcher", null); LoggerStartTimeout = Config.GetTimeSpan("akka.logger-startup-timeout", null); @@ -247,6 +273,11 @@ public Settings(ActorSystem system, Config config, ActorSystemSetup setup) /// The stdout log level. public string StdoutLogLevel { get; private set; } + /// + /// Returns a singleton instance of the standard out logger. + /// + public MinimalLogger StdoutLogger { get; } + /// /// Gets the loggers. /// diff --git a/src/core/Akka/Configuration/Pigeon.conf b/src/core/Akka/Configuration/Pigeon.conf index a2d17b96a50..49a1fce3478 100644 --- a/src/core/Akka/Configuration/Pigeon.conf +++ b/src/core/Akka/Configuration/Pigeon.conf @@ -42,6 +42,10 @@ akka { # Log level for the very basic logger activated during AkkaApplication startup # Options: OFF, ERROR, WARNING, INFO, DEBUG stdout-loglevel = "WARNING" + + # Fully qualified class name (FQCN) of the very basic logger used on startup and shutdown. + # You can substitute the logger by supplying an FQCN to a custom class that implements Akka.Event.MinimumLogger + stdout-logger-class = "Akka.Event.StandardOutLogger" # Log the complete configuration at INFO level when the actor system is started. # This is useful when you are uncertain of what configuration is used. diff --git a/src/core/Akka/Event/DefaultLogger.cs b/src/core/Akka/Event/DefaultLogger.cs index 7d03e27dfef..730b238138b 100644 --- a/src/core/Akka/Event/DefaultLogger.cs +++ b/src/core/Akka/Event/DefaultLogger.cs @@ -16,6 +16,8 @@ namespace Akka.Event /// public class DefaultLogger : ActorBase, IRequiresMessageQueue { + private MinimalLogger _stdoutLogger; + /// /// TBD /// @@ -23,17 +25,18 @@ public class DefaultLogger : ActorBase, IRequiresMessageQueueTBD protected override bool Receive(object message) { - if (message is InitializeLogger) + switch (message) { - Sender.Tell(new LoggerInitialized()); - return true; + case InitializeLogger _: + _stdoutLogger = Context.System.Settings.StdoutLogger; + Sender.Tell(new LoggerInitialized()); + return true; + case LogEvent logEvent: + Print(logEvent); + return true; + default: + return false; } - var logEvent = message as LogEvent; - if (logEvent == null) - return false; - - Print(logEvent); - return true; } /// @@ -42,7 +45,10 @@ protected override bool Receive(object message) /// The log event that is to be output. protected virtual void Print(LogEvent logEvent) { - StandardOutLogger.PrintLogEvent(logEvent); + if (_stdoutLogger == null) + throw new Exception("Logger has not been initialized yet."); + + _stdoutLogger.Tell(logEvent); } } } diff --git a/src/core/Akka/Event/LoggerInitialized.cs b/src/core/Akka/Event/LoggerInitialized.cs index d7aa17a92ec..2fe9b3e0222 100644 --- a/src/core/Akka/Event/LoggerInitialized.cs +++ b/src/core/Akka/Event/LoggerInitialized.cs @@ -12,7 +12,7 @@ namespace Akka.Event /// /// This class represents a message used to notify subscribers that a logger has been initialized. /// - public class LoggerInitialized : INoSerializationVerificationNeeded + public class LoggerInitialized : INoSerializationVerificationNeeded, IDeadLetterSuppression { } } diff --git a/src/core/Akka/Event/LoggerMailbox.cs b/src/core/Akka/Event/LoggerMailbox.cs index fb8e5d7b518..51e32e25e87 100644 --- a/src/core/Akka/Event/LoggerMailbox.cs +++ b/src/core/Akka/Event/LoggerMailbox.cs @@ -82,15 +82,15 @@ void IMessageQueue.CleanUp(IActorRef owner, IMessageQueue deadletters) { if (HasMessages) { - Envelope envelope; - + var logger = _system.Settings.StdoutLogger; + // Drain all remaining messages to the StandardOutLogger. // CleanUp is called after switching out the mailbox, which is why // this kind of look works without a limit. - while (TryDequeue(out envelope)) + while (TryDequeue(out var envelope)) { // Logging.StandardOutLogger is a MinimalActorRef, i.e. not a "real" actor - Logging.StandardOutLogger.Tell(envelope.Message, envelope.Sender); + logger.Tell(envelope.Message, envelope.Sender); } } MessageQueue.CleanUp(owner, deadletters); diff --git a/src/core/Akka/Event/Logging.cs b/src/core/Akka/Event/Logging.cs index 05d334b8eac..a1104ed3dd5 100644 --- a/src/core/Akka/Event/Logging.cs +++ b/src/core/Akka/Event/Logging.cs @@ -147,11 +147,6 @@ public static string SimpleName(Type t) private const string Off = "OFF"; private const LogLevel OffLogLevel = (LogLevel) int.MaxValue; - /// - /// Returns a singleton instance of the standard out logger. - /// - public static readonly StandardOutLogger StandardOutLogger = new StandardOutLogger(); - /// /// Retrieves the log event class associated with the specified log level. /// diff --git a/src/core/Akka/Event/LoggingBus.cs b/src/core/Akka/Event/LoggingBus.cs index 2282eb6b3f8..fbe8e70149b 100644 --- a/src/core/Akka/Event/LoggingBus.cs +++ b/src/core/Akka/Event/LoggingBus.cs @@ -102,7 +102,7 @@ internal void StartDefaultLoggers(ActorSystemImpl system) throw new ConfigurationException($@"Logger specified in config cannot be found: ""{strLoggerType}"""); } - if (loggerType == typeof(StandardOutLogger)) + if (typeof(MinimalLogger).IsAssignableFrom(loggerType)) { shouldRemoveStandardOutLogger = false; continue; @@ -143,8 +143,9 @@ internal void StartDefaultLoggers(ActorSystemImpl system) if (shouldRemoveStandardOutLogger) { - Publish(new Debug(logName, GetType(), "StandardOutLogger being removed")); - Unsubscribe(Logging.StandardOutLogger); + var stdOutLogger = system.Settings.StdoutLogger; + Publish(new Debug(logName, GetType(), $"{Logging.SimpleName(stdOutLogger)} being removed")); + Unsubscribe(stdOutLogger); } Publish(new Debug(logName, GetType(), "Default Loggers started")); @@ -157,19 +158,18 @@ internal void StartDefaultLoggers(ActorSystemImpl system) internal void StopDefaultLoggers(ActorSystem system) { var level = LogLevel; // volatile access before reading loggers - if (!_loggers.Any(c => c is StandardOutLogger)) + if (!_loggers.Any(c => c is MinimalLogger)) { SetUpStdoutLogger(system.Settings); - Publish(new Debug(SimpleName(this), GetType(), "Shutting down: StandardOutLogger started")); + Publish(new Debug(SimpleName(this), GetType(), $"Shutting down: {Logging.SimpleName(system.Settings.StdoutLogger)} started")); } foreach (var logger in _loggers) { - if (!(logger is StandardOutLogger)) + if (!(logger is MinimalLogger)) { Unsubscribe(logger); - var internalActorRef = logger as IInternalActorRef; - if (internalActorRef != null) + if (logger is IInternalActorRef internalActorRef) { internalActorRef.Stop(); } @@ -214,9 +214,9 @@ private string CreateLoggerName(Type actorClass) } /// - /// Starts the logger. + /// Starts the logger. /// - /// The configuration used to configure the . + /// The configuration used to configure the . public void StartStdoutLogger(Settings config) { SetUpStdoutLogger(config); @@ -226,7 +226,7 @@ public void StartStdoutLogger(Settings config) private void SetUpStdoutLogger(Settings config) { var logLevel = Logging.LogLevelFor(config.StdoutLogLevel); - SubscribeLogLevelAndAbove(logLevel, Logging.StandardOutLogger); + SubscribeLogLevelAndAbove(logLevel, config.StdoutLogger); } /// diff --git a/src/core/Akka/Event/StandardOutLogger.cs b/src/core/Akka/Event/StandardOutLogger.cs index 6493c136fc4..b213d0e4bac 100644 --- a/src/core/Akka/Event/StandardOutLogger.cs +++ b/src/core/Akka/Event/StandardOutLogger.cs @@ -13,6 +13,38 @@ namespace Akka.Event { + public abstract class MinimalLogger : MinimalActorRef + { + /// + /// N/A + /// + /// This exception is automatically thrown since does not support this property. + public sealed override IActorRefProvider Provider + { + get { throw new NotSupportedException("This logger does not provide."); } + } + + /// + /// The path where this logger currently resides. + /// + public sealed override ActorPath Path { get; } = new RootActorPath(Address.AllSystems, "/StandardOutLogger"); + + protected sealed override void TellInternal(object message, IActorRef sender) + { + switch (message) + { + case null: + throw new ArgumentNullException(nameof(message), "The message to log must not be null."); + + default: + Log(message); + break; + } + } + + protected abstract void Log(object message); + } + /// /// This class represents an event logger that logs its messages to standard output (e.g. the console). /// @@ -21,9 +53,8 @@ namespace Akka.Event /// even before normal logging is started. /// /// - public class StandardOutLogger : MinimalActorRef + public class StandardOutLogger : MinimalLogger { - private readonly ActorPath _path = new RootActorPath(Address.AllSystems, "/StandardOutLogger"); /// /// Initializes the class. @@ -37,23 +68,6 @@ static StandardOutLogger() UseColors = true; } - /// - /// N/A - /// - /// This exception is automatically thrown since does not support this property. - public override IActorRefProvider Provider - { - get { throw new NotSupportedException("This logger does not provide."); } - } - - /// - /// The path where this logger currently resides. - /// - public override ActorPath Path - { - get { return _path; } - } - /// /// Handles incoming log events by printing them to the console. /// @@ -62,22 +76,20 @@ public override ActorPath Path /// /// This exception is thrown if the given is undefined. /// - protected override void TellInternal(object message, IActorRef sender) + protected override void Log(object message) { - if(message == null) - throw new ArgumentNullException(nameof(message), "The message to log must not be null."); - - var logEvent = message as LogEvent; - if (logEvent != null) + switch (message) { - PrintLogEvent(logEvent); - } - else - { - Console.WriteLine(message); + case LogEvent logEvent: + PrintLogEvent(logEvent); + break; + + default: + Console.WriteLine(message); + break; } } - + /// /// The foreground color to use when printing Debug events to the console. /// @@ -107,7 +119,7 @@ protected override void TellInternal(object message, IActorRef sender) /// Prints a specified event to the console. /// /// The event to print - public static void PrintLogEvent(LogEvent logEvent) + internal static void PrintLogEvent(LogEvent logEvent) { try {