Re: Question about singleton class design for tracing
On May 23, 12:08 pm, Stephen Torri <sto...@torri.org> wrote:
I am trying to produce a singleton class that I can use throughout my
library to write tracing information to a file. My intent was to design
such that someone using the library in its debug mode would be able to
see what was happening without having to use a debugger to step through
each instruction. What they would do is run their program and view the
tracing file output. If there was something wrong then they would use the
debugger of their choosing.
What I am running into is a segfault when I run a test program that uses
on part of the library rather than through the main api interface. The
segfault is coming from the std::num_put as a part of the STL. Since that
is so well tested I am assuming my problem lies in my design of the
singleton class. I modeled it after the GoF singleton pattern.
I would appreciate any helps on the design:
#include <boost/format.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <sstream>
#include <fstream>
#ifndef WIN32
#include <unistd.h>
#else
#include <windows.h>
#endif /* WIN32 */
namespace libreverse { namespace api {
class TraceLevel {
public:
static boost::uint32_t TraceNone; // No trace
static boost::uint32_t TraceWarn; // Only trace warning
static boost::uint32_t TraceError; // Only trace error
static boost::uint32_t TraceInfo; // Some extra information
static boost::uint32_t TraceDebug; // Debugging information
static boost::uint32_t TraceDetail; // Detailed debugging
information
static boost::uint32_t TraceData; // Output data
};
boost::uint32_t TraceLevel::TraceNone = 0;
boost::uint32_t TraceLevel::TraceWarn = 10;
boost::uint32_t TraceLevel::TraceError = 20;
boost::uint32_t TraceLevel::TraceInfo = 30;
boost::uint32_t TraceLevel::TraceDebug = 40;
boost::uint32_t TraceLevel::TraceDetail = 50;
boost::uint32_t TraceLevel::TraceData = 60;
} /* namespace api */
} /* namespace libreverse */
namespace libreverse { namespace trace {
class Trace_State {
public:
typedef boost::shared_ptr<Trace_State> ptr_t;
static Trace_State& Instance()
{
if ( m_instance == 0 )
{
m_instance = new Trace_State();
}
return *m_instance;
}
void set_Trace_File_Prefix ( std::string name )
{
assert ( ! name.empty() );
// Lock the resource
// Close the present file
m_file_prefix = name;
// Unlock the resource
}
void set_Trace_Level ( boost::uint32_t level )
{
// Lock the resource
// Change level
m_trace_level = level;
// Unlock the resource
}
void open_Trace_File ( void )
{
if ( ! m_log_stream.is_open() )
{
// Create file name
std::stringstream name;
name << boost::format("%s_%s.txt")
% m_file_prefix
% this->get_ID_String();
m_log_stream.open ( (name.str()).c_str() );
}
}
std::string get_ID_String ( void )
{
// Create id string
std::stringstream name;
// Get current time
boost::posix_time::ptime now =
boost::posix_time::second_clock::local_time();
std::tm tm_ref = boost::posix_time::to_tm ( now );
boost::gregorian::date today = now.date();
name << boost::format ( "%s_%02d:%02d:%02d" )
% boost::gregorian::to_iso_extended_string ( today )
% tm_ref.tm_hour
% tm_ref.tm_min
% tm_ref.tm_sec;
return name.str();
}
void close_Trace_File ( void )
{
if ( m_log_stream.is_open() )
{
m_log_stream.close();
}
}
boost::uint32_t get_Trace_Level ( void ) const
{
boost::uint32_t level = 0;
// Lock the resource
// get the level
level = m_trace_level;
// unlock the resource
// return the level
return level;
}
void write_Message ( boost::uint32_t level, std::string msg )
{
// Write ID
m_log_stream << boost::format("%s_%d: " )
% this->get_ID_String()
#ifndef WIN32
% getpid()
#else
% GetCurrentProcessId()
#endif /* WIN32 */
<< std::flush;
// Write message prefix
if ( level == libreverse::api::TraceLevel::TraceWarn )
{
m_log_stream << "(WW) ";
}
else if ( level == libreverse::api::TraceLevel::TraceError )
{
m_log_stream << "(EE) ";
}
else if ( level == libreverse::api::TraceLevel::TraceInfo )
{
m_log_stream << "(II) ";
}
else if ( level == libreverse::api::TraceLevel::TraceDebug )
{
m_log_stream << "(DEBUG) ";
}
else if ( level == libreverse::api::TraceLevel::TraceDetail )
{
m_log_stream << "(DETAIL) ";
}
else if ( level == libreverse::api::TraceLevel::TraceData )
{
m_log_stream << "(DATA) ";
}
else
{
// We should not be here
abort();
}
// Write to the file
m_log_stream << msg << std::endl << std::flush;
// Unlock the resource
}
private:
Trace_State()
: m_file_prefix ( "Trace" ),
m_trace_level ( libreverse::api::TraceLevel::TraceNone )
{}
~Trace_State()
{
delete m_instance;
this->close_Trace_File();
}
static Trace_State* m_instance;
std::string m_file_prefix;
boost::uint32_t m_trace_level;
std::ofstream m_log_stream;
};
class Trace {
public:
#ifdef LIBREVERSE_DEBUG
bool write_Trace ( boost::uint32_t level,
std::string message )
{
// If the level is equal to or greater than the present
// level we record out message.
if ( ( Trace_State::Instance().get_Trace_Level() != 0 ) &&
( level <= Trace_State::Instance().get_Trace_Level() ) )
{
Trace_State::Instance().write_Message ( level,
message );
}
return true;
}
#else
bool write_Trace ( boost::uint32_t,
std::string )
{
return true;
}
#endif
};
Trace_State* Trace_State::m_instance = 0;
} /* namespace trace */
} /* namespace libreverse */
using namespace libreverse::trace;
using namespace libreverse::api;
int main ( int, char** )
{
Trace_State::Instance().set_Trace_Level ( TraceLevel::TraceDetail );
Trace_State::Instance().open_Trace_File ();
Trace_State::Instance().close_Trace_File ();
return 0;
}
You have absolutely no control over the order in which non-local
static objects in different translation unites are initialized.
This is a problem if you have global or static objects who's
constructor is trying to access your tracing library.
With this type of requirement, the main concern is that you need to
make sure that all your tracing library global and static objects can
safely be created before any other global or static object tries
accessing it.
You also have the opposite problem, in that you need to make sure your
tracing library global and static object is still existing when the
last global/static object destructor's try accessing it.
The only safe way to do this in a multiple translation unit
environment, is to put all your global and static objects in a wrapper
function.
static foo& GetGlblFoo()
{
static foo *local_foo = new foo();
return *local_foo;
}
This insures that your object is initialized the first time it's
called. It also insures that it's still around when the last global
object's destructor tries calling it.
The down side to above logic, is that the destructor will never get
called.