| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 |
- // Copyright The OpenTelemetry Authors
- // SPDX-License-Identifier: Apache-2.0
- #include <gtest/gtest.h>
- #include <array>
- #include <chrono>
- #include <initializer_list>
- #include <iostream>
- #include <sstream>
- #include <string>
- #include <utility>
- #include <vector>
- #include "opentelemetry/common/attribute_value.h"
- #include "opentelemetry/common/key_value_iterable_view.h"
- #include "opentelemetry/common/timestamp.h"
- #include "opentelemetry/exporters/ostream/log_record_exporter.h"
- #include "opentelemetry/exporters/ostream/log_record_exporter_factory.h"
- #include "opentelemetry/logs/event_id.h"
- #include "opentelemetry/logs/logger.h"
- #include "opentelemetry/logs/logger_provider.h"
- #include "opentelemetry/logs/provider.h"
- #include "opentelemetry/logs/severity.h"
- #include "opentelemetry/nostd/shared_ptr.h"
- #include "opentelemetry/nostd/span.h"
- #include "opentelemetry/nostd/string_view.h"
- #include "opentelemetry/nostd/utility.h"
- #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"
- #include "opentelemetry/sdk/logs/exporter.h"
- #include "opentelemetry/sdk/logs/logger_provider.h"
- #include "opentelemetry/sdk/logs/processor.h"
- #include "opentelemetry/sdk/logs/provider.h"
- #include "opentelemetry/sdk/logs/read_write_log_record.h"
- #include "opentelemetry/sdk/logs/readable_log_record.h"
- #include "opentelemetry/sdk/logs/recordable.h"
- #include "opentelemetry/sdk/logs/simple_log_record_processor.h"
- #include "opentelemetry/sdk/resource/resource.h"
- #include "opentelemetry/sdk/version/version.h"
- #include "opentelemetry/version.h"
- namespace sdklogs = opentelemetry::sdk::logs;
- namespace logs_api = opentelemetry::logs;
- namespace nostd = opentelemetry::nostd;
- namespace exporterlogs = opentelemetry::exporter::logs;
- namespace common = opentelemetry::common;
- using Attributes = std::initializer_list<std::pair<nostd::string_view, common::AttributeValue>>;
- OPENTELEMETRY_BEGIN_NAMESPACE
- namespace exporter
- {
- namespace logs
- {
- namespace
- {
- static opentelemetry::sdk::instrumentationscope::InstrumentationScope GetTestInstrumentationScope()
- {
- opentelemetry::sdk::instrumentationscope::InstrumentationScope result =
- opentelemetry::sdk::logs::ReadableLogRecord::GetDefaultInstrumentationScope();
- result.SetAttribute("scope.attr.key", "scope.attr.value");
- return result;
- }
- } // namespace
- // Test that when OStream Log exporter is shutdown, no logs should be sent to stream
- TEST(OStreamLogRecordExporter, Shutdown)
- {
- auto exporter =
- std::unique_ptr<sdklogs::LogRecordExporter>(new exporterlogs::OStreamLogRecordExporter);
- // Save cerr original buffer here
- std::streambuf *original = std::cerr.rdbuf();
- // Redirect cerr to our stringstream buffer
- std::stringstream output;
- std::cerr.rdbuf(output.rdbuf());
- EXPECT_TRUE(exporter->Shutdown());
- // After processor/exporter is shutdown, no logs should be sent to stream
- auto record = exporter->MakeRecordable();
- static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetBody("Log record not empty");
- exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1));
- // Restore original stringstream buffer
- std::cerr.rdbuf(original);
- std::string err_message =
- "[Ostream Log Exporter] Exporting 1 log(s) failed, exporter is shutdown";
- EXPECT_TRUE(output.str().find(err_message) != std::string::npos);
- }
- // ---------------------------------- Print to cout -------------------------
- // 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)
- {
- auto exporter = std::unique_ptr<sdklogs::LogRecordExporter>(
- new exporterlogs::OStreamLogRecordExporter(std::cout));
- // 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();
- opentelemetry::sdk::instrumentationscope::InstrumentationScope instrumentation_scope =
- GetTestInstrumentationScope();
- log_record->SetInstrumentationScope(instrumentation_scope);
- exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&log_record, 1));
- // Restore cout's original stringstream
- std::cout.rdbuf(original);
- std::vector<std::string> expected_output{
- "{\n",
- " timestamp : 0\n",
- " severity_num : 0\n",
- " severity_text : INVALID\n",
- " body : \n",
- " resource : \n",
- " telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
- " telemetry.sdk.name: opentelemetry\n",
- " telemetry.sdk.language: cpp\n",
- " attributes : \n",
- " event_id : 0\n",
- " event_name : \n",
- " trace_id : 00000000000000000000000000000000\n",
- " span_id : 0000000000000000\n",
- " trace_flags : 00\n",
- " scope : \n",
- " name : otel-cpp\n",
- " version : " OPENTELEMETRY_SDK_VERSION "\n",
- " schema_url : https://opentelemetry.io/schemas/1.15.0\n",
- " attributes : \n",
- " scope.attr.key: scope.attr.value\n",
- "}\n"};
- std::string ostream_output = output.str();
- for (auto &expected : expected_output)
- {
- std::string::size_type result = ostream_output.find(expected);
- if (result == std::string::npos)
- {
- std::cout << "Can not find: \"" << expected << "\" in\n" << ostream_output << '\n';
- }
- ASSERT_NE(result, std::string::npos);
- }
- }
- // Testing what a log record with only the "timestamp", "severity", "name" and "message" fields set,
- // will print out
- TEST(OStreamLogRecordExporter, SimpleLogToCout)
- {
- // Initialize an Ostream exporter to std::cout
- auto exporter = std::unique_ptr<sdklogs::LogRecordExporter>(
- new exporterlogs::OStreamLogRecordExporter(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());
- // Pass a default recordable created by the exporter to be exported
- // Create a log record and manually timestamp, severity, name, message
- common::SystemTimestamp now(std::chrono::system_clock::now());
- auto record = std::unique_ptr<sdklogs::Recordable>(new sdklogs::ReadWriteLogRecord());
- static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetTimestamp(now);
- static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetObservedTimestamp(now);
- static_cast<sdklogs::ReadWriteLogRecord *>(record.get())
- ->SetSeverity(logs_api::Severity::kTrace); // kTrace has enum value of 1
- static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetBody("Message");
- opentelemetry::sdk::instrumentationscope::InstrumentationScope instrumentation_scope =
- GetTestInstrumentationScope();
- record->SetInstrumentationScope(instrumentation_scope);
- // Log a record to cout
- exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1));
- // Reset cout's original stringstream buffer
- std::cout.rdbuf(original);
- std::vector<std::string> expected_output{
- "{\n",
- " timestamp : " + std::to_string(now.time_since_epoch().count()) +
- "\n"
- " observed_timestamp : " +
- std::to_string(now.time_since_epoch().count()) +
- "\n"
- " severity_num : 1\n"
- " severity_text : TRACE\n"
- " body : Message\n",
- " resource : \n",
- " telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
- " telemetry.sdk.name: opentelemetry\n",
- " telemetry.sdk.language: cpp\n",
- " attributes : \n",
- " event_id : 0\n",
- " event_name : \n",
- " trace_id : 00000000000000000000000000000000\n",
- " span_id : 0000000000000000\n",
- " trace_flags : 00\n",
- " scope : \n",
- " name : otel-cpp\n",
- " version : " OPENTELEMETRY_SDK_VERSION "\n",
- " schema_url : https://opentelemetry.io/schemas/1.15.0\n",
- " attributes : \n",
- " scope.attr.key: scope.attr.value\n",
- "}\n"};
- std::string ostream_output = output.str();
- for (auto &expected : expected_output)
- {
- std::string::size_type result = ostream_output.find(expected);
- if (result == std::string::npos)
- {
- std::cout << "Can not find: \"" << expected << "\" in\n" << ostream_output << '\n';
- }
- ASSERT_NE(result, std::string::npos);
- }
- }
- // ---------------------------------- 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(OStreamLogRecordExporter, LogWithStringAttributesToCerr)
- {
- // Initialize an Ostream exporter to cerr
- auto exporter = std::unique_ptr<sdklogs::LogRecordExporter>(
- new exporterlogs::OStreamLogRecordExporter(std::cerr));
- // 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());
- // Pass a recordable created by the exporter to be exported
- auto record = exporter->MakeRecordable();
- // Set resources for this log record only of type <string, string>
- auto resource = opentelemetry::sdk::resource::Resource::Create({{"key1", "val1"}});
- static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetResource(resource);
- // Set attributes to this log record of type <string, AttributeValue>
- static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetAttribute("a", true);
- opentelemetry::sdk::instrumentationscope::InstrumentationScope instrumentation_scope =
- GetTestInstrumentationScope();
- record->SetInstrumentationScope(instrumentation_scope);
- // Log record to cerr
- exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1));
- // Reset cerr's original stringstream buffer
- std::cerr.rdbuf(original);
- std::vector<std::string> expected_output{
- "{\n",
- " timestamp : 0\n",
- " severity_num : 0\n",
- " severity_text : INVALID\n",
- " body : \n",
- " resource : \n",
- " telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
- " telemetry.sdk.name: opentelemetry\n",
- " telemetry.sdk.language: cpp\n",
- " service.name: unknown_service\n",
- " key1: val1\n",
- " attributes : \n",
- " a: 1\n",
- " event_id : 0\n",
- " event_name : \n",
- " trace_id : 00000000000000000000000000000000\n",
- " span_id : 0000000000000000\n",
- " trace_flags : 00\n",
- " scope : \n",
- " name : otel-cpp\n",
- " version : " OPENTELEMETRY_SDK_VERSION "\n",
- " schema_url : https://opentelemetry.io/schemas/1.15.0\n",
- " attributes : \n",
- " scope.attr.key: scope.attr.value\n",
- "}\n"};
- std::string ostream_output = stdcerrOutput.str();
- for (auto &expected : expected_output)
- {
- std::string::size_type result = ostream_output.find(expected);
- if (result == std::string::npos)
- {
- std::cout << "Can not find: \"" << expected << "\" in\n" << ostream_output << '\n';
- }
- ASSERT_NE(result, std::string::npos);
- }
- }
- // ---------------------------------- 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(OStreamLogRecordExporter, LogWithVariantTypesToClog)
- {
- // Initialize an Ostream exporter to cerr
- auto exporter = std::unique_ptr<sdklogs::LogRecordExporter>(
- new exporterlogs::OStreamLogRecordExporter(std::clog));
- // 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());
- // Pass a recordable created by the exporter to be exported
- auto record = exporter->MakeRecordable();
- // Set resources for this log record of only integer types as the value
- std::array<int, 3> array1 = {1, 2, 3};
- nostd::span<int> data1{array1.data(), array1.size()};
- auto resource = opentelemetry::sdk::resource::Resource::Create({{"res1", data1}});
- static_cast<sdklogs::ReadWriteLogRecord *>(record.get())->SetResource(resource);
- // Set resources for this log record of bool types as the value
- // e.g. key/value is a par of type <string, array of bools>
- std::array<bool, 3> array = {false, true, false};
- static_cast<sdklogs::ReadWriteLogRecord *>(record.get())
- ->SetAttribute("attr1", nostd::span<bool>{array.data(), array.size()});
- opentelemetry::sdk::instrumentationscope::InstrumentationScope instrumentation_scope =
- GetTestInstrumentationScope();
- record->SetInstrumentationScope(instrumentation_scope);
- // Log a record to clog
- exporter->Export(nostd::span<std::unique_ptr<sdklogs::Recordable>>(&record, 1));
- // Reset clog's original stringstream buffer
- std::clog.rdbuf(original);
- std::vector<std::string> expected_output{
- "{\n",
- " timestamp : 0\n",
- " severity_num : 0\n",
- " severity_text : INVALID\n",
- " body : \n",
- " resource : \n",
- " service.name: unknown_service\n",
- " telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
- " telemetry.sdk.name: opentelemetry\n",
- " telemetry.sdk.language: cpp\n",
- " res1: [1,2,3]\n",
- " attributes : \n",
- " attr1: [0,1,0]\n",
- " event_id : 0\n",
- " event_name : \n",
- " trace_id : 00000000000000000000000000000000\n",
- " span_id : 0000000000000000\n",
- " trace_flags : 00\n",
- " scope : \n",
- " name : otel-cpp\n",
- " version : " OPENTELEMETRY_SDK_VERSION "\n",
- " schema_url : https://opentelemetry.io/schemas/1.15.0\n",
- " attributes : \n",
- " scope.attr.key: scope.attr.value\n",
- "}\n"};
- std::string ostream_output = stdclogOutput.str();
- for (auto &expected : expected_output)
- {
- std::string::size_type result = ostream_output.find(expected);
- if (result == std::string::npos)
- {
- std::cout << "Can not find: \"" << expected << "\" in\n" << ostream_output << '\n';
- }
- ASSERT_NE(result, std::string::npos);
- }
- }
- // // ---------------------------------- Integration Tests -------------------------
- // 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(OStreamLogRecordExporter, IntegrationTest)
- {
- // Initialize a logger
- auto exporter =
- std::unique_ptr<sdklogs::LogRecordExporter>(new exporterlogs::OStreamLogRecordExporter);
- auto sdkProvider = std::shared_ptr<sdklogs::LoggerProvider>(new sdklogs::LoggerProvider());
- sdkProvider->AddProcessor(std::unique_ptr<sdklogs::LogRecordProcessor>(
- new sdklogs::SimpleLogRecordProcessor(std::move(exporter))));
- auto apiProvider = nostd::shared_ptr<logs_api::LoggerProvider>(sdkProvider);
- auto provider = nostd::shared_ptr<logs_api::LoggerProvider>(apiProvider);
- sdklogs::Provider::SetLoggerProvider(provider);
- const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"};
- auto logger = logs_api::Provider::GetLoggerProvider()->GetLogger(
- "Logger", "opentelelemtry_library", OPENTELEMETRY_SDK_VERSION, schema_url,
- {{"scope.attr.key", 123}});
- // Back up cout's streambuf
- 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
- common::SystemTimestamp now(std::chrono::system_clock::now());
- logger->EmitLogRecord(logs_api::Severity::kDebug, "Hello", now);
- // Restore cout's original streambuf
- std::cout.rdbuf(original);
- // Compare actual vs expected outputs
- std::vector<std::string> expected_output{
- "{\n",
- " timestamp : " + std::to_string(now.time_since_epoch().count()) + "\n",
- " severity_num : 5\n",
- " severity_text : DEBUG\n",
- " body : Hello\n",
- " resource : \n",
- " telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
- " service.name: unknown_service\n",
- " telemetry.sdk.name: opentelemetry\n",
- " telemetry.sdk.language: cpp\n",
- " attributes : \n",
- " event_id : 0\n",
- " event_name : \n",
- " trace_id : 00000000000000000000000000000000\n",
- " span_id : 0000000000000000\n",
- " trace_flags : 00\n",
- " scope : \n",
- " name : opentelelemtry_library\n",
- " version : " OPENTELEMETRY_SDK_VERSION "\n",
- " schema_url : https://opentelemetry.io/schemas/1.11.0\n",
- " attributes : \n",
- " scope.attr.key: 123\n",
- "}\n"};
- std::string ostream_output = stdcoutOutput.str();
- for (auto &expected : expected_output)
- {
- std::string::size_type result = ostream_output.find(expected);
- if (result == std::string::npos)
- {
- std::cout << "Can not find: \"" << expected << "\" in\n" << ostream_output << '\n';
- }
- ASSERT_NE(result, std::string::npos);
- }
- }
- // Test using the simple log processor and ostream exporter to cout
- // and use the rest of the logging pipeline (Logger, LoggerProvider, Provider) and user-facing API
- // as well
- TEST(OStreamLogRecordExporter, IntegrationTestWithEventId)
- {
- // Initialize a logger
- auto exporter =
- std::unique_ptr<sdklogs::LogRecordExporter>(new exporterlogs::OStreamLogRecordExporter);
- auto sdkProvider = std::shared_ptr<sdklogs::LoggerProvider>(new sdklogs::LoggerProvider());
- sdkProvider->AddProcessor(std::unique_ptr<sdklogs::LogRecordProcessor>(
- new sdklogs::SimpleLogRecordProcessor(std::move(exporter))));
- auto apiProvider = nostd::shared_ptr<logs_api::LoggerProvider>(sdkProvider);
- auto provider = nostd::shared_ptr<logs_api::LoggerProvider>(apiProvider);
- sdklogs::Provider::SetLoggerProvider(provider);
- const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"};
- auto logger = logs_api::Provider::GetLoggerProvider()->GetLogger(
- "Logger", "opentelelemtry_library", OPENTELEMETRY_SDK_VERSION, schema_url,
- {{"scope.attr.key", 123}});
- // Back up cout's streambuf
- std::streambuf *original = std::cout.rdbuf();
- // Redirect cout to our string stream
- std::stringstream stdcoutOutput;
- std::cout.rdbuf(stdcoutOutput.rdbuf());
- logs_api::EventId event_id{12345678, "test_event_id"};
- // Write a log to ostream exporter
- logger->Debug(event_id, "Hello {key1} {key2}",
- common::MakeKeyValueIterableView<Attributes>({{"key1", 123}, {"key2", "value2"}}));
- // Restore cout's original streambuf
- std::cout.rdbuf(original);
- // Compare actual vs expected outputs
- std::vector<std::string> expected_output{
- " severity_num : 5\n",
- " severity_text : DEBUG\n",
- " body : Hello {key1} {key2}\n",
- " resource : \n",
- " telemetry.sdk.version: " OPENTELEMETRY_VERSION "\n",
- " service.name: unknown_service\n",
- " telemetry.sdk.name: opentelemetry\n",
- " telemetry.sdk.language: cpp\n",
- " attributes : \n",
- " event_id : 12345678\n",
- " event_name : test_event_id\n",
- " trace_id : 00000000000000000000000000000000\n",
- " span_id : 0000000000000000\n",
- " trace_flags : 00\n",
- " scope : \n",
- " name : opentelelemtry_library\n",
- " version : " OPENTELEMETRY_SDK_VERSION "\n",
- " schema_url : https://opentelemetry.io/schemas/1.11.0\n",
- " attributes : \n",
- " scope.attr.key: 123\n",
- "}\n"};
- std::string ostream_output = stdcoutOutput.str();
- for (auto &expected : expected_output)
- {
- std::string::size_type result = ostream_output.find(expected);
- if (result == std::string::npos)
- {
- std::cout << "Can not find: \"" << expected << "\" in\n" << ostream_output << '\n';
- }
- ASSERT_NE(result, std::string::npos);
- }
- }
- // Test using the factory to create the ostream exporter
- TEST(OStreamLogRecordExporter, Factory)
- {
- auto exporter = OStreamLogRecordExporterFactory::Create();
- ASSERT_NE(exporter, nullptr);
- }
- } // namespace logs
- } // namespace exporter
- OPENTELEMETRY_END_NAMESPACE
|