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
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,51 @@ var log = Context.GetLogger<SerilogLoggingAdapter>(); // correct
log.Info("My boss makes me use {semantic} logging", "semantic"); // serilog semantic logging format
```

or

```csharp
var log = MyActorSystem.GetLogger<SerilogLoggingAdapter>(myContextObject); // correct
log.Info("My boss makes me use {semantic} logging", "semantic"); // serilog semantic logging format
```

or
```csharp
var log = MyActorSystem.GetLogger<SerilogLoggingAdapter>(contextName, contextType); // correct
log.Info("My boss makes me use {semantic} logging", "semantic"); // serilog semantic logging format
```

This will allow all logging events to be consumed anywhere inside the `ActorSystem`, including places like the Akka.NET TestKit, without throwing `FormatException`s when they encounter semantic logging syntax outside of the `SerilogLogger`.

### Adding Property Enricher To Your Logs

#### Default Properties
You can add property enrichers to the logging adapter that will be added to all logging calls to that logging adapter.

```csharp
var log = Context.GetLogger<SerilogLoggingAdapter>()
.ForContext("Address", "No. 4 Privet Drive")
.ForContext("Town", "Little Whinging")
.ForContext("County", "Surrey")
.ForContext("Country", "England");
log.Info("My boss makes me use {Semantic} logging", "semantic");
```

All logging done using the `log` `ILoggingAdapter` instance will append "Address", "Town", "County", and "Country" properties into the Serilog log.

#### One-off Properties

You can add one-off property to a single log message by appending `PropertyEnricher` instances at the end of your logging calls.

```csharp
var log = Context.GetLogger<SerilogLoggingAdapter>();
log.Info(
"My boss makes me use {Semantic} logging", "semantic",
new PropertyEnricher("County", "Surrey"),
new PropertyEnricher("Country", "England"));
```

This log entry will have "County" and "Country" properties added to it.

## Building this solution
To run the build script associated with this solution, execute the following:

Expand Down
53 changes: 52 additions & 1 deletion src/Akka.Logger.Serilog.Tests/ForContextSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using FluentAssertions;
using Serilog;
using Serilog.Core;
using Serilog.Core.Enrichers;
using Serilog.Events;
using Xunit;
using Xunit.Abstractions;
Expand All @@ -33,7 +34,57 @@ public ForContextSpecs(ITestOutputHelper helper) : base(Config, output: helper)
var logSource = Sys.Name;
var logClass = typeof(ActorSystem);

_loggingAdapter = new SerilogLoggingAdapter(Sys.EventStream, logSource, logClass);
_loggingAdapter = Sys.GetLogger<SerilogLoggingAdapter>(logSource, logClass);
}

[Fact]
public void ShouldLogMessageWithContextProperty()
{
var context = _loggingAdapter
.ForContext("Address", "No. 4 Privet Drive")
.ForContext("Town", "Little Whinging")
.ForContext("County", "Surrey")
.ForContext("Country", "England");

_sink.Clear();
AwaitCondition(() => _sink.Writes.Count == 0);

context.Info("Hi {Person}", "Harry Potter");
AwaitCondition(() => _sink.Writes.Count == 1);

_sink.Writes.TryDequeue(out var logEvent).Should().BeTrue();
logEvent.Level.Should().Be(LogEventLevel.Information);
logEvent.RenderMessage().Should().Contain("Hi \"Harry Potter\"");
logEvent.Properties.Should().ContainKeys("Person", "Address", "Town", "County", "Country");
logEvent.Properties["Person"].ToString().Should().Be("\"Harry Potter\"");
logEvent.Properties["Address"].ToString().Should().Be("\"No. 4 Privet Drive\"");
logEvent.Properties["Town"].ToString().Should().Be("\"Little Whinging\"");
logEvent.Properties["County"].ToString().Should().Be("\"Surrey\"");
logEvent.Properties["Country"].ToString().Should().Be("\"England\"");
}

[Fact]
public void ShouldLogMessageWithContextPropertyAndPropertyEnricher()
{
var context = _loggingAdapter
.ForContext("Address", "No. 4 Privet Drive")
.ForContext("Town", "Little Whinging");

_sink.Clear();
AwaitCondition(() => _sink.Writes.Count == 0);

context.Info("Hi {Person}", "Harry Potter", new PropertyEnricher("County", "Surrey"), new PropertyEnricher("Country", "England"));
AwaitCondition(() => _sink.Writes.Count == 1);

_sink.Writes.TryDequeue(out var logEvent).Should().BeTrue();
logEvent.Level.Should().Be(LogEventLevel.Information);
logEvent.RenderMessage().Should().Contain("Hi \"Harry Potter\"");
logEvent.Properties.Should().ContainKeys("Person", "Address", "Town", "County", "Country");
logEvent.Properties["Person"].ToString().Should().Be("\"Harry Potter\"");
logEvent.Properties["Address"].ToString().Should().Be("\"No. 4 Privet Drive\"");
logEvent.Properties["Town"].ToString().Should().Be("\"Little Whinging\"");
logEvent.Properties["County"].ToString().Should().Be("\"Surrey\"");
logEvent.Properties["Country"].ToString().Should().Be("\"England\"");
}

[Fact]
Expand Down
25 changes: 20 additions & 5 deletions src/Akka.Logger.Serilog/SerilogLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,22 @@ public class SerilogLogger : ReceiveActor, IRequiresMessageQueue<ILoggerMessageQ
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string GetFormat(object message)
{
// Unwrap SerilogPayload
if (message is SerilogPayload payload)
message = payload.Message;

return message is LogMessage logMessage ? logMessage.Format : "{Message:l}";
}

private static object[] GetArgs(object message)
{
var logMessage = message as LogMessage;
return logMessage?.Parameters().Where(a => a is not PropertyEnricher).ToArray() ?? new[] { message };
// Unwrap SerilogPayload
if (message is SerilogPayload payload)
message = payload.Message;

return message is LogMessage logMessage
? logMessage.Parameters().Where(a => a is not PropertyEnricher).ToArray()
: new[] { message };
}

private static ILogger GetLogger(LogEvent logEvent) {
Expand All @@ -46,14 +55,20 @@ private static ILogger GetLogger(LogEvent logEvent) {
.ForContext("LogSource", logEvent.LogSource)
.ForContext("Thread", logEvent.Thread.ManagedThreadId.ToString("0000"));

if (logEvent.Message is SerilogPayload logMessage)
if (logEvent.Message is SerilogPayload serilogPayload)
{
logger = logMessage.Enrichers.OfType<PropertyEnricher>().Aggregate(logger, (current, enricher) => current.ForContext(enricher));
var enrichers = serilogPayload.Enrichers.ToList();
if (serilogPayload.Message is LogMessage logMessage)
enrichers.AddRange(logMessage.Parameters().OfType<ILogEventEnricher>());
if (enrichers.Count > 0)
logger = logger.ForContext(enrichers);
}

if (logEvent.Message is LogMessage message)
{
logger = message.Parameters().Where(a => a is ILogEventEnricher).Aggregate(logger, (current, enricher) => current.ForContext((ILogEventEnricher)enricher));
var enrichers = message.Parameters().OfType<ILogEventEnricher>().ToList();
if (enrichers.Count > 0)
logger = logger.ForContext(enrichers);
}

return logger;
Expand Down
16 changes: 16 additions & 0 deletions src/Akka.Logger.Serilog/SerilogLoggingAdapterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,21 @@ public static ILoggingAdapter GetLogger<T>(this IActorContext context)

return new SerilogLoggingAdapter(context.System.EventStream, logSource, logClass);
}

public static ILoggingAdapter GetLogger<T>(this ActorSystem system, object logSourceObj)
where T : class, ILoggingAdapter
{
if (logSourceObj is null)
throw new ArgumentNullException(nameof(logSourceObj));

var logSource = LogSource.Create(logSourceObj, system);
return new SerilogLoggingAdapter(system.EventStream, logSource.Source, logSource.Type);
}

public static ILoggingAdapter GetLogger<T>(this ActorSystem system, string logSource, Type logType)
where T : class, ILoggingAdapter
{
return new SerilogLoggingAdapter(system.EventStream, logSource, logType);
}
}
}