上个月换工作,新项目又要重新搭建基础框架,把日志实现部分单独记录下来方便以后参考。

自定义日志类

代码大部分使用ChatGPT生成,人工进行了测试和优化,主要特点:

  • 线程安全,日志异步写入文件不影响业务逻辑
  • 支持过期文件自动清理,也可自定义清理逻辑
  • 缓存队列有内存上限防呆,防止异常情况下内存爆满
  • 提供默认的静态日志记录器,也支持自定义多个日志记录器
  • 通过委托方法支持日志文件名、日志记录格式的自定义,更加自由

使用方法:

//正常写入日志
LogManager.Info("This is an info message: {0}", "TestInfo");
LogManager.Debug("This is a debug message: {0}", "TestDebug");
LogManager.Warn("This is an warning message: {0}", "TestInfo");
LogManager.Error("This is a error message: {0}", "TestDebug"); //自定义写入日志,一般情况下使用枚举定义日志记录器的名称
var logger = LogManager.GetLogger("测试日志");
logger.Info("This is an info message: {0}", "TestInfo");
logger.Debug("This is a debug message: {0}", "TestDebug");
logger.Warn("This is an warning message: {0}", "TestInfo");
logger.Error("This is a error message: {0}", "TestDebug"); // 在程序退出前关闭所有日志记录器,默认超时时间是3秒
LogManager.Close(5); //调试时偶尔使用
if (LogManager.LastException != null)
{
Console.WriteLine("日志异常:" + LogManager.LastException);
}

配置方法(一般情况下使用默认配置即可):

//自定义日志保存路径,默认保存到程序启动目录下的Log文件夹
LogManager.CustomLogPath = () => AppDomain.CurrentDomain.BaseDirectory + "\\CustomLogs";
//自定义日志文件名称,默认文件名为 DateTime.Now.ToString("yyyy-MM-dd") + ".log"
LogManager.CustomLogFileName = () => "MyLog_" + DateTime.Now.ToString("yyyyMMdd") + ".log";
//日志保存天数,默认30天
LogManager.SaveDays = 10;
//日志记录的格式,默认为 $"[{Time:yyyy-MM-dd HH:mm:ss ffff}] [{Level.ToString().ToUpper()}] [{ThreadId}] {Message}"
LogManager.LogFormatter = (item) =>
{
//可以在这里做日志等级筛选,如果返回string.Empty这该条日志不会记录到文件
return $"{item.Time:yyyy/MM/dd HH:mm:ss.fff} | {item.Level} | T{item.ThreadId:0000} | {item.Message}";
};
//日志回调,可用于界面实时显示日志或日志保存其它存储介质
LogManager.OnWriteLog = (item) => Console.WriteLine("An event was logged: " + item.ToString());

日志类完整代码:

using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks; /// <summary>
/// 日志组件,内部维护了一个静态日志记录类
/// </summary>
public static class LogManager
{
/// <summary>
/// 日志等级
/// </summary>
public enum LogLevel
{ Debug, Info, Warn, Error, Fatal } /// <summary>
/// 日志数据
/// </summary>
public class LogItem
{
public LogItem(LogLevel level, string message)
{
Level = level;
Message = message;
Time = DateTime.Now;
ThreadId = Thread.CurrentThread.ManagedThreadId;
} public DateTime Time { get; private set; }
public LogLevel Level { get; private set; }
public int ThreadId { get; private set; }
public string Message { get; private set; } public override string ToString()
{
return $"[{Time:yyyy-MM-dd HH:mm:ss ffff}] [{Level.ToString().ToUpper()}] [{ThreadId}] {Message}";
}
} /// <summary>
/// 线程安全异步日志基础类,默认缓存10000条日志,超出时日志会阻塞
/// </summary>
public class Logger
{
/// <summary>
/// 日志文件存储路径的委托
/// </summary>
public Func<string> CustomLogPath { get; set; } /// <summary>
/// 日志文件名的委托,文件扩展名必须是log,否则会影响日志文件的自动清理(可以自定义清理的方法)
/// </summary>
public Func<string> CustomLogFileName { get; set; } /// <summary>
/// 日志文件保存时间
/// </summary>
public int SaveDays { get; set; } = 30; /// <summary>
/// 日志格式化委托实例
/// </summary>
public Func<LogItem, string> LogFormatter { get; set; } /// <summary>
/// 写日志事件
/// </summary>
public Action<LogItem> OnWriteLog { get; set; } /// <summary>
/// 日志清理委托实例,传入日志保存时间
/// </summary>
public Action<int> LogCleanup { get; set; } /// <summary>
/// 最后一次异常(仅调试时用,不用于正常业务流程)
/// </summary>
public Exception LastException { get; set; } // 线程安全的日志队列
private BlockingCollection<string> logQueue = new BlockingCollection<string>(10000); // 标识是否允许写入新日志
private bool allowNewLogs = true; public Logger()
{
Task.Factory.StartNew(WriteToFile, TaskCreationOptions.LongRunning);
} // 添加日志至队列方法
public void EnqueueLog(LogLevel level, string message)
{
if (!allowNewLogs) return;
LogItem item = new LogItem(level, message); string logMessage;
if (LogFormatter != null)
{
logMessage = LogFormatter(item);
}
else
{
logMessage = item.ToString();
}
if (!string.IsNullOrWhiteSpace(logMessage))
{
logQueue.Add(logMessage);
}
OnWriteLog?.Invoke(item);
} // 循环写入写日志到文件
private void WriteToFile()
{
string logPath = CustomLogPath?.Invoke() ?? AppDomain.CurrentDomain.BaseDirectory + "\\Log";
DirectoryInfo logDir = Directory.CreateDirectory(logPath); while (true)
{
try
{
if (!allowNewLogs && logQueue.Count == 0) break; string logMessage;
if (logQueue.TryTake(out logMessage, TimeSpan.FromSeconds(1)))
{
string fileName = CustomLogFileName?.Invoke() ?? DateTime.Now.ToString("yyyy-MM-dd") + ".log";
if (!File.Exists(fileName))
{
// 清理旧日志
if (LogCleanup != null)
{
LogCleanup(SaveDays);
}
else
{
CleanUpOldLogs(logDir, SaveDays);
}
}
File.AppendAllText(Path.Combine(logPath, fileName), logMessage + Environment.NewLine);
}
}
catch (Exception ex)
{
LastException = ex;
Console.WriteLine("Logger Exception - WriteToFile : " + ex.Message);
}
} } /// <summary>
/// 关闭日志器方法,指定超时时间(秒)
/// </summary>
/// <param name="waitTimeInSeconds">等待时间</param>
public void Close(int waitTimeInSeconds = 3)
{
allowNewLogs = false;
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(waitTimeInSeconds));
try
{
CancellationToken token = cts.Token;
// 标识队列已完成添加
logQueue.CompleteAdding(); while (!token.IsCancellationRequested)
{
if (logQueue.Count == 0) break; // 提前退出 // 短暂休眠以降低 CPU 占用
Task.Delay(100, token).Wait();
}
}
catch (OperationCanceledException)
{
// 等待时间到,退出方法,不传播异常
}
catch (Exception ex)
{
Console.WriteLine("An unexpected exception occurred in the Close method: " + ex.Message);
}
} /// <summary>
/// 默认的清理过期日志的方法
/// </summary>
/// <param name="logDir"></param>
/// <param name="saveDays"></param>
public static void CleanUpOldLogs(DirectoryInfo logDir, int saveDays)
{
FileInfo[] logFiles = logDir.GetFiles("*.log");
foreach (FileInfo file in logFiles)
{
if (DateTime.Now - file.CreationTime >= TimeSpan.FromDays(saveDays))
{
file.Delete();
}
}
} /// <summary>
/// 记录Info等级日志
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public void Info(string message, params object[] args)
{
EnqueueLog(LogLevel.Info, string.Format(message, args));
} /// <summary>
/// 记录Debug等级日志
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public void Debug(string message, params object[] args)
{
EnqueueLog(LogLevel.Debug, string.Format(message, args));
} /// <summary>
/// 记录Warning等级日志
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public void Warn(string message, params object[] args)
{
EnqueueLog(LogLevel.Warn, string.Format(message, args));
} /// <summary>
/// 记录Error等级日志
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public void Error(string message, params object[] args)
{
EnqueueLog(LogLevel.Error, string.Format(message, args));
} /// <summary>
/// 记录Fatal等级日志
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public void Fatal(string message, params object[] args)
{
EnqueueLog(LogLevel.Fatal, string.Format(message, args));
}
} private static Logger logger = new Logger();
private static ConcurrentDictionary<string, Logger> logDic = new ConcurrentDictionary<string, Logger>(); /// <summary>
/// 获取自定义的日志记录类
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static Logger GetLogger(string name)
{
return logDic.GetOrAdd(name, key =>
{
var log = new Logger();
log.CustomLogPath = () => AppDomain.CurrentDomain.BaseDirectory + "\\Log\\" + key;
return log;
});
} /// <summary>
/// 日志文件存储路径的委托
/// </summary>
public static Func<string> CustomLogPath
{
get => logger.CustomLogPath;
set => logger.CustomLogPath = value;
} /// <summary>
/// 日志文件名的委托,文件扩展名必须是log,否则会影响日志文件的自动清理(可以自定义清理的方法)
/// </summary>
public static Func<string> CustomLogFileName
{
get => logger.CustomLogFileName;
set => logger.CustomLogFileName = value;
} /// <summary>
/// 日志文件保存时间
/// </summary>
public static int SaveDays
{
get => logger.SaveDays;
set => logger.SaveDays = value;
} /// <summary>
/// 日志格式化委托实例
/// </summary>
public static Func<LogItem, string> LogFormatter
{
get => logger.LogFormatter;
set => logger.LogFormatter = value;
} /// <summary>
/// 写日志事件
/// </summary>
public static Action<LogItem> OnWriteLog
{
get => logger.OnWriteLog;
set => logger.OnWriteLog = value;
} /// <summary>
/// 日志清理委托实例,传入日志保存时间
/// </summary>
public static Action<int> LogCleanup
{
get => logger.LogCleanup;
set => logger.LogCleanup = value;
} /// <summary>
/// 最后一次异常(仅调试时用,不用于正常业务流程)
/// </summary>
public static Exception LastException
{
get => logger.LastException;
set => logger.LastException = value;
} /// <summary>
/// 关闭所有日志记录器,指定超时时间(秒),日志记录器较多时可能耗时较久
/// </summary>
/// <param name="waitTimeInSeconds">等待时间</param>
public static void Close(int waitTimeInSeconds = 3)
{
logger.Close(waitTimeInSeconds);
foreach (var item in logDic.Values)
{
item.Close(waitTimeInSeconds);
}
} /// <summary>
/// 记录Info等级日志
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Info(string message, params object[] args)
{
logger.EnqueueLog(LogLevel.Info, string.Format(message, args));
} /// <summary>
/// 记录Debug等级日志
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Debug(string message, params object[] args)
{
logger.EnqueueLog(LogLevel.Debug, string.Format(message, args));
} /// <summary>
/// 记录Warning等级日志
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Warn(string message, params object[] args)
{
logger.EnqueueLog(LogLevel.Warn, string.Format(message, args));
} /// <summary>
/// 记录Error等级日志
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Error(string message, params object[] args)
{
logger.EnqueueLog(LogLevel.Error, string.Format(message, args));
} /// <summary>
/// 记录Fatal等级日志
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Fatal(string message, params object[] args)
{
logger.EnqueueLog(LogLevel.Fatal, string.Format(message, args));
}
}

