-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ILoggerFactory logger support (#72)
* Add ILoggerFactory logger support * Rename WithLoggerFactorLogger to WithLoggerFactory * Add ILoggerFactory sample project * Make `Akka.Hosting.LoggingDemo.csproj` not packable Co-authored-by: Aaron Stannard <[email protected]>
- Loading branch information
1 parent
1352d42
commit c969ceb
Showing
8 changed files
with
258 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// ----------------------------------------------------------------------- | ||
// <copyright file="AkkaLoggerFactoryExtensions.cs" company="Akka.NET Project"> | ||
// Copyright (C) 2013-2022 .NET Foundation <https://github.com/akkadotnet/akka.net> | ||
// </copyright> | ||
// ----------------------------------------------------------------------- | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Akka.Actor; | ||
using Akka.Configuration; | ||
using Akka.Event; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Akka.Hosting.Logging | ||
{ | ||
public static class AkkaLoggerFactoryExtensions | ||
{ | ||
public static AkkaConfigurationBuilder WithLoggerFactory(this AkkaConfigurationBuilder builder) | ||
{ | ||
return builder.AddHocon("akka.loggers = [\"Akka.Hosting.Logging.LoggerFactoryLogger, Akka.Hosting\"]"); | ||
} | ||
|
||
public static AkkaConfigurationBuilder AddLoggerFactory(this AkkaConfigurationBuilder builder) | ||
{ | ||
var loggers = builder.Configuration.HasValue | ||
? builder.Configuration.Value.GetStringList("akka.loggers") | ||
: new List<string>(); | ||
|
||
if(loggers.Count == 0) | ||
loggers.Add("Akka.Event.DefaultLogger"); | ||
|
||
loggers.Add("Akka.Hosting.Logging.LoggerFactoryLogger, Akka.Hosting"); | ||
return builder.AddHocon($"akka.loggers = [{string.Join(", ", loggers.Select(s => $"\"{s}\""))}]"); | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// ----------------------------------------------------------------------- | ||
// <copyright file="LoggerFactoryLogger.cs" company="Akka.NET Project"> | ||
// Copyright (C) 2013-2022 .NET Foundation <https://github.com/akkadotnet/akka.net> | ||
// </copyright> | ||
// ----------------------------------------------------------------------- | ||
|
||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Linq; | ||
using System.Text; | ||
using Akka.Actor; | ||
using Akka.Configuration; | ||
using Akka.DependencyInjection; | ||
using Akka.Dispatch; | ||
using Akka.Event; | ||
using Microsoft.Extensions.Logging; | ||
using LogLevel = Microsoft.Extensions.Logging.LogLevel; | ||
|
||
namespace Akka.Hosting.Logging | ||
{ | ||
public class LoggerFactoryLogger: ActorBase, IRequiresMessageQueue<ILoggerMessageQueueSemantics> | ||
{ | ||
public const string DefaultTimeStampFormat = "yy/MM/dd-HH:mm:ss.ffff"; | ||
private const string DefaultMessageFormat = "[{{Timestamp:{0}}}][{{SourceContext}}][{{LogSource}}][{{ActorPath}}][{{Thread:0000}}]: {{Message}}"; | ||
private static readonly Event.LogLevel[] AllLogLevels = Enum.GetValues(typeof(Event.LogLevel)).Cast<Event.LogLevel>().ToArray(); | ||
|
||
private readonly ConcurrentDictionary<Type, ILogger> _loggerCache = new ConcurrentDictionary<Type, ILogger>(); | ||
private readonly ILoggingAdapter _log = Akka.Event.Logging.GetLogger(Context.System.EventStream, nameof(LoggerFactoryLogger)); | ||
private ILoggerFactory _loggerFactory; | ||
private readonly string _messageFormat; | ||
|
||
public LoggerFactoryLogger() | ||
{ | ||
_messageFormat = string.Format(DefaultMessageFormat, DefaultTimeStampFormat); | ||
} | ||
|
||
protected override void PostStop() | ||
{ | ||
_log.Info($"{nameof(LoggerFactoryLogger)} stopped"); | ||
} | ||
|
||
protected override bool Receive(object message) | ||
{ | ||
switch (message) | ||
{ | ||
case InitializeLogger _: | ||
var resolver = DependencyResolver.For(Context.System); | ||
_loggerFactory = resolver.Resolver.GetService<ILoggerFactory>(); | ||
if (_loggerFactory == null) | ||
throw new ConfigurationException("Could not find any ILoggerFactory service inside ServiceProvider"); | ||
|
||
_log.Info($"{nameof(LoggerFactoryLogger)} started"); | ||
Sender.Tell(new LoggerInitialized()); | ||
return true; | ||
|
||
case LogEvent logEvent: | ||
Log(logEvent, Sender.Path); | ||
return true; | ||
|
||
default: | ||
return false; | ||
} | ||
} | ||
|
||
private void Log(LogEvent log, ActorPath path) | ||
{ | ||
var logger = _loggerCache.GetOrAdd(log.LogClass, type => _loggerFactory.CreateLogger(type)); | ||
var message = GetMessage(log.Message); | ||
logger.Log(GetLogLevel(log.LogLevel()), log.Cause, _messageFormat, GetArgs(log, path, message)); | ||
} | ||
|
||
private static object[] GetArgs(LogEvent log, ActorPath path, object message) | ||
=> new []{ log.Timestamp, log.LogClass.FullName, log.LogSource, path, log.Thread.ManagedThreadId, message }; | ||
|
||
private static object GetMessage(object obj) | ||
{ | ||
try | ||
{ | ||
return obj is LogMessage m ? string.Format(m.Format, m.Args) : obj; | ||
} | ||
catch (Exception ex) | ||
{ | ||
// Formatting/ToString error handling | ||
var sb = new StringBuilder("Exception while recording log: ") | ||
.Append(ex.Message) | ||
.Append(' '); | ||
switch (obj) | ||
{ | ||
case LogMessage msg: | ||
var args = msg.Args.Select(o => | ||
{ | ||
try | ||
{ | ||
return o.ToString(); | ||
} | ||
catch(Exception e) | ||
{ | ||
return $"{o.GetType()}.ToString() throws {e.GetType()}: {e.Message}"; | ||
} | ||
}); | ||
sb.Append($"Format: [{msg.Format}], Args: [{string.Join(",", args)}]."); | ||
break; | ||
case string str: | ||
sb.Append($"Message: [{str}]."); | ||
break; | ||
default: | ||
sb.Append($"Failed to invoke {obj.GetType()}.ToString()."); | ||
break; | ||
} | ||
|
||
sb.AppendLine(" Please take a look at the logging call where this occurred and fix your format string."); | ||
sb.Append(ex); | ||
return sb.ToString(); | ||
} | ||
} | ||
|
||
private static LogLevel GetLogLevel(Event.LogLevel level) | ||
{ | ||
switch (level) | ||
{ | ||
case Event.LogLevel.DebugLevel: | ||
return LogLevel.Debug; | ||
case Event.LogLevel.InfoLevel: | ||
return LogLevel.Information; | ||
case Event.LogLevel.WarningLevel: | ||
return LogLevel.Warning; | ||
case Event.LogLevel.ErrorLevel: | ||
return LogLevel.Warning; | ||
default: | ||
// Should never reach this code path | ||
return LogLevel.Error; | ||
} | ||
} | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/Examples/Akka.Hosting.LoggingDemo/Akka.Hosting.LoggingDemo.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<IsPackable>false</IsPackable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\Akka.Cluster.Hosting\Akka.Cluster.Hosting.csproj" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
namespace Akka.Hosting.LoggingDemo; | ||
|
||
public struct Echo{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
using Akka.Hosting; | ||
using Akka.Actor; | ||
using Akka.Actor.Dsl; | ||
using Akka.Cluster.Hosting; | ||
using Akka.Event; | ||
using Akka.Hosting.Logging; | ||
using Akka.Hosting.LoggingDemo; | ||
using Akka.Remote.Hosting; | ||
|
||
var builder = WebApplication.CreateBuilder(args); | ||
|
||
builder.Services.AddAkka("MyActorSystem", (configurationBuilder, serviceProvider) => | ||
{ | ||
configurationBuilder | ||
.AddHocon("akka.loglevel = DEBUG") | ||
.WithLoggerFactory() | ||
.WithRemoting("localhost", 8110) | ||
.WithClustering(new ClusterOptions(){ Roles = new[]{ "myRole" }, | ||
SeedNodes = new[]{ Address.Parse("akka.tcp://MyActorSystem@localhost:8110")}}) | ||
.WithActors((system, registry) => | ||
{ | ||
var echo = system.ActorOf(act => | ||
{ | ||
act.ReceiveAny((o, context) => | ||
{ | ||
Logging.GetLogger(context.System, "echo").Info($"Actor received {o}"); | ||
context.Sender.Tell($"{context.Self} rcv {o}"); | ||
}); | ||
}, "echo"); | ||
registry.TryRegister<Echo>(echo); // register for DI | ||
}); | ||
}); | ||
|
||
var app = builder.Build(); | ||
|
||
app.MapGet("/", async (context) => | ||
{ | ||
var echo = context.RequestServices.GetRequiredService<ActorRegistry>().Get<Echo>(); | ||
var body = await echo.Ask<string>(context.TraceIdentifier, context.RequestAborted).ConfigureAwait(false); | ||
await context.Response.WriteAsync(body); | ||
}); | ||
|
||
app.Run(); |
9 changes: 9 additions & 0 deletions
9
src/Examples/Akka.Hosting.LoggingDemo/appsettings.Development.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"Logging": { | ||
"LogLevel": { | ||
"Default": "Information", | ||
"Microsoft": "Warning", | ||
"Microsoft.Hosting.Lifetime": "Information" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"Logging": { | ||
"LogLevel": { | ||
"Default": "Information", | ||
"Microsoft": "Warning", | ||
"Microsoft.Hosting.Lifetime": "Information", | ||
"Akka": "Debug" | ||
} | ||
}, | ||
"AllowedHosts": "*" | ||
} |