110,571
社区成员
发帖
与我相关
我的任务
分享
public class LogInfo
{
public string AddTime;
public object Content;
public string Type;
public LogInfo(string addTime, object content, string type)
{
AddTime = addTime;
Content = content;
Type = type;
}
}
interface ILogger
{
void Warn(object msg);
void Info(object msg);
void Debug(object msg);
void Error(object msg);
}
public class SimpleLogger : ILogger
{
//貌似ConcurrentQueue在framework低版本有问题,使用StrongBox在出列后标记其值为NULL
private ConcurrentQueue<StrongBox<LogInfo>> _queue = new ConcurrentQueue<StrongBox<LogInfo>>();
private readonly string _filePath = $@"{AppDomain.CurrentDomain.BaseDirectory}\Logs\";
private bool _bThreadState = true;
//使用Lazy创建线程安全单例模式
//如果不用Lazy,则需要lock来判断对象是否需要实例化
private static readonly Lazy<SimpleLogger> _instance = new Lazy<SimpleLogger>(() => new SimpleLogger());
public static SimpleLogger Instance => _instance.Value;
private SimpleLogger()
{
Task.Factory.StartNew(HandleLogQueue);
}
/// <summary>
/// 关闭日志队列线程
/// </summary>
public void CloseLogThread()
{
_bThreadState = false;
}
/// <summary>
/// 判断日志队列是否为空
/// </summary>
/// <returns></returns>
public bool QueueIsEmpty()
{
return _queue.IsEmpty;
}
public void Debug(object msg)
{
_queue.Enqueue(new StrongBox<LogInfo>(new LogInfo(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), msg, "Debug")));
}
public void Warn(object msg)
{
_queue.Enqueue(new StrongBox<LogInfo>(new LogInfo(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), msg, "Warn")));
}
public void Info(object msg)
{
_queue.Enqueue(new StrongBox<LogInfo>(new LogInfo(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), msg, "Info")));
}
public void Error(object msg)
{
_queue.Enqueue(new StrongBox<LogInfo>(new LogInfo(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), msg, "Error")));
}
private void HandleLogQueue()
{
try
{
while (true)
{
if (_queue.IsEmpty)
{
if (!_bThreadState)
{
break;
}
Thread.Sleep(10);
}
else if (_queue.TryDequeue(out StrongBox<LogInfo> logInfo))
{
WriteLine(logInfo.Value.Content, logInfo.Value.Type, logInfo.Value.AddTime);
logInfo.Value = null;
}
}
}
catch
{
// ignored
}
}
private void WriteLine(object obj, string category, string time)
{
string path = $@"{_filePath}\{DateTime.Now.ToString("yyyyMMdd")}\";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
//File.AppendAllText(path + $"Log-{category}.txt", Format(obj, category, time));
using (StreamWriter sw = new StreamWriter(path + $"Log-{category}.txt", true, Encoding.UTF8))
{
sw.WriteLine(Format(obj, category, time));
}
}
private string Format(object obj, string category, string time)
{
StringBuilder builder = new StringBuilder();
builder.AppendFormat("{0} ", time);
if (!string.IsNullOrEmpty(category))
{
builder.AppendFormat("[{0}]", category);
}
if (obj is Exception)
{
var ex = obj as Exception;
builder.Append(ex.Message + "\r\n");
builder.Append(ex.StackTrace + "\r\n");
}
else
{
builder.Append(obj + "\r\n");
}
return builder.ToString();
}
}
private void HandleLogQueue()
{
try
{
foreach (var logInfo in _queue.GetConsumingEnumerable(_cancellToken))
{
WriteLine(logInfo.Content, logInfo.Type, logInfo.AddTime, logInfo.Ex);
}
if(_queue.QueueIsEmpty())
{
_cancellationTokenSource.Cancel();
}
}
catch
{
// ignored
}
}
using (var sw = new System.IO.StreamWriter(path + $"Log-{category}.txt", true, Encoding.UTF8))
{
sw.WriteLineAsync(Format(content, category, time, ex));
}
2、没有数据库等写入需求,完全可以抛弃队列、多线程等操作,只需要在发成错误的时候委托到异步写入方法中。
当然如果你担心长时间文件锁定造成的日志丢失等情况,可以在文件写入这里包裹try做异常处理。
这样做之后你将把文件写入等功能交到.net控制的异步写入,从而解决了你大量的队列、线程等代码构造的非阻塞写入。日志写入只要保证完整写入,并不需要保证完全的顺序一致性。
using (var sw = new StreamWriter(path + $"Log-{category}.txt", true, Encoding.UTF8))
{
sw.WriteLine(Format(content, category, time, ex));
}
这样应该是写一次打开一次文件吧
楼上有人说对文件进行独占,独占后文件就会被锁了吧,我不想文件被锁,就算程序在写日志,我也能直接打开txt文件查看日志
这样应该怎么优化好?
下面是修改后代码
public class LogInfo
{
public string AddTime;
public string Content;
public string Type;
public Exception Ex;
public LogInfo(string addTime, string content, string type, Exception ex = null)
{
AddTime = addTime;
Content = content;
Type = type;
Ex = ex;
}
}
interface ILogger
{
void Warn(string msg);
void Info(string msg);
void Debug(string msg);
void Error(string msg);
void Error(Exception ex, string msg);
}
/// <summary>
/// 支持多线程写日志
/// </summary>
public class SimpleLogger : ILogger
{
private BlockingCollection<LogInfo> _queue = new BlockingCollection<LogInfo>();
private readonly string _filePath = $@"{AppDomain.CurrentDomain.BaseDirectory}\Logs\";
private CancellationTokenSource _cancellationTokenSource;
private CancellationToken _cancellToken;
//使用Lazy创建线程安全单例模式
//如果不用Lazy,则需要lock来判断对象是否需要实例化
private static Lazy<SimpleLogger> _instance = new Lazy<SimpleLogger>(() => new SimpleLogger());
public static SimpleLogger Instance => _instance?.Value;
private SimpleLogger()
{
_cancellationTokenSource = new CancellationTokenSource();
_cancellToken = _cancellationTokenSource.Token;
Task.Factory.StartNew(HandleLogQueue);
}
/// <summary>
/// 关闭日志队列线程
/// </summary>
public void CloseLogThread()
{
_cancellationTokenSource.Cancel();
}
/// <summary>
/// 判断日志队列是否为空
/// </summary>
/// <returns></returns>
public bool QueueIsEmpty()
{
return _queue.IsCompleted;
}
public int Count()
{
return _queue.Count;
}
public void Debug(string msg)
{
_queue?.TryAdd(new LogInfo(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), msg, "Debug"));
}
public void Warn(string msg)
{
_queue?.TryAdd(new LogInfo(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), msg, "Warn"));
}
public void Info(string msg)
{
_queue?.TryAdd(new LogInfo(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), msg, "Info"));
}
public void Error(string msg)
{
_queue?.TryAdd(new LogInfo(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), msg, "Error"));
}
public void Error(Exception ex, string msg = "")
{
_queue?.TryAdd(new LogInfo(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), msg, "Error", ex));
}
private void HandleLogQueue()
{
try
{
foreach (var logInfo in _queue.GetConsumingEnumerable(_cancellToken))
{
WriteLine(logInfo.Content, logInfo.Type, logInfo.AddTime, logInfo.Ex);
}
}
catch
{
// ignored
}
}
private void WriteLine(string content, string category, string time, Exception ex = null)
{
var path = $@"{_filePath}\{DateTime.Now.ToString("yyyyMMdd")}\";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
using (var sw = new StreamWriter(path + $"Log-{category}.txt", true, Encoding.UTF8))
{
sw.WriteLine(Format(content, category, time, ex));
}
}
private string Format(string content, string category, string time, Exception ex = null)
{
var builder = new StringBuilder();
builder.AppendFormat("{0} ", time);
if (!string.IsNullOrEmpty(category))
{
builder.AppendFormat("[{0}]", category);
}
if (content != "")
{
builder.Append(content + "\r\n");
}
if (ex != null)
{
builder.Append(ex.Message + "\r\n");
builder.Append(ex.StackTrace + "\r\n");
}
return builder.ToString();
}
}
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
/*
* 代码已托管:https://gitee.com/dlgcy/dotnetcodes/tree/dlgcy/DotNet.Utilities/%E6%97%A5%E5%BF%97
*/
namespace DotNet.Utilities
{
/// <summary>
/// 简易日志类;
/// </summary>
public static class LogHelper
{
/// <summary>
/// 前缀;
/// </summary>
private static readonly string _Prefix = "Prefix";
/// <summary>
/// 目录;
/// </summary>
private static readonly string _LogDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log", _Prefix);
//private static readonly string _LogDir = Path.Combine("D:\\Log", _Prefix);
/// <summary>
/// 文件名;
/// </summary>
private static string LogFile => Path.Combine(_LogDir, $"{_Prefix}_{DateTime.Now:yyyyMMdd}.log");
/// <summary>
/// 单条内容最大长度;
/// </summary>
private static readonly int _MaxLength = 5000;
/// <summary>
/// 阻塞集合;
/// </summary>
private static BlockingCollection<string> _BC = new BlockingCollection<string>();
static LogHelper()
{
if (!Directory.Exists(_LogDir))
{
Directory.CreateDirectory(_LogDir);
}
Task.Run(() =>
{
foreach (var item in _BC.GetConsumingEnumerable())
{
try
{
File.AppendAllText(LogFile, item);
}
catch (Exception ex)
{
//Console.WriteLine($"{ex}");
File.AppendAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LogErr.txt"), $"{ex}");
}
}
});
}
/// <summary>
/// 记录日志;
/// </summary>
/// <param name="content">日志内容</param>
/// <param name="isNewLine">前面是否空行</param>
public static void Write(string content, bool isNewLine = true, [CallerFilePath]string filePath = "", [CallerMemberName]string memberName = "", [CallerLineNumber]int lineNumber = 0)
{
int len = content.Length;
if (len > _MaxLength)
{
content = $"{content.Substring(0, _MaxLength / 2)}【...】{content.Substring(len - _MaxLength / 2)}";
}
//File.AppendAllText(LogFile, $"{(isNewLine?"\r\n":"")}\r\n[{DateTime.Now:HH:mm:ss ffff}][{len}]{content}");
_BC.Add($"{(isNewLine ? $"\r\n\r\n{Path.GetFileNameWithoutExtension(filePath)}/{memberName}/{lineNumber}---->" : "")}\r\n[{DateTime.Now:HH:mm:ss ffff}][{len}]{content}");
}
}
/// <summary>
/// Console.Write 重定向到 日志
/// 用法:
/// 在构造器里加上:Console.SetOut(new ConsoleWriter());
/// </summary>
public class ConsoleWriter : TextWriter
{
// 使用 UTF-16 避免不必要的编码转换
public override Encoding Encoding => Encoding.Unicode;
// 最低限度需要重写的方法
public override void Write(string value)
{
LogHelper.Write(value, false);
}
// 为提高效率直接处理一行的输出
public override void WriteLine(string value)
{
LogHelper.Write(value);
}
}
}