The policy interface simply provides three functions to open and close an output stream and a write operation. For the file logging policy, the code will use the C++ std::ofstream to direct the output to the disk. The complete implementation for the file logging policy is visible in the Listing Four.
Listing Four: The file log policy implementation.
void file_log_policy::open_ostream(const std::string& name)
{
out_stream->open( name.c_str(), std::ios_base::binary|std::ios_base::out );
if( !out_stream->is_open() )
{
throw(std::runtime_error("LOGGER: Unable to open an output stream"));
}
}
void file_log_policy::close_ostream()
{
if( out_stream )
{
out_stream->close();
}
}
void file_log_policy::write(const std::string& msg)
{
(*out_stream)<<msg<<std::endl;
}
file_log_policy::~file_log_policy()
{
if( out_stream )
{
close_ostream();
}
}
There's nothing special to comment on here. If the stream-opening operation fails, the function throws a std::runtime_error (line 6). Obviously, a different behavior can be provided.
In this implementation, an exception is thrown exception because the logging facility in my applications is not optional, but crucial (as it is for the firm I work for). In fact, the logging functionalities are seen as features of the software that the customer will use, so if this feature is not working properly, then it is often right to abort the application startup.
The Core Printing Functionality
Every call to a logging macro will be expanded in an invocation of the print function of the logger instance. This function actually does some formatting activities on the log message and use the write function from the used policy to stream out the log string. The Listing Five shows the print function implementation.
Listing Five: The print function.
template< typename log_policy >
template< severity_type severity , typename...Args >
void logger< log_policy >::print( Args...args )
{
write_mutex.lock();
switch( severity )
{
case severity_type::debug:
log_stream<<"<DEBUG> :";
break;
case severity_type::warning:
log_stream<<"<WARNING> :";
break;
case severity_type::error:
log_stream<<"<ERROR> :";
break;
};
print_impl( args... );
write_mutex.unlock();
}
Here, you should be familiar with the variadic functions: print is a variadic, which actually accepts as formal argument the parameter pack Args and one more template argument, severity. Actually, severity is an enum, which can be one of three values as shown in Listing Six:
Listing Six: The severity_type enum.
enum severity_type
{
debug = 1,
error,
warning
};
The severity provided as a template parameter is used in the switch statement at line 6 of Listing Five to add the proper severity description to the log message: debug ,warning, or error. The variable log_stream used in the print function implementation is an attribute of the logger class, actually of a std::stringstream type. The first operation at line 5 is a lock request to write_mutex, this is needed to ensure thread safety by guaranteeing that no more than one print operation is performed at the same time. This lock is released at line 19 after the operation has finished. Please note that the locking request is a blocking operation if the mutex is already acquired by a different thread, a wait-free version can use a buffering system to store the log messages until the lock is released. The call to print_impl at line 18 is show next in Listing Seven.
Listing Seven: print_impl implementation.
template< typename log_policy >
void logger< log_policy >::print_impl()
{
policy->write( get_logline_header() + log_stream.str() );
log_stream.str("");
}
template< typename log_policy >
template<typename First, typename...Rest >
void logger< log_policy >::print_impl(First parm1, Rest...parm)
{
log_stream<<parm1;
print_impl(parm...);
}
If you're not familiar with the variadic functions, you might not be able to understand why this function has two bodies. It's done this way to access the parameters in the variadic parameter pack. If you look at line 12, you'll see that the function is recursive, and at every call the first argument expanded from parm... is used to, let's say, fill parm1, while all the others are filling the argument pack Rest... .
The value of parm1 is stored at line 11, then the recursion happens again with one parameter less. At a certain point, Rest... will be empty, the last value will be printed and the last call to print_impl performed. If parm... is empty, the recursion is ended by an invocation of the print_impl version that started at line 1. This print_impl version just makes a call to the write function of the logging policy, passing a std::string consisting of a log message preceded by the header, which contains data like the timestamp, the log line number, and so on.




