using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Text; using System.Windows.Forms; using DTS.Common.Utilities.Properties; // ReSharper disable once CheckNamespace namespace DTS.Common.Utilities.Logging { /// /// this class encapsulating writing to the log /// /// public static class APILogger { private static readonly object ProcessLock = new object(); private static readonly Dictionary> _processes = new Dictionary>(); public static void StartProcess(string name) { lock (ProcessLock) { if (!_processes.ContainsKey(name)) { _processes[name] = new Stack(); } var sw = new Stopwatch(); sw.Start(); _processes[name].Push(sw); Log($"Starting process: [{name}] ({_processes[name].Count})"); } } public static void StopProcess(string name) { lock (ProcessLock) { if (!_processes.ContainsKey(name) || 0 == _processes[name].Count) { Log($"Stopping process: [{name}] (0)"); return; } var sw = _processes[name].Pop(); sw.Stop(); var ts = new TimeSpan(sw.ElapsedTicks); Log($"Stopping process: [{name}], {TimespanToHumanReadable(ts)}"); } } private static string TimespanToHumanReadable(TimeSpan ts) { var sb = new StringBuilder(); if (ts.TotalDays > 1) { sb.Append($"{System.Math.Floor(ts.TotalDays):0} days, "); } sb.Append($"{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds}.{ts.Milliseconds:000}"); return sb.ToString(); } /// /// describes an event that should be raised when there's an error that needs to be handled by an application /// public delegate void ErrorRaisedEventDelegate(string msg); private static ErrorRaisedEventDelegate _OnErrorRaised; /// /// Error handler called when errors are raised /// public static ErrorRaisedEventDelegate OnErrorRaised { get => _OnErrorRaised; set => _OnErrorRaised = value; } /// /// raises an error, if an error handler is set, calls error handler /// public static void RaiseError(string error) { OnErrorRaised?.Invoke(error); } [MethodImpl(MethodImplOptions.NoInlining)] public static string GetCurrentMethod() { var st = new StackTrace(); var sf = st.GetFrame(1); return sf.GetMethod().Name; } private static bool _bDoPerformanceLog; public static void SetDoPerformanceLog(bool bDoLog) { _bDoPerformanceLog = bDoLog; } private static readonly object MyLock = new object(); public static void DoPerformanceLog(string msg) { if (!_bDoPerformanceLog) { return; } var dt = DateTime.Now; var s = string.Format("{0:0000}-{1:00}-{2:00} {3:00}:{4:00}:{5:00}.{6:0000} ***{7}\r\n", dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, dt.Millisecond, msg); lock (MyLock) { try { File.AppendAllText("PERFLOG.LOG", s); } catch (Exception) { // ignored - performance logging is only for statistics, we fail then we fail, just keep going } } } /// /// a function capable of logging a message /// /// message to log public delegate void Sink(string msg); /// /// a consumer of log messages /// public static Sink Writer; public static Sink ConfigurationLogWriter; public static Sink ConfigReadErrorLogWriter; public static Sink StateWriter; private static string LogException(Exception ex, bool bPrepend, bool bNewLine) { if (Writer == null) { return ""; } try { if (ex == null) { Writer("LogException: called with null exception"); } else { var sb = new StringBuilder(); if (bPrepend) { sb.Append(string.Concat(DateTime.Now.ToString(Resources.APILogging_DateTime_Format), " ")); } ExceptionFormater(ref sb, ex, 0); sb = sb.Replace(Environment.NewLine, " "); if (bNewLine) { sb.AppendLine(); } return sb.ToString(); } } catch (Exception ownEx) { // and to be really paranoid... string orgMsg; if (ex == null) { orgMsg = "null exception"; } else if (string.IsNullOrEmpty(ex.Message)) { orgMsg = "blank exception message"; } else { orgMsg = ex.Message; } try { Writer("APILogger: LogException threw an expection: " + ownEx.Message + " when handling: " + orgMsg); } catch (Exception) { } } return ""; } /// /// log an exception /// /// exception to be logged public static void LogException(Exception ex) { Writer(LogException(ex, true, true)); } public const string EXCEPTIONSTART_STRING = "!! "; private static void ExceptionFormater(ref StringBuilder sb, Exception ex, int level) { while (true) { if (ex == null) return; var front = new string(' ', level); sb.Append(EXCEPTIONSTART_STRING); sb.AppendFormat(Resources.APILogging_ExceptionFormatter_ExceptionOfTypeOccurredString, front, level > 0 ? Resources.APILogging_ExceptionFormatter_InnerIndicationString : Resources.APILogging_ExceptionFormatter_NonInnerIndicationString, ex.GetType()); if (null != ex.TargetSite) { sb.AppendFormat(Resources.APILogging_ModuleNameDisplayString, front, NullGuard(ex.TargetSite.Module.Name), NullGuard(ex.TargetSite.Name)); } sb.AppendFormat(Resources.APILogging_ExceptionMessageDisplayString, front, NullGuard(ex.Message)); if (!string.IsNullOrEmpty(ex.StackTrace)) { // RW: It appears .NET 4.5 has a subtle difference in stack formatting. I don't see it, but it's breaking the Regex. // I'm not sure why it's using regex in the first place. This should work for both, but has not been tested with .NET 2.0 string[] delimeters = { Environment.NewLine }; var lines = ex.StackTrace.Split(delimeters, 2, StringSplitOptions.None); if (lines.Length > 0) { sb.AppendFormat(Resources.APILogging_StackTraceDisplayString, front, NullGuard(lines[0])); if (lines.Length > 1) { sb.AppendFormat(Resources.APILogging_StackTraceDisplayString, front, NullGuard(lines[1])); } } } // now call ourself with inner ex = ex.InnerException; level = level + 1; } } private static string NullGuard(string s) { return s ?? Resources.Generic_NullIndicatorString; } private static readonly string DATE_FORMAT = Resources.APILogging_DateTime_Format; private static string LogString(string str, bool bPrepend, bool bNewLine, bool bReplaceNewLines = true) { if (Writer == null) throw new Exception(Resources.APILogging_NullWriterDelegateString); var sb = new StringBuilder(); if (bPrepend) { sb.Append($"{DateTime.Now.ToString(DATE_FORMAT)} "); } if (bReplaceNewLines) { sb.Append(str.Replace(Environment.NewLine, " ")); } else { sb.Append(str); } if (bNewLine) { sb.AppendLine(); } return sb.ToString(); } /// /// logs a string /// /// string to be logged public static void LogString(string str) { Writer?.Invoke(LogString(str, true, true)); } private static readonly object PhaseShiftLogLock = new object(); /// /// logs a phase shift into a table /// /// /// MICROSECONDS of shift /// /// /// /// public static void LogPhaseShift(string serial, double msPhaseShift, ulong originalT0, ulong modifiedTo, ulong samples, string rule) { try { var now = DateTime.Now; var s = string.Format("{0:0000}-{1:00}-{2:00} {3:00}:{4:00}:{5:00}.{6:0000}\t{7}\t{8}\t{9}\t{10}\t{11}\t{12}\r\n", now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Millisecond, serial, samples, msPhaseShift, originalT0, modifiedTo, rule ); lock (PhaseShiftLogLock) { File.WriteAllLines("PHASE_SHIFT_LOG.txt", new[] { s }); } } catch (Exception ex) { Log(ex); } } public static void StateLog(params object[] paramlist) { try { var sb = new StringBuilder(); for (var i = 0; i < paramlist.Length; i++) { if (i > 0) { sb.Append(" "); } var s = paramlist[i] as string; if (paramlist[i] is DialogResult) { s = string.Format(" User selected {0}", ((DialogResult)paramlist[i])); } var ex = paramlist[i] as Exception; if (null == s && null == ex && null != paramlist[i]) { s = paramlist[i].ToString(); } var bPrepend = 0 == i; var bNewLine = paramlist.Length == i + 1; if (null != ex) { sb.Append(LogException(ex, bPrepend, bNewLine)); } else if (null != s) { sb.Append(LogString(s, bPrepend, bNewLine, false)); } } var msg = sb.ToString(); StateWriter(msg); } catch (Exception ex) { //if we fail to log there's not much more we can do ... Trace.WriteLine(ex); } } /// /// logs multiple parameters /// /// /// collection of parameters, can be exceptions, strings, or an object with tostring /// public static void Log(params object[] paramlist) { try { var sb = new StringBuilder(); for (var i = 0; i < paramlist.Length; i++) { var obj = paramlist[i]; if (i > 0) { sb.Append(" "); } var s = obj as string; if (obj is DialogResult) { s = $" User selected {(DialogResult)obj}"; } if (obj is byte[]) { s = BitConverter.ToString((byte[])obj).Replace("-", ""); } var ex = obj as Exception; if (null == s && null == ex && null != obj) { s = obj.ToString(); } var bPrepend = 0 == i; var bNewLine = paramlist.Length == i + 1; if (null != ex) { sb.Append(LogException(ex, bPrepend, bNewLine)); } else if (null != s) { sb.Append(LogString(s, bPrepend, bNewLine)); } } var msg = sb.ToString(); //KeywordAlert.Instance.ProcessMsg(msg); Writer(msg); } catch (Exception ex) { //if we fail to log there's not much more we can do ... Trace.WriteLine(ex); } } /// /// logs multiple parameters /// /// /// collection of parameters, can be exceptions, strings, or an object with tostring /// public static void ConfLog(params object[] paramlist) { try { var sb = new StringBuilder(); for (var i = 0; i < paramlist.Length; i++) { if (i > 0) { sb.Append(" "); } var s = paramlist[i] as string; if (paramlist[i] is DialogResult) { s = $" User selected {(DialogResult)paramlist[i]}"; } var ex = paramlist[i] as Exception; if (null == s && null == ex && null != paramlist[i]) { s = paramlist[i].ToString(); } var bPrepend = 0 == i; var bNewLine = paramlist.Length == i + 1; if (null != ex) { sb.Append(LogException(ex, bPrepend, bNewLine)); } else if (null != s) { sb.Append(LogString(s, bPrepend, bNewLine)); } } var msg = sb.ToString(); ConfigurationLogWriter(msg); } catch (Exception ex) { //as with the other log function, if we fail we don't have much backing we can do Trace.WriteLine(ex); } } } }