using System; using System.IO; using UnityEngine; public class Logger { public static bool Enabled = true; private static Logger m_instance = null; private static readonly object FileLock = new object(); private static string ApplicationDirectory { get { // Android (and iOS): dataPath is inside the package/APK and is not writable. if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer) { return Application.persistentDataPath; } string path = Application.dataPath; #if UNITY_EDITOR // Never write under Assets/: the Editor importer locks those files, and // File.AppendAllText then throws Sharing violation. Mirror treats that as a // fatal RPC error and disconnects the client. path = Path.GetFullPath(Path.Combine(path, "..")); #else if (Application.platform == RuntimePlatform.OSXPlayer) { path = Path.Combine(path, "..", ".."); } else if (Application.platform == RuntimePlatform.WindowsPlayer) { path = Path.Combine(path, ".."); } #endif return path; } } public string LogFilePath {get; private set;} public static Logger instance { get { if (m_instance == null) { m_instance = new Logger(); } return m_instance; } } public Logger() { if(!Enabled){return;} Debug.Log("Starting logger @ " + ApplicationDirectory); if(LogFilePath == null){ LogFilePath = Path.Combine(ApplicationDirectory, "Log.txt"); } TryWriteFile(LogFilePath, "Logger initiated at " + DateTime.Now + "\n\n", append: false); } public void log(string message){ if(!Enabled){return;} Debug.Log(message); TryWriteFile(LogFilePath, $"[{DateTime.Now}] {message}\n", append: true); } public static void Log(string message){ instance.log(message); } public static void SetFileName(string fileName){ EnsureDirectoryExists(Path.Combine(ApplicationDirectory, "Logs")); instance.LogFilePath = Path.Combine(ApplicationDirectory, "Logs", fileName + ".txt"); } public static void EnsureDirectoryExists(string path){ if(!Directory.Exists(path)){ Directory.CreateDirectory(path); } } /// Writable Logs folder next to match log files (creates if missing). public static string GetLogsDirectory() { string dir = Path.Combine(ApplicationDirectory, "Logs"); EnsureDirectoryExists(dir); return dir; } public static void SetFilePath(string path){ instance.LogFilePath= path; } /// /// File I/O must never throw: callers include Mirror RPCs, and an exception there /// disconnects the client. Sharing violations are expected if another process has /// the file open; FileShare.ReadWrite plus a swallow keeps gameplay alive. /// static void TryWriteFile(string path, string contents, bool append) { if (string.IsNullOrEmpty(path) || contents == null) return; try { string dir = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(dir)) EnsureDirectoryExists(dir); } catch (Exception e) { Debug.LogWarning("Logger: could not create log directory: " + e.Message); return; } lock (FileLock) { try { FileMode mode = append ? FileMode.Append : FileMode.Create; using (var stream = new FileStream(path, mode, FileAccess.Write, FileShare.ReadWrite)) using (var writer = new StreamWriter(stream)) { writer.Write(contents); } } catch (Exception e) { Debug.LogWarning("Logger: file write failed (" + path + "): " + e.Message); } } } }