如何不使用.Net Core 中的第三方日志记录器登录到文件?

如何使用 . NET 核心中的第三方日志记录器 (serilog,elmah 等)记录到文件 没有

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


public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
}
149111 次浏览

.NET Core 没有(可能也不会)提供用于文件日志记录的内置 ILogger 提供商实现。

假象,它使跟踪源日志记录(内置的日志记录器框架起源于经典。NET).NET 核心应用程序。对于那些已经熟悉它的人来说,这是可以接受的,但是要做好配置有些麻烦的准备。NET 核心(有关详细信息,请参阅 这篇好文章)。

作为替代方案,您可以尝试我的轻量级 ILogger<T>实现,它涵盖了内置 控制台记录器的特性,并提供了额外的基本特性和良好的可定制性。我的图书馆是免费的、开源的,并且只有框架依赖关系。它完全符合 Microsoft 提供程序的实现。

用法简单如下:

dotnet add package Karambolo.Extensions.Logging.File

NET Core 6 + web 应用程序(最小托管模式) :

var builder = WebApplication.CreateBuilder(args);


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


var app = builder.Build();


// ...

NET Core 3.1 + web 应用程序:

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 + 控制台应用程序:

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

从3.3.0版开始,结构化日志记录也得到了支持。 JSON 格式可以作为一个独立的包开箱即用:

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

有关配置详细信息,请参见 工地

发行 http://github.com/aspnet/Logging/issues/441是关闭的,MS 正式推荐使用第三方文件记录器。您可能希望避免使用重量级日志记录框架,如 serillog、 nlog 等,因为如果您只需要一个简单的日志记录器写入文件,而不需要其他(没有任何附加依赖项) ,那么这些框架就有些过分了。

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

  • 可用于。NET Core 1.x 和。NET Core 2.x/3.x/4.x/5.x 和。NET 6.0 LTS 应用程序
  • 支持用 JSON 或 CSV 编写日志的自定义日志消息处理程序
  • 如果指定了最大日志文件大小,则实现简单的“滚动文件”特性
  • 支持日常文件格式日志(如2022-03-31. log,2022-03-30. log)

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

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

有关标准输出日志记录的信息,请参见 IIS 上 ASP.NET 核心的故障排除

因为 . 净核心(2.2)还没有实现这一点,所以我们仍然需要使用第三方插件。

如果您希望将“错误”、“警告”等记录到.NetCoreAPI 项目中的文本文件中。 你可以使用我在我的项目中使用的 < strong > Serilog

并且您可以按照下面的博客在您的项目上配置 Serilog。

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

亚当和维塔利提供的那些可能仍然是最简单的日期(感谢家伙顺便说一句!)。
由于无论如何都需要一个外部 NuGet,值得一提的是 Serillog 滚动文件接收器还有一个“独立”扩展,可以简单地用作 Serillog 的日志提供程序。Net 核心,而不需要完全交换日志管道(拉出其他依赖项,但是如果您需要提供一些额外的特性,我不认为这是一个问题)

截至2020年3月,情况如下:

巫师。
没那么简单!

首先,请注意.NETCore 日志记录更像是在完整.NETFramework 中进行跟踪。
因此,您需要创建一个 TraceListener (ILoggerProvider)和一个 TraceWriter (ILogger)。

此外,还需要创建一个 LoggerOptions 类,在该类中设置 logfile-name 等等。
此外,您还可以选择创建一个从 ConfigureFromConfigurationOptions<T>继承的类,这个类可以从 ILoggingBuilder.TryAddEnumerable调用,我假设您可以从配置条目配置您的选项。

此外,还需要创建一个扩展方法类,通过它可以将 ILoggerProvider添加到 ILoggingBuilder

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

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

现在它将为每个类别创建一个 logger 实例。
这意味着如果你想把你的日志输出写到 就一份文件,这将会爆炸,因为一旦 ILoggerProvider 为 ApplicationLifetime创建了一个 ILogger 的实例,并且 ILogger 已经创建了一个 FileStream 并且获得了一个锁,为下一个类别(也就是 Host)创建的日志记录器将会失败,因为它不能获得同一个文件上的锁——“太好了”..。

因此,您需要稍微作弊一下——并且总是为您想要记录的所有类别返回相同的 ILogger 实例。

如果您这样做,您将发现您的日志文件被来自 Microsoft.*的日志条目垃圾邮件..。
因此,您只需要返回您想要记录的类别的单例(例如,其名称空间不以 Microsoft 开始的所有内容) ..。 对于所有其他类别,ILoggerProvider。CreateLogger 可以返回 NULL。除了那个 ILogger 提供商。CreateLogger 永远不能返回 NULL,因为那样的话。NET 框架爆炸。

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

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

因此,在这里,我的实现(我不认为它是好的,但是它非常简单,它为我工作,最重要的是,它不是一个 < 插入脏话在这里 > 滴答队列) :

ILogger 提供商:

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

选择:

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;


}


}

分机

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




}




}

范围类:

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




}

选项设置:

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


}




}

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

大多数答案提供了使用第三方库的解决方案。这使得做其他事情听起来非常复杂。因此,我决定共享这种使用第三方库记录到文件 没有的简单方法。你所要做的就是将这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)
{
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()
{
}
}
}

扩展:

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

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

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


//...
}

因此,现在您必须在您的项目中创建一个名为“ log”的文件夹。现在,您的日志将写入为每个日期创建的文件。您可以查看创建的文件并将日志与控制台输出进行比较。

此答案已更新。日志文件格式已修复。现在可以为每个日期记录不同的文件了。

如果你不喜欢我的日志提供商的实现,你可以自己使用这个信息: https://www.codeproject.com/Articles/1556475/How-to-Write-a-Custom-Logging-Provider-in-ASP-NET

注意: 这是一个简单的解决方案,可能不适用于繁忙的系统。如果需要高级日志记录,可以考虑使用第三方日志记录器。

我只是为 Linux 内置的 syslog()函数编写了一个简单的 C # 包装器,该函数将消息写入默认的 /var/log。没有 nuget 包,没有依赖,没有文件权限。只有70行 C # 代码,1kb 的文件,如果你的目标是 linux,你可以把它们放到你的项目中。

让操作系统来处理所有事情: 日志写入、文件轮换/翻转等等。

这里的要点: https://github.com/jitbit/SyslogCore随时贡献。

用法:

Syslog.Write(Syslog.Level.Warning, "MyAwesomeApp", "something went wrong");