NLog版本的日志类

有时候因为公司或团队原因必须使用NLog日志库,这里提供一个简单的NLog版本的日志类,日志规则基本和上面的自定义类一致。

使用方法如下:

//可以指定日志保存天数,默认30天
Logger.Init();
//订阅写日志事件
Logger.GetLogEventTarget().LogReceived += Logger_LogWrite;
//记录日志
Logger.Info("软件启动");

NLog版本的日志类只是对NLog的常用配置做一个封装,如果需要自定义日志格式或存储位置需要自己改封装部分的代码。

完整代码:

using NLog;
using NLog.Config;
using NLog.Layouts;
using NLog.Targets;
using NLog.Targets.Wrappers;
using System; /// <summary>
/// 全局日志类
/// </summary>
public static class Logger
{
#region 私有字段方法 private static ILogger logger; private static void ConfigureNLog(int saveDays = 30)
{
var config = new LoggingConfiguration(); // 创建目标(这里是 FileTarget 用于写入文件)
var fileTarget = new FileTarget("logfile")
{
FileName = "${basedir}/Log/${date:format=yyyy-MM-dd}.log",
Layout = "${longdate} ${uppercase:${level}} ${message} ${exception:format=tostring}",
ArchiveOldFileOnStartup = true,
ArchiveEvery = FileArchivePeriod.Day,
MaxArchiveFiles = saveDays,
}; // 使用 AsyncTargetWrapper 实现异步写日志
var asyncFileTarget = new AsyncTargetWrapper(fileTarget, 1000, AsyncTargetWrapperOverflowAction.Discard);
// 设置规则
var rule = new LoggingRule("*", LogLevel.Debug, asyncFileTarget);
config.LoggingRules.Add(rule); // 创建自定义Target
var logEventTarget = new LogEventTarget
{
Name = "logEvent",
MessageLayout = "${longdate} ${uppercase:${level}} ${message} ${exception:format=tostring}"
};
config.AddTarget(logEventTarget); // 添加规则
var eventRule = new LoggingRule("*", LogLevel.Debug, logEventTarget);
config.LoggingRules.Add(eventRule); // 应用配置
LogManager.Configuration = config;
} #endregion 私有字段方法 #region 公共字段方法 /// <summary>
/// 初始化日志,程序初始化时启动
/// </summary>
/// <param name="saveDays"></param>
public static void Init(int saveDays = 30)
{
ConfigureNLog(saveDays);
logger = LogManager.GetCurrentClassLogger();
} /// <summary>
/// 获取日志回调Target
/// </summary>
/// <returns></returns>
public static LogEventTarget GetLogEventTarget()
{
return (LogEventTarget)LogManager.Configuration.FindTargetByName("logEvent");
} /// <summary>
/// 关闭日志,程序退出时调用
/// </summary>
public static void Close()
{
LogManager.Flush();
LogManager.Shutdown();
} #endregion 公共字段方法 #region 日志接口 /// <summary>
/// 跟踪日志级别:最详细的级别,用于开发,很少用于生产。
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Trace(string message, params object[] args)
{
logger.Trace(message, args);
} /// <summary>
/// 调试日志级别:根据感兴趣的内部事件调试应用程序行为。
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Debug(string message, params object[] args)
{
logger.Debug(message, args);
} /// <summary>
/// 信息日志级别:突出显示进度或应用程序生存期事件的信息。
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Info(string message, params object[] args)
{
logger.Info(message, args);
} /// <summary>
/// 警告日志级别:关于可以恢复的验证问题或临时故障的警告。
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Warn(string message, params object[] args)
{
logger.Warn(message, args);
} /// <summary>
/// 错误日志级别:功能失败或捕捉到System.Exception的错误。
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Error(string message, params object?[] args)
{
logger.Error(message, args);
} /// <summary>
/// 致命日志级别:最关键级别,应用程序即将中止。
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void Fatal(string message, params object[] args)
{
logger.Fatal(message, args);
} #endregion 日志接口
} /// <summary>
/// 日志事件目标
/// </summary>
public class LogEventTarget : Target
{
/// <summary>
/// 日志事件
/// </summary>
public event EventHandler<string> LogReceived;
/// <summary>
/// 日志布局
/// </summary>
[RequiredParameter]
public Layout MessageLayout { get; set; }
protected override void Write(LogEventInfo logEvent)
{
string logMessage = MessageLayout.Render(logEvent);
OnLogReceived(logMessage);
}
private void OnLogReceived(string message)
{
LogReceived?.Invoke(this, message);
}
}

