Tutorials: Code
Using Pantheios in application C++ code is extremely simple. The syntax
of all function suites follow the same form, which is to say that log statement
elements are passed as (comma-separated) arguments to the requisite function, as in:
pantheios::log_CRITICAL("abc", def, "ghi", pantheios::integer(j), "klm");
or
pantheios::log(pantheios::critical, "abc", def, "ghi", pantheios::integer(j), "klm");
The Pantheios application layer is generic, that is to say that it
accepts any types (with restrictions as described below).
Fundamentally (in the core), Pantheios understands only (slices of) character strings.
All other types have to be converted to this form. To use Pantheios it is
best to understand how the library treats different types to provide the
generic support. There are four sets of argument types to consider:
- Types for which string access shims are defined
- Numeric and pointer types
- Types for which string access shims are not defined
1. Types for which string access shims are defined.
Out of the box, Pantheios comes with string access shims (via the
requisite STLSoft header files) defined for the following types:
- char[]
- char const*
- std::string
- std::exception
- struct tm const&
- struct tm const*
- CComBSTR (if compiling with ATL)
- CWindow (if compiling with ATL)
- GUID (if compiling with COM)
- VARIANT (if compiling with COM)
- CWnd (if compiling with MFC)
- CString (if compiling with MFC)
- CListBox (if compiling with MFC)
- CListCtrl (if compiling with MFC)
- CListView (if compiling with MFC)
- struct dirent const& (if compiling on UNIX)
- struct dirent const* (if compiling on UNIX)
- HWND (if compiling on Win32)
- LSA_UNICODE_STRING (if compiling on Win32)
- FILETIME (if compiling on Win32)
- SYSTEMTIME (if compiling on Win32)
Thus, a log statement containing any of these types is implicitly supported
by Pantheios. For example, on UNIX, we can write the following:
#include <pantheios/pantheios.hpp>
std::string s1;
std::exception x;
char s2[10];
struct tm* t = . . .;
struct dirent* d = . . .;
pantheios::log(pantheios::critical, s1, x, s2, t, d);
On Windows, we can write the following:
#include <pantheios/pantheios.hpp>
std::string s1;
std::exception x;
char s2[10];
struct tm* t = . . .;
HWND hwnd = . . .;
FILETIME ft;
SYSTEMTIME st;
pantheios::log(pantheios::critical, s1, x, s2, t, hwnd, ft, st);
Furthermore, string access shims for all types for which a meaningful
string representation may be elicited are also defined for components in the
following libraries:
This means that the components from these libraries are also implicitly
compatible with Pantheios.
2. Numeric and pointer types.
Numeric and pointer types and untyped memory are not implicitly
compatible with the Pantheios application layer. However,
they may be made compatible by use of the Pantheios inserter types
integer, pointer
and real, as follows:
#include <pantheios/pantheios.hpp>
#include <pantheios/inserters/integer.hpp>
#include <pantheios/inserters/pointer.hpp>
#include <pantheios/inserters/real.hpp>
short s;
int i;
long l;
float f;
void* p;
pantheios::log_NOTICE("short=", pantheios::integer(s)
, " int=", pantheios::integer(i)
, " long=", pantheios::integer(l)
, " float=", pantheios::real(f)
, " ptr=", pantheios::pointer(p));
Width, radix and prefixing is controlled by the second, defaulted,
parameter to the constructors of the three types, as follows:
int i = 255;
pantheios::integer(i, pantheios::fullHex); // yields "0xff"
and
void* i = reinterpret_cast<void*>(255);
pantheios::pointer(i, 8 | pantheios::fullHex); // yields "0x000000ff"
3. Types for which string access shims are not defined.
If you pass an instance of a type for which string access shims are not
defined to a Pantheios application layer function, you will receive a
compilation error:
class Point
{
public:
Point(int x, int y)
: x(x)
, y(y)
{}
public:
int x;
int y;
};
Point point(10, 20);
pantheios::log_ERROR(point); // Error: none of the 17 overloads of 'c_str_len_a' can convert parameter 1 from type 'X'
There are several ways to log instances of such types:
- Write explicit conversion code in your application code.
if(pantheios::isSeverityLogged(pantheios::error))
{
pantheios::log_ERROR("Point: {", point.x, ", ", point.y, "; area=", point.x * point.y, "}");
}
- Write explicit conversion function to use in log statements.
std::string point_to_string(Point const& point)
{
char buff[101];
::sprintf(&buff[0], "{%d, %d; area=%d", point.x, point.y, point.x * point.y);
return buff;
}
This can be passed as part of the log statement:
pantheios::log_ERROR("Point: ", point_to_string(point));
- Define string access shims for the type.
Option 1 is verbose and error prone. Option 2 is inefficient,
especially in the case when the ERROR severity is not being
logged. A better, and more portable, solution is to define
string access shims for the Point type:
namespace stlsoft
{
inline stlsoft::shim_string<char> c_str_data_a(Point const& point)
{
stlsoft::shim_string<char> s(101);
int cch = ::sprintf(s, "{%d, %d; area=%d", point.x, point.y, point.x * point.y);
s.truncate(static_cast<size_t>(cch));
return s;
}
inline size_t c_str_len_a(Point const& point)
{
char buff[101];
return static_cast<size_t>(::sprintf(&buff[0], "{%d, %d; area=%d", point.x, point.y, point.x * point.y));
}
} // namespace stlsoft
In this case, the type can be passed directly to the log statement:
pantheios::log_ERROR("Point: ", point);
Note: There is a slight inefficiency in this case in that
c_str_len_a invokes
sprintf() in order to ascertain the exact
length of the point. In a real scenario we would use an algorithm to
calculate the resultant string length without incurring the cost of
a call to sprintf().
- Define an inserter class for the type.
The inserter class should use lazy evaluation, such that it only
performs the conversion, once, when the first one of either its
data()
or
length() methods is called.
class point_inserter
{
point_inserter(Point const& point)
: m_point(point)
, m_length(0)
{
m_value[0] = '\0';
}
char const* data() const
{
if('\0' == m_value[0])
{
construct_();
}
return m_value;
}
size_t length() const
{
if('\0' == m_value[0])
{
construct_();
}
return m_length;
}
private:
void construct_() const
{
const_cast<point_inserter*>(this)->construct_();
}
void construct_()
{
assert('\0' == m_value[0]);
assert(0 == m_length);
m_length = static_cast<size_t>(::sprintf(&m_value[0], "{%d, %d; area=%d", point.x, point.y, point.x * point.y));
}
private:
Point const& m_point;
size_t m_length;
char m_value[101];
};
namespace stlsoft
{
inline char const* c_str_data_a(point_inserter const& pi)
{
return pi.data();
}
inline size_t c_str_len_a(point_inserter const& pi)
{
return pi.length();
}
}
This can be passed as part of the log statement:
pantheios::log_ERROR("Point: ", pointer_inserter(point));
Although this is quite a bit of code, it offers the most efficient solution
and, once written, such classes can form part of a growing library of
inserters that can be used in arbitrary projects.
|