213 lines
7.3 KiB
C#
213 lines
7.3 KiB
C#
using LiteDB;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
public enum LogLevel { INFO, WARN, DEBUG, ERROR }
|
|
|
|
public class LogEntry
|
|
{
|
|
public int Id { get; set; } // LiteDB 전용
|
|
public DateTime Timestamp { get; set; }
|
|
public LogLevel Level { get; set; }
|
|
public string Message { get; set; }
|
|
}
|
|
|
|
public sealed class AsyncLog : IDisposable
|
|
{
|
|
private static AsyncLog _instance;
|
|
private static readonly object _lock = new object();
|
|
|
|
private readonly BlockingCollection<LogEntry> _logQueue;
|
|
private readonly BlockingCollection<LogEntry> _errorQueue;
|
|
private readonly Task _logTask;
|
|
private readonly Task _errorTask;
|
|
|
|
private StreamWriter _writer;
|
|
private StreamWriter _errorWriter;
|
|
private string _currentDateFolder;
|
|
|
|
private LiteDatabase _db;
|
|
|
|
public string BaseLogFolder { get; private set; }
|
|
public string LogFileName { get; private set; } = "app.log";
|
|
public string ErrorFileName { get; private set; } = "error.log";
|
|
public bool EnableConsole { get; private set; }
|
|
|
|
private static AsyncLog SafeInstance => _instance;
|
|
|
|
#region Singleton Init
|
|
public static void Init(string baseLogFolder, bool enableConsole = true)
|
|
{
|
|
if (_instance != null) return;
|
|
lock (_lock)
|
|
{
|
|
if (_instance == null)
|
|
{
|
|
_instance = new AsyncLog(baseLogFolder, enableConsole);
|
|
AppDomain.CurrentDomain.ProcessExit += (s, e) => _instance.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
private AsyncLog(string baseLogFolder, bool enableConsole)
|
|
{
|
|
BaseLogFolder = baseLogFolder;
|
|
EnableConsole = enableConsole;
|
|
|
|
_logQueue = new BlockingCollection<LogEntry>(new ConcurrentQueue<LogEntry>());
|
|
_errorQueue = new BlockingCollection<LogEntry>(new ConcurrentQueue<LogEntry>());
|
|
|
|
_logTask = Task.Run(ProcessLogQueue);
|
|
_errorTask = Task.Run(ProcessErrorQueue);
|
|
|
|
Directory.CreateDirectory(BaseLogFolder);
|
|
string dbPath = Path.Combine(BaseLogFolder, "Logs.db");
|
|
_db = new LiteDatabase(dbPath);
|
|
}
|
|
#endregion
|
|
|
|
#region Public Logging API (다중 인자 지원)
|
|
public static void I(string message, params object[] args)
|
|
=> SafeInstance?.EnqueueLog(LogLevel.INFO, Format(message, args));
|
|
|
|
public static void W(string message, params object[] args)
|
|
=> SafeInstance?.EnqueueLog(LogLevel.WARN, Format(message, args));
|
|
|
|
public static void D(string message, params object[] args)
|
|
=> SafeInstance?.EnqueueLog(LogLevel.DEBUG, Format(message, args));
|
|
|
|
public static void E(string message, params object[] args)
|
|
=> SafeInstance?.EnqueueError(Format(message, args));
|
|
|
|
private static string Format(string message, object[] args)
|
|
{
|
|
if (args == null || args.Length == 0) return message;
|
|
try { return string.Format(message, args); }
|
|
catch { return message + " (format error)"; }
|
|
}
|
|
#endregion
|
|
|
|
#region Queue Processing
|
|
private void EnqueueLog(LogLevel level, string message)
|
|
{
|
|
var entry = new LogEntry { Timestamp = DateTime.Now, Level = level, Message = message };
|
|
if (EnableConsole) Console.WriteLine($"[{entry.Timestamp:yyyy-MM-dd HH:mm:ss}] [{level}] {message}");
|
|
_logQueue.Add(entry);
|
|
}
|
|
|
|
private void EnqueueError(string message)
|
|
{
|
|
var entry = new LogEntry { Timestamp = DateTime.Now, Level = LogLevel.ERROR, Message = message };
|
|
if (EnableConsole) Console.WriteLine($"[{entry.Timestamp:yyyy-MM-dd HH:mm:ss}] [ERROR] {message}");
|
|
_errorQueue.Add(entry);
|
|
}
|
|
|
|
private void ProcessLogQueue()
|
|
{
|
|
foreach (var entry in _logQueue.GetConsumingEnumerable())
|
|
{
|
|
try
|
|
{
|
|
string today = DateTime.Now.ToString("yyyy-MM-dd");
|
|
if (_writer == null || _currentDateFolder != today)
|
|
{
|
|
_writer?.Dispose();
|
|
_currentDateFolder = today;
|
|
string todayFolder = Path.Combine(BaseLogFolder, today);
|
|
Directory.CreateDirectory(todayFolder);
|
|
string logFilePath = Path.Combine(todayFolder, LogFileName);
|
|
_writer = new StreamWriter(logFilePath, true) { AutoFlush = true };
|
|
}
|
|
|
|
_writer.WriteLine($"[{entry.Timestamp:HH:mm:ss}] [{entry.Level}] {entry.Message}");
|
|
|
|
var col = _db.GetCollection<LogEntry>("logs");
|
|
col.Insert(entry);
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
private void ProcessErrorQueue()
|
|
{
|
|
foreach (var entry in _errorQueue.GetConsumingEnumerable())
|
|
{
|
|
try
|
|
{
|
|
string today = DateTime.Now.ToString("yyyy-MM-dd");
|
|
if (_errorWriter == null || _currentDateFolder != today)
|
|
{
|
|
_errorWriter?.Dispose();
|
|
_currentDateFolder = today;
|
|
string todayFolder = Path.Combine(BaseLogFolder, today);
|
|
Directory.CreateDirectory(todayFolder);
|
|
string errorFilePath = Path.Combine(todayFolder, ErrorFileName);
|
|
_errorWriter = new StreamWriter(errorFilePath, true) { AutoFlush = true };
|
|
}
|
|
|
|
_errorWriter.WriteLine($"[{entry.Timestamp:HH:mm:ss}] [ERROR] {entry.Message}");
|
|
|
|
var col = _db.GetCollection<LogEntry>("errors");
|
|
col.Insert(entry);
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Dispose
|
|
public void Dispose()
|
|
{
|
|
_logQueue.CompleteAdding();
|
|
_errorQueue.CompleteAdding();
|
|
try { _logTask.Wait(); } catch { }
|
|
try { _errorTask.Wait(); } catch { }
|
|
_writer?.Dispose();
|
|
_errorWriter?.Dispose();
|
|
_logQueue.Dispose();
|
|
_errorQueue.Dispose();
|
|
_db?.Dispose();
|
|
}
|
|
#endregion
|
|
|
|
#region Query API
|
|
public static LogEntry[] QueryLogs(DateTime? from = null, DateTime? to = null, LogLevel[] levels = null, bool errorLogs = false)
|
|
{
|
|
if (_instance == null) return Array.Empty<LogEntry>();
|
|
|
|
var col = _instance._db.GetCollection<LogEntry>(errorLogs ? "errors" : "logs");
|
|
var query = col.Query();
|
|
|
|
if (from.HasValue) query = query.Where(x => x.Timestamp >= from.Value);
|
|
if (to.HasValue) query = query.Where(x => x.Timestamp <= to.Value);
|
|
if (levels != null && levels.Length > 0) query = query.Where(x => levels.Contains(x.Level));
|
|
|
|
return query.ToArray();
|
|
}
|
|
#endregion
|
|
}
|
|
/*
|
|
✅ 사용 예제
|
|
class Program
|
|
{
|
|
static void Main()
|
|
{
|
|
AsyncLog.Init("C:\\Logs", enableConsole: true);
|
|
|
|
AsyncLog.I("애플리케이션 시작됨");
|
|
AsyncLog.D("디버그 메시지: 값={0}, 상태={1}", 123, "OK");
|
|
AsyncLog.W("경고 발생: 사용자 {0}, 시도 횟수 {1}", "Alice", 5);
|
|
AsyncLog.E("에러 발생: 코드={0}, 메시지={1}", 500, "Internal Server Error");
|
|
|
|
// 로그 조회 (오늘, INFO 레벨만)
|
|
var logs = AsyncLog.QueryLogs(DateTime.Today, DateTime.Today.AddDays(1), new[] { LogLevel.INFO });
|
|
foreach (var log in logs)
|
|
{
|
|
Console.WriteLine($"[조회] {log.Timestamp} {log.Level} {log.Message}");
|
|
}
|
|
}
|
|
}
|
|
*/ |