vb6-trace-pattern — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited vb6-trace-pattern (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
VB6 has no native call stack accessible to user code. When an error fires in production, Err.Description tells you what but not where in the chain of calls. This pattern solves that by maintaining a manual stack in a module-level array, pushed by EnterMethod on entry and popped by ExitMethod on exit.
The pattern is one of the most useful additions you can make to a long-lived VB6 codebase. The cost is one line at the top and one line in each exit path of every instrumented procedure.
Every non-trivial new procedure gets EnterMethod/ExitMethod. "Non-trivial" means any of:
On Error GoTo (CSEH style)Skip for:
When in doubt, instrument. The overhead is negligible compared to the diagnostic value when something fails in production.
The pattern is paired with the CSEH error-handling pattern:
'CSEH: ErrRaise
Public Function GetActiveCustomer(ByVal lngCustomerID As Long) As Object
'<EhHeader>
On Error GoTo GetActiveCustomer_Err
EnterMethod "modDB", "GetActiveCustomer"
'</EhHeader>
' ... body ...
'<EhFooter>
On Error GoTo 0
GetActiveCustomer_Exit:
' cleanup
ExitMethod "modDB", "GetActiveCustomer"
Exit Function
GetActiveCustomer_Err:
' Capture Err FIRST — the cleanup and ExitMethod below reset the Err object
Dim lGetActiveCustomer_ErrNum As Long
Dim sGetActiveCustomer_Err As String
lGetActiveCustomer_ErrNum = Err.Number
sGetActiveCustomer_Err = "Error: " & Err.Number & " - " & Err.Description & " (" & Erl & ")"
' cleanup
ExitMethod "modDB", "GetActiveCustomer"
Err.Raise lGetActiveCustomer_ErrNum, "SampleApp.modDB.GetActiveCustomer", sGetActiveCustomer_Err
'</EhFooter>
End FunctionCritical: ExitMethod is called on both the success path (_Exit) and the error path (_Err). Skipping ExitMethod in _Err leaves the function "open" on the stack — every subsequent trace shows that function still as a caller, which is wrong.
When the parameter values would help diagnose a future failure, pass them to EnterMethod after the module and procedure names:
EnterMethod "modDB", "GetActiveCustomer", lngCustomerID, blnIncludeInactiveEnterMethod accepts a ParamArray and serializes each parameter:
CStr"Object:" followed by a property/field dump, or "Nothing"GetStringResulting log line: Enter: modDB::GetActiveCustomer( 1234, False )
Performance caveat: serializing a populated Recordset is expensive. Inside hot loops (thousands of iterations), do not pass heavy parameters. Pass scalar identifiers (IDs, counts) only.
TraceMethod() returns the current stack as a formatted string. Embed it in the error message when the error is going to surface to the UI or be sent externally (email, ticket):
GetActiveCustomer_Err:
' Capture Err FIRST — the TraceMethod and ExitMethod calls below reset the Err object
Dim lGetActiveCustomer_ErrNum As Long
Dim sGetActiveCustomer_Err As String
lGetActiveCustomer_ErrNum = Err.Number
sGetActiveCustomer_Err = "Error: " & Err.Number & " - " & Err.Description & " (" & Erl & ")"
sGetActiveCustomer_Err = sGetActiveCustomer_Err & vbCrLf & TraceMethod()
ExitMethod "modDB", "GetActiveCustomer"
Err.Raise lGetActiveCustomer_ErrNum, "SampleApp.modDB.GetActiveCustomer", sGetActiveCustomer_ErrDo not embed TraceMethod() in every handler. In re-raise chains (function A calls function B which raises; A catches and re-raises), the stack is implicit in the chain of qualified source strings. Duplicating it in every message bloats the log. Use TraceMethod() at the boundary where the error becomes user-visible.
Logging only writes when gblnRegisterLog = True. The flag is set by RegisterLog (called automatically by LogMsg) based on three signals:
fg_InIDE returns True) — always log in devdebug.txt exists in App.Path — sysadmin enables logging bycreating this file on the client machine
In production, the absence of debug.txt means logging is off and the overhead of EnterMethod/ExitMethod is reduced to a couple of array operations and an early-exit check.
LogMsg accepts a Force parameter:
LogMsg "Database connection failed", TrueForce = True writes regardless of gblnRegisterLog. Use sparingly — reserve for events that must always reach the log file:
MsgBoxCritical wrapper does this)LogMsg scans the message for "failed" and "automation error". When either is found, it triggers DumpProcesses (via WMI) which logs every running process on the machine. This is invaluable when COM/automation errors appear sporadically and you need to know what else was running — competing processes, antivirus interference, stale instances of the app itself, etc.
The dump fires only once per LogMsg call (guarded by bDumping flag) and is best-effort (On Error Resume Next throughout).
Default layout for a single-tier log:
App.Path\
log\
app-2026-05-23.txt ' general log, rotated daily
app-2026-05-22.txt
...LogMsg opens the daily file in append mode, writes a single line with Format(Now, "hh:nn:ss") & " < " & msg & " >", and closes the handle. File handles do not stay open between messages — the cost of FreeFile/Open/Close is acceptable in exchange for not losing entries on crash.
Old logs are pruned automatically by CleanupOldLogs, called once per session on first log entry. Default retention: 30 days (DEF_LOG_DAYS constant).
ExitMethod supports two signatures:
ExitMethod "modDB", "GetActiveCustomer" ' specific (paired with EnterMethod)
ExitMethod "modDB" ' bulk (pop all frames for this module)The specific form pops one frame. The bulk form pops every frame at the top of the stack whose object matches — useful in form Unload handlers where multiple methods of the same form may still be on the stack due to exceptional early returns.
Prefer the specific form. The bulk form exists as a safety net but indicates that some ExitMethod was missed elsewhere.
| Anti-pattern | Why it breaks |
|---|---|
EnterMethod without paired ExitMethod | Stack grows unbounded; every trace is wrong from that point forward |
ExitMethod only in _Exit, not in _Err | After an error, the function stays "open" on the stack |
| Instrumenting trivial getters/setters | Pollutes the log; cost > benefit |
Passing a populated Recordset to EnterMethod in a loop | Each iteration serializes the entire recordset; massive log spam |
Calling ExitMethod before fully cleaning up resources | Trace looks clean but resources leak silently |
Using TraceMethod() in every re-raise handler | The qualified source string already carries the path; duplicates inflate log |
Initializing msMethods() mid-session via Stop/restart | Stack becomes inconsistent; trace shows phantom frames until next entry |
Adding the trace pattern to an existing codebase is incremental:
modLog.bas (from reference/)Microsoft Scripting Runtime reference (for Dictionary andFileSystemObject)
fg_InIDE in any module (the helper that detects IDE vs compiledexecution)
another reason gets EnterMethod/ExitMethod
There is no requirement to instrument everything at once. Partial coverage is useful: the trace shows what is instrumented, and uninstrumented procedures appear as "missing levels" in the trace, which itself is a hint about where to add coverage.
reference/modLog.bas is a complete, self-contained implementation of the pattern. Copy it into a new project and it works after referencing Microsoft Scripting Runtime and providing a fg_InIDE function.
The reference module includes:
LogMsg — write a line to the daily log file, with optional ForceEnterMethod — push a method onto the stack with optional parameterserialization
ExitMethod — pop a method (specific or bulk)TraceMethod — return the current stack as a formatted stringDumpProcesses — WMI-based process listing, auto-invoked onautomation-error patterns
CleanupOldLogs — daily-log retentionSerializeMethod — diagnostic helper for serializing arbitrary parameterarrays
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.