Article: Lotusscript Error Tracing »
FERDY CHRISTANT - APR 22, 2005 (10:30:46 PM)
Introduction
Error trapping and handling, much like writing documentation, is not a favorite activity of the average developer. I've seen tons of Lotusscript in my life, spotting proper error handling anywhere being rare. This includes my own work. There are typical reasons to get sloppy with error handling:
- Plain 'old laziness, often sold as "time pressure"
- It doesn't look good, code is less readable
- My code is so solid, it doesn't need error trapping. much like "640k should be enough for everyone"
- You're not my boss, and by the time anyone finds out about my kiddie script, I'll be long gone
- Error trapping adds nothing, let Notes throw the error. It's not like users will be surprised
Obviously, none are valid reasons. Proper error handling is a matter of caring. If you're a craftsman, you care. It's as simple as that. Still, why not find a way to make it all easier, so eventually others will care as well? Maybe one day we'lll then live in an error-free world. Let's start with an error-traced world first...
Goal
The goal of this article is to introduce an error-tracing pattern that does two things:
- Make error-tracing as easy as possible
- Despite the simple implementation, give you comprehensive information on where your error occured and what led to the error.
Theory: Ground rule
There's (at least) one crucial ground rule that you should tattoo on your forehead when implementing error tracing:
DO NOT *HANDLE* ERRORS FROM YOU CODE, ONLY *THROW* THEM. LET THE CALLING CODE (CLIENT) WORRY ABOUT *HANDLING* THE ERROR.
Why? Your code will never posess the intelligence to understand the impact of the error, no matter how complex you implement it. It is not up to you to decide what should happen in the case of an error, this is up to the client. Additionally, when you do inline error handling, you're making decisions about the output format and/or medium that may be wrong. Maybe your class will be used by both users and agents. It would be incorrect to assume one output format.
Theory: What is tracing
As in most languages, you can implement complex code constructions in Lotusscript. Complexity lies mostly in diverse method calls among different functions, classes or subclasses. Using a basic error trapping mechanism will only tell you that an error occured, leaving you with the time-consuming process of finding where it happened. With a slightly more advanced mechanism you can instantly find where the error occured in your code. Both mechanisms do error trapping.
Error tracing adds one piece of information to the error trapping mechanism. The extra information is a trail of method calls that led to the error. Using this information you can not only find out where the error originated, you can also trace back the method call(s) that "triggered" the error.
Theory: Automated error retrieval
If you're like me, and rather drink pints than do error traciing, you would agree that when an error occurs, you would like to get as much error details automatically, instead of manually coding them. Here's an overview of the most important error information that you can retrieve in such an event:
| Name | Description | Type |
|---|---|---|
| Err | Numeric error code | integer |
| Error | Error text | string |
| GetThreadInfo(LSI_THREAD_LINE) | Current line number | variant |
| GetThreadInfo(LSI_THREAD_PROC) | Current procedure | variant |
| GetThreadInfo(LSI_THREAD_MODULE) | Current module | variant* |
| GetThreadInfo(LSI_THREAD_VERSION) | Lotusscript version | variant |
| GetThreadInfo(LSI_THREAD_LANGUAGE) | Language setting | variant |
| GetThreadInfo(LSI_THREAD_COUNTRY) | Country/Region setting | variant |
| GetThreadInfo(LSI_THREAD_TICKS) | Current clock ticks | variant |
| GetThreadInfo(LSI_THREAD_TICKS_PER_SEC) | Clock ticks per second | variant |
| GetThreadInfo(LSI_THREAD_PROCESS_ID) | Current process ID | variant |
| GetThreadInfo(LSI_THREAD_TASK_ID) | Current task ID | variant |
| GetThreadInfo(LSI_THREAD_CALLPROC) | Calling procedure | variant |
| GetThreadInfo(LSI_THREAD_CALLMODULE) | Calling module | variant |
| Lsi_info(1) | Current line number | string |
| Lsi_info(2) | Current procedure | string |
| Lsi_info(3) | Current module | string |
| Lsi_info(6) | Lotusscript version | string |
| Lsi_info(9) | Language setting | string |
| Lsi_info(12) | Calling procedure | string |
| Lsi_info(50) | LS memory allocated | string |
| Lsi_info(51) | OS memory allocated | string |
| Lsi_info(52) | LS blocks used | string |
* = this call returns a hex code, not the module name
Note:To use the GetThreadInfo constants, you must include LSPRVAL.LSS, which is automatically included through LSCONST.LSS
The functions above you can use to assemble your own custom error report. Note that you are advised to use GetThreadInfo(), since Lsi_info is an undocumented feature.
Putting it into practice
Let me demonstrate all this theory though a simple case. Assume we have a script library called "ABC". In it are 3 classes, A, B, and C. A typical client would call class A (instantiate it). Class A internally calls class B. Class B internally calls class C. An error could occur in any of the classes, but for this example we're forcing one in class C. As the client calling class A, we would like to find out where the error occured and what calls caused it. Here's the full contents of the ABC script library:
Private Const DEBUG_MODE = True 'toggle debugging
Public Class A
Sub New()
If ( DEBUG_MODE ) Then On Error Goto ErrorThrower
'instantiate class B
Dim objB As New B()
Exit Sub
ErrorThrower:
Error Err, Error & Chr(13) + "Module: " &_
Cstr( Getthreadinfo(1) ) & ", Line: " & Cstr( Erl )
End Sub
End Class
Public Class B
Sub New()
If ( DEBUG_MODE ) Then On Error Goto ErrorThrower
'instantiate class C
Dim objC As New C()
Exit Sub
ErrorThrower:
Error Err, Error & Chr(13) + "Module: " &_
Cstr( Getthreadinfo(1) ) & ", Line: " & Cstr( Erl )
End Sub
End Class
Public Class C
Sub New()
If ( DEBUG_MODE ) Then On Error Goto ErrorThrower
'force a division by zero error
Dim intI As Integer
intI = 0
Msgbox 1/intI
Exit Sub
ErrorThrower:
Error Err, Error & Chr(13) + "Module: " &_
Cstr( Getthreadinfo(1) ) & ", Line: " & Cstr( Erl )
End Sub
End Class
Now, let's introduce the code that calls class A:
Option Public
Option Declare
Use "ABC"
Sub Initialize
On Error Goto errorhandler
Dim objA As New A()
Exit Sub
ErrorHandler:
Msgbox Cstr(Err) & ": " & Error
Exit Sub
End Sub
Note that we are doing the error handling in the calling code. In this case I have decided to output the error in a messagebox, but I could just as easily decide to log or email the message. Let's run the calling code and see what happens:
- Calling code instantiates class A
- Inside the constructor of class A, class B is instantiated
- Inside the constructor of class B, class C is instantaied
- Inside the constructor of class C, an error occurs
- The error thrower of class C captures the error and throws it at the calling module(class B)
- The error thrower handler of class B receives the error throwed from class C, and throws it at the calling module(class A)
- The error thrower of class A receives the error throwed from class B, and throws it at the calling module(client code)
- The client code outputs the error trace in a messagebox
the result:
Not only do we see the actual error, in the form of an error code and text. We also see where the error originated (line 7), and the entire trace to it. The beauty is that this always works. If the error occured earlier, for example in class B, the trace would be one line shorter. It works for both custom-thrown and Motes-thrown errors. Best of all, your error trapping code is always the same, you do not need to think about it anymore.
Conclusion
There are two great advantages in using the above pattern:
- One simple error trapper, just cut and paste
- The trace makes it easier to find your error
In short: little effort, great power. So...what excuse have you got left to not do proper error handling?
Tip: Did you notice the DEBUG_MODE constant in the ABC script library? I find it a quick and easy way to fully enable/disable error trapping in my code. I learned this from someone at work.


