Bug Tracking
Background Bug Logging for LambdaMOO
Nathan Green
Adapted for Planet Oit |
I. User GuideChapter 1. IntroductionWhen there are many tasks running in the background, it can sometimes be difficult to determine when a task has terminated because on an error. When an error occurs when no one is logged in, the task silently dies. When the programmer logs in, he may notice his task in no longer running, but has no way of telling why it stopped.To solve this problem, the system object was modified so that information about every error gets saved on the object Bug Database (#4214). Errors are reviewed using a HTML interface reached through the mud's HTTP server.
From the first page, you have the option of going directly to browsing the bug list, or initiating a search.
On this page, the bug log entries are listed, starting with the most recent. If there are more entries than will fit on one page, there will be navigation links at the bottom of the page to allow you to move to the next previous page, or jump directly to a specific page.
Programmer is the programmer of the verb in which the error occurred, Object is the object the error occurred in, Player is the player that issued the command that eventually caused the error. All fields can be an object given by either the objects number (e.g. #4214), or its dollar sign notation (e.g. $g.bug_db). Additionally, the Programmer and Player fields can be the name of the player (e.g. ngreen).
This function is called by the driver whenever an error is thrown outside of a try block. It calls add_bug on Bug Database (#4214) to add the bug to the log. error_code is an error code. msg is the error message value is an optional value traceback is a stack trace. formatted is the formatted stack trace.
if (callers()) return; endif try $g.bug_db:add_bug(@args); except (ANY) endtry return 0; 4.2. handle_task_timouthandle_task_timeout(resource, traceback, formatted);This function is called by the driver whenever a task times out. It calls add_bug on Bug Database (#4214) to add the bug to the log. resource is either "ticks" or "seconds". traceback is a stack trace. formatted is the formatted stack trace.
If (callers()) return; endif {resource, traceback, formatted] = args; try $g.bug_db:add_bug(E_NONE, "Timeout: " + resource, 0, traceback, formatted); except (ANY) endtry return 0;
elseif (chunks[1] == "bugs") Page = $g.bug_db:http_get(@chunks);
Adds a bug log entry to the log. The arguments are the same as those for the handle_uncaught_error function.
bug_db = this; {error_code, msg, value, traceback, formatted} = args; {error_object, error_verb, error_programmer, error_verbloc, error_player, error_line} = traceback[1]; log_info = [time(), error_verbloc, error_verb, error_line, msg, error_programmer, error_player, formatted}; existing_log = bug_db.log; existing_log = {@existing_log, log_info}; bug_db.log = existing_log; if (time() - bug_db.last_trim > 24 * 3600) bug_db.last_trim = time(); fork (10) bug_db:trim_log(); endfork endif
{time, object, verb, line, msg, programmer, player, formatted}
time
The http_get function takes one or more path components as arguments, and returns and array of string corresponding to the HTML page to be sent to the user. The arguments should be the URL given by the user after it has been broken down such that each argument corresponds to the text between each "/" character in the URL.
bug_db = this; if (length(args) == 1) return bug_db.index_page; elseif (length(args) > 3) return bug_db.bad_page; endif form_data = {}; i = index(args[2], "?"); if (i != 0) form_data = $string_utils:explode(args[2][i + 1..$], "&"); args[2] = args[2][1..i - 1]; endif if (args[2] == "browse") if (length(args) > 2) return bug_db:html_browse(toint(args[3])); else return bug_db:html_browse(length(bug_db.log)); endif elseif (args[2] == "search") return bug_db:html_search(@form_data); else return bug_db:html_detailed(toint(args[2])); endif 8.2. html_browsehtml_browse(start);Returns a HTML page showing a page of log entries. If the optional start parameter is given, the listing will start with that log entry.
summary_length = 50; page = {"<HEAD> <TITLE> Bug Log </TITLE> <HEAD>", "<BODY>", "<TABLE border=1>", "<TR> <TH> Time <TH> Object <TH> Verb <TH> Line <TH> Error "}; bug_db = this; start = args[1]; if (start > length(bug_db.log)) start = length(bug_db.log); endif stop = start - summary_length; i = start; while (i > stop && i > 0) log_entry = bug_db.log[i]; page = {@page, "<TR> <TD> " ctime(log_entry[1]) + " <TD> " + $string_utils:nn(log_entry[2]) + " <TD> " + log_entry[3] + " <TD> " tostr(log_entry[4]) + " <TD> <A HREF=\"/Bugs/" + tostr(i) + "\">" + log_entry[5] + "</A>"}; i = i -1; endwhile page = {@page, "</TABLE>>"}; if (start < length(bug_db.log)) tmp = start + summary_length; if (tmp > length(bug_db.log)) tmp = length(bug_db.log); endif page = {page, "<A HREF=\"/bugs/browse/" + tostr(tmp) + "\"> Previous Page</A>"}; endif if (stop > 1) page = {@page, "<A HREF=\"/bugs/browse/" + tostr(start - summary_length) + "\"> Next Page</A>"}; endif page = {@page, "Page"}; i = length(bug_db.log); j = 1; while (i > 0) page = {@page, "<A HREF=\"/bugs/browse/" + tostr(i) + "\"> " + tostr(j) + "</A>"}; i = i - summary_length; j = j + 1; endwhile page = {@page, "</BODY>"}; return page; 8.3. html_detailedhtml_detailed(bug_no);Return a HTML page showing the detailed trace information for bug log entry bug_no.
bug_db = this; bug_no = args[1]; if (bug_no <= 0 || bug_no > length(bug_db.log)) return bug_db.bad_page; endif log_entry = bug_db.log[bug_no]; page = {"<HEAD> <TITLE> Bug Log Detail</TITLE> <HEAD>", "<BODY>", "<PRE>", "Time: " + ctime(log_entry[1]), "Player: " + $string_utils:nn(log_entry[7]), "", @log_entry[8], "</PRE>", "</BODY>"}; return page; 8.4. html_searchhtml_search(...);Perform a search on the bug list and return a HTML page showing the results The arguments are the data typed into the search form, as given by the web browser. They are strings of the form "variable=value". Where variable is one of programmer, player, or object, and value is URL encoded.
summary_length = 50; bug_db = this; search_programmer = 0; search_player = 0; search_pbject = 0; programmer_name = 0; player_name = 0; object_name = 0; error = 0; start = length(buf_db.log); page = {"<HEAD><TITLE>Search Results</TITLE></HEAD>", "<BODY>"}; for st in (args) i = index(st, "="); variable = st[1..i - 1]; value = this:decode_html(st[i + 1..$]); if (value == "") elseif (variable == "programmer") programmer_name = value; elseif (variable == "player") player_name = value; elseif (variable == "object") object_name = value; endif endfor if (programmer_name) search_programmer = bud_db:find_player(programmer_name); if (search_programmer == 0) page = {@page, "Couldn't find a programmer named " + programmer_name); error = 1; endif endif if (player_name) search_player = bug_db:find_player(player_name); if(search_player == 0) page = {@page, "Couldn't find a player named " + player_name}; error = 1; endif endif if (object_name) search_object - $string_utils:literal_object(object_name); if (!valid(search_object)) page = {@page, "Couldn't find object " + object_name]; error = 1; endif endif if (search_programmer !=0) page = {@page, "Searching for programmer " + $string_utils:nn(search_programmer) + "<P>"}; endif if (search_player != 0) page = {@page, "Searching for player " + $string_utils::nn(search_player) + "<P>"}; endif if (search_object != 0) page = {@page, "Searching for object " + $string_utils:nn(search_object) + "<P>"}; endif if (!error) page = {@page, "<TABLE border = 1>", "<TR> <TH> Time <TH> Object <TH> Verb <TH> Line <TH> Error "}; i = start; j = 0; while (j < summary_length && I > 0) log_entry = bug_db.log[i]; if (search_programmer == 0 || search_programmer == log_entry[6] && (search_player == 0 || search_player == log_entry[7]) && (search_object == 0 || search_object == log_entry[2])) page = {@page, "<TR> <TD> " + ctime(log_entry[1]) + " <TD> " + $string_utils:nn(log_entry[2]) + " <TD> " + log_entry[3] + " <TD> " + tostr(log_entry[4]) + " <TD><A HREF=\"/bugs/" + tostr(i) + "\">" + log_entry[5] + "</A>"}; j = j + 1; endif i = i - 1; endwhile page = {@page, "</TABLE>"}; if (j == 0) page = {page, "<P>No bugs found."}; endif endif page = {page, "</BODY>"}; return page; 8.5. decode_htmldecode_html(st);Remove URL encoding from st.
st = args[1]; i = 1; j = 1; while (i <= length(st)) if (st[i] == "+") st[j] = " "; elseif (st[i] == "%") st[j] = this:hexidecimal(st[i + 1..i + 2]); i = i + 2; else st[j] = st[i]; endif i = i + 1; j = j + 1; endwhile st = st[1..j - 1]; return st; 8.6. find_playerfind_player(name);Returns the object number of the player with name name. name may also be an object identifier in the form of "#4214" or "$g.bug_db".
name = args[1]; ob = $player_db:find(name); if (!valid(ob)) ob = $string_utils:literal_object(name); endif if (!valid(ob)) return 0; endif return ob; 8.7. hexidecimalhexidecimal(st);Convert two digit hexidecimal number into a ASCII character.
st = args[1]; hexdigits = "0123456789ABCDE"; a = index(hexdigits, st[1]) - 1; b = index(hexdigits, st[2]) - 1; return $string_utils:from_ASCII(a * 16 + b); 8.8 show_bugsshow_bugs(void);Display all bugs entries to the current player.
for log_entry in (this.log) player:tell(ctime(log_entry[1]) + ": " + log_entry[8][1]); endfor
Reduces the size of the log according to the rules given in Section 3.2.
bug_db = this; min_time = time() - bug_db.min_age * 24 * 3600; if (bug_db.max_age == 0) max_time = 0; else max_time = time() - bug_db.max_age * 24 + 3600; endif i = 0; "If log is too long, cut it to 1/2 the maximum"; if (length(bug_db.log) > bug_db.max_entries) i = length(bug_db.log) - bug_db.max_entries / 2; endif "Make sure to trim anything too old."; while (i < length(bug_db.log) && bug_db.log[i + 1][1] < max_time) i = i +1; endwhile "But don't trim anything too new."; while (i > 0 && bug_db.log[i][1] > min_time) i = i - 1; endwhile bug_db.log = bug_db.log[i + 1..$]; bug_db.last_trim = time(); |