using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; namespace P4Connect { class Logger { TextWriterTraceListener p4Listener; System.IO.FileStream p4LogFile; string prevPath; string name; private static StaticResourceManager myResourceManager = new StaticResourceManager(); static readonly Logger _instance = new Logger(); private Logger() { p4Listener = null; p4LogFile = null; prevPath = ""; name = "p4connect"; } public static Logger Instance { get { return _instance; } } public void Enable() { if (Config.LogPath != prevPath) { Disable(); if (Config.EnableLog) { try { prevPath = Config.LogPath; p4LogFile = new System.IO.FileStream(Config.LogPath, System.IO.FileMode.Append, System.IO.FileAccess.Write, System.IO.FileShare.Read); StreamWriter writer = new StreamWriter(p4LogFile); p4Listener = new TextWriterTraceListener(writer, name); myResourceManager.AddStream(writer); // ensure flush and close at application exit // Write the initial trace message to the console trace listener. p4Listener.WriteLine(DateTime.Now.ToString() + " [" + p4Listener.Name + "] - Logger Initialized."); // Add the new console trace listener to the collection of trace listeners. Trace.Listeners.Add(p4Listener); // Hook the Bridge logging calls up using the P4ConnectLog delegate Perforce.P4.LogFile.SetLoggingFunction(P4ConnectLog); Trace.WriteLine("Trace.WriteLine"); Debug.WriteLine("Debug.WriteLine"); Trace.Flush(); } catch (Exception e) { UnityEngine.Debug.Log("Exception in Logger.Enable" + e.Message); } } } } public void Disable() { try { if (p4Listener != null) { Trace.Flush(); Trace.Listeners.Remove(p4Listener); p4Listener.Close(); p4Listener = null; } if (p4LogFile != null) { p4LogFile.Close(); } p4LogFile = null; } catch (Exception e) { UnityEngine.Debug.Log("Exception in Logger.Disable " + e.Message); } } public void Log(int log_level, String source, String message) { if (Config.EnableLog) { string payload = DateTime.Now.ToString() + " " + ((LogLevel) log_level).ToString() + " [ " + source + " ] " + message; UnityEngine.Debug.Log(payload); if (p4Listener == null) System.Console.WriteLine("p4Listener is null! EnableLog: " + Config.EnableLog.ToString()); if (Config.EnableLog && p4Listener != null) { p4Listener.WriteLine(payload); Trace.Flush(); } } } /// Log_Level. The lower the level, the more severe the level, /// 0 fatal /// 1 error /// 2 warning /// 3 information /// 4+ debugging messages /// public enum LogLevel { Fatal, Error, Warn, Info, Debug }; public static void P4ConnectLog(int log_level, String source, String message) { Logger.Instance.Log(log_level, source, message); } public static void p4log(string message) { if (!String.IsNullOrEmpty(message)) P4ConnectLog((int) LogLevel.Info, "p4log", message); } public static void p4logArray(string indent, string[] a) { if (a == null || a.Length == 0) return; foreach (string s in a) { p4log(indent + s); } } public static void p4logFileSpecs(string indent, IList a) { if (a == null || a.Count == 0) return; foreach (Perforce.P4.FileSpec s in a) { //p4log(indent + " depot: " + s.DepotPath); //p4log(indent + "client: " + s.ClientPath); //p4log(indent + " local: " + s.LocalPath); //p4log(indent + " ver: " + s.Version); p4log(indent + s.ToString()); } } public static void p4logFilesAndOp(string indent, P4Connect.Engine.FilesAndOp obj) { p4log(indent + "FilesAndOp: " + obj.FileOp.ToString()); if (obj.File == null) p4log(indent + " file: NULL"); else p4log(indent + " file: " + obj.File.ToString()); if (obj.Meta == null) p4log(indent + " meta: NULL"); else p4log(indent + " meta: " + obj.Meta.ToString()); if (obj.MoveToFile == null) p4log(indent + " mov2: NULL"); else p4log(indent + " mov2: " + obj.MoveToFile.ToString()); } } /// /// Ensures deterministic StreamWriter Flush, and File close at application exit for /// static facades. The cleanup is done after all normal finalizers have been called. /// /// borrowed from: http://geekswithblogs.net/akraus1/articles/81629.aspx /// public class StaticResourceManager : System.Runtime.ConstrainedExecution.CriticalFinalizerObject { List writers = new List(); public void AddStream(StreamWriter writer) { writers.Add(writer); FileStream fStream = GetIfFileStream(writer.BaseStream); if (fStream != null) { GC.SuppressFinalize(fStream); // prevent GC on FileStream // prevent file close at application exit before want to let it happen GC.SuppressFinalize(fStream.SafeFileHandle); } } static FileStream GetIfFileStream(Stream stream) { if (stream is FileStream) return (FileStream)stream; else return null; } /// /// Deterministic cleanup of StreamWriters /// 1. StreamWriter Close -> FileStream -> Close -> possible Writes /// 2. FileHandle Close /// ~StaticResourceManager() { foreach (StreamWriter writer in writers) { FileStream fstream = GetIfFileStream(writer.BaseStream); Microsoft.Win32.SafeHandles.SafeFileHandle handle = null; if (fstream != null) { handle = fstream.SafeFileHandle; } writer.Close(); // Close StreamWriter first if (handle != null) // Close file handle now hurray handle.Close(); } } } }