diff --git a/CHANGELOG.md b/CHANGELOG.md index 4206a3015..805c832e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ Increment the: * [RESOURCE DETECTOR] Add the host resource detector [#4413](https://github.com/open-telemetry/opentelemetry-cpp/issues/4413) +* [RESOURCE DETECTOR] Add the service resource detector and builder + [#4414](https://github.com/open-telemetry/opentelemetry-cpp/issues/4414) * [CONFIGURATION] deprecate config builder cmake components [#4428](https://github.com/open-telemetry/opentelemetry-cpp/pull/4428) diff --git a/examples/configuration/kitchen-sink.yaml b/examples/configuration/kitchen-sink.yaml index cab02d8f5..0e377c5be 100644 --- a/examples/configuration/kitchen-sink.yaml +++ b/examples/configuration/kitchen-sink.yaml @@ -1067,8 +1067,8 @@ resource: host: - # Enable the process resource detector, which populates process.* attributes. process: - # - # Enable the service detector, which populates service.name based on the OTEL_SERVICE_NAME environment variable and service.instance.id. - # service: + - # Enable the service resource detector, which populates service.name based on the OTEL_SERVICE_NAME environment variable and service.instance.id. + service: # - # Enable an extension resource detector registered by name. # my_custom_detector: # Configure resource schema URL. diff --git a/examples/configuration/main.cc b/examples/configuration/main.cc index 053304823..ecd173efd 100644 --- a/examples/configuration/main.cc +++ b/examples/configuration/main.cc @@ -60,6 +60,7 @@ # include "opentelemetry/resource_detectors/container_detector_builder.h" # include "opentelemetry/resource_detectors/host_detector_builder.h" # include "opentelemetry/resource_detectors/process_detector_builder.h" +# include "opentelemetry/resource_detectors/service_detector_builder.h" #endif static bool opt_help = false; @@ -226,6 +227,7 @@ void InitOtel(const std::string &config_file) opentelemetry::resource_detector::ContainerDetectorBuilder::Register(registry.get()); opentelemetry::resource_detector::HostDetectorBuilder::Register(registry.get()); opentelemetry::resource_detector::ProcessDetectorBuilder::Register(registry.get()); + opentelemetry::resource_detector::ServiceDetectorBuilder::Register(registry.get()); #endif } diff --git a/install/test/cmake/component_tests/resource_detectors/CMakeLists.txt b/install/test/cmake/component_tests/resource_detectors/CMakeLists.txt index 2e03eee78..1b9970485 100644 --- a/install/test/cmake/component_tests/resource_detectors/CMakeLists.txt +++ b/install/test/cmake/component_tests/resource_detectors/CMakeLists.txt @@ -22,6 +22,8 @@ target_link_libraries( opentelemetry-cpp::host_resource_detector_builder opentelemetry-cpp::process_resource_detector opentelemetry-cpp::process_resource_detector_builder + opentelemetry-cpp::service_resource_detector + opentelemetry-cpp::service_resource_detector_builder GTest::gtest GTest::gtest_main) diff --git a/install/test/cmake/fetch_content_test/CMakeLists.txt b/install/test/cmake/fetch_content_test/CMakeLists.txt index bcebfa9bf..432523d96 100644 --- a/install/test/cmake/fetch_content_test/CMakeLists.txt +++ b/install/test/cmake/fetch_content_test/CMakeLists.txt @@ -112,6 +112,8 @@ target_link_libraries( opentelemetry-cpp::host_resource_detector_builder opentelemetry-cpp::process_resource_detector opentelemetry-cpp::process_resource_detector_builder + opentelemetry-cpp::service_resource_detector + opentelemetry-cpp::service_resource_detector_builder opentelemetry-cpp::http_client opentelemetry-cpp::http_client_curl opentelemetry-cpp::in_memory_span_exporter diff --git a/install/test/src/test_resource_detectors.cc b/install/test/src/test_resource_detectors.cc index bd5b7f06d..e0b8b2cc8 100644 --- a/install/test/src/test_resource_detectors.cc +++ b/install/test/src/test_resource_detectors.cc @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -10,7 +11,13 @@ #include #include #include +#include +#include +#include #include +#include + +namespace semconv = opentelemetry::semconv; TEST(ResourceDetectorsInstall, ContainerResourceDetector) { @@ -44,6 +51,24 @@ TEST(ResourceDetectorsInstall, ProcessResourceDetector) ASSERT_NO_THROW(auto resource = detector->Detect()); } +TEST(ResourceDetectorsInstall, ServiceResourceDetector) +{ + std::unique_ptr detector = + std::make_unique(); + ASSERT_TRUE(detector != nullptr); + + auto resource = detector->Detect(); + const auto &attrs = resource.GetAttributes(); + + auto name_it = attrs.find(semconv::service::kServiceName); + ASSERT_NE(name_it, attrs.end()); + EXPECT_FALSE(opentelemetry::nostd::get(name_it->second).empty()); + + auto instance_id_it = attrs.find(semconv::service::kServiceInstanceId); + ASSERT_NE(instance_id_it, attrs.end()); + EXPECT_FALSE(opentelemetry::nostd::get(instance_id_it->second).empty()); +} + TEST(ResourceDetectorsInstall, ContainerDetectorBuilder) { auto builder = std::make_unique(); @@ -73,3 +98,14 @@ TEST(ResourceDetectorsInstall, ProcessDetectorBuilder) auto detector = builder->Build(&model); ASSERT_TRUE(detector != nullptr); } + +TEST(ResourceDetectorsInstall, ServiceDetectorBuilder) +{ + auto builder = std::make_unique(); + ASSERT_TRUE(builder != nullptr); + + opentelemetry::sdk::configuration::ServiceResourceDetectorConfiguration model; + auto detector = builder->Build(&model); + ASSERT_TRUE(detector != nullptr); + ASSERT_NO_THROW(auto resource = detector->Detect()); +} diff --git a/resource_detectors/BUILD b/resource_detectors/BUILD index 83af6d4a7..e0a715f55 100644 --- a/resource_detectors/BUILD +++ b/resource_detectors/BUILD @@ -95,6 +95,43 @@ cc_library( ], ) +cc_library( + name = "service_resource_detector_utils", + srcs = ["src/service_detector_utils.cc"], + copts = ["-fexceptions"], + deps = [ + ":process_resource_detector", + "//api", + "//resource_detectors:headers", + "//sdk:headers", + "//sdk/src/common:env_variables", + "//sdk/src/common:random", + "//sdk/src/resource", + ], +) + +cc_library( + name = "service_resource_detector", + srcs = ["src/service_detector.cc"], + copts = ["-fexceptions"], + deps = [ + ":service_resource_detector_utils", + "//api", + "//resource_detectors:headers", + "//sdk:headers", + "//sdk/src/resource", + ], +) + +cc_library( + name = "service_resource_detector_builder", + srcs = ["src/service_detector_builder.cc"], + deps = [ + ":service_resource_detector", + "//sdk/src/configuration:configuration_core", + ], +) + # Convenience target linking all resource detectors. cc_library( name = "resource_detectors", @@ -103,6 +140,7 @@ cc_library( ":env_entity_resource_detector", ":host_resource_detector", ":process_resource_detector", + ":service_resource_detector", ], ) @@ -113,5 +151,6 @@ cc_library( ":container_resource_detector_builder", ":host_resource_detector_builder", ":process_resource_detector_builder", + ":service_resource_detector_builder", ], ) diff --git a/resource_detectors/CMakeLists.txt b/resource_detectors/CMakeLists.txt index 8ec9d7ece..02c0d88c2 100644 --- a/resource_detectors/CMakeLists.txt +++ b/resource_detectors/CMakeLists.txt @@ -136,6 +136,64 @@ target_link_libraries( PRIVATE opentelemetry_process_resource_detector opentelemetry_configuration_core) +# +# opentelemetry_service_resource_detector_utils +# + +add_library(opentelemetry_service_resource_detector_utils + src/service_detector_utils.cc) + +set_target_properties(opentelemetry_service_resource_detector_utils + PROPERTIES EXPORT_NAME service_resource_detector_utils) +set_target_version(opentelemetry_service_resource_detector_utils) + +target_link_libraries( + opentelemetry_service_resource_detector_utils + PUBLIC opentelemetry_resources + PRIVATE opentelemetry_process_resource_detector) +target_include_directories( + opentelemetry_service_resource_detector_utils + PUBLIC "$" + "$") + +# +# opentelemetry_service_resource_detector +# + +add_library(opentelemetry_service_resource_detector src/service_detector.cc) + +set_target_properties(opentelemetry_service_resource_detector + PROPERTIES EXPORT_NAME service_resource_detector) +set_target_version(opentelemetry_service_resource_detector) + +target_link_libraries(opentelemetry_service_resource_detector + PUBLIC opentelemetry_service_resource_detector_utils) +target_include_directories( + opentelemetry_service_resource_detector + PUBLIC "$" + "$") + +# +# opentelemetry_service_resource_detector_builder +# + +add_library(opentelemetry_service_resource_detector_builder + src/service_detector_builder.cc) + +set_target_properties(opentelemetry_service_resource_detector_builder + PROPERTIES EXPORT_NAME service_resource_detector_builder) +set_target_version(opentelemetry_service_resource_detector_builder) + +target_include_directories( + opentelemetry_service_resource_detector_builder + PUBLIC "$" + "$") + +target_link_libraries( + opentelemetry_service_resource_detector_builder + PRIVATE opentelemetry_service_resource_detector + opentelemetry_configuration_core) + # # opentelemetry_resource_detectors (interface to all detectors) # @@ -150,7 +208,8 @@ target_link_libraries( INTERFACE opentelemetry_container_resource_detector opentelemetry_env_entity_resource_detector opentelemetry_host_resource_detector - opentelemetry_process_resource_detector) + opentelemetry_process_resource_detector + opentelemetry_service_resource_detector) # # opentelemetry_resource_detectors_builders (interface to all builders) @@ -165,7 +224,8 @@ target_link_libraries( opentelemetry_resource_detectors_builders INTERFACE opentelemetry_container_resource_detector_builder opentelemetry_host_resource_detector_builder - opentelemetry_process_resource_detector_builder) + opentelemetry_process_resource_detector_builder + opentelemetry_service_resource_detector_builder) otel_add_component( COMPONENT @@ -180,6 +240,9 @@ otel_add_component( opentelemetry_host_resource_detector_builder opentelemetry_process_resource_detector opentelemetry_process_resource_detector_builder + opentelemetry_service_resource_detector + opentelemetry_service_resource_detector_builder + opentelemetry_service_resource_detector_utils FILES_DIRECTORY "include/opentelemetry/" FILES_DESTINATION diff --git a/resource_detectors/README.md b/resource_detectors/README.md index 62aa9d12a..c9ac85ac5 100644 --- a/resource_detectors/README.md +++ b/resource_detectors/README.md @@ -29,7 +29,25 @@ Contributions to detect more attributes for each Entity are welcome. [Entity: Service](https://opentelemetry.io/docs/specs/semconv/registry/entities/service/) -Not implemented. +| Attribute | Description | Linux | macOS | Windows | +| --- | --- | --- | --- | --- | +| `service.name` | From `OTEL_SERVICE_NAME` when set | Yes | Yes | Yes | +| `service.name` | Fallback `unknown_service:` or `unknown_service` | Yes | Yes | Yes | +| `service.instance.id` | Stable RFC 4122 UUID version 4 for the current process | Yes | Yes | Yes | + +`service.name` is read only from the `OTEL_SERVICE_NAME` environment variable. When +that variable is unset, the detector falls back to +`unknown_service:` when the executable name is +available for the current process, otherwise `unknown_service`. + +`service.instance.id` is generated once per process and remains stable until the +process ID changes (for example after `fork()`). + +Limitations: + +- The detector does not read `OTEL_RESOURCE_ATTRIBUTES`. +- Executable-name fallback reuses process detector utilities; on macOS the + executable name is available for the current process only. ### Container Resource Detector @@ -62,10 +80,7 @@ or inaccessible. | Attribute | Description | Linux | macOS | Windows | | --- | --- | --- | --- | --- | | `process.pid` | Process ID | Yes | Yes | Yes | -| `process.executable.path` | Path via `/proc` (Linux) or Win32 APIs | Yes | No | Yes | - -Limitation: current macOS implementation does not populate -`process.executable.path`. +| `process.executable.path` | Path via `/proc` (Linux) or Win32 APIs | Yes | Yes | Yes | ### Env Entity Resource Detector (Experimental) @@ -102,8 +117,7 @@ resource: - container: - host: - process: - # NOTE: service detector cannot be configured currently - - service: # not implemented (github.com/open-telemetry/opentelemetry-cpp/issues/4414) + - service: ``` Call `Register` for each builder before creating the SDK: @@ -112,15 +126,17 @@ Call `Register` for each builder before creating the SDK: #include "opentelemetry/resource_detectors/container_detector_builder.h" #include "opentelemetry/resource_detectors/host_detector_builder.h" #include "opentelemetry/resource_detectors/process_detector_builder.h" +#include "opentelemetry/resource_detectors/service_detector_builder.h" opentelemetry::resource_detector::ContainerDetectorBuilder::Register(registry.get()); opentelemetry::resource_detector::HostDetectorBuilder::Register(registry.get()); opentelemetry::resource_detector::ProcessDetectorBuilder::Register(registry.get()); +opentelemetry::resource_detector::ServiceDetectorBuilder::Register(registry.get()); ``` ## Linking with CMake -### Linking to detectors directly (not required for declaritive config) +### Linking to detectors directly (not required for declarative config) Link to all available detectors: @@ -136,9 +152,9 @@ find_package(opentelemetry-cpp CONFIG REQUIRED COMPONENTS resource_detectors) target_link_libraries(my_target PRIVATE opentelemetry-cpp::host_resource_detector) ``` -### Linking to builders for declaritive configuration +### Linking to builders for declarative configuration -Builder targets make detectors available for declaritive configuration and hide +Builder targets make detectors available for declarative configuration and hide the concrete detector implementations. Link all builders: @@ -166,12 +182,14 @@ All targets are part of the `resource_detectors` component. | `opentelemetry-cpp::resource_detectors` | Interface: links all detectors | | `opentelemetry-cpp::resource_detectors_builders` | Interface: links all builders | | `opentelemetry-cpp::container_resource_detector` | Container detector | -| `opentelemetry-cpp::container_resource_detector_builder` | Container detector builder for declaritive configuration | +| `opentelemetry-cpp::container_resource_detector_builder` | Container detector builder for declarative configuration | | `opentelemetry-cpp::env_entity_resource_detector` | Env entity detector | | `opentelemetry-cpp::host_resource_detector` | Host detector | | `opentelemetry-cpp::host_resource_detector_builder` | Host detector builder for declaritive configuration | | `opentelemetry-cpp::process_resource_detector` | Process detector | -| `opentelemetry-cpp::process_resource_detector_builder` | Process detector builder for declaritive configuration | +| `opentelemetry-cpp::process_resource_detector_builder` | Process detector builder for declarative configuration | +| `opentelemetry-cpp::service_resource_detector` | Service detector | +| `opentelemetry-cpp::service_resource_detector_builder` | Service detector builder for declarative configuration | ## Linking with Bazel @@ -219,3 +237,5 @@ deps = ["//resource_detectors:process_resource_detector_builder"] | `//resource_detectors:host_resource_detector_builder` | Host detector builder | | `//resource_detectors:process_resource_detector` | Process detector | | `//resource_detectors:process_resource_detector_builder` | Process detector builder | +| `//resource_detectors:service_resource_detector` | Service detector | +| `//resource_detectors:service_resource_detector_builder` | Service detector builder | diff --git a/resource_detectors/include/opentelemetry/resource_detectors/detail/process_detector_utils.h b/resource_detectors/include/opentelemetry/resource_detectors/detail/process_detector_utils.h index 4cd6a1238..95a864867 100644 --- a/resource_detectors/include/opentelemetry/resource_detectors/detail/process_detector_utils.h +++ b/resource_detectors/include/opentelemetry/resource_detectors/detail/process_detector_utils.h @@ -26,7 +26,8 @@ std::string FormFilePath(const int32_t &pid, const char *process_type); * Platform-specific behavior: * - Windows: Uses OpenProcess() + GetProcessImageFileNameW(). * - Linux/Unix: Reads the /proc//exe symbolic link. - * - TODO: Need to implement for Darwin + * - macOS: Uses _NSGetExecutablePath() for the current process only; returns + * an empty string for other PIDs. * * @param pid Process ID. */ diff --git a/resource_detectors/include/opentelemetry/resource_detectors/detail/service_detector_utils.h b/resource_detectors/include/opentelemetry/resource_detectors/detail/service_detector_utils.h new file mode 100644 index 000000000..1abf8622b --- /dev/null +++ b/resource_detectors/include/opentelemetry/resource_detectors/detail/service_detector_utils.h @@ -0,0 +1,34 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace resource_detector +{ +namespace detail +{ + +/** + * Resolves the logical service name for the service resource detector. + * Platform-specific behavior: + * - Reads OTEL_SERVICE_NAME when set. + * - Otherwise falls back to unknown_service: when the + * executable name is available for the current process. + * - Otherwise returns unknown_service. + */ +std::string GetServiceName(); + +/** + * Returns a stable service.instance.id for the current process. + * A new RFC 4122 UUID version 4 value is generated when the process ID changes. + */ +std::string GenerateServiceInstanceId() noexcept; + +} // namespace detail +} // namespace resource_detector +OPENTELEMETRY_END_NAMESPACE diff --git a/resource_detectors/include/opentelemetry/resource_detectors/service_detector.h b/resource_detectors/include/opentelemetry/resource_detectors/service_detector.h new file mode 100644 index 000000000..8d321235d --- /dev/null +++ b/resource_detectors/include/opentelemetry/resource_detectors/service_detector.h @@ -0,0 +1,33 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "opentelemetry/sdk/resource/resource_detector.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace resource_detector +{ + +/** + * ServiceResourceDetector to detect resource attributes describing the service + * the process belongs to. It sets service.name and service.instance.id following + * the OpenTelemetry semantic conventions. + * + * Platform specific behavior and limitations: + * - service.name is read from the OTEL_SERVICE_NAME environment variable when set. + * - When OTEL_SERVICE_NAME is not set, service.name falls back to + * unknown_service: or unknown_service when the + * executable name is unavailable. + * - service.instance.id is a stable RFC 4122 UUID version 4 value for the + * current process. + */ +class ServiceResourceDetector : 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/service_detector_builder.h b/resource_detectors/include/opentelemetry/resource_detectors/service_detector_builder.h new file mode 100644 index 000000000..f57d0ae42 --- /dev/null +++ b/resource_detectors/include/opentelemetry/resource_detectors/service_detector_builder.h @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "opentelemetry/sdk/configuration/service_resource_detector_builder.h" +#include "opentelemetry/sdk/configuration/service_resource_detector_configuration.h" +#include "opentelemetry/sdk/resource/resource_detector.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace configuration +{ +class Registry; +} // namespace configuration +} // namespace sdk + +namespace resource_detector +{ + +class ServiceDetectorBuilder + : public opentelemetry::sdk::configuration::ServiceResourceDetectorBuilder +{ +public: + static void Register(opentelemetry::sdk::configuration::Registry *registry); + + std::unique_ptr Build( + const opentelemetry::sdk::configuration::ServiceResourceDetectorConfiguration *model) + const override; +}; + +} // namespace resource_detector +OPENTELEMETRY_END_NAMESPACE diff --git a/resource_detectors/src/process_detector_utils.cc b/resource_detectors/src/process_detector_utils.cc index 9da40a52a..fe4823bf9 100644 --- a/resource_detectors/src/process_detector_utils.cc +++ b/resource_detectors/src/process_detector_utils.cc @@ -7,6 +7,10 @@ #include #include +#if defined(__APPLE__) +# include +#endif + #ifdef _MSC_VER // clang-format off # include @@ -56,6 +60,20 @@ std::string GetExecutablePath(const int32_t &pid) WideCharToMultiByte(CP_UTF8, 0, wbuffer, len, &utf8_path[0], size_needed, NULL, NULL); return utf8_path; +#elif defined(__APPLE__) + if (pid != static_cast(getpid())) + { + return std::string(); + } + + char path[4096]; + uint32_t size = sizeof(path); + if (_NSGetExecutablePath(path, &size) != 0) + { + return std::string(); + } + + return std::string(path); #else std::string path = FormFilePath(pid, kExecutableName); char buffer[4096]; diff --git a/resource_detectors/src/service_detector.cc b/resource_detectors/src/service_detector.cc new file mode 100644 index 000000000..c86b840e5 --- /dev/null +++ b/resource_detectors/src/service_detector.cc @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/resource_detectors/service_detector.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/resource_detectors/detail/service_detector_utils.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/resource/resource_detector.h" +#include "opentelemetry/semconv/schema_url.h" +#include "opentelemetry/semconv/service_attributes.h" +#include "opentelemetry/version.h" + +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace resource_detector +{ + +opentelemetry::sdk::resource::Resource ServiceResourceDetector::Detect() noexcept +{ + opentelemetry::sdk::resource::ResourceAttributes attributes; + attributes[semconv::service::kServiceName] = detail::GetServiceName(); + attributes[semconv::service::kServiceInstanceId] = detail::GenerateServiceInstanceId(); + return ResourceDetector::Create(attributes, semconv::kSchemaUrl); +} + +} // namespace resource_detector +OPENTELEMETRY_END_NAMESPACE diff --git a/resource_detectors/src/service_detector_builder.cc b/resource_detectors/src/service_detector_builder.cc new file mode 100644 index 000000000..11c093c48 --- /dev/null +++ b/resource_detectors/src/service_detector_builder.cc @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "opentelemetry/resource_detectors/service_detector.h" +#include "opentelemetry/resource_detectors/service_detector_builder.h" +#include "opentelemetry/sdk/configuration/registry.h" +#include "opentelemetry/sdk/configuration/service_resource_detector_builder.h" +#include "opentelemetry/sdk/resource/resource_detector.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace resource_detector +{ + +void ServiceDetectorBuilder::Register(opentelemetry::sdk::configuration::Registry *registry) +{ + auto builder = std::make_unique(); + registry->SetServiceResourceDetectorBuilder(std::move(builder)); +} + +std::unique_ptr ServiceDetectorBuilder::Build( + const opentelemetry::sdk::configuration::ServiceResourceDetectorConfiguration * /* model */) + const +{ + return std::make_unique(); +} + +} // namespace resource_detector +OPENTELEMETRY_END_NAMESPACE diff --git a/resource_detectors/src/service_detector_utils.cc b/resource_detectors/src/service_detector_utils.cc new file mode 100644 index 000000000..3a45bb72c --- /dev/null +++ b/resource_detectors/src/service_detector_utils.cc @@ -0,0 +1,118 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "opentelemetry/resource_detectors/detail/service_detector_utils.h" + +#include +#include +#include +#include + +#ifdef _MSC_VER +# include +# define getpid _getpid +#else +# include +#endif + +#include "opentelemetry/resource_detectors/detail/process_detector_utils.h" +#include "opentelemetry/sdk/common/env_variables.h" +#include "opentelemetry/version.h" +#include "src/common/random.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace resource_detector +{ +namespace detail +{ + +namespace +{ +constexpr const char *kOtelServiceName = "OTEL_SERVICE_NAME"; +constexpr const char *kUnknownService = "unknown_service"; +constexpr const char *kUnknownServicePrefix = "unknown_service:"; +constexpr const char kUuidHexDigits[] = "0123456789abcdef"; + +std::string FormatUuidV4(const uint8_t bytes[16]) noexcept +{ + std::string uuid; + uuid.reserve(36); + for (size_t i = 0; i < 16; ++i) + { + if (i == 4 || i == 6 || i == 8 || i == 10) + { + uuid.push_back('-'); + } + uuid.push_back(kUuidHexDigits[bytes[i] >> 4]); + uuid.push_back(kUuidHexDigits[bytes[i] & 0x0F]); + } + return uuid; +} + +std::string GenerateUuidV4() noexcept +{ + uint8_t bytes[16]; + for (size_t i = 0; i < sizeof(bytes); i += sizeof(uint64_t)) + { + uint64_t value = opentelemetry::sdk::common::Random::GenerateRandom64(); + std::memcpy(&bytes[i], &value, sizeof(uint64_t)); + } + bytes[6] = static_cast((bytes[6] & 0x0F) | 0x40); + bytes[8] = static_cast((bytes[8] & 0x3F) | 0x80); + return FormatUuidV4(bytes); +} + +} // namespace + +std::string GetServiceName() +{ + std::string service_name; + if (opentelemetry::sdk::common::GetStringEnvironmentVariable(kOtelServiceName, service_name) && + !service_name.empty()) + { + return service_name; + } + + const std::string executable_path = GetExecutablePath(static_cast(getpid())); + std::string executable_name; + if (!executable_path.empty()) + { + const size_t pos = executable_path.find_last_of("/\\"); + if (pos == std::string::npos) + { + executable_name = executable_path; + } + else + { + executable_name = executable_path.substr(pos + 1); + } + } + if (!executable_name.empty()) + { + std::string fallback_service_name(kUnknownServicePrefix); + fallback_service_name.append(executable_name); + return fallback_service_name; + } + + return std::string{kUnknownService}; +} + +std::string GenerateServiceInstanceId() noexcept +{ + static std::mutex mutex; + static int32_t cached_pid = -1; + static std::string cached_id; + + const int32_t pid = static_cast(getpid()); + const std::lock_guard lock(mutex); + if (cached_pid != pid || cached_id.empty()) + { + cached_pid = pid; + cached_id = GenerateUuidV4(); + } + return cached_id; +} + +} // namespace detail +} // namespace resource_detector +OPENTELEMETRY_END_NAMESPACE diff --git a/resource_detectors/test/BUILD b/resource_detectors/test/BUILD index 9438ec1a4..8c8fcd10f 100644 --- a/resource_detectors/test/BUILD +++ b/resource_detectors/test/BUILD @@ -82,3 +82,38 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "service_resource_detector_utils_test", + srcs = ["service_detector_utils_test.cc"], + tags = ["test"], + deps = [ + "//resource_detectors:process_resource_detector", + "//resource_detectors:service_resource_detector_utils", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "service_resource_detector_test", + srcs = ["service_detector_test.cc"], + tags = ["test"], + deps = [ + "//resource_detectors:service_resource_detector", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "service_resource_detector_builder_test", + srcs = ["service_detector_builder_test.cc"], + tags = ["test"], + deps = [ + "//resource_detectors:service_resource_detector_builder", + "//sdk/src/configuration:configuration_core", + "//sdk/src/resource", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/resource_detectors/test/CMakeLists.txt b/resource_detectors/test/CMakeLists.txt index c53738426..5e6212f0c 100644 --- a/resource_detectors/test/CMakeLists.txt +++ b/resource_detectors/test/CMakeLists.txt @@ -85,3 +85,46 @@ gtest_add_tests( TARGET process_resource_detector_builder_test TEST_PREFIX resource_detector. TEST_LIST process_resource_detector_builder_test) + +# +# service_resource_detector_utils tests +# + +add_executable(service_resource_detector_utils_test + service_detector_utils_test.cc) +target_link_libraries( + service_resource_detector_utils_test + PRIVATE opentelemetry_service_resource_detector_utils + opentelemetry_process_resource_detector GTest::gtest_main) +gtest_add_tests( + TARGET service_resource_detector_utils_test + TEST_PREFIX resource_detector. + TEST_LIST service_resource_detector_utils_test) + +# +# service_resource_detector tests +# + +add_executable(service_resource_detector_test service_detector_test.cc) +target_link_libraries( + service_resource_detector_test PRIVATE opentelemetry_service_resource_detector + GTest::gtest_main) +gtest_add_tests( + TARGET service_resource_detector_test + TEST_PREFIX resource_detector. + TEST_LIST service_resource_detector_test) + +# +# service_resource_detector_builder tests +# + +add_executable(service_resource_detector_builder_test + service_detector_builder_test.cc) +target_link_libraries( + service_resource_detector_builder_test + PRIVATE opentelemetry_service_resource_detector_builder + opentelemetry_configuration_core GTest::gtest_main) +gtest_add_tests( + TARGET service_resource_detector_builder_test + TEST_PREFIX resource_detector. + TEST_LIST service_resource_detector_builder_test) diff --git a/resource_detectors/test/process_detector_test.cc b/resource_detectors/test/process_detector_test.cc index 4782c18d2..f331d22d5 100644 --- a/resource_detectors/test/process_detector_test.cc +++ b/resource_detectors/test/process_detector_test.cc @@ -18,6 +18,9 @@ # include # include # include +# if defined(__APPLE__) +# include +# endif #endif #include "opentelemetry/resource_detectors/detail/process_detector_utils.h" @@ -93,6 +96,17 @@ TEST(ProcessDetectorUtilsTest, GetExecutablePathTest) path = utf8_path; } } +#elif defined(__APPLE__) + char buffer[4096]; + uint32_t size = sizeof(buffer); + if (_NSGetExecutablePath(buffer, &size) != 0) + { + path = std::string(); + } + else + { + path = std::string(buffer); + } #else std::string exe_path = opentelemetry::resource_detector::detail::FormFilePath(pid, "exe"); char buffer[4096]; diff --git a/resource_detectors/test/service_detector_builder_test.cc b/resource_detectors/test/service_detector_builder_test.cc new file mode 100644 index 000000000..4b1b5618d --- /dev/null +++ b/resource_detectors/test/service_detector_builder_test.cc @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "opentelemetry/resource_detectors/service_detector_builder.h" +#include "opentelemetry/sdk/configuration/registry.h" +#include "opentelemetry/sdk/configuration/service_resource_detector_configuration.h" +#include "opentelemetry/sdk/resource/resource_detector.h" + +TEST(ServiceDetectorBuilderTest, Register) +{ + opentelemetry::sdk::configuration::Registry registry; + ASSERT_EQ(registry.GetServiceResourceDetectorBuilder(), nullptr); + + opentelemetry::resource_detector::ServiceDetectorBuilder::Register(®istry); + ASSERT_NE(registry.GetServiceResourceDetectorBuilder(), nullptr); +} + +TEST(ServiceDetectorBuilderTest, Build) +{ + opentelemetry::resource_detector::ServiceDetectorBuilder builder; + opentelemetry::sdk::configuration::ServiceResourceDetectorConfiguration model; + + auto detector = builder.Build(&model); + ASSERT_NE(detector, nullptr); +} diff --git a/resource_detectors/test/service_detector_test.cc b/resource_detectors/test/service_detector_test.cc new file mode 100644 index 000000000..cb1afbde2 --- /dev/null +++ b/resource_detectors/test/service_detector_test.cc @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include + +#ifdef _MSC_VER +# include "opentelemetry/sdk/common/env_variables.h" +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +#else +# include +#endif + +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/resource_detectors/detail/service_detector_utils.h" +#include "opentelemetry/resource_detectors/service_detector.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/semconv/schema_url.h" +#include "opentelemetry/semconv/service_attributes.h" + +namespace semconv = opentelemetry::semconv; + +namespace +{ +constexpr const char *kOtelServiceName = "OTEL_SERVICE_NAME"; +} // namespace + +TEST(ServiceDetectorTest, DetectSetsServiceAttributes) +{ + opentelemetry::resource_detector::ServiceResourceDetector detector; + auto resource = detector.Detect(); + const auto &attrs = resource.GetAttributes(); + + auto name_it = attrs.find(semconv::service::kServiceName); + ASSERT_NE(name_it, attrs.end()); + EXPECT_FALSE(opentelemetry::nostd::get(name_it->second).empty()); + + auto instance_id_it = attrs.find(semconv::service::kServiceInstanceId); + ASSERT_NE(instance_id_it, attrs.end()); + EXPECT_EQ(opentelemetry::nostd::get(instance_id_it->second), + opentelemetry::resource_detector::detail::GenerateServiceInstanceId()); + + EXPECT_EQ(resource.GetSchemaURL(), std::string{semconv::kSchemaUrl}); +} + +TEST(ServiceDetectorTest, DetectUsesServiceNameFromEnvironment) +{ + setenv(kOtelServiceName, "stage2-detector-service", 1); + + opentelemetry::resource_detector::ServiceResourceDetector detector; + auto resource = detector.Detect(); + const auto &attrs = resource.GetAttributes(); + + auto name_it = attrs.find(semconv::service::kServiceName); + ASSERT_NE(name_it, attrs.end()); + EXPECT_EQ(opentelemetry::nostd::get(name_it->second), + std::string{"stage2-detector-service"}); + + unsetenv(kOtelServiceName); +} + +TEST(ServiceDetectorTest, DetectUsesServiceNameFallbackWhenEnvUnset) +{ + unsetenv(kOtelServiceName); + + opentelemetry::resource_detector::ServiceResourceDetector detector; + auto resource = detector.Detect(); + const auto &attrs = resource.GetAttributes(); + + auto name_it = attrs.find(semconv::service::kServiceName); + ASSERT_NE(name_it, attrs.end()); + EXPECT_EQ(opentelemetry::nostd::get(name_it->second), + opentelemetry::resource_detector::detail::GetServiceName()); +} diff --git a/resource_detectors/test/service_detector_utils_test.cc b/resource_detectors/test/service_detector_utils_test.cc new file mode 100644 index 000000000..8229f4410 --- /dev/null +++ b/resource_detectors/test/service_detector_utils_test.cc @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include + +#ifdef _MSC_VER +# include "opentelemetry/sdk/common/env_variables.h" +using opentelemetry::sdk::common::setenv; +using opentelemetry::sdk::common::unsetenv; +#else +# include +#endif + +#include "opentelemetry/resource_detectors/detail/service_detector_utils.h" + +namespace detail = opentelemetry::resource_detector::detail; + +namespace +{ +constexpr const char *kOtelServiceName = "OTEL_SERVICE_NAME"; + +bool IsUuidV4(const std::string &value) +{ + static const std::regex kUuidV4Pattern( + "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"); + return std::regex_match(value, kUuidV4Pattern); +} +} // namespace + +TEST(ServiceDetectorUtilsTest, GetServiceNameFromEnvironment) +{ + setenv(kOtelServiceName, "stage1-test-service", 1); + + EXPECT_EQ(detail::GetServiceName(), std::string{"stage1-test-service"}); + + unsetenv(kOtelServiceName); +} + +TEST(ServiceDetectorUtilsTest, GetServiceNameFallbackUsesUnknownServicePrefix) +{ + unsetenv(kOtelServiceName); + + const std::string service_name = detail::GetServiceName(); + if (service_name.rfind("unknown_service:", 0) == 0) + { + EXPECT_GT(service_name.size(), std::string{"unknown_service:"}.size()); + } + else + { + EXPECT_EQ(service_name, std::string{"unknown_service"}); + } +} + +TEST(ServiceDetectorUtilsTest, GenerateServiceInstanceIdIsUuidV4) +{ + const std::string instance_id = detail::GenerateServiceInstanceId(); + EXPECT_FALSE(instance_id.empty()); + EXPECT_TRUE(IsUuidV4(instance_id)); +} + +TEST(ServiceDetectorUtilsTest, GenerateServiceInstanceIdIsStableWithinProcess) +{ + const std::string first_id = detail::GenerateServiceInstanceId(); + const std::string second_id = detail::GenerateServiceInstanceId(); + EXPECT_EQ(first_id, second_id); +}