diff --git a/BUILD.bazel b/BUILD.bazel index 2ca95bd..7593cdb 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,7 +1,8 @@ cc_library( name = "opentracing", - srcs = glob(["src/*.cpp"]), + srcs = glob(["src/*.cpp"], exclude=["src/dynamic_load_unsupported.cpp"]), hdrs = glob(["include/opentracing/*.h"]) + [ + ":include/opentracing/config.h", ":include/opentracing/version.h", ], strip_include_prefix = "include", @@ -19,14 +20,19 @@ genrule( "cmake/*", "src/*", ]), - outs = ["include/opentracing/version.h"], + outs = [ + "include/opentracing/config.h", + "include/opentracing/version.h" + ], cmd = """ TEMP_DIR=$$(mktemp -d) - TARGET=$${PWD}/$@ + CONFIG_H_OUT=$${PWD}/$(location :include/opentracing/config.h) + VERSION_H_OUT=$${PWD}/$(location :include/opentracing/version.h) OPENTRACING_ROOT=$$(dirname $${PWD}/$(location :CMakeLists.txt)) cd $$TEMP_DIR cmake -DBUILD_TESTING=OFF -DBUILD_MOCKTRACER=OFF -L $$OPENTRACING_ROOT - mv include/opentracing/version.h $$TARGET + mv include/opentracing/config.h $$CONFIG_H_OUT + mv include/opentracing/version.h $$VERSION_H_OUT rm -rf $$TEMP_DIR """, ) diff --git a/CMakeLists.txt b/CMakeLists.txt index b223316..cb88913 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,14 +25,6 @@ SET(CPACK_PACKAGE_VERSION_MINOR ${OPENTRACING_VERSION_MINOR}) SET(CPACK_PACKAGE_VERSION_PATCH ${OPENTRACING_VERSION_PATCH}) include(CPack) -# ============================================================================== -# Set up generated version.h - -configure_file(version.h.in include/opentracing/version.h) -include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) -install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/opentracing - DESTINATION include) - # ============================================================================== # Configure compilers @@ -40,6 +32,7 @@ set(CMAKE_CXX_STANDARD 11) if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weverything \ -Wno-c++98-compat \ + -Wno-c++98-compat-pedantic \ -Wno-c++98-compat-bind-to-temporary-copy \ -Wno-weak-vtables \ -Wno-exit-time-destructors \ @@ -65,23 +58,70 @@ if(ENABLE_LINTING) endif() # ============================================================================== -# OpenTracing library targets +# Check for weak symbol support -include_directories(include) -include_directories(SYSTEM 3rd_party/include) +try_compile( + SUPPORTS_WEAK_SYMBOLS + "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp" + SOURCES ${CMAKE_SOURCE_DIR}/cmake/weak_symbol.cpp) + +# ============================================================================== +# Set up options option(BUILD_SHARED_LIBS "Build as a shared library" ON) option(BUILD_STATIC_LIBS "Build as a static library" ON) option(BUILD_MOCKTRACER "Build mocktracer library" ON) +option(BUILD_DYNAMIC_LOADING "Build with dynamic loading support" ON) + +if (BUILD_DYNAMIC_LOADING) + if (NOT SUPPORTS_WEAK_SYMBOLS OR NOT UNIX) + message(WARNING "Building without dynamic loading support.") + set(BUILD_DYNAMIC_LOADING OFF) + endif() +endif() + +set(OPENTRACING_BUILD_DYNAMIC_LOADING ${BUILD_DYNAMIC_LOADING}) if (NOT BUILD_SHARED_LIBS AND NOT BUILD_STATIC_LIBS) message(FATAL_ERROR "One or both of BUILD_SHARED_LIBS or BUILD_STATIC_LIBS must be set to ON to build") endif() -set(SRCS src/propagation.cpp src/noop.cpp src/tracer.cpp) +# ============================================================================== +# Set up generated header files config.h and version.h + +configure_file(version.h.in include/opentracing/version.h) +configure_file(config.h.in include/opentracing/config.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/opentracing + DESTINATION include) + +# ============================================================================== +# OpenTracing library targets + +include_directories(include) +include_directories(SYSTEM 3rd_party/include) + +set(SRCS src/propagation.cpp + src/dynamic_load.cpp + src/noop.cpp + src/tracer.cpp + src/tracer_factory.cpp) + +if (BUILD_DYNAMIC_LOADING) + list(APPEND SRCS src/dynamic_load_unix.cpp) +else() + list(APPEND SRCS src/dynamic_load_unsupported.cpp) +endif() + +list(APPEND LIBRARIES "") +if (BUILD_DYNAMIC_LOADING) + list(APPEND LIBRARIES ${CMAKE_DL_LIBS}) +endif() + if (BUILD_SHARED_LIBS) add_library(opentracing SHARED ${SRCS}) + target_link_libraries(opentracing ${LIBRARIES}) target_include_directories(opentracing INTERFACE "$") set_target_properties(opentracing PROPERTIES VERSION ${OPENTRACING_VERSION_STRING} SOVERSION ${OPENTRACING_VERSION_MAJOR}) @@ -96,6 +136,7 @@ endif() if (BUILD_STATIC_LIBS) add_library(opentracing-static STATIC ${SRCS}) + target_link_libraries(opentracing-static ${LIBRARIES}) set_target_properties(opentracing-static PROPERTIES OUTPUT_NAME opentracing) target_include_directories(opentracing-static INTERFACE "$") install(TARGETS opentracing-static EXPORT OpenTracingTargets @@ -147,3 +188,10 @@ include(CTest) if(BUILD_TESTING) add_subdirectory(test) endif() + +# ============================================================================== +# Examples + +if(BUILD_TESTING) + add_subdirectory(example) +endif() diff --git a/cmake/weak_symbol.cpp b/cmake/weak_symbol.cpp new file mode 100644 index 0000000..902f683 --- /dev/null +++ b/cmake/weak_symbol.cpp @@ -0,0 +1,3 @@ +void __attribute((weak)) f(); + +int main() { return 0; } diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..18d1172 --- /dev/null +++ b/config.h.in @@ -0,0 +1,3 @@ +#pragma once + +#cmakedefine OPENTRACING_BUILD_DYNAMIC_LOADING diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..39565cc --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(tutorial) +add_subdirectory(dynamic_load) diff --git a/example/dynamic_load/CMakeLists.txt b/example/dynamic_load/CMakeLists.txt new file mode 100644 index 0000000..1413a4b --- /dev/null +++ b/example/dynamic_load/CMakeLists.txt @@ -0,0 +1,4 @@ +if (BUILD_DYNAMIC_LOADING AND BUILD_SHARED_LIBS) + add_executable(dynamic_load-example dynamic_load-example.cpp) + target_link_libraries(dynamic_load-example opentracing) +endif() diff --git a/example/dynamic_load/dynamic_load-example.cpp b/example/dynamic_load/dynamic_load-example.cpp new file mode 100644 index 0000000..8ef1cb1 --- /dev/null +++ b/example/dynamic_load/dynamic_load-example.cpp @@ -0,0 +1,64 @@ +// Demonstrates how to load a tracer library in at runtime and how to use it +// to construct spans. To run it using the mocktracer, invoke with +// +// TRACER_CONFIG=`mktemp` +// echo '{ "output_file": "/dev/stdout" }' > $TRACER_CONFIG +// dynamic_load-example /path/to/libopentracing_mocktracer.so $TRACER_CONFIG + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cerr << "Usage: \n"; + return -1; + } + + // Load the tracer library. + std::string error_message; + auto handle_maybe = + opentracing::DynamicallyLoadTracingLibrary(argv[1], error_message); + if (!handle_maybe) { + std::cerr << "Failed to load tracer library " << error_message << "\n"; + return -1; + } + + // Read in the tracer's configuration. + std::ifstream istream{argv[2]}; + if (!istream.good()) { + std::cerr << "Failed to open tracer config file " << argv[2] << ": " + << std::strerror(errno) << "\n"; + return -1; + } + std::string tracer_config{std::istreambuf_iterator{istream}, + std::istreambuf_iterator{}}; + + // Construct a tracer. + auto& tracer_factory = handle_maybe->tracer_factory(); + auto tracer_maybe = + tracer_factory.MakeTracer(tracer_config.c_str(), error_message); + if (!tracer_maybe) { + std::cerr << "Failed to create tracer " << error_message << "\n"; + return -1; + } + auto& tracer = *tracer_maybe; + + // Use the tracer to create some spans. + { + auto span_a = tracer->StartSpan("A"); + assert(span_a != nullptr); + span_a->SetTag("abc", 123); + auto span_b = + tracer->StartSpan("B", {opentracing::ChildOf(&span_a->context())}); + assert(span_b != nullptr); + span_b->SetTag("xyz", 987); + } + + tracer->Close(); + return 0; +} diff --git a/example/tutorial/CMakeLists.txt b/example/tutorial/CMakeLists.txt new file mode 100644 index 0000000..37ea6d3 --- /dev/null +++ b/example/tutorial/CMakeLists.txt @@ -0,0 +1,5 @@ +if (BUILD_MOCKTRACER AND BUILD_SHARED_LIBS) + include_directories(${CMAKE_SOURCE_DIR}/mocktracer/include) + add_executable(tutorial-example tutorial-example.cpp) + target_link_libraries(tutorial-example opentracing_mocktracer) +endif() diff --git a/example/tutorial/text_map_carrier.h b/example/tutorial/text_map_carrier.h new file mode 100644 index 0000000..22829e8 --- /dev/null +++ b/example/tutorial/text_map_carrier.h @@ -0,0 +1,37 @@ +#ifndef LIGHTSTEP_TEXT_MAP_CARRIER +#define LIGHTSTEP_TEXT_MAP_CARRIER + +#include +#include +#include + +using opentracing::TextMapReader; +using opentracing::TextMapWriter; +using opentracing::expected; +using opentracing::string_view; + +class TextMapCarrier : public TextMapReader, public TextMapWriter { + public: + TextMapCarrier(std::unordered_map& text_map) + : text_map_(text_map) {} + + expected Set(string_view key, string_view value) const override { + text_map_[key] = value; + return {}; + } + + expected ForeachKey( + std::function(string_view key, string_view value)> f) + const override { + for (const auto& key_value : text_map_) { + auto result = f(key_value.first, key_value.second); + if (!result) return result; + } + return {}; + } + + private: + std::unordered_map& text_map_; +}; + +#endif // LIGHTSTEP_TEXT_MAP_CARRIER diff --git a/example/tutorial/tutorial-example.cpp b/example/tutorial/tutorial-example.cpp new file mode 100644 index 0000000..e842765 --- /dev/null +++ b/example/tutorial/tutorial-example.cpp @@ -0,0 +1,99 @@ +// Demonstrates basic usage of the OpenTracing API. Uses OpenTracing's +// mocktracer to capture all the recorded spans as JSON. + +#include +#include +#include +#include +#include +#include +#include "text_map_carrier.h" +using namespace opentracing; +using namespace opentracing::mocktracer; + +int main() { + MockTracerOptions options; + std::unique_ptr output{new std::ostringstream{}}; + std::ostringstream& oss = *output; + options.recorder = std::unique_ptr{ + new JsonRecorder{std::move(output)}}; + + std::shared_ptr tracer{ + new MockTracer{std::move(options)}}; + + auto parent_span = tracer->StartSpan("parent"); + assert(parent_span); + + // Create a child span. + { + auto child_span = + tracer->StartSpan("childA", {ChildOf(&parent_span->context())}); + assert(child_span); + + // Set a simple tag. + child_span->SetTag("simple tag", 123); + + // Set a complex tag. + child_span->SetTag("complex tag", + Values{123, Dictionary{{"abc", 123}, {"xyz", 4.0}}}); + + // Log simple values. + child_span->Log({{"event", "simple log"}, {"abc", 123}}); + + // Log complex values. + child_span->Log({{"event", "complex log"}, + {"data", Dictionary{{"a", 1}, {"b", Values{1, 2}}}}}); + + child_span->Finish(); + } + + // Create a follows from span. + { + auto child_span = + tracer->StartSpan("childB", {FollowsFrom(&parent_span->context())}); + + // child_span's destructor will finish the span if not done so explicitly. + } + + // Use custom timestamps. + { + auto t1 = SystemClock::now(); + auto t2 = SteadyClock::now(); + auto span = tracer->StartSpan( + "useCustomTimestamps", + {ChildOf(&parent_span->context()), StartTimestamp(t1)}); + assert(span); + span->Finish({FinishTimestamp(t2)}); + } + + // Extract and Inject a span context. + { + std::unordered_map text_map; + TextMapCarrier carrier(text_map); + auto err = tracer->Inject(parent_span->context(), carrier); + assert(err); + auto span_context_maybe = tracer->Extract(carrier); + assert(span_context_maybe); + auto span = tracer->StartSpan("propagationSpan", + {ChildOf(span_context_maybe->get())}); + } + + // You get an error when trying to extract a corrupt span. + { + std::unordered_map text_map = { + {"x-ot-span-context", "123"}}; + TextMapCarrier carrier(text_map); + auto err = tracer->Extract(carrier); + assert(!err); + assert(err.error() == span_context_corrupted_error); + // How to get a readable message from the error. + std::cout << "Example error message: \"" << err.error().message() << "\"\n"; + } + + parent_span->Finish(); + tracer->Close(); + + std::cout << "\nRecorded spans as JSON:\n\n"; + std::cout << oss.str() << "\n"; + return 0; +} diff --git a/include/opentracing/dynamic_load.h b/include/opentracing/dynamic_load.h new file mode 100644 index 0000000..1d265ef --- /dev/null +++ b/include/opentracing/dynamic_load.h @@ -0,0 +1,125 @@ +#ifndef OPENTRACING_DYNAMIC_LOAD_H +#define OPENTRACING_DYNAMIC_LOAD_H + +#include +#include +#include +#include +#include + +// OpenTracingMakeTracerFactory provides a common hook that can be used to +// create an TracerFactory from a dynamically loaded library. Users should +// prefer to use the function DynamicallyLoadTracingLibrary over calling it +// directly. +// +// It takes the parameter `opentracing_version` representing the version of +// opentracing used by the caller. Upon success it returns the code `0` and +// sets `tracer_factory` to point to an instance of TracerFactory. +// +// On failure, it returns a non-zero error code and sets `error_category` to +// point to an std::error_category for the returned error code. +// +// Example usage, +// +// const std::error_category* error_category = nullptr; +// opentracing::TracerFactory* tracer_factory = nullptr; +// int rcode = opentracing_make_factory( +// OPENTRACING_VERSION, +// &static_cast(error_category), +// &static_cast(tracer_factory)); +// if (rcode == 0) { +// // success +// assert(tracer_factory != nullptr); +// } else { +// // failure +// assert(error_category != nullptr); +// std::error_code error{rcode, *error_category}; +// } +extern "C" { +#ifdef OPENTRACING_BUILD_DYNAMIC_LOADING +int __attribute((weak)) +OpenTracingMakeTracerFactory(const char* opentracing_version, + const void** error_category, + void** tracer_factory); +#endif +} // extern "C" + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +// Returns the std::error_category class used for opentracing dynamic loading +// errors. +// +// See +// http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-1.html +// https://ned14.github.io/boost.outcome/md_doc_md_03-tutorial_b.html +const std::error_category& dynamic_load_error_category(); + +// `dynamic_load_failure_error` occurs when dynamically loading a tracer library +// fails. Possible reasons could be the library doesn't exist or it is missing +// the required symbols. +const std::error_code dynamic_load_failure_error(1, + dynamic_load_error_category()); + +// `dynamic_load_not_supported_error` means dynamic loading of tracing libraries +// is not supported for the platform used. +const std::error_code dynamic_load_not_supported_error( + 2, dynamic_load_error_category()); + +// `incompatible_library_versions_error` occurs if the tracing library +// dynamically loaded uses an incompatible version of opentracing. +const std::error_code incompatible_library_versions_error( + 3, dynamic_load_error_category()); + +class DynamicLibraryHandle { + public: + virtual ~DynamicLibraryHandle() = default; +}; + +// Provides a handle to a dynamically loaded tracing library that can be used +// to create tracers. +// +// Note: The handle must not be destructed while any associated tracers are +// still in use. +// +// See TracerFactory +class DynamicTracingLibraryHandle { + public: + DynamicTracingLibraryHandle() = default; + + DynamicTracingLibraryHandle( + std::unique_ptr&& tracer_factory, + std::unique_ptr&& dynamic_library_handle) noexcept; + + const TracerFactory& tracer_factory() const noexcept { + return *tracer_factory_; + } + + private: + std::unique_ptr dynamic_library_handle_; + std::unique_ptr tracer_factory_; +}; + +// Dynamically loads a tracing library and returns a handle that can be used +// to create tracers. +// +// Example: +// std::string error_message; +// auto handle_maybe = DynamicallyLoadTracingLibrary( +// "libtracing_vendor.so", +// error_message); +// if (handle_maybe) { +// // success +// auto& tracer_factory = handle_maybe->tracer_factory(); +// } else { +// // failure +// std::error_code error = handle_maybe.error(); +// // `error_message` may also contain a more descriptive message +// } +// +// See DynamicTracingLibraryHandle, TracerFactory +expected DynamicallyLoadTracingLibrary( + const char* shared_library, std::string& error_message) noexcept; +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing + +#endif // OPENTRACING_DYNAMIC_LOAD_H diff --git a/include/opentracing/propagation.h b/include/opentracing/propagation.h index d7392d3..62c9ce3 100644 --- a/include/opentracing/propagation.h +++ b/include/opentracing/propagation.h @@ -64,15 +64,14 @@ enum class SpanReferenceType { // https://ned14.github.io/boost.outcome/md_doc_md_03-tutorial_b.html const std::error_category& propagation_error_category(); -// `invalid_span_context_error` errors occur when Tracer::Inject() is asked to -// operate on a SpanContext which it is not prepared to handle (for -// example, since it was created by a different tracer implementation). +// `invalid_span_context_error` occurs when Tracer::Inject() is asked to operate +// on a SpanContext which it is not prepared to handle (for example, since it +// was created by a different tracer implementation). const std::error_code invalid_span_context_error(1, propagation_error_category()); -// `invalid_carrier_error` errors occur when Tracer::Inject() or -// Tracer::Extract() implementations expect a different type of `carrier` than -// they are given. +// `invalid_carrier_error` occurs when Tracer::Inject() or Tracer::Extract() +// implementations expect a different type of `carrier` than they are given. const std::error_code invalid_carrier_error(2, propagation_error_category()); // `span_context_corrupted_error` occurs when the `carrier` passed to diff --git a/include/opentracing/string_view.h b/include/opentracing/string_view.h index 83676dd..a30eb7f 100644 --- a/include/opentracing/string_view.h +++ b/include/opentracing/string_view.h @@ -85,6 +85,9 @@ class string_view { // Returns a RandomAccessIterator for the last element. const char* end() const noexcept { return data() + length(); } + // Returns the character in the i-th position. + const char& operator[](std::size_t i) { return *(data() + i); } + private: const char* data_; // Pointer to external storage size_t length_; // Length of data pointed to by 'data_' diff --git a/include/opentracing/tracer_factory.h b/include/opentracing/tracer_factory.h new file mode 100644 index 0000000..c2d0354 --- /dev/null +++ b/include/opentracing/tracer_factory.h @@ -0,0 +1,55 @@ +#ifndef OPENTRACING_TRACER_FACTORY_H +#define OPENTRACING_TRACER_FACTORY_H + +#include +#include + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +// Returns the std::error_category class used for tracer factory errors. +// +// See +// http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-1.html +// https://ned14.github.io/boost.outcome/md_doc_md_03-tutorial_b.html +const std::error_category& tracer_factory_error_category(); + +// `configuration_parse_error` occurs when the configuration string used to +// construct a tracer does not adhere to the expected format. +const std::error_code configuration_parse_error( + 1, tracer_factory_error_category()); + +// `invalid_configuration_error` occurs if the requested configuration for a +// tracer has invalid values. +const std::error_code invalid_configuration_error( + 2, tracer_factory_error_category()); + +// TracerFactory constructs tracers from configuration strings. +class TracerFactory { + public: + virtual ~TracerFactory() = default; + + // Creates a tracer with the requested `configuration`. + // + // Example, + // const char* configuration = R"( + // "collector": "localhost:123", + // "max_buffered_spans": 500 + // )"; + // std:string error_message; + // auto tracer_maybe = tracer_factory->MakeTracer(configuration, + // error_message); + // if (tracer_mabye) { + // // success + // std::shared_ptr tracer = *tracer_maybe; + // } else { + // // failure + // std::error_code error = tracer_maybe.error(); + // // `error_message` may also contain a more descriptive message + // } + virtual expected> MakeTracer( + const char* configuration, std::string& error_message) const noexcept = 0; +}; +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing + +#endif // OPENTRACING_TRACER_FACTORY_H diff --git a/mocktracer/3rd_party/base64/BUILD b/mocktracer/3rd_party/base64/BUILD deleted file mode 100644 index 5e4c781..0000000 --- a/mocktracer/3rd_party/base64/BUILD +++ /dev/null @@ -1,10 +0,0 @@ -cc_library( - name = "base64", - srcs = glob(["src/*.cpp"]), - hdrs = glob(["include/opentracing/**/*.h"]), - strip_include_prefix = "include", - visibility = ["//visibility:public"], - deps = [ - "//:opentracing", - ] -) diff --git a/mocktracer/3rd_party/base64/README.txt b/mocktracer/3rd_party/base64/README.txt deleted file mode 100644 index 9f893d6..0000000 --- a/mocktracer/3rd_party/base64/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -Modiefied from Envoy (https://github.com/envoyproxy/envoy). - -commit d70404f7e5c89ab2c5fab72b516e2bf601969557 diff --git a/mocktracer/BUILD b/mocktracer/BUILD index a639013..3b22bab 100644 --- a/mocktracer/BUILD +++ b/mocktracer/BUILD @@ -6,6 +6,14 @@ cc_library( visibility = ["//visibility:public"], deps = [ "//:opentracing", - "//mocktracer/3rd_party/base64:base64", + ], +) + +cc_binary( + name = "libmocktracer_plugin.so", + linkshared = 1, + visibility = ["//visibility:public"], + deps = [ + "//mocktracer:mocktracer" ], ) diff --git a/mocktracer/CMakeLists.txt b/mocktracer/CMakeLists.txt index 58a5c94..2f3d0a6 100644 --- a/mocktracer/CMakeLists.txt +++ b/mocktracer/CMakeLists.txt @@ -1,17 +1,17 @@ include_directories(include) -include_directories(3rd_party/base64/include) -set(SRCS 3rd_party/base64/src/base64.cpp - src/mock_span_context.cpp +set(SRCS src/mock_span_context.cpp src/mock_span.cpp src/in_memory_recorder.cpp src/json_recorder.cpp + src/base64.cpp src/propagation.cpp src/json.cpp - src/tracer.cpp) + src/tracer.cpp + src/tracer_factory.cpp) if (BUILD_SHARED_LIBS) - add_library(opentracing_mocktracer SHARED ${SRCS}) + add_library(opentracing_mocktracer SHARED ${SRCS} src/dynamic_load.cpp) target_include_directories(opentracing_mocktracer INTERFACE "$") set_target_properties(opentracing_mocktracer PROPERTIES VERSION ${OPENTRACING_VERSION_STRING} SOVERSION ${OPENTRACING_VERSION_MAJOR}) diff --git a/mocktracer/3rd_party/base64/LICENSE b/mocktracer/LICENSE.apache similarity index 100% rename from mocktracer/3rd_party/base64/LICENSE rename to mocktracer/LICENSE.apache diff --git a/mocktracer/include/opentracing/mocktracer/tracer_factory.h b/mocktracer/include/opentracing/mocktracer/tracer_factory.h new file mode 100644 index 0000000..49227e0 --- /dev/null +++ b/mocktracer/include/opentracing/mocktracer/tracer_factory.h @@ -0,0 +1,21 @@ +#ifndef OPENTRACING_MOCKTRACER_TRACER_FACTORY_H +#define OPENTRACING_MOCKTRACER_TRACER_FACTORY_H + +#include + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { + +class MockTracerFactory : public TracerFactory { + public: + expected> MakeTracer(const char* configuration, + std::string& error_message) const + noexcept override; +}; + +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing + +#endif // OPENTRACING_MOCKTRACER_TRACER_FACTORY_H diff --git a/mocktracer/3rd_party/base64/src/base64.cpp b/mocktracer/src/base64.cpp similarity index 97% rename from mocktracer/3rd_party/base64/src/base64.cpp rename to mocktracer/src/base64.cpp index 10fc4d4..d039dcf 100644 --- a/mocktracer/3rd_party/base64/src/base64.cpp +++ b/mocktracer/src/base64.cpp @@ -1,4 +1,11 @@ -#include +/* + * Envoy + * Copyright 2016-2017 Lyft Inc. + * + * Licensed under Apache License 2.0. See LICENSE.apache for terms. + */ + +#include "base64.h" #include #include diff --git a/mocktracer/3rd_party/base64/include/opentracing/mocktracer/base64.h b/mocktracer/src/base64.h similarity index 90% rename from mocktracer/3rd_party/base64/include/opentracing/mocktracer/base64.h rename to mocktracer/src/base64.h index 67845c6..438bbda 100644 --- a/mocktracer/3rd_party/base64/include/opentracing/mocktracer/base64.h +++ b/mocktracer/src/base64.h @@ -1,5 +1,5 @@ -#ifndef OPENTRACING_MOCKTRACER_BASE64_h -#define OPENTRACING_MOCKTRACER_BASE64_h +#ifndef OPENTRACING_MOCKTRACER_BASE64_H +#define OPENTRACING_MOCKTRACER_BASE64_H #include #include @@ -45,4 +45,4 @@ class Base64 { END_OPENTRACING_ABI_NAMESPACE } // namespace opentracing -#endif // OPENTRACING_MOCKTRACER_BASE64_h +#endif // OPENTRACING_MOCKTRACER_BASE64_H diff --git a/mocktracer/src/dynamic_load.cpp b/mocktracer/src/dynamic_load.cpp new file mode 100644 index 0000000..3aad32a --- /dev/null +++ b/mocktracer/src/dynamic_load.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +int OpenTracingMakeTracerFactory(const char* opentracing_version, + const void** error_category, + void** tracer_factory) { + if (error_category == nullptr || tracer_factory == nullptr) { + fprintf(stderr, + "`error_category` and `tracer_factory` must be non-null.\n"); + std::terminate(); + } + + if (std::strcmp(opentracing_version, OPENTRACING_VERSION) != 0) { + *error_category = + static_cast(&opentracing::dynamic_load_error_category()); + return opentracing::incompatible_library_versions_error.value(); + } + + *tracer_factory = + new (std::nothrow) opentracing::mocktracer::MockTracerFactory{}; + if (*tracer_factory == nullptr) { + *error_category = static_cast(&std::generic_category()); + return static_cast(std::errc::not_enough_memory); + } + + return 0; +} diff --git a/mocktracer/src/propagation.cpp b/mocktracer/src/propagation.cpp index 598a5fe..a037f90 100644 --- a/mocktracer/src/propagation.cpp +++ b/mocktracer/src/propagation.cpp @@ -1,10 +1,10 @@ #include "propagation.h" -#include #include #include #include #include #include +#include "base64.h" namespace opentracing { BEGIN_OPENTRACING_ABI_NAMESPACE diff --git a/mocktracer/src/tracer_factory.cpp b/mocktracer/src/tracer_factory.cpp new file mode 100644 index 0000000..1c672ce --- /dev/null +++ b/mocktracer/src/tracer_factory.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace mocktracer { + +namespace { +struct InvalidConfigurationError : public std::exception { + public: + InvalidConfigurationError(const char* position, std::string&& message) + : position_{position}, message_{std::move(message)} {} + + const char* what() const noexcept override { return message_.c_str(); } + + const char* position() const { return position_; } + + private: + const char* position_; + std::string message_; +}; +} // namespace + +static void Consume(const char*& i, const char* last, string_view s) { + if (static_cast(std::distance(i, last)) < s.size()) { + throw InvalidConfigurationError{i, + std::string{"expected "} + std::string{s}}; + } + + for (size_t index = 0; index < s.size(); ++index) { + if (*i++ != s[index]) { + throw InvalidConfigurationError{ + i, std::string{"expected "} + + std::string{s.data() + index, s.data() + s.size()}}; + } + } +} + +static void ConsumeWhitespace(const char*& i, const char* last) { + for (; i != last; ++i) { + if (!std::isspace(*i)) { + return; + } + } +} + +static void ConsumeToken(const char*& i, const char* last, string_view token) { + ConsumeWhitespace(i, last); + Consume(i, last, token); +} + +static std::string ParseFilename(const char*& i, const char* last) { + ConsumeToken(i, last, "\""); + std::string result; + while (i != last) { + if (*i == '\"') { + ++i; + return result; + } + if (*i == '\\') { + throw InvalidConfigurationError{ + i, "escaped characters are not supported in filename"}; + } + if (std::isprint(*i)) { + result.push_back(*i); + } else { + throw InvalidConfigurationError{i, "invalid character"}; + } + ++i; + } + + throw InvalidConfigurationError{i, R"(no matching ")"}; +} + +static std::string ParseConfiguration(const char* i, const char* last) { + ConsumeToken(i, last, "{"); + ConsumeToken(i, last, R"("output_file")"); + ConsumeToken(i, last, ":"); + auto filename = ParseFilename(i, last); + ConsumeToken(i, last, "}"); + ConsumeWhitespace(i, last); + if (i != last) { + throw InvalidConfigurationError{i, "expected EOF"}; + } + + return filename; +} + +struct MockTracerConfiguration { + std::string output_file; +}; + +expected> MockTracerFactory::MakeTracer( + const char* configuration, std::string& error_message) const noexcept try { + MockTracerConfiguration tracer_configuration; + try { + tracer_configuration.output_file = ParseConfiguration( + configuration, configuration + std::strlen(configuration)); + } catch (const InvalidConfigurationError& e) { + error_message = std::string{"Error parsing configuration at position "} + + std::to_string(std::distance(configuration, e.position())) + + ": " + e.what(); + return make_unexpected(invalid_configuration_error); + } + + std::unique_ptr ostream{ + new std::ofstream{tracer_configuration.output_file}}; + if (!ostream->good()) { + error_message = "failed to open file `"; + error_message += tracer_configuration.output_file + "`"; + return make_unexpected(invalid_configuration_error); + } + + MockTracerOptions tracer_options; + tracer_options.recorder = + std::unique_ptr{new JsonRecorder{std::move(ostream)}}; + + return std::shared_ptr{new MockTracer{std::move(tracer_options)}}; +} catch (const std::bad_alloc&) { + return make_unexpected(std::make_error_code(std::errc::not_enough_memory)); +} + +} // namespace mocktracer +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/src/dynamic_load.cpp b/src/dynamic_load.cpp new file mode 100644 index 0000000..cee0dc3 --- /dev/null +++ b/src/dynamic_load.cpp @@ -0,0 +1,57 @@ +#include +#include +#include + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace { +class DynamicLoadErrorCategory : public std::error_category { + public: + DynamicLoadErrorCategory() {} + + const char* name() const noexcept override { + return "OpenTracingDynamicLoadError"; + } + + std::error_condition default_error_condition(int code) const + noexcept override { + if (code == dynamic_load_failure_error.value()) { + return std::make_error_condition(std::errc::no_such_file_or_directory); + } + if (code == dynamic_load_not_supported_error.value()) { + return std::make_error_condition(std::errc::not_supported); + } + if (code == incompatible_library_versions_error.value()) { + return std::make_error_condition(std::errc::invalid_argument); + } + return std::error_condition(code, *this); + } + + std::string message(int code) const override { + if (code == dynamic_load_failure_error.value()) { + return "opentracing: failed to load dynamic library"; + } + if (code == dynamic_load_not_supported_error.value()) { + return "opentracing: dynamic library loading is not supported"; + } + if (code == incompatible_library_versions_error.value()) { + return "opentracing: versions of opentracing libraries are incompatible"; + } + return "opentracing: unknown dynamic load error"; + } +}; +} // anonymous namespace + +const std::error_category& dynamic_load_error_category() { + static const DynamicLoadErrorCategory error_category; + return error_category; +} + +DynamicTracingLibraryHandle::DynamicTracingLibraryHandle( + std::unique_ptr&& tracer_factory, + std::unique_ptr&& dynamic_library_handle) noexcept + : dynamic_library_handle_{std::move(dynamic_library_handle)}, + tracer_factory_{std::move(tracer_factory)} {} + +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/src/dynamic_load_unix.cpp b/src/dynamic_load_unix.cpp new file mode 100644 index 0000000..2bcfa07 --- /dev/null +++ b/src/dynamic_load_unix.cpp @@ -0,0 +1,85 @@ +#include +#include +#include + +// Copied from https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html +#define GCC_VERSION \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace { +class DynamicLibraryHandleUnix : public DynamicLibraryHandle { + public: + explicit DynamicLibraryHandleUnix(void* handle) : handle_{handle} {} + + ~DynamicLibraryHandleUnix() override { dlclose(handle_); } + + private: + void* handle_; +}; +} // namespace + +// Undefined behavior sanitizer has a bug where it will produce a false positive +// when casting the result of dlsym to a function pointer. +// +// See https://github.com/envoyproxy/envoy/pull/2252#issuecomment-362668221 +// https://github.com/google/sanitizers/issues/911 +// +// Note: undefined behavior sanitizer is supported in clang and gcc > 4.9 +#if defined(__clang__) +__attribute__((no_sanitize("function"))) +#elif (GCC_VERSION >= 40900) +__attribute__((no_sanitize_undefined)) +#endif +expected +DynamicallyLoadTracingLibrary(const char* shared_library, + std::string& error_message) noexcept try { + dlerror(); // Clear any existing error. + + const auto handle = dlopen(shared_library, RTLD_NOW | RTLD_LOCAL); + if (handle == nullptr) { + error_message = dlerror(); + return make_unexpected(dynamic_load_failure_error); + } + + std::unique_ptr dynamic_library_handle{ + new DynamicLibraryHandleUnix{handle}}; + + const auto make_tracer_factory = + reinterpret_cast( + dlsym(handle, "OpenTracingMakeTracerFactory")); + if (make_tracer_factory == nullptr) { + error_message = dlerror(); + return make_unexpected(dynamic_load_failure_error); + } + + const void* error_category = nullptr; + void* tracer_factory = nullptr; + const auto rcode = make_tracer_factory(OPENTRACING_VERSION, &error_category, + &tracer_factory); + if (rcode != 0) { + if (error_category != nullptr) { + return make_unexpected(std::error_code{ + rcode, *static_cast(error_category)}); + } else { + error_message = "failed to construct a TracerFactory: unknown error code"; + return make_unexpected(dynamic_load_failure_error); + } + } + + if (tracer_factory == nullptr) { + error_message = + "failed to construct a TracerFactory: `tracer_factory` is null"; + return make_unexpected(dynamic_load_failure_error); + } + + return DynamicTracingLibraryHandle{ + std::unique_ptr{ + static_cast(tracer_factory)}, + std::move(dynamic_library_handle)}; +} catch (const std::bad_alloc&) { + return make_unexpected(std::make_error_code(std::errc::not_enough_memory)); +} +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/src/dynamic_load_unsupported.cpp b/src/dynamic_load_unsupported.cpp new file mode 100644 index 0000000..fd0d767 --- /dev/null +++ b/src/dynamic_load_unsupported.cpp @@ -0,0 +1,10 @@ +#include + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +expected DynamicallyLoadTracingLibrary( + const char* /*shared_library*/, std::string& /*error_message*/) noexcept { + return make_unexpected(dynamic_load_not_supported_error); +} +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/src/tracer_factory.cpp b/src/tracer_factory.cpp new file mode 100644 index 0000000..8958310 --- /dev/null +++ b/src/tracer_factory.cpp @@ -0,0 +1,44 @@ +#include +#include + +namespace opentracing { +BEGIN_OPENTRACING_ABI_NAMESPACE +namespace { +class TracerFactoryErrorCategory : public std::error_category { + public: + TracerFactoryErrorCategory() {} + + const char* name() const noexcept override { + return "OpenTracingTracerFactoryError"; + } + + std::error_condition default_error_condition(int code) const + noexcept override { + if (code == configuration_parse_error.value()) { + return std::make_error_condition(std::errc::invalid_argument); + } + if (code == invalid_configuration_error.value()) { + return std::make_error_condition(std::errc::invalid_argument); + } + return std::error_condition(code, *this); + } + + std::string message(int code) const override { + if (code == configuration_parse_error.value()) { + return "opentracing: failed to parse configuration"; + } + if (code == invalid_configuration_error.value()) { + return "opentracing: invalid configuration"; + } + return "opentracing: unknown tracer factory error"; + } +}; +} // anonymous namespace + +const std::error_category& tracer_factory_error_category() { + static const TracerFactoryErrorCategory error_category; + return error_category; +} + +END_OPENTRACING_ABI_NAMESPACE +} // namespace opentracing diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2afbff6..ac39669 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,3 +16,12 @@ add_test(value_test value_test) add_executable(util_test util_test.cpp) add_test(util_test util_test) + +if (BUILD_SHARED_LIBS AND BUILD_MOCKTRACER AND BUILD_DYNAMIC_LOADING) + add_executable(dynamic_load_test dynamic_load_test.cpp) + target_link_libraries(dynamic_load_test ${OPENTRACING_LIBRARY}) + add_dependencies(dynamic_load_test opentracing_mocktracer) + add_test(dynamic_load_test dynamic_load_test + --mocktracer_library + ${CMAKE_BINARY_DIR}/mocktracer/${CMAKE_SHARED_LIBRARY_PREFIX}opentracing_mocktracer${CMAKE_SHARED_LIBRARY_SUFFIX}) +endif() diff --git a/test/dynamic_load_test.cpp b/test/dynamic_load_test.cpp new file mode 100644 index 0000000..813f1e4 --- /dev/null +++ b/test/dynamic_load_test.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +using namespace opentracing; + +#define CATCH_CONFIG_RUNNER +#include + +static std::string mocktracer_library; + +TEST_CASE("dynamic_load") { + std::string error_message; + + SECTION( + "Dynamically loading a library that doesn't exists gives a proper error " + "code.") { + auto handle_maybe = DynamicallyLoadTracingLibrary("abc/123", error_message); + REQUIRE(!handle_maybe); + CHECK(handle_maybe.error() == dynamic_load_failure_error); + } + + error_message.clear(); + auto handle_maybe = + DynamicallyLoadTracingLibrary(mocktracer_library.c_str(), error_message); + REQUIRE(handle_maybe); + REQUIRE(error_message.empty()); + + SECTION("Creating a tracer from invalid json gives an error.") { + auto tracer_maybe = + handle_maybe->tracer_factory().MakeTracer("abc 123", error_message); + REQUIRE(!tracer_maybe); + } + + SECTION("Creating a tracer from an invalid configuration gives an error.") { + auto tracer_maybe = handle_maybe->tracer_factory().MakeTracer( + R"({"abc": 123})", error_message); + REQUIRE(!tracer_maybe); + REQUIRE(tracer_maybe.error() == invalid_configuration_error); + } + + SECTION("Creating a tracer with an invalid output_file gives an error.") { + auto tracer_maybe = handle_maybe->tracer_factory().MakeTracer( + R"({"output_file": ""})", error_message); + REQUIRE(!tracer_maybe); + REQUIRE(tracer_maybe.error() == invalid_configuration_error); + } + + SECTION( + "We can create spans from an OpenTracing library dynamically loaded.") { + std::string span_filename{"spans."}; + const auto random_id = std::random_device{}(); + span_filename.append(std::to_string(random_id)); + std::string configuration = R"({ "output_file": ")"; + configuration.append(span_filename); + configuration.append(R"(" })"); + + { + auto tracer_maybe = handle_maybe->tracer_factory().MakeTracer( + configuration.c_str(), error_message); + REQUIRE(tracer_maybe); + auto tracer = *tracer_maybe; + tracer->StartSpan("abc"); + tracer->Close(); + } + + std::ifstream istream{span_filename}; + REQUIRE(istream.good()); + std::string spans_json{std::istreambuf_iterator{istream}, + std::istreambuf_iterator{}}; + istream.close(); + CHECK(std::remove(span_filename.c_str()) == 0); + CHECK(!spans_json.empty()); + } +} + +int main(int argc, char* argv[]) { + Catch::Session session; + + using namespace Catch::clara; + auto cli = session.cli() | Opt(mocktracer_library, + "mocktracer_library")["--mocktracer_library"]; + + session.cli(cli); + int rcode = session.applyCommandLine(argc, argv); + if (rcode != 0) { + return rcode; + } + + if (mocktracer_library.empty()) { + std::cerr << "Must provide mocktracer_library!\n"; + return -1; + } + + return session.run(); +} diff --git a/test/string_view_test.cpp b/test/string_view_test.cpp index f700ce6..33a8bc4 100644 --- a/test/string_view_test.cpp +++ b/test/string_view_test.cpp @@ -40,4 +40,10 @@ TEST_CASE("string_view") { CHECK(val == cpy.data()); CHECK(val.length() == cpy.length()); } + + SECTION("operator[] can be used to access characters in a string_view") { + string_view s = "abc123"; + CHECK(&s[0] == s.data()); + CHECK(&s[1] == s.data() + 1); + } }