Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ cc_library(
"src/datadog/trace_segment.cpp",
"src/datadog/trace_source.cpp",
"src/datadog/tracer.cpp",
"src/datadog/stable_config.cpp",
"src/datadog/stable_config.h",
"src/datadog/stable_config_source.h",
"src/datadog/yaml_parser.cpp",
"src/datadog/yaml_parser.h",
"src/datadog/tracer_config.cpp",
"src/datadog/version.cpp",
"src/datadog/w3c_propagation.cpp",
Expand Down Expand Up @@ -159,5 +164,10 @@ cc_library(
deps = [
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:optional",
# yaml-cpp is an implementation detail; CMake links it PRIVATE.
# rules_cc's `implementation_deps` would be the analog, but the
# version pinned in MODULE.bazel does not yet support it, so we
# keep yaml-cpp in `deps` and document the asymmetry here.
"@yaml_cpp//:yaml-cpp",
],
)
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
include(cmake/compiler/gcc.cmake)
endif ()

# yaml-cpp must be included AFTER the compiler setup above, because
# clang.cmake sets -stdlib=libc++ which yaml-cpp needs to inherit.
include(cmake/deps/yaml.cmake)

if (DD_TRACE_BUILD_FUZZERS)
add_subdirectory(fuzz)
endif ()
Expand Down Expand Up @@ -212,6 +216,8 @@ target_sources(dd-trace-cpp-objects
src/datadog/tags.cpp
src/datadog/tag_propagation.cpp
src/datadog/threaded_event_scheduler.cpp
src/datadog/stable_config.cpp
src/datadog/yaml_parser.cpp
src/datadog/tracer_config.cpp
src/datadog/tracer.cpp
src/datadog/trace_id.cpp
Expand Down Expand Up @@ -244,6 +250,7 @@ target_link_libraries(dd-trace-cpp-objects
Threads::Threads
PRIVATE
dd-trace-cpp::specs
$<BUILD_INTERFACE:yaml-cpp>
)

set_target_properties(dd-trace-cpp-objects
Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ bazel_dep(name = "abseil-cpp", version = "20260107.1", repo_name = "com_google_a
bazel_dep(name = "bazel_skylib", version = "1.9.0")
bazel_dep(name = "platforms", version = "1.1.0")
bazel_dep(name = "rules_cc", version = "0.2.18")
bazel_dep(name = "yaml-cpp", version = "0.8.0.bcr.1", repo_name = "yaml_cpp")
10 changes: 10 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ http_archive(
urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.2.14/rules_cc-0.2.14.tar.gz"],
)

# This pulls the upstream yaml-cpp 0.8.0 tarball directly.
# MODULE.bazel uses "0.8.0.bcr.1" because that is the BCR (Bazel Central
# Registry) patched release; the underlying library version is the same 0.8.0.
http_archive(
name = "yaml_cpp",
sha256 = "fbe74bbdcee21d656715688706da3c8becfd946d92cd44705cc6098bb23b3a16",
strip_prefix = "yaml-cpp-0.8.0",
urls = ["https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.8.0.tar.gz"],
)

load("@rules_cc//cc:extensions.bzl", "compatibility_proxy_repo")

compatibility_proxy_repo()
36 changes: 36 additions & 0 deletions cmake/deps/yaml.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
include(FetchContent)

set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "" FORCE)
set(YAML_CPP_INSTALL OFF CACHE BOOL "" FORCE)
set(YAML_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)

FetchContent_Declare(yaml-cpp
URL https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.8.0.tar.gz
URL_HASH SHA256=fbe74bbdcee21d656715688706da3c8becfd946d92cd44705cc6098bb23b3a16
EXCLUDE_FROM_ALL
SYSTEM
)

# yaml-cpp 0.8.0 uses cmake_minimum_required(VERSION 3.4) which is rejected
# by CMake >= 4.0. Allow it via CMAKE_POLICY_VERSION_MINIMUM.
set(_yaml_saved_policy_min "${CMAKE_POLICY_VERSION_MINIMUM}")
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
FetchContent_MakeAvailable(yaml-cpp)
set(CMAKE_POLICY_VERSION_MINIMUM "${_yaml_saved_policy_min}")

# Ensure yaml-cpp is compiled with the same sanitizer flags as the main
# project. Without this, MSVC ASAN annotation mismatches cause linker
# errors (LNK2038). We add only the sanitizer flags — not the full set
# of compile options from dd-trace-cpp-specs (which includes -WX and
# warning levels that would break yaml-cpp's own code).
if (DD_TRACE_ENABLE_SANITIZE AND TARGET yaml-cpp)
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC"))
target_compile_options(yaml-cpp PRIVATE /fsanitize=address)
target_link_options(yaml-cpp PRIVATE /fsanitize=address)
else()
target_compile_options(yaml-cpp PRIVATE -fsanitize=address,undefined)
target_link_options(yaml-cpp PRIVATE -fsanitize=address,undefined)
endif()
endif()
1 change: 0 additions & 1 deletion include/datadog/telemetry/configuration.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#pragma once

#include <datadog/config.h>
#include <datadog/expected.h>
#include <datadog/optional.h>
#include <datadog/telemetry/product.h>
Expand Down
1 change: 1 addition & 0 deletions include/datadog/tracer_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <cstddef>
#include <memory>
#include <string>
#include <variant>
#include <vector>

Expand Down
115 changes: 115 additions & 0 deletions src/datadog/stable_config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "stable_config.h"

#include <sys/stat.h>

#include <cerrno>
#include <cstddef>
#include <cstring>
#include <fstream>
#include <ostream>
#include <string>

#include "yaml_parser.h"

namespace datadog {
namespace tracing {
namespace {

// Maximum file size accepted for stable configuration files: 256KB.
// This is a file I/O concern, not a parser concern, so it lives here rather
// than in yaml_parser.h.
constexpr std::size_t kMaxYamlFileSize = 256 * 1024;

} // namespace

// Read a file and parse it into a StableConfig. Logs warnings on errors.
// Returns an empty StableConfig if the file doesn't exist or can't be read.
StableConfig load_one(const std::string& path, Logger& logger) {
StableConfig result;

// Probe for existence before opening so we can distinguish a missing file
// (silent no-op) from a present-but-unreadable file (permission / I/O
// error worth logging).
struct stat st;
if (::stat(path.c_str(), &st) != 0) {
// Most often ENOENT — file simply isn't there. Silent skip.
return result;
}

std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
const int err = errno;
logger.log_error([&path, err](std::ostream& log) {
log << "Stable config: file " << path << " exists but could not be "
<< "opened (errno " << err << ": " << std::strerror(err)
<< "); skipping.";
});
return result;
}

// Check file size.
const auto size = file.tellg();
if (size < 0) {
logger.log_error([&path](std::ostream& log) {
log << "Stable config: unable to determine size of " << path
<< "; skipping.";
});
return result;
}

if (static_cast<std::size_t>(size) > kMaxYamlFileSize) {
logger.log_error([&path](std::ostream& log) {
log << "Stable config: file " << path
<< " exceeds 256KB size limit; skipping.";
});
return result;
}

file.seekg(0);
std::string content(static_cast<std::size_t>(size), '\0');
if (!file.read(content.data(), size)) {
logger.log_error([&path](std::ostream& log) {
log << "Stable config: unable to read " << path << "; skipping.";
});
return result;
}

YamlParseResult parsed;
if (parse_yaml(content, parsed) != YamlParseStatus::OK) {
logger.log_error([&path](std::ostream& log) {
log << "Stable config: malformed YAML in " << path << "; skipping.";
});
return {}; // Return empty config on parse error.
}

result.config_id = std::move(parsed.config_id);
result.values = std::move(parsed.values);
return result;
}

StableConfigPaths get_stable_config_paths() {
return {
"/etc/datadog-agent/application_monitoring.yaml",
"/etc/datadog-agent/managed/datadog-agent/stable/"
"application_monitoring.yaml",
};
}

Optional<std::string> StableConfig::lookup(const std::string& key) const {
auto it = values.find(key);
if (it != values.end()) {
return it->second;
}
return nullopt;
}

StableConfigs load_stable_configs(Logger& logger) {
const auto paths = get_stable_config_paths();
StableConfigs configs;
configs.local = load_one(paths.local_path, logger);
configs.fleet = load_one(paths.fleet_path, logger);
return configs;
}

} // namespace tracing
} // namespace datadog
65 changes: 65 additions & 0 deletions src/datadog/stable_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#pragma once

// This component provides support for reading "stable configuration" from
// YAML files on disk. Two files are read at tracer initialization:
//
// - A "local" (user-managed) file
// - A "fleet" (fleet-managed) file
//
// Each file may contain a flat map of DD_* environment variable names to
// scalar values under the `apm_configuration_default` key. These values
// participate in configuration precedence:
//
// fleet_stable > env > user/code > local_stable > default

#include <datadog/logger.h>
#include <datadog/optional.h>

#include <string>
#include <unordered_map>

namespace datadog {
namespace tracing {

// Paths to the two stable configuration files.
struct StableConfigPaths {
std::string local_path;
std::string fleet_path;
};

// Return the platform-specific paths for stable configuration files.
// Currently Linux-only — Windows support deferred to a follow-up.
StableConfigPaths get_stable_config_paths();

// Parsed contents of one stable configuration file.
struct StableConfig {
// Config ID from the file (optional, for telemetry).
Optional<std::string> config_id;

// Map of environment variable names (e.g. "DD_SERVICE") to string values.
std::unordered_map<std::string, std::string> values;

// Look up a config key, returning nullopt if not present.
Optional<std::string> lookup(const std::string& key) const;
};

// Load and parse a single stable config file at the given path. Exposed
// here (not in an anonymous namespace) so tests can exercise the
// single-file edge cases — oversized, malformed, missing, unreadable —
// against arbitrary temporary paths. Production code uses
// load_stable_configs() below, which composes load_one over the two
// platform paths.
StableConfig load_one(const std::string& path, Logger& logger);

// Holds both the local and fleet stable configs.
struct StableConfigs {
StableConfig local;
StableConfig fleet;
};

// Load and parse both stable configuration files.
// Returns empty configs (no error) if files don't exist.
StableConfigs load_stable_configs(Logger& logger);

} // namespace tracing
} // namespace datadog
52 changes: 52 additions & 0 deletions src/datadog/stable_config_source.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#pragma once

// `ConfigSource` adapters that wrap a parsed `StableConfig` so it can
// participate in the `ConfigProvider` source list alongside the
// environment and any future sources.

#include <datadog/config.h>
#include <datadog/optional.h>
#include <datadog/string_view.h>

#include <string>

#include "config_source.h"
#include "stable_config.h"

namespace datadog {
namespace tracing {

class LocalStableConfigSource : public ConfigSource {
const StableConfig* cfg_;

public:
explicit LocalStableConfigSource(const StableConfig& cfg) : cfg_(&cfg) {}

Optional<std::string> lookup(StringView key) const override {
return cfg_->lookup(std::string{key});
}

ConfigMetadata::Origin origin() const override {
return ConfigMetadata::Origin::LOCAL_STABLE_CONFIG;
}
};

class FleetStableConfigSource : public ConfigSource {
const StableConfig* cfg_;

public:
explicit FleetStableConfigSource(const StableConfig& cfg) : cfg_(&cfg) {}

Optional<std::string> lookup(StringView key) const override {
return cfg_->lookup(std::string{key});
}

ConfigMetadata::Origin origin() const override {
return ConfigMetadata::Origin::FLEET_STABLE_CONFIG;
}

Optional<std::string> config_id() const override { return cfg_->config_id; }
};

} // namespace tracing
} // namespace datadog
Loading