Serilog版本的日志类

Serilog 是用于 .NET 应用程序的诊断日志记录库。它易于设置,具有干净的 API,并且可以在所有最新的 .NET 平台上运行。

github链接:Serilog

安装以下NuGet包:

使用方法如下:

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
//异步写入,滚动间隔:天,默认情况下只保留最近的 31 个文件
.WriteTo.Async(a => a.File("logs/log-.txt", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 60))
.WriteTo.MySink()
.CreateLogger(); Log.Information("Hello, world!"); // 最后,在应用程序退出之前。。。
Log.CloseAndFlush();

自定义日志计算器如下:

public class MySink : ILogEventSink
{
private readonly IFormatProvider _formatProvider; public MySink(IFormatProvider formatProvider)
{
_formatProvider = formatProvider;
} public void Emit(LogEvent logEvent)
{
var message = logEvent.RenderMessage(_formatProvider);
//自己实现写入到界面控件或其它日志中心
Console.WriteLine(DateTimeOffset.Now.ToString() + " " + message);
}
} public static class MySinkExtensions
{
public static LoggerConfiguration MySink(
this LoggerSinkConfiguration loggerConfiguration,
IFormatProvider formatProvider = null)
{
return loggerConfiguration.Sink(new MySink(formatProvider));
}
}

