The Lumber has arrived!
Freshly hewn but unfinished pile of Code Lumber is below, ideal for building your debuggin' & audtin' & tree-house solutions.The latest versions of the code and doxygen docs are all over at the libfbp git repository github.com/ifknot/libfbp
Logging Services
The logging services package is contained within the log namespace and consists of:- logger - the thread-safe policy based logging class that uses C++11 variadic templates and initializer lists.
- stamp_policy - an abstract interface for classes providing std::string log stamps for log records.
- line_number_stamp - a line number producer.
- unix_time_stamp - a unix time stamp producer .
- core_stamps - convenience header file of typical log record stamps.
N.B.
I couldn't bring myself to implement the logger as a singleton. Although this might seem one of those (increasingly) rare cases where a singleton might seem appropriate I can't bring myself to imagine that I would only ever want 1 logger.
I am now completely won over by the arguments in this matter and have deprecated the singleton pattern.[2,3]
Serializing Services
However, logging services depend upon a second package in the io namespace that consists of:- serializor_policy - an abstract template interface for classes providing serializing[1] services.
- null_serializor - a serializor that does nothing, which is handy when you don't want any output.
- console_serializor - a serializor that displays output to the console, handy for debugging.
- file_serializor - a serializor that directs output to disk.
Error Messages
Finally, logging services also rely on the error message constant strings defined in the errors.h header file within the doh namespace. Separating out the error messages into a separate header file enables the dual goals of:
- fungibility - enabling interchangeable error messages for different language locales or audiences.
- no magic strings - fighting the war on magic strings.[4]
Todo:
In order to work well with parallelizable concurrent FBP programs, as per the initial strategy described here, without imposing undue waits it is clear that logging should be buffered to a background thread.
- asynchronous_serializor - a serializor that uses an active object to wrap more primitive serializors.
Listing 1: usage
Usage example for my logger class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "resources/errors.h" | |
#include "io/console_serializor.h" | |
#include "io/file_serializor.h" | |
#include "logger.h" | |
int main() { | |
//build a logger with a console serializor policy and some stamp trinkets | |
log::logger<io::console_serializor> | |
clog("", { // an intializer list of stamp trinkets | |
new log::line_number_stamp(), //auto line number starting 0 | |
new log::line_number_stamp(10), //auto line number starting 10 | |
new log::unix_time_stamp() //a unix timestamp | |
} | |
); | |
//build a logger with a file serializor policy and some stamp trinkets | |
log::logger<io::file_serializor> flog("test.log", { new log::line_number_stamp(), new log::line_number_stamp(10), new log::unix_time_stamp()}); | |
//test them using the variadic write fucntion | |
for (int i = 0; i < 10; ++i) { | |
clog.write("hello ", "world ", 1,1,1); | |
flog.write("hello ", "file " "world ", 1,2,3); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
0 10 1389904038 hello world 111 | |
1 11 1389904038 hello world 111 | |
2 12 1389904038 hello world 111 | |
3 13 1389904038 hello world 111 | |
4 14 1389904038 hello world 111 | |
5 15 1389904038 hello world 111 | |
6 16 1389904038 hello world 111 | |
7 17 1389904038 hello world 111 | |
8 18 1389904038 hello world 111 | |
9 19 1389904038 hello world 111 | |
Program ended with exit code: 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
0 10 1389904038 hello file world 123 | |
1 11 1389904038 hello file world 123 | |
2 12 1389904038 hello file world 123 | |
3 13 1389904038 hello file world 123 | |
4 14 1389904038 hello file world 123 | |
5 15 1389904038 hello file world 123 | |
6 16 1389904038 hello file world 123 | |
7 17 1389904038 hello file world 123 | |
8 18 1389904038 hello file world 123 | |
9 19 1389904038 hello file world 123 |
Listing 2: logger
A policy based approach to fungibility for providing traces of execution in a log file(s). Policy-based design (a.k.a. policy-based class design or policy-based programming) being a computer programming design pattern based on a C++ template metaprogramming idiom as described in the book "Modern C++ Design" (Andrei Alexandrescu 2001) - I see it is a compile-time variant of the PIMPL pattern.
Employs the Resource Aquisition Is Initialization (RAII) pattern, Bjarne Stroustrup(2000), where the basic idea is to represent a resource by a local object, so that the local object's destructor will release the resource.
The write_policy is managed by the logger constructor and destructor and the log_mutex is managed by the std::lock_guard. A simple, eloquent and efficient way to deal with many situations where there is a risk of “leaking” memory or handles, failing to release file locks, mutexes, etc.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <sstream> | |
#include <stdexcept> | |
#include <initializer_list> | |
#include <mutex> | |
#include "../io/serializor_policy.h" | |
#include "core_stamps.h" | |
namespace log { | |
template<typename policy> | |
class logger { | |
public: | |
logger(const std::string name): | |
write_policy(new policy) { | |
help_init(name); | |
} | |
logger(const std::string name, std::initializer_list<stamp_policy*> stamp_list): | |
write_policy(new policy), | |
stamps(stamp_list) { | |
help_init(name); | |
} | |
template<typename...Args> | |
void write(Args...args) { | |
std::lock_guard<std::mutex> lock(log_mutex); | |
for(auto stamp: stamps) | |
log_stream << stamp->str(); | |
do_write(args...); | |
log_stream << std::endl; | |
} | |
virtual ~logger() { | |
write_policy->close(); | |
delete(write_policy); | |
for(auto stamp: stamps) | |
delete(stamp); | |
} | |
private: | |
void help_init(const std::string name) { | |
if (write_policy) { | |
write_policy->open(name); | |
} else { | |
throw std::runtime_error(doh::FAIL + doh::NO_POLICY); | |
} | |
} | |
void do_write() { | |
write_policy->write_object(log_stream.str()); | |
log_stream.str(std::string()); | |
} | |
template<typename First, typename...Next> | |
void do_write(First first, Next...next) { | |
log_stream << first; | |
do_write(next...); | |
} | |
io::serializor_policy<std::string>* write_policy; | |
std::initializer_list<stamp_policy*> stamps; | |
std::stringstream log_stream; | |
std::mutex log_mutex; | |
}; | |
} |
Listing 2: serializor policy
A design by contract abstract template interface to typesafe serialization resources.
Design by contract(DbC) requires pre & post condition checking, typical public virtual interface design does not permit this behaviour. Rather, prefer the Non-Virtual Interface (NVI), Herb Sutter (2001), to enable DbC and separate the stable, non-virutal interface definition from virtual customizable behaviour.
N.B. precondition: Object of type T must be serializable i.e. there exists an overloaded operator << such that...
std::ostream& operator<<(std::ostream& s, const O& object)
Also, unifies the approach to serialization within a framework.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <string> | |
#include <iostream> | |
#include <cassert> | |
namespace io { | |
template<typename T> | |
class serializor_policy { | |
public: | |
void open(const std::string name) { | |
assert(is_closed()); | |
do_open(name); | |
assert(is_open()); | |
} | |
void write_object(const T& obj) { | |
assert(is_open()); | |
do_write_object(obj); | |
assert(is_written()); | |
} | |
void close() { | |
assert(is_open()); | |
do_close(); | |
assert(is_closed()); | |
} | |
virtual ~serializor_policy() {} | |
private: | |
virtual void do_open(const std::string name) = 0; | |
virtual void do_write_object(const T& obj) = 0; | |
virtual void do_close() = 0; | |
virtual bool is_closed() { return(true); } | |
virtual bool is_open() { return(true); } | |
virtual bool is_written() { return(true); } | |
}; | |
} |
Listing 4a: file serializor definition
Simple concrete serializor policy that writes string objects to the C++ std::ofstream to direct the output to the disk.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "serializor_policy.h" | |
#include "../resources/errors.h" | |
#include <fstream> | |
namespace io { | |
class file_serializor: public serializor_policy<std::string> { | |
public: | |
file_serializor(); | |
virtual ~file_serializor(); | |
private: | |
virtual void do_write_object(const std::string& obj) override; | |
virtual void do_open(std::string name) override; | |
virtual void do_close() override; | |
std::ofstream* output_stream; | |
}; | |
} |
Listing 4b: file serializor implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "file_serializor.h" | |
namespace io { | |
file_serializor::file_serializor(): | |
output_stream(new std::ofstream) {} | |
void file_serializor::do_write_object(const std::string &obj) { | |
(*output_stream) << obj; | |
} | |
void file_serializor::do_open(std::string name) { | |
output_stream->exceptions( std::ifstream::failbit | std::ifstream::badbit); | |
if (name == "") | |
throw std::runtime_error(doh::FAIL + doh::BAD_NAME); | |
try { | |
output_stream->open(name); | |
} | |
catch (std::ofstream::failure& e) { | |
throw std::runtime_error(doh::FAIL + doh::NO_RESOURCE); | |
} | |
} | |
void file_serializor::do_close() { | |
output_stream->close(); | |
} | |
file_serializor::~file_serializor() { | |
if (output_stream) | |
delete output_stream; | |
} | |
} |
Listing 5: error descriptions, english(UK), resource file
Error message string constants for the english(UK) locale.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <string> | |
namespace doh { | |
//Opinions | |
const std::string FAIL = " (╯°□°)╯︵ ┻━┻ "; | |
const std::string DISAPPROVE = " ಠ_ಠ "; | |
const std::string APPROVE = " d(^_^d) "; | |
const std::string UNKNOWN = " ¯\\(°_o)/¯ "; | |
//Policy | |
const std::string NO_POLICY = "Failed to implement policy!"; | |
//RAII | |
const std::string BAD_NAME = "Malformed resource location!"; | |
const std::string NO_RESOURCE = "Failed to acquire resource!"; | |
//HAL | |
const std::string HALSORRY = " I'm sorry, Dave. I'm afraid I can't do that. "; | |
const std::string HALDISCONNECT = " I know that you and Frank were planning to disconnect me, and I'm afraid that's something I cannot allow to happen."; | |
} |
Listing 6: log record stamp policy
A simple abstract NVI interface for unified access to stamp string resources. (see also io::serializor_policy)
Stamp strings are used in log files to populate the fields of a log record. Log file records have an application specific file format, be that a server log, data log or audit trail, but fields commonly consist of e.g. date stamp, time-stamp, thread id, RFC 1413 user id.
The interface mimics the std::stringstream accessor member function str()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
namespace log { | |
struct stamp_policy { | |
inline std::string str() { | |
return(get_str()); | |
} | |
virtual ~stamp_policy() {} | |
private: | |
virtual std::string get_str() = 0; | |
}; | |
} |
Listing 7: unix time stamp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "stamp_policy.h" | |
namespace log { | |
struct unix_time_stamp: public stamp_policy { | |
unix_time_stamp() {} | |
virtual ~unix_time_stamp() {} | |
private: | |
virtual std::string get_str() override { | |
std::stringstream ss; | |
auto unix_timestamp = std::chrono::seconds(std::time(NULL)).count(); | |
ss << unix_timestamp << " "; | |
return(ss.str()); | |
} | |
}; | |
} |
No comments:
Post a Comment