How to log to a file without using third party logger in .Net Core?

前端 未结 8 1117
北海茫月
北海茫月 2020-12-25 09:22

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

public void ConfigureSer         


        
相关标签:
8条回答
  • 2020-12-25 09:51

    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.

    0 讨论(0)
  • 2020-12-25 09:59

    .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 3.x 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>();
            });
    

    ASP.NET Core 2.1+ web applications:

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

    .NET Core 2.1+ 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
    using (var sp = services.BuildServiceProvider())
    {
        var loggerFactory = sp.GetService<ILoggerFactory>();
        // ...
    }
    

    For configuration details, see the project site.

    0 讨论(0)
  • 2020-12-25 10:01

    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

    • can be used in .NET Core 1.x and .NET Core 2.x / 3.x 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
    0 讨论(0)
  • 2020-12-25 10:02

    No file logger is currently included in the framework but adding one is being considered: http://github.com/aspnet/Logging/issues/441. Feel free to upvote the issue on github.

    0 讨论(0)
  • 2020-12-25 10:06

    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.

    0 讨论(0)
  • 2020-12-25 10:07

    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 讨论(0)
提交回复
热议问题