60

I'm logging just fine using dependency injection on my controllers, now I need to log something from a static class.

How can I log from a static class?

I can't use dependency injection because it's static and I can't just pass in an existing logger object to the static class because it would have the wrong name of class in the log file.

In other words how can I get a logger from the loggerfactory inside my static class?

I came across a similar question and they pointed to an article on a loggerfactory in a static class but that doesn't actually work as I can't get a new logger from it because it won't accept a static class as the parameter:

"Static types cannot be used as type arguments"

JohnC
  • 3,938
  • 7
  • 41
  • 48

6 Answers6

89

Solution is to have a static reference to the LoggerFactory in a utility static class initialized on startup:

/// <summary>
/// Shared logger
/// </summary>
internal static class ApplicationLogging
{
    internal static ILoggerFactory LoggerFactory { get; set; }// = new LoggerFactory();
    internal static ILogger CreateLogger<T>() => LoggerFactory.CreateLogger<T>();        
    internal static ILogger CreateLogger(string categoryName) => LoggerFactory.CreateLogger(categoryName);
}

Which you initialize on Startup.cs:

public Startup(ILogger<Startup> logger, ILoggerFactory logFactory, IHostingEnvironment hostingEnvironment)
{
    _log = logger;
    _hostingEnvironment = hostingEnvironment;
    Util.ApplicationLogging.LoggerFactory = logFactory;//<===HERE
}

Then you can build a logger to use from your static class like so:

internal static class CoreJobSweeper
{
    private static ILogger log = Util.ApplicationLogging.CreateLogger("CoreJobSweeper");
}
Walkman
  • 67
  • 1
  • 2
  • 8
JohnC
  • 3,938
  • 7
  • 41
  • 48
  • 1
    Perfect! However just worth adding an example of a strongly-typed CreateLogger, especially for us copy/paste junkies just looking for a quick fix to a problem :) i.e. private static ILogger log = Util.ApplicationLogging.CreateLogger(); – alv Apr 26 '19 at 17:52
  • 3
    This no longer works as if the announcement in https://github.com/aspnet/Announcements/issues/353. Injecting ILogger and ILoggerFactory does not work. "The new ApplicationInsightsLoggerProvider can capture logs from early in the application-startup pipeline. Although ApplicationInsightsLoggerProvider is automatically enabled in Application Insights (starting with version 2.7.1), it doesn't have an instrumentation key set up until later in the pipeline. So, only logs from Controller/other classes will be captured..." – Anders Apr 09 '20 at 06:44
  • 3
    Solution is to move ILogger logger, ILoggerFactory logFactory to Configure() – Anders Apr 09 '20 at 08:35
  • Suggestion: change the return Type to ILogger : internal static ILogger CreateLogger() => LoggerFactory.CreateLogger(); Usage: private static ILogger log = Util.ApplicationLogging.CreateLogger(); Otherwise explicit cast is mandantory – Dominik Sand Apr 22 '20 at 06:26
  • 3
    But what if static class is in project/dll with no ref to server project with startup ? – user1785960 Jul 03 '20 at 11:44
  • How to implement something similar to static logger in azure function .net core 3.1 or higher @spottedmahn – Rakesh T.R Oct 07 '22 at 08:26
  • 3
    Hello... how to do this in .NET Core 6 where there is no `Startup` class? – jstuardo Nov 04 '22 at 18:27
20

I found this question with answers useful, but I remembered I already have installed Serilog. Instead of implementing a custom static instance, I could use the default implementation from Serilog.

It comes with multiple nice logging features with multiple overloaded version.
Here are two examples:

using Serilog;
...
    Log.Error(myException, "my message");
    Log.Warning("My message", myValue);

Serilog.Log is a static instance that is ready when configured in Program.cs or Startup.cs.

