diff --git a/sdk/include/opentelemetry/sdk/configuration/configuration.h b/sdk/include/opentelemetry/sdk/configuration/configuration.h index d24cffe8bf..7629c28bcf 100644 --- a/sdk/include/opentelemetry/sdk/configuration/configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/configuration.h @@ -34,6 +34,15 @@ * Yaml node OpenTelemetryConfiguration, * in file * https://github.com/open-telemetry/opentelemetry-configuration/blob/main/schema/opentelemetry_configuration.json + * + * Every property in the yaml schema is already documented in the + * opentelemetry-configuration repository, + * in file schema/type_descriptions.yaml, see + * https://github.com/open-telemetry/opentelemetry-configuration/blob/main/schema/type_descriptions.yaml + * + * As a result, C++ class members representing yaml properties are not + * commented with details, refer to the source of truth in + * type_descriptions.yaml directly. */ OPENTELEMETRY_BEGIN_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h b/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h new file mode 100644 index 0000000000..cd4f6e3af5 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/configuration/configuration_parser.h @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "opentelemetry/sdk/configuration/configuration.h" +#include "opentelemetry/sdk/configuration/document_node.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +class ConfigurationParser +{ +public: + static std::unique_ptr Parse(std::unique_ptr doc); +}; + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/configuration/document.h b/sdk/include/opentelemetry/sdk/configuration/document.h new file mode 100644 index 0000000000..f444279f8e --- /dev/null +++ b/sdk/include/opentelemetry/sdk/configuration/document.h @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "opentelemetry/sdk/configuration/document_node.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +class Document +{ +public: + Document() = default; + Document(Document &&) = default; + Document(const Document &) = default; + Document &operator=(Document &&) = default; + Document &operator=(const Document &other) = default; + virtual ~Document() = default; + + virtual std::unique_ptr GetRootNode() = 0; +}; + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/configuration/document_node.h b/sdk/include/opentelemetry/sdk/configuration/document_node.h new file mode 100644 index 0000000000..1c3f4bef9f --- /dev/null +++ b/sdk/include/opentelemetry/sdk/configuration/document_node.h @@ -0,0 +1,177 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +class DocumentNodeConstIterator; +class PropertiesNodeConstIterator; + +class DocumentNode +{ +public: + // FIXME: proper sizing + static constexpr std::size_t MAX_NODE_DEPTH = 100; + + DocumentNode() = default; + DocumentNode(DocumentNode &&) = default; + DocumentNode(const DocumentNode &) = default; + DocumentNode &operator=(DocumentNode &&) = default; + DocumentNode &operator=(const DocumentNode &other) = default; + virtual ~DocumentNode() = default; + + virtual std::string Key() const = 0; + + virtual bool AsBoolean() const = 0; + virtual std::size_t AsInteger() const = 0; + virtual double AsDouble() const = 0; + virtual std::string AsString() const = 0; + + virtual std::unique_ptr GetRequiredChildNode(const std::string &name) const = 0; + virtual std::unique_ptr GetChildNode(const std::string &name) const = 0; + + virtual bool GetRequiredBoolean(const std::string &name) const = 0; + virtual bool GetBoolean(const std::string &name, bool default_value) const = 0; + + virtual std::size_t GetRequiredInteger(const std::string &name) const = 0; + virtual std::size_t GetInteger(const std::string &name, std::size_t default_value) const = 0; + + virtual double GetRequiredDouble(const std::string &name) const = 0; + virtual double GetDouble(const std::string &name, double default_value) const = 0; + + virtual std::string GetRequiredString(const std::string &name) const = 0; + virtual std::string GetString(const std::string &name, + const std::string &default_value) const = 0; + + virtual DocumentNodeConstIterator begin() const = 0; + virtual DocumentNodeConstIterator end() const = 0; + + virtual std::size_t num_children() const = 0; + virtual std::unique_ptr GetChild(std::size_t index) const = 0; + + virtual PropertiesNodeConstIterator begin_properties() const = 0; + virtual PropertiesNodeConstIterator end_properties() const = 0; + +protected: + std::string DoSubstitution(const std::string &text) const; + std::string DoOneSubstitution(const std::string &text) const; + + bool BooleanFromString(const std::string &value) const; + std::size_t IntegerFromString(const std::string &value) const; + double DoubleFromString(const std::string &value) const; +}; + +class DocumentNodeConstIteratorImpl +{ +public: + DocumentNodeConstIteratorImpl() = default; + DocumentNodeConstIteratorImpl(DocumentNodeConstIteratorImpl &&) = default; + DocumentNodeConstIteratorImpl(const DocumentNodeConstIteratorImpl &) = default; + DocumentNodeConstIteratorImpl &operator=(DocumentNodeConstIteratorImpl &&) = default; + DocumentNodeConstIteratorImpl &operator=(const DocumentNodeConstIteratorImpl &other) = default; + virtual ~DocumentNodeConstIteratorImpl() = default; + + virtual void Next() = 0; + virtual std::unique_ptr Item() const = 0; + virtual bool Equal(const DocumentNodeConstIteratorImpl *rhs) const = 0; +}; + +class PropertiesNodeConstIteratorImpl +{ +public: + PropertiesNodeConstIteratorImpl() = default; + PropertiesNodeConstIteratorImpl(PropertiesNodeConstIteratorImpl &&) = default; + PropertiesNodeConstIteratorImpl(const PropertiesNodeConstIteratorImpl &) = default; + PropertiesNodeConstIteratorImpl &operator=(PropertiesNodeConstIteratorImpl &&) = default; + PropertiesNodeConstIteratorImpl &operator=(const PropertiesNodeConstIteratorImpl &other) = + default; + virtual ~PropertiesNodeConstIteratorImpl() = default; + + virtual void Next() = 0; + virtual std::string Name() const = 0; + virtual std::unique_ptr Value() const = 0; + virtual bool Equal(const PropertiesNodeConstIteratorImpl *rhs) const = 0; +}; + +class DocumentNodeConstIterator +{ +public: + DocumentNodeConstIterator(std::unique_ptr impl) + : impl_(std::move(impl)) + {} + DocumentNodeConstIterator(DocumentNodeConstIterator &&) = default; + DocumentNodeConstIterator(const DocumentNodeConstIterator &) = delete; + DocumentNodeConstIterator &operator=(DocumentNodeConstIterator &&) = default; + DocumentNodeConstIterator &operator=(const DocumentNodeConstIterator &other) = delete; + ~DocumentNodeConstIterator() = default; + + bool operator==(const DocumentNodeConstIterator &rhs) const + { + return (impl_->Equal(rhs.impl_.get())); + } + + bool operator!=(const DocumentNodeConstIterator &rhs) const + { + return (!impl_->Equal(rhs.impl_.get())); + } + + std::unique_ptr operator*() const { return impl_->Item(); } + + DocumentNodeConstIterator &operator++() + { + impl_->Next(); + return *this; + } + +private: + std::unique_ptr impl_; +}; + +class PropertiesNodeConstIterator +{ +public: + PropertiesNodeConstIterator(std::unique_ptr impl) + : impl_(std::move(impl)) + {} + PropertiesNodeConstIterator(PropertiesNodeConstIterator &&) = default; + PropertiesNodeConstIterator(const PropertiesNodeConstIterator &) = delete; + PropertiesNodeConstIterator &operator=(PropertiesNodeConstIterator &&) = default; + PropertiesNodeConstIterator &operator=(const PropertiesNodeConstIterator &other) = delete; + ~PropertiesNodeConstIterator() = default; + + bool operator==(const PropertiesNodeConstIterator &rhs) const + { + return (impl_->Equal(rhs.impl_.get())); + } + + bool operator!=(const PropertiesNodeConstIterator &rhs) const + { + return (!impl_->Equal(rhs.impl_.get())); + } + + std::string Name() const { return impl_->Name(); } + std::unique_ptr Value() const { return impl_->Value(); } + + PropertiesNodeConstIterator &operator++() + { + impl_->Next(); + return *this; + } + +private: + std::unique_ptr impl_; +}; + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/configuration/invalid_schema_exception.h b/sdk/include/opentelemetry/sdk/configuration/invalid_schema_exception.h new file mode 100644 index 0000000000..c6abe648b3 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/configuration/invalid_schema_exception.h @@ -0,0 +1,27 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "opentelemetry/sdk/configuration/document.h" +#include "opentelemetry/sdk/configuration/document_node.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +class InvalidSchemaException : public std::runtime_error +{ +public: + InvalidSchemaException(const std::string &msg) : std::runtime_error(msg) {} +}; + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/configuration/ryml_document.h b/sdk/include/opentelemetry/sdk/configuration/ryml_document.h new file mode 100644 index 0000000000..8fa8ca13d0 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/configuration/ryml_document.h @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +#include "opentelemetry/sdk/configuration/document.h" +#include "opentelemetry/sdk/configuration/document_node.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +class RymlDocument : public Document +{ +public: + static std::unique_ptr Parse(const std::string &source, const std::string &content); + + RymlDocument(ryml::Tree tree) : tree_(std::move(tree)) {} + RymlDocument(RymlDocument &&) = delete; + RymlDocument(const RymlDocument &) = delete; + RymlDocument &operator=(RymlDocument &&) = delete; + RymlDocument &operator=(const RymlDocument &other) = delete; + ~RymlDocument() override = default; + + std::unique_ptr GetRootNode() override; + +private: + ryml::Tree tree_; +}; + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h b/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h new file mode 100644 index 0000000000..d524f57a21 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/configuration/ryml_document_node.h @@ -0,0 +1,118 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +#include "opentelemetry/sdk/configuration/document_node.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +class RymlDocumentNode : public DocumentNode +{ +public: + RymlDocumentNode(ryml::ConstNodeRef node, std::size_t depth) : node_(node), depth_(depth) {} + RymlDocumentNode(RymlDocumentNode &&) = delete; + RymlDocumentNode(const RymlDocumentNode &) = delete; + RymlDocumentNode &operator=(RymlDocumentNode &&) = delete; + RymlDocumentNode &operator=(const RymlDocumentNode &other) = delete; + ~RymlDocumentNode() override = default; + + std::string Key() const override; + + bool AsBoolean() const override; + std::size_t AsInteger() const override; + double AsDouble() const override; + std::string AsString() const override; + + std::unique_ptr GetRequiredChildNode(const std::string &name) const override; + std::unique_ptr GetChildNode(const std::string &name) const override; + + bool GetRequiredBoolean(const std::string &name) const override; + bool GetBoolean(const std::string &name, bool default_value) const override; + + std::size_t GetRequiredInteger(const std::string &name) const override; + std::size_t GetInteger(const std::string &name, std::size_t default_value) const override; + + double GetRequiredDouble(const std::string &name) const override; + double GetDouble(const std::string &name, double default_value) const override; + + std::string GetRequiredString(const std::string &name) const override; + std::string GetString(const std::string &name, const std::string &default_value) const override; + + DocumentNodeConstIterator begin() const override; + DocumentNodeConstIterator end() const override; + + std::size_t num_children() const override; + std::unique_ptr GetChild(std::size_t index) const override; + + PropertiesNodeConstIterator begin_properties() const override; + PropertiesNodeConstIterator end_properties() const override; + +private: + ryml::ConstNodeRef GetRequiredRymlChildNode(const std::string &name) const; + ryml::ConstNodeRef GetRymlChildNode(const std::string &name) const; + + ryml::ConstNodeRef node_; + std::size_t depth_; +}; + +class RymlDocumentNodeConstIteratorImpl : public DocumentNodeConstIteratorImpl +{ +public: + RymlDocumentNodeConstIteratorImpl(ryml::ConstNodeRef parent, + std::size_t index, + std::size_t depth); + RymlDocumentNodeConstIteratorImpl(RymlDocumentNodeConstIteratorImpl &&) = delete; + RymlDocumentNodeConstIteratorImpl(const RymlDocumentNodeConstIteratorImpl &) = delete; + RymlDocumentNodeConstIteratorImpl &operator=(RymlDocumentNodeConstIteratorImpl &&) = delete; + RymlDocumentNodeConstIteratorImpl &operator=(const RymlDocumentNodeConstIteratorImpl &other) = + delete; + ~RymlDocumentNodeConstIteratorImpl() override; + + void Next() override; + std::unique_ptr Item() const override; + bool Equal(const DocumentNodeConstIteratorImpl *rhs) const override; + +private: + ryml::ConstNodeRef parent_; + std::size_t index_; + std::size_t depth_; +}; + +class RymlPropertiesNodeConstIteratorImpl : public PropertiesNodeConstIteratorImpl +{ +public: + RymlPropertiesNodeConstIteratorImpl(ryml::ConstNodeRef parent, + std::size_t index, + std::size_t depth); + RymlPropertiesNodeConstIteratorImpl(RymlPropertiesNodeConstIteratorImpl &&) = delete; + RymlPropertiesNodeConstIteratorImpl(const RymlPropertiesNodeConstIteratorImpl &) = delete; + RymlPropertiesNodeConstIteratorImpl &operator=(RymlPropertiesNodeConstIteratorImpl &&) = delete; + RymlPropertiesNodeConstIteratorImpl &operator=(const RymlPropertiesNodeConstIteratorImpl &other) = + delete; + ~RymlPropertiesNodeConstIteratorImpl() override; + + void Next() override; + std::string Name() const override; + std::unique_ptr Value() const override; + bool Equal(const PropertiesNodeConstIteratorImpl *rhs) const override; + +private: + ryml::ConstNodeRef parent_; + std::size_t index_; + std::size_t depth_; +}; + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/configuration/yaml_configuration_parser.h b/sdk/include/opentelemetry/sdk/configuration/yaml_configuration_parser.h new file mode 100644 index 0000000000..e5a326b88b --- /dev/null +++ b/sdk/include/opentelemetry/sdk/configuration/yaml_configuration_parser.h @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include "opentelemetry/sdk/configuration/configuration.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +class YamlConfigurationParser +{ +public: + static std::unique_ptr ParseFile(const std::string &filename); + static std::unique_ptr ParseString(const std::string &source, + const std::string &content); +}; + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/configuration/document_node.cc b/sdk/src/configuration/document_node.cc new file mode 100644 index 0000000000..bc2bc76f0f --- /dev/null +++ b/sdk/src/configuration/document_node.cc @@ -0,0 +1,271 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include + +#include "opentelemetry/sdk/common/env_variables.h" +#include "opentelemetry/sdk/configuration/document_node.h" +#include "opentelemetry/sdk/configuration/invalid_schema_exception.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +/** + * Perform environment variables substitution on a full line. + * Supported: + * - Text line with no substitution + * - ${SUBSTITUTION} + * - Some text with ${SUBSTITUTION} in it + * - Multiple ${SUBSTITUTION_A} substitutions ${SUBSTITUTION_B} in the line + */ +std::string DocumentNode::DoSubstitution(const std::string &text) const +{ + static std::string begin_token{"${"}; + static std::string end_token{"}"}; + + std::string result; + + std::string::size_type current_pos; + std::string::size_type begin_pos; + std::string::size_type end_pos; + std::string copy; + std::string substitution; + + current_pos = 0; + begin_pos = text.find(begin_token); + + while (begin_pos != std::string::npos) + { + if (current_pos < begin_pos) + { + /* + * ^ < COPY_ME > ${... + * ...} < COPY_ME > ${... + * + * copy [current_pos, begin_pos[ + */ + copy = text.substr(current_pos, begin_pos - current_pos); + result.append(copy); + current_pos = begin_pos; + } + + end_pos = text.find(end_token, begin_pos); + + if (end_pos == std::string::npos) + { + /* ... < ${NOT_A_SUBSTITUTION > */ + copy = text.substr(current_pos); + result.append(copy); + return result; + } + + /* ... < ${SUBSTITUTION} > ... */ + substitution = text.substr(current_pos, end_pos - current_pos + 1); + + copy = DoOneSubstitution(substitution); + result.append(copy); + begin_pos = text.find(begin_token, end_pos); + current_pos = end_pos + 1; + } + + copy = text.substr(current_pos); + result.append(copy); + + return result; +} + +/** + Perform one substitution on a string scalar. + Supported: + - ${ENV_NAME} + - ${env:ENV_NAME} + - ${ENV_NAME:-fallback} (including when ENV_NAME is actually "env") + - ${env:ENV_NAME:-fallback} +*/ +std::string DocumentNode::DoOneSubstitution(const std::string &text) const +{ + static std::string illegal_msg{"Illegal substitution expression: "}; + + static std::string env_token{"env:"}; + static std::string env_with_replacement{"env:-"}; + static std::string replacement_token{":-"}; + + size_t len = text.length(); + char c; + std::string::size_type begin_name; + std::string::size_type end_name; + std::string::size_type begin_fallback; + std::string::size_type end_fallback; + std::string::size_type pos; + std::string name; + std::string fallback; + std::string sub; + bool env_exists; + + if (len < 4) + { + std::string message = illegal_msg; + message.append(text); + throw InvalidSchemaException(message); + } + + c = text[0]; + if (c != '$') + { + std::string message = illegal_msg; + message.append(text); + throw InvalidSchemaException(message); + } + + c = text[1]; + if (c != '{') + { + std::string message = illegal_msg; + message.append(text); + throw InvalidSchemaException(message); + } + + c = text[len - 1]; + if (c != '}') + { + std::string message = illegal_msg; + message.append(text); + throw InvalidSchemaException(message); + } + + begin_name = 2; + + /* If text[2] starts with "env:" */ + if (text.find(env_token, begin_name) == begin_name) + { + /* If text[2] starts with "env:-" */ + if (text.find(env_with_replacement, begin_name) == begin_name) + { + /* ${env:-fallback} is legal. It is variable "env". */ + } + else + { + begin_name += env_token.length(); // Skip "env:" + } + } + + c = text[begin_name]; + if (!std::isalpha(c) && c != '_') + { + std::string message = illegal_msg; + message.append(text); + throw InvalidSchemaException(message); + } + + end_name = begin_name + 1; + + for (size_t i = begin_name + 1; i + 2 <= len; i++) + { + c = text[i]; + if (std::isalnum(c) || (c == '_')) + { + end_name = i + 1; + } + else + { + break; + } + } + + // text is of the form ${ENV_NAME ... + + name = text.substr(begin_name, end_name - begin_name); + + if (end_name + 1 == len) + { + // text is of the form ${ENV_NAME} + begin_fallback = 0; + end_fallback = 0; + } + else + { + pos = text.find(replacement_token, end_name); + if (pos != end_name) + { + std::string message = illegal_msg; + message.append(text); + throw InvalidSchemaException(message); + } + // text is of the form ${ENV_NAME:-fallback} + begin_fallback = pos + replacement_token.length(); + end_fallback = len - 1; + } + + env_exists = sdk::common::GetStringEnvironmentVariable(name.c_str(), sub); + if (env_exists) + { + return sub; + } + + if (begin_fallback < end_fallback) + { + fallback = text.substr(begin_fallback, end_fallback - begin_fallback); + } + else + { + fallback = ""; + } + return fallback; +} + +bool DocumentNode::BooleanFromString(const std::string &value) const +{ + if (value == "true") + { + return true; + } + + if (value == "false") + { + return false; + } + + std::string message("Illegal boolean value: "); + message.append(value); + throw InvalidSchemaException(message); +} + +size_t DocumentNode::IntegerFromString(const std::string &value) const +{ + const char *ptr = value.c_str(); + char *end = nullptr; + size_t len = value.length(); + size_t val = strtoll(ptr, &end, 10); + if (ptr + len != end) + { + std::string message("Illegal integer value: "); + message.append(value); + throw InvalidSchemaException(message); + } + return val; +} + +double DocumentNode::DoubleFromString(const std::string &value) const +{ + const char *ptr = value.c_str(); + char *end = nullptr; + size_t len = value.length(); + double val = strtod(ptr, &end); + if (ptr + len != end) + { + std::string message("Illegal double value: "); + message.append(value); + throw InvalidSchemaException(message); + } + return val; +} + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/configuration/ryml_document.cc b/sdk/src/configuration/ryml_document.cc new file mode 100644 index 0000000000..24968bcc0e --- /dev/null +++ b/sdk/src/configuration/ryml_document.cc @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/configuration/document.h" +#include "opentelemetry/sdk/configuration/document_node.h" +#include "opentelemetry/sdk/configuration/ryml_document.h" +#include "opentelemetry/sdk/configuration/ryml_document_node.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +std::unique_ptr RymlDocument::Parse(const std::string &source, const std::string &content) +{ + ryml::ParserOptions opts; + opts.locations(true); + + ryml::Parser::handler_type event_handler; + ryml::Parser parser(&event_handler, opts); + + ryml::Tree tree; + ryml::csubstr filename; + ryml::csubstr csubstr_content; + std::unique_ptr doc; + + filename = ryml::to_csubstr(source); + csubstr_content = ryml::to_csubstr(content); + + try + { + tree = parse_in_arena(&parser, filename, csubstr_content); + tree.resolve(); + } + catch (const std::exception &e) + { + OTEL_INTERNAL_LOG_ERROR("[Ryml Document] Parse failed with exception: " << e.what()); + return doc; + } + catch (...) + { + OTEL_INTERNAL_LOG_ERROR("[Ryml Document] Parse failed with unknown exception."); + return doc; + } + + doc = std::make_unique(tree); + return doc; +} + +std::unique_ptr RymlDocument::GetRootNode() +{ + auto node = std::make_unique(tree_.rootref(), 0); + return node; +} + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/configuration/ryml_document_node.cc b/sdk/src/configuration/ryml_document_node.cc new file mode 100644 index 0000000000..286c4f8e5b --- /dev/null +++ b/sdk/src/configuration/ryml_document_node.cc @@ -0,0 +1,504 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/configuration/document_node.h" +#include "opentelemetry/sdk/configuration/invalid_schema_exception.h" +#include "opentelemetry/sdk/configuration/ryml_document_node.h" +#include "opentelemetry/version.h" + +// Local debug, do not use in production +// #define WITH_DEBUG_NODE + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +#ifdef WITH_DEBUG_NODE +static void DebugNode(opentelemetry::nostd::string_view name, ryml::ConstNodeRef node) +{ + OTEL_INTERNAL_LOG_DEBUG("Processing: " << name); + OTEL_INTERNAL_LOG_DEBUG(" - readable() : " << node.readable()); + OTEL_INTERNAL_LOG_DEBUG(" - empty() : " << node.empty()); + OTEL_INTERNAL_LOG_DEBUG(" - is_container() : " << node.is_container()); + OTEL_INTERNAL_LOG_DEBUG(" - is_map() : " << node.is_map()); + OTEL_INTERNAL_LOG_DEBUG(" - is_seq() : " << node.is_seq()); + OTEL_INTERNAL_LOG_DEBUG(" - is_val() : " << node.is_val()); + OTEL_INTERNAL_LOG_DEBUG(" - is_keyval() : " << node.is_keyval()); + OTEL_INTERNAL_LOG_DEBUG(" - has_key() : " << node.has_key()); + OTEL_INTERNAL_LOG_DEBUG(" - has_val() : " << node.has_val()); + OTEL_INTERNAL_LOG_DEBUG(" - num_children() : " << node.num_children()); + if (node.has_key()) + { + OTEL_INTERNAL_LOG_DEBUG(" - key() : " << node.key()); + } + if (node.has_val()) + { + OTEL_INTERNAL_LOG_DEBUG(" - val() : " << node.val()); + } +} +#endif // WITH_DEBUG_NODE + +std::string RymlDocumentNode::Key() const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::Key()"); + + if (!node_.has_key()) + { + throw InvalidSchemaException("Yaml: no key"); + } + + ryml::csubstr k = node_.key(); + std::string name(k.str, k.len); + return name; +} + +bool RymlDocumentNode::AsBoolean() const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::AsBoolean()"); + + if (!node_.is_val() && !node_.is_keyval()) + { + throw InvalidSchemaException("Yaml: not scalar"); + } + ryml::csubstr view = node_.val(); + std::string value(view.str, view.len); + return BooleanFromString(value); +} + +size_t RymlDocumentNode::AsInteger() const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::AsInteger()"); + + if (!node_.is_val() && !node_.is_keyval()) + { + throw InvalidSchemaException("Yaml: not scalar"); + } + ryml::csubstr view = node_.val(); + std::string value(view.str, view.len); + return IntegerFromString(value); +} + +double RymlDocumentNode::AsDouble() const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::AsDouble()"); + + if (!node_.is_val() && !node_.is_keyval()) + { + throw InvalidSchemaException("Yaml: not scalar"); + } + ryml::csubstr view = node_.val(); + std::string value(view.str, view.len); + return DoubleFromString(value); +} + +std::string RymlDocumentNode::AsString() const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::AsString()"); + + if (!node_.is_val() && !node_.is_keyval()) + { + throw InvalidSchemaException("Yaml: not scalar"); + } + ryml::csubstr view = node_.val(); + std::string value(view.str, view.len); + return value; +} + +ryml::ConstNodeRef RymlDocumentNode::GetRequiredRymlChildNode(const std::string &name) const +{ + if (!node_.is_map()) + { + std::string message("Yaml: not a map, looking for: "); + message.append(name); + throw InvalidSchemaException(message); + } + + const char *name_str = name.c_str(); + if (!node_.has_child(name_str)) + { + std::string message("Yaml: required node: "); + message.append(name); + throw InvalidSchemaException(message); + } + + ryml::ConstNodeRef ryml_child = node_[name_str]; + return ryml_child; +} + +ryml::ConstNodeRef RymlDocumentNode::GetRymlChildNode(const std::string &name) const +{ + if (!node_.is_map()) + { + return ryml::ConstNodeRef{}; + } + + const char *name_str = name.c_str(); + if (!node_.has_child(name_str)) + { + return ryml::ConstNodeRef{}; + } + + ryml::ConstNodeRef ryml_child = node_[name_str]; + return ryml_child; +} + +std::unique_ptr RymlDocumentNode::GetRequiredChildNode(const std::string &name) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetRequiredChildNode(" << depth_ << ", " << name + << ")"); + + if (depth_ >= MAX_NODE_DEPTH) + { + std::string message("Yaml nested too deeply: "); + message.append(name); + throw InvalidSchemaException(message); + } + + auto ryml_child = GetRequiredRymlChildNode(name); + auto child = std::make_unique(ryml_child, depth_ + 1); + return child; +} + +std::unique_ptr RymlDocumentNode::GetChildNode(const std::string &name) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetChildNode(" << depth_ << ", " << name << ")"); + + if (depth_ >= MAX_NODE_DEPTH) + { + std::string message("Yaml nested too deeply: "); + message.append(name); + throw InvalidSchemaException(message); + } + + std::unique_ptr child; + + if (!node_.is_map()) + { + return child; + } + + const char *name_str = name.c_str(); + if (!node_.has_child(name_str)) + { + return child; + } + + ryml::ConstNodeRef ryml_child = node_[name_str]; + child = std::make_unique(ryml_child, depth_ + 1); + return child; +} + +bool RymlDocumentNode::GetRequiredBoolean(const std::string &name) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetRequiredBoolean(" << name << ")"); + + auto ryml_child = GetRequiredRymlChildNode(name); + + ryml::csubstr view = ryml_child.val(); + std::string value(view.str, view.len); + + value = DoSubstitution(value); + + return BooleanFromString(value); +} + +bool RymlDocumentNode::GetBoolean(const std::string &name, bool default_value) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetBoolean(" << name << ", " << default_value << ")"); + + auto ryml_child = GetRymlChildNode(name); + + if (ryml_child.invalid()) + { + return default_value; + } + + ryml::csubstr view = ryml_child.val(); + std::string value(view.str, view.len); + + value = DoSubstitution(value); + + if (value.empty()) + { + return default_value; + } + + return BooleanFromString(value); +} + +size_t RymlDocumentNode::GetRequiredInteger(const std::string &name) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetRequiredInteger(" << name << ")"); + + auto ryml_child = GetRequiredRymlChildNode(name); + + ryml::csubstr view = ryml_child.val(); + std::string value(view.str, view.len); + + value = DoSubstitution(value); + + return IntegerFromString(value); +} + +size_t RymlDocumentNode::GetInteger(const std::string &name, size_t default_value) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetInteger(" << name << ", " << default_value << ")"); + + auto ryml_child = GetRymlChildNode(name); + + if (ryml_child.invalid()) + { + return default_value; + } + + ryml::csubstr view = ryml_child.val(); + std::string value(view.str, view.len); + + value = DoSubstitution(value); + + if (value.empty()) + { + return default_value; + } + + return IntegerFromString(value); +} + +double RymlDocumentNode::GetRequiredDouble(const std::string &name) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetRequiredDouble(" << name << ")"); + + auto ryml_child = GetRequiredRymlChildNode(name); + + ryml::csubstr view = ryml_child.val(); + std::string value(view.str, view.len); + + value = DoSubstitution(value); + + return DoubleFromString(value); +} + +double RymlDocumentNode::GetDouble(const std::string &name, double default_value) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetDouble(" << name << ", " << default_value << ")"); + + auto ryml_child = GetRymlChildNode(name); + + if (ryml_child.invalid()) + { + return default_value; + } + + ryml::csubstr view = ryml_child.val(); + std::string value(view.str, view.len); + + value = DoSubstitution(value); + + if (value.empty()) + { + return default_value; + } + + return DoubleFromString(value); +} + +std::string RymlDocumentNode::GetRequiredString(const std::string &name) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetRequiredString(" << name << ")"); + + ryml::ConstNodeRef ryml_child = GetRequiredRymlChildNode(name); + ryml::csubstr view = ryml_child.val(); + std::string value(view.str, view.len); + + value = DoSubstitution(value); + + if (value.empty()) + { + std::string message("Yaml: string value is empty: "); + message.append(name); + throw InvalidSchemaException(message); + } + + return value; +} + +std::string RymlDocumentNode::GetString(const std::string &name, + const std::string &default_value) const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::GetString(" << name << ", " << default_value << ")"); + + ryml::ConstNodeRef ryml_child = GetRymlChildNode(name); + + if (ryml_child.invalid()) + { + return default_value; + } + + ryml::csubstr view = ryml_child.val(); + std::string value(view.str, view.len); + + value = DoSubstitution(value); + + return value; +} + +DocumentNodeConstIterator RymlDocumentNode::begin() const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::begin()"); + +#ifdef WITH_DEBUG_NODE + DebugNode("::begin()", node_); + + for (int index = 0; index < node_.num_children(); index++) + { + DebugNode("(child)", node_[index]); + } +#endif // WITH_DEBUG_NODE + + auto impl = std::make_unique(node_, 0, depth_); + + return DocumentNodeConstIterator(std::move(impl)); +} + +DocumentNodeConstIterator RymlDocumentNode::end() const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::end()"); + + auto impl = + std::make_unique(node_, node_.num_children(), depth_); + + return DocumentNodeConstIterator(std::move(impl)); +} + +size_t RymlDocumentNode::num_children() const +{ + return node_.num_children(); +} + +std::unique_ptr RymlDocumentNode::GetChild(size_t index) const +{ + std::unique_ptr child; + ryml::ConstNodeRef ryml_child = node_[index]; + child = std::make_unique(ryml_child, depth_ + 1); + return child; +} + +PropertiesNodeConstIterator RymlDocumentNode::begin_properties() const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::begin_properties()"); + +#ifdef WITH_DEBUG_NODE + DebugNode("::begin_properties()", node_); + + for (int index = 0; index < node_.num_children(); index++) + { + DebugNode("(child)", node_[index]); + } +#endif // WITH_DEBUG_NODE + + auto impl = std::make_unique(node_, 0, depth_); + + return PropertiesNodeConstIterator(std::move(impl)); +} + +PropertiesNodeConstIterator RymlDocumentNode::end_properties() const +{ + OTEL_INTERNAL_LOG_DEBUG("RymlDocumentNode::end_properties()"); + + auto impl = + std::make_unique(node_, node_.num_children(), depth_); + + return PropertiesNodeConstIterator(std::move(impl)); +} + +RymlDocumentNodeConstIteratorImpl::RymlDocumentNodeConstIteratorImpl(ryml::ConstNodeRef parent, + size_t index, + size_t depth) + : parent_(parent), index_(index), depth_(depth) +{} + +RymlDocumentNodeConstIteratorImpl::~RymlDocumentNodeConstIteratorImpl() {} + +void RymlDocumentNodeConstIteratorImpl::Next() +{ + ++index_; +} + +std::unique_ptr RymlDocumentNodeConstIteratorImpl::Item() const +{ + std::unique_ptr item; + ryml::ConstNodeRef ryml_item = parent_[index_]; + if (ryml_item.invalid()) + { + // FIXME: runtime exception really + throw InvalidSchemaException("iterator is lost"); + } + item = std::make_unique(ryml_item, depth_ + 1); + return item; +} + +bool RymlDocumentNodeConstIteratorImpl::Equal(const DocumentNodeConstIteratorImpl *rhs) const +{ + const RymlDocumentNodeConstIteratorImpl *other = + static_cast(rhs); + return index_ == other->index_; +} + +RymlPropertiesNodeConstIteratorImpl::RymlPropertiesNodeConstIteratorImpl(ryml::ConstNodeRef parent, + size_t index, + size_t depth) + : parent_(parent), index_(index), depth_(depth) +{} + +RymlPropertiesNodeConstIteratorImpl::~RymlPropertiesNodeConstIteratorImpl() {} + +void RymlPropertiesNodeConstIteratorImpl::Next() +{ + OTEL_INTERNAL_LOG_DEBUG("RymlPropertiesNodeConstIteratorImpl::Next()"); + ++index_; +} + +std::string RymlPropertiesNodeConstIteratorImpl::Name() const +{ + ryml::ConstNodeRef ryml_item = parent_[index_]; + // FIXME: check there is a key() + ryml::csubstr k = ryml_item.key(); + std::string name(k.str, k.len); + + OTEL_INTERNAL_LOG_DEBUG("RymlPropertiesNodeConstIteratorImpl::Name() = " << name); + + return name; +} + +std::unique_ptr RymlPropertiesNodeConstIteratorImpl::Value() const +{ + std::unique_ptr item; + + ryml::ConstNodeRef ryml_item = parent_[index_]; + item = std::make_unique(ryml_item, depth_ + 1); + + OTEL_INTERNAL_LOG_DEBUG("RymlPropertiesNodeConstIteratorImpl::Value()"); + + return item; +} + +bool RymlPropertiesNodeConstIteratorImpl::Equal(const PropertiesNodeConstIteratorImpl *rhs) const +{ + const RymlPropertiesNodeConstIteratorImpl *other = + static_cast(rhs); + return index_ == other->index_; +} + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/configuration/yaml_configuration_parser.cc b/sdk/src/configuration/yaml_configuration_parser.cc new file mode 100644 index 0000000000..627849362e --- /dev/null +++ b/sdk/src/configuration/yaml_configuration_parser.cc @@ -0,0 +1,94 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/sdk/common/env_variables.h" +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/configuration/configuration.h" +#include "opentelemetry/sdk/configuration/configuration_parser.h" +#include "opentelemetry/sdk/configuration/document.h" +#include "opentelemetry/sdk/configuration/ryml_document.h" +#include "opentelemetry/sdk/configuration/yaml_configuration_parser.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ + +std::unique_ptr YamlConfigurationParser::ParseFile(const std::string &filename) +{ + std::string input_file = filename; + + if (input_file.empty()) + { + static std::string env_var_name("OTEL_EXPERIMENTAL_CONFIG_FILE"); + std::string env_var; + bool env_exists; + env_exists = sdk::common::GetStringEnvironmentVariable(env_var_name.c_str(), env_var); + + if (env_exists) + { + input_file = env_var; + } + } + + if (input_file.empty()) + { + input_file = "config.yaml"; + } + + std::unique_ptr conf; + std::ifstream in(input_file, std::ios::binary); + if (!in.is_open()) + { + OTEL_INTERNAL_LOG_ERROR("Failed to open yaml file <" << input_file << ">."); + } + else + { + std::ostringstream content; + content << in.rdbuf(); + conf = YamlConfigurationParser::ParseString(input_file, content.str()); + } + + return conf; +} + +std::unique_ptr YamlConfigurationParser::ParseString(const std::string &source, + const std::string &content) +{ + std::unique_ptr doc; + std::unique_ptr config; + + doc = RymlDocument::Parse(source, content); + + try + { + if (doc) + { + config = ConfigurationParser::Parse(std::move(doc)); + } + } + catch (const std::exception &e) + { + OTEL_INTERNAL_LOG_ERROR( + "[Yaml Configuration Parser] Parse failed with exception: " << e.what()); + } + catch (...) + { + OTEL_INTERNAL_LOG_ERROR("[Yaml Configuration Parser] Parse failed with unknown exception."); + } + + return config; +} + +} // namespace configuration +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE