| Crates.io | tracing-cloudchamber |
| lib.rs | tracing-cloudchamber |
| version | 0.1.0 |
| created_at | 2025-04-07 03:39:35.143358+00 |
| updated_at | 2025-04-07 03:39:35.143358+00 |
| description | Extend tracing with an ffi via cxx to emit events and create spans in C++ |
| homepage | |
| repository | https://github.com/Ryex/tracing-cloudchamber |
| max_upload_size | |
| id | 1623618 |
| size | 91,921 |
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.
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:
#include "cloudchamber/tracing.h"
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");
}
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
::cloudchamber::level::ERROR;
::cloudchamber::level::WARN;
::cloudchamber::level::INFO;
::cloudchamber::level::DEBUG;
::cloudchamber::level::TRACE;
/// 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>...);
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>...);
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>...);
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>...);
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>...);
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>...);
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>...);
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 */
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");
}));
/// 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>...)
tcc_error_span(ident, name)
tcc_error_span_f(ident, name, <ident>...)
tcc_error_span_p(ident, name, <name, value>...)
tcc_warn_span(ident, name)
tcc_warn_span_f(ident, name, <ident>...)
tcc_warn_span_p(ident, name, <name, value>...)
tcc_info_span(ident, name)
tcc_info_span_f(ident, name, <ident>...)
tcc_info_span_p(ident, name, <name, value>...)
tcc_debug_span(ident, name)
tcc_debug_span_f(ident, name, <ident>...)
tcc_debug_span_p(ident, name, <name, value>...)
tINFOcc_trace_span(ident, name)
tcc_trace_span_f(ident, name, <ident>...)
tcc_trace_span_p(ident, name, <name, value>...)
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:
std::to_string exists.to_string(T const&) function exists in scope.std::ostream& operator<<(std::ostream&, Tconst&) function exists in scopestd::string ::cloudchamber::field_format(T const&) existsTo handle containers the following are implemented:
std::string ::cloudchamber::field_format(std::array<T, N> const&) for any "stringable" type Tstd::string ::cloudchamber::field_format(std::vector<T> const&) for any "stringable" type Tstd::string ::cloudchamber::field_format(std::map<K, V> const&) for any "stringable" types K and VFor 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;
}
};