1 unstable release
new 0.1.0 | Apr 7, 2025 |
---|
#344 in Debugging
92 downloads per month
40KB
774 lines
Tracing - CloudChamber
Overview
tracing
is a framework for instrumenting Rust programs to collect
structured, event-based diagnostic information. But sometimes you need to write a C++ ffi
and this beautiful framework is unavailable to you in your C++ code.
Not anymore! tracing-cloudchanber
extends tracing
with a ffi via cxx
so you can construct spans and emit events from your C++ code with the same safety as you could in Rust.
Usage
Assuming you have set up your project correctly and either added tracing-cloudchamber
as a direct dependency
or your using something like corrosion to link tracing-cloudchamber
to your C++ code ...
You only need to do two things:
- add the header
#include "cloudchamber/tracing.h"
- prevent the linker from eliding "unused" symbols
mod _using {
/// prevent symbol elision
#[allow(unused_imports)]
pub use tracing_cloudchamber::*;
}
Then you'll be able to do this!
void emit_events_in_span() {
MyTestStruct span_struct{999, "a"};
tcc_span_f(_span, ::cloudchamber::level::INFO, "events_in_span", span_struct);
tcc_trace_msg("a trace message after span construction but before enter");
{
auto _guard = _span->enter();
emit_event();
}
tcc_error_msg("a fake error msg");
}
Emitting Events
tracing-cloudchamber
's ffi is implemented somewhat restrictively and based on
re-implementing how tracing
's own macros work.
That is, there are a selection of macros that expand to statically defined call-sites (::cloudchamber::Callsite
)
with associated static metadata. Just like tracing all field names must be defined up front and field values must either be primitive or
there must be an overload of the tcc_field_format
function that accepts const&
to it.
The macros support defining fields in two formats: Paired name and value pairs, or assumed named from the ident
passed in
e.g.
int val = 10;
tcc_info_msg_f(val)
will emit a INFO
level event with a field named val
and the value 10
Levels
::cloudchamber::level::ERROR;
::cloudchamber::level::WARN;
::cloudchamber::level::INFO;
::cloudchamber::level::DEBUG;
::cloudchamber::level::TRACE;
Available event macros
Raw Event
/// emit an event named after the call-site (file:line)
tcc_event(level);
/// emit an event with passed fields, names are assumed from `idents` passed
/// e.g, `tcc_event_f(::cloudchamber::level::TRACE, val)`
tcc_event_f(level, <ident>...);
/// emit an event with passed fields; fields are defined as named, value
/// e.g. `tcc_event_p(::cloudchamber::level::TRACE, val, 10)`
/// note that names are not quoted. that's done by the preprocessor.
tcc_event_p(level, <name, value>...);
/// emit an event with a field "message" (tracing's default) using the passed value
tcc_event_msg(level, msg);
/// emit an event with a field "message" (tracing's default) using the passed value
/// adds field using names form the `idents` passed
/// e.g. `tcc_event_msg_f(::cloudchamber::level::TRACE, "shaving a yak", val)`
tcc_event_msg_f(level, msg, <ident>...);
/// emit an event with a field "message" (tracing's default) using the passed value
/// adds field using paired names and values
/// e.g. `tcc_event_msg_p(::cloudchamber::level::TRACE, "shaving a yak", val, 10)`
tcc_event_msg_p(level, msg, <name, value>...);
Raw Named events
tcc_named_event(level, name);
tcc_named_event_f(level, name, <ident>...);
tcc_named_event_p(level, name, <name, value>...);
tcc_named_event_msg(level, name, msg);
tcc_named_event_msg(level, name, msg, <ident>...);
tcc_named_event_msg(level, name, msg, <name, value>...);
ERROR level Events
tcc_error();
tcc_error_f(<ident>...);
tcc_error_p(<name, value>...);
tcc_error_msg(msg);
tcc_error_msg_f(msg, <ident>...);
tcc_error_msg_p(msg, <name, value>...);
WARN level Events
tcc_warn();
tcc_warn_f(<ident>...);
tcc_warn_p(<name, value>...);
tcc_warn_msg(msg);
tcc_warn_msg_f(msg, <ident>...);
tcc_warn_msg_p(msg, <name, value>...);
INFO level Events
tcc_info();
tcc_info_f(<ident>...);
tcc_info_p(<name, value>...);
tcc_info_msg(msg);
tcc_info_msg_f(msg, <ident>...);
tcc_info_msg_p(msg, <name, value>...);
DEBUG level Events
tcc_debug();
tcc_debug_f(<ident>...);
tcc_debug_p(<name, value>...);
tcc_debug_msg(msg);
tcc_debug_msg_f(msg, <ident>...);
tcc_debug_msg_p(msg, <name, value>...);
TRACE level Events
tcc_trace();
tcc_trace_f(<ident>...);
tcc_trace_p(<name, value>...);
tcc_trace_msg(msg);
tcc_trace_msg_f(msg, <ident>...);
tcc_trace_msg_p(msg, <name, value>...);
Using Spans
Spans work almost just like in tracing
.
There are a set of macros that mirror the event macros to define a span and it's call-site.
You can then use auto _guard = span->enter();
and span->in_scope(...);
Example:
tcc_span_f(_span, ::cloudchamber::level::INFO, "events_in_span", span_struct);
{
auto _guard = _span->enter();
tcc_trace_msg("a trace message inside a span");
}
tcc_trace_msg("a trace message after and outside a span");
The tcc_span_<...>(ident, ...)
span macros expand to a call-site and a Boxed ::cloudchamber::Span
::rust::Box<::cloudchamber::Span> ident = /* span construction dependant on call-site enable */
Span::in_scope
Due to how cxx
currently restricts ffi
in_scope
can't accept a generic std::function<>
.
Instead you must wrap the function with a ::cloudchamber::ScopeLambda
std::array an_array = {"an", "array", "of", "strings"};
std::map<std::string, int> answers{
{"life", 42}, {"universe", 42}, {"everything", 42}};
tcc_trace_span_f(_t_span, "a_trace_span", an_array, answers);
_t_span->in_scope(::cloudchamber::ScopeLambda([=]() {
tcc_info_msg("in_scope info msg");
}));
Available span macros
Raw Spans
/// construct a span held by `ident` for level `level` named `name`
tcc_span(ident, level, name)
tcc_span_f(ident, level, name, <ident>...)
tcc_span_p(ident, level, name, <name, value>...)
ERROR Spans
tcc_error_span(ident, name)
tcc_error_span_f(ident, name, <ident>...)
tcc_error_span_p(ident, name, <name, value>...)
WARN Spans
tcc_warn_span(ident, name)
tcc_warn_span_f(ident, name, <ident>...)
tcc_warn_span_p(ident, name, <name, value>...)
INFO Spans
tcc_info_span(ident, name)
tcc_info_span_f(ident, name, <ident>...)
tcc_info_span_p(ident, name, <name, value>...)
DEBUG Spans
tcc_debug_span(ident, name)
tcc_debug_span_f(ident, name, <ident>...)
tcc_debug_span_p(ident, name, <name, value>...)
TRACE Spans
tINFOcc_trace_span(ident, name)
tcc_trace_span_f(ident, name, <ident>...)
tcc_trace_span_p(ident, name, <name, value>...)
Formatting field values
To attach a field value we need to know how to turn it into a &dyn tracing::field::Value
.
Fortunately tracing-cloudchamber
knows how to construct this for all "stringable" types
and some containers there-of.
In this case "stringable" means:
- Any type for which
std::to_string
exists. - Any type for which a
to_string(T const&)
function exists in scope. - Any type for which a
std::ostream& operator<<(std::ostream&, Tconst&)
function exists in scope - Any type for which
std::string ::cloudchamber::field_format(T const&)
exists
To handle containers the following are implemented:
std::string ::cloudchamber::field_format(std::array<T, N> const&)
for any "stringable" typeT
std::string ::cloudchamber::field_format(std::vector<T> const&)
for any "stringable" typeT
std::string ::cloudchamber::field_format(std::map<K, V> const&)
for any "stringable" typesK
andV
For custom types or containers simple implement one of those functions
e.g.
// by a std::string to_string(T const &)
struct MyTestStruct {
int val;
std::string str;
};
std::string to_string(MyTestStruct const &self) {
std::ostringstream out;
out << "MyTestStruct { val: " << self.val << ", str: \"" << self.str
<< "\" }";
return out.str();
}
// by std::ostring& operator<<
struct MyTestStruct2 {
int val;
std::string str;
friend std::ostream &operator<<(std::ostream &os, MyTestStruct2 const &self) {
os << "MyTestStruct { val: " << self.val << ", str: \"" << self.str
<< "\" }";
return os;
}
};
Dependencies
~0.8–2.5MB
~38K SLoC