' 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