RuoVea.ExLog
10.0.0
dotnet add package RuoVea.ExLog --version 10.0.0
NuGet\Install-Package RuoVea.ExLog -Version 10.0.0
<PackageReference Include="RuoVea.ExLog" Version="10.0.0" />
<PackageVersion Include="RuoVea.ExLog" Version="10.0.0" />
<PackageReference Include="RuoVea.ExLog" />
paket add RuoVea.ExLog --version 10.0.0
#r "nuget: RuoVea.ExLog, 10.0.0"
#:package RuoVea.ExLog@10.0.0
#addin nuget:?package=RuoVea.ExLog&version=10.0.0
#tool nuget:?package=RuoVea.ExLog&version=10.0.0
📋 RuoVea.ExLog 组件概览
RuoVea.ExLog 是一个基于 log4net 的日志记录组件,提供简单易用的日志记录接口,支持 .NET Framework 和 .NET Core 系列版本。
🏗️ 核心架构
1. 日志级别体系
ERROR > WARN > INFO > DEBUG
2. 日志文件组织
按日期分文件夹,按级别分文件:
log/
├── 2024-01-01/
│ ├── Error.log
│ ├── Warn.log
│ ├── Info.log
│ └── Debug.log
├── 2024-01-02/
│ ├── Error.log
│ └── ...
🔧 核心功能类
1. LogFactory 静态类
主要日志方法
信息日志 (INFO)
// 记录信息级别日志
LogFactory.Info("用户登录成功");
LogFactory.Info($"用户 {username} 在 {DateTime.Now} 登录系统");
调试日志 (DEBUG)
// 记录调试级别日志
LogFactory.Debug("开始处理用户请求");
LogFactory.Debug($"请求参数: {JsonConvert.SerializeObject(parameters)}");
警告日志 (WARN)
// 记录警告级别日志
LogFactory.Warn("数据库连接超时");
LogFactory.Warn($"用户 {userId} 尝试访问未授权资源");
错误日志 (ERROR)
// 记录错误级别日志
LogFactory.Error("系统发生异常");
LogFactory.Error(exception);
LogFactory.Error("用户操作失败", exception);
LogFactory.Error($"处理订单 {orderId} 时发生错误", exception);
🚀 完整使用示例
1. 基础日志记录
public class UserService
{
private readonly string _serviceName = nameof(UserService);
public bool ValidateUser(string username, string password)
{
LogFactory.Debug($"{_serviceName}: 开始验证用户 {username}");
try
{
// 验证逻辑
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
LogFactory.Warn($"{_serviceName}: 用户名或密码为空");
return false;
}
var user = GetUserFromDatabase(username);
if (user == null)
{
LogFactory.Warn($"{_serviceName}: 用户 {username} 不存在");
return false;
}
if (user.Password != HashPassword(password))
{
LogFactory.Warn($"{_serviceName}: 用户 {username} 密码错误");
return false;
}
LogFactory.Info($"{_serviceName}: 用户 {username} 验证成功");
return true;
}
catch (Exception ex)
{
LogFactory.Error($"{_serviceName}: 验证用户 {username} 时发生异常", ex);
return false;
}
}
private User GetUserFromDatabase(string username)
{
LogFactory.Debug($"{_serviceName}: 从数据库查询用户 {username}");
// 数据库查询逻辑
return null; // 示例返回
}
private string HashPassword(string password)
{
// 密码哈希逻辑
return password; // 示例返回
}
}
2. Web API 日志记录
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
private readonly IOrderService _orderService;
private readonly string _controllerName = nameof(OrderController);
public OrderController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
{
var requestId = Guid.NewGuid().ToString();
LogFactory.Info($"{_controllerName}: 开始创建订单 [RequestId: {requestId}]");
LogFactory.Debug($"{_controllerName}: 订单请求数据: {JsonConvert.SerializeObject(request)}");
try
{
// 参数验证
if (!ModelState.IsValid)
{
LogFactory.Warn($"{_controllerName}: 订单请求参数验证失败 [RequestId: {requestId}]");
return BadRequest(ModelState);
}
// 业务逻辑
var orderId = await _orderService.CreateOrderAsync(request);
LogFactory.Info($"{_controllerName}: 订单创建成功 [OrderId: {orderId}, RequestId: {requestId}]");
return Ok(new { OrderId = orderId, Success = true });
}
catch (BusinessException ex)
{
LogFactory.Warn($"{_controllerName}: 业务异常 [RequestId: {requestId}, Message: {ex.Message}]");
return BadRequest(new { Success = false, Message = ex.Message });
}
catch (Exception ex)
{
LogFactory.Error($"{_controllerName}: 创建订单时发生系统异常 [RequestId: {requestId}]", ex);
return StatusCode(500, new { Success = false, Message = "系统内部错误" });
}
}
[HttpGet("{id}")]
public async Task<IActionResult> GetOrder(long id)
{
LogFactory.Debug($"{_controllerName}: 查询订单信息 [OrderId: {id}]");
try
{
var order = await _orderService.GetOrderByIdAsync(id);
if (order == null)
{
LogFactory.Warn($"{_controllerName}: 订单不存在 [OrderId: {id}]");
return NotFound();
}
LogFactory.Debug($"{_controllerName}: 订单查询成功 [OrderId: {id}]");
return Ok(order);
}
catch (Exception ex)
{
LogFactory.Error($"{_controllerName}: 查询订单时发生异常 [OrderId: {id}]", ex);
return StatusCode(500, "系统内部错误");
}
}
}
3. 数据库操作日志记录
public class DatabaseService
{
private readonly string _serviceName = nameof(DatabaseService);
private readonly string _connectionString;
public DatabaseService(string connectionString)
{
_connectionString = connectionString;
}
public async Task<List<T>> QueryAsync<T>(string sql, object parameters = null)
{
var operationId = Guid.NewGuid().ToString()[..8];
LogFactory.Debug($"{_serviceName}: 开始数据库查询 [OperationId: {operationId}]");
LogFactory.Debug($"{_serviceName}: SQL: {sql}");
LogFactory.Debug($"{_serviceName}: 参数: {JsonConvert.SerializeObject(parameters)}");
var stopwatch = Stopwatch.StartNew();
try
{
using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();
var result = (await connection.QueryAsync<T>(sql, parameters)).ToList();
stopwatch.Stop();
LogFactory.Debug($"{_serviceName}: 数据库查询成功 [OperationId: {operationId}, 耗时: {stopwatch.ElapsedMilliseconds}ms, 记录数: {result.Count}]");
return result;
}
catch (SqlException ex)
{
stopwatch.Stop();
LogFactory.Error($"{_serviceName}: 数据库查询失败 [OperationId: {operationId}, 耗时: {stopwatch.ElapsedMilliseconds}ms]", ex);
throw new DatabaseException("数据库操作失败", ex);
}
catch (Exception ex)
{
stopwatch.Stop();
LogFactory.Error($"{_serviceName}: 数据库操作异常 [OperationId: {operationId}, 耗时: {stopwatch.ElapsedMilliseconds}ms]", ex);
throw;
}
}
public async Task<int> ExecuteAsync(string sql, object parameters = null)
{
var operationId = Guid.NewGuid().ToString()[..8];
LogFactory.Debug($"{_serviceName}: 开始数据库执行 [OperationId: {operationId}]");
LogFactory.Debug($"{_serviceName}: SQL: {sql}");
var stopwatch = Stopwatch.StartNew();
try
{
using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync();
var affectedRows = await connection.ExecuteAsync(sql, parameters);
stopwatch.Stop();
LogFactory.Info($"{_serviceName}: 数据库执行成功 [OperationId: {operationId}, 耗时: {stopwatch.ElapsedMilliseconds}ms, 影响行数: {affectedRows}]");
return affectedRows;
}
catch (SqlException ex)
{
stopwatch.Stop();
LogFactory.Error($"{_serviceName}: 数据库执行失败 [OperationId: {operationId}, 耗时: {stopwatch.ElapsedMilliseconds}ms]", ex);
throw new DatabaseException("数据库操作失败", ex);
}
}
}
4. 自定义日志格式器
public static class LogFormat
{
public static string FormatBusinessLog(string module, string operation, object data = null)
{
var logEntry = new
{
Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
Module = module,
Operation = operation,
Data = data,
ThreadId = Thread.CurrentThread.ManagedThreadId
};
return JsonConvert.SerializeObject(logEntry, Formatting.Indented);
}
public static string FormatPerformanceLog(string operation, long elapsedMilliseconds, object additionalInfo = null)
{
var logEntry = new
{
Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
Operation = operation,
ElapsedMilliseconds = elapsedMilliseconds,
AdditionalInfo = additionalInfo,
Level = elapsedMilliseconds > 1000 ? "WARN" : "INFO"
};
return JsonConvert.SerializeObject(logEntry);
}
public static string FormatErrorLog(string context, Exception exception, object requestData = null)
{
var errorEntry = new
{
Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
Context = context,
ExceptionType = exception.GetType().Name,
ExceptionMessage = exception.Message,
StackTrace = exception.StackTrace,
InnerException = exception.InnerException?.Message,
RequestData = requestData
};
return JsonConvert.SerializeObject(errorEntry, Formatting.Indented);
}
}
// 使用自定义格式器
public class BusinessService
{
public void ProcessOrder(Order order)
{
// 业务日志
var businessLog = LogFormat.FormatBusinessLog("OrderService", "ProcessOrder", new { OrderId = order.Id, Amount = order.Amount });
LogFactory.Info(businessLog);
var stopwatch = Stopwatch.StartNew();
try
{
// 业务处理逻辑
Thread.Sleep(100); // 模拟处理时间
stopwatch.Stop();
// 性能日志
var performanceLog = LogFormat.FormatPerformanceLog("ProcessOrder", stopwatch.ElapsedMilliseconds, new { OrderId = order.Id });
if (stopwatch.ElapsedMilliseconds > 1000)
{
LogFactory.Warn(performanceLog);
}
else
{
LogFactory.Info(performanceLog);
}
}
catch (Exception ex)
{
stopwatch.Stop();
// 错误日志
var errorLog = LogFormat.FormatErrorLog("ProcessOrder", ex, new { OrderId = order.Id });
LogFactory.Error(errorLog);
throw;
}
}
}
5. 全局异常处理器
public class GlobalExceptionHandler
{
private readonly RequestDelegate _next;
private readonly string _handlerName = nameof(GlobalExceptionHandler);
public GlobalExceptionHandler(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var request = context.Request;
var requestId = Guid.NewGuid().ToString();
// 记录详细的错误日志
var errorInfo = new
{
RequestId = requestId,
RequestPath = request.Path,
RequestMethod = request.Method,
QueryString = request.QueryString.ToString(),
UserAgent = request.Headers["User-Agent"].ToString(),
ClientIP = GetClientIpAddress(context),
User = context.User.Identity?.Name ?? "Anonymous"
};
var errorLog = LogFormat.FormatErrorLog("GlobalExceptionHandler", exception, errorInfo);
LogFactory.Error(errorLog);
// 返回统一的错误响应
context.Response.ContentType = "application/json";
context.Response.StatusCode = exception switch
{
BusinessException => StatusCodes.Status400BadRequest,
UnauthorizedAccessException => StatusCodes.Status401Unauthorized,
_ => StatusCodes.Status500InternalServerError
};
var response = new
{
Success = false,
RequestId = requestId,
Message = exception is BusinessException ? exception.Message : "系统内部错误",
Timestamp = DateTime.Now
};
await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
}
private string GetClientIpAddress(HttpContext context)
{
return context.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
}
}
// 在 Startup 中注册
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 注册全局异常处理中间件
app.UseMiddleware<GlobalExceptionHandler>();
// 其他中间件...
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
6. 日志监控和诊断
public class LogMonitorService
{
private readonly string _logDirectory;
private readonly Timer _monitorTimer;
public LogMonitorService()
{
_logDirectory = Path.Combine(Directory.GetCurrentDirectory(), "log");
_monitorTimer = new Timer(MonitorLogs, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
}
private void MonitorLogs(object state)
{
try
{
var today = DateTime.Today;
var todayLogDir = Path.Combine(_logDirectory, today.ToString("yyyy-MM-dd"));
if (!Directory.Exists(todayLogDir))
return;
// 检查错误日志文件大小
var errorLogFile = Path.Combine(todayLogDir, "Error.log");
if (File.Exists(errorLogFile))
{
var fileInfo = new FileInfo(errorLogFile);
if (fileInfo.Length > 10 * 1024 * 1024) // 10MB
{
LogFactory.Warn($"错误日志文件过大: {fileInfo.Length / 1024 / 1024}MB");
// 可以发送告警通知
}
}
// 统计今日错误数量
var errorCount = CountLogEntries(todayLogDir, "ERROR");
if (errorCount > 100)
{
LogFactory.Warn($"今日错误数量异常: {errorCount}");
// 发送告警通知
}
}
catch (Exception ex)
{
LogFactory.Error("日志监控异常", ex);
}
}
private int CountLogEntries(string logDirectory, string level)
{
var count = 0;
var logFile = Path.Combine(logDirectory, $"{level}.log");
if (File.Exists(logFile))
{
var lines = File.ReadAllLines(logFile);
count = lines.Length;
}
return count;
}
public void Dispose()
{
_monitorTimer?.Dispose();
}
}
7. 配置优化示例
更详细的 log4net.config 配置
运行
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<root>
<level value="ALL" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="RollingFileAppender" />
<appender-ref ref="ErrorFileAppender" />
</root>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="logs/" />
<datePattern value="yyyy-MM-dd/'app.log'" />
<appendToFile value="true" />
<rollingStyle value="Composite" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="10MB" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="ErrorFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="logs/" />
<datePattern value="yyyy-MM-dd/'error.log'" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline%exception" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="ERROR" />
<levelMax value="FATAL" />
</filter>
</appender>
</log4net>
🎯 最佳实践
1. 日志级别使用指南
- DEBUG: 详细的调试信息,生产环境通常关闭
- INFO: 重要的业务流程信息
- WARN: 潜在的问题,但不影响系统运行
- ERROR: 错误信息,需要关注和处理
2. 日志内容规范
- 包含足够的上下文信息
- 避免记录敏感信息(密码、密钥等)
- 使用结构化日志格式
- 包含请求ID用于追踪
3. 性能考虑
- 避免在循环中记录大量日志
- 使用条件日志记录
- 合理配置日志级别
这个日志组件为应用程序提供了完整、灵活的日志记录解决方案,帮助开发者和运维人员更好地监控和诊断系统运行状态。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- log4net (>= 3.1.0)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on RuoVea.ExLog:
| Package | Downloads |
|---|---|
|
RuoVea.ExFilter
注入 进行全局的异常日志收集、执行操作日志、参数验证,支持简体中文、繁体中文、粤语、日语、法语、英语.services.ExceptionSetup();// 注入 全局错误日志处 services.ExceptionSetup(ExceptionLog actionOptions);// 注入 全局错误日志处 services.ExceptionSetup(builder.Configuration.GetSection("AopOption:ExceptionLog"));// 注入 全局错误日志处 services.RequestActionSetup();// 注入 请求日志拦截 [执行操作日志、参数验证 ] services.RequestActionSetup(RequestLog actionOptions);// 注入 请求日志拦截 [执行操作日志、参数验证 ] services.RequestActionSetup(builder.Configuration.GetSection("AopOption:RequestLog"));// 注入 请求日志拦截 [执行操作日志、参数验证 ] services.ResourceSetup();//对资源型信息进行过滤 services.ResultSetup();//对结果进行统一 services.ApISafeSetup(AppSign actionOptions);//接口安全校验 services.ApISafeSetup(builder.Configuration.GetSection("AopOption:AppSign"));//接口安全校验 services.ApISignSetup(AppSign actionOptions);//签名验证 ( appKey + signKey + timeStamp + data ); services.ApISignSetup(builder.Configuration.GetSection("AopOption:AppSign"));//签名验证 ( appKey + signKey + timeStamp + data ); services.AddValidateSetup();//模型校验 services.AddUiFilesZipSetup();//将前端UI压缩文件进行解压 不进行接口安全校验 -> NonAplSafeAttribute 不签名验证 -> NonAplSignAttribute 不进行全局的异常日志收集 -> NonExceptionAttribute 不对资源型信息进行过滤 -> NonResourceAttribute 不对结果进行统一 -> NonRestfulResultAttribute |
|
|
RuoVea.ExGlobal
web 注入 全局错误日志、操作日志记录 |
|
|
RuoVea.OmiApi.Upload
文件上传模块 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0 | 182 | 1/12/2026 |
| 9.0.0 | 691 | 7/25/2025 |
| 8.0.0.1 | 7,078 | 7/23/2024 |
| 8.0.0 | 327 | 11/24/2023 |
| 7.0.0 | 8,428 | 7/23/2024 |
| 6.0.1.1 | 16,236 | 7/23/2024 |
| 6.0.1 | 3,667 | 6/8/2022 |
| 6.0.0 | 2,416 | 2/9/2022 |
| 5.0.6 | 954 | 6/8/2022 |
| 5.0.5 | 2,186 | 11/26/2021 |
| 5.0.4 | 2,359 | 11/26/2021 |
| 5.0.3 | 2,056 | 11/26/2021 |
| 5.0.2 | 4,858 | 11/24/2021 |
| 5.0.1 | 1,648 | 9/30/2021 |
| 5.0.0 | 760 | 9/27/2021 |
| 2.1.1.2 | 188 | 7/23/2024 |
| 2.1.1.1 | 225 | 11/24/2023 |
| 2.1.1 | 660 | 6/9/2022 |
| 2.1.0 | 632 | 6/8/2022 |
| 2.0.0 | 222 | 9/22/2024 |