如何在.Net Core中不使用第三方记录器的情况下登录文件?

cil*_*ler 25 c# .net-core asp.net-core

如何登录到一个文件,而不使用第三方记录器(serilog,ELMAH等).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();
}
Run Code Online (Sandbox Code Playgroud)

Paw*_*wel 19

框架中当前不包含文件记录器,但正在考虑添加一个:http://github.com/aspnet/Logging/issues/441.随意在github上提出问题.

  • 不再考虑-他们没有添加 (2认同)

see*_*mal 15

Adam 和 Vitaliy 提供的那些可能仍然是迄今为止最简单的(顺便说一句,谢谢你们!)。
由于无论如何都需要外部 NuGet,值得一提的是,还有一个 Serilog 滚动文件接收器的“独立”扩展,可以简单地用作 .net 核心的日志记录提供程序,而无需完全换出日志记录管道(拉取其他依赖项) ,但如果您需要提供的一些额外功能,我认为这不是问题)

截至 2020 年 3 月,这是完整的图片:

  • Karambolo 记录器的作者在这里。我不知道其他库,但我的库使用*每个日志文件*一个队列,并且绝对不使用间隔刷新。日志条目会尽快写入。(准确地说,写入写入缓冲区,并且有“FileAccessMode”选项用于控制何时刷新缓冲区。默认情况下,写入的每个条目都会立即刷新到磁盘。)此外,还确保当记录器提供程序启动时,会刷新挂起的条目。处置。因此,如果您最终丢失了行,则您要么错过了记录器提供程序的处理,要么您的程序因某些不可恢复的异常而崩溃。 (3认同)
  • 警告:NReco.Logging.File 和 Karambolo.Extensions.Logging.File 是使用队列和间隔刷新到磁盘的记录器。如果您的应用程序崩溃,您将丢失相关行,因为队列不会在崩溃的确切时刻刷新 - 除非您非常幸运。不知道 Serilog,但他们可能从那里开始实施。 (2认同)

Vit*_*nko 9

问题http://github.com/aspnet/Logging/issues/441已关闭,MS正式建议使用第三方文件记录器。您可能要避免使用重量级的日志记录框架,例如serilog,nlog等,因为如果您只需要一个写入文件的简单记录器而已(没有任何其他依赖项),它们就足够了。

我遇到了同样的情况,并实现了简单(但高效)的文件记录器:https : //github.com/nreco/logging

  • 可以在.NET Core 1.x和.NET Core 2应用程序中使用
  • 支持自定义日志消息处理程序,用于以JSON或CSV格式写入日志
  • 如果指定了最大日志文件大小,则实现简单的“滚动文件”功能

  • 有谁知道.net 5 是否仍然如此? (3认同)

ale*_*291 8

如果您使用的是 IIS,则可以启用和查看标准输出日志:

  1. 编辑web.config文件。
  2. stdoutLogEnabled设置为true.
  3. stdoutLogFile路径更改为指向日志文件夹(例如,.\logs\stdout)。
  4. 保存文件。
  5. 向应用程序发出请求。
  6. 导航到日志文件夹。查找并打开最新的标准输出日志。

有关标准输出日志记录的信息,请参阅IIS 上的 ASP.NET Core 疑难解答

  • 只需[注意 MS 说](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/logging-and-diagnostics?view=aspnetcore-6.0):“使用仅建议在 IIS 上托管或在 Visual Studio 中使用 IIS 的开发时支持时排除应用程序启动问题,而不是在本地调试并使用 IIS Express 运行应用程序时使用 stdout 日志。不要将 stdout 日志用于一般应用程序日志记录目的”。 (2认同)

Ada*_*mon 7

.NET Core 2.0 2.1已经发布但它仍然没有为文件日志记录提供ILoggerProvider实现.

我一直在寻找可行且轻量级的第三方实现,但没有找到,所以我决定编写一个涵盖内置ConsoleLogger功能的实现,并提供其他基本功能.我的库是免费的,开源的,只有框架依赖.

