178

How to log to a file without using third party logger (serilog, elmah etc.) in .NET CORE?

public void ConfigureServices(IServiceCollection services)
{
    services.AddLogging();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();
}
4
  • 15
    That's because there is no out-of-the-box logger which logs to a file, hence my comment: "Write your own". Look at how console logger is implemented, its just 3 classes or so you need to implement. Since your constrain is "no third party" library, the only remaining answer is: "Write your own [logger]".
    – Tseng
    Oct 16, 2016 at 18:51
  • And to add to this, in the standard scenario (hosting in IIS or on azure) you just dump / redirect the console output to a file (for IIS/Azure, you do that in the web.config). Can be done on Linux too, with the stuff linux/the shell offers you
    – Tseng
    Oct 16, 2016 at 18:53
  • I agree about that and it can be implemented through Console.SetOut too, but in Enterprise Library and previous logging system had several default options. But looks like new one has only Debug, Console, TraceSource and EventSource which makes me think I'm missing something here and looking for a method and/or hidden links that I may missed on my search. Thanks for your time
    – cilerler
    Oct 16, 2016 at 19:04
  • Writing your own simple logger is easy. You just need to implement 3 classes like in this answer. In some cases, writing your logger is the best solution, but if you need advanced logging consider using a third-party logger.
    – ge333
    Sep 19, 2021 at 12:46

11 Answers 11

72

Issue http://github.com/aspnet/Logging/issues/441 is closed and MS officially recommends to use 3rd party file loggers. You might want to avoid using heavyweight logging frameworks like serilog, nlog etc because they are just excessive in case if all you need is a simple logger that writes to a file and nothing more (without any additional dependencies).

I faced the same situation, and implemented simple (but efficient) file logger: https://github.com/nreco/logging NuGet Release

  • can be used in .NET Core 1.x and .NET Core 2.x / 3.x / 4.x / 5.x and .NET 6.0 - 8.0 apps.
  • supports custom log message handler for writing logs in JSON or CSV
  • implements simple 'rolling file' feature if max log file size is specified
  • Support daily file format log (eg. 2022-03-31.log, 2022-03-30.log)
4
  • 3
    does anyone knows if this is still the case for .net 5?
    – mvoelcker
    May 17, 2021 at 21:54
  • 1
    @mvoelcker NReco.Logging.File should work just fine in NET5 / upcoming NET6 apps. May 18, 2021 at 6:05
  • 1
    For, .NET 6 equivalent the following needs to be added to Program.cs, to config NReco.logging with options in appsettings.json - builder.Services.AddLogging(loggingBuilder => { var loggingSection = builder.Configuration.GetSection("Logging"); loggingBuilder.AddFile(loggingSection); }); Aug 15, 2022 at 7:03
  • 1
    this is GOLD! thanks!
    – JobaDiniz
    Oct 23, 2023 at 17:50
34

Most answers provided a solution using third party libraries. This made it sound that doing anything else will be very complicated. So I have decided to share this easy way of logging to a file without using a third party library. All you have to do is add these 3 classes to your project.

FileLogger:

using Microsoft.Extensions.Logging;
using System;
using System.IO;

namespace WebApp1
{
    public class FileLogger : ILogger
    {
        private string filePath;
        private static object _lock = new object();
        public FileLogger(string path)
        {
            filePath = path;
        }
        public IDisposable BeginScope<TState>(TState state)
        {
            return null;
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            //return logLevel == LogLevel.Trace;
            return true;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (formatter != null)
            {
                lock (_lock)
                {
                    string fullFilePath = Path.Combine(filePath, DateTime.Now.ToString("yyyy-MM-dd") + "_log.txt");
                    var n = Environment.NewLine;
                    string exc = "";
                    if (exception != null) exc = n + exception.GetType() + ": " + exception.Message + n + exception.StackTrace + n;
                    File.AppendAllText(fullFilePath, logLevel.ToString() + ": " + DateTime.Now.ToString() + " " + formatter(state, exception) + n + exc);
                }
            }
        }
    }
}

FileLoggerProvider:

using Microsoft.Extensions.Logging;