Comments: 10
Reviews: 3
Average rating:
Highest rating: 5
Lowest rating: 3
COMMENT: RICHARD SCHWARTZ


APR 25, 04:50:59
-rich «
COMMENT: FERDY
APR 25, 18:37:05
COMMENT: NATHAN T. FREEMAN
MAY 3, 13:04:24
COMMENT: FERDY
MAY 3, 19:04:52
Actually I did check out the OpenLog project. I find it to be an awesome tool, yet unusable at work due to a tightly managed infrastructure
Actually, Openlog implements the pattern described above in its stacktrace methods. And it does a whole lot more. The point of my article was to describe the idea behind the pattern and how a basic implementation could look like. At the beginning of the article, I also mentioned that more people have documented this, yet that practise shows that still hardly any developer actually uses this pattern (or any error handling for that matter). It cannot be said enough dude. «
COMMENT: MATTHEW SMITH
FEB 21, 06:53:36 AM
Obviously you can still work that out by the line number if you scroll through your code - although when you have multiple large script libraries in play, it's handy to know what class to look for. «
COMMENT: FERDY
FEB 22, 07:40:37 AM
COMMENT: JERRY CARTER


MAR 17, 04:40:54 PM
Thanks! «
COMMENT: BERTRAND
MAR 2, 17:16:17
I have a subsidiary question for you. With LSI_Info and GetThreadInfo, it is possible to get lots of information about the current script but do you know a tip to know if a method exists for a given object in LotusScript ?
Many collegues said to me it was impossible but I want to be sure.
Thanks a lot ! «
COMMENT: MARCELO
APR 1, 02:35:02 AM
e.g. it would be nice to know if the "NEW" comes from code defined in a script library, agent, etc. and which one, the hex code means nothing to me.
Thanks! «
COMMENT: PIERRE

MAY 22, 12:17:14
one question, isn't it dangerous to desactivate the error handling ??
thank you ! «