diff --git a/dvmconsole/AudioConverter.cs b/dvmconsole/AudioConverter.cs index dea8d4c..d72575b 100644 --- a/dvmconsole/AudioConverter.cs +++ b/dvmconsole/AudioConverter.cs @@ -34,7 +34,7 @@ namespace dvmconsole if (audioData.Length != origLen) { - Trace.WriteLine($"Invalid PCM length: {audioData.Length}, expected: {origLen}"); + Log.WriteLine($"Invalid PCM length: {audioData.Length}, expected: {origLen}"); return chunks; } @@ -57,7 +57,7 @@ namespace dvmconsole { if (chunks.Count * expectedLength != origLen) { - Trace.WriteLine($"Invalid number of chunks: {chunks.Count}, expected total length: {origLen}"); + Log.WriteLine($"Invalid number of chunks: {chunks.Count}, expected total length: {origLen}"); return null; } diff --git a/dvmconsole/FneSystemBase.cs b/dvmconsole/FneSystemBase.cs index cb21bb2..f92896b 100644 --- a/dvmconsole/FneSystemBase.cs +++ b/dvmconsole/FneSystemBase.cs @@ -134,20 +134,20 @@ namespace dvmconsole switch (level) { case LogLevel.WARNING: - Trace.WriteLine(message); + Log.WriteWarning(message); break; case LogLevel.ERROR: - Trace.WriteLine(message); + Log.WriteError(message); break; case LogLevel.DEBUG: - Trace.WriteLine(message); + Log.WriteLine($"[DEBUG] {message}"); break; case LogLevel.FATAL: - Trace.WriteLine(message); + Log.WriteError(message); break; case LogLevel.INFO: default: - Trace.WriteLine(message); + Log.WriteLine(message); break; } }; diff --git a/dvmconsole/KeyStatusWindow.xaml.cs b/dvmconsole/KeyStatusWindow.xaml.cs index 951991c..88bd208 100644 --- a/dvmconsole/KeyStatusWindow.xaml.cs +++ b/dvmconsole/KeyStatusWindow.xaml.cs @@ -95,7 +95,7 @@ namespace dvmconsole { if (child == null) { - Trace.WriteLine("A child in ChannelsCanvas.Children is null."); + Log.WriteLine("A child in ChannelsCanvas.Children is null."); continue; } @@ -107,14 +107,14 @@ namespace dvmconsole Codeplug.System system = Codeplug.GetSystemForChannel(channelBox.ChannelName); if (system == null) { - Trace.WriteLine($"System not found for {channelBox.ChannelName}"); + Log.WriteLine($"System not found for {channelBox.ChannelName}"); continue; } Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channelBox.ChannelName); if (cpgChannel == null) { - Trace.WriteLine($"Channel not found for {channelBox.ChannelName}"); + Log.WriteLine($"Channel not found for {channelBox.ChannelName}"); continue; } @@ -123,7 +123,7 @@ namespace dvmconsole if (channelBox.Crypter == null) { - Trace.WriteLine($"Crypter is null for channel {channelBox.ChannelName}"); + Log.WriteLine($"Crypter is null for channel {channelBox.ChannelName}"); continue; } diff --git a/dvmconsole/Log.cs b/dvmconsole/Log.cs new file mode 100644 index 0000000..0d317b9 --- /dev/null +++ b/dvmconsole/Log.cs @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: AGPL-3.0-only +/** +* Digital Voice Modem - Desktop Dispatch Console +* AGPLv3 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Desktop Dispatch Console +* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0) +* +* Copyright (C) 2025 Bryan Biedenkapp, N2PLL +* +*/ + +using System.Collections; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; + +namespace dvmconsole +{ + /// + /// + /// + /// + public delegate void LogWriteLine(string message); + + /// + /// Implements the logger system. + /// + public class Log + { + public static FileStream logStream = null; + /// + /// Flag indicating logging should display on a console window. + /// + public static bool DisplayToConsole = false; + private static TextWriter tw; + private const string LOG_TIME_FORMAT = "MM/dd/yyyy HH:mm:ss"; + + private static ConsoleColor defColor = Console.ForegroundColor; + + /// + /// Flag indicating logging should stop. + /// + public static bool StopLogging = false; + + /* + ** Properties + */ + + /// + /// Gets or sets a delegate to also write logs to. + /// + public static LogWriteLine LogWriter { get; set; } = null; + + /* + ** Methods + */ + + /// + /// Sets up the trace logging. + /// + /// + /// + public static void SetupTextWriter(string directoryPath, string logFile) + { + // destroy existing log file + if (File.Exists(directoryPath + Path.DirectorySeparatorChar + logFile)) + File.Delete(directoryPath + Path.DirectorySeparatorChar + logFile); + + // open a new log stream + logStream = new FileStream(directoryPath + Path.DirectorySeparatorChar + logFile, FileMode.CreateNew); + tw = new StreamWriter(logStream); + } + + /// + /// Writes a log entry to the text log. + /// + /// + /// + public static void WriteLog(string message, bool noTimeStamp = false) + { + string logTime = DateTime.Now.ToString(LOG_TIME_FORMAT) + " "; + if (tw != null) + { + if (noTimeStamp) + tw.WriteLine(message); + else + tw.WriteLine(logTime + message); + tw.Flush(); + } + + if (noTimeStamp) + System.Diagnostics.Trace.WriteLine(message); + else + System.Diagnostics.Trace.WriteLine(logTime + message); + if (LogWriter != null) + LogWriter(message); + if (DisplayToConsole) + { + if (!noTimeStamp) + Console.Write(logTime); + + if (message.StartsWith("WARN")) + Console.ForegroundColor = ConsoleColor.Yellow; + else if (message.StartsWith("FAIL") || message.StartsWith("ERROR")) + Console.ForegroundColor = ConsoleColor.Red; + + Console.WriteLine(message); + + Console.ForegroundColor = defColor; + } + } + + /// + /// + /// + /// + /// + /// + /// + public static string GenericToString(T value, bool dynamic) + { + return GenericToString(value, dynamic, ", "); + } + + /// + /// + /// + /// + /// + /// + /// + /// + public static string GenericToString(T value, bool dynamic, string separator) + { + return GenericToString(dynamic ? value.GetType() : typeof(T), ref value, separator); + } + + /// + /// + /// + /// + /// + /// + /// + /// + private static string GenericToString(Type type, [In] ref T value, string separator) + { + var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + List strs = new List(); + foreach (var property in properties) + { + if (property.CanRead) + { + try { strs.Add(string.Format("{0} = {1}", property.Name, property.GetValue(value, null))); } + catch (InvalidOperationException) { } + } + } + return string.Format("{0} {{{1}}}", type.Name, string.Join(separator, strs.ToArray())); + } + + /// + /// Writes the exception stack trace to the console/trace log + /// + /// Exception to obtain information from + /// + public static void StackTrace(Exception throwable, bool reThrow = true) + { + StackTrace(string.Empty, throwable, reThrow); + } + + /// + /// Writes the exception stack trace to the console/trace log + /// + /// + /// Exception to obtain information from + /// + public static void StackTrace(string msg, Exception throwable, bool reThrow = true) + { + MethodBase mb = new System.Diagnostics.StackTrace().GetFrame(1).GetMethod(); + ParameterInfo[] param = mb.GetParameters(); + string funcParams = string.Empty; + for (int i = 0; i < param.Length; i++) + if (i < param.Length - 1) + funcParams += param[i].ParameterType.Name + ", "; + else + funcParams += param[i].ParameterType.Name; + + Exception inner = throwable.InnerException; + + WriteError("caught an unrecoverable exception! " + msg); + WriteLog("---- TRACE SNIP ----"); + WriteLog(throwable.Message + (inner != null ? " (Inner: " + inner.Message + ")" : "")); + WriteLog(throwable.GetType().ToString()); + + WriteLog("<" + mb.ReflectedType.Name + "::" + mb.Name + "(" + funcParams + ")>"); + WriteLog(throwable.Source); + foreach (string str in throwable.StackTrace.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)) + WriteLog(str); + if (inner != null) + foreach (string str in throwable.StackTrace.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)) + WriteLog("inner trace: " + str); + WriteLog("---- TRACE SNIP ----"); + + if (reThrow) + throw throwable; + } + + /// + /// Writes a error trace message w/ calling function information. + /// + /// Message to print + public static void WriteWarning(string message) + { + WriteLog("WARN: " + message); + } + + /// + /// Writes a error trace message w/ calling function information. + /// + /// Message to print + public static void WriteError(string message) + { + WriteLog("ERROR: " + message); + } + + /// + /// Writes a trace message w/ calling function information. + /// + /// Message to print to debug window + /// + /// + /// + /// + public static void WriteLine(string message, int frame = 1, bool dropToConsole = false, bool noTimeStamp = false) + { + string trace = string.Empty; + + MethodBase mb = new System.Diagnostics.StackTrace().GetFrame(frame).GetMethod(); + ParameterInfo[] param = mb.GetParameters(); + string funcParams = string.Empty; + for (int i = 0; i < param.Length; i++) + if (i < param.Length - 1) + funcParams += param[i].ParameterType.Name + ", "; + else + funcParams += param[i].ParameterType.Name; + + trace += "<" + mb.ReflectedType.Name + "::" + mb.Name + "(" + funcParams + ")> "; + trace += message; + + WriteLog(trace, noTimeStamp); + } + + /// + /// Helper to display the ASCII representation of a hex dump. + /// + /// + /// + /// + private static string DisplayHexChars(byte[] buffer, int offset) + { + int bCount = 0; + + string _out = string.Empty; + for (int i = offset; i < buffer.Length; i++) + { + // stop every 16 bytes... + if (bCount == 16) + break; + + byte b = buffer[i]; + char c = Convert.ToChar(b); + + // make control and illegal characters spaces + if (c >= 0x00 && c <= 0x1F) + c = ' '; + if (c >= 0x7F) + c = ' '; + + _out += c; + + bCount++; + } + + return _out; + } + + /// + /// Perform a hex dump of a buffer. + /// + /// + /// + /// + /// + /// + /// + public static void TraceHex(string message, byte[] buffer, int maxLength = 32, int startOffset = 0) + { + int bCount = 0, j = 0, lenCount = 0; + + // iterate through buffer printing all the stored bytes + string traceMsg = message + "\nDUMP " + j.ToString("X4") + ": "; + for (int i = startOffset; i < buffer.Length; i++) + { + byte b = buffer[i]; + + // split the message every 16 bytes... + if (bCount == 16) + { + traceMsg += "\t*" + DisplayHexChars(buffer, j) + "*"; + WriteLine(traceMsg, 2, false, true); + + bCount = 0; + j += 16; + traceMsg = "DUMP " + j.ToString("X4") + ": "; + } + else + traceMsg += (bCount > 0) ? " " : ""; + + traceMsg += b.ToString("X2"); + + bCount++; + + // increment the length counter, and check if we've exceeded the specified + // maximum, then break the loop + lenCount++; + if (lenCount > maxLength) + break; + } + + // if the byte count at this point is non-zero print the message + if (bCount != 0) + { + traceMsg += "\t*" + DisplayHexChars(buffer, j) + "*"; + WriteLine(traceMsg, 2, false, true); + } + } + } // public class Log +} // namespace dvmconsole diff --git a/dvmconsole/MainWindow.xaml.cs b/dvmconsole/MainWindow.xaml.cs index ab22dba..f1008ed 100644 --- a/dvmconsole/MainWindow.xaml.cs +++ b/dvmconsole/MainWindow.xaml.cs @@ -309,7 +309,7 @@ namespace dvmconsole // hook FNE events peer.peer.PeerConnected += (sender, response) => { - Trace.WriteLine("FNE Peer connected"); + Log.WriteLine("FNE Peer connected"); Dispatcher.Invoke(() => { EnableCommandControls(); @@ -320,7 +320,7 @@ namespace dvmconsole peer.peer.PeerDisconnected += (response) => { - Trace.WriteLine("FNE Peer disconnected"); + Log.WriteLine("FNE Peer disconnected"); Dispatcher.Invoke(() => { DisableCommandControls(); @@ -529,7 +529,7 @@ namespace dvmconsole Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); if (system == null) { - Trace.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); + Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -538,7 +538,7 @@ namespace dvmconsole Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); if (cpgChannel == null) { - Trace.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); + Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -547,7 +547,7 @@ namespace dvmconsole PeerSystem fne = fneSystemManager.GetFneSystem(system.Name); if (fne == null) { - Trace.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); + Log.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -777,7 +777,7 @@ namespace dvmconsole Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); if (system == null) { - Trace.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); + Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -786,7 +786,7 @@ namespace dvmconsole Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); if (cpgChannel == null) { - Trace.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); + Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -795,7 +795,7 @@ namespace dvmconsole PeerSystem fne = fneSystemManager.GetFneSystem(system.Name); if (fne == null) { - Trace.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); + Log.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -813,7 +813,7 @@ namespace dvmconsole if (chunk.Length == PCM_SAMPLES_LENGTH) P25EncodeAudioFrame(chunk, fne, channel, cpgChannel, system); else - Trace.WriteLine("bad sample length: " + chunk.Length); + Log.WriteLine("bad sample length: " + chunk.Length); } }); } @@ -1091,7 +1091,7 @@ namespace dvmconsole Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); if (system == null) { - Trace.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}."); + Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -1100,7 +1100,7 @@ namespace dvmconsole Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); if (cpgChannel == null) { - Trace.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}."); + Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -1704,7 +1704,7 @@ namespace dvmconsole Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); if (system == null) { - Trace.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}."); + Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -1713,7 +1713,7 @@ namespace dvmconsole Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); if (cpgChannel == null) { - Trace.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}."); + Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -1722,7 +1722,7 @@ namespace dvmconsole PeerSystem fne = fneSystemManager.GetFneSystem(system.Name); if (fne == null) { - Trace.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}."); + Log.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -1857,7 +1857,7 @@ namespace dvmconsole Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName); if (system == null) { - Trace.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}."); + Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -1866,7 +1866,7 @@ namespace dvmconsole Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName); if (cpgChannel == null) { - Trace.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}."); + Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}."); channel.IsSelected = false; selectedChannelsManager.RemoveSelectedChannel(channel); continue; @@ -1981,7 +1981,7 @@ namespace dvmconsole if (tone > 0) { MBEToneGenerator.IMBEEncodeSingleTone((ushort)tone, imbe); - Trace.WriteLine($"({system.Name}) P25D: {tone} HZ TONE DETECT"); + Log.WriteLine($"({system.Name}) P25D: {tone} HZ TONE DETECT"); } else { @@ -2112,7 +2112,7 @@ namespace dvmconsole else pktSeq = peer.pktSeq(); - //Trace.WriteLine($"({channel.SystemName}) P25D: Traffic *VOICE FRAME * PEER {handler.PeerId} SRC_ID {srcId} TGID {dstId} [STREAM ID {channel.txStreamId}]"); + Log.WriteLine($"({channel.SystemName}) P25D: Traffic *VOICE FRAME * PEER {handler.PeerId} SRC_ID {srcId} TGID {dstId} [STREAM ID {channel.TxStreamId}]"); byte[] payload = new byte[200]; handler.CreateNewP25MessageHdr((byte)P25DUID.LDU1, callData, ref payload, cpgChannel.GetAlgoId(), cpgChannel.GetKeyId(), channel.mi); @@ -2130,7 +2130,7 @@ namespace dvmconsole else pktSeq = peer.pktSeq(); - //Trace.WriteLine($"({channel.SystemName}) P25D: Traffic *VOICE FRAME * PEER {handler.PeerId} SRC_ID {srcId} TGID {dstId} [STREAM ID {channel.txStreamId}]"); + Log.WriteLine($"({channel.SystemName}) P25D: Traffic *VOICE FRAME * PEER {handler.PeerId} SRC_ID {srcId} TGID {dstId} [STREAM ID {channel.TxStreamId}]"); byte[] payload = new byte[200]; handler.CreateNewP25MessageHdr((byte)P25DUID.LDU2, callData, ref payload, cpgChannel.GetAlgoId(), cpgChannel.GetKeyId(), channel.mi); @@ -2214,9 +2214,7 @@ namespace dvmconsole if (samples != null) { - //Log.Logger.Debug($"({Config.Name}) P25D: Traffic *VOICE FRAME * PEER {e.PeerId} SRC_ID {e.SrcId} TGID {e.DstId} VC{n} ERRS {errs} [STREAM ID {e.StreamId}]"); - //Log.Logger.Debug($"IMBE {FneUtils.HexDump(imbe)}"); - //Trace.WriteLine($"SAMPLE BUFFER {FneUtils.HexDump(samples)}"); + //Log.WriteLine($"P25D: Traffic *VOICE FRAME * PEER {e.PeerId} SRC_ID {e.SrcId} TGID {e.DstId} VC{n} [STREAM ID {e.StreamId}]"); channel.VolumeMeterLevel = 0; @@ -2253,7 +2251,7 @@ namespace dvmconsole } catch (Exception ex) { - Trace.WriteLine($"Audio Decode Exception: {ex.Message}"); + Log.WriteLine($"Audio Decode Exception: {ex.Message}"); } } @@ -2263,21 +2261,21 @@ namespace dvmconsole /// public void KeyResponseReceived(KeyResponseEvent e) { - //Trace.WriteLine($"Message ID: {e.KmmKey.MessageId}"); - //Trace.WriteLine($"Decrypt Info Format: {e.KmmKey.DecryptInfoFmt}"); - //Trace.WriteLine($"Algorithm ID: {e.KmmKey.AlgId}"); - //Trace.WriteLine($"Key ID: {e.KmmKey.KeyId}"); - //Trace.WriteLine($"Keyset ID: {e.KmmKey.KeysetItem.KeysetId}"); - //Trace.WriteLine($"Keyset Alg ID: {e.KmmKey.KeysetItem.AlgId}"); - //Trace.WriteLine($"Keyset Key Length: {e.KmmKey.KeysetItem.KeyLength}"); - //Trace.WriteLine($"Number of Keys: {e.KmmKey.KeysetItem.Keys.Count}"); + //Log.WriteLine($"Message ID: {e.KmmKey.MessageId}"); + //Log.WriteLine($"Decrypt Info Format: {e.KmmKey.DecryptInfoFmt}"); + //Log.WriteLine($"Algorithm ID: {e.KmmKey.AlgId}"); + //Log.WriteLine($"Key ID: {e.KmmKey.KeyId}"); + //Log.WriteLine($"Keyset ID: {e.KmmKey.KeysetItem.KeysetId}"); + //Log.WriteLine($"Keyset Alg ID: {e.KmmKey.KeysetItem.AlgId}"); + //Log.WriteLine($"Keyset Key Length: {e.KmmKey.KeysetItem.KeyLength}"); + //Log.WriteLine($"Number of Keys: {e.KmmKey.KeysetItem.Keys.Count}"); foreach (var key in e.KmmKey.KeysetItem.Keys) { - //Trace.WriteLine($" Key Format: {key.KeyFormat}"); - //Trace.WriteLine($" SLN: {key.Sln}"); - //Trace.WriteLine($" Key ID: {key.KeyId}"); - //Trace.WriteLine($" Key Data: {BitConverter.ToString(key.GetKey())}"); + //Log.WriteLine($" Key Format: {key.KeyFormat}"); + //Log.WriteLine($" SLN: {key.Sln}"); + //Log.WriteLine($" Key ID: {key.KeyId}"); + //Log.WriteLine($" Key Data: {BitConverter.ToString(key.GetKey())}"); Dispatcher.Invoke(() => { @@ -2370,7 +2368,7 @@ namespace dvmconsole { channel.IsReceiving = true; slot.RxStart = pktTime; - Trace.WriteLine($"({system.Name}) P25D: Traffic *CALL START * PEER {e.PeerId} SRC_ID {e.SrcId} TGID {e.DstId} [STREAM ID {e.StreamId}]"); + Log.WriteLine($"({system.Name}) P25D: Traffic *CALL START * PEER {e.PeerId} SRC_ID {e.SrcId} TGID {e.DstId} [STREAM ID {e.StreamId}]"); FneUtils.Memset(channel.mi, 0x00, P25Defines.P25_MI_LENGTH); @@ -2401,7 +2399,7 @@ namespace dvmconsole { channel.IsReceiving = false; TimeSpan callDuration = pktTime - slot.RxStart; - Trace.WriteLine($"({system.Name}) P25D: Traffic *CALL END * PEER {e.PeerId} SRC_ID {e.SrcId} TGID {e.DstId} DUR {callDuration} [STREAM ID {e.StreamId}]"); + Log.WriteLine($"({system.Name}) P25D: Traffic *CALL END * PEER {e.PeerId} SRC_ID {e.SrcId} TGID {e.DstId} DUR {callDuration} [STREAM ID {e.StreamId}]"); channel.Background = ChannelBox.BLUE_GRADIENT; channel.VolumeMeterLevel = 0; callHistoryWindow.ChannelUnkeyed(cpgChannel.Name, (int)e.SrcId); diff --git a/dvmconsole/SettingsManager.cs b/dvmconsole/SettingsManager.cs index c26febd..fb9d571 100644 --- a/dvmconsole/SettingsManager.cs +++ b/dvmconsole/SettingsManager.cs @@ -14,7 +14,6 @@ using System.Diagnostics; using System.IO; - using Newtonsoft.Json; namespace dvmconsole @@ -118,6 +117,11 @@ namespace dvmconsole /// public string UserBackgroundImage { get; set; } = null; + /// + /// Flag enabling trace logging. + /// + public bool SaveTraceLog { get; set; } + /* ** Methods */ @@ -173,6 +177,14 @@ namespace dvmconsole UserBackgroundImage = loadedSettings.UserBackgroundImage; + SaveTraceLog = loadedSettings.SaveTraceLog; + if (SaveTraceLog) + Log.SetupTextWriter(Environment.CurrentDirectory, "dvmconsole.log"); + + Log.WriteLine("Digital Voice Modem - Desktop Dispatch Console"); + Log.WriteLine("Copyright (c) 2025 DVMProject (https://github.com/dvmproject) Authors."); + Log.WriteLine(">> Desktop Dispatch Console"); + return true; } @@ -180,7 +192,8 @@ namespace dvmconsole } catch (Exception ex) { - Trace.WriteLine($"Error loading settings: {ex.Message}"); + Log.WriteLine($"Error loading settings: {ex.Message}"); + Log.StackTrace(ex, false); return false; } } @@ -200,7 +213,8 @@ namespace dvmconsole } catch (Exception ex) { - Trace.WriteLine($"Error saving settings: {ex.Message}"); + Log.WriteLine($"Error saving settings: {ex.Message}"); + Log.StackTrace(ex, false); } }