pekaaw
  • 2,309
  • 19
  • 18
  • This works if you don´t need to use the separation of concerns principle where you could replace the logging framework easily if needed. – Banshee Feb 23 '21 at 15:47
  • 1
    True, and if one would like to use Serilog in a custom logger, one can let the custom logger basically be a wrapper around Serilog :) To get a binding to the calling class, as OP asked for, one can implement something like the factorymethod with type parameter as [@Adrian](https://stackoverflow.com/a/54234887/3032543) described. – pekaaw Feb 24 '21 at 10:28
  • You are coding to a concrete, not coding to an interface. Future readers, just "be aware" of this situation. – granadaCoder Sep 08 '21 at 20:39
  • @Banshee - This works if you don't want to rely on **Abstraction** where you could replace the logging framework easily if needed. – Suamere Jun 29 '22 at 15:07
  • As clarification to my last comment: **Separation of Concerns** is an observation of the different *Vertical* Concerns of an application: Design, UI Logic, API, BLL, DLL, Persistence. **Cross-Cutting Concerns** is when you share logic *Horizontally* at a particular layer. ie: Auth (or Logging, Caching, etc) is a CCC across API1, API2, API3, etc. Lastly, **Bounded Context** is observing ownership (Low Coupling, High Cohesion) of models between *Horizontally*-Split applications or MicroServices. – Suamere Jun 29 '22 at 15:07
7

You can use the static LoggerFactory instance with the following extension method which accepts a regular parameter of type Type, rather than a generic type parameter: CreateLogger(ILoggerFactory, Type)

i.e. loggerFactory.CreateLogger(typeof(T)) rather than loggerFactory.CreateLogger<T>()

Adrian
  • 95
  • 1
  • 1
  • 8
    Did you mean "**_a_** static `LoggerFactory` instance"? There is no static `LoggerFactory` instance available unless you set one up. – Daniel May 05 '20 at 17:03
1

I've added a static member of ILoggerFactory in Startup class so it can be used in other classes.

public class Startup
{
    internal static ILoggerFactory LogFactory { get; set; }
    …

    public void Configure(…, ILoggerFactory logFactory)
    {
        …
        LogFactory = logFactory;
    }
}

Then use it in the static class.

static class YourClass
{
    private static ILogger<YourClass> Logger = Startup.LogFactory.CreateLogger<YourClass>();
…

However, this code will result in a TypeInitializationException when the CreateLogger throws an exception, so you should try to do better. [1]

criticabug
  • 331
  • 6
  • 12
-1

First of all, I agree with NightOwl888 answer.

But as an option (consider this as a workaround) you could inject ILogger dependency via method parameter => so a class that will call this static method should be responsible for providing a correct implementation of ILogger.

static class YourClass
{
    public static void DoSomething(ILogger logger)
    {
         // do something
         // call log.Log();
    }
}
Set
  • 47,577
  • 22
  • 132
  • 150
  • 9
    Nightowl didn't answer the question at all and instead delivered an unwanted lecture, I specifically covered what you suggest and why I can't do it in my question. – JohnC Feb 08 '18 at 16:05
  • 6
    To be able to log Everywhere, you have to force a required logger dependency on every method/object in your tens of thousands of lines of code. That sounds like a terrible idea to me. A static factory is the saner option in this case. – Cesar Oct 08 '19 at 18:35
-1

Here is the combined answer of @JohnC & @Pekaaw,

LoggingFactory can be created from SeriLog logger to retain same the log configuration and it can be used for logging mechanism

(Microsoft.Extensions.Logging.LoggerFactory)Serilog.SerilogLoggerFactoryExtensions.AddSerilog(new Microsoft.Extensions.Logging.LoggerFactory(), Serilog.Log.Logger);

/// <summary>
/// Shared logger
/// </summary>
internal static class ApplicationLogging
{
    internal static ILoggerFactory LoggerFactory { get; set; } = (Microsoft.Extensions.Logging.LoggerFactory)Serilog.SerilogLoggerFactoryExtensions.AddSerilog(new Microsoft.Extensions.Logging.LoggerFactory(), Serilog.Log.Logger);
    internal static Microsoft.Extensions.Logging.ILogger CreateLogger<T>() => LoggerFactory.CreateLogger<T>();
    internal static Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) => LoggerFactory.CreateLogger(categoryName);
}
public static class YourClass
{
   public static readonly ILogger _logger = ApplicationLogging.CreateLogger("YourClass");
   public static void MyMethod(string param)
   {
      try
      {
         _logger.LogInformation($"Inside MyMethod { param }");
         //Todo: my static code;
      }
   }
}