Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
534 changes: 534 additions & 0 deletions src/benchmark/Akka.Benchmarks/Logging/SemanticLoggingBenchmarks.cs

Large diffs are not rendered by default.

80 changes: 73 additions & 7 deletions src/core/Akka.API.Tests/LogFormatSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public DefaultLogFormatSpec() : base(CustomLoggerSetup())
{
_logger = (CustomLogger)Sys.Settings.StdoutLogger;
}

private readonly CustomLogger _logger;

public class CustomLogger : StandardOutLogger
{
protected override void Log(object message)
Expand All @@ -44,13 +44,13 @@ protected override void Log(object message)
{
_events.Add(e);
}

}

private readonly ConcurrentBag<LogEvent> _events = new();
public IReadOnlyCollection<LogEvent> Events => _events;
}

public static ActorSystemSetup CustomLoggerSetup()
{
var hocon = @$"
Expand Down Expand Up @@ -109,10 +109,76 @@ await AwaitConditionAsync(() =>
text = SanitizeThreadNumber(text);
// to resolve https://github.com/akkadotnet/akka.net/issues/7421
text = SanitizeTestEventListener(text);

await Verifier.Verify(text);
}


[Fact]
public async Task ShouldHandleSemanticLogEdgeCases()
{
// arrange
var filePath = Path.GetTempFileName();

// act
using (new OutputRedirector(filePath))
{
// Named properties
Sys.Log.Debug("User {UserId} logged in from {IpAddress}", 12345, "192.168.1.1");
Sys.Log.Info("Processing order {OrderId} for customer {CustomerId}", "ORD-001", "CUST-999");

// Positional properties (old style)
Sys.Log.Warning("Processing item {0} of {1}", 5, 10);

// Mixed types - use F2 instead of C for culture-independent output
Sys.Log.Info("Order total is ${Amount:F2} with {ItemCount} items", 123.45m, 3);

// Edge cases
Sys.Log.Debug("Empty template");
Sys.Log.Info("Single property {Value}", 42);
Sys.Log.Warning("Null value: {NullValue}", null);
Sys.Log.Error("Exception occurred for user {UserId}", 999);

// Special characters and escaping
Sys.Log.Debug("Path: {FilePath}, Size: {FileSize} bytes", @"C:\temp\file.txt", 1024);

// Boolean and date types - use explicit date format for culture-independent output
Sys.Log.Info("User {Username} is active: {IsActive}, joined on {JoinDate:yyyy-MM-dd}", "john.doe", true, DateTime.Parse("2024-01-15"));

// Long strings and alignment
Sys.Log.Debug("Request from {RemoteAddress} to endpoint {Endpoint} took {DurationMs}ms", "192.168.1.100:54321", "/api/v1/users", 250);

// force all logs to be received - wait for the last log message
await AwaitConditionAsync(() => Task.FromResult(_logger.Events.Any(e => e.Message.ToString()!.Contains("took 250ms"))), TimeSpan.FromSeconds(5));
}

// assert
// ReSharper disable once MethodHasAsyncOverload
var text = File.ReadAllText(filePath);

// need to sanitize the thread id and timestamps
text = SanitizeDateTime(text);
text = SanitizeThreadNumber(text);
text = SanitizeTestEventListener(text);
text = SanitizeDefaultLoggersStarted(text);
text = SanitizeCustomLoggerRemoved(text);

await Verifier.Verify(text);
}

private static string SanitizeDefaultLoggersStarted(string logs)
{
var pattern = @"^.*Default Loggers started.*$\r?\n?";
var result = Regex.Replace(logs, pattern, string.Empty, RegexOptions.Multiline);
return result;
}

private static string SanitizeCustomLoggerRemoved(string logs)
{
var pattern = @"^.*CustomLogger being removed.*$\r?\n?";
var result = Regex.Replace(logs, pattern, string.Empty, RegexOptions.Multiline);
return result;
}

private static string SanitizeTestEventListener(string logs)
{
var pattern = @"^.*Akka\.TestKit\.TestEventListener.*$";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3483,6 +3483,17 @@ namespace Akka.Event
public abstract Akka.Event.LogLevel LogLevel();
public override string ToString() { }
}
[System.Runtime.CompilerServices.NullableAttribute(0)]
public class static LogEventExtensions
{
public static System.Collections.Generic.IEnumerable<object> GetParameters(this Akka.Event.LogEvent evt) { }
public static System.Collections.Generic.IReadOnlyList<string> GetPropertyNames(this Akka.Event.LogEvent evt) { }
public static string GetTemplate(this Akka.Event.LogEvent evt) { }
public static bool TryGetProperties(this Akka.Event.LogEvent evt, [System.Runtime.CompilerServices.NullableAttribute(new byte[] {
2,
1,
1})] out System.Collections.Generic.IReadOnlyDictionary<string, object> properties) { }
}
public abstract class LogFilterBase : Akka.Actor.INoSerializationVerificationNeeded, Akka.Event.IDeadLetterSuppression
{
protected LogFilterBase() { }
Expand Down Expand Up @@ -3542,6 +3553,8 @@ namespace Akka.Event
protected readonly Akka.Event.ILogMessageFormatter Formatter;
public LogMessage(Akka.Event.ILogMessageFormatter formatter, string format) { }
public string Format { get; }
public System.Collections.Generic.IReadOnlyList<string> PropertyNames { get; }
public System.Collections.Generic.IReadOnlyDictionary<string, object> GetProperties() { }
[Akka.Annotations.InternalApiAttribute()]
public abstract System.Collections.Generic.IEnumerable<object> Parameters();
[Akka.Annotations.InternalApiAttribute()]
Expand Down Expand Up @@ -3719,6 +3732,12 @@ namespace Akka.Event
public override Akka.Event.LogFilterType FilterType { get; }
public override Akka.Event.LogFilterDecision ShouldKeepMessage(Akka.Event.LogEvent content, [System.Runtime.CompilerServices.NullableAttribute(2)] string expandedMessage = null) { }
}
public sealed class SemanticLogMessageFormatter : Akka.Event.ILogMessageFormatter
{
public static readonly Akka.Event.SemanticLogMessageFormatter Instance;
public string Format(string format, params object[] args) { }
public string Format(string format, System.Collections.Generic.IEnumerable<object> args) { }
}
public class StandardOutLogger : Akka.Event.MinimalLogger
{
public StandardOutLogger() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3474,6 +3474,17 @@ namespace Akka.Event
public abstract Akka.Event.LogLevel LogLevel();
public override string ToString() { }
}
[System.Runtime.CompilerServices.NullableAttribute(0)]
public class static LogEventExtensions
{
public static System.Collections.Generic.IEnumerable<object> GetParameters(this Akka.Event.LogEvent evt) { }
public static System.Collections.Generic.IReadOnlyList<string> GetPropertyNames(this Akka.Event.LogEvent evt) { }
public static string GetTemplate(this Akka.Event.LogEvent evt) { }
public static bool TryGetProperties(this Akka.Event.LogEvent evt, [System.Runtime.CompilerServices.NullableAttribute(new byte[] {
2,
1,
1})] out System.Collections.Generic.IReadOnlyDictionary<string, object> properties) { }
}
public abstract class LogFilterBase : Akka.Actor.INoSerializationVerificationNeeded, Akka.Event.IDeadLetterSuppression
{
protected LogFilterBase() { }
Expand Down Expand Up @@ -3533,6 +3544,8 @@ namespace Akka.Event
protected readonly Akka.Event.ILogMessageFormatter Formatter;
public LogMessage(Akka.Event.ILogMessageFormatter formatter, string format) { }
public string Format { get; }
public System.Collections.Generic.IReadOnlyList<string> PropertyNames { get; }
public System.Collections.Generic.IReadOnlyDictionary<string, object> GetProperties() { }
[Akka.Annotations.InternalApiAttribute()]
public abstract System.Collections.Generic.IEnumerable<object> Parameters();
[Akka.Annotations.InternalApiAttribute()]
Expand Down Expand Up @@ -3708,6 +3721,12 @@ namespace Akka.Event
public override Akka.Event.LogFilterType FilterType { get; }
public override Akka.Event.LogFilterDecision ShouldKeepMessage(Akka.Event.LogEvent content, [System.Runtime.CompilerServices.NullableAttribute(2)] string expandedMessage = null) { }
}
public sealed class SemanticLogMessageFormatter : Akka.Event.ILogMessageFormatter
{
public static readonly Akka.Event.SemanticLogMessageFormatter Instance;
public string Format(string format, params object[] args) { }
public string Format(string format, System.Collections.Generic.IEnumerable<object> args) { }
}
public class StandardOutLogger : Akka.Event.MinimalLogger
{
public StandardOutLogger() { }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[DEBUG][DateTime][Thread 0001][ActorSystem(test)] User 12345 logged in from 192.168.1.1
[INFO][DateTime][Thread 0001][ActorSystem(test)] Processing order ORD-001 for customer CUST-999
[WARNING][DateTime][Thread 0001][ActorSystem(test)] Processing item 5 of 10
[INFO][DateTime][Thread 0001][ActorSystem(test)] Order total is $123.45 with 3 items
[DEBUG][DateTime][Thread 0001][ActorSystem(test)] Empty template
[INFO][DateTime][Thread 0001][ActorSystem(test)] Single property 42
[WARNING][DateTime][Thread 0001][ActorSystem(test)] Null value: {NullValue}
[ERROR][DateTime][Thread 0001][ActorSystem(test)] Exception occurred for user 999
[DEBUG][DateTime][Thread 0001][ActorSystem(test)] Path: C:\temp\file.txt, Size: 1024 bytes
[INFO][DateTime][Thread 0001][ActorSystem(test)] User john.doe is active: True, joined on 2024-01-15
[DEBUG][DateTime][Thread 0001][ActorSystem(test)] Request from 192.168.1.100:54321 to endpoint /api/v1/users took 250ms
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[DEBUG][DateTime][Thread 0001][ActorSystem(test)] User 12345 logged in from 192.168.1.1
[INFO][DateTime][Thread 0001][ActorSystem(test)] Processing order ORD-001 for customer CUST-999
[WARNING][DateTime][Thread 0001][ActorSystem(test)] Processing item 5 of 10
[INFO][DateTime][Thread 0001][ActorSystem(test)] Order total is $123.45 with 3 items
[DEBUG][DateTime][Thread 0001][ActorSystem(test)] Empty template
[INFO][DateTime][Thread 0001][ActorSystem(test)] Single property 42
[WARNING][DateTime][Thread 0001][ActorSystem(test)] Null value: {NullValue}
[ERROR][DateTime][Thread 0001][ActorSystem(test)] Exception occurred for user 999
[DEBUG][DateTime][Thread 0001][ActorSystem(test)] Path: C:\temp\file.txt, Size: 1024 bytes
[INFO][DateTime][Thread 0001][ActorSystem(test)] User john.doe is active: True, joined on 2024-01-15
[DEBUG][DateTime][Thread 0001][ActorSystem(test)] Request from 192.168.1.100:54321 to endpoint /api/v1/users took 250ms
26 changes: 22 additions & 4 deletions src/core/Akka.TestKit/EventFilter/Internal/EventFilterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ namespace Akka.TestKit.Internal
/// <param name="logEvent">TBD</param>
public delegate void EventMatched(EventFilterBase eventFilter, LogEvent logEvent);

/// <summary>Internal!
/// <summary>Internal!
/// Facilities for selectively filtering out expected events from logging so
/// that you can keep your test runs console output clean and do not miss real
/// that you can keep your test run's console output clean and do not miss real
/// error messages.
/// <remarks>Note! Part of internal API. Breaking changes may occur without notice. Use at own risk.</remarks>
/// </summary>
Expand Down Expand Up @@ -86,8 +86,26 @@ protected virtual void OnEventMatched(LogEvent logEvent)
/// <returns>TBD</returns>
protected bool InternalDoMatch(string src, object msg)
{
var msgstr = msg == null ? "null" : msg.ToString();
return _sourceMatcher.IsMatch(src) && _messageMatcher.IsMatch(msgstr);
// Check source matcher first (fast path)
if (!_sourceMatcher.IsMatch(src))
return false;

// For semantic logging support, try matching against both the formatted message
// and the unformatted template pattern
if (msg is LogMessage logMessage)
{
// Try matching against the template pattern first (e.g., "User {UserId} logged in")
if (_messageMatcher.IsMatch(logMessage.Format))
return true;

// Fall back to matching the formatted message (e.g., "User 12345 logged in")
var formattedMsg = logMessage.ToString() ?? "null";
return _messageMatcher.IsMatch(formattedMsg);
}

// Non-semantic logging or legacy messages
var msgstr = msg == null ? "null" : msg.ToString() ?? "null";
return _messageMatcher.IsMatch(msgstr);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/core/Akka.Tests/Configuration/ConfigurationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void The_default_configuration_file_contain_all_configuration_properties(
settings.LogDeadLetters.ShouldBe(10);
settings.LogDeadLettersDuringShutdown.ShouldBeFalse();
settings.LogDeadLettersSuspendDuration.ShouldBe(TimeSpan.FromMinutes(5));
settings.LogFormatter.Should().BeOfType<DefaultLogMessageFormatter>();
settings.LogFormatter.Should().BeOfType<SemanticLogMessageFormatter>();

settings.ProviderClass.ShouldBe(typeof (LocalActorRefProvider).FullName);
settings.SupervisorStrategyClass.ShouldBe(typeof (DefaultSupervisorStrategy).FullName);
Expand Down
Loading
Loading