Search code examples
c#asp.net-coreasp.net-core-webapibackgroundworkerserilog

Serilog does not output logs into the txt file


Im using dotnet worker service (.net 5) I integrated serilog as well as enrichers and sinks. But for some reason I dont see any outputs into my files logs.

Here is my appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=eeee;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "WorkerOptions": {
    "Batch": 5
  },
  "Serilog": {
    "Using": [],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": {
          "path": "E:\\Logs\\log_.txt",
          "outputTemplate": "{Timestamp:o} {Message}{NewLine:1}{Exception:1}",
          "rollingInterval": "Day",
          "retainedFileCountLimit": 7
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "E:\\Logs\\log_.json",
          "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
          "outputTemplate": "{Timestamp:o} {Message}{NewLine:1}{Exception:1}",
          "rollingInterval": "Day",
          "retainedFileCountLimit": 7
        }
      }
    ]
  }
}

Registration of logger:

public class Program
{
    public static void Main(string[] args)
    {
            
        var configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .Build();

        Log.Logger = new LoggerConfiguration()
                            .ReadFrom.Configuration(configuration)
                            .CreateLogger();

        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
               
            .ConfigureServices((hostContext, services) =>
            {

                var configuration = new ConfigurationBuilder()
                                            .AddJsonFile("appsettings.json")
                                            .Build();

                var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
                optionsBuilder.UseSqlServer(configuration.GetConnectionString("DefaultConnection"));
                services.AddScoped<ApplicationDbContext>(s => new ApplicationDbContext(optionsBuilder.Options));
                services.AddHostedService<Worker>();
            });
}

and this is an implementation:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IServiceScopeFactory _serviceScopeFactory;
    private readonly IConfiguration _config;
    public Worker(ILogger<Worker> logger, IServiceScopeFactory serviceScopeFactory, IConfiguration config)
    {
        _config = config;
        _logger = logger;
        _serviceScopeFactory = serviceScopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
     _logger.LogInformation("Worker picked up {0} requests for dispatch at at: {time}", generatedRequests.Count(), DateTimeOffset.Now);
    }

And when I run the worker through Kestrel I can see in my console logs, but not in my .txt file nor .json file

UPDATE #1 I tried with this answer below, but I still have an issue when I deploy my worker, I do not see any logs. When I run it thrpugh service.exe fi;le or just do debugg (Kestrel) Logs files are generated.

Here is updated application.json:

"Serilog": {
  "Using": [],
  "MinimumLevel": {
    "Default": "Information",
    "Override": {
      "Microsoft": "Warning",
      "System": "Warning"
    }
  },
  "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
  "WriteTo": [
    { "Name": "Console" },
    {
      "Name": "File",
      "Args": {
        "path": ".\\Logs\\log_.txt",
        "outputTemplate": "{Timestamp:o} {Message}{NewLine:1}{Exception:1}",
        "rollingInterval": "Day",
        "retainedFileCountLimit": 7
      }
    },
    {
      "Name": "File",
      "Args": {
        "path": ".\\Logs\\log_.json",
        "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
        "outputTemplate": "{Timestamp:o} {Message}{NewLine:1}{Exception:1}",
        "rollingInterval": "Day",
        "retainedFileCountLimit": 7
      }
    }
  ]
}

and this is the Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        var configuration = new ConfigurationBuilder()
                    .AddJsonFile("appsettings.json")
                    .Build();

        Log.Logger = new LoggerConfiguration()
                            .ReadFrom.Configuration(configuration)
                            .CreateLogger();

        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .UseWindowsService()
                    .ConfigureServices((hostContext, services) =>
                    {

                        var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
                        optionsBuilder.UseSqlServer(hostContext.Configuration.GetConnectionString("DefaultConnection"));
                        services.AddScoped<ApplicationDbContext>(s => new ApplicationDbContext(optionsBuilder.Options));
                        services.AddHostedService<Worker>();
                    })
                    .UseSerilog(); // no args
}

UPDATE#2 I updated Program class like this:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .UseWindowsService()
                    .ConfigureServices((hostContext, services) =>
                    {

                        var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
                        optionsBuilder.UseSqlServer(hostContext.Configuration.GetConnectionString("DefaultConnection"));
                        services.AddScoped<ApplicationDbContext>(s => new ApplicationDbContext(optionsBuilder.Options));
                        services.AddHostedService<Worker>();
                    })
                    .UseSerilog((hostContext, services, logger) => {
                        logger.ReadFrom.Configuration(hostContext.Configuration);
                    });
    }
}