namespace WebApp1
{
    public class FileLoggerProvider : ILoggerProvider
    {
        private string path;
        public FileLoggerProvider(string _path)
        {
            path = _path;
        }
        public ILogger CreateLogger(string categoryName)
        {
            return new FileLogger(path);
        }

        public void Dispose()
        {
        }
    }
}

FileLoggerExtensions:

using Microsoft.Extensions.Logging;

namespace WebApp1
{
    public static class FileLoggerExtensions
    {
        public static ILoggerFactory AddFile(this ILoggerFactory factory, string filePath)
        {
            factory.AddProvider(new FileLoggerProvider(filePath));
            return factory;
        }
    }
}

Add these lines to your Configure method in Startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddFile(Path.Combine(Directory.GetCurrentDirectory(), "logs"));

    //...
}

So now you will have to create a folder called "logs" in your project. Your logs will be now written to files created for each date. You can view the created file and compare the logs with the console output.

This answer has been updated. Log file formatting has been fixed. Now it is possible to log to different files for each date.

If you don't like the implementation of my logging provider you can make your own using this info: https://www.codeproject.com/Articles/1556475/How-to-Write-a-Custom-Logging-Provider-in-ASP-NET

Note: This is simple solution and it may not be suitable for busy systems. If you need advanced logging consider using a third party logger.

6
  • 9
    It's not that simple. This code has quite a few concurrency bugs already and will end up crashing with locked file exceptions on a busy system - it creates new FileLogger instances all the time that append to the same file. The inefficient way of generating the message line means it leaks temporary strings as well. Log level filters aren't respected. Scopes are broken. Using this code in a real application will have a severe impact, especially if the developer expects the logger to not log verbose or debug messages, as it should. Sep 17, 2021 at 14:51
  • 9
    This code will not work on a busy system, but it is useful for applications that don’t need advanced logging and if developers are looking for a simple solution without using a third-party library. This solution was used for a small project in my company and was capable of writing more than 200 log messages per second. Using this code never caused the application to crash. If this code doesn’t work for you, consider using a third-party library.
    – ge333
    Sep 18, 2021 at 10:19
  • Except you didn't implement it properly Panagiotis Kanavos, this is a working simple solution. Apr 5, 2022 at 13:26
  • 1
    It's a great job. I will only add three lines in case the directory does not exist. After lock (_lock) in FileLogger class if(!System.IO.Directory.Exists(filePath)){ System.IO.Directory.CreateDirectory(filePath); } Apr 13, 2022 at 12:27
  • 8
    It IS a third-party. You're just writing it...
    – as9876
    Aug 18, 2022 at 22:16
32

.NET Core does not (and probably will not) provide a built-in ILoggerProvider implementation for file logging.

There is a facade which makes trace source logging (the built-in logger framework originated in classic .NET) available for .NET Core applications. It can be ok for those who are already familiar with it, but be prepared that configuration is somewhat cumbersome on .NET Core (for details, see this nice article).

As an alternative, you may try my lightweight ILogger<T> implementation which covers the features of the built-in ConsoleLogger and provides additional essential features and good customizability. My library is free, open-source and has only framework dependencies. It completely conforms with the Microsoft provider implementations.

Usage is as simple as follows:

dotnet add package Karambolo.Extensions.Logging.File

ASP.NET Core 6+ web applications (minimal hosting model):

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddFile(o => o.RootPath = o.RootPath = builder.Environment.ContentRootPath);

var app = builder.Build();

// ...

ASP.NET Core 3.1+ web applications:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder
                .ConfigureLogging((ctx, builder) =>
                {
                    builder.AddConfiguration(ctx.Configuration.GetSection("Logging"));
                    builder.AddFile(o => o.RootPath = ctx.HostingEnvironment.ContentRootPath);
                })
                .UseStartup<Startup>();
        });

.NET Core 3.1/.NET 5+ console applications:

// build configuration
// var configuration = ...;

// configure DI
var services = new ServiceCollection();

services.AddLogging(builder =>
{
    builder.AddConfiguration(configuration.GetSection("Logging"));
    builder.AddFile(o => o.RootPath = AppContext.BaseDirectory);
});

