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);
}
}