ロギングを実装するとき、それ自体のタスクは次のように設定されました。
- 最終的な実装から切り離すために、「メタレベル」で問題を解決する
- ロギング用のフロントエンドAPIはシンプルで透過的でなければなりません
- 1つの定数を使用して、コンパイル段階で不要なログレベルをオフにする機能があります。 例:LOG_NOTICEより上のすべてが結果のバイナリに入らない
フロントエンドは次のようになります。
1. CConnectionコンストラクターで、次のログを記録します。
TestLog::Log<LOG_NOTICE>() << *this << "connection created";
2. CConnectionの継承元
public TLogHeader<'c', CConnection>
3. CRTPを使用すると、CConnectionには次のようなものがあることがわかります。
using this_t = CConnection; int m_id; using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>; std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>; char m_state; using m_state_t = TParamAccessor<this_t, decltype(this_t::m_state), &this_t::m_state>; using log_header_param_list_t = std::tuple<m_id_t, m_name_t, m_state_t>;
4.そして、このデータをログ行に変換します。
c:23:test_conn 1:a:connection created
コロンがリストされている場所:c-TLogHeaderテンプレートの最初のパラメーター、次に値m_id、m_name、m_state
私の場合、これはすべてrsyslogにさらに書き込まれ、そこでMongoDBの適切なフィールドに解析されます(ヘッダーのタイプ、つまりコロンの前の最初のパラメーターによって異なります)。
現在、このアプローチは、負荷の高いシステムでの私の生産で行われているため、構造石はピークに達し、猫にとってはより良い状態になります。 怠な場合は、http://coliru.stacked-crooked.com/に実際の例があります 250行のコードの猫の下にありますが、説明にはたくさんの文字があります。
音訳と英語の用語がたくさんある場合は申し訳ありません。 この記事は完全なギャグですが、20年間にわたってロシアの用語に遅れをとっていました。 したがって、文献への参照はありませんが、ある時点で彼らはstackoverflow.comで私を助けましたが、リンクを与えることは、私が理解しているように、砂のユーザーとして私の匿名性を燃やすことを意味します。
だから。
パート1-ロギング
簡単なことから始めましょう
TestLog::Log<LOG_NOTICE>() << *this << "connection created";
この場合の「テンプレートTestLog ::ログ」は型です(ただし、型を返す関数として実装することもできます)。 ご覧のとおり、このタイプの一時オブジェクトが作成されます。 そして、スコープを離れるとオブジェクトが破棄されることを知っているので、このタイプの場合は ';'になることは明らかです。 したがって、動作モデルは単純です-データをこのタイプにマージし、その後デストラクタが呼び出されます。
これはTSinkクラスに実装されています。
ここでは、「1つの定数を使用して、コンパイル段階で不要なログレベルをオフにできるようにするために、部分的な専門化が必要です。 例:LOG_NOTICEより上のすべてが、結果のバイナリに入らないようにしてください。 これはコンパイラーからのプレゼントです(コードを逆コンパイルして、出力に何があるかを確認したいという気持ちに燃えていないので、私は100%確信が持てません。こんにちは!)。 アイデアは簡単です:ストリームへの書き込み演算子はデータを操作しません。一時オブジェクトを作成し、それを削除する前に行うすべてのことを考慮すると、最新のコンパイラーがすべてを最適化します。
template<int logLevel, bool isNull> struct TSink { ~TSink() { std::cerr << "std::err log line flushed: \"" << stream.str() << " \"" << std::endl; } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } };
ここでは、「1つの定数を使用して、コンパイル段階で不要なログレベルをオフにできるようにするために、部分的な専門化が必要です。 例:LOG_NOTICEより上のすべてが、結果のバイナリに入らないようにしてください。 これはコンパイラーからのプレゼントです(コードを逆コンパイルして、出力に何があるかを確認したいという気持ちに燃えていないので、私は100%確信が持てません。こんにちは!)。 アイデアは簡単です:ストリームへの書き込み演算子はデータを操作しません。一時オブジェクトを作成し、それを削除する前に行うすべてのことを考慮すると、最新のコンパイラーがすべてを最適化します。
そして、これはcerrの代わりにsyslogにマージした場合の外観です
template<int logLevel, bool isNull> struct TSink { ~TSink() { syslog(logLevel, stream.str().c_str(), "junk"); } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } };
TSinkは、特定のログモジュール(たとえば、標準出力ストリーム、ファイル、syslogなど)に関連付けられた最終的な実装であることに注意してください。 後で説明するTLogクラス自体は、どのように実装されるかを知らず、既に特定のシンク実装を参照する特性を受け取ります。
TestLog :: Logとは何かを見てみましょう。
これは:
static const int OUR_LOG_LEVEL = LOG_DEBUG; using TestLog = TLog<OUR_LOG_LEVEL, TLogTraitsStdErr>;
そして、実際には、TLogの実装
template<int logUpTo, template<int> class log_traits> struct TLog : public log_traits<logUpTo> { using trats_t = log_traits<logUpTo>; template<int logLevel, bool isNull> using sink_type = typename trats_t::template sink_type<logLevel, isNull>; template<int neededLogLevel, int logLevel> struct TGetLogOfLevel { using type = typename std::conditional< neededLogLevel == logLevel, sink_type<neededLogLevel, (neededLogLevel > logUpTo)>, typename TGetLogOfLevel<neededLogLevel, logLevel + 1>::type >::type; }; template<int neededLogLevel> struct TGetLogOfLevel<neededLogLevel, LOG_DEBUG + 1> { using type = void; }; template<int neededLogLevel> using Log = typename TGetLogOfLevel<neededLogLevel, 0>::type; };
「テンプレート入力」で、2つのパラメーターを取得します。指定されたプログラムアセンブリの最大ログレベル(OUR_LOG_LEVEL)と、後で継承するテンプレートタイプlog_traitsです。
コードからわかるように、log_traitsタイプには2つの要件があります。
- この型はint logUpToを受け入れる必要があります(これは最初のTLogパラメーターから転送されます)
- タイプは、別のテンプレートタイプsink_type <int logLevel、bool isNull>の定義内になければなりません。ここで、logLevelは現在のロギング操作のレベルであり、isNull-現在のレベルをまったく記録します(TSink <logLevel、true>の部分的な特殊化を参照)
すべてが非常に単純に内部で機能します。再帰メタ関数TGetLogOfLevel(neededLogLevel、logLevel)を呼び出した結果であるLogタイプを定義します。
これらすべてに基づいて、log_traitsの最終的な実装を行います。
これを行うには、上記のlog_traits要件を考慮して、完成したTSinkを新しいTLogTraitsクラスにラップします。 つまり タイプtraits_typeを定義します
template<int logLevel, bool isNull> using sink_type = TSink<logLevel, isNull>;
また、OpenLog関数を追加し、デストラクタ(ログ)で閉じます。
TLogTraitsStdErrコードは次のとおりです
template<int logUpTo> struct TLogTraitsStdErr { void Open(const char* logName, int facility) { std::cerr << "std::err log opened with logName: " << logName << std::endl; } ~TLogTraitsStdErr() { std::cerr << "std::err log closed" << std::endl; } template<int logLevel, bool isNull> struct TSink { ~TSink() { std::cerr << "std::err log line flushed: \"" << stream.str() << " \"" << std::endl; } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } }; template<int logLevel, bool isNull> using sink_type = TSink<logLevel, isNull>; };
TLogTraitsSyslogとの類推によって
template<int logUpTo> struct TLogTraitsSyslog { static void Open(const char* logName, int facility) { openlog(logName, LOG_PID | LOG_NDELAY, facility); setlogmask(LOG_UPTO(logUpTo)); } ~TLogTraitsSyslog() { closelog(); } template<int logLevel, bool isNull> struct TSink { ~TSink() { syslog(logLevel, stream.str().c_str(), "junk"); } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } }; template<int logLevel, bool isNull> using sink_type = TSink<logLevel, isNull>; };
そして、もちろん、この猫をテストします
テストケースを書く
static const int OUR_LOG_LEVEL = LOG_DEBUG; using TestLog = TLog<OUR_LOG_LEVEL, TLogTraitsStdErr>; struct CConnection { CConnection() { TestLog::Log<LOG_NOTICE>() << "connection created"; } ~CConnection() { TestLog::Log<LOG_NOTICE>() << "connection destroyed"; } void DoSomething() { TestLog::Log<LOG_DEBUG>() << "connection is doing something"; } }; class CServer : public TestLog { public: CServer() { TestLog::Open("test_log", 1); }; int Run() { TestLog::Log<LOG_DEBUG>() << "Creating connection"; CConnection test_conn1; test_conn1.DoSomething(); return 0; } }; int main(int argc, char** argv) { CServer server; return server.Run(); }
次の出力をコンパイルして取得します。
clang++ -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out std::err log opened with logName: test_log std::err log line flushed: "Creating connection " std::err log line flushed: "connection created " std::err log line flushed: "connection is doing something " std::err log line flushed: "connection destroyed " std::err log closed
そして、彼と一緒にプレイします(このリソースを何と呼ぶべきかもわかりません) Coliru
その結果、25行のTLogメタクラスと、log_traitsの特定の実装ごとに40行を取得しました。 また、要件が満たされています。不要なロギングをオフにしました。 ただ? -はいのように。 透明? -メタコードを読むのは難しくありません。
私の観点からはKISS。
どう? こんにちは石2!
パート2-反射
テストケースでCConnectionを拡張する
初めに、面白いパンがあったことを思い出してください。
TLogHeader <'c'、CConnection>は、 CRTPを使用して、CConnectionがこれを持っていることを知っています。 using this_t = CConnection; int m_id; using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>; std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>; char m_state; using m_state_t = TParamAccessor<this_t, decltype(this_t::m_state), &this_t::m_state>; using log_header_param_list_t = std::tuple<m_id_t, m_name_t, m_state_t>;
これが高度なCConnectionです
struct CConnection : public TLogHeader<'c', CConnection> { CConnection(int _id, const std::string& _name) : m_id(_id), m_name(_name), m_state('a') { TestLog::Log<LOG_NOTICE>() << *this << "connection created"; m_state = 'b'; } ~CConnection() { TestLog::Log<LOG_NOTICE>() << *this << "connection destroyed"; } void DoSomething() { TestLog::Log<LOG_DEBUG>() << *this << "connection is doing something"; m_state = 'c'; } using this_t = CConnection; int m_id; using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>; std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>; char m_state; using m_state_t = TParamAccessor<this_t, decltype(this_t::m_state), &this_t::m_state>; using log_header_param_list_t = std::tuple<m_id_t, m_name_t, m_state_t>; };
もちろん、これはすべて、PARAM(int、m_id)などの単純なマクロでラップできますが、正気のためにはしません。
TParamAccessorは、クラス変数への参照を返す1つの静的GetRef関数が定義されている型です。
このように
したがって、これをsink_typeストリームにプッシュするには、CConnection :: log_header_param_list_tを介してTLogHeader <'c'、CConnection>から継承し、sink_type << TParamAccessor :: GetRef(オブジェクト)を呼び出す必要があります。
struct TParamAccessorDefaultTraits { }; template <typename _object_t, typename _value_type, _value_type _object_t::*member_t, typename _traits_t = TParamAccessorDefaultTraits> struct TParamAccessor : public _traits_t { using traits_t = _traits_t; using object_t = _object_t; using value_type = _value_type; static value_type& GetRef(object_t& data) { return data.*member_t; } };
警告:メタオプションfor_eachを見るまで、ここでは理解するのが少し難しいでしょう。
TLogHeaderクラスを取得します
template<char moduleName, typename object_t> struct TLogHeader { template<size_t idx, typename accessor_t> struct TLogHeaderWriter { using type = TLogHeaderWriter<idx, accessor_t>; static void call(typename accessor_t::object_t& obj, std::stringstream& out) { typename accessor_t::value_type& val = accessor_t::GetRef(obj); out << val << ":"; } }; template<typename sink_type> friend sink_type& operator << (sink_type& sink, object_t& val) { std::stringstream header; using writers = typename for_each<typename object_t::log_header_param_list_t>::template instantiate<TLogHeaderWriter>; for_each<writers>::call(*static_cast<object_t*>(&val), header); sink << moduleName << ":" << header.str(); return sink; } };
TLogHeaderは、ストリームへのオーバーロード書き込み演算子という1つの関数のみを記述します。
ここでは、2つの装いでfor_eachを確認します。
- TLogHeaderWriterのすべてのインスタンス<from object_t :: log_header_param_list_t ::各タイプ内から>がタプルとして返されるライターのタイプを決定するメタ関数呼び出し)
- ライターで定義されたすべてのタイプの静的呼び出し関数の呼び出し
for_eachのメタ仮説を行いましょう
これを行うには、TLogHeaderを単純化してメタ関数のみを呼び出します
template<char moduleName, typename object_t> struct TLogHeader { template<size_t idx, typename accessor_t> struct TLogHeaderWriter { using type = TLogHeaderWriter<idx, accessor_t>; }; template<typename sink_type> friend sink_type& operator << (sink_type& sink, object_t& val) { using writers = typename for_each<typename object_t::log_header_param_list_t>::template instantiate<TLogHeaderWriter>; return sink; } };
そして、メタ関数を書きます。
免責事項:なぜなら 簡単にするために、最終コードをカットし、テストしませんでした。 最後のものは、後でライブでテストできます。
念のため、私にとって最も明確な用語は、「メタ機能とは、プログラムをコンパイルする段階で実行される機能であり、その結果は新しいタイプである」ということです(すべて自分の言葉で)
私はコードでコメントします さらに少し混乱します。
For_eachコード
//////////////////////////////////////////////////////////////// // append to tuple //- new_t // tuple template argument pack, template<typename new_t, typename... other_t> struct append_to_tuple { using type = std::tuple<other_t..., new_t>; }; template<typename new_t, typename... other_t> struct append_to_tuple<new_t, std::tuple<other_t...>> { using type = std::tuple<other_t..., new_t>; }; //////////////////////////////////////////////////////////////// // for_each_impl // for_each - , func_t instatiate, //, tuple append_to_tuple // , , template<size_t i, typename... args_t> struct for_each_impl { using this_type = typename std::tuple_element<i, std::tuple<args_t...>>::type; using prev_type = for_each_impl<i - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename append_to_tuple< typename func_t<i, this_type>::type, typename prev_type::template instantiate<func_t> >::type; }; // // template<typename... args_t> struct for_each_impl<0, args_t...> { using this_type = typename std::tuple_element<0, std::tuple<args_t...>>::type; template<template<size_t, typename> class func_t> using instantiate = std::tuple<typename func_t<0, this_type>::type>; }; /////////////////////////////////////////////////////////////// // for_each // . // for_each_impl, // + tuple< > tuple<> template<typename... args_t> struct for_each { using prev_type = for_each_impl<sizeof...(args_t) - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename prev_type::template instantiate<func_t>; }; template<typename... args_t> struct for_each<std::tuple<args_t...>> : public for_each<args_t...> { }; template<> struct for_each<std::tuple<>> { template<template<size_t, typename> class func_t> using instantiate = std::tuple<>; };
したがって、writers = typename for_each <typename object_t :: log_header_param_list_t> :: template instantiate;を使用して取得しました。 作家が展開する場所:
std::tuple< TLogHeaderWriter<0, TParamAccessor<CConnection, decltype(CConnection::m_id), &CConnection::m_id>>, TLogHeaderWriter<1, TParamAccessor<CConnection, decltype(CConnection::m_name), &CConnection::m_name>>, TLogHeaderWriter<2, TParamAccessor<CConnection, decltype(CConnection::m_state), &CConnection::m_state>> >;
これらの型を通過し、GetRefを呼び出してからストリームに書き込むことは残っています。
for_eachの実行時の仮説を立てましょう
そしてすぐに、拡張されたfor_eachを見てください。これは、静的関数呼び出しに呼び出しを追加します(注意する価値があるだけで、残りは変更されません)。
そしてここにある-最後のコード
//////////////////////////////////////////////////////////////// // for_each_impl template<size_t i, typename... args_t> struct for_each_impl { using this_type = typename std::tuple_element<i, std::tuple<args_t...>>::type; using prev_type = for_each_impl<i - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename append_to_tuple< typename func_t<i, this_type>::type, typename prev_type::template instantiate<func_t> >::type; template<typename object_t, typename... call_args_t> static void call(object_t&& obj, call_args_t&&... args) { prev_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); this_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); } }; template<typename... args_t> struct for_each_impl<0, args_t...> { using this_type = typename std::tuple_element<0, std::tuple<args_t...>>::type; template<template<size_t, typename> class func_t> using instantiate = std::tuple<typename func_t<0, this_type>::type>; template<typename object_t, typename... call_args_t> static void call(object_t&& obj, call_args_t&&... args) { this_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); } template<typename object_t> static void call(object_t&& obj) { this_type::call(std::forward<object_t>(obj)); } }; /////////////////////////////////////////////////////////////// // for_each template<typename... args_t> struct for_each { using prev_type = for_each_impl<sizeof...(args_t) - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename prev_type::template instantiate<func_t>; template<typename object_t, typename... call_args_t> static object_t call(object_t&& obj, call_args_t&&... args) { prev_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); return obj; } template<typename object_t> static object_t call(object_t&& obj) { prev_type::call(std::forward<object_t>(obj)); return obj; } }; template<typename... args_t> struct for_each<std::tuple<args_t...>> : public for_each<args_t...> { }; template<> struct for_each<std::tuple<>> { template<template<size_t, typename> class func_t> using instantiate = std::tuple<>; template<typename object_t, typename... call_args_t> static object_t call(object_t&& obj, call_args_t&&... args) { return obj; } template<typename object_t> static object_t call(object_t&& obj) { return obj; } };
ここでコメントする必要は特にないと思います。呼び出しは少なくとも1つのパラメーターを受け取ります。これはプロキシオブジェクトであり、作業の最後に戻り、残りのパラメーターは呼び出し呼び出しに転送されます。
私たちの場合、これはTLogHeaderWirter :: call:です。
static void call(typename accessor_t::object_t& obj, std::stringstream& out) { typename accessor_t::value_type& val = accessor_t::GetRef(obj); out << val << ":"; }
where accessor_t :: object_t = CConnection
そしてもちろん、完璧な転送は重要です!
パート3-結論
全コード
#include <cstdlib> #include <iostream> #include <sstream> #include <syslog.h> static const int OUR_LOG_LEVEL = LOG_DEBUG; // = LOG_NOTICE; // log up to LOG_DEBUG output: //std::err log opened with logName: test_log //std::err log line flushed: "s:1:test server:Creating connection " //std::err log line flushed: "c:23:test_conn 1:a:connection created " //std::err log line flushed: "c:23:test_conn 1:b:connection is doing something " //std::err log line flushed: "c:23:test_conn 1:c:connection destroyed " //std::err log closed // log up to LOG_NOTICE output: //std::err log opened with logName: test_log //std::err log line flushed: "c:23:test_conn 1:a:connection created " //std::err log line flushed: "c:23:test_conn 1:c:connection destroyed " //std::err log closed // ---------------------------- for_each.h start ----------------------------// #include <tuple> #include <utility> namespace helpers { //////////////////////////////////////////////////////////////// // param accessor struct TParamAccessorDefaultTraits { }; template <typename _object_t, typename _value_type, _value_type _object_t::*member_t, typename _traits_t = TParamAccessorDefaultTraits> struct TParamAccessor : public _traits_t { using traits_t = _traits_t; using object_t = _object_t; using value_type = _value_type; static value_type& GetRef(object_t& data) { return data.*member_t; } }; //////////////////////////////////////////////////////////////// // append to tuple template<typename new_t, typename... other_t> struct append_to_tuple { using type = std::tuple<other_t..., new_t>; }; template<typename new_t, typename... other_t> struct append_to_tuple<new_t, std::tuple<other_t...>> { using type = std::tuple<other_t..., new_t>; }; //////////////////////////////////////////////////////////////// // for_each_impl template<size_t i, typename... args_t> struct for_each_impl { using this_type = typename std::tuple_element<i, std::tuple<args_t...>>::type; using prev_type = for_each_impl<i - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename append_to_tuple< typename func_t<i, this_type>::type, typename prev_type::template instantiate<func_t> >::type; template<typename object_t, typename... call_args_t> static void call(object_t&& obj, call_args_t&&... args) { prev_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); this_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); } }; template<typename... args_t> struct for_each_impl<0, args_t...> { using this_type = typename std::tuple_element<0, std::tuple<args_t...>>::type; template<template<size_t, typename> class func_t> using instantiate = std::tuple<typename func_t<0, this_type>::type>; template<typename object_t, typename... call_args_t> static void call(object_t&& obj, call_args_t&&... args) { this_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); } template<typename object_t> static void call(object_t&& obj) { this_type::call(std::forward<object_t>(obj)); } }; /////////////////////////////////////////////////////////////// // for_each template<typename... args_t> struct for_each { using prev_type = for_each_impl<sizeof...(args_t) - 1, args_t...>; template<template<size_t, typename> class func_t> using instantiate = typename prev_type::template instantiate<func_t>; template<typename object_t, typename... call_args_t> static object_t call(object_t&& obj, call_args_t&&... args) { prev_type::call(std::forward<object_t>(obj), std::forward<call_args_t>(args)...); return obj; } template<typename object_t> static object_t call(object_t&& obj) { prev_type::call(std::forward<object_t>(obj)); return obj; } }; template<typename... args_t> struct for_each<std::tuple<args_t...>> : public for_each<args_t...> { }; template<> struct for_each<std::tuple<>> { template<template<size_t, typename> class func_t> using instantiate = std::tuple<>; template<typename object_t, typename... call_args_t> static object_t call(object_t&& obj, call_args_t&&... args) { return obj; } template<typename object_t> static object_t call(object_t&& obj) { return obj; } }; } //namespace helpers // ---------------------------- for_each.h end ----------------------------// using namespace helpers; ////////////////////////////////////////////////////////////////////////////////////////// template<int logUpTo> struct TLogTraitsStdErr { static void Open(const char* logName, int facility) { std::cerr << "std::err log opened with logName: " << logName << std::endl; } ~TLogTraitsStdErr() { std::cerr << "std::err log closed" << std::endl; } template<int logLevel, bool isNull> struct TSink { ~TSink() { std::cerr << "std::err log line flushed: \"" << stream.str() << " \"" << std::endl; } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } }; template<int logLevel, bool isNull> using sink_type = TSink<logLevel, isNull>; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// template<int logUpTo> struct TLogTraitsSyslog { static void Open(const char* logName, int facility) { openlog(logName, LOG_PID | LOG_NDELAY, facility); setlogmask(LOG_UPTO(logUpTo)); } ~TLogTraitsSyslog() { closelog(); } template<int logLevel, bool isNull> struct TSink { ~TSink() { syslog(logLevel, stream.str().c_str(), "junk"); } std::stringstream stream; template<typename T> TSink<logLevel, isNull>& operator << (T&& val) { stream << val; return *this; } }; template<int logLevel> struct TSink<logLevel, true> { template<typename T> TSink<logLevel, true>& operator << (T&& val) { return *this; } }; template<int logLevel, bool isNull> using sink_type = TSink<logLevel, isNull>; }; ////////////////////////////////////////////////////////////////////////////////////////// template<char moduleName, typename object_t> struct TLogHeader { template<size_t idx, typename accessor_t> struct TLogHeaderWriter { using type = TLogHeaderWriter<idx, accessor_t>; static void call(typename accessor_t::object_t& obj, std::stringstream& out) { typename accessor_t::value_type& val = accessor_t::GetRef(obj); out << val << ":"; } }; template<typename sink_type> friend sink_type& operator << (sink_type& sink, object_t& val) { std::stringstream header; using writers = typename for_each<typename object_t::log_header_param_list_t>::template instantiate<TLogHeaderWriter>; for_each<writers>::call(*static_cast<object_t*>(&val), header); sink << moduleName << ":" << header.str(); return sink; } }; ////////////////////////////////////////////////////////////////////////////////////////// template<int logUpTo, template<int> class log_traits = TLogTraitsSyslog> struct TLog : public log_traits<logUpTo> { using trats_t = log_traits<logUpTo>; template<int logLevel, bool isNull> using sink_type = typename trats_t::template sink_type<logLevel, isNull>; template<int neededLogLevel, int logLevel> struct TGetLogOfLevel { using type = typename std::conditional< neededLogLevel == logLevel, sink_type<neededLogLevel, (neededLogLevel > logUpTo)>, typename TGetLogOfLevel<neededLogLevel, logLevel + 1>::type >::type; }; template<int neededLogLevel> struct TGetLogOfLevel<neededLogLevel, LOG_DEBUG + 1> { using type = void; }; template<int neededLogLevel> using Log = typename TGetLogOfLevel<neededLogLevel, 0>::type; }; /////////////////////////////// //testcase using TestLog = TLog<OUR_LOG_LEVEL, TLogTraitsStdErr>; struct CConnection : public TLogHeader<'c', CConnection> { CConnection(int _id, const std::string& _name) : m_id(_id), m_name(_name), m_state('a') { TestLog::Log<LOG_NOTICE>() << *this << "connection created"; m_state = 'b'; } ~CConnection() { TestLog::Log<LOG_NOTICE>() << *this << "connection destroyed"; } void DoSomething() { TestLog::Log<LOG_DEBUG>() << *this << "connection is doing something"; m_state = 'c'; } using this_t = CConnection; int m_id; using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>; std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>; char m_state; using m_state_t = TParamAccessor<this_t, decltype(this_t::m_state), &this_t::m_state>; using log_header_param_list_t = std::tuple<m_id_t, m_name_t, m_state_t>; }; class CServer : public TLogHeader<'s', CServer>, public TestLog { public: CServer() : m_id(1), m_name("test server") { TestLog::Open("test_log", 1); }; int Run() { TestLog::Log<LOG_DEBUG>() << *this << "Creating connection"; CConnection test_conn1(23, "test_conn 1"); test_conn1.DoSomething(); return 0; } using this_t = CServer; int m_id; using m_id_t = TParamAccessor<this_t, decltype(this_t::m_id), &this_t::m_id>; std::string m_name; using m_name_t = TParamAccessor<this_t, decltype(this_t::m_name), &this_t::m_name>; using log_header_param_list_t = std::tuple<m_id_t, m_name_t> ; }; int main(int argc, char** argv) { CServer server; return server.Run(); }
これが既製の例です。
2つのfor_eachオプションの実装をさらに100行追加しました。
そして、例を見ると、それらが別のファイルfor_each.hにあることがわかります。
なぜなら私は歴史の5つのモジュールで積極的に使用しています:エンドシステムのエンディアンを考慮してpostgresと通信するネイティブバイナリ形式、バッファからパケットを展開し、コンパイル段階でWebソケットにメッセージ形式を生成するなど
そして私が言ったように:石!レセプション!
PSコードの作成には半日かかり、Habrの日の記事は...パラドックスです!