/** * @file oboe.hpp - C++ liboboe wrapper primarily for generating SWIG interfaces * * This API should follow https://github.com/tracelytics/tracelons/wiki/Instrumentation-API */ #ifndef OBOE_HPP #define OBOE_HPP #include #include #define isinf #define isnan class Event; class Reporter; class Context; /** * Metadata is the X-Trace identifier and the information needed to work with it. */ class Metadata : private oboe_metadata_t { friend class Reporter; friend class SslReporter; friend class UdpReporter; friend class FileReporter; friend class Context; public: Metadata(oboe_metadata_t *md) { oboe_metadata_copy(this, md); } ~Metadata() { oboe_metadata_destroy(this); } static Metadata* fromString(std::string s) { oboe_metadata_t md; oboe_metadata_fromstr(&md, s.data(), s.size()); return new Metadata(&md); // copies md } // these new objects are managed by SWIG %newobject Event *createEvent(); static Metadata *makeRandom(bool sampled=true) { oboe_metadata_t md; oboe_metadata_init(&md); oboe_metadata_random(&md); if (sampled) { md.flags |= XTR_FLAGS_SAMPLED; } return new Metadata(&md); // copies md } Metadata *copy() { return new Metadata(this); } bool isValid() { return oboe_metadata_is_valid(this); } bool isSampled() { return oboe_metadata_is_sampled(this); } #ifdef SWIGJAVA std::string toStr() { #else std::string toString() { #endif char buf[OBOE_MAX_METADATA_PACK_LEN]; int rc = oboe_metadata_tostr(this, buf, sizeof(buf) - 1); if (rc == 0) { return std::string(buf); } else { return std::string(); // throw exception? } } }; /** * The Context class manages the metadata and the settings configuration. * * The metadata includes the X-Trace identifier fields and the information work working with it. * The metadata is needed before any trace messages can be sent and must either be generated for * new traces or derived from the X-Trace header of an existing trace. * * The settings information is used primarily to determine when a new request should be traced. * The information begins with configuration values for tracing_mode and sample_rate and then * updates are received periodically from the collector to adjust the rate at which traces * are generated. */ class Context { public: /** * Set the tracing mode. * * @param newMode One of * - OBOE_TRACE_NEVER(0) to disable tracing, * - OBOE_TRACE_ALWAYS(1) to start a new trace if needed, or * - OBOE_TRACE_THROUGH(2) to only add to an existing trace. */ static void setTracingMode(int newMode) { oboe_settings_mode_set(newMode); } /** * Set the default sample rate. * * This rate is used until overridden by the AppOptics servers. If not set then the * value comes from settings records downloaded from AppOptics. * * The rate is interpreted as a ratio out of OBOE_SAMPLE_RESOLUTION (currently 1,000,000). * * @param newRate A number between 0 (none) and OBOE_SAMPLE_RESOLUTION (a million) */ static void setDefaultSampleRate(int newRate) { oboe_settings_rate_set(newRate); } /** * Check if the current request should be traced based on the current settings. * * If in_xtrace is empty, or if it is identified as a foreign (ie. cross customer) * trace, then sampling will be considered as a new trace. * Otherwise sampling will be considered as adding to the current trace. * Different layers may have special rules. Also special rules for AppView * Web synthetic traces apply if in_tv_meta is given a non-empty string. * * This is designed to be called once per layer per request. * * @param layer Name of the layer being considered for tracing * @param in_xtrace Incoming X-Trace ID (NULL or empty string if not present) * @return Zero to not trace; otherwise return the sample rate used in the low order * bytes 0 to 2 and the sample source in the higher-order byte 3. */ static int sampleRequest( std::string layer, std::string in_xtrace) { int sample_rate = 0; int sample_source = 0; int rc = (oboe_sample_layer(layer.c_str(), in_xtrace.c_str(), &sample_rate, &sample_source)); return (rc == 0 ? 0 : (((sample_source & 0xFF) << 24) | (sample_rate & 0xFFFFFF))); } /** * Get a pointer to the current context (from thread-local storage) */ static oboe_metadata_t *get() { return oboe_context_get(); } /** * Get the current context as a printable string. */ #ifdef SWIGJAVA static std::string toStr() { #else static std::string toString() { #endif char buf[OBOE_MAX_METADATA_PACK_LEN]; oboe_metadata_t *md = Context::get(); int rc = oboe_metadata_tostr(md, buf, sizeof(buf) - 1); if (rc == 0) { return std::string(buf); } else { return std::string(); // throw exception? } } /** * Set the current context (this updates thread-local storage). */ static void set(oboe_metadata_t *md) { oboe_context_set(md); } /** * Set the current context from a string. */ static void fromString(std::string s) { oboe_context_set_fromstr(s.data(), s.size()); } // this new object is managed by SWIG %newobject static Metadata *copy() { return new Metadata(Context::get()); } static void setSampledFlag() { oboe_metadata_t *md = Context::get(); md->flags |= XTR_FLAGS_SAMPLED; } static void clear() { oboe_context_clear(); } static bool isValid() { return oboe_context_is_valid(); } static bool isSampled() { return oboe_context_is_sampled(); } /** * Initialize the Oboe subsystems. * * This should be called before any other oboe_* functions. However, in order * to make the library easier to work with, checks are in place so that it * will be called by any of the other functions that depend on it. * * Besides initializing the oboe library, this will also initialize a * reporter based on the values of environment variables, configuration * file options. */ static void init(std::string access_key, std::string hostname_alias) { oboe_init(access_key.c_str(), hostname_alias.c_str()); } /** * Initialize the Oboe subsytems using a specific reporter configuration. * * This should be called before any other oboe_* functions butm may also be * used to change or re-initialize the current reporter. To reconnect the * reporter use oboe_disconnect() and oboe_reconnect() instead. * * @param protocol One of OBOE_REPORTER_PROTOCOL_FILE, OBOE_REPORTER_PROTOCOL_UDP, * or OBOE_REPORTER_PROTOCOL_SSL. * @param args A configuration string for the specified protocol (protocol dependent syntax). * @return Zero on success; otherwise an error code. */ static int init_reporter(const char *protocol, const char *args) { return oboe_init_reporter(protocol, args); } /** * Disconnect or shut down the Oboe reporter, but allow it to be reconnect()ed. * * We don't make this a Reporter method in case there is other housework to do. * * @param rep Pointer to the active reporter object. */ static void disconnect(Reporter *rep); /** * Reconnect or restart the Oboe reporter. * * We don't make this a Reporter method in case there is other housework to do. * * @param rep Pointer to the active reporter object. */ static void reconnect(Reporter *rep); /** * Shut down the Oboe library. * * This releases any resources held by the library which may include terminating * child threads. */ static void shutdown() { oboe_shutdown(); } // these new objects are managed by SWIG %newobject static Event *createEvent(); static Event *startTrace(); private: }; class Event : private oboe_event_t { friend class Reporter; friend class SslReporter; friend class UdpReporter; friend class FileReporter; friend class Context; friend class Metadata; private: Event() { oboe_event_init(this, Context::get(), NULL); } Event(const oboe_metadata_t *md, bool addEdge=true) { // both methods copy metadata from md -> this if (addEdge) { // create_event automatically adds edge in event to md oboe_metadata_create_event(md, this); } else { // initializes new Event with this md's task_id & new random op_id; no edges set oboe_event_init(this, md, NULL); } } public: ~Event() { oboe_event_destroy(this); } // called e.g. from Python e.addInfo("Key", None) & Ruby e.addInfo("Key", nil) bool addInfo(char *key, void* val) { // oboe_event_add_info(evt, key, NULL) does nothing (void) key; (void) val; return true; } bool addInfo(char *key, const std::string& val) { if (memchr(val.data(), '\0', val.size())) { return oboe_event_add_info_binary(this, key, val.data(), val.size()) == 0; } else { return oboe_event_add_info(this, key, val.data()) == 0; } } bool addInfo(char *key, long val) { int64_t val_ = val; return oboe_event_add_info_int64(this, key, val_) == 0; } bool addInfo(char *key, double val) { return oboe_event_add_info_double(this, key, val) == 0; } bool addEdge(oboe_metadata_t *md) { return oboe_event_add_edge(this, md) == 0; } bool addEdgeStr(const std::string& val) { return oboe_event_add_edge_fromstr(this, val.c_str(), val.size()) == 0; } /** * Get a new copy of this metadata. * * NOTE: The returned object must be "delete"d. */ Metadata* getMetadata() { return new Metadata(&this->metadata); } std::string metadataString() { char buf[OBOE_MAX_METADATA_PACK_LEN]; int rc = oboe_metadata_tostr(&this->metadata, buf, sizeof(buf) - 1); if (rc == 0) { return std::string(buf); } else { return std::string(); // throw exception? } } /** * Report this event. * * This sends the event using the default reporter. * * @return True on success; otherwise an error message is logged. */ bool send() { return (oboe_event_send(OBOE_SEND_EVENT, this, Context::get()) >= 0); } static Event* startTrace(const oboe_metadata_t *md); }; class Span { public: static void createHttpSpan(const char *transaction, const char *url, const int64_t duration, const int status, const char *method, const int has_error); }; void Span::createHttpSpan(const char *transaction, const char *url, const int64_t duration, const int status, const char *method, const int has_error) { oboe_http_span(transaction, url, duration, status, method, has_error); } class MetricTags { friend class CustomMetrics; public: MetricTags(size_t count) { tags = new oboe_metric_tag_t[count]; size = count; } ~MetricTags() { delete[] tags; } bool add(size_t index, char *k, char *v) { if (index < size) { tags[index].key = k; tags[index].value = v; return true; } return false; } private: oboe_metric_tag_t* get() const { return tags; } oboe_metric_tag_t *tags; size_t size; }; class CustomMetrics { public: static int summary(const char *name, const double value, const int count, const int host_tag, const MetricTags *tags, const size_t tags_count) { return oboe_custom_metric_summary(name, value, count, host_tag, tags->get(), tags_count); } static int increment(const char *name, const int count, const int host_tag, const MetricTags *tags, const size_t tags_count) { return oboe_custom_metric_increment(name, count, host_tag, tags->get(), tags_count); } }; /** * Create a new event object using the thread's context. * * NOTE: The returned object must be "delete"d. */ Event *Context::createEvent() { return new Event(Context::get()); } /** * Create a new event object using this Metadata's context. * * NOTE: The returned object must be "delete"d. */ Event *Metadata::createEvent() { return new Event(this); } /** * Create a new event object with a new trace context. * * NOTE: The returned object must be "delete"d. */ Event *Context::startTrace() { oboe_metadata_t *md = Context::get(); oboe_metadata_random(md); return new Event(); } /** * Create a new event object using the given metadata context. * * NOTE: The metadata context must be unique to the new trace. * * NOTE: The returned object must be "delete"d. * * @param md The metadata object to use when creating the new event. */ Event *Event::startTrace(const oboe_metadata_t *md) { return new Event(md, false); } class Reporter : private oboe_reporter_t { friend class Context; // Access to the private oboe_reporter_t base structure. public: /** * Initialize a reporter structure for use with the specified protocol. * * @param protocol One of "file", "udp", or "ssl". * @param args A configuration string for the specified protocol (protocol dependent syntax). */ Reporter(const char *protocol, const char *args) { oboe_init_reporter(protocol, args); } ~Reporter() { oboe_reporter_destroy(this); } bool sendReport(Event *evt) { return oboe_event_send(OBOE_SEND_EVENT, evt, Context::get()) >= 0; } bool sendReport(Event *evt, oboe_metadata_t *md) { return oboe_event_send(OBOE_SEND_EVENT, evt, md) >= 0; } bool sendStatus(Event *evt) { return oboe_event_send(OBOE_SEND_STATUS, evt, Context::get()) >= 0; } bool sendStatus(Event *evt, oboe_metadata_t *md) { return oboe_event_send(OBOE_SEND_STATUS, evt, md) >= 0; } }; class SslReporter : private oboe_reporter_t { public: SslReporter(const char *config) { oboe_reporter_ssl_init(this, config); } ~SslReporter() { oboe_reporter_destroy(this); } bool sendReport(Event *evt) { return oboe_event_send(OBOE_SEND_EVENT, evt, Context::get()) >= 0; } bool sendReport(Event *evt, oboe_metadata_t *md) { return oboe_event_send(OBOE_SEND_EVENT, evt, md) >= 0; } bool sendStatus(Event *evt) { return oboe_event_send(OBOE_SEND_STATUS, evt, Context::get()) >= 0; } bool sendStatus(Event *evt, oboe_metadata_t *md) { return oboe_event_send(OBOE_SEND_STATUS, evt, md) >= 0; } }; class UdpReporter : private oboe_reporter_t { public: UdpReporter(const char *addr, const char *port=NULL) { if (port == NULL) port = "7831"; oboe_reporter_udp_init(this, addr, port); } ~UdpReporter() { oboe_reporter_destroy(this); } bool sendReport(Event *evt) { return oboe_event_send(OBOE_SEND_EVENT, evt, Context::get()) >= 0; } bool sendReport(Event *evt, oboe_metadata_t *md) { return oboe_event_send(OBOE_SEND_EVENT, evt, md) >= 0; } bool sendStatus(Event *evt) { return oboe_event_send(OBOE_SEND_STATUS, evt, Context::get()) >= 0; } bool sendStatus(Event *evt, oboe_metadata_t *md) { return oboe_event_send(OBOE_SEND_STATUS, evt, md) >= 0; } }; class FileReporter : private oboe_reporter_t { public: FileReporter(const char *file) { oboe_reporter_file_init(this, file); } ~FileReporter() { oboe_reporter_destroy(this); } bool sendReport(Event *evt) { return oboe_event_send(OBOE_SEND_EVENT, evt, Context::get()) >= 0; } bool sendReport(Event *evt, oboe_metadata_t *md) { return oboe_event_send(OBOE_SEND_EVENT, evt, md) >= 0; } bool sendStatus(Event *evt) { return oboe_event_send(OBOE_SEND_STATUS, evt, Context::get()) >= 0; } bool sendStatus(Event *evt, oboe_metadata_t *md) { return oboe_event_send(OBOE_SEND_STATUS, evt, md) >= 0; } }; /** * Base class for a diagnostic log message handler. */ class DebugLogger { public: virtual ~DebugLogger() {} virtual void log(int module, int level, const char *source_name, int source_lineno, const char *msg) = 0; }; /** * "C" language wrapper for DebugLogger classes. * * A logging function that can be added to the logger chain using * DebugLog::addDebugLogger(). * * @param context The context pointer that was registered in the call to * DebugLog::addDebugLogger(). Use it to pass the pointer-to-self for * objects (ie. "this" in C++) or just a structure in C, May be * NULL. * @param module The module identifier as passed to oboe_debug_logger(). * @param level The diagnostic detail level as passed to oboe_debug_logger(). * @param source_name Name of the source file as passed to oboe_debug_logger(). * @param source_lineno Number of the line in the source file where message is * logged from as passed to oboe_debug_logger(). * @param msg The formatted message produced from the format string and its * arguments as passed to oboe_debug_logger(). */ extern "C" void oboe_debug_log_handler(void *context, int module, int level, const char *source_name, int source_lineno, const char *msg) { ((DebugLogger *)context)->log(module, level, source_name, source_lineno, msg); } class DebugLog { public: /** * Get a printable name for a diagnostics logging level. * * @param level A detail level in the range 0 to 6 (OBOE_DEBUG_FATAL to OBOE_DEBUG_HIGH). */ static std::string getLevelName(int level) { return std::string(oboe_debug_log_level_name(level)); } /** * Get a printable name for a diagnostics logging module identifier. * * @param module One of the OBOE_MODULE_* values. */ static std::string getModuleName(int module) { return std::string(oboe_debug_module_name(module)); } /** * Get the maximum logging detail level for a module or for all modules. * * This level applies to the default logger only. Added loggers get all messages * below their registed detail level and need to do their own module-specific * filtering. * * @param module One of the OBOE_MODULE_* values. Use OBOE_MODULE_ALL (-1) to * get the overall maximum detail level. * @return Maximum detail level value for module (or overall) where zero is the * lowest and higher values generate more detailed log messages. */ static int getLevel(int module) { return oboe_debug_log_level_get(module); } /** * Set the maximum logging detail level for a module or for all modules. * * This level applies to the default logger only. Added loggers get all messages * below their registered detail level and need to do their own module-specific * filtering. * * @param module One of the OBOE_MODULE_* values. Use OBOE_MODULE_ALL to set * the overall maximum detail level. * @param newLevel Maximum detail level value where zero is the lowest and higher * values generate more detailed log messages. */ static void setLevel(int module, int newLevel) { oboe_debug_log_level_set(module, newLevel); } /** * Set the output stream for the default logger. * * @param newStream A valid, open FILE* stream or NULL to disable the default logger. * @return Zero on success; otherwise an error code (normally from errno). */ static int setOutputStream(FILE *newStream) { return oboe_debug_log_to_stream(newStream); } /** * Set the default logger to write to the specified file. * * A NULL or empty path name will disable the default logger. * * If the file exists then it will be opened in append mode. * * @param pathname The path name of the * @return Zero on success; otherwise an error code (normally from errno). */ static int setOutputFile(const char *pathname) { return oboe_debug_log_to_file(pathname); } /** * Add a logger that takes messages up to a given logging detail level. * * This adds the logger to a chain in order of the logging level. Log messages * are passed to each logger down the chain until the remaining loggers only * accept messages of a lower detail level. * * @return Zero on success, one if re-registered with the new logging level, and * otherwise a negative value to indicate an error. */ static int addDebugLogger(DebugLogger *newLogger, int logLevel) { return oboe_debug_log_add(oboe_debug_log_handler, newLogger, logLevel); } /** * Remove a logger. * * Remove the logger from the message handling chain. * * @return Zero on success, one if it was not found, and otherwise a negative * value to indicate an error. */ static int removeDebugLogger(DebugLogger *oldLogger) { return oboe_debug_log_remove(oboe_debug_log_handler, oldLogger); } /** * Low-level diagnostics logging function. * * Use this to pass * @param module One of the numeric module identifiers defined in debug.h - used to control logging detail by module. * @param level Diagnostic detail level of this message - used to control logging volume by detail level. * @param source_name Name of the source file, if available, or another useful name, or NULL. * @param source_lineno Number of the line in the source file where message is logged from, if available, or zero. * @param format A C language printf format specification string. * @param args A variable argument list in VA_ARG format containing arguments for each argument specifier in the format. */ static void logMessage(int module, int level, const char *source_name, int source_lineno, const char *msg) { oboe_debug_logger(module, level, source_name, source_lineno, "%s", msg); } }; class Config { public: /** * Check if the Oboe library is compatible with a given version.revision. * * This will succeed if the library is at least as recent as specified and if no * definitions have been removed since that revision. * * @param version The library's version number which increments every time the API changes. * @param revision The revision of the current version of the library. * @return Non-zero if the Oboe library is considered compatible with the specified revision. */ static bool checkVersion(int version, int revision) { return (oboe_config_check_version(version, revision) != 0); } /** * Get the Oboe library version number. * * This number increments whenever an incompatible change to the API/ABI is made. * * @return The library's version number or -1 if the version is not known. */ static int getVersion() { return oboe_config_get_version(); } /** * Get the Oboe library revision number. * * This number increments whenever a compatible change is made to the * API/ABI (ie. an addition). * * @return The library's revision number or -1 if not known. */ static int getRevision() { return oboe_config_get_revision(); } }; void Context::disconnect(Reporter *rep) { oboe_disconnect(rep); } void Context::reconnect(Reporter *rep) { oboe_reconnect(rep); } #endif // OBOE_HPP