Introduction
Debugging your way through code can be boring and tiring. When you're hunting down a bug, it helps if you can zero right in on the problem. By adding a simple trace file to your app, you can pinpoint the exact sub or function where things went wrong.
With a bit more work, you can configure your app to turn on or turn off writing to the trace file as needed. You can also start with a fresh trace file every time your app is loaded.
With the trace mechanism in place, you can run your app until things go wrong. Then by examining the trace file, you know just where to turn in your code.
This simple approach uses .NET's StackTrace
feature to keep track of where users have been as they work with your app.
Discussion
StackTrace
is part of the System.Diagnostics
namespace, so you will want to include a reference to that in your project with an Imports
statement at the beginning of your class or module.
StackTrace
is a pile of StackFrames
. Each StackFrame
holds information about each function or sub as your application accesses it. Every time a function or sub is accessed, information in the form of a StackFrame
about it is pushed onto the top of the StackTrace.
This information is available as a string, which we can easily parse to extract just the information we want.
There are two parts to using this mechanism: writing a short subroutine to write material to the trace file, and adding a line to each sub or function calling that subroutine. Let's look at the subroutine first. I've simplified the code somewhat to make it easier to follow, and I use some constants that I've defined elsewhere; their values should be obvious.
Friend Sub Tracer()
Dim texttoadd As String
Dim logtext() As String
Dim fileline() As String
Dim fs As StreamWriter
Dim strace As New StackTrace(True)
Try
If Not File.Exists(TRACE_LOG) Then
fs = File.CreateText(TRACE_LOG)
fs.Write("Trace Log " & Format(Now) & CR & CR)
fs.Flush()
fs.Close()
End If
logtext = strace.GetFrame(1).ToString.Split(SPACE)
fileline = logtext(6).Split(BACKSLASH)
Dim i As Integer = fileline.GetUpperBound(0)
texttoadd = logtext(0) & COLON & SPACE & _
fileline(i).Substring(0, fileline(i).Length - 2)
fs = File.AppendText(TRACE_LOG)
fs.WriteLine(texttoadd)
fs.Flush()
fs.Close()
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
The StreamWriter
is the mechanism used to write the information to the log file. If you are unfamiliar with writing streams, check out the .NET documentation on the topic.
The StackTrace
is created using the optional True
parameter to indicate that we want the trace to include information about the file the function or subroutine belongs to. The nice thing about the StackTrace
is that it holds information about everything that has already happened in the application. In fact, the information we want is not even on the top of the stack--it's one item down, because the last item pushed onto the stack is the call to our Tracer()
routine. That's why when we capture the StackFrame
we want, we have to get the second item (in a zero-based array).
We can confidently split the StackFrame
to an array because these entries always have the same format. The first element in the array will contain the name of the sub or function we want to capture. The last element will have the name of the source file as well as the line number and offset. A typical logtext
array will look something like this:
(0): "myFunction"
(1): "at"
(2): "offset"
(3): "87"
(4): "in"
(5): "file:line:column"
(6): "C:\ProgramFiles\VS\MyProject\MyModule.vb:12345:67
We can then confidently split the last element of logtext
into an array, using the backslash as the delimiter. We only care about the last element of that array. Note, though, that the last element actually ends with a line-end character, so we must parse that away before we write to our trace file. (If you don't parse it away, your trace file will end up double-spaced, which you might prefer.)
The second part of the setup is easy: Just add a call to Tracer()
in every function or sub you want to include in your trace file:
Private Sub DoSomething(somethingToDo as String)
Tracer()
somethingToDo = somethingToDo & somethingElse
...
End Sub
To turn the trace on and off at will, you can add a global Boolean variable to your code, and then examine the state of that variable at run-time to decide if you are tracing or not. The easiest way to do this is to pass a command-line argument:
Class MyMainForm
Public traceIt as Boolean = False
Public Sub Main()
If Environment.CommandLine.IndexOf("/t")> 0 then traceIt = True
...
End Sub
Private Sub DoSomething(somethingToDo as String)
If traceIt Then Tracer()
somethingToDo = somethingToDo & somethingElse
...
End Sub
Finally, to start a new trace file every time you start your app, add a line to the main program block to delete the existing file:
If File.Exists(TRACE_LOG) then File.Delete(TRACE_LOG)
You can apply the same technique in a Try/Catch block as well, and incorporate the exception in the trace file. But I'll leave that for you to play with.