// create logger factory
await using (var sp = services.BuildServiceProvider())
{
    var loggerFactory = sp.GetService<ILoggerFactory>();
    // ...
}

As of v3.3.0 structured logging is also supported. JSON format is available out of the box, as a separate package:

dotnet add package Karambolo.Extensions.Logging.File.Json

For configuration details, see the project site.

2
  • 2
    I started using your library, but do not know how to supply the json configuration that is shown on the project site. Is there documentation?
    – umutesen
    Feb 22, 2018 at 15:57
  • 1
    You find the .NET Core configuration API documentation here.
    – Adam Simon
    Feb 25, 2018 at 11:54
23

The ones provided by Adam and Vitaliy are still probably the most simple ones to date (thanks guys btw!).
Since an external NuGet is necessary anyway, worth mentioning that there is also a "standalone" extension of the Serilog rolling file sink that can be simply used as a logging provider for .net core, without completely swapping out the logging pipeline (pulls other dependencies, but I don't see that as an issue if you need the few extra features provided)

As of March 2020, this is the complete picture:

3
  • 3
    Warning:NReco.Logging.File and Karambolo.Extensions.Logging.File are loggers using a queue and an interval flushing to disk. If your application crashes, you'll be missing the relevant lines, because the queue isn't flushed on the exact moment of the crash - unless you are extremely lucky. Don't know about Serilog, but they probably have their implementation from there. Aug 30, 2021 at 11:47
  • 4
    Author of Karambolo logger here. I don't know about the other libs but mine uses a queue per log file and definitely doesn't use interval flushing. Log entries are written ASAP. (Precisely, written to a write buffer and there's the FileAccessMode option for controlling when to flush the buffer. By default, each entry written is flushed to disk immediately.) Also, it's ensured that pending entries are flushed when the logger provider is disposed. So if you end up with missing lines, you either missed disposing the logger provider or your program crashed with some non-recoverable exception.
    – Adam Simon
    Aug 9, 2022 at 0:55
  • BTW, it looks like, if something does interval flushing, then it's Serilog.Extensions.Logging.File: github.com/serilog/serilog-extensions-logging-file/blob/…
    – Adam Simon
    Aug 9, 2022 at 1:09
18

If you are using IIS, you can enable and view stdout logs:

  1. Edit the web.config file.
  2. Set stdoutLogEnabled to true.
  3. Change the stdoutLogFile path to point to the logs folder (for example, .\logs\stdout).
  4. Save the file.
  5. Make a request to the app.
  6. Navigate to the logs folder. Find and open the most recent stdout log.

For information about stdout logging, see Troubleshoot ASP.NET Core on IIS.

3
  • 1
    What about the windows services projects in .net core ? Sep 11, 2020 at 8:13
  • This really saved my day
    – the91end
    May 17, 2021 at 10:45
  • 2
    Just note that MS says: "Using the stdout log is only recommended for troubleshooting app startup issues when hosting on IIS or when using development-time support for IIS with Visual Studio, not while debugging locally and running the app with IIS Express. Don't use the stdout log for general app logging purposes."
    – KekuSemau
    Jan 21, 2022 at 8:53
5

Necromancing.
It's not quite that easy !

First, notice that .NET Core logging is more like Tracing in Full .NET Framework.
So you need to create both a TraceListener (ILoggerProvider), and a TraceWriter (ILogger).

Also, you need the create a LoggerOptions class, where you set the logfile-name, etc.
Furthermore, you can optionally create a class which inherits from ConfigureFromConfigurationOptions<T>, which can be called from ILoggingBuilder.TryAddEnumerable, I presume to configure your options from configuration entries.

Also, you need to create an an Extension-Method class, with which you can add ILoggerProvider to ILoggingBuilder.

The next stumbling-block is, that Microsoft has different "log-categories", e.g. in a Windows-Service

Microsoft.Extensions.Hosting.Internal.ApplicationLifetime
Microsoft.Extensions.Hosting.Internal.Host
Microsoft.Hosting.Lifetime

Now it will create a logger-instance for each of those categories.
Which means if you want to write your log-output to just one file, this will explode, because once the ILoggerProvider has created an instance of ILogger for ApplicationLifetime, and ILogger has created a FileStream and acquired a lock on it, the logger that gets created for the next category (aka Host) will fail, because it can't acquire the lock on the same file - "great" ...

So you need to cheat a little bit - and always return the same ILogger instance for all categories you want to log.

If you do that, you will find your logfile spammed by log-entries from Microsoft.* ...
So you need to only return your singleton for the categories you want to log (e.g. everything whose namespace doesn't start with Microsoft)... For all other categories, ILoggerProvider.CreateLogger can return NULL. Except that ILoggerProvider.CreateLogger CANNOT return NULL ever, because then the .NET framework explodes.

Thus, you need to create an IgnoreLogger, for all the log-categories you don't want to log...
Then you need to return the same instance (singleton) of logger all the time, for all categories, so it won't create a second instance of Logger and try to acquire a lock on the already locked logfile. Yupee.

Highlighs include, instead of using a singleton with locks, some file-loggers out there put log-statements in a queue, so they can have multiple instances writing to the same file by making the queue static, and periodically flushing that static queue to disk. Of course, if your service EXITS (e.g. crashes) before the queue has been flushed, you will be missing the exact lines in the logfile which would have told you why your service crashed there (or did otherwise funny things)... e.g. your service works fine when debugging with VS or when running on console, but fails as windows-service, because the current-directory when running as windows-serivce is C:\windows\system32, and consequently, your configuration files cannot be found/read. But although you tried to log that, you don't get the error log on that, because the queue hasn't been flushed before the program exited. And tick-tack, just like that, the day is over until you find out what the problem actually was...

So here, my implementation (i don't claim it's good, but it's as simple as it gets, and it works for me, and most importantly, IT'S NOT A <insert expletive here> TICK-TACK QUEUE):

ILoggerProvider:

namespace RamMonitor
{


    public class IgnoreLogger
      : Microsoft.Extensions.Logging.ILogger
    {

        public class IgnoreScope
            : System.IDisposable
        {
            void System.IDisposable.Dispose()
            {
            }
        }

        System.IDisposable Microsoft.Extensions.Logging.ILogger.BeginScope<TState>(TState state)
        {
            return new IgnoreScope();
        }

        bool Microsoft.Extensions.Logging.ILogger.IsEnabled(
            Microsoft.Extensions.Logging.LogLevel logLevel)
        {
            return false;
        }

        void Microsoft.Extensions.Logging.ILogger.Log<TState>(
              Microsoft.Extensions.Logging.LogLevel logLevel
            , Microsoft.Extensions.Logging.EventId eventId
            , TState state
            , System.Exception exception
            , System.Func<TState, System.Exception, string> formatter)
        { }

    }


    public class FileLoggerProvider
        : Microsoft.Extensions.Logging.ILoggerProvider
    {

        protected FileLoggerOptions m_options;
        protected IgnoreLogger m_nullLogger;
        protected FileLogger m_cachedLogger;


        public FileLoggerProvider(Microsoft.Extensions.Options.IOptions<FileLoggerOptions> fso)
        {
            this.m_options = fso.Value;
            this.m_nullLogger = new IgnoreLogger();
            this.m_cachedLogger = new FileLogger(this, this.m_options, "OneInstanceFitsAll");
        } // End Constructor 


        Microsoft.Extensions.Logging.ILogger Microsoft.Extensions.Logging.ILoggerProvider
            .CreateLogger(string categoryName)
        {
            // Microsoft.Extensions.Hosting.Internal.ApplicationLifetime
            // Microsoft.Extensions.Hosting.Internal.Host
            // Microsoft.Hosting.Lifetime
            if (categoryName.StartsWith("Microsoft", System.StringComparison.Ordinal))
                return this.m_nullLogger; // NULL is not a valid value... 

            return this.m_cachedLogger;
        } // End Function CreateLogger 



        private bool disposedValue = false; // Dient zur Erkennung redundanter Aufrufe.

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: verwalteten Zustand (verwaltete Objekte) entsorgen.
                }

                // TODO: nicht verwaltete Ressourcen (nicht verwaltete Objekte) freigeben und Finalizer weiter unten überschreiben.
                // TODO: große Felder auf Null setzen.

                disposedValue = true;
            }
        }


        // TODO: Finalizer nur überschreiben, wenn Dispose(bool disposing) weiter oben Code für die Freigabe nicht verwalteter Ressourcen enthält.
        // ~FileLoggerProvider() {
        //   // Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in Dispose(bool disposing) weiter oben ein.
        //   Dispose(false);
        // }


        // Dieser Code wird hinzugefügt, um das Dispose-Muster richtig zu implementieren.
        void System.IDisposable.Dispose()
        {
            // Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in Dispose(bool disposing) weiter oben ein.
            Dispose(true);
            // TODO: Auskommentierung der folgenden Zeile aufheben, wenn der Finalizer weiter oben überschrieben wird.
            // GC.SuppressFinalize(this);
        }


    } // End Class FileLoggerProvider 


}

