WLA: Implement a Log for your agent

Logging information of applications usage is something not only useful but most times vital. It can help both in the development phase (debugging variable values, code flows,...) and when your application is deployed (for example, tracing error sources).

While developing a Windows Live Agent, you can use the IDE integrated Query Window to test your patterns, matchings and debug some info, but when you start to test the Agent launched on MSN Messenger, things get complicated and you loose some of this capabilities.

One thing that I've implemented for our first agent is a "Log and Tracing" package, with some levels of information dumping.

Creating a simple file log is very easy:

function Globals_LogFileName()
return "AgentLog.log"

procedure WriteLog(CONTENT)
CONTENT |= ""
DESCRIPTION = DateTimeObjectToDateTimeString(GetCurrentDateTimeAsObject()) + " : " + ObjectToString(CONTENT) + "\r\n"
RES = WriteToFile(Globals_LogFileName(), "append", DESCRIPTION)
RES = WriteToFile(Globals_LogFileName(), "close")

The first function is what I call a "global constant". BuddyScript has some scopes for variables (local variables, package variables, global variables and shared global variables), but lacks support for global constants, so instead of having a global, per-user variable, or even worse, a shared global variable (which has to be locked-unlocked for reading it's value), defining a function that returns a string is IMHO the most elegant solution.
In this example, it contains the name of the log file.

The second procedure is the actual log writing. It creates the file if not present, and writes the contents of the parameter to the file. If the parameter is an object, it converts it to a string array with it's key-value pairs. If it's a simple data-type, it writes it's contents like if it were a string.

Another trick you can see here is avoiding null references with parameters. By doing CONTENT |= "", if CONTENT comes uninitialized, it becomes initialized as an empty string (BuddyScript uses internally almost everything as strings).
Having objects is way more handy, so for example if you want to write an exception, doing EXCEPTION.DATA |= "" will avoid exceptions thrown without DATA property failing and crashing your code.

This is a very simple example, usually you would log more stuff, like user email address, source file where the WriteLog() procedure was called, or a short description just in case you want both to flush an object's contents and a message indicating why you do it.

Note: I will mix simple low-level posts with tricks and tips like this one. The SDK documentation is very good as a starting point for learning the basics.

Comments?

Posted by Kartones on 2007-09-16