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上提出问题.
see*_*mal 15
Adam 和 Vitaliy 提供的那些可能仍然是迄今为止最简单的(顺便说一句,谢谢你们!)。
由于无论如何都需要外部 NuGet,值得一提的是,还有一个 Serilog 滚动文件接收器的“独立”扩展,可以简单地用作 .net 核心的日志记录提供程序,而无需完全换出日志记录管道(拉取其他依赖项) ,但如果您需要提供的一些额外功能,我认为这不是问题)
截至 2020 年 3 月,这是完整的图片:
问题http://github.com/aspnet/Logging/issues/441已关闭,MS正式建议使用第三方文件记录器。您可能要避免使用重量级的日志记录框架,例如serilog,nlog等,因为如果您只需要一个写入文件的简单记录器而已(没有任何其他依赖项),它们就足够了。
我遇到了同样的情况,并实现了简单(但高效)的文件记录器:https : //github.com/nreco/logging
如果您使用的是 IIS,则可以启用和查看标准输出日志:
true..\logs\stdout)。有关标准输出日志记录的信息,请参阅IIS 上的 ASP.NET Core 疑难解答。
.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)
有关配置详细信息,请参阅项目站点.
死灵法术。
没那么容易!
首先,请注意 .NET Core 日志记录更像是完整 .NET Framework 中的跟踪。
所以你需要同时创建一个 TraceListener (ILoggerProvider) 和一个 TraceWriter (ILogger)。
此外,您需要创建一个 LoggerOptions 类,您可以在其中设置日志文件名等。
此外,您可以选择创建一个继承自 的类 ConfigureFromConfigurationOptions<T>,可以从 调用它ILoggingBuilder.TryAddEnumerable,我假设从配置条目中配置您的选项。
此外,您需要创建一个扩展方法类,您可以使用它添加ILoggerProvider到ILoggingBuilder.
下一个绊脚石是,微软有不同的“日志类别”,例如在 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,则可能会因异步调用因日志记录而相互阻塞而导致死锁。
大多数答案提供了使用第三方库的解决方案。这听起来做任何其他事情都会非常复杂。因此,我决定分享这种无需使用第三方库即可轻松登录文件的方法。您所要做的就是将这 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
| 归档时间: |
|
| 查看次数: |
32398 次 |
| 最近记录: |