ILogger:

// using Microsoft.Extensions.Logging;


namespace RamMonitor
{


    public class FileLogger
        : Microsoft.Extensions.Logging.ILogger
        , System.IDisposable
    {

        protected const int NUM_INDENT_SPACES = 4;

        protected object m_scopeLock;
        protected object m_lock;

        protected Microsoft.Extensions.Logging.LogLevel m_logLevel;
        protected Microsoft.Extensions.Logging.ILoggerProvider m_provider;
        protected int m_indentLevel;
        protected System.IO.TextWriter m_textWriter;

        protected System.Collections.Generic.LinkedList<object> m_scopes;

        protected System.IO.Stream m_stream;


        public FileLogger(Microsoft.Extensions.Logging.ILoggerProvider provider, FileLoggerOptions options, string categoryName)
        {
            this.m_scopeLock = new object();
            this.m_lock = new object();

            this.m_logLevel = Microsoft.Extensions.Logging.LogLevel.Trace;
            this.m_provider = provider;
            this.m_indentLevel = 0;
            this.m_scopes = new System.Collections.Generic.LinkedList<object>();
            // this.m_textWriter = System.Console.Out;

            string logDir = System.IO.Path.GetDirectoryName(options.LogFilePath);
            if (!System.IO.Directory.Exists(logDir))
                System.IO.Directory.CreateDirectory(logDir);

            this.m_stream = System.IO.File.Open(options.LogFilePath, System.IO.FileMode.Append, System.IO.FileAccess.Write, System.IO.FileShare.Read);
            this.m_textWriter = new System.IO.StreamWriter(this.m_stream, System.Text.Encoding.UTF8);
            this.m_textWriter.Flush();
            this.m_stream.Flush();
        } // End Constructor 


        protected void WriteIndent()
        {
            this.m_textWriter.Write(new string(' ', this.m_indentLevel * NUM_INDENT_SPACES));
        } // End Sub WriteIndent 


        System.IDisposable Microsoft.Extensions.Logging.ILogger.BeginScope<TState>(TState state)
        {
            FileLoggerScope<TState> scope = null;

            lock (this.m_lock)
            {
                scope = new FileLoggerScope<TState>(this, state);
                this.m_scopes.AddFirst(scope);

                this.m_indentLevel++;
                WriteIndent();
                this.m_textWriter.Write("BeginScope<TState>: ");
                this.m_textWriter.WriteLine(state);
                this.m_indentLevel++;

                // this.m_provider.ScopeProvider.Push(state);
                // throw new System.NotImplementedException();

                this.m_textWriter.Flush();
                this.m_stream.Flush();
            }

            return scope;
        } // End Function BeginScope 


        public void EndScope<TState>(TState scopeName)
        {
            lock (this.m_lock)
            {
                // FooLoggerScope<TState> scope = (FooLoggerScope<TState>)this.m_scopes.First.Value;
                this.m_indentLevel--;

                WriteIndent();
                this.m_textWriter.Write("EndScope ");
                // this.m_textWriter.WriteLine(scope.ScopeName);
                this.m_textWriter.WriteLine(scopeName);

                this.m_indentLevel--;
                this.m_scopes.RemoveFirst();

                this.m_textWriter.Flush();
                this.m_stream.Flush();
            }
        } // End Sub EndScope 


        bool Microsoft.Extensions.Logging.ILogger.IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel)
        {
            // return this.m_provider.IsEnabled(logLevel);
            return logLevel >= this.m_logLevel;
        } // End Function IsEnabled 


