#nullable enable using Cysharp.Threading.Tasks; using SQLite4Unity3d; using System; using System.IO; using System.Threading.Tasks; using UnityEngine; namespace UVC.Log { /// /// ServerLog는 HTTP 요청과 MQTT 메시지를 기록하는 기능을 제공합니다. /// public class ServerLog { private static bool doDeletedHttp = false; private static bool doDeletedMqtt = false; private static SQLiteConnection? db; private static readonly object _dbLock = new object(); static ServerLog() { try { #if UNITY_WEBGL && !UNITY_EDITOR // WebGL: 백그라운드(ThreadPool) 사용 불가 → 동기 초기화 EnsureDatabaseConnectionWebGL(); CleanupOldLogsWebGL(); #else CheckDatabaseConnectionAsync().ContinueWith((_) => { lock (_dbLock) { if (doDeletedHttp == false) { //한달 지난 로그 삭제 db?.Execute("DELETE FROM HttpLogEntry WHERE RequestDate < datetime('now', '-1 month', 'localtime');"); doDeletedHttp = true; } if (doDeletedMqtt == false) { //한달 지난 로그 삭제 db?.Execute("DELETE FROM MqttLogEntry WHERE Date < datetime('now', '-1 month', 'localtime');"); doDeletedMqtt = true; } } }); #endif } catch (Exception ex) { Debug.LogException(ex); throw; } } #if UNITY_WEBGL && !UNITY_EDITOR /// /// WebGL 환경에서 데이터베이스 연결을 동기적으로 초기화합니다. /// private static void EnsureDatabaseConnectionWebGL() { if (db != null) return; string appDataPath = Application.persistentDataPath; DirectoryInfo di = new DirectoryInfo(Path.Combine(appDataPath, "unityLogs")); if (!di.Exists) di.Create(); // WebGL Player 환경 구분 string dbPath = Application.platform == RuntimePlatform.WebGLPlayer ? Path.Combine(di.FullName, "serverWebGLLog.db") : Path.Combine(di.FullName, "serverEditorLog.db"); db = new SQLiteConnection(dbPath); try { db.CreateTable(); db.CreateTable(); } catch (Exception ex) { Debug.LogException(ex); } } /// /// WebGL에서 오래된 로그를 동기적으로 정리합니다. /// private static void CleanupOldLogsWebGL() { lock (_dbLock) { if (!doDeletedHttp) { db?.Execute("DELETE FROM HttpLogEntry WHERE RequestDate < datetime('now', '-1 month', 'localtime');"); doDeletedHttp = true; } if (!doDeletedMqtt) { db?.Execute("DELETE FROM MqttLogEntry WHERE Date < datetime('now', '-1 month', 'localtime');"); doDeletedMqtt = true; } } } #endif /// /// HTTP 요청을 로깅하여 데이터베이스에 로그 항목을 생성하고 저장합니다. /// /// 이 메소드는 스레드 안전하며 로그 항목이 동기화된 방식으로 데이터베이스에 저장되도록 보장합니다. /// 호출자는 유효한 입력 값을 제공할 책임이 있습니다. /// HTTP 요청의 URL입니다. /// 요청에 사용된 HTTP 메소드(예: GET, POST)입니다. /// JSON 형식의 HTTP 요청 헤더입니다. /// HTTP 요청의 본문을 문자열로 표현한 것입니다. /// HTTP 요청의 날짜와 시간을 문자열로 표현한 것입니다. /// 로깅된 HTTP 요청을 나타내는 객체입니다. 반환된 객체는 /// 요청 URL, 메소드, 헤더, 본문, 날짜와 같은 세부 정보를 포함합니다. public static HttpLogEntry LogHttpRequest(string url, string method, string headerJson, string bodyString, string date) { #if UNITY_WEBGL && !UNITY_EDITOR // WebGL: 초기화 보장 if (db == null) EnsureDatabaseConnectionWebGL(); #endif var logEntry = new HttpLogEntry { RequestURL = url, RequestMethod = method, RequestHeader = headerJson, RequestBody = bodyString, RequestDate = date, }; lock (_dbLock) { try { db?.Insert(logEntry); } catch (Exception ex) { Debug.LogException(ex); } return logEntry; // 반환값으로 ID를 반환 } } /// /// HTTP 응답의 세부 정보를 데이터베이스에 기록합니다. /// /// 이 메소드는 스레드 안전하며 로깅 작업이 동기화되도록 보장합니다. /// 기록할 응답의 세부 정보가 포함된 HTTP 로그 항목입니다. 이 매개변수는 일 수 없습니다. public static void LogHttpResponse(HttpLogEntry httpLogEntry) { #if UNITY_WEBGL && !UNITY_EDITOR if (db == null) EnsureDatabaseConnectionWebGL(); #endif lock (_dbLock) { try { db?.Update(httpLogEntry); } catch (Exception ex) { Debug.LogException(ex); } } } /// /// MQTT 메시지를 데이터베이스에 기록하고 생성된 로그 항목을 반환합니다. /// /// 이 메서드는 MQTT 메시지에 대한 새로운 로그 항목을 생성하여 데이터베이스에 저장합니다. 이 메서드는 스레드 안전하며 데이터베이스 작업이 동기화되도록 보장합니다. /// MQTT 브로커의 URL입니다. /// MQTT 브로커에 연결하는 데 사용된 포트입니다. /// MQTT 메시지의 토픽입니다. /// MQTT 메시지의 페이로드입니다. /// 메시지가 수신된 날짜와 시간(문자열 형식)입니다. /// 처리 중 오류가 발생한 경우의 예외 메시지(옵션). 일 수 있습니다. /// 기록된 MQTT 메시지를 나타내는 객체입니다. public static MqttLogEntry LogMqtt(string url, string port, string topic, string payload, string date, string? exception = null) { #if UNITY_WEBGL && !UNITY_EDITOR if (db == null) EnsureDatabaseConnectionWebGL(); #endif var logEntry = new MqttLogEntry { URL = url, Port = port, Topic = topic, Payload = payload, Date = date, Exception = exception }; lock (_dbLock) { try { db?.Insert(logEntry); } catch (Exception ex) { Debug.LogException(ex); } return logEntry; } } protected static async Task CheckDatabaseConnectionAsync() { #if UNITY_WEBGL && !UNITY_EDITOR // WebGL: 비동기 + 스레드 전환 제거, 동기 루틴 사용 EnsureDatabaseConnectionWebGL(); return; #else if (db == null) { string dbPath = string.Empty; string appDataPath = string.Empty; bool isMainThread = PlayerLoopHelper.IsMainThread; if (!isMainThread) await UniTask.SwitchToMainThread(); appDataPath = Application.persistentDataPath; if (!isMainThread) await UniTask.SwitchToThreadPool(); DirectoryInfo di = new DirectoryInfo(Path.Combine(appDataPath, "unityLogs")); if (!di.Exists) di.Create(); // unity runtime 일때 if (Application.platform == RuntimePlatform.WindowsPlayer) { dbPath = Path.Combine(di.FullName, @"serverRuntimeLog.db"); } else { // unity editor 일때 dbPath = Path.Combine(di.FullName, @"serverEditorLog.db"); } db = new SQLiteConnection(dbPath); try { db.CreateTable(); db.CreateTable(); } catch (Exception ex) { Debug.LogException(ex); } } #endif } } /// /// HTTP 요청 및 관련 응답을 위한 로그 항목을 나타냅니다. /// /// /// 이 클래스는 HTTP 요청의 URL, 메소드, 헤더, 본문과 응답 데이터를 (가능한 경우) 포함한 세부 정보를 저장하는 데 사용됩니다. /// 또한 요청 또는 응답 처리 중 발생한 예외 사항도 기록합니다. /// public class HttpLogEntry { [PrimaryKey, AutoIncrement] public int Id { get; set; } public string RequestURL { get; set; } public string RequestMethod { get; set; } public string RequestHeader { get; set; } public string RequestBody { get; set; } public string RequestDate { get; set; } public string? ResponseData { get; set; } public string? ResponseDate { get; set; } public string? Exception { get; set; } } /// /// MQTT 메시지에 대한 로그 항목을 나타냅니다. 연결, 토픽, 페이로드 및 모든 관련 예외에 대한 세부 정보를 포함합니다. /// /// 이 클래스는 일반적으로 로깅 또는 디버깅 목적으로 MQTT 메시지에 대한 정보를 저장하고 검색하는 데 사용됩니다. /// 각 로그 항목에는 URL, 포트, 토픽, 페이로드 및 메시지가 기록된 날짜와 같은 세부 정보가 포함됩니다. /// MQTT 작업 중 예외가 발생한 경우 속성에도 기록될 수 있습니다. public class MqttLogEntry { [PrimaryKey, AutoIncrement] public int Id { get; set; } public string URL { get; set; } public string Port { get; set; } public string Topic { get; set; } public string Payload { get; set; } public string Date { get; set; } public string? Exception { get; set; } } }