它完全符合Microsoft提供程序的实现.用法如下:

Install-Package Karambolo.Extensions.Logging.File
Run Code Online (Sandbox Code Playgroud)

.NET Core 2.1:

public void ConfigureServices(IServiceCollection services)
{
    services.AddLogging(lb =>
    {
        lb.AddConfiguration(Configuration.GetSection("Logging"));
        lb.AddFile(o => o.RootPath = AppContext.BaseDirectory);
    });
}
Run Code Online (Sandbox Code Playgroud)

.NET Core 2.0:

public void ConfigureServices(IServiceCollection services)
{
    services.AddLogging(lb =>
    {
        lb.AddConfiguration(Configuration.GetSection("Logging"));
        lb.AddFile(new FileLoggerContext(AppContext.BaseDirectory, "default.log"));
    });

    services.Configure<FileLoggerOptions>(Configuration.GetSection("Logging:File"));
}
Run Code Online (Sandbox Code Playgroud)

.NET Core 1.1:

var context = new FileLoggerContext(AppContext.BaseDirectory, "default.log");
loggerFactory.AddFile(context, Configuration.GetSection("Logging:File"));
Run Code Online (Sandbox Code Playgroud)

有关配置详细信息,请参阅项目站点.

  • 我开始使用你的库,但不知道如何提供项目网站上显示的 json 配置。有文档吗? (2认同)

Ste*_*ger 5

死灵法术。
没那么容易!

首先,请注意 .NET Core 日志记录更像是完整 .NET Framework 中的跟踪。
所以你需要同时创建一个 TraceListener (ILoggerProvider) 和一个 TraceWriter (ILogger)。

此外,您需要创建一个 LoggerOptions 类,您可以在其中设置日志文件名等。
此外,您可以选择创建一个继承自 的类 ConfigureFromConfigurationOptions<T>,可以从 调用它ILoggingBuilder.TryAddEnumerable,我假设从配置条目中配置您的选项。

此外,您需要创建一个扩展方法类,您可以使用它添加ILoggerProviderILoggingBuilder.

下一个绊脚石是,微软有不同的“日志类别”,例如在 Windows 服务中

Microsoft.Extensions.Hosting.Internal.ApplicationLifetime
Microsoft.Extensions.Hosting.Internal.Host
Microsoft.Hosting.Lifetime
Run Code Online (Sandbox Code Playgroud)

现在它将为每个类别创建一个记录器实例。
这意味着如果您只想将日志输出写入一个文件,这将爆炸,因为一旦 ILoggerProvider 创建了 ILogger for 的实例ApplicationLifetime,并且 ILogger 创建了一个 FileStream 并获得了对它的锁定,则创建的记录器对于下一个类别(又名Host)将失败,因为它无法获取同一文件的锁定 - “很棒”...

因此,您需要作弊 - 并且始终为要记录的所有类别返回相同的 ILogger 实例。

如果你这样做,你会发现你的日志文件被来自Microsoft.*......的日志条目垃圾邮件
所以你只需要返回你想要记录的类别的单例(例如所有命名空间不以微软开头的东西)......对于所有其他类别,ILoggerProvider.CreateLogger 可以返回 NULL。除了 ILoggerProvider.CreateLogger 永远不能返回 NULL,因为那时 .NET 框架会爆炸。

因此,您需要为您不想记录的所有日志类别创建一个 IgnoreLogger...
然后您需要始终为所有类别返回记录器的相同实例(单例),因此它不会t 创建 Logger 的第二个实例并尝试获取对已锁定日志文件的锁定。尤佩。