Also I put simple stuff in appsettings.json:

  "Serilog": {
    "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": { "path": "Logs/log.txt" }
      },
      //{
      //  "Name": "File",
      //  "Args": {
      //    "path": ".\\Logs\\log_.txt",
      //    "outputTemplate": "{Timestamp:o} {Message}{NewLine:1}{Exception:1}",
      //    "rollingInterval": "Day",
      //    "retainedFileCountLimit": 7
      //  }
      //},
      {
        "Name": "File",
        "Args": {
          "path": ".\\Logs\\log_.json",
          "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
          "outputTemplate": "{Timestamp:o} {Message}{NewLine:1}{Exception:1}",
          "rollingInterval": "Day",
          "retainedFileCountLimit": 7
        }
      }
    ]
  }

Here is the also call of the _logger which is working, agaion when I start .exe file (console)

using Microsoft.Extensions.Logging;
...
private readonly ILogger<Worker> _logger;
...
_logger.LogInformation("Worker picked up {0} requests for dispatch at: {time}", generatedRequests.Count(), DateTimeOffset.Now);

UPDATE#3 I just setup absolute path for logging c:\\Logs\log.txt and it works partially - it creates a txt file, but file is empty. Here is the new appsettings.json:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=svcProcessor_dev;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "WorkerOptions": {
    "Batch": 10,
    "Delay": 5000,
    "MaxAttempts":  3
  },
  "Serilog": {
    "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": { "path": "C:\\Logs\\log.txt" }
      }
      //{
      //  "Name": "File",
      //  "Args": {
      //    "path": "C:\\Logs\\log_.txt",
      //    "outputTemplate": "{Timestamp:o} {Message}{NewLine:1}{Exception:1}",
      //    "rollingInterval": "Day",
      //    "retainedFileCountLimit": 7
      //  }
      //}
      //{
      //  "Name": "File",
      //  "Args": {
      //    "path": "C:\\Logs\\log_.json",
      //    "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
      //    "outputTemplate": "{Timestamp:o} {Message}{NewLine:1}{Exception:1}",
      //    "rollingInterval": "Day",
      //    "retainedFileCountLimit": 7
      //  }
      //}
    ]
  }
}

update#4

Here is the minimal example of my worker worker example


Solution

  • You aren't adding the infrastructure to convert Serilog's Logger into a Microsoft.Extensions.Logging.ILogger. As it stands, you are are just creating the Serilog Logger but not actually using it. What you're seeing in the console are actually the logs from the built-in Microsoft logger and not from Serilog.

    Fortunately Serilog has an adapter from their logger to the Microsoft logger interfaces and various helper packages depending on your usage scenario. You're using the Generic Host so first add the Serilog.Extensions.Hosting package to your project. Then call the UseSerilog extension method. The delegate passed to this method receives an instance of the HostBuilderContext from which you can retrieve the current IConfiguration as well as the IServiceProvider and a LoggingConfiguration you use to configure the logger:

    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
    
    public static IHostBuilder CreateHostBuilder(string[] args) =>
       Host.CreateDefaultBuilder(args)              
           .ConfigureServices((hostContext, services) =>
            {
               /* elided */
            })
           .UseSerilog((hostContext, services, logger) => {
                logger.ReadFrom.Configuration(hostContext.Configuration);
            });
    

    Note that there's no longer a need to set the Log.Logger property and build a LoggerConfiguration directly from within Main. The UseSerilog method will set the static logger automatically--though you won't need it, you're logging via ILogger<> anyways.

    Alternatively, you can configure the logger way you do currently (directly in Main). If you go this route you would call UseSerilog without any parameters. This will in turn use the current configuration of the static logger.

    public static void Main(string[] args)
    {
        var config = new ConfigurationBuilder()
                            .AddJsonFile("appsettings.json")
                            .Build();
    
        Log.Logger = new LoggingConfiguration()
                            .ReadFromConfiguration(config)
                            .CreateLogger();
    
        CreateHostBuilder(args).Build().Run();
    }
    
    public static IHostBuilder CreateHostBuilder(string[] args) =>
       Host.CreateDefaultBuilder(args)              
           .ConfigureServices((hostContext, services) =>
            {
               /* elided */
            })
           .UseSerilog(); // no args
    

    Personally I don't like this option, especially when using ReadFrom.Configuration as it requires you to build an extra configuration object. I'd prefer to use the one passed in to the callback since it will match all other configuration used by the Host.

    ASP.NET Core

    You tagged your question with aspnet-core and refer to "Kestrel" in your question. However the code you demonstrated shows a pretty basic Generic Host and not a web application. If this really is a web application then you would add the Serilog.AspNetCore package instead. That package implicitly references Serilog.Extensions.Hosting and includes some other bits and pieces for use from within a web application.