Tip 6: Write meaningful error logs (to a central location if possible).

By way of an example, Listing 1-3 is a typical log file entry produced by our internal application template code. No explanation is provided because most of the entry is pretty obvious.

Listing 1-3

Typical log file entry

********************************************************************************
* Error Entry Start. TEST. Created 04 February 1997 18:15
********************************************************************************
The Application:
----------------
C:\TMS\TEMPLATE\TEST.EXE  Version 1.0.10
OS App Name C:\TMS\TEMPLATE\TEST.EXE

The Error:
----------
An error has occurred in C:\TMS\TEMPLATE\TEST.EXE - the TMS error code associated 
with this error is 000053. If the problem persists, please report the error to TMS
support.  The error occurred at line 100.

The error probably occurred in frmMainForm.cmd1_Click.
The standard VB error text for this error is 'File not found'.

Active Environment Variables:
-----------------------------
TMP=C:\WINDOWS\TEMP
winbootdir=C:\WINDOWS
COMSPEC=C:\COMMAND.COM
PATH=C:\WINDOWS;C:\WINDOWS\COMMAND;C:\;C:\DOS
TEMP=C:\TEMP
DIRCMD=/OGN/L
PROMPT=$e[0m[$e[1;33m$p$e[0m]$_$g
CMDLINE=WIN
windir=C:\WINDOWS

Relevant Directories:
---------------------
Windows DIR C:\WINDOWS        - 16
System  DIR C:\WINDOWS\SYSTEM - 16
Current DIR C:\TMS\TEMPLATE   - 16

Versions:
----------
Windows    - 3.95
DOS        - 7.0
Mode       - Enhanced
CPU        - 486 or Better
COPRO      - True
Windows 95 - True

Resources:
----------
Free Mem (Rough) 15,752 MB
Free GDI  (%) 79
Free USER (%) 69
Free Handles 103
********************************************************************************
* Error Entry End. TEST
********************************************************************************

********************************************************************************
* Stack Dump Start. TEST. Created 04 February 1997 18:15
********************************************************************************
Stack Frame: 001 of 003 AppObject   - Run            Called @ line 70 CheckStack
Stack Frame: 002 of 003 AppObject   - CheckStack     Called @ line 10 cmd1_Click
Stack Frame: 003 of 003 frmMainForm - cmd1_Click
********************************************************************************
* Stack Dump End. TEST
********************************************************************************

********************************************************************************
* DAO Errors Start. TEST. Created 04 February 1997 18:15
********************************************************************************
No Errors
********************************************************************************
* DAO Errors End. TEST
********************************************************************************

The log files you write can be centralized; that is, all your applications can write to a single file or perhaps to many different files held in a central location. That “file” could be a Microsoft Jet database. Now if you log meaningful information of the same kind from different sources to a database, what have you got? Useful data, that’s what! At TMS, we created a system like this once for a client. All the data gathered was analyzed in real time by another Visual Basic application and displayed on a machine in the company’s support department. The application had some standard queries it could throw at the data (“How’s application xyz performing today?”) as well as a query editor that the company could use to build its own queries on the data. (“Show me all the Automation errors that occurred for user abc this year, and sort them by error code.”) All the results could be graphed too, which, as is usual, allowed the true nature of the data statistics to become apparent.

After a little while, it wasn’t just Support who accessed this database. User Education used it to spot users who were experiencing errors because of a lack of training, and developers would use it to check on how their beta release was running. Remember, users are generally bad at reporting errors. Most prefer to Ctrl+Alt+Delete and try again before contacting support. By logging errors automatically, you don’t need the user to report the error (sometimes incorrectly or with missing information: “Let’s see, it said something about…”); it’s always done by the application, and all the necessary information is logged automatically.

Another issue involved in making error messages and error logs meaningful is to know who was using the application and as a result who will be viewing the errors. It’s a good idea to display different error messages for different audiences. The best method for identifying the audience whenever an error occurs is to determine whether we’re running in Visual Basic’s design time Integrated Development Environment (IDE). Once this has been determined, we can log or output different error text and do some other stuff differently, depending on the result of that test. The reason we do this is that programmers, who would be the only people using Visual Basic’s IDE, are not end users. A programmer doesn’t mind seeing “Input past end of file,” but users almost always do. If you know how you’re running, you can easily switch messages.

The test we do internally to determine this involves a routine named bInDesign. Listing 1-4 on the next page is a comment- and explanation-free version (to save a little space).

By using bInDesign, we can do things a little differently at run time, depending on whether or not we’re running in the IDE. We store the result of a single call to bInDesign in a property of the App object named InDesign. We replace the real App object with our own, also called App, and add this property at application start-up.

Listing 1-4

bInDesign determines whether the application is running in design mode

Option Explicit

Private Const GWW_HINSTANCE As Integer = (-6)

Private Declare Function WinFindWindow Lib _
"user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal l As Long) As Long

Private Declare Function WinGetWindowLong Lib _
"user32" Alias "GetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long) As Long


Public Function bInDesign() As Boolean

    If Forms.Count = 0 Then
        MsgBox "This routine cannot be called if " & _
               "no forms are loaded." _
               , vbOK Or vbCritical _
               , "Error"
    Else
    
        Dim hInstanceVB As Long
        Dim hVBMainWindow As Long
    
        ' Find the VB IDE's main window.
        hVBMainWindow = WinFindWindow("ThunderMain", 0)
        
        hInstanceVB = IIf( _
                           hVBMainWindow <> 0 _
                         , WinGetWindowLong(hVBMainWindow _
                                            , GWW_HINSTANCE) _
                         , 0 _
                         )
        
        bInDesign = IIf( _
                         hInstanceVB = WinGetWindowLong( _
                                             Forms(0).hwnd _
                                             , GWW_HINSTANCE _
                                             ) _
                       , True _
                       , False _
                       )
        
    End If
  
End Function

Another use of App.InDesign is to turn off your own error handling altogether. Now, I know that Visual Basic allows you to Break On All Errors, but that’s rarely useful because you might have deliberate delayed error handling activated that you don’t want to be ignored. Use App.InDesign to conditionally turn error handling on or off:

If Not App.InDesign Then On Error GoTo ...

The reason for this is that one of the last things you want within the IDE is active error handlers. Imagine you’re hitting F8 and tracing through your code. I’m sure you know what happens next—you suddenly find yourself in an error handler. What you really want is for Visual Basic to issue a Stop for you on the erroring line (which it will do by default if you’re using the IDE and hit an error and don’t have an error handler active). The code above causes that to happen even when your error handling code has been added. Only if you’re running as an EXE will the error trap become enabled.