Click here to Skip to main content
Click here to Skip to main content

World's easiest Trace function

By , 3 Mar 2005
 

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

terpy
Software Developer
United States United States
Member
Terpy is a consultant reluctantly based in Olympia, Wash. She'd much rather be back in Maine, where the ocean is on the correct side of the road. She is largely a self-taught programmer who clings jealously to her bad habits. Handbells, anyone?

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralGreat Idea [modified]memberEradikator13 Feb '07 - 21:51 
This is exactly that what i searched for.
 
Many Thanks to you.
 
Peer Royla from Germany
 
Edit: I Prefer to get the Class of the Funktion in stead the Filename, someone has an Idea?
 
2nd Edit:
Somewhat easier ->
Imports System.Diagnostics
Public Class StackTraceTest
Public Function FU_GetSUBFUCLASS(ByRef Trans_SUBFU As String, ByRef Trans_CLASS As String, ByRef Trans_Temp As Integer) As Boolean

Dim strace As New StackTrace(True)
Dim sFrame As New StackFrame

Trans_CLASS = strace.GetFrame(1).GetMethod().DeclaringType.Name
Trans_SUBFU = strace.GetFrame(1).GetMethod().Name

Return True
End Function
End Class

SUBFU returns the Calling-Funktion, and CLASS the Class the Funktion is depending on.
 

 
-- modified at 7:55 Friday 16th February, 2007
Questionspace in pathmembermjstraka19 Aug '06 - 18:24 
I have a space in my file path "C:\Documents and Settings\..." so the trace file only shows "C:\Documents" any ideas?
 
Thanks
AnswerRe: space in pathmemberterpy19 Aug '06 - 19:11 
You have at least three options.
 
1. You can convert the path to a short path
 
2. You can look to see if the array has more than seven elements. If it does, you can reconstruct the path by appending those "extra" elements.
 
logtext = strace.GetFrame(1).ToString.Split(SPACE)
If logtext.GetUpperBound(0) > 6 Then
Dim j as Integer
For j = 7 to logtext.GetUpperBound(0)
logtext(6) = logtext(6) & BACKSLASH & logtext(j)
Next
End If
fileline = logtext(6).Split(BACKSLASH)
 
3. Since all you probably really care about is the name of the module, which will be in the last element of logtext, you can do this:
 
logtext = strace.GetFrame(1).ToString.Split(SPACE)
fileline = logtext(logtext.GetUpperBound(0)).Split(BACKSLASH)
 

 
terpy
GeneralSystem.Diagnostics membersmemberMarcus Poilus31 Mar '06 - 2:49 
I there,
 
Your submission is great. But in my .Net project, when I write "Dim st As New System.Diagnostics.", the only available members (trough intellisense) are:
 
- Conditional Attribute
- Debug
- DebuggableAttribute
- Debugger
- DebuggerStepThroughAttribute
- DefaultTraceListener
- Trace
- TraceListenerCollection
 
There are no "StackTrace" type. If I write it anyways, I get an error message which is 'Type sytem.Diagnostics.StackTrace' is not defined.
 
Any ideas?
 
Thank you!
 
Mark
AnswerRe: System.Diagnostics membersmemberterpy31 Mar '06 - 5:10 
Marcus,
 
You need to import System.Diagnostics at the beginning of the module or class or form, and then Dim st as StackTrace. If you reread the article, I think you will see what I mean.
 
So at the beginning of your code, you should have:
 
Imports System.Diagnostics
 
And then within the routine you are writing:
 
Dim st as New StackTrace
 

terpy
GeneralRe: System.Diagnostics membersmemberMarcus Poilus31 Mar '06 - 6:16 
Hi Terpy,
 
Thank you so much for your fast reply.
I think that Importing the assembly is as good as specifying it directly, before using the type. But your suggestion is still a good suggestion; before I even posted this message, I already tried it.
 
I think I found an answer to my problem though, and I'd be more than happy if you could confirm: My .Net porject is for deployment on a Pocket PC, perhaps the framework used on the pocket PC has some restrictions on some assemblies.
 
Do you agree that this could be my problem? (I tried in a standard vb.Net Windows application project and stacktrace is available).
 
Also, do you know a way with which I could access the stack other than the one you suggest, but for a pocket PC application?
 
Thank you so much, once again.
Mark
AnswerRe: System.Diagnostics membersmemberterpy31 Mar '06 - 6:39 
Mark,
I don't know anything about the differences between the .NET for PocketPC and for 'regular' PCs. But I do notice that StackTrace is not supported for PocketPCs. Sorry.
 
terpy
GeneralRe: System.Diagnostics membersmemberMarcus Poilus31 Mar '06 - 7:42 
Hey Terpy,
 
Ok great! (well, maybe not that great!)
Thanks!
 
Mark
GeneralSimple is goodmemberKato16 Apr '05 - 3:13 
I had used System.Reflection.MethodBase to create logging in the past to determine the source of the report.
Had not tried System.Diagnostics.StackTrace

 
Great profile, I am from Maine myself.
GeneralRelease ModememberNeil Baliga4 Mar '05 - 6:15 
Nice short and sweet article. Useful considering the .NET debug stack trace window is virtually useless.
 
I have not looked into this, but would this work with Release mode of an assembly.
 
------------ Neil -----------
Give someone a program,
and you frustrate them for a day
Teach them to program,
and you frustrate them for life
-----------------------------

GeneralRe: Release Modememberterpy7 Mar '05 - 11:58 
Yes and no. The trace file will have the names of the subs and functions as they are called, but the file info will be missing. (It will just be an open angle bracket followed by "filena", part of the error message that the file info wasn't available. Doesn't cause a problem but probably not what you want your customers to be looking at.
 
terpy
Generallog4netsitebuilderUwe Keim3 Mar '05 - 17:08 
Sorry to be intrusive, but for every logging/tracing article posted here, I add this reference:
 
Please also take a look at log4net[^] which is a superb and well-testet logging framework.
 
--
Affordable Windows-based CMS: www.zeta-producer.com
 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 3 Mar 2005
Article Copyright 2005 by terpy
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid