using System; using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace StardewModdingAPI { public static class Log { private static readonly LogWriter _writer; static Log() { _writer = LogWriter.Instance; } private static void PrintLog(LogInfo li) { _writer.WriteToLog(li); } #region Exception Logging /// /// Catch unhandled exception from the application /// /// Should be moved out of here if we do more than just log the exception. public static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine("An exception has been caught"); File.WriteAllText(Path.Combine(Constants.LogDir, $"MODDED_ErrorLog.Log_{DateTime.UtcNow.Ticks}.txt"), e.ExceptionObject.ToString()); } /// /// Catch thread exception from the application /// /// Should be moved out of here if we do more than just log the exception. public static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) { Console.WriteLine("A thread exception has been caught"); File.WriteAllText(Path.Combine(Constants.LogDir, $"MODDED_ErrorLog.Log_{Extensions.Random.Next(100000000, 999999999)}.txt"), e.Exception.ToString()); } #endregion #region Sync Logging /// /// NOTICE: Sync logging is discouraged. Please use Async instead. /// /// Message to log /// Colour of message public static void SyncColour(object message, ConsoleColor colour) { PrintLog(new LogInfo(message?.ToString(), colour)); } #endregion #region Async Logging public static void AsyncColour(object message, ConsoleColor colour) { Task.Run(() => { PrintLog(new LogInfo(message?.ToString(), colour)); }); } public static void Async(object message) { AsyncColour(message?.ToString(), ConsoleColor.Gray); } public static void AsyncR(object message) { AsyncColour(message?.ToString(), ConsoleColor.Red); } public static void AsyncO(object message) { AsyncColour(message.ToString(), ConsoleColor.DarkYellow); } public static void AsyncY(object message) { AsyncColour(message?.ToString(), ConsoleColor.Yellow); } public static void AsyncG(object message) { AsyncColour(message?.ToString(), ConsoleColor.Green); } public static void AsyncC(object message) { AsyncColour(message?.ToString(), ConsoleColor.Cyan); } public static void AsyncM(object message) { AsyncColour(message?.ToString(), ConsoleColor.Magenta); } public static void Error(object message) { AsyncR("[ERROR] " + message); } public static void Success(object message) { AsyncG("[SUCCESS] " + message); } public static void Info(object message) { AsyncY("[INFO] " + message); } public static void Out(object message) { Async("[OUT] " + message); } public static void Debug(object message) { AsyncO("[DEBUG] " + message); } #endregion #region ToRemove public static void LogValueNotSpecified() { AsyncR(" must be specified"); } public static void LogObjectValueNotSpecified() { AsyncR(" and must be specified"); } public static void LogValueInvalid() { AsyncR(" is invalid"); } public static void LogObjectInvalid() { AsyncR(" is invalid"); } public static void LogValueNotInt32() { AsyncR(" must be a whole number (Int32)"); } [Obsolete("Parameter 'values' is no longer supported. Format before logging.")] private static void PrintLog(object message, bool disableLogging, params object[] values) { PrintLog(new LogInfo(message?.ToString())); } [Obsolete("Parameter 'values' is no longer supported. Format before logging.")] public static void Success(object message, params object[] values) { Success(message); } [Obsolete("Parameter 'values' is no longer supported. Format before logging.")] public static void Verbose(object message, params object[] values) { Out(message); } [Obsolete("Parameter 'values' is no longer supported. Format before logging.")] public static void Comment(object message, params object[] values) { AsyncC(message); } [Obsolete("Parameter 'values' is no longer supported. Format before logging.")] public static void Info(object message, params object[] values) { Info(message); } [Obsolete("Parameter 'values' is no longer supported. Format before logging.")] public static void Error(object message, params object[] values) { Error(message); } [Obsolete("Parameter 'values' is no longer supported. Format before logging.")] public static void Debug(object message, params object[] values) { Debug(message); } #endregion } /// /// A Logging class implementing the Singleton pattern and an internal Queue to be flushed perdiodically /// public class LogWriter { private static LogWriter _instance; private static ConcurrentQueue _logQueue; private static DateTime _lastFlushTime = DateTime.Now; private static StreamWriter _stream; /// /// Private to prevent creation of other instances /// private LogWriter() { } /// /// Exposes _instace and creates a new one if it is null /// internal static LogWriter Instance { get { if (_instance == null) { _instance = new LogWriter(); // Field cannot be used by anything else regardless, do not surround with lock { } // ReSharper disable once InconsistentlySynchronizedField _logQueue = new ConcurrentQueue(); Console.WriteLine(Constants.LogPath); // If the ErrorLogs dir doesn't exist StreamWriter will throw an exception. if (!Directory.Exists(Constants.LogDir)) { Directory.CreateDirectory(Constants.LogDir); } _stream = new StreamWriter(Constants.LogPath, false); Console.WriteLine("Created log instance"); } return _instance; } } /// /// Writes into the ConcurrentQueue the Message specified /// /// The message to write to the log public void WriteToLog(string message) { lock (_logQueue) { var logEntry = new LogInfo(message); _logQueue.Enqueue(logEntry); if (_logQueue.Any()) { FlushLog(); } } } /// /// Writes into the ConcurrentQueue the Entry specified /// /// The logEntry to write to the log public void WriteToLog(LogInfo logEntry) { lock (_logQueue) { _logQueue.Enqueue(logEntry); if (_logQueue.Any()) { FlushLog(); } } } /// /// Flushes the ConcurrentQueue to the log file specified in Constants /// private void FlushLog() { lock (_stream) { LogInfo entry; while (_logQueue.TryDequeue(out entry)) { string m = $"[{entry.LogTime}] {entry.Message}"; Console.ForegroundColor = entry.Colour; Console.WriteLine(m); Console.ForegroundColor = ConsoleColor.Gray; _stream.WriteLine(m); } _stream.Flush(); } } } /// /// A struct to store the message and the Date and Time the log entry was created /// public struct LogInfo { public string Message { get; set; } public string LogTime { get; set; } public string LogDate { get; set; } public ConsoleColor Colour { get; set; } public LogInfo(string message, ConsoleColor colour = ConsoleColor.Gray) { if (string.IsNullOrEmpty(message)) message = "[null]"; Message = message; LogDate = DateTime.Now.ToString("yyyy-MM-dd"); LogTime = DateTime.Now.ToString("hh:mm:ss.fff tt"); Colour = colour; } } }