From d5a8c51d4010285be02f3252520300a737799872 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 10 Apr 2015 16:29:15 +0200 Subject: Add diagnostics support --- brep/diagnostics | 316 +++++++++++++++++++++++++++++++++++++++++++++++++++ brep/diagnostics.cxx | 33 ++++++ brep/module | 54 ++++++++- brep/module.cxx | 30 ++++- brep/search | 20 ++++ brep/search.cxx | 44 +++++++ web/apache/log | 2 +- web/module | 2 +- 8 files changed, 496 insertions(+), 5 deletions(-) create mode 100644 brep/diagnostics create mode 100644 brep/diagnostics.cxx create mode 100644 brep/search create mode 100644 brep/search.cxx diff --git a/brep/diagnostics b/brep/diagnostics new file mode 100644 index 0000000..eda35cc --- /dev/null +++ b/brep/diagnostics @@ -0,0 +1,316 @@ +// file : brep/diagnostics -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BREP_DIAGNOSTICS +#define BREP_DIAGNOSTICS + +#include +#include // uint64_t +#include // move() +#include +#include + +namespace brep +{ + struct location + { + location (): line (0), column (0) {} + location (std::string f, std::uint64_t l, std::uint64_t c) + : file (std::move (f)), line (l), column (c) {} + + std::string file; + std::uint64_t line; + std::uint64_t column; + }; + + enum class severity {error, warning, info, trace}; + + struct diag_entry + { + severity sev; + const char* name {nullptr}; // E.g., a function name in tracing. + location loc; + std::string msg; + }; + + using diag_data = std::vector; + + // + // + template struct diag_prologue; + template struct diag_mark; + + using diag_epilogue = std::function; + + struct diag_record + { + template + friend const diag_record& + operator<< (const diag_record& r, const T& x) + { + r.os_ << x; + return r; + } + + diag_record () = default; + + template + explicit + diag_record (const diag_prologue& p) {*this << p;} // See below. + + template + explicit + diag_record (const diag_mark& m) {*this << m;} // See below. + + ~diag_record () noexcept(false); + + void + append (const diag_epilogue& e) const + { + if (epilogue_ == nullptr) // Keep the first epilogue (think 'fail'). + epilogue_ = &e; + + if (!data_.empty ()) + { + data_.back ().msg = os_.str (); + + // Reset the stream. There got to be a more efficient way to do it. + // + os_.clear (); + os_.str (""); + } + + data_.push_back (diag_entry ()); + } + + diag_entry& + current () const {return data_.back ();} + + // Move constructible-only type. + // + /* + @@ libstdc++ doesn't yet have the ostringstream move support. + + diag_record (diag_record&& r) + : data_ (std::move (r.data_)), os_ (std::move (r.os_)) + { + empty_ = r.empty_; + r.empty_ = true; + + epilogue_ = r.epilogue_; + r.epilogue_ = nullptr; + } + */ + + diag_record (diag_record&& r) + : data_ (std::move (r.data_)) + { + empty_ = r.empty_; + epilogue_ = r.epilogue_; + + if (!empty_) + { + os_ << r.os_.str (); + + r.empty_ = true; + r.epilogue_ = nullptr; + } + } + + diag_record& operator= (diag_record&&) = delete; + + diag_record (const diag_record&) = delete; + diag_record& operator= (const diag_record&) = delete; + + private: + mutable diag_data data_; + mutable std::ostringstream os_; + mutable const diag_epilogue* epilogue_ {nullptr}; + }; + + // Base (B) should provide operator() that configures diag_record. + // + template + struct diag_prologue: B + { + diag_prologue (const diag_epilogue& e): B (), epilogue_ (e) {} + + template + diag_prologue (const diag_epilogue& e, A&&... a) + : B (std::forward (a)...), epilogue_ (e) {} + + template + diag_record + operator<< (const T& x) const + { + diag_record r; + r.append (epilogue_); + B::operator() (r); + r << x; + return r; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_prologue& p) + { + r.append (p.epilogue_); + p (r); + return r; + } + + private: + const diag_epilogue& epilogue_; + }; + + // Base (B) should provide operator() that returns diag_prologue. + // + template + struct diag_mark: B + { + diag_mark (): B () {} + + template + diag_mark (A&&... a): B (std::forward (a)...) {} + + template + diag_record + operator<< (const T& x) const + { + return B::operator() () << x; + } + + friend const diag_record& + operator<< (const diag_record& r, const diag_mark& m) + { + return r << m (); + } + }; + + // Prologues. + // + struct simple_prologue_base + { + explicit + simple_prologue_base (severity s, const char* name) + : sev_ (s), name_ (name) {} + + void + operator() (const diag_record& r) const + { + diag_entry& e (r.current ()); + e.sev = sev_; + e.name = name_; + } + + private: + severity sev_; + const char* name_; + }; + typedef diag_prologue simple_prologue; + + struct location_prologue_base + { + location_prologue_base (severity s, + const char* name, + const location& l) + : sev_ (s), name_ (name), loc_ (l) {} + + void + operator() (const diag_record& r) const + { + diag_entry& e (r.current ()); + e.sev = sev_; + e.name = name_; + e.loc = loc_; //@@ I think we can probably move it. + } + + private: + severity sev_; + const char* name_; + const location loc_; + }; + typedef diag_prologue location_prologue; + + // Marks. + // + struct basic_mark_base + { + explicit + basic_mark_base (severity s, + const diag_epilogue& e, + const char* name = nullptr, + const void* data = nullptr) + : sev_ (s), epilogue (e), name_ (name), data_ (data) {} + + simple_prologue + operator() () const + { + return simple_prologue (epilogue_, sev_, name_); + } + + location_prologue + operator() (const location& l) const + { + return location_prologue (epilogue_, sev_, name_, l); + } + + template + location_prologue + operator() (const L& l) const + { + // get_location() is the user-supplied ADL-searched function. + // + return location_prologue ( + epilogue_, sev_, name_, get_location (l, data_)); + } + + private: + severity sev_; + const diag_epilogue& epilogue_; + const char* name_; + const void* data_; + }; + typedef diag_mark basic_mark; + + template + struct fail_mark_base + { + explicit + fail_mark_base (const char* name = nullptr, const void* data = nullptr) + : name_ (name), data_ (data) {} + + simple_prologue + operator() () const + { + return simple_prologue (epilogue_, severity::error, name_); + } + + location_prologue + operator() (const location& l) const + { + return location_prologue (epilogue_, severity::error, name_, l); + } + + template + location_prologue + operator() (const L& l) const + { + return location_prologue ( + epilogue_, severity::error, name, get_location (l, data_)); + } + + static void + epilogue (diag_data&& d) {throw E (std::move (d));} + + private: + const diag_epilogue epilogue_ {&epilogue}; + const char* name_; + const void* data_; + }; + + template + using fail_mark = diag_mark>; +} + +#endif // BREP_DIAGNOSTICS diff --git a/brep/diagnostics.cxx b/brep/diagnostics.cxx new file mode 100644 index 0000000..5ab34b6 --- /dev/null +++ b/brep/diagnostics.cxx @@ -0,0 +1,33 @@ +// file : brep/diagnostics.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include + +using namespace std; + +namespace build +{ + diag_record:: + ~diag_record () noexcept(false) + { + // Don't flush the record if this destructor was called as part of + // the stack unwinding. Right now this means we cannot use this + // mechanism in destructors, which is not a big deal, except for + // one place: exception_guard. So for now we are going to have + // this ugly special check which we will be able to get rid of + // once C++17 uncaught_exceptions() becomes available. + // + if (!data_.empty () && + (!std::uncaught_exception () /*|| exception_unwinding_dtor*/)) + { + data_.back ().msg = os_.str (); // Save last message. + + assert (epilogue_ != nullptr); + (*epilogue_) (move (data_)); // Can throw. + } + } +} diff --git a/brep/module b/brep/module index 6687a31..0b69aaf 100644 --- a/brep/module +++ b/brep/module @@ -5,6 +5,8 @@ #ifndef BREP_MODULE #define BREP_MODULE +#include + namespace brep { // This exception is used to signal that the request is invalid @@ -21,6 +23,16 @@ namespace brep // }; + // And this exception indicated a server error (5XX). In particular, + // it is thrown by the fail diagnostics stream and is caught by the + // module implementation where it is both logged as an error and + // returned to the user with the 5XX status code. + // + struct server_error + { + diag_data data; + }; + // Adaptation of the web::module to our needs. // class module: public web::module @@ -29,14 +41,52 @@ namespace brep virtual void handle (request&, response&) = 0; + // Diagnostics. + // + protected: + const basic_mark error; + const basic_mark warn; + const basic_mark info; + const fail_mark fail; + + // Trace verbosity level. + // + // 0 - tracing disabled. + // 1 - @@ TODO: document + // 2 - @@ TODO: document + // + // While uint8 is more than enough, use uint16 for the ease of printing. + // + std::uint16_t verb_ {0}; + + template static void level1 (const F& f) {if (verb_ >= 1) f ();} + template static void level2 (const F& f) {if (verb_ >= 2) f ();} + + struct trace_mark_base: basic_mark_base + { + trace_mark_base (const module* this_, const char* name) + : basic_mark_base (severity::trace, this_->log_writer_, name) {} + }; + using trace_mark = diag_mark; + using tracer = trace_mark; + // Implementation details. // protected: + module (); + virtual void handle (request&, response&, log&); - protected: - log* log_; + // Diagnostics implementation details. + // + private: + log* log_ {nullptr}; // Diagnostics backend provided by the web server. + + void + log_write (diag_data&&) const; + + const diag_epilogue log_writer_; }; } diff --git a/brep/module.cxx b/brep/module.cxx index 1837ccd..31e5f99 100644 --- a/brep/module.cxx +++ b/brep/module.cxx @@ -4,10 +4,13 @@ #include +#include // bind() + using namespace std; namespace brep { + void module:: handle (request& rq, response& rs, log& l) { @@ -19,10 +22,16 @@ namespace brep } catch (const invalid_request& e) { - // @@ Format as HTML in proper style. + // @@ Both log and format as HTML in proper style, etc. // rs.content (e.status, "text/html;charset=utf-8") << e.description; } + catch (const server_error& e) + { + // @@ Both log and return as 505. + // + write (move (e.data)); + } catch (const exception& e) { // @@ Exception: log e.what () & 505. @@ -36,4 +45,23 @@ namespace brep rs.status (505); } } + + module:: + module () + : error (severity::error, log_writer_), + warn (severity::warn, log_writer_), + info (severity::info, log_writer_), + log_writer_ (bind (&module::write, this, _1)) + { + } + + void module:: + log_write (diag_data&& d) const + { + if (log_ == nullptr) + return; // No backend yet. + + //@@ Cast log_ to apache::log and write the records. + // + } } diff --git a/brep/search b/brep/search new file mode 100644 index 0000000..9ea9345 --- /dev/null +++ b/brep/search @@ -0,0 +1,20 @@ +// file : brep/search -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BREP_SEARCH +#define BREP_SEARCH + +#include + +namespace brep +{ + class search: public module + { + public: + virtual void + handle (request&, response&); + }; +} + +#endif // BREP_SEARCH diff --git a/brep/search.cxx b/brep/search.cxx new file mode 100644 index 0000000..d8da134 --- /dev/null +++ b/brep/search.cxx @@ -0,0 +1,44 @@ +// file : brep/search.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include + +#include + +using namespace std; + +namespace brep +{ + void search:: + handle (request& rq, response& rs) + { + //@@ Could probably have module name in which case this will + // then become: + // + // tracer trace (this, "handle"); + // + tracer trace (this, "search::handle"); + + const name_values& ps (rq.parameters ()); + + if (ps.empty ()) + throw invalid_request ("search parameters expected"); + + if (ps.size () > 100) + fail << "too many parameters: " < ps.size (); + + info << "handling search request from " << rq.client_ip (); + + level2 ([&]{trace << "search request with " << ps.size () << " params";}); + + ostream& o (rs.content (202, "text/html;charset=utf-8")); + + o << "Search page:" << endl; + + for (const name_value& p: ps) + { + o << p.name << "=" << p.value << endl; + } + } +} diff --git a/web/apache/log b/web/apache/log index 8464763..0e39420 100644 --- a/web/apache/log +++ b/web/apache/log @@ -27,7 +27,7 @@ namespace web write (int level, const char* msg) {write (nullptr, 0, level, msg);} void - write (const char* file, uint64_t line, int level, const char* msg); + write (const char* file, std::uint64_t line, int level, const char* msg); private: // ... diff --git a/web/module b/web/module index e9787a4..642b1bd 100644 --- a/web/module +++ b/web/module @@ -24,7 +24,7 @@ namespace web // // @@ Define some commonly used constants? // - using status_code = uint16_t; + using status_code = std::uint16_t; struct name_value { -- cgit v1.1