A:添加日志类
This commit is contained in:
1 parent
eaaedd48cf
commit
efcdf01436
14 files changed
+666
-4
No files matched your search
@@ -9,11 +9,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-preview.4.25258.110" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-preview.4.25258.110" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-preview.4.25258.110" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-preview.4.25258.110" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.3.1-dev-02373" />
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.1-dev-02317" />
|
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.1-dev-02317" />
|
||||||
</ItemGroup>
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.1-dev-00953" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Logger\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace Common.Logger;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志配置类
|
||||||
|
/// </summary>
|
||||||
|
public class LoggerConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用控制台输出
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableConsole { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用文件输出
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableFile { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志文件路径
|
||||||
|
/// </summary>
|
||||||
|
public string LogPath { get; set; } = "logs";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单个日志文件最大大小(MB)
|
||||||
|
/// </summary>
|
||||||
|
public int MaxFileSizeMB { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志保留天数
|
||||||
|
/// </summary>
|
||||||
|
public int RetentionDays { get; set; } = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 压缩天数阈值(几天前的日志开始压缩)
|
||||||
|
/// </summary>
|
||||||
|
public int CompressDaysThreshold { get; set; } = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最小日志级别
|
||||||
|
/// </summary>
|
||||||
|
public LogEventLevel MinimumLevel { get; set; } = LogEventLevel.Information;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志格式模板
|
||||||
|
/// </summary>
|
||||||
|
public string OutputTemplate { get; set; } = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用JSON格式
|
||||||
|
/// </summary>
|
||||||
|
public bool UseJsonFormat { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 压缩检查间隔(小时)
|
||||||
|
/// </summary>
|
||||||
|
public int CompressCheckIntervalHours { get; set; } = 24;
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# UniversalLogger - 通用日志记录库
|
||||||
|
|
||||||
|
一个基于Serilog构建的功能完整的C#日志记录库,支持多种输出方式、自动维护、配置灵活等特性。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
✅ **多种输出方式**: 支持控制台、文件输出,可单独或同时使用
|
||||||
|
✅ **按日期分割**: 自动按日期创建日志文件
|
||||||
|
✅ **文件大小控制**: 超过指定大小自动创建新文件
|
||||||
|
✅ **自动压缩**: 指定天数后自动压缩旧日志文件
|
||||||
|
✅ **自动清理**: 超过保留期限自动删除旧日志
|
||||||
|
✅ **配置灵活**: 支持代码配置、JSON文件配置
|
||||||
|
✅ **功能完整**: 支持各级别日志、异常堆栈、上下文信息
|
||||||
|
✅ **性能优化**: 基于Serilog高性能日志框架
|
||||||
|
✅ **易于使用**: 提供静态类和实例两种使用方式
|
||||||
|
|
||||||
|
## 安装依赖
|
||||||
|
|
||||||
|
在你的项目中添加以下NuGet包:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Formatting.Json" Version="1.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 使用默认配置
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using UniversalLogger;
|
||||||
|
|
||||||
|
// 使用默认配置创建日志记录器
|
||||||
|
using var logger = new UniversalLogger(new LoggerConfig());
|
||||||
|
|
||||||
|
logger.LogInformation("Hello, World!");
|
||||||
|
logger.LogWarning("这是一个警告");
|
||||||
|
logger.LogError("这是一个错误");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用静态日志记录器
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 初始化静态日志记录器
|
||||||
|
Log.Initialize();
|
||||||
|
|
||||||
|
// 直接使用
|
||||||
|
Log.Information("应用程序启动");
|
||||||
|
Log.Warning("警告信息");
|
||||||
|
Log.Error(exception, "发生错误: {Message}", exception.Message);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 从配置文件加载
|
||||||
|
|
||||||
|
创建 `appsettings.json` 文件:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"EnableConsole": true,
|
||||||
|
"EnableFile": true,
|
||||||
|
"LogPath": "logs",
|
||||||
|
"MaxFileSizeMB": 1,
|
||||||
|
"RetentionDays": 30,
|
||||||
|
"CompressDaysThreshold": 3,
|
||||||
|
"MinimumLevel": "Information",
|
||||||
|
"OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
|
||||||
|
"UseJsonFormat": false,
|
||||||
|
"CompressCheckIntervalHours": 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
使用配置文件:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 从配置文件初始化
|
||||||
|
Log.InitializeFromJsonFile("appsettings.json");
|
||||||
|
|
||||||
|
Log.Information("使用配置文件的日志记录器");
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置选项
|
||||||
|
|
||||||
|
| 属性 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `EnableConsole` | bool | true | 是否启用控制台输出 |
|
||||||
|
| `EnableFile` | bool | true | 是否启用文件输出 |
|
||||||
|
| `LogPath` | string | "logs" | 日志文件存储路径 |
|
||||||
|
| `MaxFileSizeMB` | int | 1 | 单个日志文件最大大小(MB) |
|
||||||
|
| `RetentionDays` | int | 30 | 日志保留天
|
||||||
@@ -0,0 +1,403 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Events;
|
||||||
|
using Serilog.Formatting.Json;
|
||||||
|
using System.Timers;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Timers;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Core;
|
||||||
|
using Serilog.Events;
|
||||||
|
using Serilog.Formatting.Json;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Common.Logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通用日志记录器
|
||||||
|
/// </summary>
|
||||||
|
public class UniversalLogger : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Serilog.Core.Logger _logger;
|
||||||
|
private readonly LoggerConfig _config;
|
||||||
|
private readonly System.Timers.Timer _maintenanceTimer;
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
|
public UniversalLogger(LoggerConfig config = null)
|
||||||
|
{
|
||||||
|
_config = config ?? new LoggerConfig();
|
||||||
|
_logger = CreateLogger();
|
||||||
|
|
||||||
|
// 启动维护定时器
|
||||||
|
_maintenanceTimer =
|
||||||
|
new System.Timers.Timer(TimeSpan.FromHours(_config.CompressCheckIntervalHours).TotalMilliseconds);
|
||||||
|
_maintenanceTimer.Elapsed += async (s, e) => await PerformMaintenanceAsync();
|
||||||
|
_maintenanceTimer.Start();
|
||||||
|
|
||||||
|
// 启动时执行一次维护
|
||||||
|
Task.Run(async () => await PerformMaintenanceAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从配置文件创建日志记录器
|
||||||
|
/// </summary>
|
||||||
|
public static UniversalLogger FromConfiguration(IConfiguration configuration, string sectionName = "Logging")
|
||||||
|
{
|
||||||
|
var config = new LoggerConfig();
|
||||||
|
configuration.GetSection(sectionName).Bind(config);
|
||||||
|
return new UniversalLogger(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从JSON配置文件创建日志记录器
|
||||||
|
/// </summary>
|
||||||
|
public static UniversalLogger FromJsonFile(string configPath)
|
||||||
|
{
|
||||||
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddJsonFile(configPath, optional: false)
|
||||||
|
.Build();
|
||||||
|
return FromConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Serilog.Core.Logger CreateLogger()
|
||||||
|
{
|
||||||
|
var loggerConfig = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Is(_config.MinimumLevel)
|
||||||
|
.Enrich.WithProperty("MachineName", Environment.MachineName)
|
||||||
|
.Enrich.WithProperty("ProcessId", Environment.ProcessId)
|
||||||
|
.Enrich.WithProperty("ThreadId", Environment.CurrentManagedThreadId);
|
||||||
|
|
||||||
|
// 配置控制台输出
|
||||||
|
if (_config.EnableConsole)
|
||||||
|
{
|
||||||
|
loggerConfig = loggerConfig.WriteTo.Console(
|
||||||
|
outputTemplate: _config.OutputTemplate,
|
||||||
|
restrictedToMinimumLevel: _config.MinimumLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置文件输出
|
||||||
|
if (_config.EnableFile)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_config.LogPath);
|
||||||
|
|
||||||
|
string logFileName = Path.Combine(_config.LogPath, "log-.txt");
|
||||||
|
|
||||||
|
if (_config.UseJsonFormat)
|
||||||
|
{
|
||||||
|
loggerConfig = loggerConfig.WriteTo.File(
|
||||||
|
formatter: new JsonFormatter(),
|
||||||
|
path: logFileName,
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
rollOnFileSizeLimit: true,
|
||||||
|
fileSizeLimitBytes: _config.MaxFileSizeMB * 1024 * 1024,
|
||||||
|
retainedFileCountLimit: null,
|
||||||
|
restrictedToMinimumLevel: _config.MinimumLevel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loggerConfig = loggerConfig.WriteTo.File(
|
||||||
|
path: logFileName,
|
||||||
|
outputTemplate: _config.OutputTemplate,
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
rollOnFileSizeLimit: true,
|
||||||
|
fileSizeLimitBytes: _config.MaxFileSizeMB * 1024 * 1024,
|
||||||
|
retainedFileCountLimit: null,
|
||||||
|
restrictedToMinimumLevel: _config.MinimumLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loggerConfig.CreateLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 日志记录方法
|
||||||
|
|
||||||
|
public void LogVerbose(string message, params object[] args)
|
||||||
|
{
|
||||||
|
_logger.Verbose(message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogDebug(string message, params object[] args)
|
||||||
|
{
|
||||||
|
_logger.Debug(message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogInformation(string message, params object[] args)
|
||||||
|
{
|
||||||
|
_logger.Information(message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogWarning(string message, params object[] args)
|
||||||
|
{
|
||||||
|
_logger.Warning(message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogError(string message, params object[] args)
|
||||||
|
{
|
||||||
|
_logger.Error(message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogError(Exception exception, string message, params object[] args)
|
||||||
|
{
|
||||||
|
_logger.Error(exception, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogFatal(string message, params object[] args)
|
||||||
|
{
|
||||||
|
_logger.Fatal(message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogFatal(Exception exception, string message, params object[] args)
|
||||||
|
{
|
||||||
|
_logger.Fatal(exception, message, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录带上下文信息的日志
|
||||||
|
/// </summary>
|
||||||
|
public void LogWithContext(LogEventLevel level, string message, Dictionary<string, object> context,
|
||||||
|
Exception exception = null)
|
||||||
|
{
|
||||||
|
var logEvent = new LogEventLevel[] { level };
|
||||||
|
ILogger logger = _logger;
|
||||||
|
|
||||||
|
foreach (var kvp in context ?? new Dictionary<string, object>())
|
||||||
|
{
|
||||||
|
var i = logger.ForContext(kvp.Key, kvp.Value);
|
||||||
|
logger = logger.ForContext(kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
logger.Write(level, exception, message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Write(level, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 维护功能
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行日志维护任务
|
||||||
|
/// </summary>
|
||||||
|
private async Task PerformMaintenanceAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_config.EnableFile)
|
||||||
|
{
|
||||||
|
await CompressOldLogsAsync();
|
||||||
|
await DeleteOldLogsAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "执行日志维护任务时发生错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 压缩旧日志文件
|
||||||
|
/// </summary>
|
||||||
|
private async Task CompressOldLogsAsync()
|
||||||
|
{
|
||||||
|
var logDirectory = new DirectoryInfo(_config.LogPath);
|
||||||
|
if (!logDirectory.Exists) return;
|
||||||
|
|
||||||
|
var cutoffDate = DateTime.Now.Date.AddDays(-_config.CompressDaysThreshold);
|
||||||
|
var logFiles = logDirectory.GetFiles("log*.txt")
|
||||||
|
.Where(f => f.CreationTime.Date < cutoffDate && !f.Name.EndsWith(".gz"))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var file in logFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var compressedFileName = file.FullName + ".gz";
|
||||||
|
if (File.Exists(compressedFileName)) continue;
|
||||||
|
|
||||||
|
await CompressFileAsync(file.FullName, compressedFileName);
|
||||||
|
File.Delete(file.FullName);
|
||||||
|
|
||||||
|
_logger.Information("已压缩日志文件: {FileName}", file.Name);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "压缩日志文件失败: {FileName}", file.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除过期的日志文件
|
||||||
|
/// </summary>
|
||||||
|
private async Task DeleteOldLogsAsync()
|
||||||
|
{
|
||||||
|
var logDirectory = new DirectoryInfo(_config.LogPath);
|
||||||
|
if (!logDirectory.Exists) return;
|
||||||
|
|
||||||
|
var cutoffDate = DateTime.Now.Date.AddDays(-_config.RetentionDays);
|
||||||
|
var oldFiles = logDirectory.GetFiles("log*.*")
|
||||||
|
.Where(f => f.CreationTime.Date < cutoffDate)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var file in oldFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(file.FullName);
|
||||||
|
_logger.Information("已删除过期日志文件: {FileName}", file.Name);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "删除过期日志文件失败: {FileName}", file.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 压缩文件
|
||||||
|
/// </summary>
|
||||||
|
private async Task CompressFileAsync(string sourceFile, string compressedFile)
|
||||||
|
{
|
||||||
|
using (var originalFileStream = File.OpenRead(sourceFile))
|
||||||
|
using (var compressedFileStream = File.Create(compressedFile))
|
||||||
|
using (var compressionStream = new GZipStream(compressedFileStream, CompressionMode.Compress))
|
||||||
|
{
|
||||||
|
await originalFileStream.CopyToAsync(compressionStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 手动执行维护任务
|
||||||
|
/// </summary>
|
||||||
|
public async Task RunMaintenanceAsync()
|
||||||
|
{
|
||||||
|
await PerformMaintenanceAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_maintenanceTimer?.Stop();
|
||||||
|
_maintenanceTimer?.Dispose();
|
||||||
|
_logger?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~UniversalLogger()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 静态日志记录器(单例模式)
|
||||||
|
/// </summary>
|
||||||
|
public static class Log
|
||||||
|
{
|
||||||
|
private static UniversalLogger _instance;
|
||||||
|
private static readonly object _lock = new object();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化日志记录器
|
||||||
|
/// </summary>
|
||||||
|
public static void Initialize(LoggerConfig config = null)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_instance?.Dispose();
|
||||||
|
_instance = new UniversalLogger(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从配置文件初始化
|
||||||
|
/// </summary>
|
||||||
|
public static void InitializeFromConfig(IConfiguration configuration, string sectionName = "Logging")
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_instance?.Dispose();
|
||||||
|
_instance = UniversalLogger.FromConfiguration(configuration, sectionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从JSON文件初始化
|
||||||
|
/// </summary>
|
||||||
|
public static void InitializeFromJsonFile(string configPath)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_instance?.Dispose();
|
||||||
|
_instance = UniversalLogger.FromJsonFile(configPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UniversalLogger Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
_instance = new UniversalLogger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Verbose(string message, params object[] args) => Instance.LogVerbose(message, args);
|
||||||
|
public static void Debug(string message, params object[] args) => Instance.LogDebug(message, args);
|
||||||
|
public static void Information(string message, params object[] args) => Instance.LogInformation(message, args);
|
||||||
|
public static void Warning(string message, params object[] args) => Instance.LogWarning(message, args);
|
||||||
|
public static void Error(string message, params object[] args) => Instance.LogError(message, args);
|
||||||
|
|
||||||
|
public static void Error(Exception exception, string message, params object[] args) =>
|
||||||
|
Instance.LogError(exception, message, args);
|
||||||
|
|
||||||
|
public static void Fatal(string message, params object[] args) => Instance.LogFatal(message, args);
|
||||||
|
|
||||||
|
public static void Fatal(Exception exception, string message, params object[] args) =>
|
||||||
|
Instance.LogFatal(exception, message, args);
|
||||||
|
|
||||||
|
public static void WithContext(LogEventLevel level, string message, Dictionary<string, object> context,
|
||||||
|
Exception exception = null)
|
||||||
|
=> Instance.LogWithContext(level, message, context, exception);
|
||||||
|
|
||||||
|
public static async Task RunMaintenanceAsync() => await Instance.RunMaintenanceAsync();
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinDivertNet", "WinDivertNe
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UI.Shared.Wpf", "UI.Shared.Wpf\UI.Shared.Wpf.csproj", "{8512B4F6-65F3-48EE-845F-09A1692BB901}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -42,5 +44,9 @@ Global
|
|||||||
{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}.Release|Any CPU.Build.0 = Release|Any CPU
|
{F16BB136-23AD-43F6-8D95-4C0D88FD8FBA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8512B4F6-65F3-48EE-845F-09A1692BB901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8512B4F6-65F3-48EE-845F-09A1692BB901}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8512B4F6-65F3-48EE-845F-09A1692BB901}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8512B4F6-65F3-48EE-845F-09A1692BB901}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFile_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003F71_003F40854420_003FFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFile_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F211e6f3b24fa438a92f1815153647ce2c8f908_003F71_003F40854420_003FFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIWebSocketConnection_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa7d96aff8e344c1d9aa8042f229d493cae00_003Fc1_003Fb154f2eb_003FIWebSocketConnection_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIWebSocketConnection_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa7d96aff8e344c1d9aa8042f229d493cae00_003Fc1_003Fb154f2eb_003FIWebSocketConnection_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKernelTraceEventParser_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F25b12134b1124ffbab73641db2e54e5b310830_003F24_003Fd19d02f2_003FKernelTraceEventParser_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKernelTraceEventParser_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F25b12134b1124ffbab73641db2e54e5b310830_003F24_003Fd19d02f2_003FKernelTraceEventParser_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALogger_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F59d0b425a1244469855c0039f03f864c27e00_003F43_003F8a29b095_003FLogger_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetworkInterface_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9c72a65889eb40ae9d8e0f130d1572e0268a0_003F34_003F49f5e6ff_003FNetworkInterface_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetworkInterface_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F9c72a65889eb40ae9d8e0f130d1572e0268a0_003F34_003F49f5e6ff_003FNetworkInterface_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AProcess_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4a26a752ec6249c4a090905d31c0cdfa528a0_003F8f_003F7037e590_003FProcess_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AProcess_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4a26a752ec6249c4a090905d31c0cdfa528a0_003F8f_003F7037e590_003FProcess_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReadOnlyCollectionBase_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4c1fd115dbe5414db1b4da1de3e96c5819910_003F04_003Fcae9fe22_003FReadOnlyCollectionBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReadOnlyCollectionBase_002Ecs_002Fl_003AC_0021_003FUsers_003Fcharlatans_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4c1fd115dbe5414db1b4da1de3e96c5819910_003F04_003Fcae9fe22_003FReadOnlyCollectionBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
|||||||
@@ -2,5 +2,17 @@
|
|||||||
"AppSettings": {
|
"AppSettings": {
|
||||||
"AppName": "MyApp",
|
"AppName": "MyApp",
|
||||||
"MaxConnection": 10
|
"MaxConnection": 10
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"EnableConsole": true,
|
||||||
|
"EnableFile": true,
|
||||||
|
"LogPath": "logs",
|
||||||
|
"MaxFileSizeMB": 1,
|
||||||
|
"RetentionDays": 30,
|
||||||
|
"CompressDaysThreshold": 3,
|
||||||
|
"MinimumLevel": "Information",
|
||||||
|
"OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
|
||||||
|
"UseJsonFormat": false,
|
||||||
|
"CompressCheckIntervalHours": 24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<Application x:Class="UI.Shared.Wpf.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:UI.Shared.Wpf"
|
||||||
|
StartupUri="MainWindow.xaml">
|
||||||
|
<Application.Resources>
|
||||||
|
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Configuration;
|
||||||
|
using System.Data;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace UI.Shared.Wpf;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<Window x:Class="UI.Shared.Wpf.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:UI.Shared.Wpf"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="MainWindow" Height="450" Width="800">
|
||||||
|
<Grid>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace UI.Shared.Wpf;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
| 模块 | 说明 |
|
||||||
|
| ---------- | ------------------------------------------------------------------------------------- |
|
||||||
|
| 🔥 全局异常处理 | `App.xaml.cs` 注册的 `DispatcherUnhandledException`、`AppDomain.UnhandledException` 等逻辑封装 |
|
||||||
|
| 🔧 异常日志捕捉器 | 异常信息收集、弹窗提示、自动日志写入 |
|
||||||
|
| 🧠 通用行为类 | 拖拽移动窗口、全屏控制、窗口热键等 |
|
||||||
|
| 🎨 通用控件 | Loading 动画、消息弹窗、状态提示组件 |
|
||||||
|
| 💡 通用转换器 | `BoolToVisibilityConverter`, `EnumToDescriptionConverter` |
|
||||||
|
| 🌈 样式资源 | `Generic.xaml`, Theme, 通用 `DataTemplate` |
|
||||||
|
| 🛠 辅助类 | `UIHelper`, `WindowHelper`, `DialogManager` |
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in new issue
Block a user