        void Microsoft.Extensions.Logging.ILogger.Log<TState>(
              Microsoft.Extensions.Logging.LogLevel logLevel
            , Microsoft.Extensions.Logging.EventId eventId
            , TState state
            , System.Exception exception
            , System.Func<TState, System.Exception, string> formatter)
        {

            lock (this.m_lock)
            {
                WriteIndent();
                this.m_textWriter.Write("Log<TState>: ");
                this.m_textWriter.WriteLine(state);
                this.m_textWriter.Flush();
                this.m_stream.Flush();

                System.Exception currentException = exception;

                while (currentException != null)
                {
                    WriteIndent();
                    this.m_textWriter.Write("Log<TState>.Message: ");
                    this.m_textWriter.WriteLine(exception.Message);
                    WriteIndent();
                    this.m_textWriter.Write("Log<TState>.StackTrace: ");
                    this.m_textWriter.WriteLine(exception.StackTrace);
                    this.m_textWriter.Flush();
                    this.m_stream.Flush();

                    currentException = currentException.InnerException;
                } // Whend 

            } // End Lock 

        } // End Sub Log 


        void System.IDisposable.Dispose()
        {
            this.m_textWriter.Flush();
            this.m_stream.Flush();
            this.m_textWriter.Close();
            this.m_stream.Close();
        } // End Sub Dispose 


    } // End Class FileLogger 


} // End Namespace RamMonitor 

