Files
XRLib/Assets/Scripts/UVC/Log/SQLiteAppender.cs
2025-11-11 13:51:23 +09:00

180 lines
7.1 KiB
C#

#nullable enable
#if !UNITY_WEBGL || UNITY_EDITOR
using log4net.Appender;
using log4net.Core;
using SQLite4Unity3d;
using System.Threading;
using UVC.Extention;
namespace UVC.Log
{
/// <summary>
/// SQLite 데이터베이스에 로그를 저장하는 log4net Appender 구현체입니다.
/// 로그 메시지를 SQLite 데이터베이스 테이블에 저장하고 오래된 로그를 자동으로 정리합니다.
/// </summary>
/// <remarks>
/// 이 클래스는 log4net의 AppenderSkeleton을 상속받아 로그 레코드를 SQLite 데이터베이스에 저장합니다.
/// 한 달 이상 지난 로그 레코드는 자동으로 삭제되며, 데이터베이스 락 상황에서의 재시도 메커니즘을 포함하고 있습니다.
/// </remarks>
/// <example>
/// log4net 구성 파일(App.config 또는 log4net.config)에서 다음과 같이 설정할 수 있습니다:
/// <code>
/// <appender name="SQLiteAppender" type="UVC.Log.SQLiteAppender, AssemblyName">
/// <connectionString value="data.db" />
/// <param name="Threshold" value="DEBUG" />
/// </appender>
/// </code>
///
/// 코드에서 직접 설정하는 방법:
/// <code>
/// var sqliteAppender = new SQLiteAppender();
/// sqliteAppender.ConnectionString = "data.db";
/// sqliteAppender.Threshold = log4net.Core.Level.Debug;
///
/// log4net.Config.BasicConfigurator.Configure(sqliteAppender);
/// </code>
/// </example>
public class SQLiteAppender : AppenderSkeleton
{
/// <summary>
/// SQLite 데이터베이스 파일의 경로를 지정하거나 가져옵니다.
/// </summary>
/// <remarks>
/// 절대 경로 또는 상대 경로로 설정할 수 있으며, 상대 경로는 애플리케이션의 실행 디렉터리를 기준으로 합니다.
/// </remarks>
/// <example>
/// <code>
/// var sqliteAppender = new SQLiteAppender();
/// // 절대 경로 사용
/// sqliteAppender.ConnectionString = "C:\\Logs\\application.db";
/// // 또는 상대 경로 사용
/// sqliteAppender.ConnectionString = "Logs\\application.db";
/// </code>
/// </example>
public string ConnectionString { get; set; }
/// <summary>
/// 한 달 이상 된 로그 데이터 삭제 여부를 추적합니다.
/// </summary>
private bool doDeleted = false;
/// <summary>
/// SQLite 데이터베이스 연결을 저장합니다.
/// </summary>
private SQLiteConnection? db;
/// <summary>
/// 데이터베이스 작업에 대한 동기화 잠금 객체입니다.
/// </summary>
private static readonly object _dbLock = new object();
/// <summary>
/// 로깅 이벤트를 SQLite 데이터베이스에 기록합니다.
/// </summary>
/// <param name="loggingEvent">기록할 로깅 이벤트</param>
/// <remarks>
/// 이 메서드는 log4net 프레임워크에 의해 새로운 로그 메시지가 발생할 때마다 호출됩니다.
/// 빈 메시지는 무시하고, 로그 항목을 데이터베이스에 저장합니다.
/// 데이터베이스 연결이 없는 경우 새로 생성하고, 필요시 오래된 로그를 삭제합니다.
/// </remarks>
protected override void Append(LoggingEvent loggingEvent)
{
// 빈 메시지 무시
if (loggingEvent.RenderedMessage.Trim().IsNullOrEmpty()) return;
// 로그 항목 생성
var logEntry = new LogEntry
{
Date = loggingEvent.TimeStamp.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
Thread = loggingEvent.ThreadName,
Level = loggingEvent.Level.ToString(),
Logger = loggingEvent.LoggerName,
Message = loggingEvent.RenderedMessage,
Exception = loggingEvent.GetExceptionString()
};
// 데이터베이스 연결이 없으면 새로 생성
if (db == null)
{
var dbPath = ConnectionString;// System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConnectionString);
db = new SQLiteConnection(dbPath);
db.CreateTable<LogEntry>();
}
// 한 달 이상 된 로그 데이터 삭제 (최초 1회만 실행)
if (doDeleted == false)
{
//한달 지난 로그 삭제
db.Execute("DELETE FROM LogEntry WHERE Date < datetime('now', '-1 month', 'localtime');");
doDeleted = true;
}
// 스레드 동기화를 통해 데이터베이스 작업 수행
lock (_dbLock)
{
// 데이터베이스 락 시 재시도 메커니즘
int retryCount = 100;
while (retryCount > 0)
{
try
{
db.Insert(logEntry);
break;
}
catch (SQLiteException ex) when (ex.Message.Contains("database is locked"))
{
retryCount--;
if (retryCount == 0) break; // 최대 재시도 횟수 초과
Thread.Sleep(100); // 재시도 전에 100ms 대기
}
}
}
}
/// <summary>
/// SQLite 데이터베이스에 저장되는 로그 항목 구조를 정의합니다.
/// </summary>
/// <remarks>
/// 이 클래스는 로그 테이블의 스키마를 정의하며, SQLite4Unity3d의 ORM 기능을 사용합니다.
/// </remarks>
public class LogEntry
{
/// <summary>
/// 로그 항목의 고유 식별자입니다. 자동 증가되는 기본 키로 사용됩니다.
/// </summary>
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
/// <summary>
/// 로그가 생성된 날짜와 시간을 ISO 8601 형식으로 저장합니다.
/// </summary>
public string Date { get; set; }
/// <summary>
/// 로그를 생성한 스레드의 이름을 저장합니다.
/// </summary>
public string Thread { get; set; }
/// <summary>
/// 로그 레벨을 저장합니다 (DEBUG, INFO, WARN, ERROR, FATAL 등).
/// </summary>
public string Level { get; set; }
/// <summary>
/// 로그를 생성한 로거의 이름을 저장합니다.
/// </summary>
public string Logger { get; set; }
/// <summary>
/// 로그 메시지 내용을 저장합니다.
/// </summary>
public string Message { get; set; }
/// <summary>
/// 예외 정보를 저장합니다. 예외가 없는 경우 빈 문자열이 될 수 있습니다.
/// </summary>
public string Exception { get; set; }
}
}
}
#endif