Channels ▼
RSS

C/C++

Logging In C++

Source Code Accompanies This Article. Download It Now.


Logging is a critical technique for troubleshooting and maintaining software systems. It's simple, provides information without requiring knowledge of programming language, and does not require specialized tools. Logging is a useful means to figure out if an application is actually doing what it is supposed to do. Good logging mechanisms can save long debugging sessions and dramatically increase the maintainability of applications.

In this article, I present a simple—but highly useful—logging framework that is typesafe, threadsafe (at line-level), efficient, portable, fine-grained, compact, and flexible. The complete source code, which works with Visual C++ 7.1, g++ 3.3.5, and CC 5.3 on Sun and other platforms, is available in the DDJ October 2007 zip file at www.ddj.com/code/.

In Part 2 of this article, I enhance this technique by providing more granularity.

The First Step

Let's take a first stab at a Log class. Listing One uses an std::ostringstream member variable called "os" to accumulate logged data. The Get() member function gives access to that variable. After all data is formatted, Log's destructor persists the output to the standard error. You use Log class like this:

Log().Get(logINFO) << "Hello " << username;

Executing this code creates a Log object with the logINFOx logging level, fetches its std::stringstream object, formats and accumulates the user-supplied data, and finally, persists the resulting string into the log file using exactly one call to fprintf().

Why flush during destruction? The last two steps are important because they confer threadsafety to the logging mechanism. The fprintf() function is threadsafe, so even if this log is used from different threads, the output lines won't be scrambled. According to gnu.org/software/libc/manual/html_node/Streams-and-Threads.html:

Listing One

// Log, version 0.1: a simple logging class
enum TLogLevel {logERROR, logWARNING, logINFO, logDEBUG, logDEBUG1,
logDEBUG2, logDEBUG3, logDEBUG4};
class Log
{
public:
   Log();
   virtual ~Log();
   std::ostringstream& Get(TLogLevel level = logINFO);
public:
   static TLogLevel& ReportingLevel();
protected:
   std::ostringstream os;
private:
   Log(const Log&);
   Log& operator =(const Log&);
private:
   TLogLevel messageLevel;
};
std::ostringstream& Log::Get(TLogLevel level)
{
   os << "- " << NowTime();
   os << " " << ToString(level) << ": ";
   os << std::string(level > logDEBUG ? 0 : level - logDEBUG, '\t');
   messageLevel = level;
   return os;
}
Log::~Log()
{
   if (messageLevel >= Log::ReportingLevel())
   {
      os << std::endl;
      fprintf(stderr, "%s", os.str().c_str());
      fflush(stderr);
   }
}

The POSIX Standard requires that by default the stream operations are atomic...issuing two stream operations for the same stream in two threads at the same time will cause the operations to be executed as if they were issued sequentially. The buffer operations performed while reading or writing are protected from other uses of the same stream. To do this, each stream has an internal lock object that has to be (implicitly) acquired before any work can be done.

Before moving on to a more efficient implementation, let's write code to insert tabs in proportion to the logging level, and append an std::endl to each chunk of text. This makes the log line oriented and easy to read by both humans and machines. Here's the relevant code:

Log::ReportingLevel() = logDEBUG2;
const int count = 3;
Log().Get(logDEBUG) << "A loop with "    << count << " iterations";
for (int i = 0; i != count; ++i)
{
   Log().Get(logDEBUG1)        << "the counter i = " << i;
}

which outputs:

- 22:25:23.643 DEBUG: 	A loop with 3 iterations
- 22:25:23.644 DEBUG1: 	the counter i = 0
- 22:25:23.645 DEBUG1: 	the counter i = 1
- 22:25:23.645 DEBUG1: 	the counter i = 2

Indentation makes the logging more readable. More leading tabs imply a more detailed level of logging.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Comments:

ubm_techweb_disqus_sso_-0972aef8dccd8d3f7ee54e9eccdd9489
2014-07-28T21:54:00

This still is the best startup / tutorial for a logger in C++. Simple, extendable and well documented. I used this as a base framework and integrated logging from a calling Python interpreter to merge C++ and Python log. I learned a lot about simplicity in logging. Too much log4xyz monsters flying around, where you need a training before writing a single line to the log.


Permalink
ubm_techweb_disqus_sso_-7958d1694f3140e7c5f8bf2c24c75600
2013-07-26T00:31:36

Late reply is late, but I only ran across this just now while looking to save myself from work writing a logging facility for a new app.

If you were to dynamically create an instance of the Log class and use that for all your logging throughout your application, what you say here would be correct. However, using the macro as presented, the object automatically created goes out of scope as soon as you exit the if-else statement where it exists. The destructor is therefore called immediately.


Permalink
ubm_techweb_disqus_sso_-8c406ec5e958c6d539838d8ca7b1a4b2
2013-04-23T11:04:15

EasyLogging++ is the way to go that is fast light weight an high performing with extra features!


Permalink
ubm_techweb_disqus_sso_-3bd7ed18ad870a7da3abfed0c7a579da
2012-12-18T19:31:36

I like this log facility. I made one change that adds some value for me. I added a logNONE enum at the first position (index=0) with the idea that it would not be used to log to. If used when setting the ReportingLevel() none of the log entries will be printed.


Permalink
ubm_techweb_disqus_sso_-f771ac9bb3698b0374ec8af2c83562cf
2012-12-04T14:43:43

nice ..........


Permalink
ubm_techweb_disqus_sso_-db6b242b43e4ba27dd6c28e696b2715c
2012-09-06T22:50:06

I am not going to say that this is not the (one) way of doing it.
However, there is one issue here. It seems that all logs has been appended to the string, and at the end of the life of application that string is written to the file. Is that 'buffering" taken to extreme?
Does some one really wants to allow the log to be accumulated in the application memory indefinitly?


Permalink
ubm_techweb_disqus_sso_-3d19a21b5840097aa2f922d24dc572b3
2012-07-08T00:29:19

Great little header file. Im using it as a simple logger for some research Im doing. One small problem I had was that static boost::mutex mtx inside the Output2File class refused to compile and gave me a linker error. I moved it out of the class as a global static variable and that fixed it.


Permalink
ubm_techweb_disqus_sso_-ab950343e97df05f1262e24666111fb6
2012-04-20T15:47:23

I can't open logcpp.zip which is no suprise since the file's size is only 44 Bytes.

And the "0710.zip" download link doesn't seem to work for me.


Permalink
ewuewu
2012-03-24T12:37:17

Click the "0710.zip" instead, it contains the logcpp.zip, too.


Permalink
ubm_techweb_disqus_sso_-63b76b69149f89dba9e79f77d9327fec
2012-03-14T15:54:56

Having trouble unzipping this file.


Permalink

Video