Files
DP44/Common/DTS.Common.Utilities/APILogging.cs

431 lines
16 KiB
C#
Raw Normal View History

2026-04-17 14:55:32 -04:00
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
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
{
/// <summary>
/// this class encapsulating writing to the log
///
/// </summary>
public static class APILogger
{
private static readonly object ProcessLock = new object();
private static readonly Dictionary<string, Stack<Stopwatch>> _processes = new Dictionary<string, Stack<Stopwatch>>();
public static int LogLevel { get; set; } = 3;
public static void StartProcess(string name)
{
lock (ProcessLock)
{
if (!_processes.ContainsKey(name)) { _processes[name] = new Stack<Stopwatch>(); }
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();
}
/// <summary>
/// describes an event that should be raised when there's an error that needs to be handled by an application
/// </summary>
public delegate void ErrorRaisedEventDelegate(string msg);
private static ErrorRaisedEventDelegate _OnErrorRaised;
/// <summary>
/// Error handler called when errors are raised
/// </summary>
public static ErrorRaisedEventDelegate OnErrorRaised
{
get => _OnErrorRaised;
set => _OnErrorRaised = value;
}
/// <summary>
/// raises an error, if an error handler is set, calls error handler
/// </summary>
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
}
}
}
/// <summary>
/// a function capable of logging a message
/// </summary>
/// <param name="msg">message to log</param>
public delegate void Sink(string msg);
/// <summary>
/// a consumer of log messages
/// </summary>
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 "";
}
/// <summary>
/// log an exception
/// </summary>
/// <param name="ex">exception to be logged</param>
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();
}
/// <summary>
/// logs a string
/// </summary>
/// <param name="str">string to be logged</param>
public static void LogString(string str)
{
Writer?.Invoke(LogString(str, true, true));
}
private static readonly object PhaseShiftLogLock = new object();
/// <summary>
/// logs a phase shift into a table
/// </summary>
/// <param name="serial"></param>
/// <param name="msPhaseShift">MICROSECONDS of shift</param>
/// <param name="originalT0"></param>
/// <param name="modifiedTo"></param>
/// <param name="samples"></param>
/// <param name="rule"></param>
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);
}
}
private const int LOG_LEVEL_DEBUG = 2;
private static void LogEx(int logLevel, params object[] paramList)
{
if (logLevel >= LogLevel)
{
var newparams = new object[paramList.Length + 1];
Array.Copy(new[] { $"LOG_LEVEL_{logLevel}" }, 0, newparams, 0, 1);
Array.Copy(paramList, 0, newparams, 1, paramList.Length);
Log(newparams);
}
}
public static void DebugLog(params object[] paramList)
{
LogEx(LOG_LEVEL_DEBUG, paramList);
}
/// <summary>
/// logs multiple parameters
/// </summary>
/// <param name="paramlist">
/// collection of parameters, can be exceptions, strings, or an object with tostring
/// </param>
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}"; }
else if (obj is byte[])
{
s = BitConverter.ToString((byte[])obj).Replace("-", "");
}
else if (obj is object[] objArray)
{
s = string.Join(", ", objArray);
}
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();
Writer(msg);
}
catch (Exception ex)
{
//if we fail to log there's not much more we can do ...
Trace.WriteLine(ex);
}
}
/// <summary>
/// logs multiple parameters
/// </summary>
/// <param name="paramlist">
/// collection of parameters, can be exceptions, strings, or an object with tostring
/// </param>
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);
}
}
}
}