aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-04-10 16:29:15 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-04-10 16:29:15 +0200
commitd5a8c51d4010285be02f3252520300a737799872 (patch)
tree50c1ea90ce3d7a23c6c8400b4d982aa158485a5f
parent2d89c9d6aa0f2fcdd6403c4973f7e7005a6796a0 (diff)
Add diagnostics support
-rw-r--r--brep/diagnostics316
-rw-r--r--brep/diagnostics.cxx33
-rw-r--r--brep/module54
-rw-r--r--brep/module.cxx30
-rw-r--r--brep/search20
-rw-r--r--brep/search.cxx44
-rw-r--r--web/apache/log2
-rw-r--r--web/module2
8 files changed, 496 insertions, 5 deletions
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 <vector>
+#include <cstdint> // uint64_t
+#include <utility> // move()
+#include <sstream>
+#include <functional>
+
+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<diag_entry>;
+
+ //
+ //
+ template <typename> struct diag_prologue;
+ template <typename> struct diag_mark;
+
+ using diag_epilogue = std::function<void (diag_data&&)>;
+
+ struct diag_record
+ {
+ template <typename T>
+ friend const diag_record&
+ operator<< (const diag_record& r, const T& x)
+ {
+ r.os_ << x;
+ return r;
+ }
+
+ diag_record () = default;
+
+ template <typename B>
+ explicit
+ diag_record (const diag_prologue<B>& p) {*this << p;} // See below.
+
+ template <typename B>
+ explicit
+ diag_record (const diag_mark<B>& 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 <typename B>
+ struct diag_prologue: B
+ {
+ diag_prologue (const diag_epilogue& e): B (), epilogue_ (e) {}
+
+ template <typename... A>
+ diag_prologue (const diag_epilogue& e, A&&... a)
+ : B (std::forward<A> (a)...), epilogue_ (e) {}
+
+ template <typename T>
+ 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 <typename B>
+ struct diag_mark: B
+ {
+ diag_mark (): B () {}
+
+ template <typename... A>
+ diag_mark (A&&... a): B (std::forward<A> (a)...) {}
+
+ template <typename T>
+ 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_base> 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_base> 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 <typename L>
+ 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_base> basic_mark;
+
+ template <typename E>
+ 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 <typename L>
+ 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 <typename E>
+ using fail_mark = diag_mark<fail_mark_base<E>>;
+}
+
+#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 <brep/diagnostics>
+
+#include <cassert>
+#include <exception>
+
+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 <brep/diagnostics>
+
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<server_error> 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 <class F> static void level1 (const F& f) {if (verb_ >= 1) f ();}
+ template <class F> 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<trace_mark_base>;
+ 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 <brep/module>
+#include <functional> // 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 <brep/module>
+
+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 <brep/search>
+
+#include <ostream>
+
+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
{