Page MenuHomeDevCentral

Trace eggdrop errors
Open, NormalPublic

Description

Trace could be a payload with global variables errorCode, errorInfo and the new TCL 8.6 command info errorstack.

For example:

Wearg partyline
17:24:36 <Dereckson> .tcl putdebug $errorInfo
17:24:36 <Wearg> [DEBUG] can not find channel named "sock8018e0910"
17:24:36 <Wearg>     while executing
17:24:36 <Wearg> "eof $sock"
17:24:36 <Wearg> Tcl:

17:24:39 <Dereckson> .tcl putdebug $errorCode
17:24:39 <Wearg> [DEBUG] TCL LOOKUP CHANNEL sock8018e0910
17:24:39 <Wearg> Tcl:

17:25:04 <Dereckson> .tcl info errorstack
17:25:04 <Wearg> Tcl: INNER {invokeStk1 eof sock8018e0910} CALL {http::Event sock8018e0910 ::http::5}

A payload could be generated from those three values:

Payload
errorCode: TCL LOOKUP CHANNEL sock8018e0910
errorInfo: |
    can not find channel named "sock8018e0910"
         while executing
    "eof $sock"
errorstack: |
    INNER {invokeStk1 eof sock8018e0910} CALL {http::Event sock8018e0910 ::http::5}

We can watch $errorInfo to trigger a method to report the trace:

Trace code
trace add variable ::errorInfo write {
    # Generate payload
    # Log it or send it to Sentry
}

Documentation for that is located at https://wiki.tcl-lang.org/page/Traces

Event Timeline

dereckson triaged this task as Normal priority.Mar 7 2023, 17:38
dereckson created this task.

To create the payload, we can use a dictionary: [dict create code "$::errorCode" info "$::errorInfo" stack [info errorstack]].

It can then represented as a JSON payload ready to be send to Sentry or a log:

Error traced as a JSON payload
01:09:14 <Dereckson> .tcl incr x
01:09:14 <Wearg> Tcl error: expected integer but got "a"

01:09:30 <Dereckson> .tcl dict2json [dict create code "$::errorCode" info "$::errorInfo" stack [info errorstack]]
01:09:30 <Wearg> Tcl: {
01:09:30 <Wearg> Tcl:     "code"  : "TCL VALUE INTEGER",
01:09:30 <Wearg> Tcl:     "info"  : "expected integer but got \"a\"\n    while executing\n\"incr x\"",
01:09:30 <Wearg> Tcl:     "stack" : "INNER {incr x} UP 1"
01:09:30 <Wearg> Tcl: }

Seems my dict2json expects every value to be a string,
but [info errorstack] returns a dictionary, so let's use this:

Wearg :: Correct JSON payload
01:13:17 <Dereckson> .tcl ::json::write object code [::json::write string $errorCode] info [::json::write string $errorInfo] stack [dict2json [info errorstack]]
01:13:17 <Wearg> Tcl: {
01:13:17 <Wearg> Tcl:     "code"  : "TCL VALUE INTEGER",
01:13:17 <Wearg> Tcl:     "info"  : "expected integer but got \"a\"\n    while executing\n\"incr x\"",
01:13:17 <Wearg> Tcl:     "stack" : {
01:13:17 <Wearg> Tcl:         "INNER" : "incr x",
01:13:17 <Wearg> Tcl:         "UP"    : "1"
01:13:17 <Wearg> Tcl:     }
01:13:17 <Wearg> Tcl: }

To test what it can reports, I've added this:

Wearg :: Error payload to partyline
trace add variable ::errorInfo write {
    set error_payload [dict create code "$::errorCode" info "$::errorInfo" stack [info errorstack]]
    putdebug "Error traced: $error_payload" ;#
}

An issue with the code above is [info errorstack] gives the previous error call stack. It will update when we're done with the error.

I'm adding to Tech.tcl the following error context with:

  • a generic part, to be handled by the Sentry TCL toolkit/SDK
  • a specialized part

1#
2# Error handling
3#
4
5proc build_error_context {} {
6 global username version
7
8 # Should be done by Sentry TCL toolkit
9 set context [dict create tcl_version [info patchlevel]]
10 dict append context nameofexecutable [info nameofexecutable]
11 dict append context environment [array get ::env]
12 dict append context extensions [info loaded]
13 dict append context error [dict create code $::errorCode info $::errorInfo stack [info errorstack]]
14
15 # Can only be done by us
16 set app_context [dict create username $username eggdrop_version $version]
17 dict append app_context scripts_version [exec sh -c "cd scripts && git rev-parse HEAD"]
18
19 dict merge $context $app_context
20}

From there, a short timer can call error management:

trace add variable ::errorInfo write {
    utimer 10 handle_error [clock microseconds]
}

proc handle_error {time} {
   set context [build_error_context]
   dict append context time $time

   sentry_send [vault_get sentry dsn] $context
}