ASP.NET Core Web API:如何用Log4Net为每个用户单独记录日志
问题描述
我需要为每个登录用户单独记录日志,比如用户A的日志文件路径为logs/USER A/06.02.2024.log。目前使用Log4Net写入日志到文件,但尝试了以下代码后,日志文件并未创建,请求技术支持。
自定义Log4NetLogger实现
public class Log4NetLogger : ILogger { private readonly ILog _logger; public Log4NetLogger(ILog logger) { _logger = logger; } public IDisposable BeginScope<TState>(TState state) { return null; // No scope support in log4net } public bool IsEnabled(LogLevel logLevel) { switch (logLevel) { case LogLevel.Trace: case LogLevel.Debug: return _logger.IsDebugEnabled; case LogLevel.Information: return _logger.IsInfoEnabled; case LogLevel.Warning: return _logger.IsWarnEnabled; case LogLevel.Error: return _logger.IsErrorEnabled; case LogLevel.Critical: return _logger.IsFatalEnabled; default: throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null); } } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (!IsEnabled(logLevel)) { return; } var message = formatter(state, exception); if (string.IsNullOrWhiteSpace(message)) { return; } switch (logLevel) { case LogLevel.Trace: case LogLevel.Debug: _logger.Debug(message); break; case LogLevel.Information: _logger.Info(message); break; case LogLevel.Warning: _logger.Warn(message); break; case LogLevel.Error: _logger.Error(message, exception); break; case LogLevel.Critical: _logger.Fatal(message, exception); break; default: _logger.Warn($"Encountered unknown log level {logLevel}, writing out as Info."); _logger.Info(message, exception); break; } } } }
自定义LoggerFactory实现
public class CustomLoggerFactory : ILoggerFactory { private readonly IHttpContextAccessor _httpContextAccessor; public CustomLoggerFactory(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public void AddProvider(ILoggerProvider provider) { // Do nothing or implement as needed } public ILogger CreateLogger(string categoryName) { var logger = LogManager.GetLogger(categoryName); var user = _httpContextAccessor.HttpContext?.User; if (user != null && user.Identity.IsAuthenticated) { var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (!string.IsNullOrEmpty(userId)) { var logFilePath = $"logs/{userId}/log.txt"; var logFolder = Path.GetDirectoryName(logFilePath); if (!Directory.Exists(logFolder)) { Directory.CreateDirectory(logFolder); } // Configure log4net to use the dynamic file path GlobalContext.Properties["LogFileName"] = logFilePath; } } return new Log4NetLogger(logger); } public void Dispose() { // Dispose resources if needed } }
Startup配置代码
public static void ConfigureLogging(ILoggingBuilder loggingBuilder) { loggingBuilder.ClearProviders(); loggingBuilder.AddLog4Net(); loggingBuilder.Services.AddSingleton<ILoggerFactory, CustomLoggerFactory>(); }
解决方案
问题根源
- Log4Net配置未关联动态变量:代码中设置了
GlobalContext.Properties["LogFileName"],但log4net配置文件未引用该属性,导致日志仍按默认路径输出。 - LoggerFactory注册顺序错误:先调用
AddLog4Net()会注册默认日志提供者,后续自定义工厂无法覆盖生效。 - 路径格式不符合需求:当前生成的是固定文件名
log.txt,未实现按日期命名的要求。 - HttpContextAccessor未注册:若未在服务中注入该对象,无法获取当前登录用户信息。
修复步骤
1. 修改Log4Net配置文件
在log4net.config中配置滚动文件追加器,引用动态路径并按日期生成日志文件:
<appender name="UserRollingFileAppender" type="log4net.Appender.RollingFileAppender"> <!-- 使用GlobalContext中的动态路径 --> <file type="log4net.Util.PatternString" value="%property{LogFileName}" /> <!-- 按日期滚动,生成格式如06.02.2024.log的文件 --> <rollingStyle value="Date" /> <datePattern value="'.'dd.MM.yyyy'.log'" /> <staticLogFileName value="false" /> <appendToFile value="true" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> </layout> </appender> <root> <level value="ALL" /> <appender-ref ref="UserRollingFileAppender" /> </root>
2. 调整LoggerFactory注册顺序
先注册自定义工厂,再添加Log4Net支持,确保日志系统使用自定义逻辑:
public static void ConfigureLogging(ILoggingBuilder loggingBuilder) { loggingBuilder.ClearProviders(); // 先注册自定义LoggerFactory loggingBuilder.Services.AddSingleton<ILoggerFactory, CustomLoggerFactory>(); // 再添加Log4Net支持 loggingBuilder.AddLog4Net(); }
3. 修正日志路径生成逻辑
只生成用户目录,日期部分由log4net自动处理:
public ILogger CreateLogger(string categoryName) { var logger = LogManager.GetLogger(categoryName); var user = _httpContextAccessor.HttpContext?.User; if (user != null && user.Identity.IsAuthenticated) { var userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (!string.IsNullOrEmpty(userId)) { var userLogDir = $"logs/{userId}/"; if (!Directory.Exists(userLogDir)) { Directory.CreateDirectory(userLogDir); } // 设置基础文件名,log4net会自动追加日期后缀 GlobalContext.Properties["LogFileName"] = Path.Combine(userLogDir, "log"); } } return new Log4NetLogger(logger); }
4. 注册HttpContextAccessor
在ConfigureServices中添加依赖注入:
public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); // 其他服务注册... }
5. 检查目录权限
确保应用程序对logs目录拥有读写权限,避免因权限不足导致文件创建失败。
内容的提问来源于stack exchange,提问作者abhi
相关产品推荐
相关产品推荐