Options:

namespace RamMonitor
{

    public class FileLoggerOptions
    {
        public FileLoggerOptions()
        { }


        public string LogFilePath { get; set; }
        
        public Microsoft.Extensions.Logging.LogLevel LogLevel { get; set; } =
            Microsoft.Extensions.Logging.LogLevel.Information;

    }

}

Extensions

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;

using Microsoft.Extensions.Options;


namespace RamMonitor
{


    public static class FileLoggerExtensions
    {


        public static Microsoft.Extensions.Logging.ILoggingBuilder AddFileLogger( 
              this Microsoft.Extensions.Logging.ILoggingBuilder builder
            , System.Action<FileLoggerOptions> configure)
        {
            builder.AddConfiguration();

            builder.Services.TryAddEnumerable(Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Singleton<
                    Microsoft.Extensions.Logging.ILoggerProvider,
                    FileLoggerProvider
                >()
            );

            builder.Services.TryAddEnumerable(Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Singleton
                <IConfigureOptions<FileLoggerOptions>, FileLoggerOptionsSetup>());

            builder.Services.TryAddEnumerable(
                Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Singleton
                <
                    IOptionsChangeTokenSource<FileLoggerOptions>,
                    LoggerProviderOptionsChangeTokenSource<FileLoggerOptions
                    , FileLoggerProvider>
                >());

            builder.Services.Configure(configure);

            return builder;
        }


    }


}

ScopeClass:

namespace RamMonitor
{

    public class FileLoggerScope<TState>
        : System.IDisposable
    {
        protected FileLogger m_logger;
        protected TState m_scopeName;


        public TState ScopeName
        {
            get
            {
                return this.m_scopeName;
            }
        } // End Property ScopeName


        public FileLoggerScope(FileLogger logger, TState scopeName)
        {
            this.m_logger = logger;
            this.m_scopeName = scopeName;
        } // End Constructor  


        void System.IDisposable.Dispose()
        {
            this.m_logger.EndScope(this.m_scopeName);
        } // End Sub Dispose 


    } // End Class FileLoggerScope 


}

OptionsSetup:

namespace RamMonitor
{


