From 228b3461b8573b311d76cc738230564a8e087e6f Mon Sep 17 00:00:00 2001 From: Karen Xu Date: Mon, 30 Nov 2020 07:17:46 -0500 Subject: [PATCH 1/6] Add Ostream log exporter and tests --- api/include/opentelemetry/logs/log_record.h | 38 ++- api/include/opentelemetry/logs/logger.h | 95 ++++--- exporters/ostream/BUILD | 23 ++ exporters/ostream/CMakeLists.txt | 13 + .../exporters/ostream/log_exporter.h | 151 ++++++++++ exporters/ostream/src/log_exporter.cc | 92 ++++++ exporters/ostream/test/ostream_log_test.cc | 263 ++++++++++++++++++ sdk/include/opentelemetry/sdk/logs/exporter.h | 2 +- .../opentelemetry/sdk/logs/processor.h | 2 +- .../sdk/logs/simple_log_processor.h | 7 +- sdk/src/logs/logger.cc | 9 +- sdk/src/logs/simple_log_processor.cc | 11 +- sdk/test/logs/CMakeLists.txt | 2 + sdk/test/logs/logger_provider_sdk_test.cc | 6 +- sdk/test/logs/logger_sdk_test.cc | 8 +- sdk/test/logs/simple_log_processor_test.cc | 4 +- 16 files changed, 645 insertions(+), 81 deletions(-) create mode 100644 exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h create mode 100644 exporters/ostream/src/log_exporter.cc create mode 100644 exporters/ostream/test/ostream_log_test.cc diff --git a/api/include/opentelemetry/logs/log_record.h b/api/include/opentelemetry/logs/log_record.h index c663c1c9c7..ad2cd1ca9a 100644 --- a/api/include/opentelemetry/logs/log_record.h +++ b/api/include/opentelemetry/logs/log_record.h @@ -65,11 +65,6 @@ enum class Severity : uint8_t kDefault = kInfo // default severity is set to kInfo level, similar to what is done in ILogger }; -/* _nullKV is defined as a private variable that allows "resource" and - "attributes" fields to be instantiated using it as the default value */ -static common::KeyValueIterableView> _nullKV = - common::KeyValueIterableView>{{}}; - /** * A default Event object to be passed in log statements, * matching the 10 fields of the Log Data Model. @@ -79,29 +74,30 @@ static common::KeyValueIterableView resource; // key/value pair list + nostd::shared_ptr attributes; // key/value pair list /* Default log record if user does not overwrite this. * TODO: find better data type to represent the type for "body" * Future enhancement: Potentially add other constructors to take default arguments * from the user **/ - LogRecord() : resource(_nullKV), attributes(_nullKV) - { - // TODO: in SDK, assign a default timestamp if not specified - name = ""; - } + LogRecord() + : timestamp{core::SystemTimestamp(std::chrono::seconds(0))}, + severity{Severity::kDefault}, + trace_id{trace::TraceId()}, + span_id{trace::SpanId()}, + trace_flags{trace::TraceFlags()} + {} /* for ease of use; user can use this function to convert a map into a KeyValueIterable for the * resources field */ @@ -109,7 +105,8 @@ struct LogRecord nostd::enable_if_t::value> * = nullptr> inline void SetResource(const T &_resource) { - resource = common::KeyValueIterableView(_resource); + resource = + nostd::shared_ptr(new common::KeyValueIterableView{_resource}); } /* for ease of use; user can use this function to convert a map into a KeyValueIterable for the @@ -118,7 +115,8 @@ struct LogRecord nostd::enable_if_t::value> * = nullptr> inline void SetAttributes(const T &_attributes) { - attributes = common::KeyValueIterableView(_attributes); + attributes = nostd::shared_ptr( + new common::KeyValueIterableView{_attributes}); } }; } // namespace logs diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index 5f22066460..b52b344c50 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -38,10 +38,6 @@ class Logger public: virtual ~Logger() = default; - /* Returns the name of the logger */ - // TODO: decide whether this is useful and/or should be kept, as this is not a method required in - // the specification. virtual nostd::string_view getName() = 0; - /** * Each of the following overloaded log(...) methods * creates a log message with the specific parameters passed. @@ -55,69 +51,92 @@ class Logger * @throws No exceptions under any circumstances. */ - /* The below method is a logging statement that takes in a LogRecord. - * A default LogRecord that will be assigned if no parameters are passed to Logger's .log() method - * which should at minimum assign the trace_id, span_id, and timestamp + /** + * Logs a LogRecord, which contains all the fields of the Log Data Model. Normally called + * indirectly from other log() Methods, but can be called directly for high detail. + * @param record A log record filled with information from the user. */ virtual void log(const LogRecord &record) noexcept = 0; - /** Overloaded methods for unstructured logging **/ - inline void log(nostd::string_view message) noexcept - { - // Set severity to the default then call log(Severity, String message) method - log(Severity::kDefault, message); - } + /// Overloaded Log methods, which are simpler than calling a LogRecord directly + /** + * Writes a log. + * @param severity The log's severity + * @param message The message to log + */ inline void log(Severity severity, nostd::string_view message) noexcept { - // TODO: set default timestamp later (not in API) - log(severity, message, core::SystemTimestamp(std::chrono::system_clock::now())); + // Create a LogRecord to hold this information + LogRecord r; + r.severity = severity; + r.body = message; + + // Call the main log(LogRecord) method + log(r); } - inline void log(Severity severity, - nostd::string_view message, - core::SystemTimestamp time) noexcept + /** + * Writes a log. + * @param severity The log's severity + * @param name The name of the log + * @param message The message to log + */ + inline void log(Severity severity, nostd::string_view name, nostd::string_view message) noexcept { - // creates a LogRecord object with given parameters, then calls log(LogRecord) + // Create a LogRecord to hold this information LogRecord r; - r.severity = severity; - r.body = message; - r.timestamp = time; + r.severity = severity; + r.name = name; + r.body = message; + // Call the main log(LogRecord) method log(r); } - /** Overloaded methods for structured logging**/ - // TODO: separate this method into separate methods since it is not useful for user to create - // empty logs + /** + * Writes a log. + * @param severity The severity of the log, from 1 to 24 + * @param attributes A key/value object + */ template ::value> * = nullptr> - inline void log(Severity severity = Severity::kDefault, - nostd::string_view name = "", - const T &attributes = {}) noexcept + inline void log(Severity severity, const T &attributes) noexcept { - log(severity, name, common::KeyValueIterableView(attributes)); + // Create a LogRecord to hold this information + LogRecord r; + r.severity = severity; + r.attributes = nostd::shared_ptr( + new common::KeyValueIterableView{attributes}); + + // Call the main log(LogRecord) method + log(r); } - inline void log(Severity severity, - nostd::string_view name, - const common::KeyValueIterable &attributes) noexcept + /** + * Writes a log. + * @param severity The severity of the log, from 1 to 24 + * @param name The name of the log + * @param attributes A key/value object + */ + template ::value> * = nullptr> + inline void log(Severity severity, nostd::string_view name, const T &attributes) noexcept { - // creates a LogRecord object with given parameters, then calls log(LogRecord) + // Create a LogRecord to hold this information LogRecord r; r.severity = severity; r.name = name; - r.attributes = attributes; + r.attributes = nostd::shared_ptr( + new common::KeyValueIterableView{attributes}); + // Call the main log(LogRecord) method log(r); } - // TODO: add function aliases such as void debug(), void trace(), void info(), etc. for each - // severity level - /** Future enhancement: templated method for objects / custom types (e.g. JSON, XML, custom * classes, etc) **/ // template virtual void log(T &some_obj) noexcept; }; } // namespace logs -OPENTELEMETRY_END_NAMESPACE \ No newline at end of file +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/ostream/BUILD b/exporters/ostream/BUILD index e2d7c206ea..d1b5d5bd98 100644 --- a/exporters/ostream/BUILD +++ b/exporters/ostream/BUILD @@ -1,5 +1,28 @@ package(default_visibility = ["//visibility:public"]) +cc_library( + name = "ostream_log_exporter", + srcs = [ + "src/log_exporter.cc", + ], + hdrs = [ + "include/opentelemetry/exporters/ostream/log_exporter.h", + ], + strip_include_prefix = "include", + deps = [ + "//sdk/src/logs", + ], +) + +cc_test( + name = "ostream_log_test", + srcs = ["test/ostream_log_test.cc"], + deps = [ + ":ostream_log_exporter", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "ostream_metrics_exporter", srcs = [ diff --git a/exporters/ostream/CMakeLists.txt b/exporters/ostream/CMakeLists.txt index b6faccf513..e870bb09cf 100644 --- a/exporters/ostream/CMakeLists.txt +++ b/exporters/ostream/CMakeLists.txt @@ -1,11 +1,13 @@ include_directories(include) +add_library(opentelemetry_exporter_ostream_logs src/log_exporter.cc) add_library(opentelemetry_exporter_ostream_metrics src/metrics_exporter.cc) add_library(opentelemetry_exporter_ostream_span src/span_exporter.cc) if(BUILD_TESTING) add_executable(ostream_metrics_test test/ostream_metrics_test.cc) add_executable(ostream_span_test test/ostream_span_test.cc) + add_executable(ostream_log_test test/ostream_log_test.cc) target_link_libraries( ostream_span_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} @@ -15,12 +17,23 @@ if(BUILD_TESTING) ostream_metrics_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_exporter_ostream_metrics) + target_link_libraries( + ostream_log_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_ostream_logs opentelemetry_logs) + + gtest_add_tests( + TARGET ostream_log_test + TEST_PREFIX exporter. + TEST_LIST ostream_log_test) + gtest_add_tests( TARGET ostream_metrics_test TEST_PREFIX exporter. TEST_LIST ostream_metrics_test) + gtest_add_tests( TARGET ostream_span_test TEST_PREFIX exporter. TEST_LIST ostream_span_test) + endif() # BUILD_TESTING diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h b/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h new file mode 100644 index 0000000000..8d940caaad --- /dev/null +++ b/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h @@ -0,0 +1,151 @@ +#pragma once + +#include "opentelemetry/logs/log_record.h" +#include "opentelemetry/nostd/type_traits.h" +#include "opentelemetry/sdk/logs/exporter.h" +#include "opentelemetry/version.h" + +#include +#include +#include + +namespace nostd = opentelemetry::nostd; +namespace sdklogs = opentelemetry::sdk::logs; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace logs +{ +/** + * The OStreamLogExporter exports logs through an ostream (default set to std::cout) + */ +class OStreamLogExporter final : public sdklogs::LogExporter +{ +public: + /** + * Create an OStreamLogExporter. This constructor takes in a reference to an ostream that the + * Export() method will send log data into. The default ostream is set to stdout. + */ + explicit OStreamLogExporter(std::ostream &sout = std::cout) noexcept; + + /*********************** Overloads of LogExporter interface *********************/ + + /** + * Exports a span of logs sent from the processor. + */ + sdklogs::ExportResult Export(const nostd::span> + &records) noexcept override; + + /** + * Marks the OStream Log Exporter as shut down. + */ + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; + +private: + // The OStream to send the logs to + std::ostream &sout_; + // Whether this exporter is ShutDown + bool isShutdown_ = false; + + /** + * Internal map of the severity number (from 0 to 24) to severity text, matching the + * values set by the default Severity enum in api/include/opentelemetry/logs/log_record.h + * + * If more than one exporter requires this, could move this to Severity enum. + */ + const nostd::string_view severityNumToText[25] = { + "kInvalid", "kTrace", "kTrace2", "kTrace3", "kTrace4", "kDebug", "kDebug2", + "kDebug3", "kDebug4", "kInfo", "kInfo2", "kInfo3", "kInfo4", "kWarn", + "kWarn2", "kWarn3", "kWarn4", "kError", "kError2", "kError3", "kError4", + "kFatal", "kFatal2", "kFatal3", "kFatal4"}; + + /** + * Helper function to print a KeyValueIterable. Outputs a AttributeValue type. + * + * Based off api/include/opentelemetry/common/attribute_value.h + * In the future, may be better to add operator overloads to attribute_value.h + * to make more maintainable. + * + */ + void print_value(const common::AttributeValue &value) + { + switch (value.index()) + { + case common::AttributeType::TYPE_BOOL: + sout_ << (nostd::get(value) ? "true" : "false"); + break; + case common::AttributeType::TYPE_INT: + sout_ << nostd::get(value); + break; + case common::AttributeType::TYPE_INT64: + sout_ << nostd::get(value); + break; + case common::AttributeType::TYPE_UINT: + sout_ << nostd::get(value); + break; + case common::AttributeType::TYPE_UINT64: + sout_ << nostd::get(value); + break; + case common::AttributeType::TYPE_DOUBLE: + sout_ << nostd::get(value); + break; + case common::AttributeType::TYPE_STRING: + case common::AttributeType::TYPE_CSTRING: + sout_ << nostd::get(value); + break; + + /*** Need to support these? ***/ + // case common::AttributeType::TYPE_SPAN_BOOL: + // sout_ << nostd::get>(value); + // break; + // case common::AttributeType::TYPE_SPAN_INT: + // sout_ << nostd::get>(value); + // break; + // case common::AttributeType::TYPE_SPAN_INT64: + // sout_ << nostd::get>(value); + // break; + // case common::AttributeType::TYPE_SPAN_UINT: + // sout_ << nostd::get>(value); + // break; + // case common::AttributeType::TYPE_SPAN_UINT64: + // sout_ << nostd::get>(value); + // break; + // case common::AttributeType::TYPE_SPAN_DOUBLE: + // sout_ << nostd::get>(value); + // break; + // case common::AttributeType::TYPE_SPAN_STRING: + // sout_ << nostd::get>(value); + // break; + /******** Up to here ************/ + + default: + sout_ << "Invalid type"; + break; + } + } + + /** + * Helper function to print a KeyValueIterable. + * Outputs a pair of type + */ + void printKV(bool &firstKV, const nostd::string_view &key, const common::AttributeValue &value) + { + if (firstKV) + { + firstKV = false; + } + else + { + sout_ << ", "; + } + + sout_ << "{" << key << ": "; + print_value(value); + sout_ << "}"; + } +}; +} // namespace logs +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/ostream/src/log_exporter.cc b/exporters/ostream/src/log_exporter.cc new file mode 100644 index 0000000000..f0de1dcb50 --- /dev/null +++ b/exporters/ostream/src/log_exporter.cc @@ -0,0 +1,92 @@ +#include "opentelemetry/exporters/ostream/log_exporter.h" + +#include + +namespace nostd = opentelemetry::nostd; +namespace sdklogs = opentelemetry::sdk::logs; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace logs +{ +OStreamLogExporter::OStreamLogExporter(std::ostream &sout) noexcept : sout_(sout) {} + +sdklogs::ExportResult OStreamLogExporter::Export( + const nostd::span> &records) noexcept +{ + if (isShutdown_) + { + return sdklogs::ExportResult::kFailure; + } + + for (auto &record : records) + { + // Convert trace, spanid, traceflags into string convertable representation + constexpr int trace_id_bytes = 16; + char trace_id[trace_id_bytes * 2] = {0}; + record->trace_id.ToLowerBase16(trace_id); + + constexpr int span_id_bytes = 8; + char span_id[span_id_bytes * 2] = {0}; + record->span_id.ToLowerBase16(span_id); + + constexpr int trace_flags_bytes = 1; + char trace_flags[trace_flags_bytes * 2] = {0}; + record->trace_flags.ToLowerBase16(trace_flags); + + /*** Print out each field of the log record ***/ + + // Print fields most useful to user first + sout_ << "{\n" + << " timestamp : " << record->timestamp.time_since_epoch().count() << "\n" + << " severity : " << severityNumToText[static_cast(record->severity)] << "\n" + << " name : " << record->name << "\n" + << " body : " << record->body << "\n"; + + // Print "resource" field + sout_ << " resource : {"; + bool firstKV = true; + if (record->resource != nullptr) + { + record->resource->ForEachKeyValue([&](nostd::string_view key, + common::AttributeValue value) noexcept { + printKV(firstKV, key, value); + return true; + }); + } + sout_ << "}\n"; + + // Print "attributes" field + sout_ << " attributes : {"; + firstKV = true; + if (record->attributes != nullptr) + { + firstKV = true; + record->attributes->ForEachKeyValue([&](nostd::string_view key, + common::AttributeValue value) noexcept { + printKV(firstKV, key, value); + return true; + }); + } + sout_ << "}\n"; + + // Print span context fields + sout_ << " trace_id : " << std::string(trace_id, 32) << "\n" + << " span_id : " << std::string(span_id, 16) << "\n" + << " trace_flags : " << std::string(trace_flags, 2) << "\n" + << "}\n"; + } + + return sdklogs::ExportResult::kSuccess; +} + +bool OStreamLogExporter::Shutdown(std::chrono::microseconds timeout) noexcept +{ + isShutdown_ = true; + return true; +} + +} // namespace logs +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/ostream/test/ostream_log_test.cc b/exporters/ostream/test/ostream_log_test.cc new file mode 100644 index 0000000000..01a0cad545 --- /dev/null +++ b/exporters/ostream/test/ostream_log_test.cc @@ -0,0 +1,263 @@ +#include "opentelemetry/exporters/ostream/log_exporter.h" +#include "opentelemetry/logs/provider.h" +#include "opentelemetry/sdk/logs/logger_provider.h" +#include "opentelemetry/sdk/logs/simple_log_processor.h" + +#include +#include + +#include +#include + +namespace sdklogs = opentelemetry::sdk::logs; +namespace logs_api = opentelemetry::logs; +namespace nostd = opentelemetry::nostd; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace logs +{ + +// Test that processor/exporter is shutdown, no logs should be sent to stream +TEST(OStreamLogExporter, Shutdown) +{ + auto exporter = + std::unique_ptr(new opentelemetry::exporter::logs::OStreamLogExporter); + auto processor = + std::shared_ptr(new sdklogs::SimpleLogProcessor(std::move(exporter))); + + auto record = std::shared_ptr(new logs_api::LogRecord()); + record->name = "Test Log"; + + // Create stringstream to redirect to + std::stringstream stdoutOutput; + + // Save cout's buffer here + std::streambuf *sbuf = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::cout.rdbuf(stdoutOutput.rdbuf()); + + processor->Shutdown(); + + // After processor/exporter is shutdown, no logs should be sent to stream + processor->OnReceive(record); + + std::cout.rdbuf(sbuf); + + ASSERT_EQ(stdoutOutput.str(), ""); +} + +// ---------------------------------- Print to cout, cerr, and clog ------------------------- + +// Print Log to std::cout +TEST(OStreamLogExporter, PrintSimpleLogToCout) +{ + // Initialize an Ostream exporter to cout + auto exporter = std::unique_ptr( + new opentelemetry::exporter::logs::OStreamLogExporter(std::cout)); + auto processor = + std::shared_ptr(new sdklogs::SimpleLogProcessor(std::move(exporter))); + + // Save original stream buffer and redirect cout to our new stream buffer + std::streambuf *sbuf = std::cout.rdbuf(); + std::stringstream stdcoutOutput; + std::cout.rdbuf(stdcoutOutput.rdbuf()); + + // Create a log record and manually set all fields (since we are not using SDK to inject fields) + opentelemetry::core::SystemTimestamp now(std::chrono::system_clock::now()); + + auto record = std::shared_ptr(new logs_api::LogRecord()); + record->timestamp = now; + record->severity = logs_api::Severity::kInfo; + record->name = "Test Log"; + record->body = "Message"; + + // Log a record to cout + processor->OnReceive(record); + + // Reset cout's original stringstream buffer + std::cout.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " timestamp : " + + std::to_string(now.time_since_epoch().count()) + + "\n" + " severity : kInfo\n" + " name : Test Log\n" + " body : Message\n" + " resource : {}\n" + " attributes : {}\n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" + "}\n"; + ASSERT_EQ(stdcoutOutput.str(), expectedOutput); +} + +// Print log to std::cerr +TEST(OStreamLogExporter, PrintLogWithResourceToCerr) +{ + // Initialize an Ostream exporter to cerr + auto exporter = std::unique_ptr( + new opentelemetry::exporter::logs::OStreamLogExporter(std::cerr)); + auto processor = + std::shared_ptr(new sdklogs::SimpleLogProcessor(std::move(exporter))); + + // Save original stream buffer and redirect cerr to our new stream buffer + std::streambuf *sbuf = std::cerr.rdbuf(); + std::stringstream stdcerrOutput; + std::cerr.rdbuf(stdcerrOutput.rdbuf()); + + // Create a log record and manually set all fields (since we are not using SDK to inject fields) + opentelemetry::core::SystemTimestamp now(std::chrono::system_clock::now()); + + auto record = std::shared_ptr(new logs_api::LogRecord()); + record->timestamp = now; + record->severity = logs_api::Severity::kInfo; + record->name = "Test Log"; + record->body = "Message"; + + // Set attributes for this log record of type + const std::map m = {{"key1", "val1"}, {"key2", "val2"}}; + record->SetResource(m); + + // Log a record to cerr + processor->OnReceive(record); + + // Reset cerr's original stringstream buffer + std::cerr.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " timestamp : " + + std::to_string(now.time_since_epoch().count()) + + "\n" + " severity : kInfo\n" + " name : Test Log\n" + " body : Message\n" + " resource : {{key1: val1}, {key2: val2}}\n" + " attributes : {}\n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" + "}\n"; + ASSERT_EQ(stdcerrOutput.str(), expectedOutput); +} + +// Pirnt log to std::clog +TEST(OStreamLogExporter, PrintLogWithMixedAttributesToClog) +{ + // Initialize an ostream exporter to clog + auto exporter = std::unique_ptr( + new opentelemetry::exporter::logs::OStreamLogExporter(std::clog)); + auto processor = + std::shared_ptr(new sdklogs::SimpleLogProcessor(std::move(exporter))); + + // Save original stream buffer and redirect clog to our new stream buffer + std::streambuf *sbuf = std::clog.rdbuf(); + std::stringstream stdcerrOutput; + std::clog.rdbuf(stdcerrOutput.rdbuf()); + + // Create a log record and manually set all fields (since we are not using SDK to inject fields) + opentelemetry::core::SystemTimestamp now(std::chrono::system_clock::now()); + + auto record = std::shared_ptr(new logs_api::LogRecord()); + record->timestamp = now; + record->severity = logs_api::Severity::kInfo; // kInfo = 9 + record->name = "Test Log"; + record->body = "Message"; + + // Set resources to this log record of type + const std::map m = {{"key1", 1.1}, {"key2", 2.2}, {"key3", 3.3}}; + record->SetResource(m); + + // Set attributes to this log record of type + const std::map n = { + {"s1", 10}, {"s2", true}, {"s3", false}}; + record->SetAttributes(n); + + // Log a record to clog + processor->OnReceive(record); + + // Reset clog's original stringstream buffer + std::clog.rdbuf(sbuf); + + std::string expectedOutput = + "{\n" + " timestamp : " + + std::to_string(now.time_since_epoch().count()) + + "\n" + " severity : kInfo\n" + " name : Test Log\n" + " body : Message\n" + " resource : {{key1: 1.1}, {key2: 2.2}, {key3: 3.3}}\n" + " attributes : {{s1: 10}, {s2: true}, {s3: false}}\n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" + "}\n"; + ASSERT_EQ(stdcerrOutput.str(), expectedOutput); +} + +// ---------------------------------- Integration Tests ------------------------- + +// Print a log using the full logging pipeline +TEST(OStreamLogExporter, IntegrationTest) +{ + // Initialize a logger + auto exporter = + std::unique_ptr(new opentelemetry::exporter::logs::OStreamLogExporter); + auto processor = + std::shared_ptr(new sdklogs::SimpleLogProcessor(std::move(exporter))); + auto sdkProvider = std::shared_ptr(new sdklogs::LoggerProvider()); + sdkProvider->SetProcessor(processor); + auto apiProvider = nostd::shared_ptr(sdkProvider); + auto provider = nostd::shared_ptr(apiProvider); + logs_api::Provider::SetLoggerProvider(provider); + auto logger = logs_api::Provider::GetLoggerProvider()->GetLogger("Logger"); + + // Back up cout's streambuf + std::streambuf *sbuf = std::cout.rdbuf(); + + // Redirect cout to our string stream + std::stringstream stdcoutOutput; + std::cout.rdbuf(stdcoutOutput.rdbuf()); + + // Write a log to ostream exporter + logs_api::LogRecord record; + opentelemetry::core::SystemTimestamp now(std::chrono::system_clock::now()); + record.timestamp = now; + record.severity = logs_api::Severity::kInfo; + record.name = "Name"; + record.body = "Message"; + + logger->log(record); + + // Restore cout's original streambuf + std::cout.rdbuf(sbuf); + + // Compare actual vs expected outputs + std::string expectedOutput = + "{\n" + " timestamp : " + + std::to_string(now.time_since_epoch().count()) + + "\n" + " severity : kInfo\n" + " name : Name\n" + " body : Message\n" + " resource : {}\n" + " attributes : {}\n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" + "}\n"; + + ASSERT_EQ(stdcoutOutput.str(), expectedOutput); +} + +} // namespace logs +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/logs/exporter.h b/sdk/include/opentelemetry/sdk/logs/exporter.h index 6f44d32a28..e550ed40b3 100644 --- a/sdk/include/opentelemetry/sdk/logs/exporter.h +++ b/sdk/include/opentelemetry/sdk/logs/exporter.h @@ -55,7 +55,7 @@ class LogExporter * @returns an ExportResult code (whether export was success or failure) */ virtual ExportResult Export( - const nostd::span> &records) noexcept = 0; + const nostd::span> &records) noexcept = 0; /** * Marks the exporter as ShutDown and cleans up any resources as required. diff --git a/sdk/include/opentelemetry/sdk/logs/processor.h b/sdk/include/opentelemetry/sdk/logs/processor.h index 7468548dcf..bb3ad3c08e 100644 --- a/sdk/include/opentelemetry/sdk/logs/processor.h +++ b/sdk/include/opentelemetry/sdk/logs/processor.h @@ -38,7 +38,7 @@ class LogProcessor * OnReceive is called by the SDK once a log record has been successfully created. * @param record the log record */ - virtual void OnReceive(std::unique_ptr &&record) noexcept = 0; + virtual void OnReceive(std::shared_ptr record) noexcept = 0; /** * Exports all log records that have not yet been exported to the configured Exporter. diff --git a/sdk/include/opentelemetry/sdk/logs/simple_log_processor.h b/sdk/include/opentelemetry/sdk/logs/simple_log_processor.h index 18ed28b2cf..3c64d48c98 100644 --- a/sdk/include/opentelemetry/sdk/logs/simple_log_processor.h +++ b/sdk/include/opentelemetry/sdk/logs/simple_log_processor.h @@ -43,13 +43,12 @@ class SimpleLogProcessor : public LogProcessor explicit SimpleLogProcessor(std::unique_ptr &&exporter); virtual ~SimpleLogProcessor() = default; - void OnReceive(std::unique_ptr &&record) noexcept override; + void OnReceive(std::shared_ptr record) noexcept override; bool ForceFlush( - std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; + std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; - bool Shutdown( - std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; private: // The configured exporter diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index bc19f5d1c1..1da73c1542 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -42,17 +42,20 @@ void Logger::log(const opentelemetry::logs::LogRecord &record) noexcept * converting record a heap variable can be removed */ auto record_pointer = - std::unique_ptr(new opentelemetry::logs::LogRecord(record)); + std::shared_ptr(new opentelemetry::logs::LogRecord(record)); // TODO: Do not want to overwrite user-set timestamp if there already is one - // add a flag in the API to check if timestamp is set by user already before setting timestamp // Inject timestamp if none is set - record_pointer->timestamp = core::SystemTimestamp(std::chrono::system_clock::now()); + if (record_pointer->timestamp == opentelemetry::core::SystemTimestamp(std::chrono::seconds(0))) + { + record_pointer->timestamp = core::SystemTimestamp(std::chrono::system_clock::now()); + } // TODO: inject traceid/spanid later // Send the log record to the processor - processor->OnReceive(std::move(record_pointer)); + processor->OnReceive(record_pointer); } } // namespace logs diff --git a/sdk/src/logs/simple_log_processor.cc b/sdk/src/logs/simple_log_processor.cc index d88fc9eb92..d20e27e04c 100644 --- a/sdk/src/logs/simple_log_processor.cc +++ b/sdk/src/logs/simple_log_processor.cc @@ -36,16 +36,17 @@ SimpleLogProcessor::SimpleLogProcessor(std::unique_ptr &&exporter) * Batches the log record it receives in a batch of 1 and immediately sends it * to the configured exporter */ -void SimpleLogProcessor::OnReceive( - std::unique_ptr &&record) noexcept +void SimpleLogProcessor::OnReceive(std::shared_ptr record) noexcept { - nostd::span> batch(&record, 1); + std::vector> batch; + batch.emplace_back(record); // Get lock to ensure Export() is never called concurrently const std::lock_guard locked(lock_); - if (exporter_->Export(batch) != ExportResult::kSuccess) + if (exporter_->Export(opentelemetry::nostd::span>( + batch.data(), batch.size())) != ExportResult::kSuccess) { - /* Alert user of the failed export */ + /* TODO: alert user of the failed or timedout export result */ } } /** diff --git a/sdk/test/logs/CMakeLists.txt b/sdk/test/logs/CMakeLists.txt index 024c42c45a..e07f569b52 100644 --- a/sdk/test/logs/CMakeLists.txt +++ b/sdk/test/logs/CMakeLists.txt @@ -3,8 +3,10 @@ foreach(testname logger_provider_sdk_test logger_sdk_test add_executable(${testname} "${testname}.cc") target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_logs) + gtest_add_tests( TARGET ${testname} TEST_PREFIX logs. TEST_LIST ${testname}) + endforeach() diff --git a/sdk/test/logs/logger_provider_sdk_test.cc b/sdk/test/logs/logger_provider_sdk_test.cc index 02e7c47275..86f250cce2 100644 --- a/sdk/test/logs/logger_provider_sdk_test.cc +++ b/sdk/test/logs/logger_provider_sdk_test.cc @@ -69,12 +69,12 @@ TEST(LoggerProviderSDK, LoggerProviderLoggerArguments) class DummyProcessor : public LogProcessor { - void OnReceive(std::unique_ptr &&record) noexcept {} - bool ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept + void OnReceive(std::shared_ptr record) noexcept {} + bool ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept { return true; } - bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept { return true; } diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index d4825d0032..0c8418144b 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -37,12 +37,12 @@ TEST(LoggerSDK, LogToNullProcessor) class DummyProcessor : public LogProcessor { - void OnReceive(std::unique_ptr &&record) noexcept {} - bool ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept + void OnReceive(std::shared_ptr record) noexcept {} + bool ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept { return true; } - bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept { return true; } @@ -74,4 +74,4 @@ TEST(LoggerSDK, LogToAProcessor) opentelemetry::logs::LogRecord r; r.name = "Test log"; logger->log(r); -} +} \ No newline at end of file diff --git a/sdk/test/logs/simple_log_processor_test.cc b/sdk/test/logs/simple_log_processor_test.cc index 80c83183d3..00e40a53b7 100644 --- a/sdk/test/logs/simple_log_processor_test.cc +++ b/sdk/test/logs/simple_log_processor_test.cc @@ -26,7 +26,7 @@ class TestExporter final : public LogExporter // Stores the names of the log records this exporter receives to an internal list ExportResult Export( - const opentelemetry::nostd::span> &records) noexcept override + const opentelemetry::nostd::span> &records) noexcept override { *batch_size_received = records.size(); for (auto &record : records) @@ -116,7 +116,7 @@ class FailShutDownExporter final : public LogExporter FailShutDownExporter() {} ExportResult Export( - const opentelemetry::nostd::span> &records) noexcept override + const opentelemetry::nostd::span> &records) noexcept override { return ExportResult::kSuccess; } From adfe0d70959dbc18c7f0cccf6e5a1d6353941c37 Mon Sep 17 00:00:00 2001 From: Karen Xu Date: Wed, 9 Dec 2020 15:44:27 -0500 Subject: [PATCH 2/6] Remove namespace, use google style variable names --- .../exporters/ostream/log_exporter.h | 50 +++++++++---------- exporters/ostream/src/log_exporter.cc | 6 +-- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h b/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h index 8d940caaad..c9bca05776 100644 --- a/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h +++ b/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h @@ -9,9 +9,6 @@ #include #include -namespace nostd = opentelemetry::nostd; -namespace sdklogs = opentelemetry::sdk::logs; - OPENTELEMETRY_BEGIN_NAMESPACE namespace exporter { @@ -20,7 +17,7 @@ namespace logs /** * The OStreamLogExporter exports logs through an ostream (default set to std::cout) */ -class OStreamLogExporter final : public sdklogs::LogExporter +class OStreamLogExporter final : public opentelemetry::sdk::logs::LogExporter { public: /** @@ -34,8 +31,9 @@ class OStreamLogExporter final : public sdklogs::LogExporter /** * Exports a span of logs sent from the processor. */ - sdklogs::ExportResult Export(const nostd::span> - &records) noexcept override; + opentelemetry::sdk::logs::ExportResult Export( + const opentelemetry::nostd::span> + &records) noexcept override; /** * Marks the OStream Log Exporter as shut down. @@ -47,7 +45,7 @@ class OStreamLogExporter final : public sdklogs::LogExporter // The OStream to send the logs to std::ostream &sout_; // Whether this exporter is ShutDown - bool isShutdown_ = false; + bool is_shutdown_ = false; /** * Internal map of the severity number (from 0 to 24) to severity text, matching the @@ -55,7 +53,7 @@ class OStreamLogExporter final : public sdklogs::LogExporter * * If more than one exporter requires this, could move this to Severity enum. */ - const nostd::string_view severityNumToText[25] = { + const opentelemetry::nostd::string_view kSeverityNumToText[25] = { "kInvalid", "kTrace", "kTrace2", "kTrace3", "kTrace4", "kDebug", "kDebug2", "kDebug3", "kDebug4", "kInfo", "kInfo2", "kInfo3", "kInfo4", "kWarn", "kWarn2", "kWarn3", "kWarn4", "kError", "kError2", "kError3", "kError4", @@ -74,50 +72,50 @@ class OStreamLogExporter final : public sdklogs::LogExporter switch (value.index()) { case common::AttributeType::TYPE_BOOL: - sout_ << (nostd::get(value) ? "true" : "false"); + sout_ << (opentelemetry::nostd::get(value) ? "true" : "false"); break; case common::AttributeType::TYPE_INT: - sout_ << nostd::get(value); + sout_ << opentelemetry::nostd::get(value); break; case common::AttributeType::TYPE_INT64: - sout_ << nostd::get(value); + sout_ << opentelemetry::nostd::get(value); break; case common::AttributeType::TYPE_UINT: - sout_ << nostd::get(value); + sout_ << opentelemetry::nostd::get(value); break; case common::AttributeType::TYPE_UINT64: - sout_ << nostd::get(value); + sout_ << opentelemetry::nostd::get(value); break; case common::AttributeType::TYPE_DOUBLE: - sout_ << nostd::get(value); + sout_ << opentelemetry::nostd::get(value); break; case common::AttributeType::TYPE_STRING: case common::AttributeType::TYPE_CSTRING: - sout_ << nostd::get(value); + sout_ << opentelemetry::nostd::get(value); break; /*** Need to support these? ***/ // case common::AttributeType::TYPE_SPAN_BOOL: - // sout_ << nostd::get>(value); + // sout_ << opentelemetry::nostd::get>(value); // break; // case common::AttributeType::TYPE_SPAN_INT: - // sout_ << nostd::get>(value); + // sout_ << opentelemetry::nostd::get>(value); // break; // case common::AttributeType::TYPE_SPAN_INT64: - // sout_ << nostd::get>(value); + // sout_ << opentelemetry::nostd::get>(value); // break; // case common::AttributeType::TYPE_SPAN_UINT: - // sout_ << nostd::get>(value); - // break; + // sout_ << opentelemetry::nostd::get>(value); break; // case common::AttributeType::TYPE_SPAN_UINT64: - // sout_ << nostd::get>(value); + // sout_ << opentelemetry::nostd::get>(value); // break; // case common::AttributeType::TYPE_SPAN_DOUBLE: - // sout_ << nostd::get>(value); + // sout_ << opentelemetry::nostd::get>(value); // break; // case common::AttributeType::TYPE_SPAN_STRING: - // sout_ << nostd::get>(value); - // break; + // sout_ << opentelemetry::nostd::get>(value); break; /******** Up to here ************/ default: @@ -130,7 +128,9 @@ class OStreamLogExporter final : public sdklogs::LogExporter * Helper function to print a KeyValueIterable. * Outputs a pair of type */ - void printKV(bool &firstKV, const nostd::string_view &key, const common::AttributeValue &value) + void printKV(bool &firstKV, + const opentelemetry::nostd::string_view &key, + const common::AttributeValue &value) { if (firstKV) { diff --git a/exporters/ostream/src/log_exporter.cc b/exporters/ostream/src/log_exporter.cc index f0de1dcb50..f8b14c77c4 100644 --- a/exporters/ostream/src/log_exporter.cc +++ b/exporters/ostream/src/log_exporter.cc @@ -15,7 +15,7 @@ OStreamLogExporter::OStreamLogExporter(std::ostream &sout) noexcept : sout_(sout sdklogs::ExportResult OStreamLogExporter::Export( const nostd::span> &records) noexcept { - if (isShutdown_) + if (is_shutdown_) { return sdklogs::ExportResult::kFailure; } @@ -40,7 +40,7 @@ sdklogs::ExportResult OStreamLogExporter::Export( // Print fields most useful to user first sout_ << "{\n" << " timestamp : " << record->timestamp.time_since_epoch().count() << "\n" - << " severity : " << severityNumToText[static_cast(record->severity)] << "\n" + << " severity : " << kSeverityNumToText[static_cast(record->severity)] << "\n" << " name : " << record->name << "\n" << " body : " << record->body << "\n"; @@ -83,7 +83,7 @@ sdklogs::ExportResult OStreamLogExporter::Export( bool OStreamLogExporter::Shutdown(std::chrono::microseconds timeout) noexcept { - isShutdown_ = true; + is_shutdown_ = true; return true; } From 329f14482d2c5f14a7219c2ac00a74d9ca0b7bad Mon Sep 17 00:00:00 2001 From: Karen Xu Date: Sun, 13 Dec 2020 14:37:02 -0500 Subject: [PATCH 3/6] Update with Recordable --- api/include/opentelemetry/logs/log_record.h | 38 +-- api/include/opentelemetry/logs/logger.h | 95 +++--- .../exporters/ostream/log_exporter.h | 201 ++++++------ exporters/ostream/src/log_exporter.cc | 103 +++--- exporters/ostream/test/ostream_log_test.cc | 293 ++++++++++-------- sdk/include/opentelemetry/sdk/logs/exporter.h | 2 +- .../opentelemetry/sdk/logs/processor.h | 2 +- .../sdk/logs/simple_log_processor.h | 7 +- sdk/src/logs/logger.cc | 9 +- sdk/src/logs/simple_log_processor.cc | 11 +- sdk/test/logs/CMakeLists.txt | 2 - sdk/test/logs/logger_provider_sdk_test.cc | 6 +- sdk/test/logs/logger_sdk_test.cc | 8 +- sdk/test/logs/simple_log_processor_test.cc | 4 +- 14 files changed, 395 insertions(+), 386 deletions(-) diff --git a/api/include/opentelemetry/logs/log_record.h b/api/include/opentelemetry/logs/log_record.h index ad2cd1ca9a..c663c1c9c7 100644 --- a/api/include/opentelemetry/logs/log_record.h +++ b/api/include/opentelemetry/logs/log_record.h @@ -65,6 +65,11 @@ enum class Severity : uint8_t kDefault = kInfo // default severity is set to kInfo level, similar to what is done in ILogger }; +/* _nullKV is defined as a private variable that allows "resource" and + "attributes" fields to be instantiated using it as the default value */ +static common::KeyValueIterableView> _nullKV = + common::KeyValueIterableView>{{}}; + /** * A default Event object to be passed in log statements, * matching the 10 fields of the Log Data Model. @@ -74,30 +79,29 @@ enum class Severity : uint8_t struct LogRecord { // default fields that will be set if the user doesn't specify them - core::SystemTimestamp timestamp; // default is 0 - trace::TraceId trace_id; // default is 00000000000000000000000000000000 - trace::SpanId span_id; // default is 0000000000000000 - trace::TraceFlags trace_flags; // default is 00 - Severity severity; // Severity enum that combines severity_text and severity_number + core::SystemTimestamp timestamp; // uint64 nanoseconds since Unix epoch + trace::TraceId trace_id; // byte sequence + trace::SpanId span_id; // byte sequence + trace::TraceFlags trace_flag; // byte + Severity severity; // Severity enum that combines severity_text and severity_number in the + // LogDataModel (can separate in SDK) // other fields that will not be set by default nostd::string_view name; // string nostd::string_view body; // currently a simple string, but should be changed "Any" type - nostd::shared_ptr resource; // key/value pair list - nostd::shared_ptr attributes; // key/value pair list + common::KeyValueIterable &resource; // key/value pair list + common::KeyValueIterable &attributes; // key/value pair list /* Default log record if user does not overwrite this. * TODO: find better data type to represent the type for "body" * Future enhancement: Potentially add other constructors to take default arguments * from the user **/ - LogRecord() - : timestamp{core::SystemTimestamp(std::chrono::seconds(0))}, - severity{Severity::kDefault}, - trace_id{trace::TraceId()}, - span_id{trace::SpanId()}, - trace_flags{trace::TraceFlags()} - {} + LogRecord() : resource(_nullKV), attributes(_nullKV) + { + // TODO: in SDK, assign a default timestamp if not specified + name = ""; + } /* for ease of use; user can use this function to convert a map into a KeyValueIterable for the * resources field */ @@ -105,8 +109,7 @@ struct LogRecord nostd::enable_if_t::value> * = nullptr> inline void SetResource(const T &_resource) { - resource = - nostd::shared_ptr(new common::KeyValueIterableView{_resource}); + resource = common::KeyValueIterableView(_resource); } /* for ease of use; user can use this function to convert a map into a KeyValueIterable for the @@ -115,8 +118,7 @@ struct LogRecord nostd::enable_if_t::value> * = nullptr> inline void SetAttributes(const T &_attributes) { - attributes = nostd::shared_ptr( - new common::KeyValueIterableView{_attributes}); + attributes = common::KeyValueIterableView(_attributes); } }; } // namespace logs diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index b52b344c50..5f22066460 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -38,6 +38,10 @@ class Logger public: virtual ~Logger() = default; + /* Returns the name of the logger */ + // TODO: decide whether this is useful and/or should be kept, as this is not a method required in + // the specification. virtual nostd::string_view getName() = 0; + /** * Each of the following overloaded log(...) methods * creates a log message with the specific parameters passed. @@ -51,92 +55,69 @@ class Logger * @throws No exceptions under any circumstances. */ - /** - * Logs a LogRecord, which contains all the fields of the Log Data Model. Normally called - * indirectly from other log() Methods, but can be called directly for high detail. - * @param record A log record filled with information from the user. + /* The below method is a logging statement that takes in a LogRecord. + * A default LogRecord that will be assigned if no parameters are passed to Logger's .log() method + * which should at minimum assign the trace_id, span_id, and timestamp */ virtual void log(const LogRecord &record) noexcept = 0; - /// Overloaded Log methods, which are simpler than calling a LogRecord directly + /** Overloaded methods for unstructured logging **/ + inline void log(nostd::string_view message) noexcept + { + // Set severity to the default then call log(Severity, String message) method + log(Severity::kDefault, message); + } - /** - * Writes a log. - * @param severity The log's severity - * @param message The message to log - */ inline void log(Severity severity, nostd::string_view message) noexcept { - // Create a LogRecord to hold this information - LogRecord r; - r.severity = severity; - r.body = message; - - // Call the main log(LogRecord) method - log(r); + // TODO: set default timestamp later (not in API) + log(severity, message, core::SystemTimestamp(std::chrono::system_clock::now())); } - /** - * Writes a log. - * @param severity The log's severity - * @param name The name of the log - * @param message The message to log - */ - inline void log(Severity severity, nostd::string_view name, nostd::string_view message) noexcept + inline void log(Severity severity, + nostd::string_view message, + core::SystemTimestamp time) noexcept { - // Create a LogRecord to hold this information + // creates a LogRecord object with given parameters, then calls log(LogRecord) LogRecord r; - r.severity = severity; - r.name = name; - r.body = message; + r.severity = severity; + r.body = message; + r.timestamp = time; - // Call the main log(LogRecord) method log(r); } - /** - * Writes a log. - * @param severity The severity of the log, from 1 to 24 - * @param attributes A key/value object - */ + /** Overloaded methods for structured logging**/ + // TODO: separate this method into separate methods since it is not useful for user to create + // empty logs template ::value> * = nullptr> - inline void log(Severity severity, const T &attributes) noexcept + inline void log(Severity severity = Severity::kDefault, + nostd::string_view name = "", + const T &attributes = {}) noexcept { - // Create a LogRecord to hold this information - LogRecord r; - r.severity = severity; - r.attributes = nostd::shared_ptr( - new common::KeyValueIterableView{attributes}); - - // Call the main log(LogRecord) method - log(r); + log(severity, name, common::KeyValueIterableView(attributes)); } - /** - * Writes a log. - * @param severity The severity of the log, from 1 to 24 - * @param name The name of the log - * @param attributes A key/value object - */ - template ::value> * = nullptr> - inline void log(Severity severity, nostd::string_view name, const T &attributes) noexcept + inline void log(Severity severity, + nostd::string_view name, + const common::KeyValueIterable &attributes) noexcept { - // Create a LogRecord to hold this information + // creates a LogRecord object with given parameters, then calls log(LogRecord) LogRecord r; r.severity = severity; r.name = name; - r.attributes = nostd::shared_ptr( - new common::KeyValueIterableView{attributes}); + r.attributes = attributes; - // Call the main log(LogRecord) method log(r); } + // TODO: add function aliases such as void debug(), void trace(), void info(), etc. for each + // severity level + /** Future enhancement: templated method for objects / custom types (e.g. JSON, XML, custom * classes, etc) **/ // template virtual void log(T &some_obj) noexcept; }; } // namespace logs -OPENTELEMETRY_END_NAMESPACE +OPENTELEMETRY_END_NAMESPACE \ No newline at end of file diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h b/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h index c9bca05776..f0faa1b7ea 100644 --- a/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h +++ b/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h @@ -1,12 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once -#include "opentelemetry/logs/log_record.h" #include "opentelemetry/nostd/type_traits.h" #include "opentelemetry/sdk/logs/exporter.h" +#include "opentelemetry/sdk/logs/log_record.h" #include "opentelemetry/version.h" #include -#include #include OPENTELEMETRY_BEGIN_NAMESPACE @@ -26,13 +41,90 @@ class OStreamLogExporter final : public opentelemetry::sdk::logs::LogExporter */ explicit OStreamLogExporter(std::ostream &sout = std::cout) noexcept; - /*********************** Overloads of LogExporter interface *********************/ + /*********************************** Helper functions ************************/ + + /* + print_value is used to print out the value of an attribute within a vector. + These values are held in a variant which makes the process of printing them much more + complicated. + */ + + template + void print_value(const T &item) + { + sout_ << item; + } + + template + void print_value(const std::vector &vec) + { + sout_ << '['; + size_t i = 1; + size_t sz = vec.size(); + for (auto v : vec) + { + sout_ << v; + if (i != sz) + sout_ << ',' << ' '; + i++; + }; + sout_ << ']'; + } + +// Prior to C++14, generic lambda is not available so fallback to functor. +#if __cplusplus < 201402L + + class OwnedAttributeValueVisitor + { + public: + OwnedAttributeValueVisitor(OStreamLogExporter &exporter) : exporter_(exporter) {} + + template + void operator()(T &&arg) + { + exporter_.print_value(arg); + } + + private: + OStreamLogExporter &exporter_; + }; + +#endif + + void print_value(sdk::common::OwnedAttributeValue &value) + { +#if __cplusplus < 201402L + nostd::visit(OwnedAttributeValueVisitor(*this), value); +#else + nostd::visit([this](auto &&arg) { print_value(arg); }, value); +#endif + } + + void printMap(std::unordered_map map) + { + size_t size = map.size(); + size_t i = 1; + for (auto kv : map) + { + sout_ << "{" << kv.first << ": "; + print_value(kv.second); + sout_ << "}"; + + if (i != size) + sout_ << ", "; + i++; + } + } + + /*********************** LogExporter overloaded methods ***********************/ + + std::unique_ptr MakeRecordable() noexcept override; /** * Exports a span of logs sent from the processor. */ opentelemetry::sdk::logs::ExportResult Export( - const opentelemetry::nostd::span> + const opentelemetry::nostd::span> &records) noexcept override; /** @@ -44,107 +136,8 @@ class OStreamLogExporter final : public opentelemetry::sdk::logs::LogExporter private: // The OStream to send the logs to std::ostream &sout_; - // Whether this exporter is ShutDown + // Whether this exporter has been shut down bool is_shutdown_ = false; - - /** - * Internal map of the severity number (from 0 to 24) to severity text, matching the - * values set by the default Severity enum in api/include/opentelemetry/logs/log_record.h - * - * If more than one exporter requires this, could move this to Severity enum. - */ - const opentelemetry::nostd::string_view kSeverityNumToText[25] = { - "kInvalid", "kTrace", "kTrace2", "kTrace3", "kTrace4", "kDebug", "kDebug2", - "kDebug3", "kDebug4", "kInfo", "kInfo2", "kInfo3", "kInfo4", "kWarn", - "kWarn2", "kWarn3", "kWarn4", "kError", "kError2", "kError3", "kError4", - "kFatal", "kFatal2", "kFatal3", "kFatal4"}; - - /** - * Helper function to print a KeyValueIterable. Outputs a AttributeValue type. - * - * Based off api/include/opentelemetry/common/attribute_value.h - * In the future, may be better to add operator overloads to attribute_value.h - * to make more maintainable. - * - */ - void print_value(const common::AttributeValue &value) - { - switch (value.index()) - { - case common::AttributeType::TYPE_BOOL: - sout_ << (opentelemetry::nostd::get(value) ? "true" : "false"); - break; - case common::AttributeType::TYPE_INT: - sout_ << opentelemetry::nostd::get(value); - break; - case common::AttributeType::TYPE_INT64: - sout_ << opentelemetry::nostd::get(value); - break; - case common::AttributeType::TYPE_UINT: - sout_ << opentelemetry::nostd::get(value); - break; - case common::AttributeType::TYPE_UINT64: - sout_ << opentelemetry::nostd::get(value); - break; - case common::AttributeType::TYPE_DOUBLE: - sout_ << opentelemetry::nostd::get(value); - break; - case common::AttributeType::TYPE_STRING: - case common::AttributeType::TYPE_CSTRING: - sout_ << opentelemetry::nostd::get(value); - break; - - /*** Need to support these? ***/ - // case common::AttributeType::TYPE_SPAN_BOOL: - // sout_ << opentelemetry::nostd::get>(value); - // break; - // case common::AttributeType::TYPE_SPAN_INT: - // sout_ << opentelemetry::nostd::get>(value); - // break; - // case common::AttributeType::TYPE_SPAN_INT64: - // sout_ << opentelemetry::nostd::get>(value); - // break; - // case common::AttributeType::TYPE_SPAN_UINT: - // sout_ << opentelemetry::nostd::get>(value); break; - // case common::AttributeType::TYPE_SPAN_UINT64: - // sout_ << opentelemetry::nostd::get>(value); - // break; - // case common::AttributeType::TYPE_SPAN_DOUBLE: - // sout_ << opentelemetry::nostd::get>(value); - // break; - // case common::AttributeType::TYPE_SPAN_STRING: - // sout_ << opentelemetry::nostd::get>(value); break; - /******** Up to here ************/ - - default: - sout_ << "Invalid type"; - break; - } - } - - /** - * Helper function to print a KeyValueIterable. - * Outputs a pair of type - */ - void printKV(bool &firstKV, - const opentelemetry::nostd::string_view &key, - const common::AttributeValue &value) - { - if (firstKV) - { - firstKV = false; - } - else - { - sout_ << ", "; - } - - sout_ << "{" << key << ": "; - print_value(value); - sout_ << "}"; - } }; } // namespace logs } // namespace exporter diff --git a/exporters/ostream/src/log_exporter.cc b/exporters/ostream/src/log_exporter.cc index f8b14c77c4..90585aefc2 100644 --- a/exporters/ostream/src/log_exporter.cc +++ b/exporters/ostream/src/log_exporter.cc @@ -1,3 +1,19 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "opentelemetry/exporters/ostream/log_exporter.h" #include @@ -12,8 +28,13 @@ namespace logs { OStreamLogExporter::OStreamLogExporter(std::ostream &sout) noexcept : sout_(sout) {} +std::unique_ptr OStreamLogExporter::MakeRecordable() noexcept +{ + return std::unique_ptr(new sdklogs::LogRecord()); +} + sdklogs::ExportResult OStreamLogExporter::Export( - const nostd::span> &records) noexcept + const nostd::span> &records) noexcept { if (is_shutdown_) { @@ -22,59 +43,51 @@ sdklogs::ExportResult OStreamLogExporter::Export( for (auto &record : records) { - // Convert trace, spanid, traceflags into string convertable representation - constexpr int trace_id_bytes = 16; - char trace_id[trace_id_bytes * 2] = {0}; - record->trace_id.ToLowerBase16(trace_id); + // Convert recordable to a LogRecord so that the getters of the LogRecord can be used + auto log_record = + std::unique_ptr(static_cast(record.release())); + + if (log_record == nullptr) + { + // Error "recordable data was lost" + continue; + } + // Convert trace, spanid, traceflags into exportable representation + constexpr int trace_id_bytes = 16; constexpr int span_id_bytes = 8; - char span_id[span_id_bytes * 2] = {0}; - record->span_id.ToLowerBase16(span_id); + constexpr int trace_flags_bytes = 1; - constexpr int trace_flags_bytes = 1; + char trace_id[trace_id_bytes * 2] = {0}; + char span_id[span_id_bytes * 2] = {0}; char trace_flags[trace_flags_bytes * 2] = {0}; - record->trace_flags.ToLowerBase16(trace_flags); - /*** Print out each field of the log record ***/ + log_record->GetTraceId().ToLowerBase16(trace_id); + log_record->GetSpanId().ToLowerBase16(span_id); + log_record->GetTraceFlags().ToLowerBase16(trace_flags); - // Print fields most useful to user first + // Print out each field of the log record sout_ << "{\n" - << " timestamp : " << record->timestamp.time_since_epoch().count() << "\n" - << " severity : " << kSeverityNumToText[static_cast(record->severity)] << "\n" - << " name : " << record->name << "\n" - << " body : " << record->body << "\n"; - - // Print "resource" field - sout_ << " resource : {"; - bool firstKV = true; - if (record->resource != nullptr) - { - record->resource->ForEachKeyValue([&](nostd::string_view key, - common::AttributeValue value) noexcept { - printKV(firstKV, key, value); - return true; - }); - } - sout_ << "}\n"; + << " timestamp : " << log_record->GetTimestamp().time_since_epoch().count() << "\n" + << " severity_num : " << static_cast(log_record->GetSeverity()) << "\n" + << " severity_text : " + << opentelemetry::logs::SeverityNumToText[static_cast(log_record->GetSeverity())] + << "\n" + << " name : " << log_record->GetName() << "\n" + << " body : " << log_record->GetBody() << "\n" + << " resource : {"; - // Print "attributes" field - sout_ << " attributes : {"; - firstKV = true; - if (record->attributes != nullptr) - { - firstKV = true; - record->attributes->ForEachKeyValue([&](nostd::string_view key, - common::AttributeValue value) noexcept { - printKV(firstKV, key, value); - return true; - }); - } - sout_ << "}\n"; + printMap(log_record->GetResource()); + + sout_ << "}\n" + << " attributes : {"; + + printMap(log_record->GetAttributes()); - // Print span context fields - sout_ << " trace_id : " << std::string(trace_id, 32) << "\n" - << " span_id : " << std::string(span_id, 16) << "\n" - << " trace_flags : " << std::string(trace_flags, 2) << "\n" + sout_ << "}\n" + << " trace_id : " << std::string(trace_id, 32) << "\n" + << " span_id : " << std::string(span_id, 16) << "\n" + << " trace_flags : " << std::string(trace_flags, 2) << "\n" << "}\n"; } diff --git a/exporters/ostream/test/ostream_log_test.cc b/exporters/ostream/test/ostream_log_test.cc index 01a0cad545..296b74c960 100644 --- a/exporters/ostream/test/ostream_log_test.cc +++ b/exporters/ostream/test/ostream_log_test.cc @@ -6,9 +6,6 @@ #include #include -#include -#include - namespace sdklogs = opentelemetry::sdk::logs; namespace logs_api = opentelemetry::logs; namespace nostd = opentelemetry::nostd; @@ -19,192 +16,224 @@ namespace exporter namespace logs { -// Test that processor/exporter is shutdown, no logs should be sent to stream +// Test that when OStream Log exporter is shutdown, no logs should be sent to stream TEST(OStreamLogExporter, Shutdown) { auto exporter = std::unique_ptr(new opentelemetry::exporter::logs::OStreamLogExporter); - auto processor = - std::shared_ptr(new sdklogs::SimpleLogProcessor(std::move(exporter))); - - auto record = std::shared_ptr(new logs_api::LogRecord()); - record->name = "Test Log"; - // Create stringstream to redirect to - std::stringstream stdoutOutput; - - // Save cout's buffer here - std::streambuf *sbuf = std::cout.rdbuf(); + // Save cout's original buffer here + std::streambuf *original = std::cout.rdbuf(); // Redirect cout to our stringstream buffer - std::cout.rdbuf(stdoutOutput.rdbuf()); + std::stringstream output; + std::cout.rdbuf(output.rdbuf()); - processor->Shutdown(); + EXPECT_TRUE(exporter->Shutdown()); // After processor/exporter is shutdown, no logs should be sent to stream - processor->OnReceive(record); + auto record = exporter->MakeRecordable(); + record->SetBody("Log record not empty"); + exporter->Export(nostd::span>(&record, 1)); - std::cout.rdbuf(sbuf); + // Restore original stringstream buffer + std::cout.rdbuf(original); - ASSERT_EQ(stdoutOutput.str(), ""); + ASSERT_EQ(output.str(), ""); } -// ---------------------------------- Print to cout, cerr, and clog ------------------------- +// ---------------------------------- Print to cout ------------------------- -// Print Log to std::cout -TEST(OStreamLogExporter, PrintSimpleLogToCout) +// Testing what a default log record that has no values changed will print out +// This function tests MakeRecordable() as well as Export(). +TEST(OstreamLogExporter, DefaultLogRecordToCout) { - // Initialize an Ostream exporter to cout auto exporter = std::unique_ptr( new opentelemetry::exporter::logs::OStreamLogExporter(std::cout)); - auto processor = - std::shared_ptr(new sdklogs::SimpleLogProcessor(std::move(exporter))); - // Save original stream buffer and redirect cout to our new stream buffer - std::streambuf *sbuf = std::cout.rdbuf(); - std::stringstream stdcoutOutput; - std::cout.rdbuf(stdcoutOutput.rdbuf()); + // Save cout's original buffer here + std::streambuf *original = std::cout.rdbuf(); + + // Redirect cout to our stringstream buffer + std::stringstream output; + std::cout.rdbuf(output.rdbuf()); + + // Pass a default recordable created by the exporter to be exported + auto log_record = exporter->MakeRecordable(); + exporter->Export(nostd::span>(&log_record, 1)); + + // Restore cout's original stringstream + std::cout.rdbuf(original); + + std::string expectedOutput = + "{\n" + " timestamp : 0\n" + " severity_num : 0\n" + " severity_text : INVALID\n" + " name : \n" + " body : \n" + " resource : {}\n" + " attributes : {}\n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" + "}\n"; + + ASSERT_EQ(output.str(), expectedOutput); +} + +// Testing what a log record with only the "timestamp", "severity", "name" and "message" fields set, +// will print out +TEST(OStreamLogExporter, SimpleLogToCout) +{ + // Initialize an Ostream exporter to std::cout + auto exporter = std::unique_ptr( + new opentelemetry::exporter::logs::OStreamLogExporter(std::cout)); + + // Save original stream buffer, then redirect cout to our new stream buffer + std::streambuf *original = std::cout.rdbuf(); + std::stringstream output; + std::cout.rdbuf(output.rdbuf()); - // Create a log record and manually set all fields (since we are not using SDK to inject fields) + // Pass a default recordable created by the exporter to be exported + // Create a log record and manually timestamp, severity, name, message opentelemetry::core::SystemTimestamp now(std::chrono::system_clock::now()); - auto record = std::shared_ptr(new logs_api::LogRecord()); - record->timestamp = now; - record->severity = logs_api::Severity::kInfo; - record->name = "Test Log"; - record->body = "Message"; + auto record = std::unique_ptr(new sdklogs::LogRecord()); + record->SetTimestamp(now); + record->SetSeverity(logs_api::Severity::kTrace); // kTrace has enum value of 1 + record->SetName("Name"); + record->SetBody("Message"); // Log a record to cout - processor->OnReceive(record); + exporter->Export(nostd::span>(&record, 1)); // Reset cout's original stringstream buffer - std::cout.rdbuf(sbuf); + std::cout.rdbuf(original); std::string expectedOutput = "{\n" - " timestamp : " + + " timestamp : " + std::to_string(now.time_since_epoch().count()) + "\n" - " severity : kInfo\n" - " name : Test Log\n" - " body : Message\n" - " resource : {}\n" - " attributes : {}\n" - " trace_id : 00000000000000000000000000000000\n" - " span_id : 0000000000000000\n" - " trace_flags : 00\n" + " severity_num : 1\n" + " severity_text : TRACE\n" + " name : Name\n" + " body : Message\n" + " resource : {}\n" + " attributes : {}\n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" "}\n"; - ASSERT_EQ(stdcoutOutput.str(), expectedOutput); + ASSERT_EQ(output.str(), expectedOutput); } -// Print log to std::cerr -TEST(OStreamLogExporter, PrintLogWithResourceToCerr) +// ---------------------------------- Print to cerr -------------------------- + +// Testing what a log record with only the "resource" and "attributes" fields +// (i.e. KeyValueIterable types) set with primitive types, will print out +TEST(OStreamLogExporter, LogWithStringAttributesToCerr) { // Initialize an Ostream exporter to cerr auto exporter = std::unique_ptr( new opentelemetry::exporter::logs::OStreamLogExporter(std::cerr)); - auto processor = - std::shared_ptr(new sdklogs::SimpleLogProcessor(std::move(exporter))); - // Save original stream buffer and redirect cerr to our new stream buffer - std::streambuf *sbuf = std::cerr.rdbuf(); + // Save original stream buffer, then redirect cout to our new stream buffer + std::streambuf *original = std::cerr.rdbuf(); std::stringstream stdcerrOutput; std::cerr.rdbuf(stdcerrOutput.rdbuf()); - // Create a log record and manually set all fields (since we are not using SDK to inject fields) - opentelemetry::core::SystemTimestamp now(std::chrono::system_clock::now()); + // Pass a recordable created by the exporter to be exported + auto record = exporter->MakeRecordable(); - auto record = std::shared_ptr(new logs_api::LogRecord()); - record->timestamp = now; - record->severity = logs_api::Severity::kInfo; - record->name = "Test Log"; - record->body = "Message"; + // Set resources for this log record only of type + record->SetResource("key1", "val1"); - // Set attributes for this log record of type - const std::map m = {{"key1", "val1"}, {"key2", "val2"}}; - record->SetResource(m); + // Set attributes to this log record of type + record->SetAttribute("a", true); - // Log a record to cerr - processor->OnReceive(record); + // Log record to cerr + exporter->Export(nostd::span>(&record, 1)); // Reset cerr's original stringstream buffer - std::cerr.rdbuf(sbuf); + std::cerr.rdbuf(original); std::string expectedOutput = "{\n" - " timestamp : " + - std::to_string(now.time_since_epoch().count()) + - "\n" - " severity : kInfo\n" - " name : Test Log\n" - " body : Message\n" - " resource : {{key1: val1}, {key2: val2}}\n" - " attributes : {}\n" - " trace_id : 00000000000000000000000000000000\n" - " span_id : 0000000000000000\n" - " trace_flags : 00\n" + " timestamp : 0\n" + " severity_num : 0\n" + " severity_text : INVALID\n" + " name : \n" + " body : \n" + " resource : {{key1: val1}}\n" + " attributes : {{a: 1}}\n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" "}\n"; ASSERT_EQ(stdcerrOutput.str(), expectedOutput); } -// Pirnt log to std::clog -TEST(OStreamLogExporter, PrintLogWithMixedAttributesToClog) +// ---------------------------------- Print to clog ------------------------- + +// Testing what a log record with only the "resource", and "attributes" fields +// (i.e. KeyValueIterable types), set with 2D arrays as values, will print out +TEST(OStreamLogExporter, LogWithVariantTypesToClog) { - // Initialize an ostream exporter to clog + + // Initialize an Ostream exporter to cerr auto exporter = std::unique_ptr( new opentelemetry::exporter::logs::OStreamLogExporter(std::clog)); - auto processor = - std::shared_ptr(new sdklogs::SimpleLogProcessor(std::move(exporter))); - // Save original stream buffer and redirect clog to our new stream buffer - std::streambuf *sbuf = std::clog.rdbuf(); - std::stringstream stdcerrOutput; - std::clog.rdbuf(stdcerrOutput.rdbuf()); + // Save original stream buffer, then redirect cout to our new stream buffer + std::streambuf *original = std::clog.rdbuf(); + std::stringstream stdclogOutput; + std::clog.rdbuf(stdclogOutput.rdbuf()); - // Create a log record and manually set all fields (since we are not using SDK to inject fields) - opentelemetry::core::SystemTimestamp now(std::chrono::system_clock::now()); + // Pass a recordable created by the exporter to be exported + auto record = exporter->MakeRecordable(); - auto record = std::shared_ptr(new logs_api::LogRecord()); - record->timestamp = now; - record->severity = logs_api::Severity::kInfo; // kInfo = 9 - record->name = "Test Log"; - record->body = "Message"; + // Set resources for this log record of only string types as the value + // e.g. key/value is a par of type + // std::vector vect = {1, 2, 1}; + // record->SetResource("num", opentelemetry::nostd::span {vect.data(), vect.size()}); - // Set resources to this log record of type - const std::map m = {{"key1", 1.1}, {"key2", 2.2}, {"key3", 3.3}}; - record->SetResource(m); + std::array array1 = {1, 2, 3}; + opentelemetry::nostd::span data1{array1.data(), array1.size()}; + record->SetResource("res1", data1); - // Set attributes to this log record of type - const std::map n = { - {"s1", 10}, {"s2", true}, {"s3", false}}; - record->SetAttributes(n); + // Set resources for this log record of bool types as the value + // e.g. key/value is a par of type + std::array array = {false, true, false}; + record->SetAttribute("attr1", opentelemetry::nostd::span{array.data(), array.size()}); // Log a record to clog - processor->OnReceive(record); + exporter->Export(nostd::span>(&record, 1)); // Reset clog's original stringstream buffer - std::clog.rdbuf(sbuf); + std::clog.rdbuf(original); std::string expectedOutput = "{\n" - " timestamp : " + - std::to_string(now.time_since_epoch().count()) + - "\n" - " severity : kInfo\n" - " name : Test Log\n" - " body : Message\n" - " resource : {{key1: 1.1}, {key2: 2.2}, {key3: 3.3}}\n" - " attributes : {{s1: 10}, {s2: true}, {s3: false}}\n" - " trace_id : 00000000000000000000000000000000\n" - " span_id : 0000000000000000\n" - " trace_flags : 00\n" + " timestamp : 0\n" + " severity_num : 0\n" + " severity_text : INVALID\n" + " name : \n" + " body : \n" + " resource : {{res1: [1, 2, 3]}}\n" + " attributes : {{attr1: [0, 1, 0]}}\n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" "}\n"; - ASSERT_EQ(stdcerrOutput.str(), expectedOutput); + ASSERT_EQ(stdclogOutput.str(), expectedOutput); } -// ---------------------------------- Integration Tests ------------------------- +// // ---------------------------------- Integration Tests ------------------------- -// Print a log using the full logging pipeline +// Test using the simple log processor and ostream exporter to cout +// and use the rest of the logging pipeline (Logger, LoggerProvider, Provider) as well TEST(OStreamLogExporter, IntegrationTest) { // Initialize a logger @@ -220,40 +249,36 @@ TEST(OStreamLogExporter, IntegrationTest) auto logger = logs_api::Provider::GetLoggerProvider()->GetLogger("Logger"); // Back up cout's streambuf - std::streambuf *sbuf = std::cout.rdbuf(); + std::streambuf *original = std::cout.rdbuf(); // Redirect cout to our string stream std::stringstream stdcoutOutput; std::cout.rdbuf(stdcoutOutput.rdbuf()); // Write a log to ostream exporter - logs_api::LogRecord record; opentelemetry::core::SystemTimestamp now(std::chrono::system_clock::now()); - record.timestamp = now; - record.severity = logs_api::Severity::kInfo; - record.name = "Name"; - record.body = "Message"; - - logger->log(record); + logger->Log(opentelemetry::logs::Severity::kDebug, "Hello", now); // Restore cout's original streambuf - std::cout.rdbuf(sbuf); + std::cout.rdbuf(original); // Compare actual vs expected outputs std::string expectedOutput = "{\n" - " timestamp : " + + " timestamp : " + std::to_string(now.time_since_epoch().count()) + "\n" - " severity : kInfo\n" - " name : Name\n" - " body : Message\n" - " resource : {}\n" - " attributes : {}\n" - " trace_id : 00000000000000000000000000000000\n" - " span_id : 0000000000000000\n" - " trace_flags : 00\n" + " severity_num : 5\n" + " severity_text : DEBUG\n" + " name : \n" + " body : Hello\n" + " resource : {}\n" + " attributes : {}\n" + " trace_id : 00000000000000000000000000000000\n" + " span_id : 0000000000000000\n" + " trace_flags : 00\n" "}\n"; + "}\n"; ASSERT_EQ(stdcoutOutput.str(), expectedOutput); } diff --git a/sdk/include/opentelemetry/sdk/logs/exporter.h b/sdk/include/opentelemetry/sdk/logs/exporter.h index e550ed40b3..6f44d32a28 100644 --- a/sdk/include/opentelemetry/sdk/logs/exporter.h +++ b/sdk/include/opentelemetry/sdk/logs/exporter.h @@ -55,7 +55,7 @@ class LogExporter * @returns an ExportResult code (whether export was success or failure) */ virtual ExportResult Export( - const nostd::span> &records) noexcept = 0; + const nostd::span> &records) noexcept = 0; /** * Marks the exporter as ShutDown and cleans up any resources as required. diff --git a/sdk/include/opentelemetry/sdk/logs/processor.h b/sdk/include/opentelemetry/sdk/logs/processor.h index bb3ad3c08e..7468548dcf 100644 --- a/sdk/include/opentelemetry/sdk/logs/processor.h +++ b/sdk/include/opentelemetry/sdk/logs/processor.h @@ -38,7 +38,7 @@ class LogProcessor * OnReceive is called by the SDK once a log record has been successfully created. * @param record the log record */ - virtual void OnReceive(std::shared_ptr record) noexcept = 0; + virtual void OnReceive(std::unique_ptr &&record) noexcept = 0; /** * Exports all log records that have not yet been exported to the configured Exporter. diff --git a/sdk/include/opentelemetry/sdk/logs/simple_log_processor.h b/sdk/include/opentelemetry/sdk/logs/simple_log_processor.h index 3c64d48c98..18ed28b2cf 100644 --- a/sdk/include/opentelemetry/sdk/logs/simple_log_processor.h +++ b/sdk/include/opentelemetry/sdk/logs/simple_log_processor.h @@ -43,12 +43,13 @@ class SimpleLogProcessor : public LogProcessor explicit SimpleLogProcessor(std::unique_ptr &&exporter); virtual ~SimpleLogProcessor() = default; - void OnReceive(std::shared_ptr record) noexcept override; + void OnReceive(std::unique_ptr &&record) noexcept override; bool ForceFlush( - std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; - bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override; + bool Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept override; private: // The configured exporter diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index 1da73c1542..bc19f5d1c1 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -42,20 +42,17 @@ void Logger::log(const opentelemetry::logs::LogRecord &record) noexcept * converting record a heap variable can be removed */ auto record_pointer = - std::shared_ptr(new opentelemetry::logs::LogRecord(record)); + std::unique_ptr(new opentelemetry::logs::LogRecord(record)); // TODO: Do not want to overwrite user-set timestamp if there already is one - // add a flag in the API to check if timestamp is set by user already before setting timestamp // Inject timestamp if none is set - if (record_pointer->timestamp == opentelemetry::core::SystemTimestamp(std::chrono::seconds(0))) - { - record_pointer->timestamp = core::SystemTimestamp(std::chrono::system_clock::now()); - } + record_pointer->timestamp = core::SystemTimestamp(std::chrono::system_clock::now()); // TODO: inject traceid/spanid later // Send the log record to the processor - processor->OnReceive(record_pointer); + processor->OnReceive(std::move(record_pointer)); } } // namespace logs diff --git a/sdk/src/logs/simple_log_processor.cc b/sdk/src/logs/simple_log_processor.cc index d20e27e04c..d88fc9eb92 100644 --- a/sdk/src/logs/simple_log_processor.cc +++ b/sdk/src/logs/simple_log_processor.cc @@ -36,17 +36,16 @@ SimpleLogProcessor::SimpleLogProcessor(std::unique_ptr &&exporter) * Batches the log record it receives in a batch of 1 and immediately sends it * to the configured exporter */ -void SimpleLogProcessor::OnReceive(std::shared_ptr record) noexcept +void SimpleLogProcessor::OnReceive( + std::unique_ptr &&record) noexcept { - std::vector> batch; - batch.emplace_back(record); + nostd::span> batch(&record, 1); // Get lock to ensure Export() is never called concurrently const std::lock_guard locked(lock_); - if (exporter_->Export(opentelemetry::nostd::span>( - batch.data(), batch.size())) != ExportResult::kSuccess) + if (exporter_->Export(batch) != ExportResult::kSuccess) { - /* TODO: alert user of the failed or timedout export result */ + /* Alert user of the failed export */ } } /** diff --git a/sdk/test/logs/CMakeLists.txt b/sdk/test/logs/CMakeLists.txt index e07f569b52..024c42c45a 100644 --- a/sdk/test/logs/CMakeLists.txt +++ b/sdk/test/logs/CMakeLists.txt @@ -3,10 +3,8 @@ foreach(testname logger_provider_sdk_test logger_sdk_test add_executable(${testname} "${testname}.cc") target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_logs) - gtest_add_tests( TARGET ${testname} TEST_PREFIX logs. TEST_LIST ${testname}) - endforeach() diff --git a/sdk/test/logs/logger_provider_sdk_test.cc b/sdk/test/logs/logger_provider_sdk_test.cc index 86f250cce2..02e7c47275 100644 --- a/sdk/test/logs/logger_provider_sdk_test.cc +++ b/sdk/test/logs/logger_provider_sdk_test.cc @@ -69,12 +69,12 @@ TEST(LoggerProviderSDK, LoggerProviderLoggerArguments) class DummyProcessor : public LogProcessor { - void OnReceive(std::shared_ptr record) noexcept {} - bool ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept + void OnReceive(std::unique_ptr &&record) noexcept {} + bool ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept { return true; } - bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept { return true; } diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index 0c8418144b..d4825d0032 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -37,12 +37,12 @@ TEST(LoggerSDK, LogToNullProcessor) class DummyProcessor : public LogProcessor { - void OnReceive(std::shared_ptr record) noexcept {} - bool ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept + void OnReceive(std::unique_ptr &&record) noexcept {} + bool ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept { return true; } - bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept + bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept { return true; } @@ -74,4 +74,4 @@ TEST(LoggerSDK, LogToAProcessor) opentelemetry::logs::LogRecord r; r.name = "Test log"; logger->log(r); -} \ No newline at end of file +} diff --git a/sdk/test/logs/simple_log_processor_test.cc b/sdk/test/logs/simple_log_processor_test.cc index 00e40a53b7..80c83183d3 100644 --- a/sdk/test/logs/simple_log_processor_test.cc +++ b/sdk/test/logs/simple_log_processor_test.cc @@ -26,7 +26,7 @@ class TestExporter final : public LogExporter // Stores the names of the log records this exporter receives to an internal list ExportResult Export( - const opentelemetry::nostd::span> &records) noexcept override + const opentelemetry::nostd::span> &records) noexcept override { *batch_size_received = records.size(); for (auto &record : records) @@ -116,7 +116,7 @@ class FailShutDownExporter final : public LogExporter FailShutDownExporter() {} ExportResult Export( - const opentelemetry::nostd::span> &records) noexcept override + const opentelemetry::nostd::span> &records) noexcept override { return ExportResult::kSuccess; } From 8f9dc4dda603d24e8cd1e5712249ecb3d4867df0 Mon Sep 17 00:00:00 2001 From: Karen Xu Date: Tue, 15 Dec 2020 09:19:13 -0500 Subject: [PATCH 4/6] Minor cleanup --- exporters/ostream/src/log_exporter.cc | 21 +++++++++++---------- exporters/ostream/test/ostream_log_test.cc | 8 ++------ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/exporters/ostream/src/log_exporter.cc b/exporters/ostream/src/log_exporter.cc index 90585aefc2..42cfa7b124 100644 --- a/exporters/ostream/src/log_exporter.cc +++ b/exporters/ostream/src/log_exporter.cc @@ -54,19 +54,20 @@ sdklogs::ExportResult OStreamLogExporter::Export( } // Convert trace, spanid, traceflags into exportable representation - constexpr int trace_id_bytes = 16; - constexpr int span_id_bytes = 8; - constexpr int trace_flags_bytes = 1; + constexpr int trace_id_len = 32; + constexpr int span_id__len = 16; + constexpr int trace_flags_len = 2; - char trace_id[trace_id_bytes * 2] = {0}; - char span_id[span_id_bytes * 2] = {0}; - char trace_flags[trace_flags_bytes * 2] = {0}; + char trace_id[trace_id_len] = {0}; + char span_id[span_id__len] = {0}; + char trace_flags[trace_flags_len] = {0}; log_record->GetTraceId().ToLowerBase16(trace_id); log_record->GetSpanId().ToLowerBase16(span_id); log_record->GetTraceFlags().ToLowerBase16(trace_flags); - // Print out each field of the log record + // Print out each field of the log record, noting that severity is separated + // into severity_num and severity_text sout_ << "{\n" << " timestamp : " << log_record->GetTimestamp().time_since_epoch().count() << "\n" << " severity_num : " << static_cast(log_record->GetSeverity()) << "\n" @@ -85,9 +86,9 @@ sdklogs::ExportResult OStreamLogExporter::Export( printMap(log_record->GetAttributes()); sout_ << "}\n" - << " trace_id : " << std::string(trace_id, 32) << "\n" - << " span_id : " << std::string(span_id, 16) << "\n" - << " trace_flags : " << std::string(trace_flags, 2) << "\n" + << " trace_id : " << std::string(trace_id, trace_id_len) << "\n" + << " span_id : " << std::string(span_id, span_id__len) << "\n" + << " trace_flags : " << std::string(trace_flags, trace_flags_len) << "\n" << "}\n"; } diff --git a/exporters/ostream/test/ostream_log_test.cc b/exporters/ostream/test/ostream_log_test.cc index 296b74c960..e91528b20a 100644 --- a/exporters/ostream/test/ostream_log_test.cc +++ b/exporters/ostream/test/ostream_log_test.cc @@ -194,17 +194,13 @@ TEST(OStreamLogExporter, LogWithVariantTypesToClog) // Pass a recordable created by the exporter to be exported auto record = exporter->MakeRecordable(); - // Set resources for this log record of only string types as the value - // e.g. key/value is a par of type - // std::vector vect = {1, 2, 1}; - // record->SetResource("num", opentelemetry::nostd::span {vect.data(), vect.size()}); - + // Set resources for this log record of only integer types as the value std::array array1 = {1, 2, 3}; opentelemetry::nostd::span data1{array1.data(), array1.size()}; record->SetResource("res1", data1); // Set resources for this log record of bool types as the value - // e.g. key/value is a par of type + // e.g. key/value is a par of type std::array array = {false, true, false}; record->SetAttribute("attr1", opentelemetry::nostd::span{array.data(), array.size()}); From dfd679ed0a132c7f115a96ebd215e836b3e6cd2c Mon Sep 17 00:00:00 2001 From: Karen Xu Date: Tue, 15 Dec 2020 14:05:16 -0500 Subject: [PATCH 5/6] Add {} to printMap, add todo for error --- .../opentelemetry/exporters/ostream/log_exporter.h | 2 ++ exporters/ostream/src/log_exporter.cc | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h b/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h index f0faa1b7ea..73aa7c80ff 100644 --- a/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h +++ b/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h @@ -102,6 +102,7 @@ class OStreamLogExporter final : public opentelemetry::sdk::logs::LogExporter void printMap(std::unordered_map map) { + sout_ << "{"; size_t size = map.size(); size_t i = 1; for (auto kv : map) @@ -114,6 +115,7 @@ class OStreamLogExporter final : public opentelemetry::sdk::logs::LogExporter sout_ << ", "; i++; } + sout_ << "}"; } /*********************** LogExporter overloaded methods ***********************/ diff --git a/exporters/ostream/src/log_exporter.cc b/exporters/ostream/src/log_exporter.cc index 42cfa7b124..d9b8b41735 100644 --- a/exporters/ostream/src/log_exporter.cc +++ b/exporters/ostream/src/log_exporter.cc @@ -49,7 +49,7 @@ sdklogs::ExportResult OStreamLogExporter::Export( if (log_record == nullptr) { - // Error "recordable data was lost" + // TODO: Log Internal SDK error "recordable data was lost" continue; } @@ -76,16 +76,16 @@ sdklogs::ExportResult OStreamLogExporter::Export( << "\n" << " name : " << log_record->GetName() << "\n" << " body : " << log_record->GetBody() << "\n" - << " resource : {"; + << " resource : "; printMap(log_record->GetResource()); - sout_ << "}\n" - << " attributes : {"; + sout_ << "\n" + << " attributes : "; printMap(log_record->GetAttributes()); - sout_ << "}\n" + sout_ << "\n" << " trace_id : " << std::string(trace_id, trace_id_len) << "\n" << " span_id : " << std::string(span_id, span_id__len) << "\n" << " trace_flags : " << std::string(trace_flags, trace_flags_len) << "\n" From 6c94e6e51cd91e0e5e92aadc796108c23c63a167 Mon Sep 17 00:00:00 2001 From: Karen Xu Date: Tue, 15 Dec 2020 19:02:20 -0500 Subject: [PATCH 6/6] Move helper functions to .cc file --- .../exporters/ostream/log_exporter.h | 79 ----------------- exporters/ostream/src/log_exporter.cc | 87 ++++++++++++++++++- 2 files changed, 85 insertions(+), 81 deletions(-) diff --git a/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h b/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h index 73aa7c80ff..47b58dd9ee 100644 --- a/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h +++ b/exporters/ostream/include/opentelemetry/exporters/ostream/log_exporter.h @@ -41,85 +41,6 @@ class OStreamLogExporter final : public opentelemetry::sdk::logs::LogExporter */ explicit OStreamLogExporter(std::ostream &sout = std::cout) noexcept; - /*********************************** Helper functions ************************/ - - /* - print_value is used to print out the value of an attribute within a vector. - These values are held in a variant which makes the process of printing them much more - complicated. - */ - - template - void print_value(const T &item) - { - sout_ << item; - } - - template - void print_value(const std::vector &vec) - { - sout_ << '['; - size_t i = 1; - size_t sz = vec.size(); - for (auto v : vec) - { - sout_ << v; - if (i != sz) - sout_ << ',' << ' '; - i++; - }; - sout_ << ']'; - } - -// Prior to C++14, generic lambda is not available so fallback to functor. -#if __cplusplus < 201402L - - class OwnedAttributeValueVisitor - { - public: - OwnedAttributeValueVisitor(OStreamLogExporter &exporter) : exporter_(exporter) {} - - template - void operator()(T &&arg) - { - exporter_.print_value(arg); - } - - private: - OStreamLogExporter &exporter_; - }; - -#endif - - void print_value(sdk::common::OwnedAttributeValue &value) - { -#if __cplusplus < 201402L - nostd::visit(OwnedAttributeValueVisitor(*this), value); -#else - nostd::visit([this](auto &&arg) { print_value(arg); }, value); -#endif - } - - void printMap(std::unordered_map map) - { - sout_ << "{"; - size_t size = map.size(); - size_t i = 1; - for (auto kv : map) - { - sout_ << "{" << kv.first << ": "; - print_value(kv.second); - sout_ << "}"; - - if (i != size) - sout_ << ", "; - i++; - } - sout_ << "}"; - } - - /*********************** LogExporter overloaded methods ***********************/ - std::unique_ptr MakeRecordable() noexcept override; /** diff --git a/exporters/ostream/src/log_exporter.cc b/exporters/ostream/src/log_exporter.cc index d9b8b41735..37458df479 100644 --- a/exporters/ostream/src/log_exporter.cc +++ b/exporters/ostream/src/log_exporter.cc @@ -26,8 +26,91 @@ namespace exporter { namespace logs { +/*********************** Helper functions ************************/ + +/* + print_value is used to print out the value of an attribute within a vector. + These values are held in a variant which makes the process of printing them much more + complicated. +*/ + +template +void print_value(const T &item, std::ostream &sout) +{ + sout << item; +} + +template +void print_value(const std::vector &vec, std::ostream &sout) +{ + sout << '['; + size_t i = 1; + size_t sz = vec.size(); + for (auto v : vec) + { + sout << v; + if (i != sz) + sout << ',' << ' '; + i++; + }; + sout << ']'; +} + +// Prior to C++14, generic lambda is not available so fallback to functor. +#if __cplusplus < 201402L + +class OwnedAttributeValueVisitor +{ +public: + OwnedAttributeValueVisitor(std::ostream &sout) : sout_(sout) {} + + template + void operator()(T &&arg) + { + print_value(arg, sout_); + } + +private: + // The OStream to send the logs to + std::ostream &sout_; +}; + +#endif + +void print_value(sdk::common::OwnedAttributeValue &value, std::ostream &sout) +{ +#if __cplusplus < 201402L + nostd::visit(OwnedAttributeValueVisitor(sout), value); +#else + nostd::visit([&sout](auto &&arg) { print_value(arg, sout); }, value); +#endif +} + +void printMap(std::unordered_map map, + std::ostream &sout) +{ + sout << "{"; + size_t size = map.size(); + size_t i = 1; + for (auto kv : map) + { + sout << "{" << kv.first << ": "; + print_value(kv.second, sout); + sout << "}"; + + if (i != size) + sout << ", "; + i++; + } + sout << "}"; +} + +/*********************** Constructor ***********************/ + OStreamLogExporter::OStreamLogExporter(std::ostream &sout) noexcept : sout_(sout) {} +/*********************** Exporter methods ***********************/ + std::unique_ptr OStreamLogExporter::MakeRecordable() noexcept { return std::unique_ptr(new sdklogs::LogRecord()); @@ -78,12 +161,12 @@ sdklogs::ExportResult OStreamLogExporter::Export( << " body : " << log_record->GetBody() << "\n" << " resource : "; - printMap(log_record->GetResource()); + printMap(log_record->GetResource(), sout_); sout_ << "\n" << " attributes : "; - printMap(log_record->GetAttributes()); + printMap(log_record->GetAttributes(), sout_); sout_ << "\n" << " trace_id : " << std::string(trace_id, trace_id_len) << "\n"