Reading List

The Selfish Gene
The Psychopath Test: A Journey Through the Madness Industry
Bad Science
The Feynman Lectures on Physics
The Theory of Everything: The Origin and Fate of the Universe


ifknot's favorite books »

Thursday, 16 January 2014

Part (3/3): I'm a Lumberjack and I'm ok.

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
#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);
}
}
view raw main.cpp hosted with ❤ by GitHub
Which produces the following output to the console:
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
view raw console.txt hosted with ❤ by GitHub
And the following output to the test.log file:
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
view raw test.log hosted with ❤ by GitHub

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.
#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;
};
}
view raw logger.h hosted with ❤ by GitHub

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.
#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.
#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

#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.
#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.";
}
view raw errors.h hosted with ❤ by GitHub

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()
#include <iostream>
namespace log {
struct stamp_policy {
inline std::string str() {
return(get_str());
}
virtual ~stamp_policy() {}
private:
virtual std::string get_str() = 0;
};
}
view raw stamp_policy.h hosted with ❤ by GitHub

Listing 7: unix time stamp

#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());
}
};
}
References:

No comments:

Post a Comment