From d33ffdb2f860f2f13ae19dbbf3b50268e39492a8 Mon Sep 17 00:00:00 2001 From: Karen Xu Date: Tue, 13 Oct 2020 00:36:16 -0400 Subject: [PATCH] Add Logging SDK implementation for Logging API --- sdk/include/opentelemetry/sdk/logs/TBD | 0 sdk/include/opentelemetry/sdk/logs/logger.h | 60 +++++++++++ .../opentelemetry/sdk/logs/logger_provider.h | 101 ++++++++++++++++++ .../opentelemetry/sdk/logs/processor.h | 47 ++++++++ sdk/src/CMakeLists.txt | 1 + sdk/src/logs/BUILD | 26 +++++ sdk/src/logs/CMakeLists.txt | 3 + sdk/src/logs/TBD | 0 sdk/src/logs/logger.cc | 60 +++++++++++ sdk/src/logs/logger_provider.cc | 81 ++++++++++++++ sdk/test/CMakeLists.txt | 1 + sdk/test/logs/BUILD | 22 ++++ sdk/test/logs/CMakeLists.txt | 6 ++ sdk/test/logs/logger_provider_sdk_test.cc | 87 +++++++++++++++ sdk/test/logs/logger_sdk_test.cc | 71 ++++++++++++ 15 files changed, 566 insertions(+) delete mode 100644 sdk/include/opentelemetry/sdk/logs/TBD create mode 100644 sdk/include/opentelemetry/sdk/logs/logger.h create mode 100644 sdk/include/opentelemetry/sdk/logs/logger_provider.h create mode 100644 sdk/include/opentelemetry/sdk/logs/processor.h create mode 100644 sdk/src/logs/BUILD create mode 100644 sdk/src/logs/CMakeLists.txt delete mode 100644 sdk/src/logs/TBD create mode 100644 sdk/src/logs/logger.cc create mode 100644 sdk/src/logs/logger_provider.cc create mode 100644 sdk/test/logs/BUILD create mode 100644 sdk/test/logs/CMakeLists.txt create mode 100644 sdk/test/logs/logger_provider_sdk_test.cc create mode 100644 sdk/test/logs/logger_sdk_test.cc diff --git a/sdk/include/opentelemetry/sdk/logs/TBD b/sdk/include/opentelemetry/sdk/logs/TBD deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/sdk/include/opentelemetry/sdk/logs/logger.h b/sdk/include/opentelemetry/sdk/logs/logger.h new file mode 100644 index 0000000000..4b49351030 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/logs/logger.h @@ -0,0 +1,60 @@ +/* + * 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/logs/logger.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/common/atomic_shared_ptr.h" +#include "opentelemetry/sdk/logs/logger_provider.h" +#include "opentelemetry/sdk/logs/processor.h" + +#include +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace logs +{ +class LoggerProvider; + +class Logger final : public opentelemetry::logs::Logger +{ +public: + /** + * Initialize a new logger. + * @param logger_provider The logger provider that owns this logger. + */ + explicit Logger(std::shared_ptr logger_provider) noexcept; + + /** + * Writes a log record into the processor. + * @param record The record to write into the processor. + */ + void log(const opentelemetry::logs::LogRecord &record) noexcept override; + +private: + // The logger provider of this Logger. Uses a weak_ptr to avoid cyclic dependancy issues the with + // logger provider + std::weak_ptr logger_provider_; +}; + +} // namespace logs +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/logs/logger_provider.h b/sdk/include/opentelemetry/sdk/logs/logger_provider.h new file mode 100644 index 0000000000..fdba956624 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/logs/logger_provider.h @@ -0,0 +1,101 @@ +/* + * 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 +#include +#include +#include + +#include "opentelemetry/logs/logger_provider.h" +#include "opentelemetry/logs/noop.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/sdk/common/atomic_shared_ptr.h" +#include "opentelemetry/sdk/logs/logger.h" +#include "opentelemetry/sdk/logs/processor.h" + +// Define the maximum number of loggers that are allowed to be registered to the loggerprovider. +// TODO: Add link to logging spec once this is added to it +#define MAX_LOGGER_COUNT 100 + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace logs +{ +class Logger; + +class LoggerProvider final : public opentelemetry::logs::LoggerProvider, + public std::enable_shared_from_this +{ +public: + /** + * Initialize a new logger provider. A processor must later be assigned + * to this logger provider via the SetProcessor() method. + */ + explicit LoggerProvider() noexcept; + + /** + * Creates a logger with the given name, and returns a shared pointer to it. + * If a logger with that name already exists, return a shared pointer to it + * @param name The name of the logger to be created. + * @param options (OPTIONAL) The options for the logger. TODO: Once the logging spec defines it, + * give a list of options that the logger supports. + */ + opentelemetry::nostd::shared_ptr GetLogger( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view options = "") noexcept override; + + /** + * Creates a logger with the given name, and returns a shared pointer to it. + * If a logger with that name already exists, return a shared pointer to it + * @param name The name of the logger to be created. + * @param args (OPTIONAL) The arguments for the logger. TODO: Once the logging spec defines it, + * give a list of arguments that the logger supports. + */ + opentelemetry::nostd::shared_ptr GetLogger( + opentelemetry::nostd::string_view name, + nostd::span args) noexcept override; + + /** + * Returns a shared pointer to the processor currently stored in the + * logger provider. If no processor exists, returns a nullptr + */ + std::shared_ptr GetProcessor() noexcept; + + // Sets the common processor that all the Logger instances will use + /** + * Sets the processor that is stored internally in the logger provider. + * @param processor The processor to be stored inside the logger provider. + * This must not be a nullptr. + */ + void SetProcessor(std::shared_ptr processor) noexcept; + +private: + // A pointer to the processor stored by this logger provider + opentelemetry::sdk::AtomicSharedPtr processor_; + + // A vector of pointers to all the loggers that have been created + std::unordered_map> + loggers_; + + // A mutex that ensures only one thread is using the map of loggers + std::mutex mu_; +}; +} // namespace logs +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/logs/processor.h b/sdk/include/opentelemetry/sdk/logs/processor.h new file mode 100644 index 0000000000..d77b7a65d0 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/logs/processor.h @@ -0,0 +1,47 @@ +/* + * 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 +#include +#include "opentelemetry/logs/log_record.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace logs +{ +/** + * This Log Processor is responsible for conversion of logs to exportable + * representation and passing them to exporters. + */ +class LogProcessor +{ +public: + virtual ~LogProcessor() = default; + + virtual void OnReceive(std::unique_ptr &&record) noexcept = 0; + + virtual void ForceFlush( + std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept = 0; + + virtual void Shutdown( + std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept = 0; +}; +} // namespace logs +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/CMakeLists.txt b/sdk/src/CMakeLists.txt index 607e98bc99..633f9c3465 100644 --- a/sdk/src/CMakeLists.txt +++ b/sdk/src/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(common) add_subdirectory(trace) add_subdirectory(metrics) +add_subdirectory(logs) diff --git a/sdk/src/logs/BUILD b/sdk/src/logs/BUILD new file mode 100644 index 0000000000..c1a0542fa1 --- /dev/null +++ b/sdk/src/logs/BUILD @@ -0,0 +1,26 @@ +# Copyright 2020, 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. + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "logs", + srcs = glob(["**/*.cc"]), + hdrs = glob(["**/*.h"]), + include_prefix = "src/logs", + deps = [ + "//api", + "//sdk:headers", + ], +) diff --git a/sdk/src/logs/CMakeLists.txt b/sdk/src/logs/CMakeLists.txt new file mode 100644 index 0000000000..22260f059d --- /dev/null +++ b/sdk/src/logs/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(opentelemetry_logs logger_provider.cc logger.cc) + +target_link_libraries(opentelemetry_logs opentelemetry_common) diff --git a/sdk/src/logs/TBD b/sdk/src/logs/TBD deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc new file mode 100644 index 0000000000..bc19f5d1c1 --- /dev/null +++ b/sdk/src/logs/logger.cc @@ -0,0 +1,60 @@ +/* + * 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/sdk/logs/logger.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace logs +{ +Logger::Logger(std::shared_ptr logger_provider) noexcept + : logger_provider_(logger_provider) +{} + +void Logger::log(const opentelemetry::logs::LogRecord &record) noexcept +{ + // If this logger does not have a processor, no need to create a log record + auto processor = logger_provider_.lock()->GetProcessor(); + if (processor == nullptr) + { + return; + } + + // TODO: Sampler logic (should include check for minSeverity) + + /** + * Convert the LogRecord to the heap first before sending to processor. + * TODO: Change the API log(LogRecord) function to log(*LogRecord) so the following line + * converting record a heap variable can be removed + */ + auto record_pointer = + 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 + 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)); +} + +} // namespace logs +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/logs/logger_provider.cc b/sdk/src/logs/logger_provider.cc new file mode 100644 index 0000000000..b040f9baab --- /dev/null +++ b/sdk/src/logs/logger_provider.cc @@ -0,0 +1,81 @@ +/* + * 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/sdk/logs/logger_provider.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace logs +{ + +LoggerProvider::LoggerProvider() noexcept : processor_{nullptr} {} + +opentelemetry::nostd::shared_ptr LoggerProvider::GetLogger( + opentelemetry::nostd::string_view name, + opentelemetry::nostd::string_view options) noexcept +{ + // Ensure only one thread can read/write from the map of loggers + std::lock_guard lock_guard{mu_}; + + // If a logger with a name "name" already exists, return it + auto loggerkv = loggers_.find(name.data()); + if (loggerkv != loggers_.end()) + { + return opentelemetry::nostd::shared_ptr(loggerkv->second); + } + + // Check if creating a new logger would exceed the max number of loggers + // TODO: Remove the noexcept from the API's and SDK's GetLogger(~) + /* + if (loggers_.size() > MAX_LOGGER_COUNT) + { +#if __EXCEPTIONS + throw std::length_error("Number of loggers exceeds max count"); +#else + std::terminate(); +#endif + } + */ + + // If no logger with that name exists yet, create it and add it to the map of loggers + + opentelemetry::nostd::shared_ptr logger( + new Logger(this->shared_from_this())); + loggers_[name.data()] = logger; + return logger; +} + +opentelemetry::nostd::shared_ptr LoggerProvider::GetLogger( + opentelemetry::nostd::string_view name, + nostd::span args) noexcept +{ + // Currently, no args support + return GetLogger(name); +} + +std::shared_ptr LoggerProvider::GetProcessor() noexcept +{ + return processor_.load(); +} + +void LoggerProvider::SetProcessor(std::shared_ptr processor) noexcept +{ + processor_.store(processor); +} +} // namespace logs +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/test/CMakeLists.txt b/sdk/test/CMakeLists.txt index 607e98bc99..633f9c3465 100644 --- a/sdk/test/CMakeLists.txt +++ b/sdk/test/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(common) add_subdirectory(trace) add_subdirectory(metrics) +add_subdirectory(logs) diff --git a/sdk/test/logs/BUILD b/sdk/test/logs/BUILD new file mode 100644 index 0000000000..b58eac0eee --- /dev/null +++ b/sdk/test/logs/BUILD @@ -0,0 +1,22 @@ +cc_test( + name = "logger_provider_sdk_test", + srcs = [ + "logger_provider_sdk_test.cc", + ], + deps = [ + "//api", + "//sdk/src/logs", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "logger_sdk_test", + srcs = [ + "logger_sdk_test.cc", + ], + deps = [ + "//sdk/src/logs", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sdk/test/logs/CMakeLists.txt b/sdk/test/logs/CMakeLists.txt new file mode 100644 index 0000000000..87f7ae9a96 --- /dev/null +++ b/sdk/test/logs/CMakeLists.txt @@ -0,0 +1,6 @@ +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 new file mode 100644 index 0000000000..a2a020c838 --- /dev/null +++ b/sdk/test/logs/logger_provider_sdk_test.cc @@ -0,0 +1,87 @@ +/* + * 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/logs/provider.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/sdk/logs/logger.h" +#include "opentelemetry/sdk/logs/logger_provider.h" + +#include + +using namespace opentelemetry::sdk::logs; + +TEST(LoggerProviderSDK, PushToAPI) +{ + auto lp = opentelemetry::nostd::shared_ptr( + new opentelemetry::sdk::logs::LoggerProvider()); + opentelemetry::logs::Provider::SetLoggerProvider(lp); + + // Check that the loggerprovider was correctly pushed into the API + ASSERT_EQ(lp, opentelemetry::logs::Provider::GetLoggerProvider()); +} + +TEST(LoggerProviderSDK, LoggerProviderGetLoggerSimple) +{ + auto lp = std::shared_ptr(new LoggerProvider()); + + auto logger1 = lp->GetLogger("logger1"); + auto logger2 = lp->GetLogger("logger2"); + + // Check that the logger is not nullptr + ASSERT_NE(logger1, nullptr); + ASSERT_NE(logger2, nullptr); + + // Check that two loggers with different names aren't the same instance + ASSERT_NE(logger1, logger2); + + // Check that two loggers with the same name are the same instance + auto logger3 = lp->GetLogger("logger1"); + ASSERT_EQ(logger1, logger3); +} + +TEST(LoggerProviderSDK, LoggerProviderLoggerArguments) +{ + // Currently, arguments are not supported by the loggers. + // TODO: Once the logging spec defines what arguments are allowed, add more + // detail to this test + auto lp = std::shared_ptr(new LoggerProvider()); + + auto logger1 = lp->GetLogger("logger1", ""); + + // Check GetLogger(logger_name, args) + std::array sv{"string"}; + opentelemetry::nostd::span args{sv}; + auto logger2 = lp->GetLogger("logger2", args); +} + +class DummyProcessor : public LogProcessor +{ + void OnReceive(std::unique_ptr &&record) noexcept {} + void ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept {} + void Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept {} +}; + +TEST(LoggerProviderSDK, GetAndSetProcessor) +{ + // Create a LoggerProvider without a processor + LoggerProvider lp; + ASSERT_EQ(lp.GetProcessor(), nullptr); + + // Create a new processor and check if it is pushed correctly + std::shared_ptr proc2 = std::shared_ptr(new DummyProcessor()); + lp.SetProcessor(proc2); + ASSERT_EQ(proc2, lp.GetProcessor()); +} diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc new file mode 100644 index 0000000000..6c36b68c0c --- /dev/null +++ b/sdk/test/logs/logger_sdk_test.cc @@ -0,0 +1,71 @@ +/* + * 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/sdk/logs/logger.h" + +#include + +using namespace opentelemetry::sdk::logs; + +TEST(LoggerSDK, LogToNullProcessor) +{ + // Confirm Logger::log() does not have undefined behavior + // even when there is no processor set + // since it calls Processor::OnReceive() + + auto lp = std::shared_ptr(new LoggerProvider()); + auto logger = lp->GetLogger("logger"); + + // Log a sample log record to a nullptr processor + opentelemetry::logs::LogRecord r; + r.name = "Test log"; + logger->log(r); +} + +class DummyProcessor : public LogProcessor +{ + void OnReceive(std::unique_ptr &&record) noexcept {} + void ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept {} + void Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept {} +}; + +TEST(LoggerSDK, LogToAProcessor) +{ + // Create an API LoggerProvider and logger + auto api_lp = std::shared_ptr(new LoggerProvider()); + auto logger = api_lp->GetLogger("logger"); + + // Cast the API LoggerProvider to an SDK Logger Provider and assert that it is still the same + // LoggerProvider by checking that getting a logger with the same name as the previously defined + // logger is the same instance + auto lp = static_cast(api_lp.get()); + auto logger2 = lp->GetLogger("logger"); + ASSERT_EQ(logger, logger2); + + // Set a processor for the LoggerProvider + std::shared_ptr processor = std::shared_ptr(new DummyProcessor()); + lp->SetProcessor(processor); + ASSERT_EQ(processor, lp->GetProcessor()); + + // Should later introduce a way to assert that + // the logger's processor is the same as "proc" + // and that the logger's processor is the same as lp's processor + + // Log a sample log record to the processor + opentelemetry::logs::LogRecord r; + r.name = "Test log"; + logger->log(r); +}