loggingiisasp.net-core-webapi.net-9.0

How to store ASP.NET Core 9 Web API logs called from ILogger in same location set by IIS for site


In IIS I set log files to a specific folder which I would like to store my logs for my application as well however I am not sure how to directly do that in my application so it stays consistent with IIS.

An implementation of ILogger in my code looks like this:

internal sealed class GlobalExceptionHandler : IExceptionHandler
{
    private readonly ILogger<GlobalExceptionHandler> _logger;

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

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        _logger.LogError(
            exception, "Exception occurred: {Message}", exception.Message);

        var problemDetails = new ProblemDetails
        {
            Status = StatusCodes.Status500InternalServerError,
            Title = "Server Error"
        };

        httpContext.Response.StatusCode = problemDetails.Status.Value;

        await httpContext.Response
            .WriteAsJsonAsync(problemDetails, cancellationToken);

        return true;
    }
}

What do I need to register in program.cs to make this logger dependency actually write to a file and how to target the path of where the file is or creates in IIS? And can it target this file based off of IIS settings or does it need to be a manual set as a relative path in appsettings.json?


Solution

  • yes, you can configure it to be written to a file, but for a more professional purpose, and especially for production, I recommend using serilog.

    First you need to install its packages:

    dotnet add package Serilog.AspNetCore
    dotnet add package Serilog.Sinks.File
    

    update appsettings.json and add IisLogPath

    {
      "Logging": {
        "IisLogPath": "C:\\inetpub\\logs\\LogFiles\\W3SVC1"
      }
    }
    

    then configure serilog in program.cs

    using Serilog;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Configure Serilog
    string iisLogPath = builder.Configuration["Logging:IisLogPath"] ?? 
                       Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), 
                       "inetpub", "logs", "LogFiles", "W3SVC1");
    
    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Information()
        .MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning)
        .Enrich.FromLogContext()
        .WriteTo.File(
            Path.Combine(iisLogPath, "application-.log"),
            rollingInterval: RollingInterval.Day,
            retainedFileCountLimit: 10,
            fileSizeLimitBytes: 20 * 1024 * 1024)
        .CreateLogger();
    
    builder.Host.UseSerilog();
    
    // ... rest of your configuration
    

    more info : serilog configuration

    If for some reason you don't want to use serilog, try this way. :

    first you need create custom FilerLoggerProvider

    public class FileLoggerProvider : ILoggerProvider
    {
        private readonly string _filePath;
        private readonly LoggerFilterOptions _filterOptions;
    
        public FileLoggerProvider(string filePath, LoggerFilterOptions filterOptions)
        {
            _filePath = filePath;
            _filterOptions = filterOptions;
        }
    
        public ILogger CreateLogger(string categoryName)
        {
            return new FileLogger(_filePath, categoryName, _filterOptions);
        }
    
        public void Dispose() { }
    }
    
    public class FileLogger : ILogger
    {
        private readonly string _filePath;
        private readonly string _categoryName;
        private readonly LoggerFilterOptions _filterOptions;
    
        public FileLogger(string filePath, string categoryName, LoggerFilterOptions filterOptions)
        {
            _filePath = filePath;
            _categoryName = categoryName;
            _filterOptions = filterOptions;
        }
    
        public IDisposable BeginScope<TState>(TState state) => null;
    
        public bool IsEnabled(LogLevel logLevel)
        {
            return logLevel >= _filterOptions.MinLevel;
        }
    
        public void Log<TState>(
            LogLevel logLevel,
            EventId eventId,
            TState state,
            Exception exception,
            Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
            {
                return;
            }
    
            var message = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{logLevel}] {_categoryName}: {formatter(state, exception)}{(exception != null ? $"\n{exception}" : "")}";
    
            lock (this)
            {
                File.AppendAllText(_filePath, message + Environment.NewLine);
            }
        }
    }
    

    then use it in program.cs as follows :

    var builder = WebApplication.CreateBuilder(args);
    
    string iisLogPath = builder.Configuration["Logging:IisLogPath"] ?? 
                       Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), 
                       "inetpub", "logs", "LogFiles", "W3SVC1");
    
    
    Directory.CreateDirectory(iisLogPath);
    
    builder.Logging.AddProvider(
        new FileLoggerProvider(
            Path.Combine(iisLogPath, "application.log"),
            new LoggerFilterOptions()
            {
                MinLevel = LogLevel.Information
            }
        )
    );
    
    // ... rest of your configuration