From 10e39907d15ded64043aacaa477a8ba7868659f4 Mon Sep 17 00:00:00 2001 From: Kyle-Neale Date: Tue, 26 May 2026 15:14:17 -0400 Subject: [PATCH 1/4] Add PHP single-step APM instrumentation support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a PHP arm to enable_apm_ssi() so a PHP app pushed with this buildpack alongside php_buildpack gets dd-library-php loaded just by setting DD_APM_INSTRUMENTATION_ENABLED=true — same shape as the existing Python, Node.js, and Ruby SSI flows. At app start, the PHP arm fetches datadog-setup.php from github.com/DataDog/dd-trace-php/releases and runs it against the php_buildpack-staged PHP binary. DD_TRACE_PHP_VERSION (default 1.19.2) pins the tracer per app. No tracer payload is bundled into the buildpack ZIP. Concurrency: .profile.d is sourced once per process, so web + each sidecar race to install. A lock dir (php-ssi.installing) plus a done marker (php-ssi.installed) gate the install so the winner runs once and losers block up to 120s for the done marker — otherwise PHP-FPM in the losing process would exec before ddtrace.so is in place. Also tightens Node.js and Ruby gating to require their respective buildpacks to be declared, not just npm/gem on PATH — this buildpack ships its own Ruby which was previously triggering ddtrace installs on non-Ruby apps. Refs FRAGENT-3448. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 1 - README.md | 3 +- lib/run-datadog.sh | 107 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 105 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 39d5e94b..a0bec272 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,3 @@ examples/*/node_modules/ examples/*/.bundle/ examples/cf-go-sample-app/main examples/cf-java-sample-app/target/ -examples/*/extensions/ diff --git a/README.md b/README.md index f0497540..4127bf2c 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,8 @@ Download and import the [relevant libraries][6] to send data. To learn more, che | Variable | Description | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `DD_APM_INSTRUMENTATION_ENABLED` | Use this option to automatically instrument your application, without any additional installation or configuration steps. See [Single Step APM Instrumentation](https://docs.datadoghq.com/tracing/trace_collection/single-step-apm/?tab=linuxhostorvm). | +| `DD_APM_INSTRUMENTATION_ENABLED` | Use this option to automatically instrument your application, without any additional installation or configuration steps. See [Single Step APM Instrumentation](https://docs.datadoghq.com/tracing/trace_collection/single-step-apm/?tab=linuxhostorvm). Supported runtimes: Python, Ruby, Node.js, and PHP. | +| `DD_TRACE_PHP_VERSION` | Pin the PHP tracer version installed by SSI. Defaults to `1.19.2`. | | `DD_WAIT_TRACE_AGENT` | Use this option to delay the startup of the application until the Trace Agent is ready. This option is especially useful for Golang apps. | ### Log collection diff --git a/lib/run-datadog.sh b/lib/run-datadog.sh index aaef1649..0ef7097c 100644 --- a/lib/run-datadog.sh +++ b/lib/run-datadog.sh @@ -225,11 +225,14 @@ enable_apm_ssi() { PIP_CMD="" PYTHON_BUILDPACK_DIR="" + NODEJS_BUILDPACK_DETECTED="" + RUBY_BUILDPACK_DETECTED="" + PHP_BUILDPACK_DETECTED="" # add all language buildpack bin folders to the PATH for dir in "${DEPS_DIR:-}"/*/; do dir="${dir%/}" # remove trailing slash - if grep -q -E "name: (python|ruby|go|nodejs|java)" "$dir/config.yml" >/dev/null 2>&1; then + if grep -q -E "name: (python|ruby|go|nodejs|java|php)" "$dir/config.yml" >/dev/null 2>&1; then buildpack_name=$(grep 'name:' $dir/config.yml | sed 's/name: //g') echo "Detected buildpack: $buildpack_name" @@ -239,7 +242,13 @@ enable_apm_ssi() { if ls $dir/bin/pip* > /dev/null 2>&1; then PIP_CMD=$(basename $(ls $dir/bin/pip* | head -1)) fi - + + elif [ "$buildpack_name" = "nodejs" ]; then + NODEJS_BUILDPACK_DETECTED="true" + elif [ "$buildpack_name" = "ruby" ]; then + RUBY_BUILDPACK_DETECTED="true" + elif [ "$buildpack_name" = "php" ]; then + PHP_BUILDPACK_DETECTED="true" fi export PATH=$PATH:$dir/bin fi @@ -255,7 +264,10 @@ enable_apm_ssi() { fi # nodejs - if which npm > /dev/null; then + # Gate on the nodejs buildpack being detected, not just the presence of npm: + # this buildpack installs its own Ruby (and may pick up other interpreters) + # which leaves npm/gem on PATH for apps that don't use that language. + if [ -n "$NODEJS_BUILDPACK_DETECTED" ] && which npm > /dev/null; then # nodejs version is <= 12 if [ "$(node -v | cut -d '.' -f 1 | sed 's/v//g')" -le 12 ]; then npm install dd-trace@latest-node12 @@ -267,9 +279,96 @@ enable_apm_ssi() { fi # ruby - if which gem > /dev/null; then + # Same gating rationale as nodejs above — `which gem` is always true on + # cflinuxfs4 because this buildpack installs its own Ruby in bin/supply. + if [ -n "$RUBY_BUILDPACK_DETECTED" ] && which gem > /dev/null; then gem install ddtrace fi + + # php + # PHP lives at $HOME/php/bin/php once php_buildpack has staged the app — it + # is not under DEPS_DIR. Install via datadog-setup.php in a subshell so a + # network or installer failure cannot abort the rest of enable_apm_ssi / + # main (matches the best-effort behavior of the other tracers above). + if [ -n "$PHP_BUILDPACK_DETECTED" ]; then + PHP_BIN="" + # Prefer the php_buildpack-staged binary over PATH so PHPRC derivation + # (below) lands at the runtime ini dir rather than a system PHP's. + if [ -x "${HOME}/php/bin/php" ]; then + PHP_BIN="${HOME}/php/bin/php" + elif command -v php > /dev/null 2>&1; then + PHP_BIN=$(command -v php) + fi + + if [ -z "${PHP_BIN}" ]; then + echo "PHP buildpack detected but no php binary found; skipping PHP SSI install" + else + DD_TRACE_PHP_VERSION="${DD_TRACE_PHP_VERSION:-1.19.2}" + # .profile.d is sourced once per process (web + each sidecar). The web + # process and any sidecars race here. We need three states, not two: + # - installing (lock dir held by the winner for the install duration) + # - installed (success marker; the winner sets this when done) + # - failed (no success marker after lock release) + # Losers MUST block until the winner finishes, otherwise PHP-FPM in the + # losing process exec()s before ddtrace.so is in place and runs without + # the extension loaded. + mkdir -p "${HOME}/.datadog" + PHP_SSI_LOCK_DIR="${HOME}/.datadog/php-ssi.installing" + PHP_SSI_DONE="${HOME}/.datadog/php-ssi.installed" + PHP_SSI_WAIT_TIMEOUT=120 + + if [ -f "${PHP_SSI_DONE}" ]; then + echo "PHP SSI: tracer already installed; skipping" + elif mkdir "${PHP_SSI_LOCK_DIR}" 2>/dev/null; then + echo "Installing dd-library-php ${DD_TRACE_PHP_VERSION} via datadog-setup.php (php=${PHP_BIN})" + if ( + set -e + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + # php_buildpack ships php.ini with @{HOME}/@{TMPDIR} placeholders that + # its finalize_rewrite.sh substitutes at app start. We source before + # that script, so run the rewrite ourselves; the later run is a no-op + # once placeholders are gone. + if [ -x "${HOME}/.bp/bin/rewrite" ] && command -v python3 > /dev/null 2>&1; then + PYTHONPATH="${HOME}/.bp/lib" python3 "${HOME}/.bp/bin/rewrite" "${HOME}/php/etc" + fi + curl -fL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 60 \ + -o "$tmp/datadog-setup.php" \ + "https://github.com/DataDog/dd-trace-php/releases/download/${DD_TRACE_PHP_VERSION}/datadog-setup.php" + PROFILING_FLAG="" + if [ "${DD_PROFILING_ENABLED:-}" = "true" ]; then + PROFILING_FLAG="--enable-profiling" + fi + # PHPRC points at the runtime php.ini so datadog-setup.php's `php -i` + # discovery succeeds (php_buildpack bakes a build-time path into the + # binary and leaves PHPRC unset). --install-dir picks a writable path + # under the app home; the default /opt/datadog isn't writable as vcap. + PHPRC="$(dirname "${PHP_BIN}")/../etc" \ + "${PHP_BIN}" "$tmp/datadog-setup.php" \ + --php-bin="${PHP_BIN}" \ + --install-dir="${HOME}/.datadog/dd-library-php" \ + $PROFILING_FLAG + ); then + touch "${PHP_SSI_DONE}" + else + echo "PHP SSI: install failed, app will start uninstrumented" + fi + rmdir "${PHP_SSI_LOCK_DIR}" 2>/dev/null + else + echo "PHP SSI: install in progress on another process; waiting up to ${PHP_SSI_WAIT_TIMEOUT}s" + WAITED=0 + while [ -d "${PHP_SSI_LOCK_DIR}" ] && [ "${WAITED}" -lt "${PHP_SSI_WAIT_TIMEOUT}" ]; do + sleep 1 + WAITED=$((WAITED + 1)) + done + if [ -f "${PHP_SSI_DONE}" ]; then + echo "PHP SSI: install completed by another process" + else + echo "PHP SSI: install did not complete within ${PHP_SSI_WAIT_TIMEOUT}s; app may start uninstrumented" + fi + fi + fi + fi } main() { From f942f96604ad14b0273bd827079e6cddfd40fcf2 Mon Sep 17 00:00:00 2001 From: Kyle-Neale Date: Tue, 30 Jun 2026 10:50:02 -0400 Subject: [PATCH 2/4] PHP SSI: log php binary detection and setup.php URL Mirrors the python branch's "detected pip command:" line so the chosen PHP binary is visible in `cf logs`, and echoes the datadog-setup.php release URL before the curl so 404s (bad DD_TRACE_PHP_VERSION) and network failures point at the cause directly. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/run-datadog.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/run-datadog.sh b/lib/run-datadog.sh index 0ef7097c..d87e0451 100644 --- a/lib/run-datadog.sh +++ b/lib/run-datadog.sh @@ -303,6 +303,7 @@ enable_apm_ssi() { if [ -z "${PHP_BIN}" ]; then echo "PHP buildpack detected but no php binary found; skipping PHP SSI install" else + echo "detected php binary: $PHP_BIN" DD_TRACE_PHP_VERSION="${DD_TRACE_PHP_VERSION:-1.19.2}" # .profile.d is sourced once per process (web + each sidecar). The web # process and any sidecars race here. We need three states, not two: @@ -332,9 +333,11 @@ enable_apm_ssi() { if [ -x "${HOME}/.bp/bin/rewrite" ] && command -v python3 > /dev/null 2>&1; then PYTHONPATH="${HOME}/.bp/lib" python3 "${HOME}/.bp/bin/rewrite" "${HOME}/php/etc" fi + DD_SETUP_URL="https://github.com/DataDog/dd-trace-php/releases/download/${DD_TRACE_PHP_VERSION}/datadog-setup.php" + echo "PHP SSI: fetching $DD_SETUP_URL" curl -fL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 60 \ -o "$tmp/datadog-setup.php" \ - "https://github.com/DataDog/dd-trace-php/releases/download/${DD_TRACE_PHP_VERSION}/datadog-setup.php" + "$DD_SETUP_URL" PROFILING_FLAG="" if [ "${DD_PROFILING_ENABLED:-}" = "true" ]; then PROFILING_FLAG="--enable-profiling" From a2d15106635b2b4ec95bb463c98686404c7c1349 Mon Sep 17 00:00:00 2001 From: Kyle-Neale Date: Tue, 30 Jun 2026 10:50:13 -0400 Subject: [PATCH 3/4] PHP sample app: vendor at lib/vendor to match php_buildpack LIBDIR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit php_buildpack runs `composer install` during staging and places vendor at $LIBDIR/vendor (default LIBDIR=lib), so the container ends up with /home/vcap/app/lib/vendor/. Index.php was requiring __DIR__/../vendor/autoload.php which doesn't exist there, so every request 500'd in the autoloader before the app could send custom metrics via DataDog\DogStatsd. Pin composer's vendor-dir to lib/vendor so local builds and the staging-time install land in the same place, and update the require path to match. `cf push` now works standalone for online envs — no ./build.sh required, in line with the other sample apps. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 1 + examples/cf-php-sample-app/composer.json | 3 ++- examples/cf-php-sample-app/htdocs/index.php | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a0bec272..a0c435c3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ tmp # sample app vendor artifacts (regenerated by ./build.sh) examples/*/vendor/ +examples/*/lib/vendor/ examples/*/node_modules/ examples/*/.bundle/ examples/cf-go-sample-app/main diff --git a/examples/cf-php-sample-app/composer.json b/examples/cf-php-sample-app/composer.json index 6fe18737..ec819504 100644 --- a/examples/cf-php-sample-app/composer.json +++ b/examples/cf-php-sample-app/composer.json @@ -10,6 +10,7 @@ "php": "8.2", "ext-sockets": "8.2" }, - "sort-packages": true + "sort-packages": true, + "vendor-dir": "lib/vendor" } } diff --git a/examples/cf-php-sample-app/htdocs/index.php b/examples/cf-php-sample-app/htdocs/index.php index 8a5d7b06..477f28de 100644 --- a/examples/cf-php-sample-app/htdocs/index.php +++ b/examples/cf-php-sample-app/htdocs/index.php @@ -1,5 +1,5 @@ Date: Tue, 30 Jun 2026 10:51:31 -0400 Subject: [PATCH 4/4] PHP sample app: update build.sh to clean lib/vendor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the vendor-dir change — `rm -rf vendor` was a no-op once composer was pinned to write into lib/vendor. Point the cleanup at the right path so re-running build.sh from a populated worktree actually starts from a clean slate. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/cf-php-sample-app/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cf-php-sample-app/build.sh b/examples/cf-php-sample-app/build.sh index 402fc813..5d6f2a6e 100755 --- a/examples/cf-php-sample-app/build.sh +++ b/examples/cf-php-sample-app/build.sh @@ -7,5 +7,5 @@ set -euo pipefail # Datadog Cloud Foundry buildpack itself when DD_APM_INSTRUMENTATION_ENABLED # is set. -rm -rf vendor +rm -rf lib/vendor composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader