12

I'm working on a ASP.NET Core 3.1 application. I want to log events to file and be able to read them during application runtime. To do that I'm trying to use Serilog.Extensions.Logging.File NuGet package and then specifying log file path as following:

Startup.cs

 public void Configure(IApplicationBuilder app, ILoggerFactory logFactory)
 {
     logFactory.AddFile("Log");
 }

Any attempt to read or write to file like this way

string ReadLog(string logPath)
{
    return System.IO.File.ReadAllText(logPath);
}

ends in System.IO.IOException: 'The process cannot access the file {log-path} because it is being used by another process.' exception.


EDIT: I have installed Serilog.AspNetCore and made changes shown below while also removing logFactory from Configure function. But exception continues to occur.


Program.cs

public static int Main(string[] args)
{
    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Debug()
        .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
        .Enrich.FromLogContext()
        .WriteTo.Console()
        .WriteTo.File(
            "Logs/log-.txt", 
            shared: true,
            flushToDiskInterval: TimeSpan.FromSeconds(5),
            rollingInterval: RollingInterval.Day)
        .CreateLogger();

    try
    {
        Log.Information("Starting web host");
        CreateHostBuilder(args).Build().Run();
        return 0;
    }
    catch (Exception ex)
    {
        Log.Fatal(ex, "Host terminated unexpectedly");
        return 1;
    }
    finally
    {
        Log.CloseAndFlush();
    }
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        }).UseSerilog();

EDIT 2: As requested by julealgon I'm sharing my exact read logic. I'm trying to read using a controller, which I declared following way:

[Controller]
[Route("[controller]")]
public class LogController : Controller
{
    [Route("Read")]
    public IActionResult ReadLog(string logPath)
    {
        if (System.IO.File.Exists(logPath))
        {
            string logContent = System.IO.File.ReadAllText(logPath);//exception appears here.
            return Content(logContent);
        }
        else return NotFound();
    }
}

Then using example query below to read log recorded by Friday, March 13, 2020.

https://localhost:44323/Log/Read?logPath=Logs\log-20200313.txt
4
  • 2
    You might want to review how you are configuring the logger. The recommended approach is to configure it on Program.cs using AddSerilog extension from here. Commented Mar 9, 2020 at 0:42
  • From documentation, AddFile() method seems not accept shared parameter. Commented Mar 9, 2020 at 8:45
  • Can you share your exact reading logic? Is this really your file path "Logs/log-.txt"? Commented Mar 12, 2020 at 21:23
  • @julealgon Sure. Check out my updated question please. Commented Mar 13, 2020 at 14:43

4 Answers 4

11

When reading the file, I think use something like this will work:

using (var stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var reader = new StreamReader(stream))
{
    return reader.ReadToEnd();
}

The trick appears to be the specification of FileShare.ReadWrite as this is the policy used to write to when the sink opens the log file to write to. There's a similar bug reported over in the serilog-sinks-file repo.

Sign up to request clarification or add additional context in comments.

2 Comments

c# 6 does not have 'ReadAllBytes'
@OverMars - yes, you're right, that was my bad when I wrote the answer, ReadAllBytes is an extension method I have in a common library. I've updated the answer with something that should be logically equivalent.
10

When configuring the File sink, there is an overload that provides a shared Boolean argument. If you set that to true (it's false by default) you should then be able to read the contents of the file somewhere else in your application.

6 Comments

Yep! github.com/serilog/serilog-sinks-file - To enable multi-process shared log files, set shared to true: .WriteTo.File("log.txt", shared: true)
does not seem to be working for me. using StreamReader elsewhere in .Net 6 application still gives "it is being used by another process"
@PBMe_HikeIt can you post how you are creating the StreamReader instance? Are you explicitly passing the share option on the constructor? If not, can you give that a try?
@julealgon, I was not passing that and doing that fixed the problem. thanks!
Does not work for me either. Using a RollingLog, but am unable to read the file while my program is running. I need to do this for reasons.
|
0

Another way, where you don't need to read the file all the time is to create a simple sink which collects the logs:

using Serilog.Core;
using Serilog.Events;

public class RuntimeLogsSink : ILogEventSink
{
    private const int MaxLogs = 1000;
    private readonly List<LogEvent> _logEvents = new(MaxLogs);

    public void Emit(LogEvent logEvent)
    {
        lock (_logEvents)
        {
            if (_logEvents.Count == MaxLogs)
            {
                _logEvents.RemoveAt(0);
            }

            _logEvents.Add(logEvent);
        }
    }

    public ImmutableArray<LogEvent> GetLogs()
    {
        lock (_logEvents)
        {
            return [.._logEvents];
        }
    }
}

Then you can simply use it:

var sink = new RuntimeLogsSink();
Log.Logger = new LoggerConfiguration()
        // your configuration ...
        .WriteTo.Sink(sink)
        .CreateLogger();

// Inject sink into some service and use it

Comments

0

After setting the shared option to true you can read and write in the log file.

For writing

  using var stream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
  using var writer = new StreamWriter(stream);
  writer.Write(string.Empty);

For Reading

   using var stream = new FileStream(logFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
   using var reader = new StreamReader(stream);
   vat txt = await reader.ReadToEndAsync();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.