Files
XRLib/Assets/Scripts/UVC/Log/ServerLog.cs
2025-11-10 16:38:43 +09:00

301 lines
12 KiB
C#

#nullable enable
using Cysharp.Threading.Tasks;
using SQLite4Unity3d;
using System;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
namespace UVC.Log
{
/// <summary>
/// ServerLog는 HTTP 요청과 MQTT 메시지를 기록하는 기능을 제공합니다.
/// </summary>
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
/// <summary>
/// WebGL 환경에서 데이터베이스 연결을 동기적으로 초기화합니다.
/// </summary>
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<HttpLogEntry>();
db.CreateTable<MqttLogEntry>();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
/// <summary>
/// WebGL에서 오래된 로그를 동기적으로 정리합니다.
/// </summary>
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
/// <summary>
/// HTTP 요청을 로깅하여 데이터베이스에 로그 항목을 생성하고 저장합니다.
/// </summary>
/// <remarks>이 메소드는 스레드 안전하며 로그 항목이 동기화된 방식으로 데이터베이스에 저장되도록 보장합니다.
/// 호출자는 유효한 입력 값을 제공할 책임이 있습니다.</remarks>
/// <param name="url">HTTP 요청의 URL입니다.</param>
/// <param name="method">요청에 사용된 HTTP 메소드(예: GET, POST)입니다.</param>
/// <param name="headerJson">JSON 형식의 HTTP 요청 헤더입니다.</param>
/// <param name="bodyString">HTTP 요청의 본문을 문자열로 표현한 것입니다.</param>
/// <param name="date">HTTP 요청의 날짜와 시간을 문자열로 표현한 것입니다.</param>
/// <returns>로깅된 HTTP 요청을 나타내는 <see cref="HttpLogEntry"/> 객체입니다. 반환된 객체는
/// 요청 URL, 메소드, 헤더, 본문, 날짜와 같은 세부 정보를 포함합니다.</returns>
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를 반환
}
}
/// <summary>
/// HTTP 응답의 세부 정보를 데이터베이스에 기록합니다.
/// </summary>
/// <remarks>이 메소드는 스레드 안전하며 로깅 작업이 동기화되도록 보장합니다.</remarks>
/// <param name="httpLogEntry">기록할 응답의 세부 정보가 포함된 HTTP 로그 항목입니다. 이 매개변수는 <see
/// langword="null"/>일 수 없습니다.</param>
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);
}
}
}
/// <summary>
/// MQTT 메시지를 데이터베이스에 기록하고 생성된 로그 항목을 반환합니다.
/// </summary>
/// <remarks>이 메서드는 MQTT 메시지에 대한 새로운 로그 항목을 생성하여 데이터베이스에 저장합니다. 이 메서드는 스레드 안전하며 데이터베이스 작업이 동기화되도록 보장합니다.</remarks>
/// <param name="url">MQTT 브로커의 URL입니다.</param>
/// <param name="port">MQTT 브로커에 연결하는 데 사용된 포트입니다.</param>
/// <param name="topic">MQTT 메시지의 토픽입니다.</param>
/// <param name="payload">MQTT 메시지의 페이로드입니다.</param>
/// <param name="date">메시지가 수신된 날짜와 시간(문자열 형식)입니다.</param>
/// <param name="exception">처리 중 오류가 발생한 경우의 예외 메시지(옵션). <see langword="null"/>일 수 있습니다.</param>
/// <returns>기록된 MQTT 메시지를 나타내는 <see cref="MqttLogEntry"/> 객체입니다.</returns>
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<HttpLogEntry>();
db.CreateTable<MqttLogEntry>();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
#endif
}
}
/// <summary>
/// HTTP 요청 및 관련 응답을 위한 로그 항목을 나타냅니다.
/// </summary>
/// <remarks>
/// 이 클래스는 HTTP 요청의 URL, 메소드, 헤더, 본문과 응답 데이터를 (가능한 경우) 포함한 세부 정보를 저장하는 데 사용됩니다.
/// 또한 요청 또는 응답 처리 중 발생한 예외 사항도 기록합니다.
/// </remarks>
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; }
}
/// <summary>
/// MQTT 메시지에 대한 로그 항목을 나타냅니다. 연결, 토픽, 페이로드 및 모든 관련 예외에 대한 세부 정보를 포함합니다.
/// </summary>
/// <remarks>이 클래스는 일반적으로 로깅 또는 디버깅 목적으로 MQTT 메시지에 대한 정보를 저장하고 검색하는 데 사용됩니다.
/// 각 로그 항목에는 URL, 포트, 토픽, 페이로드 및 메시지가 기록된 날짜와 같은 세부 정보가 포함됩니다.
/// MQTT 작업 중 예외가 발생한 경우 <see cref="Exception"/> 속성에도 기록될 수 있습니다.</remarks>
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; }
}
}