#nullable enable #if !UNITY_WEBGL || UNITY_EDITOR using log4net.Appender; using log4net.Core; using SQLite4Unity3d; using System.Threading; using UVC.Extention; namespace UVC.Log { /// /// SQLite 데이터베이스에 로그를 저장하는 log4net Appender 구현체입니다. /// 로그 메시지를 SQLite 데이터베이스 테이블에 저장하고 오래된 로그를 자동으로 정리합니다. /// /// /// 이 클래스는 log4net의 AppenderSkeleton을 상속받아 로그 레코드를 SQLite 데이터베이스에 저장합니다. /// 한 달 이상 지난 로그 레코드는 자동으로 삭제되며, 데이터베이스 락 상황에서의 재시도 메커니즘을 포함하고 있습니다. /// /// /// log4net 구성 파일(App.config 또는 log4net.config)에서 다음과 같이 설정할 수 있습니다: /// /// /// /// /// /// /// /// 코드에서 직접 설정하는 방법: /// /// var sqliteAppender = new SQLiteAppender(); /// sqliteAppender.ConnectionString = "data.db"; /// sqliteAppender.Threshold = log4net.Core.Level.Debug; /// /// log4net.Config.BasicConfigurator.Configure(sqliteAppender); /// /// public class SQLiteAppender : AppenderSkeleton { /// /// SQLite 데이터베이스 파일의 경로를 지정하거나 가져옵니다. /// /// /// 절대 경로 또는 상대 경로로 설정할 수 있으며, 상대 경로는 애플리케이션의 실행 디렉터리를 기준으로 합니다. /// /// /// /// var sqliteAppender = new SQLiteAppender(); /// // 절대 경로 사용 /// sqliteAppender.ConnectionString = "C:\\Logs\\application.db"; /// // 또는 상대 경로 사용 /// sqliteAppender.ConnectionString = "Logs\\application.db"; /// /// public string ConnectionString { get; set; } /// /// 한 달 이상 된 로그 데이터 삭제 여부를 추적합니다. /// private bool doDeleted = false; /// /// SQLite 데이터베이스 연결을 저장합니다. /// private SQLiteConnection? db; /// /// 데이터베이스 작업에 대한 동기화 잠금 객체입니다. /// private static readonly object _dbLock = new object(); /// /// 로깅 이벤트를 SQLite 데이터베이스에 기록합니다. /// /// 기록할 로깅 이벤트 /// /// 이 메서드는 log4net 프레임워크에 의해 새로운 로그 메시지가 발생할 때마다 호출됩니다. /// 빈 메시지는 무시하고, 로그 항목을 데이터베이스에 저장합니다. /// 데이터베이스 연결이 없는 경우 새로 생성하고, 필요시 오래된 로그를 삭제합니다. /// 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(); } // 한 달 이상 된 로그 데이터 삭제 (최초 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 대기 } } } } /// /// SQLite 데이터베이스에 저장되는 로그 항목 구조를 정의합니다. /// /// /// 이 클래스는 로그 테이블의 스키마를 정의하며, SQLite4Unity3d의 ORM 기능을 사용합니다. /// public class LogEntry { /// /// 로그 항목의 고유 식별자입니다. 자동 증가되는 기본 키로 사용됩니다. /// [PrimaryKey, AutoIncrement] public int Id { get; set; } /// /// 로그가 생성된 날짜와 시간을 ISO 8601 형식으로 저장합니다. /// public string Date { get; set; } /// /// 로그를 생성한 스레드의 이름을 저장합니다. /// public string Thread { get; set; } /// /// 로그 레벨을 저장합니다 (DEBUG, INFO, WARN, ERROR, FATAL 등). /// public string Level { get; set; } /// /// 로그를 생성한 로거의 이름을 저장합니다. /// public string Logger { get; set; } /// /// 로그 메시지 내용을 저장합니다. /// public string Message { get; set; } /// /// 예외 정보를 저장합니다. 예외가 없는 경우 빈 문자열이 될 수 있습니다. /// public string Exception { get; set; } } } } #endif