' by Daniel Moth (http://www.danielmoth.com/Blog/) Option Explicit On Option Strict On Imports System.Diagnostics ' Define in yor project properties FULL_FRAME for non-CF projects '#Const FULL_FRAME = True ' USAGE 'MyTrace.Info("ClassName: MethodName", "Diagnostic msg") 'MyTrace.InfoIf(SomeCondition, "ClassName: MethodName", "Diagnostic msg") 'MyTrace.Warning("ClassName: MethodName", "Diagnostic msg") 'MyTrace.WarningIf(SomeCondition, "ClassName: MethodName", "Diagnostic msg") 'MyTrace.Assert(SomeCondition, "ClassName: MethodName", "Diagnostic msg") 'MyTrace.LogError("SomeMessage") 'MyTrace.LogError(SomeException) ' Optionally, use this in your desktop config client for extra control on debug builds ' ' ' ' ' ' ' ' ' ' ' Namespace SomeNamespace Public NotInheritable Class MyTrace ' by Daniel Moth (http://www.danielmoth.com/Blog/) Private Shared ReadOnly INFO_LEVEL As String = "INFO," Private Shared ReadOnly WARNING_LEVEL As String = "WARNING," Private Shared ReadOnly ASSERT_LEVEL As String = "ASSERT," Private Shared ReadOnly ERROR_LEVEL As String = "ERROR," #If FULL_FRAME = True Then Private Shared ReadOnly FILE_EXT As String = ".log" #Else Private Shared ReadOnly FILE_EXT As String = ".txt" #End If Private Shared ReadOnly CommaString As String = "," Public Shared MySwitch As TraceSwitch Shared Sub New() ' Assign from file assuming its there MySwitch = New TraceSwitch("MyTraceSwitch", "Set the level of tracing") Dim logPath As String #If FULL_FRAME = True Then ' For CF the switch is always Info thus controlled by build type ' Now check if it wasn't there in order to assign default value Dim o As Object = Configuration.ConfigurationSettings.GetConfig("system.diagnostics") If o Is Nothing OrElse _ CType(o, System.Collections.Hashtable).Item("switches") Is Nothing OrElse _ DirectCast(DirectCast(o, System.Collections.Hashtable).Item("switches"), System.Collections.Hashtable).Item("MyTraceSwitch") Is Nothing Then System.Diagnostics.Debug.WriteLine("Could not find switch in config file!") System.Diagnostics.Trace.WriteLine("Could not find switch in config file!") ' default level for desktop is Warning MySwitch.Level = TraceLevel.Warning End If ' COM Interop logging logPath = AppDomain.CurrentDomain.FriendlyName If logPath = "DefaultDomain" Then 'COM Interop client logPath = "\ComInteropTrace" ' some default ' TODO read from path the COM Interop log path End If #Else logPath = "\CFTrace" 'TODO read from file log path for CF too #End If ' Modify file name by appending number if necessary MyTrace.ReNumberFile(logPath, FILE_EXT) ' Create listener Dim fs As New IO.FileStream(logPath & FILE_EXT, IO.FileMode.Append, IO.FileAccess.Write, IO.FileShare.Read) Dim sw As New IO.StreamWriter(fs) sw.AutoFlush = True Dim l As New TextWriterTraceListener(sw) ' Add listener Trace.Listeners.Add(l) End Sub _ Public Shared Sub Info(ByVal aSource As String, ByVal aMessage As String) If MySwitch.TraceInfo = True Then Trace.WriteLine(ComposeMessage(aSource, aMessage, INFO_LEVEL)) End If End Sub _ Public Shared Sub InfoIf(ByVal aCondition As Boolean, ByVal aSource As String, ByVal aMessage As String) If MySwitch.TraceInfo = True AndAlso aCondition = True Then Trace.WriteLine(ComposeMessage(aSource, aMessage, INFO_LEVEL)) End If End Sub _ Public Shared Sub Warning(ByVal aSource As String, ByVal aMessage As String) If MySwitch.TraceWarning = True Then Trace.WriteLine(ComposeMessage(aSource, aMessage, WARNING_LEVEL)) End If End Sub _ Public Shared Sub WarningIf(ByVal aCondition As Boolean, ByVal aSource As String, ByVal aMessage As String) If MySwitch.TraceWarning = True AndAlso aCondition = True Then Trace.WriteLine(ComposeMessage(aSource, aMessage, WARNING_LEVEL)) End If End Sub _ Public Shared Sub Assert(ByVal aCondition As Boolean, ByVal aSource As String, ByVal aMessage As String) If MySwitch.TraceWarning = True AndAlso aCondition = False Then Trace.WriteLine(ComposeMessage(aSource, aMessage, ASSERT_LEVEL)) End If System.Diagnostics.Debug.Assert(aCondition = True, aSource, aMessage) End Sub Public Shared Sub LogError(ByVal aMessage As String) If MySwitch.TraceError = True Then #If FULL_FRAME = True Then Trace.WriteLine(ComposeMessage(String.Empty, aMessage & CommaString & Environment.StackTrace, ERROR_LEVEL)) #Else Trace.WriteLine(ComposeMessage(String.Empty, aMessage, ERROR_LEVEL)) #End If End If End Sub Public Shared Sub LogError(ByVal aException As Exception) If MySwitch.TraceError = True Then #If FULL_FRAME = True Then Dim loMessage As String = Environment.OSVersion.ToString() & CommaString & _ Environment.MachineName & CommaString & "Thread ID: " & AppDomain.GetCurrentThreadId() _ & Microsoft.VisualBasic.ControlChars.CrLf & aException.ToString() #Else Dim loMessage As String = Environment.OSVersion.ToString() & Microsoft.VisualBasic.ControlChars.CrLf & aException.ToString() #End If Trace.WriteLine(ComposeMessage(String.Empty, loMessage, ERROR_LEVEL)) End If End Sub Private Shared Function ComposeMessage(ByVal aSource As String, ByVal aMessage As String, ByVal aLevel As String) As String Return DateTime.Now.ToString("G") & ":" & Environment.TickCount & CommaString & aLevel & aSource & CommaString & aMessage End Function Private Shared Sub ReNumberFile(ByRef aTheFilePath As String, ByVal aExt As String) Dim bakLog As String = aTheFilePath ' Determine if and what number to append Dim i As Int32 While IO.File.Exists(bakLog & aExt) = True i += 1 bakLog = aTheFilePath & i 'MsgBox(bakLog) If i = 3 Then 'So we maintain 4 files bakLog = RecycleFile(aTheFilePath, aExt) End If End While ' Update file name with integer (potentially) aTheFilePath = bakLog End Sub ' Only called from ReNumberFile Private Shared Function RecycleFile(ByVal aFilePath As String, ByVal aExt As String) As String Dim bakLog As String = aFilePath Dim i As Int32 While IO.File.Exists(bakLog & aExt) = True Try IO.File.Delete(bakLog & aExt) Return bakLog 'If here cause of iteration: means that current file might not be last or first Catch ' only if more than 1 process tries to use the same file name End Try i += 1 bakLog = aFilePath & i End While Return bakLog ' goes over the numeric limit set out by shared constructor! End Function End Class #If FULL_FRAME = False Then ' Some Diagnostics classes are not available on CF so just provide simple implementations here Public Enum TraceLevel Off = 0 [Error] = 1 Warning = 2 Info = 3 Verbose = 4 End Enum Public NotInheritable Class TraceSwitch Private mLevel As TraceLevel Public Sub New(ByVal aDisplayName As String, ByVal aDescription As String) ' Left it as Info BECAUSE it is determined by the calling assemblies config options ' The switch level is just a further control mechanism mLevel = TraceLevel.Info End Sub Public Property Level() As TraceLevel Get Return mLevel End Get Set(ByVal Value As TraceLevel) If Value < 0 Then mLevel = TraceLevel.Off ElseIf Value > 4 Then mLevel = TraceLevel.Verbose Else mLevel = Value End If End Set End Property Public ReadOnly Property TraceInfo() As Boolean Get Return Not (mLevel < 3) End Get End Property Public ReadOnly Property TraceWarning() As Boolean Get Return Not (mLevel < 2) End Get End Property Public ReadOnly Property TraceError() As Boolean Get Return Not (mLevel < 1) End Get End Property Public ReadOnly Property TraceVerbose() As Boolean Get Return (mLevel = 4) End Get End Property End Class Friend NotInheritable Class Trace Friend Shared Listeners As System.Collections.ArrayList Private Shared mMainWriter As TextWriterTraceListener Shared Sub New() Listeners = New System.Collections.ArrayList(1) End Sub Friend Shared Sub WriteLine(ByVal aMessage As String) ' For performance's sake and given that we know there will always be 1 and only 1 object in Listeners and ' and we know it will be a TextWriter System.Diagnostics.Debug.WriteLine(aMessage) ' as would happen for desktop automatically since the Listeners collection is shared GetWriter.WriteLine(aMessage) Win32Api.NKDbgPrintfW(aMessage & Microsoft.VisualBasic.ControlChars.CrLf) 'hyperterminal catches this over RS232 if the CE OS on the target is a debug one '' Could do following plus change the obj Type 'Dim obj As TextWriterTraceListener 'For Each obj In Listeners ' obj.WriteLine(aMessage) 'Next obj End Sub Private Shared Function GetWriter() As TextWriterTraceListener If mMainWriter Is Nothing Then mMainWriter = DirectCast(Listeners.Item(0), TextWriterTraceListener) End If Return mMainWriter End Function Friend Shared Sub Assert(ByVal aCond As Boolean, ByVal aMsg As String, ByVal aDetailMsg As String) System.Diagnostics.Debug.Assert(aCond, aMsg, aDetailMsg) End Sub End Class Friend NotInheritable Class TextWriterTraceListener Private mWriter As IO.TextWriter Sub New(ByVal aWriter As IO.TextWriter) mWriter = aWriter End Sub Friend Sub WriteLine(ByVal aMessage As String) Threading.Monitor.Enter(Me) Try mWriter.WriteLine(aMessage) Catch ex As InvalidOperationException System.Diagnostics.Debug.Assert(False) 'Don't use MyTrace here obviously! Catch ex As ObjectDisposedException System.Diagnostics.Debug.Assert(False) 'Don't use MyTrace here obviously! Finally Threading.Monitor.Exit(Me) End Try End Sub End Class #End If End Namespace