diff --git a/.gitignore b/.gitignore index 39d5e94b..a0c435c3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,8 +11,8 @@ 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 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/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 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 @@ /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,99 @@ 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 + 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: + # - 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 + 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" \ + "$DD_SETUP_URL" + 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() {