    internal class FileLoggerOptionsSetup
        : Microsoft.Extensions.Options.ConfigureFromConfigurationOptions<FileLoggerOptions>
    {

        public FileLoggerOptionsSetup(
            Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration<FileLoggerProvider>
            providerConfiguration
        )
            : base(providerConfiguration.Configuration)
        {
            // System.Console.WriteLine(providerConfiguration);
        }

    }


}

Note:
The scopes will not be thread-safe this way.
If you have a multi-threaded application - remove the scopes, or make it thread-safe.
The way MS implemented scopes, I can't think of a proper way to do this.
If you add a separate ScopeLock, you might get deadlocks by asynchronous calls blocking each other due to logging.

9
  • 6
    Am I the only one that expects in 2021 we wouldn't be writing 200 line programs to facilitate something as mundane as logging? To your point there's still thread safety and every other little thing to worry about. I generally go for nlog because Developers are users too and we should just have expectations. :) Appreciate the example though looks nice.
    – Dan Chase
    Nov 1, 2021 at 1:53
  • 1
    @Dan Chase; I advise caution using 3rd party logging tools. You don't know what they do (of course unless you read their source-code), and that might come back to hurt you. Most have solved the problem with interval-flushing of a concurrent-queue. That's bad - if your program abnormally exists, there won't be no flushing - and you will be missing the log-file entries. I would advise to log to network instead - UPD or TCP to syslog. You can then either use Visual Syslog (development on Windows), or any other syslog server. Or roll your own (most syslog servers seem to be crap). Nov 1, 2021 at 11:06
  • It should be noted though that syslog is quite archaic, and I don't find it good. Maybe roll your own logging server, and just log JSON over network - via SSL or TCP/UPD, depending on the level of safety or reliablity vs speed you want. Also maybe check StackExchange.Exceptional - i don't know how good that is - and it probably logs to DB - which is fine - then the DB handles the concurrent writes, and since the DB is its own process, the log entries still get written if your process abnormally exits... Also, you can then just assign a uuid/GUID to a scope, and that solves that problem. Nov 1, 2021 at 11:09
  • Then you just need to write a program that displays/visualises the log-entries (correctly) - yet another thing to do in 2021 :) I recommend a HTML/CSS/JavaScript solution. This could also be done with a single log file, you'd just need to write tr and td entries, and write scope-uid into an attribute. Then you can write some inline javascript when you create the logger, and make the log-file a html-file. Then the JS can parse the log-entries-table (CSS display: none), and correctly display the log-info in time-sequence. But I didn't have enough time for that - barely enough for the above. Nov 1, 2021 at 11:15
  • Excellent thoughts!! It sounds like you should make a product and sell it, there seems to be a need for it. I enjoyed reading your commentary, and maybe these things just point out gaps in the evolution in the capabilities of developers. For example we can't focus on AI if we're still trying to figure out logging and datetime - Someone like yourself could come up with a really good solution, and everyone can start using it. Same with date time, it's amazing we're still dealing with that. Thank you for your commentary!
    – Dan Chase
    Nov 4, 2021 at 14:02
2

I just wrote a simple C# wrapper for the linux built-in syslog() function that writes messages to the default /var/log. No nuget packages, no dependencies, no file permissions. Just 70 lines of C# code, 1kb file that you can throw into your project if you're targeting linux.

And let the OS take care of everything: log writing, file rotation/rollover etc. etc.

Here's the gist: https://github.com/jitbit/SyslogCore feel free to contribute.

Usage:

