RuoVea.ExLog 10.0.0

dotnet add package RuoVea.ExLog --version 10.0.0
                    
NuGet\Install-Package RuoVea.ExLog -Version 10.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RuoVea.ExLog" Version="10.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RuoVea.ExLog" Version="10.0.0" />
                    
Directory.Packages.props
<PackageReference Include="RuoVea.ExLog" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RuoVea.ExLog --version 10.0.0
                    
#r "nuget: RuoVea.ExLog, 10.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RuoVea.ExLog@10.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RuoVea.ExLog&version=10.0.0
                    
Install as a Cake Addin
#tool nuget:?package=RuoVea.ExLog&version=10.0.0
                    
Install as a Cake Tool

📋 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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