diff --git a/api/include/opentelemetry/logs/TBD b/api/include/opentelemetry/logs/TBD deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/api/include/opentelemetry/logs/log_record.h b/api/include/opentelemetry/logs/log_record.h new file mode 100644 index 0000000000..c663c1c9c7 --- /dev/null +++ b/api/include/opentelemetry/logs/log_record.h @@ -0,0 +1,125 @@ +/* + * 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/common/key_value_iterable_view.h" +#include "opentelemetry/core/timestamp.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_flags.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace logs +{ + +/* Note: using a class enum here won't allow enum values to be compared to integers, i.e. only other + * Severity enums (need an explicit cast) + * Follows the Google standard for naming: + * https://google.github.io/styleguide/cppguide.html#Enumerator_Names + */ +enum class Severity : uint8_t +{ + kTrace = 1, + kTrace2 = 2, + kTrace3 = 3, + kTrace4 = 4, + kDebug = 5, + kDebug2 = 6, + kDebug3 = 7, + kDebug4 = 8, + kInfo = 9, + kInfo2 = 10, + kInfo3 = 11, + kInfo4 = 12, + kWarn = 13, + kWarn2 = 14, + kWarn3 = 15, + kWarn4 = 16, + kError = 17, + kError2 = 18, + kError3 = 19, + kError4 = 20, + kFatal = 21, + kFatal2 = 22, + kFatal3 = 23, + kFatal4 = 24, + 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. + * (https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/logs/data-model.md#log-and-event-record-definition) + * + */ +struct LogRecord +{ + // default fields that will be set if the user doesn't specify them + 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 + 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() : 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 */ + template ::value> * = nullptr> + inline void SetResource(const T &_resource) + { + resource = common::KeyValueIterableView(_resource); + } + + /* for ease of use; user can use this function to convert a map into a KeyValueIterable for the + * attributes field */ + template ::value> * = nullptr> + inline void SetAttributes(const T &_attributes) + { + attributes = common::KeyValueIterableView(_attributes); + } +}; +} // namespace logs +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h new file mode 100644 index 0000000000..5f22066460 --- /dev/null +++ b/api/include/opentelemetry/logs/logger.h @@ -0,0 +1,123 @@ +/* + * 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 "opentelemetry/common/key_value_iterable.h" +#include "opentelemetry/logs/log_record.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace logs +{ +/** + * Handles log record creation. + **/ +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. + * + * @param name the name of the log event. + * @param severity the severity level of the log event. + * @param message the string message of the log (perhaps support std::fmt or fmt-lib format). + * @param record the log record (object type LogRecord) that is logged. + * @param attributes the attributes, stored as a 2D list of key/value pairs, that are associated + * with this log. + * @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 + */ + 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); + } + + 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())); + } + + inline void log(Severity severity, + nostd::string_view message, + core::SystemTimestamp time) noexcept + { + // creates a LogRecord object with given parameters, then calls log(LogRecord) + LogRecord r; + r.severity = severity; + r.body = message; + r.timestamp = time; + + 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 + template ::value> * = nullptr> + inline void log(Severity severity = Severity::kDefault, + nostd::string_view name = "", + const T &attributes = {}) noexcept + { + log(severity, name, common::KeyValueIterableView(attributes)); + } + + inline void log(Severity severity, + nostd::string_view name, + const common::KeyValueIterable &attributes) noexcept + { + // creates a LogRecord object with given parameters, then calls log(LogRecord) + LogRecord r; + r.severity = severity; + r.name = name; + r.attributes = attributes; + + 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 diff --git a/api/include/opentelemetry/logs/logger_provider.h b/api/include/opentelemetry/logs/logger_provider.h new file mode 100644 index 0000000000..b0ca7303dc --- /dev/null +++ b/api/include/opentelemetry/logs/logger_provider.h @@ -0,0 +1,52 @@ +/* + * 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/logger.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace logs +{ +/** + * Creates new Logger instances. + */ +class LoggerProvider +{ +public: + virtual ~LoggerProvider() = default; + + /** + * Gets or creates a named Logger instance. + * + * Optionally a version can be passed to create a named and versioned Logger + * instance. + * + * Optionally a configuration file name can be passed to create a configuration for + * the Logger instance. + * + */ + + virtual nostd::shared_ptr GetLogger(nostd::string_view logger_name, + nostd::string_view options = "") = 0; + + virtual nostd::shared_ptr GetLogger(nostd::string_view logger_name, + nostd::span args) = 0; +}; +} // namespace logs +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/logs/noop.h b/api/include/opentelemetry/logs/noop.h new file mode 100644 index 0000000000..3184baa3c6 --- /dev/null +++ b/api/include/opentelemetry/logs/noop.h @@ -0,0 +1,74 @@ +/* + * 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 +// Please refer to provider.h for documentation on how to obtain a Logger object. +// +// This file is part of the internal implementation of OpenTelemetry. Nothing in this file should be +// used directly. Please refer to logger.h for documentation on these interfaces. + +#include + +#include "opentelemetry/context/runtime_context.h" +#include "opentelemetry/logs/logger.h" +#include "opentelemetry/logs/logger_provider.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace logs +{ +/** + * No-op implementation of Logger. This class should not be used directly. It should only be + * instantiated using a LoggerProvider's GetLogger() call. + */ +class NoopLogger final : public Logger +{ +public: + NoopLogger() = default; + + void log(const LogRecord &record) noexcept override {} +}; + +/** + * No-op implementation of a LoggerProvider. + */ +class NoopLoggerProvider final : public opentelemetry::logs::LoggerProvider +{ +public: + NoopLoggerProvider() + : logger_{ + nostd::shared_ptr(new opentelemetry::logs::NoopLogger)} + {} + + nostd::shared_ptr GetLogger(nostd::string_view logger_name, + nostd::string_view options) override + { + return logger_; + } + + nostd::shared_ptr GetLogger(nostd::string_view logger_name, + nostd::span args) override + { + return logger_; + } + +private: + nostd::shared_ptr logger_; +}; +} // namespace logs +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/logs/provider.h b/api/include/opentelemetry/logs/provider.h new file mode 100644 index 0000000000..ab8e779b20 --- /dev/null +++ b/api/include/opentelemetry/logs/provider.h @@ -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. + */ + +#pragma once + +#include + +#include "opentelemetry/common/spin_lock_mutex.h" +#include "opentelemetry/logs/logger_provider.h" +#include "opentelemetry/logs/noop.h" +#include "opentelemetry/nostd/shared_ptr.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace logs +{ +/** + * Stores the singleton global LoggerProvider. + */ +class Provider +{ +public: + /** + * Returns the singleton LoggerProvider. + * + * By default, a no-op LoggerProvider is returned. This will never return a + * nullptr LoggerProvider. + */ + static nostd::shared_ptr GetLoggerProvider() noexcept + { + std::lock_guard guard(GetLock()); + return nostd::shared_ptr(GetProvider()); + } + + /** + * Changes the singleton LoggerProvider. + */ + static void SetLoggerProvider(nostd::shared_ptr tp) noexcept + { + std::lock_guard guard(GetLock()); + GetProvider() = tp; + } + +private: + static nostd::shared_ptr &GetProvider() noexcept + { + static nostd::shared_ptr provider(new NoopLoggerProvider); + return provider; + } + + static common::SpinLockMutex &GetLock() noexcept + { + static common::SpinLockMutex lock; + return lock; + } +}; + +} // namespace logs +OPENTELEMETRY_END_NAMESPACE diff --git a/api/test/CMakeLists.txt b/api/test/CMakeLists.txt index 11cf6d1319..05ed4f5720 100644 --- a/api/test/CMakeLists.txt +++ b/api/test/CMakeLists.txt @@ -4,3 +4,4 @@ add_subdirectory(plugin) add_subdirectory(nostd) add_subdirectory(trace) add_subdirectory(metrics) +add_subdirectory(logs) diff --git a/api/test/logs/BUILD b/api/test/logs/BUILD new file mode 100644 index 0000000000..899400b2e8 --- /dev/null +++ b/api/test/logs/BUILD @@ -0,0 +1,23 @@ +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + +cc_test( + name = "logger_provider_test", + srcs = [ + "logger_provider_test.cc", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "logger_test", + srcs = [ + "logger_test.cc", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/api/test/logs/CMakeLists.txt b/api/test/logs/CMakeLists.txt new file mode 100644 index 0000000000..737edb893c --- /dev/null +++ b/api/test/logs/CMakeLists.txt @@ -0,0 +1,6 @@ +foreach(testname logger_provider_test logger_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) + gtest_add_tests(TARGET ${testname} TEST_PREFIX logs. TEST_LIST ${testname}) +endforeach() diff --git a/api/test/logs/logger_provider_test.cc b/api/test/logs/logger_provider_test.cc new file mode 100644 index 0000000000..91fd8f8c15 --- /dev/null +++ b/api/test/logs/logger_provider_test.cc @@ -0,0 +1,65 @@ +#include +#include + +#include "opentelemetry/logs/provider.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" + +using opentelemetry::logs::Logger; +using opentelemetry::logs::LoggerProvider; +using opentelemetry::logs::Provider; +using opentelemetry::nostd::shared_ptr; +using opentelemetry::nostd::span; +using opentelemetry::nostd::string_view; + +class TestProvider : public LoggerProvider +{ + shared_ptr GetLogger(string_view library_name, string_view options) override + { + return shared_ptr(nullptr); + } + + shared_ptr GetLogger(string_view library_name, span args) override + { + return shared_ptr(nullptr); + } +}; + +TEST(Provider, GetLoggerProviderDefault) +{ + auto tf = Provider::GetLoggerProvider(); + EXPECT_NE(nullptr, tf); +} + +TEST(Provider, SetLoggerProvider) +{ + auto tf = shared_ptr(new TestProvider()); + Provider::SetLoggerProvider(tf); + ASSERT_EQ(tf, Provider::GetLoggerProvider()); +} + +TEST(Provider, MultipleLoggerProviders) +{ + auto tf = shared_ptr(new TestProvider()); + Provider::SetLoggerProvider(tf); + auto tf2 = shared_ptr(new TestProvider()); + Provider::SetLoggerProvider(tf2); + + ASSERT_NE(Provider::GetLoggerProvider(), tf); +} + +TEST(Provider, GetLogger) +{ + auto tf = shared_ptr(new TestProvider()); + // tests GetLogger(name, options) + auto logger = tf->GetLogger("logger1"); + EXPECT_EQ(nullptr, logger); + + // tests GetLogger(name, arguments) + + std::array sv{"string"}; + span args{sv}; + auto logger2 = tf->GetLogger("logger2", args); + EXPECT_EQ(nullptr, logger2); +} diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc new file mode 100644 index 0000000000..937ece860a --- /dev/null +++ b/api/test/logs/logger_test.cc @@ -0,0 +1,86 @@ +#include +#include + +#include "opentelemetry/logs/logger.h" +#include "opentelemetry/logs/provider.h" +#include "opentelemetry/nostd/shared_ptr.h" + +using opentelemetry::common::KeyValueIterable; +using opentelemetry::logs::Logger; +using opentelemetry::logs::LoggerProvider; +using opentelemetry::logs::LogRecord; +using opentelemetry::logs::Provider; +using opentelemetry::logs::Severity; +using opentelemetry::nostd::shared_ptr; +using opentelemetry::nostd::span; +using opentelemetry::nostd::string_view; + +TEST(Logger, GetLoggerDefault) +{ + auto lp = Provider::GetLoggerProvider(); + auto logger = lp->GetLogger("TestLogger"); + EXPECT_NE(nullptr, logger); +} + +TEST(Logger, GetNoopLoggerName) +{ + auto lp = Provider::GetLoggerProvider(); + auto logger = lp->GetLogger("TestLogger"); +} + +TEST(Logger, GetNoopLoggerNameWithArgs) +{ + auto lp = Provider::GetLoggerProvider(); + + std::array sv{"string"}; + span args{sv}; + auto logger = lp->GetLogger("NoopLoggerWithArgs", args); + // should probably also test that arguments were set properly too + // by adding a getArgs() method in NoopLogger +} + +TEST(Logger, NoopLog) +{ + auto lp = Provider::GetLoggerProvider(); + auto logger = lp->GetLogger("TestLogger"); + LogRecord r; + r.name = "Noop log name"; + logger->log(r); +} + +// Define a basic Logger class +class TestLogger : public Logger +{ + void log(const LogRecord &record) noexcept override {} +}; + +// Define a basic LoggerProvider class that returns an instance of the logger class defined above +class TestProvider : public LoggerProvider +{ + shared_ptr GetLogger(string_view library_name, string_view options = "") override + { + return shared_ptr(new TestLogger()); + } + + shared_ptr GetLogger(string_view library_name, span args) override + { + return shared_ptr(new TestLogger()); + } +}; + +TEST(Logger, PushLoggerImplementation) +{ + // Push the new loggerprovider class into the global singleton + auto test_provider = shared_ptr(new TestProvider()); + Provider::SetLoggerProvider(test_provider); + + auto lp = Provider::GetLoggerProvider(); + + // GetLogger(name, options) function + auto logger = lp->GetLogger("TestLogger"); + + // GetLogger(name, args) function + std::array sv{"string"}; + span args{sv}; + auto logger2 = lp->GetLogger("TestLogger2", args); +}