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 1 commit
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
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;
}
}
}
}
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
97 changes: 97 additions & 0 deletions src/core/Akka.Tests/Loggers/ShutdownLoggerSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// //-----------------------------------------------------------------------
// // <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() : base(Config, new ThrowingOutputHelper())
{
}

[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 ThrowingOutputHelper : ITestOutputHelper
{
public void WriteLine(string message)
{
throw new ShutdownLogException();
}

public void WriteLine(string format, params object[] args)
{
throw new ShutdownLogException();
}
}

internal class ThrowingLogger : MinimalLogger
{
protected override void Log(object message)
{
throw new ShutdownLogException("This logger should never be called.");
}
}
}
31 changes: 31 additions & 0 deletions src/core/Akka/Actor/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved the hard-wired StandardOutLogger instance from the Logging class to the Settings class.
Default StandardOutLogger is now customizable through the HOCON configuration

Copy link
Member

Choose a reason for hiding this comment

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

I believe this was @zbynek001's suggestion in one of those threads - we should document this inside https://getakka.net/articles/utilities/logging.html#standard-loggers

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(
Copy link
Member

Choose a reason for hiding this comment

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

LGTM

"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);
Expand Down Expand Up @@ -247,6 +273,11 @@ public Settings(ActorSystem system, Config config, ActorSystemSetup setup)
/// <value>The stdout log level.</value>
public string StdoutLogLevel { get; private set; }

/// <summary>
/// Returns a singleton instance of the standard out logger.
/// </summary>
public MinimalLogger StdoutLogger { get; }

/// <summary>
/// Gets the loggers.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions src/core/Akka/Configuration/Pigeon.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

New HOCON setting to customize the standard out logger


# 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.
Expand Down
26 changes: 16 additions & 10 deletions src/core/Akka/Event/DefaultLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,27 @@ namespace Akka.Event
/// </summary>
public class DefaultLogger : ActorBase, IRequiresMessageQueue<ILoggerMessageQueueSemantics>
{
private MinimalLogger _stdoutLogger;

/// <summary>
/// TBD
/// </summary>
/// <param name="message">TBD</param>
/// <returns>TBD</returns>
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;
}

/// <summary>
Expand All @@ -42,7 +45,10 @@ protected override bool Receive(object message)
/// <param name="logEvent">The log event that is to be output.</param>
protected virtual void Print(LogEvent logEvent)
{
StandardOutLogger.PrintLogEvent(logEvent);
if (_stdoutLogger == null)
throw new Exception("Logger has not been initialized yet.");

_stdoutLogger.Tell(logEvent);
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/core/Akka/Event/LogLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public enum LogLevel
/// The error log level.
/// </summary>
ErrorLevel,

/// <summary>
/// The off log level.
/// </summary>
OffLevel = int.MaxValue
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added OffLevel to LogLevel enum, this would act as a marker for the off state

Copy link
Member

Choose a reason for hiding this comment

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

I'd revert this change - it's a major part of the public API and will require an update to all downstream loggers probably.

}
}

Loading