Syslog.Write(Syslog.Level.Warning, "MyAwesomeApp", "something went wrong");
2
  • You have done a good job writing this, but how do I write unhandled exception messages to a file? It looks like I can write only user messages.
    – ge333
    Oct 21, 2021 at 19:28
  • @ge333 if you want to log all errors you have to add a error-handling code yourself. The simplest way is via middleware. app.Use(async (context, next) => { try { await next.Invoke(); } catch (Exception ex) { ...... } Oct 22, 2021 at 8:24
1

Since .Net core (2.2) does not implement this yet, still, we have to use a third party plugin for this.

If you want to log Error, Warning and etc into a txt file in a .Net Core API project. You can use what I used on my project which is called Serilog.

and you can follow the below blog to configure Serilog on your project.

http://anthonygiretti.com/2018/11/19/common-features-in-asp-net-core-2-1-webapi-logging/

0

in your catch where you handle the exception add this following code _log.Add(_Accessor.HttpContext.Session.Id.ToString(), "get_DtProfili", ex.Message); create a new class Log with two params as below:

    private string _logPath = String.Empty;
    private static object _lock = new object();

setting your path in order to your envirment. in app setting you can set the level and the ather information as the path then create this following function

1
0

Use TraceSource logger, bur add switch with level verbose, then all your logging goes to the trace listener test.log :)

    var builder = webApplication.CreateBuilder(args);

    builder.Logging.AddTraceSource(new 
    SourceSwitch("trace", "Verbose"), 
            new TextWriterTraceListener(Path.Combine(b.Environment.ContentRootPath,"trace.log")));
New contributor
triumph109 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
-2

Personally, I think writing to a .log file is the most fundamental way of logging and don't understand why it wouldn't be included in ILogger (and as the default).

Here is a simple way to implement your own:

In the config file appsettings.json set where the log file should be, its name, and level of detail to log:

  "LogPath": "C:\\logs\\",
  "LogFile": "MyApp.log",
  "LogLevel": "1"

In a base class instantiate and open the log file:

    public class BaseClass
    {
        protected string _logPath = ".";
        protected string _logFile = "logfile.log";
        protected int _logLevel = 0;
        public Shared.Log _log;

        public BaseClass()
        {
            _logPath = Configuration.GetValue<string>("LogPath");
            _logFile = Configuration.GetValue<string>("LogFile");
            _logLevel = int.Parse(Configuration.GetValue<string>("LogLevel"));

            _log = new Shared.Log(_logPath, _logFile);
        }

        //...code...
    }

Add the Log class to a shared project:

    public class Log
    {
        private string _logPath = String.Empty;
        private static object _lock = new object();

        public Log(string filePath, string fileName)
        {
            _logPath = filePath + @"\" + fileName;

            //create log file in filePath if it doesnt exist
            if (!File.Exists(_logPath))
            {
                Directory.CreateDirectory(_logPath.Substring(0, _logPath.LastIndexOf(@"\")));
                var fs = File.Create(_logPath);
                fs.Close();
            }
        }

        public void Add(string user, string source, string entry)
        {
            var dtNow = DateTime.Now.ToString("yyyyMMddHHmmss");
            string contents = dtNow + " " + user + " " + source + " " + entry;

            Debug.WriteLine(dtNow + " " + contents);

            lock (_lock)
            {
                File.AppendAllText(_logPath, contents + Environment.NewLine);
            }
        }
    }
  • will create C:\logs\myapp.log
  • will prefix with ordered datetime
  • will lock writing to file for concurrency
  • will also write to output window in Debug

Then in application to write to it:

            if (_logLevel >= 1)
            {
                var msg = "Get id=" + id;
                _log.Add(<current_userid>, "JobController:GetById()", msg);
            }
  • the higher the _logLevel the more detailed the output
  • msg can be formatted to provide whatever is relevant/important at that point
9
  • How do I write unhandled exception messages to a file? It looks like your implementation can write only user messages.
    – ge333
    Dec 4, 2023 at 8:33
  • just wrap your critical code in a try/catch and put _log.Add() inside the catch and concatentate the values of the Exception into msg and it will then log the expection. (I also usually include the class name and method name to uniquely identify where it came from). Dec 5, 2023 at 8:44
  • Relying solely on a try/catch block may not be sufficient for handling all possible exceptions. You may still miss out on other important system-level logs or error messages that are not explicitly caught as exceptions.
    – ge333
    Dec 6, 2023 at 9:50
  • you asked about writing 'unhandled exception messages to a file'. Dec 6, 2023 at 14:11
  • I was trying to point out that your implementation can’t write unhandled exception messages to a file. Using a try/catch block (that’s called handling exceptions yourself and writing them as a user message) as you suggested may not be sufficient and you still miss out on important system-level logs.
    – ge333
    Dec 7, 2023 at 7:52

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.