Bug Tracking

Background Bug Logging for LambdaMOO

Nathan Green

Adapted for Planet Oit
7/26/2000

Table Of Contents

I. User Guide
1. Introduction
2. The HTML Interface
2.1. The First Page
2.2. Browsing the Bug List
2.3. Searching The List
3. User Settable Properties
3.1. HTML Pages
3.1.1. index_page
3.1.2. bad_page
3.2. Log Entry Expiration
3.2.1. max_entries
3.2.2. min_age
3.2.3. max_age
II. Implementation
4. Changes To The System Object
4.1. handle_uncaught_error
4.2. handle_task_timeout
5. Changes to the HTML Page Maker
6. Adding a bug
6.1. add_bug
7. The log Property
8. Viewing the Bug List
8.1. http_get
8.2. html_browse
8.3. html_detailed
8.4. html_search
8.5. decode_html
8.6. find_player
8.7. hexidecimal
8.8. show_bugs
9. Log Maintainence
9.1. trim_log

I. User Guide

Chapter 1. Introduction

When 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.

Chapter 2. The HTML Interface

2.1. The First Page

The first page can be accessed by retrieving the file /bugs from the HTTP server.

From the first page, you have the option of going directly to browsing the bug list, or initiating a search.

2.2 Browsing The Bug List

Browsing the bug log can be done by going the /bugs/browse on the HTTP server.

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.

2.3. Searching The List

From the first page, enter one or more of the search fields, and press the Search Button.

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).

Chapter 3. User Settable Properties

3.1. HTML Pages

Some of the properties on the Bug Database contain HTML pages. These properties consist on an array of strings, each string is a single line of the HTML page.

3.1.1 index_page

The index_page property contains the HTML page that is returned when /bugs is retrieved.

3.1.2 bad_page

When a request is made that cannot be translated into a valid request, the contents of the bad_page property is returned. This is basically the same as a 404 error page.

3.2. Log Entry Expiration

As the number of entries into the bug log grows, eventually older entries will be deleted to make room for newer entries. The maximum size of the bug log is determined by the following properties.

3.2.1 max_entries

It the log grows to more than max_entries log entries, it will be trimmed to half the maximum size.

3.2.2 min_age

All error messages that are less than min_age days old will not be removed from the log, no matter how many log entries are in the log. This allows all recent log entries to remain in the log, even if a large number of entries are made in a short amount of time.

3.2.3 max_age

Any error message that is older than max_age days old will be removed form the log to save space. If this number is 0, the log entries will never expire.

II. Implementation

Chapter 4. Changes To The System Object

4.1 handle_uncaught_error

handle_uncaught_error(error_code, msg, value, traceback, formatted);

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_timout

handle_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;

Chapter 5. Changes to the HTML Page Maker

The HTML Page Maker (#105) needed to be modified to send page requests to the Bug Database (#4214). This was accomplished by adding the following lines to #105:find_page:

elseif (chunks[1] == "bugs")
  Page = $g.bug_db:http_get(@chunks);

Chapter 6. Adding a bug

6.1. add_bug

add_bug(error_code, msg, value, traceback, formatted);

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

Chapter 7. The log Property

The log property is an array of log entries. Each log entry is an array with the format:

{time, object, verb, line, msg, programmer, player, formatted}

time is the time the error occurred, as reported by time(). object is the object that defined the verb that the error occurred in. verb is the name of the verb where the error occurred. line is the line number. msg is the error message. programmer is the programmer who owns the verb the error occurred in. player is the player who typed the command that caused the error. formatted is the formatted traceback listing. It is an array of strings, one string per line.

Chapter 8. Viewing the Bug List

8.1. http_get

http_get(...);

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_browse

html_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_detailed

html_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_search

html_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_html

decode_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_player

find_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. hexidecimal

hexidecimal(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_bugs

show_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

Chapter 9. Log Maintainence

9.1. trim_log

trim_log(void);

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();