C#实现一个简单的日志类的更多相关文章

  1. python+selenium之自定义封装一个简单的Log类

    python+selenium之自定义封装一个简单的Log类 一. 问题分析: 我们需要封装一个简单的日志类,主要有以下内容: 1. 生成的日志文件格式是 年月日时分秒.log 2. 生成的xxx.l ...

  2. Python之自定义封装一个简单的Log类

    参考:http://www.jb51.net/article/42626.htm 参考:http://blog.csdn.net/u011541946/article/details/70198676 ...

  3. VC++ 一个简单的Log类

    在软件开发中,为程序建立Log日志是很必要的,它可以记录程序运行的状态以及出错信息,方便维护和调试. 下面实现了一个简单的Log类,使用非常简单,仅供参考. // CLogHelper.h : hea ...

  4. C++ 最简单的日志类

    最近搞一个 C++ 项目的二次开发,没玩过 C++,可谓步履维艰.自己写个简单的日志类都被各种坑折磨.终于搞定了. 参考了这篇博客,并且进一步简化:https://www.cnblogs.com/Ds ...

  5. Linux下一个简单的日志系统的设计及其C代码实现

    1.概述 在大型软件系统中,为了监测软件运行状况及排查软件故障,一般都会要求软件程序在运行的过程中产生日志文件.在日志文件中存放程序流程中的一些重要信息, 包括:变量名称及其值.消息结构定义.函数返回 ...

  6. C++定义一个简单的Computer类

    /*定义一个简单的Computer类 有数据成员芯片(cpu).内存(ram).光驱(cdrom)等等, 有两个公有成员函数run.stop.cpu为CPU类的一个对象, ram为RAM类的一个对象, ...

  7. Python+Selenium中级篇之8-Python自定义封装一个简单的Log类《转载》

    Python+Selenium中级篇之8-Python自定义封装一个简单的Log类: https://blog.csdn.net/u011541946/article/details/70198676

  8. 通过编写一个简单的日志类库来加深了解C#的文件访问控制

    在程序的开发调试过程及发布运行后的状态监控中,日志都有着极其重要的分量,通过在关键逻辑节点将关键数据记录到日志文件当中能帮助我们尽快找到程序问题所在.网上有不少专业成熟的日志组件可用,比如log4ne ...

  9. setbuffer和freopen做一个简单的日志组件

    目标场景是这样的: 多线程的应用程序要频繁打一些小字节的日志,也不想引用很重的日志库. 设想了一个极其简单的日志组件,main线程中重定向stdout到文件,同时setbuffer设置一个10k的缓冲 ...

  10. 一个简单的日志函数C++

    有时候程序总是会发生意想不到的情况,为了方便排查错误的情况,还是写日志比较方便.这里自己写了一个简单的函数,能实现基本的功能. BOOL WriteLog(char * DataBuffer) { C ...

随机推荐

  1. Gitee一个仓库存储多个项目

    需求:     平时会做一些小项目,有时候一个小项目就几行代码,十几K的项目,给这些小项目建一个库保存太奢侈了太浪费了,所以换个思路,根据项目类型来创建库,然后每个小项目以孤立分支的方式存到该库中,这 ...

  2. chrony客户端发送时间戳随机问题

    现象   使用centos8的chrony给本机同步时间时,发现客户端发送给服务器的NTP包中,transmit timestamp(T3)的时间戳是随机的,同时,服务器端收到客户端请求的包后,原封不 ...

  3. GoodSync(最好的文件同步软件)

    GoodSync是一款使用创新的最好的文件同步软件,可以在你的台式机.笔记本.USB外置驱动器等设备直接进行数据文件同步软件. GoodSync将高度稳定的可靠性和极其简单的易用性完美结合起来,可以实 ...

  4. 【可观测性系列】 OpenTelemetry Collector的部署模式分析

    作者简介:大家好,我是蓝胖子 ️博客首页:主页蓝胖子的编程梦 ️热门专题:我的服务监控实践 ,500行代码手写Docker **每日一句:白日莫闲过,青春不再来 大家好,我是蓝胖子,在前面我介绍了下O ...

  5. Go语言核心36讲(Go语言实战与应用十)--学习笔记

    32 | context.Context类型 我们在上篇文章中讲到了sync.WaitGroup类型:一个可以帮我们实现一对多 goroutine 协作流程的同步工具. 在使用WaitGroup值的时 ...

  6. JOISC 2020 记录

    Day1 T1 Building 4 首先有一个 \(O(n^2)\) 的 DP:记 \(f_{i,j,0/1}\) 表示已经填了前 \(i\) 位,其中有 \(j\) 位选择了 A 序列,当前第 \ ...

  7. NC207751 牛牛的旅游纪念品

    题目链接 题目 题目描述 牛牛在牛市的旅游纪念商店里面挑花了眼,于是简单粗暴的牛牛决定--买最受欢迎的就好了. 但是牛牛的背包有限,他只能在商店的n个物品里面带m个回去,不然就装不下了. 并且牛牛希望 ...

  8. mysql 外键索引入门介绍,为什么工作中很少有人使用?

    背景 以前工作学习中,一直被告诫不要使用外键,所以也没有仔细整理过. 这里记录一下笔记. 外键 是什么? MySQL 的外键(Foreign Key)是一种关系型数据库中用于建立表与表之间关联关系的重 ...

  9. Atom N2600, N2800 安装 Ubuntu22.04 卡住的问题处理

    问题描述 Atom N2600, N2800 的某些旧型号机器, 安装 Ubuntu 时在安装界面选择安装后, 启动过程中会卡住, 或者数秒即黑屏, 再无反应. 这个问题对于Debian系的其他发行版 ...

  10. Javascript操作对象数组实现增删改查

    1.介绍 最近帮朋友弄一个简单的针对json数组的增删改成页面,正好涉及到了js去操作对象数组实现增删改查功能.我估计很多朋友应该也会遇到这类操作,所以记录一下以便分享. 2.数据准备 这里我就以学生 ...