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

How to pass parameters to custom logger? #2924

Closed
alexvy86 opened this issue Aug 3, 2017 · 19 comments
Closed

How to pass parameters to custom logger? #2924

alexvy86 opened this issue Aug 3, 2017 · 19 comments

Comments

@alexvy86
Copy link
Contributor

alexvy86 commented Aug 3, 2017

I'm trying to implement a custom logger to log through the Microsoft.Extensions.Logging infrastructure, and it seems like I need to hand in the logger factory that was already created and configured as part of my app's init code. But I can't figure out how to give it anything. If I'm looking at the right code here, it seems that there's just no way to feed something into a custom logger... except maybe sending a custom message to it? If that's the way it's supposed to be done, how would I get an IActorRef for it?

@alexvy86
Copy link
Contributor Author

alexvy86 commented Aug 3, 2017

I got it to work with the code below, but having to hardcode log1-MyLoggingActorClassName for the ActorSelection feels wrong... I'm still open to suggestions on a better way of doing it.

var selection = actorSystem.ActorSelection("/system/log1-MyLoggingActorClassName");
var actorRef = selection.ResolveOne(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult();
actorRef.Tell(new MyParametersMessage() { MyLoggerFactoryParam = container.GetService<ILoggerFactory>() });

And in MyLoggingActorClassName (besides skipping actual logging while _loggerFactory hasn't been set, with a simple if):

public class MyLoggingActorClassName : ReceiveActor, IRequiresMessageQueue<ILoggerMessageQueueSemantics>
    {
        private Microsoft.Extensions.Logging.ILoggerFactory _loggerFactory;
        [...]
        public MyLoggingActorClassName() {
            [...]
            Receive<MyParametersMessage>(m =>
            {
                _log.Info("MyLoggingActorClassName received initialization parameters");
                _loggerFactory = m.MyLoggerFactoryParam;
            });
        }

@Danthar
Copy link
Member

Danthar commented Aug 3, 2017

If your trying to build a custom logger. Then why aren't you looking at one of the many akka.logger.* extensions already available? Look at log4net for the simplest thing that could possibly work. Then check out the NLog and Serilog integrations for examples on whats possible as well.

@alexvy86
Copy link
Contributor Author

alexvy86 commented Aug 3, 2017

I did, and I checked slf4net as well, but none of them pass parameters to their loggers. They all use a static LogFactory/LogManager from which they can create a NLog/Serilog/log4net/slf4net logger object, but with Microsoft.Extensions.Logging the LoggerFactory is not static, it's an instantiated object that I have to pass to the Akka extension custom logger, so it can create a "Microsoft.Extensions.Logging logger" and write to it.

For context, I was actually using the NLog extension logger for Akka (and NLog in my app) but I'm deploying my application to Azure Web Apps and it turns out that NLog doesn't have a target that can write to the diagnostics log there. For that reason I moved to Microsoft.Extensions.Logging, the "native" way of doing it when working with .NET Core; they already have a provider that handles that part.

@Danthar
Copy link
Member

Danthar commented Aug 3, 2017

Why not instantiate it in the logger actor's constructor ?

@Danthar
Copy link
Member

Danthar commented Aug 3, 2017

Oh, and it seems Serilog has a Sink as well https://blogs.msdn.microsoft.com/webdev/2017/04/26/asp-net-core-logging/

@alexvy86
Copy link
Contributor Author

alexvy86 commented Aug 3, 2017

Why not instantiate it in the logger actor's constructor ?

Because I need it outside of the Akka logger to configure it fully. In fact it's .NET Core's Dependency Injection infrastructure that instantiates it, and I just ask for the instance. I might be able to instantiate it myself and feed it in to the DI infrastructure, but it will still have to be outside of the custom Akka logger, and then hand it in for the logger to be able to get the object it will write to.

And yes, I know all the other frameworks (NLog, Serilog, log4net, etc) have their respective versions of targets/sinks/appenders; Microsoft.Extensions.Logging sometimes calls them "providers", and I want to use an existing provider, the one that writes to Azure's Diagnostic Log. AFAIK, Microsoft.Extensions.Logging is the only one framework with a target/sink/appender/provider that writes successfully to Azure's Diagnostic Log, so I can't pipe the Akka logs into NLog/Serilog/log4net/etc, I need to pipe them specifically into Microsoft.Extensions.Logging.

In other words, what I'm building is the equivalent of Akka.Logger.NLog, Akka.Logger.Serilog, etc, but to use Microsoft.Extensions.Logging as the underlying framework. Call it "Akka.Logger.MEL" if you will.

@Danthar
Copy link
Member

Danthar commented Aug 3, 2017

Because I need it outside of the Akka logger to configure it fully. In fact it's .NET Core's Dependency Injection infrastructure that instantiates it, and I just ask for the instance. I might be able to instantiate it myself and feed it in to the DI infrastructure, but it will still have to be outside of the custom Akka logger, and then hand it in for the logger to be able to get the object it will write to.

Ok. Is the logger instance transient? Or can it be static ? Because if it is, i'd opt to have your logger integration expose a well known static variable somewhere. Which you initialise before you start your actor system. That way your logging actor will be able to get it.
Alternatively if you could somehow get to the DI system in your logger constructor. That would be an option as well.

@alexvy86
Copy link
Contributor Author

alexvy86 commented Aug 3, 2017

The latter option sounds preferable; it is more or less what I was trying to do, although I assumed I wouldn't be able to wire it up with the DI system, but at least I'd be able to manually get the dependencies from the DI container and pass them to the custom logger's constructor. And if my research is correct, I can't even do that.

I think you wanted to ask if the logger factory instance was transient or static, correct? It is an instance served by DI as a singleton, so for some purposes it "feels" static, but not for this one, and I want to keep it that way. I understand making it static would let me do the same thing NLog and the others do, but I'm trying to make a point (for myself, if nothing else) of avoiding static classes. However, you gave me the idea of just exposing that singleton instance as a static variable of the class... That might work, and it would save me the extra message to initialize the logger agent, so I'm liking the idea.

@alexvy86
Copy link
Contributor Author

alexvy86 commented Aug 3, 2017

Yep, that approach worked and while it makes some assumptions about the ILoggerFactory that my app uses (it must not fail or be replaced through the life of the application), I think it's a reasonable option.

For completeness in case somebody reads this in the future, I created a (non-static) LoggerFactoryManager class whose only purpose is to hold a static, read-only variable of type ILoggerFactory, and partially coupled that class with my DI system to make sure it gets instantiated (which populates the static variable with the DI-provided ILoggerFactory) before creating the ActorSystem. In my custom logger I can now just do LoggerFactoryManager.LoggerFactoryInstance. Implementation below (not thread-safe, so be wary of that).

public class LoggerFactoryManager
{
	private static ILoggerFactory _loggerFactoryInstance;
	public static ILoggerFactory LoggerFactoryInstance
	{
		get
		{
			if (_loggerFactoryInstance == null) throw new InvalidOperationException("The logger factory instance was requested but it was never set. Make sure this class is instantiated before requesting the instance.");
			return _loggerFactoryInstance;
		}
	}

	public LoggerFactoryManager(ILoggerFactory loggerFactory)
	{
		_loggerFactoryInstance = loggerFactory;
	}
}

And I guess that answers my question so this issue can probably be closed now. No way to pass parameters to a custom logger, but the workaround is to put whatever you need to give it in static variables.

@Danthar
Copy link
Member

Danthar commented Aug 4, 2017

👍 Good to see you found a workable solution.

@2MmYxoeg
Copy link

Hi @alexvy86, I'm trying to do exactly the same thing (implement a Microsoft.Extensions.Logging logger for Akka.NET) and I was wondering if (part of) the source for your discussed solution is available? If so, that could save me a lot of time - if not, thanks for the above pointers, I'm sure they will guide me enough!

@alexvy86
Copy link
Contributor Author

alexvy86 commented Jun 18, 2018

Hey @ivansams, that project that I was working on is not in a public repo and I can't share it just like that but I can certainly extract the pieces that will be relevant to you and share those. I'll try to get to that done in the next couple of days :)

@2MmYxoeg
Copy link

That would be fantastic, thank you!

@alexvy86
Copy link
Contributor Author

@ivansams here you go. I didn't test after a significant clean up and simplification (and I refined the approach a bit, so a couple minor details of my comments above won't apply anymore) but I'm pretty confident the ideas you need are there:

  • Create a custom logger actor (MicrosoftExtensionsLogger) and tell Akka to use that instead of its default logger (with the akka.config file). The actor basically just forwards log messages received from Akka, to a logger from Microsoft.Extensions.Logging.
  • We don't control how the custom logger actor gets instantiated so there's no way to pass an ILoggerFactory (or any other object) from Microsoft.Extensions.Logging as a constructor parameter. Instead we put the ILoggerFactory in a static property that the custom logger actor then grabs in its constructor. In this case I put it in LoggerFactoryManager.LoggerFactoryInstance. Make sure to do that before calling ActorSystem.Create() (which is when the custom logger actor is instantiated).

ActorsApp.zip

@snakefoot
Copy link

Regarding NLog and Azure Web Apps. Then the following targets should support logging in the Azure-world:

https://github.com/NLog/NLog/wiki/Trace-target
https://www.nuget.org/packages/Microsoft.ApplicationInsights.NLogTarget/

@alexvy86
Copy link
Contributor Author

To complement @snakefoot's comment, NLog's Trace target will work for .NET Framework apps but last time I checked it doesn't work with .NET Core, since System.Diagnostics.Trace is not implemented there. I haven't tried the Microsoft.ApplicationInsights.NLogTarget so can't say for sure about that one... I assume it correctly sends the log messages to Application Insights, but I don't know if they would also show in the Streaming Logs panel of a WebApp?

@snakefoot
Copy link

snakefoot commented Jun 19, 2018 via email

@2MmYxoeg
Copy link

Hi @alexvy86 thank you so much for the code, I was successful in implementing it into my project. The only problem is that the logger that is returned from Akka.Event.Logging.GetLogger(Context); is Akka.Event.BusLogging, not the WindowsExtensionsLogger.

My config file contains the following hocon:

        loggers = [ "FooProject.Actors.Logging.MicrosoftExtensionsLogger, FooProject.Actors" ]
        loglevel = info
        log-config-on-start = on
        stdout-loglevel = off
        actor {
          debug {
            receive = on      # log any received message
            autoreceive = on  # log automatically received messages, e.g. PoisonPill
            lifecycle = on    # log actor lifecycle changes
            event-stream = on # log subscription changes for Akka.NET event stream
            unhandled = on    # log unhandled messages sent to actors
          }
        }

and it is definitely being read because it throws an exception if I change the logger namespace. However it persists in using BusLogging instead of the WindowsExtensionsLogger. I am not calling LoggerFactoryManager anywhere - how does ActorSystem.Create know to set the logger from this?

I'm going to try to figure out why this is now but if you have any ideas please let me know!

By the way, for us we do not want to use NLog as we are already using Microsoft.Extensions.Logging throughout this and our other projects.

@alexvy86
Copy link
Contributor Author

Did you use the same code I did for MicrosoftExtensionsLogger? Its constructor accesses the LoggerFactoryManager (_loggerFactory = LoggerFactoryManager.LoggerFactoryInstance;), and in Program.cs I set it before creating the Actor System: LoggerFactoryManager.LoggerFactoryInstance = container.GetService<ILoggerFactory>();.

Regarding ActorSystem.Create knowing which class to use as logger, I thought it was just based on the loggers property in the hocon, but to be honest I haven't researched that in detail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants