C plus plus:Simple XML Logging System

From GPWiki
Jump to: navigation, search

XML?

XML has been a really big buzz-word for a while, but it seems that the XML bird has some trouble taking off. A lot of people have yet to find out about XML.

XML uses a very simple HTML-like syntax for storing data. It's basically just a left angled bracket, the tag name, a right angled bracket, the data for the tag, a left angled bracket and a slash, the tag name and finishing off with another right angled bracket. Like so: < TAG > DATA < / TAG >

Then there are all sorts of things you can do to complicate things when you get more experienced, like adding setting-tags inside the tag-definition, including tags inside other tags and much more stuff like that.

The beautiful aspect of XML is in its simplicity. Because it has a certain set of definitions of how the data is stored, it's possible for everyone to read XML straight and in most cases get a very good idea of what's being stored, and most importantly it allows for data to be seamlessly shared between different computer, platforms, companies etc..

If you couple XML with scripting capabilities such as XSL stylesheets you can present the data stored in the XML in any way you like, and changing how the data is displayed doesn't change or modify the original data in any way.

If you haven't experimented with XML I suggest that you go ahead and give it a twirl.


XML used for logging

Being able to dump information from your game to a file is great for testing and debugging purposes. However, many logging systems just use brute force to do this; dumping game data into a file in raw readable text. For simple applications this can work, but if you want your logging system to be readable and simple and flexible, then XML comes to the rescue!


LogHandler.hpp

File: LogHandler.hpp

  1. ifndef LOGHANDLER_HPP
  2. define LOGHANDLER_HPP
  1. if (_MSC_VER >= 1300)
  2. pragma once
  3. endif // (_MSC_VER >= 1300)
  1. include <fstream>
  2. include <string>
  3. include <vector>

class CFile { std::string m_filename;

public: CFile(std::string const & file_name) : m_filename(file_name) {} virtual ~CFile() {}

void clear() { std::ofstream file_stream(m_filename.c_str(), std::ios_base::trunc); }

template <typename T> bool write(T const & data) { std::ofstream file_stream(m_filename.c_str(), std::ios_base::app); // Set the append flag if (!file_stream) // Use operator! to check file-bit-state (fail & bad-bit) return false;

file_stream << data; return file_stream; // writing data successful? operator void* or (return !!file_stream) operator! } };

class CLogHandler { private: std::vector<std::string> m_tags; std::size_t m_layer;

protected: CFile* m_ptrFile;

public: CLogHandler(std::string const & filename) : m_layer(0) { m_ptrFile = new CFile(filename); m_ptrFile->clear(); m_ptrFile->write("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>\n"); }

virtual ~CLogHandler() { while (!m_tags.empty() == false) { close_tag_tree(); }

delete m_ptrFile; }

void add_tag_tree(std::string const & tag_name) { m_ptrFile->write(std::string(m_layer, '\t') + '<' + tag_name + ">\n"); m_tags.push_back(tag_name); ++m_layer; }

template <typename T> void add_tag(std::string const& name, T const & data) { m_ptrFile->write(std::string(m_layer, '\t') + '<' + name + '>'); m_ptrFile->write<T>(data); m_ptrFile->write(std::string("</") + name + ">\n"); }

void add_comment(std::string const& comment) { m_ptrFile->write(std::string(m_layer, '\t') + ""); }

void close_tag_tree() { if (m_tags.empty() == true || m_layer == 0) return; // any tag_tree exist?

--m_layer; m_ptrFile->write(std::string(m_layer, '\t') + "</" + m_tags.back() + ">\n" ); m_tags.pop_back(); } };

  1. endif // LOGHANDLER_HPP

MyLogHandler.hpp

A wrapper class for the logging system might look something like this.

File: MyLogHandler.hpp

  1. ifndef MYLOGHANDLER_HPP
  2. define MYLOGHANDLER_HPP
  1. if (_MSC_VER >= 1300)
  2. pragma once
  3. endif // (_MSC_VER >= 1300)
  1. include <string>
  2. include <ctime>
  1. include "LogHandler.hpp"

class CMyLogHandler : public CLogHandler { public: CMyLogHandler(std::string const & file_name) : CLogHandler(file_name) { m_ptrFile->write("<?xml-stylesheet type=\"text/xsl\" href=\"stylesheet.xsl\" ?>\n\n"); add_comment("Created by XMLLogger"); }

virtual ~CMyLogHandler() {}

template <typename T> void write_log(std::string const & class_name, std::string const & function_name, std::string const & debug_type, T const& info, std::string const& file, const long line) { std::time_t time(std::time(NULL));

add_tag_tree("LogEntry"); add_tag("Class", class_name); add_tag("Function", function_name); add_tag("DebugType", debug_type); add_tag("Timestamp", std::ctime(&time)); add_tag<T>("Info", info); add_tag("Line", line); add_tag("File", file); close_tag_tree(); // LogEntry } };

  1. endif // MYLOGHANDLER_HPP

Example Usage

File: main.cpp

  1. include "MyLogHandler.hpp"
  1. define COMPILE_TIMESTAMP __DATE__ " " __TIME__
  2. define FILE_INFO __FILE__, __LINE__

int main(int argc, char* argv[]) { CMyLogHandler log("DebugLog.xml");

log.add_tag_tree("Project"); log.add_tag("Name", "XMLLogger");

log.add_tag("Type", "Project"); log.add_tag("FileName", __FILE__); log.add_tag("Compiled", COMPILE_TIMESTAMP);

log.add_tag_tree("CmdArguments"); for (int i(0); i < argc; ++i) log.add_tag("CmdArgument", argv[i]); log.close_tag_tree(); // CmdArguments

log.add_tag_tree("CommentLines"); log.add_tag("CommentLine", "This is a test"); log.add_tag("CommentLine", "of multiple lines of"); log.add_tag("CommentLine", "comments."); log.close_tag_tree(); // CommentLines

log.add_tag_tree("LogEntries"); log.write_log("Main", "main", "Info", "Starting up", FILE_INFO); log.write_log("Main", "main", "Info", "Second test", FILE_INFO); log.write_log("Main", "main", "Warning", "A fake warning", FILE_INFO); log.write_log("CLogHandler", "Write", "Info", "Writing down some stuff...", FILE_INFO); log.write_log("Main", "main", "Error", "A fake error!", FILE_INFO); log.write_log("Main", "main", "Critical", "Terminating", FILE_INFO); log.write_log("Main", "main", "Info", "(I'm fine, don't worry!)", FILE_INFO); log.write_log("Main", "main", "Info", "Look below it's a integer!", FILE_INFO); log.write_log("Main", "main", "Info", 666, FILE_INFO); log.close_tag_tree(); // LogEntries }


As you might be able to tell from the source code, you can add tags and tag-trees. For instance, consider the code below:

add_tag_tree("Project"); add_tag_tree("Files"); add_tag_tree("File"); add_tag_tree("CodeModule");

That would result in a XML-file that would like like this:

<Project> <Files> <File> <CodeModule></CodeModule> </File> </Files> </Project>

If you need to data, you have to use the add_tag()-function. Here's an example:

add_tag_tree("Project"); add_tag_tree("Files"); add_tag_tree("File");

add_tag("FileName", "Test file");

add_tag_tree("CodeModule");

add_tag("SomeCode", "For each some object do something"); add_tag("Global", false);

Would result in:

<Project> <Files> <File> <FileName>Test file</FileName> <CodeModule> <SomeCode>For each some object do something</SomeCode> <Global>false<Global> </CodeModule> </File> </Files> </Project>

The CLogHandler class automaticly closes all the open tag trees when the class is shut down, but if you want to add another <File> tag tree, for instance, you need to call close_tag_tree(). That will close the inner-most tag tree - which is the lastest created tag tree. To add another <File> tag tree, you should use the code below:

add_tag_tree("Project"); add_tag_tree("Files"); add_tag_tree("File");

add_tag("FileName", "Test file");

add_tag_tree("CodeModule");

add_tag("SomeCode", "For each some object do something"); add_tag("Global", false);

close_tag_tree(); // CodeModule close_tag_tree(); // File

add_tag_tree("File"); add_tag_tree("CodeModule");

add_tag("SomeCode", "Do until true == false");

Would result in an XML-file with the following text:

<Project> <Files> <File> <FileName>Test file</FileName> <CodeModule> <SomeCode>For each some object do something</SomeCode> <Global>false<Global> </CodeModule> </File> <File> <CodeModule> <SomeCode>Do until true == false</SomeCode> </CodeModule> </File> </Files> </Project>

Displaying the XML file

If you run the application, you should get a file called "DebugLog.xml" that you can open with any text editor or internet browser. It shouldn't be too hard to read, but what we want is to give the XML-file an elegant website-like appearence. For that purpose we need a XML stylesheet template, which is like a CSS-script, just for XML-files instead of for HTML-files. The stylesheet is a bit lengthy, so I won't post the code here, but you'll find a working stylesheet in the download below.


C++ source code for the logging system and an example XML stylesheet:

Files:XMLLoggerVC6.zip [7kB] (Note: It's not a direct download link. Just click the link, and you'll be taken to the file download page)


If you're too lazy to download the file, then you can view the XML file online right here. You can also link your XML file to an online stylesheet, so that any changes you make to the stylesheet will be reflected when users view their local XML file data. Cool, huh? And simple, too!


By Anders "Sion" Nissen

Added 24. March 2005

Updated 24. March 2005


Alternatives

C/C++

  • TinyXML - A simple, small, C++ XML parser that can be easily integrating into other programs.

Java

  • Log4cxx - A high performance, highly flexible Logging Framework similar to the Log4J Java Logging framework. It is possible to output XML using this Framework. The benefits of using Log4cxx is that there already exist many tools for reading/interpreting Log4X style logs.
    Log4cxx was designed for performance and in "gold" builds can be completely #ifdef'ed out for a zero performance hit.

VB .NET

  • XML INI Class - A VB .NET class that nicely wraps the functionality of a regular INI file into a XML file.