亮点包括,而不是使用带锁的单例,一些文件记录器将日志语句放在队列中,因此它们可以通过使队列静态来将多个实例写入同一个文件,并定期将该静态队列刷新到磁盘. 当然,如果您的服务在队列被刷新之前退出(例如崩溃),您将丢失日志文件中的确切行,这些行会告诉您为什么您的服务在那里崩溃(或做了其他有趣的事情)......例如您的服务在使用 VS 调试或在控制台上运行时工作正常,但作为 windows-service 失败,因为作为 windows-serivce 运行时的当前目录是 C:\windows\system32,因此,无法找到/读取您的配置文件. 但是,尽管您尝试记录该记录,但您没有得到错误记录,因为队列没有 t 在程序退出之前被刷新。滴答作响,就这样,一天结束了,直到你发现问题究竟是什么......

所以在这里,我的实现(我不声称它很好,但它很简单,它对我有用,最重要的是,它不是 <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 


}
Run Code Online (Sandbox Code Playgroud)

日志记录器:

// 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 
Run Code Online (Sandbox Code Playgroud)

选项:

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;

    }

}
Run Code Online (Sandbox Code Playgroud)

扩展

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;
        }


    }


}
Run Code Online (Sandbox Code Playgroud)

范围类:

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 


}
Run Code Online (Sandbox Code Playgroud)

选项设置:

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);
        }

    }


}
Run Code Online (Sandbox Code Playgroud)

注意:
这种作用域不会是线程安全的。
如果您有一个多线程应用程序 - 删除范围,或使其成为线程安全的。
MS 实现范围的方式,我想不出合适的方法来做到这一点。
如果添加单独的 ScopeLock,则可能会因异步调用因日志记录而相互阻塞而导致死锁。

  • 我是唯一一个预计到 2021 年我们不会编写 200 行程序来促进像日志记录这样平凡的事情的人吗?就您而言,仍然存在线程安全和其他需要担心的小事情。我通常会选择 nlog,因为开发人员也是用户,我们应该有期望。:) 欣赏这个例子,虽然看起来不错。 (5认同)

ge3*_*333 5

大多数答案提供了使用第三方库的解决方案。这听起来做任何其他事情都会非常复杂。因此,我决定分享这种无需使用第三方库即可轻松登录文件的方法。您所要做的就是将这 3 个类添加到您的项目中。

文件记录器:

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)
                {
                    File.AppendAllText(filePath, logLevel.ToString() + ": " + formatter(state, exception) + Environment.NewLine);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

文件记录器提供者:

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()
        {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

文件记录器扩展:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

将这些行添加到 Startup.cs 中的 Configure 方法中:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddFile(Path.Combine(Directory.GetCurrentDirectory(), "logger.txt"));
    var logger = loggerFactory.CreateLogger("FileLogger");

    //...
}
Run Code Online (Sandbox Code Playgroud)

所以现在您的日志将写入 logger.txt 文件。您可以查看 logger.txt 文件并将结果与​​控制台输出进行比较。

如果您不喜欢我的日志记录提供程序的实现,您可以使用以下信息自行创建:https : //www.codeproject.com/Articles/1556475/How-to-Write-a-Custom-Logging-Provider-in -ASP-NET

  • 事情没那么简单。该代码已经存在相当多的并发错误,并且最终会在繁忙的系统上因锁定文件异常而崩溃 - 它始终创建附加到同一文件的新“FileLogger”实例。生成消息行的低效方式意味着它也会泄漏临时字符串。不尊重日志级别过滤器。范围被打破。在实际应用程序中使用此代码将产生严重影响,特别是如果开发人员希望记录器*不*记录详细或调试消息,因为它应该。 (9认同)
  • 此代码无法在繁忙的系统上运行,但对于不需要高级日志记录的应用程序以及开发人员正在寻找不使用第三方库的简单解决方案的应用程序非常有用。该解决方案用于我公司的一个小项目,每秒能够写入 200 多条日志消息。使用此代码不会导致应用程序崩溃。如果此代码不适合您,请考虑使用第三方库。 (9认同)
  • 它是第三方。你只是在写... (7认同)