diff --git a/CMakeLists.txt b/CMakeLists.txt index 005717c46d..1a6f9cf99a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -261,6 +261,9 @@ option(WITH_METRICS_EXEMPLAR_PREVIEW option(WITH_THREAD_INSTRUMENTATION_PREVIEW "Whether to enable thread instrumentation" OFF) +option(WITH_RESOURCE_DETECTORS_PREVIEW + "Whether to enable inbuilt resource detectors" OFF) + option(OPENTELEMETRY_SKIP_DYNAMIC_LOADING_TESTS "Whether to build test libraries that are always linked as shared libs" OFF) @@ -645,6 +648,9 @@ if(NOT WITH_API_ONLY) add_subdirectory(sdk) add_subdirectory(ext) add_subdirectory(exporters) + if(WITH_RESOURCE_DETECTORS_PREVIEW) + add_subdirectory(resource_detectors) + endif() if(BUILD_TESTING) add_subdirectory(test_common) diff --git a/resource_detectors/BUILD b/resource_detectors/BUILD new file mode 100644 index 0000000000..503949ca28 --- /dev/null +++ b/resource_detectors/BUILD @@ -0,0 +1,24 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "headers", + hdrs = glob(["include/**/*.h"]), + strip_include_prefix = "include", +) + +cc_library( + name = "resource_detectors", + srcs = [ + "container_detector.cc", + "container_detector_utils.cc", + ], + deps = [ + "//api", + "//resource_detectors:headers", + "//sdk:headers", + "//sdk/src/resource", + ], +) diff --git a/resource_detectors/CMakeLists.txt b/resource_detectors/CMakeLists.txt new file mode 100644 index 0000000000..108ca00bad --- /dev/null +++ b/resource_detectors/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +add_library(opentelemetry_resource_detectors container_detector_utils.cc + container_detector.cc) + +set_target_properties(opentelemetry_resource_detectors + PROPERTIES EXPORT_NAME resource_detectors) +set_target_version(opentelemetry_resource_detectors) + +target_link_libraries(opentelemetry_resource_detectors + PUBLIC opentelemetry_resources) +target_include_directories( + opentelemetry_resource_detectors + PUBLIC "$" + "$") + +otel_add_component( + COMPONENT + resource_detectors + TARGETS + opentelemetry_resource_detectors + FILES_DIRECTORY + "include/opentelemetry/" + FILES_DESTINATION + "include/opentelemetry" + FILES_MATCHING + PATTERN + "*.h" + PATTERN + "container_detector_utils.h" + EXCLUDE) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/resource_detectors/container_detector.cc b/resource_detectors/container_detector.cc new file mode 100644 index 0000000000..3cf27d4b9e --- /dev/null +++ b/resource_detectors/container_detector.cc @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/resource_detectors/container_detector.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/resource_detectors/container_detector_utils.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/resource/resource_detector.h" +#include "opentelemetry/semconv/incubating/container_attributes.h" +#include "opentelemetry/version.h" + +#include +#include +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace resource_detector +{ + +/** + * This is the file path from where we can get container.id + */ +constexpr const char *kCGroupPath = "/proc/self/cgroup"; + +opentelemetry::sdk::resource::Resource ContainerResourceDetector::Detect() noexcept +{ + std::string container_id = + opentelemetry::resource_detector::detail::GetContainerIDFromCgroup(kCGroupPath); + if (container_id.empty()) + { + return ResourceDetector::Create({}); + } + + opentelemetry::sdk::resource::ResourceAttributes attributes; + + attributes[semconv::container::kContainerId] = std::move(container_id); + return ResourceDetector::Create(attributes); +} + +} // namespace resource_detector +OPENTELEMETRY_END_NAMESPACE diff --git a/resource_detectors/container_detector_utils.cc b/resource_detectors/container_detector_utils.cc new file mode 100644 index 0000000000..a1c54e2f47 --- /dev/null +++ b/resource_detectors/container_detector_utils.cc @@ -0,0 +1,60 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/resource_detectors/container_detector_utils.h" +#include "opentelemetry/nostd/string_view.h" + +#include +#include +#include + +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace resource_detector +{ +namespace detail +{ + +std::string GetContainerIDFromCgroup(const char *file_path) +{ + std::ifstream cgroup_file(file_path); + std::string line; + + while (std::getline(cgroup_file, line)) + { + std::string container_id = ExtractContainerIDFromLine(line); + if (!container_id.empty()) + { + return container_id; + } + } + return std::string(); +} + +std::string ExtractContainerIDFromLine(nostd::string_view line) +{ + /** + * This regex is designed to extract container IDs from cgroup file lines. + * It matches hexadecimal container IDs used by container runtimes like Docker, containerd, and + * cri-o. + * Examples of matching lines: + * - 0::/docker/3fae9b2c6d7e8f90123456789abcdef0123456789abcdef0123456789abcdef0 + * - "13:name=systemd:/podruntime/docker/kubepods/ac679f8a8319c8cf7d38e1adf263bc08d23.aaaa" + * - "e857a4bf05a69080a759574949d7a0e69572e27647800fa7faff6a05a8332aa1" + * Please see the test cases in resource_test.cc for more examples. + */ + static const std::regex container_id_regex(R"(^.*/(?:.*[-:])?([0-9a-f]+)(?:\.|\s*$))"); + std::match_results match; + + if (std::regex_search(line.data(), line.data() + line.size(), match, container_id_regex)) + { + return match.str(1); + } + + return std::string(); +} + +} // namespace detail +} // namespace resource_detector +OPENTELEMETRY_END_NAMESPACE diff --git a/resource_detectors/include/opentelemetry/resource_detectors/container_detector.h b/resource_detectors/include/opentelemetry/resource_detectors/container_detector.h new file mode 100644 index 0000000000..24ac6b3298 --- /dev/null +++ b/resource_detectors/include/opentelemetry/resource_detectors/container_detector.h @@ -0,0 +1,26 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/resource/resource_detector.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace resource_detector +{ + +/** + * ContainerResourceDetector to detect resource attributes when running inside a containerized + * environment. This detector extracts metadata such as container ID from cgroup information and + * sets attributes like container.id following the OpenTelemetry semantic conventions. + */ +class ContainerResourceDetector : public opentelemetry::sdk::resource::ResourceDetector +{ +public: + opentelemetry::sdk::resource::Resource Detect() noexcept override; +}; + +} // namespace resource_detector +OPENTELEMETRY_END_NAMESPACE diff --git a/resource_detectors/include/opentelemetry/resource_detectors/container_detector_utils.h b/resource_detectors/include/opentelemetry/resource_detectors/container_detector_utils.h new file mode 100644 index 0000000000..666bd95b7c --- /dev/null +++ b/resource_detectors/include/opentelemetry/resource_detectors/container_detector_utils.h @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace resource_detector +{ +namespace detail +{ + +/** + * Reads the container.id from /proc/self/cgroup file. + * @param file_path file path of cgroup + * @return container.id as string or an empty string if not found on error + */ +std::string GetContainerIDFromCgroup(const char *file_path); + +/** + * Matches the line with the regex to find container.id + * @param line a single line of text, typically from the /proc/self/cgroup file + * @return matched id or empty string + */ +std::string ExtractContainerIDFromLine(nostd::string_view line); + +} // namespace detail +} // namespace resource_detector +OPENTELEMETRY_END_NAMESPACE diff --git a/resource_detectors/test/BUILD b/resource_detectors/test/BUILD new file mode 100644 index 0000000000..2cac8174dc --- /dev/null +++ b/resource_detectors/test/BUILD @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +cc_test( + name = "resource_detector_test", + srcs = [ + "container_detector_test.cc", + ], + tags = ["test"], + deps = [ + "//api", + "//resource_detectors", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/resource_detectors/test/CMakeLists.txt b/resource_detectors/test/CMakeLists.txt new file mode 100644 index 0000000000..9414e64212 --- /dev/null +++ b/resource_detectors/test/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +add_executable(resource_detector_test container_detector_test.cc) + +# Link the required dependencies +target_link_libraries( + resource_detector_test PRIVATE opentelemetry_resource_detectors + opentelemetry_api GTest::gtest_main) + +gtest_add_tests( + TARGET resource_detector_test + TEST_PREFIX resource_detector. + TEST_LIST resource_detector_test) diff --git a/resource_detectors/test/container_detector_test.cc b/resource_detectors/test/container_detector_test.cc new file mode 100644 index 0000000000..0243973c46 --- /dev/null +++ b/resource_detectors/test/container_detector_test.cc @@ -0,0 +1,69 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include + +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/resource_detectors/container_detector_utils.h" + +TEST(ContainerIdDetectorTest, ExtractValidContainerIdFromLine) +{ + std::string line = + "13:name=systemd:/podruntime/docker/kubepods/ac679f8a8319c8cf7d38e1adf263bc08d23.aaaa"; + std::string extracted_id = + opentelemetry::resource_detector::detail::ExtractContainerIDFromLine(line); + EXPECT_EQ(std::string{"ac679f8a8319c8cf7d38e1adf263bc08d23"}, extracted_id); +} + +TEST(ContainerIdDetectorTest, ExtractIdFromMockUpCGroupFile) +{ + const char *filename = "test_cgroup.txt"; + + { + std::ofstream outfile(filename); + outfile << "13:name=systemd:/kuberuntime/containerd" + "/kubepods-pod872d2066_00ef_48ea_a7d8_51b18b72d739:cri-containerd:" + "e857a4bf05a69080a759574949d7a0e69572e27647800fa7faff6a05a8332aa1\n"; + outfile << "9:cpu:/not-a-container\n"; + } + + std::string container_id = + opentelemetry::resource_detector::detail::GetContainerIDFromCgroup(filename); + EXPECT_EQ(container_id, + std::string{"e857a4bf05a69080a759574949d7a0e69572e27647800fa7faff6a05a8332aa1"}); + + std::remove(filename); +} + +TEST(ContainerIdDetectorTest, DoesNotExtractInvalidLine) +{ + std::string line = "this line does not contain a container id"; + std::string id = opentelemetry::resource_detector::detail::ExtractContainerIDFromLine(line); + EXPECT_EQ(id, std::string{""}); +} + +TEST(ContainerIdDetectorTest, ReturnsEmptyOnNoMatch) +{ + const char *filename = "test_empty_cgroup.txt"; + + { + std::ofstream outfile(filename); + outfile << "no container id here\n"; + } + + std::string id = opentelemetry::resource_detector::detail::GetContainerIDFromCgroup(filename); + EXPECT_EQ(id, std::string{""}); + + std::remove(filename); // cleanup +} + +TEST(ContainerIdDetectorTest, ReturnsEmptyOnFileFailingToOpen) +{ + const char *filename = "test_invalid_cgroup.txt"; + + std::string id = opentelemetry::resource_detector::detail::GetContainerIDFromCgroup(filename); + EXPECT_EQ(id, std::string{""}); +} diff --git a/sdk/src/resource/resource_detector.cc b/sdk/src/resource/resource_detector.cc index 3158f2a43d..f51d9a2a70 100644 --- a/sdk/src/resource/resource_detector.cc +++ b/sdk/src/resource/resource_detector.cc @@ -19,8 +19,8 @@ namespace sdk namespace resource { -const char *OTEL_RESOURCE_ATTRIBUTES = "OTEL_RESOURCE_ATTRIBUTES"; -const char *OTEL_SERVICE_NAME = "OTEL_SERVICE_NAME"; +constexpr const char *kOtelResourceAttributes = "OTEL_RESOURCE_ATTRIBUTES"; +constexpr const char *kOtelServiceName = "OTEL_SERVICE_NAME"; Resource ResourceDetector::Create(const ResourceAttributes &attributes, const std::string &schema_url) @@ -33,9 +33,9 @@ Resource OTELResourceDetector::Detect() noexcept std::string attributes_str, service_name; bool attributes_exists = opentelemetry::sdk::common::GetStringEnvironmentVariable( - OTEL_RESOURCE_ATTRIBUTES, attributes_str); + kOtelResourceAttributes, attributes_str); bool service_name_exists = - opentelemetry::sdk::common::GetStringEnvironmentVariable(OTEL_SERVICE_NAME, service_name); + opentelemetry::sdk::common::GetStringEnvironmentVariable(kOtelServiceName, service_name); if (!attributes_exists && !service_name_exists) { diff --git a/test_common/cmake/preview-options.cmake b/test_common/cmake/preview-options.cmake index 88acf7a187..41a35aa55b 100644 --- a/test_common/cmake/preview-options.cmake +++ b/test_common/cmake/preview-options.cmake @@ -13,5 +13,6 @@ set(WITH_THREAD_INSTRUMENTATION_PREVIEW ${ENABLE_PREVIEW} CACHE BOOL "" FORCE) set(WITH_OTLP_GRPC_SSL_MTLS_PREVIEW ${ENABLE_PREVIEW} CACHE BOOL "" FORCE) set(WITH_OTLP_GRPC_CREDENTIAL_PREVIEW ${ENABLE_PREVIEW} CACHE BOOL "" FORCE) set(WITH_OTLP_RETRY_PREVIEW ${ENABLE_PREVIEW} CACHE BOOL "" FORCE) +set(WITH_RESOURCE_DETECTORS_PREVIEW ${ENABLE_PREVIEW} CACHE BOOL "" FORCE) set(WITH_OTLP_HTTP_COMPRESSION ${ENABLE_PREVIEW} CACHE BOOL "" FORCE) set(WITH_CURL_LOGGING ${ENABLE_PREVIEW} CACHE BOOL "" FORCE)