How do I log from other classes than the controller in ASP.NET Core?

Regarding using logging in any component:

Adding logging to a component in your application is done by requesting either an ILoggerFactory or an ILogger<T> via Dependency Injection. If an ILoggerFactory is requested, a logger must be created using its CreateLogger method.

If your CustomClass is a data container (DTO class), it should not know about logging, but just contain data.

For other classes with names like "Service" , "Provider", "Handler" and so on, the best practices is to resolve instances using dependency injection. In general, you should use DI wherever it is possible, as it’s a technique for achieving loose coupling between objects and their collaborators or dependencies. For more information, the following question might be interesting: Should I use Dependency Injection or static factories?

So simply add ILogger<CustomClass> to its constructor (actually the same way, as you do for controllers), as .NET Core supports only constructor injection by default:

public class CustomClass
{
    private readonly ILogger _logger;

    public CustomClass(ILogger<CustomClass> logger)
    {
        _logger = logger;
    }
}

ASP.NET Core’s built-in dependency injection container will automatically resolved transient dependencies. So if your controller has class A as a dependency that uses class B where you want to log something, your code can be something like this:

public class MyController
{
    public MyController(ClassA classA)
    { ... }
}

public class ClassA
{
    public ClassA(ClassB classB)
    { ... }
}

public class ClassB
{
    private readonly ILogger _logger;

    public ClassB(ILogger<ClassB> logger)
    {
        _logger = logger;
    }

    public void DoSomethingWithLogging()
    {
        // use _logger
    }
}

Note that you also need to register the dependencies in Startup using IServiceCollection.Add… methods:

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddTransient<ClassB>();
    services.AddTransient<ClassA>();
}

Built-in logging support means that .NET Core out of the box knows about and is using built-in abstractions for logging. This is mainly done by ILoggerFactory and ILoggerProvider interfaces.

/// <summary>
/// Represents a type used to configure the logging system and create instances of <see cref="ILogger"/> from
/// the registered <see cref="ILoggerProvider"/>s.
/// </summary>
public interface ILoggerFactory : IDisposable

// <summary>
/// Represents a type that can create instances of <see cref="ILogger"/>.
/// </summary>
public interface ILoggerProvider : IDisposable

Using your own ILoggerProvider implementation, you can add your own logger that can do whatever you want. You can check the NLog logger implementation as working example.

And thanks to ILoggerFactory, you can simply configure your Logger for project-specific purposes:

To configure logging in your ASP.NET Core application, you should resolve ILoggerFactory in the Configure method of your Startup class. ASP.NET Core will automatically provide an instance of ILoggerFactory using Dependency Injection when you add a parameter of this type to the Configure method.


Sharing Bjorn Reppens concerns regarding the accepted answer, and taking Cyprien Autexiers comments into account, I ended up with this (boiled down) solution for "built in DI" without "polluting constructors" in "other classes than the controller" in .NET Core 3.1

Factory:

public class Runtime
{
    public static ILogger<T> GetLogger<T>()
    {
        using var serviceScope = host.Services.CreateScope();
        var services = serviceScope.ServiceProvider;
        return services.GetRequiredService<ILogger<T>>();
    }

    static IHost host;

    CancellationTokenSource CancellationSource { get; } = new CancellationTokenSource();

    Task hostTask;

    public void Start()
    {
        var hostBuilder = Host.CreateDefaultBuilder();
        hostBuilder.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
        host = hostBuilder.Build();
        hostTask = host.RunAsync(CancellationSource.Token);
    }
}

Client:

public class OtherClass
{
    ILogger Logger { get; }

    public OtherClass() => Logger = Runtime.GetLogger<OtherClass>();
}

For further abstraction:

public abstract class LoggerBase<T>
{   
    protected ILogger Logger => RunTime.GetLogger<T>();
}

e.g.

public class OtherClass : LoggerBase<OtherClass>
{
    public OtherClass() => Logger.LogInformation("Instantiated");
}

This article indicates that you should use DI to inject your logger in all classes that need it: https://docs.asp.net/en/latest/fundamentals/logging.html

"This technique is not limited to controllers, but can be utilized by any of your application services that utilize Dependency Injection."

On the other hand, here is an article (Microsoft sponsored) that mentions a global static reference approach: https://msdn.microsoft.com/en-us/magazine/mt694089.aspx?f=255&MSPPError=-2147217396

"As you may notice, this requires access to the same logger factory instance on which the providers were previously configured. And while it’s conceivable you could pass the logger factory instance into every class from which you want to perform logging, it would quickly become a hassle that would beg for refactoring.

The solution is to save a single static ILoggerFactory as a static property that’s available for all classes when instantiating their object’s specific ILoggger instance. For example, consider adding an ApplicationLogging static class that includes a static ILoggerFactory instance:"

Not trying to stoke the Static vs. DI vs. Service Locator: Pattern or Anti-pattern debates, I personally find common cross-cutting concerns such as logging and configuration to be candidates for this latter approach, but have used both in the past.