├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── check-style.yml │ ├── fossa.yml │ ├── nightly.yml │ ├── ossf-scorecard.yml │ ├── publish-php-debug-images.yml │ └── split.yml ├── .gitignore ├── .gitsplit.yml ├── CODEOWNERS ├── DEVELOPMENT.md ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── docker-compose.yaml ├── docker ├── Dockerfile.alpine └── Dockerfile.debian └── ext ├── .clang-format ├── .gitignore ├── LICENSE ├── build.sh ├── config.m4 ├── config.w32 ├── format.sh ├── opentelemetry.c ├── opentelemetry.stub.php ├── opentelemetry_arginfo.h ├── otel_observer.c ├── otel_observer.h ├── package.xml ├── php_opentelemetry.h └── tests ├── 001.phpt ├── 002.phpt ├── 003.phpt ├── 004.phpt ├── 005.phpt ├── 006.phpt ├── 007.phpt ├── 008.phpt ├── 009.phpt ├── 010.phpt ├── calling_die_lead_to_segfault.phpt ├── disable_does_nothing_with_bad_data.phpt ├── disable_hook_function_validation.phpt ├── disabled_with_conflicting_extension.phpt ├── expand_params.phpt ├── expand_params_extend.phpt ├── expand_params_extend_internal.phpt ├── expand_params_extend_internal_long.phpt ├── expand_params_extend_limit.phpt ├── expand_params_extra.phpt ├── expand_params_internal.phpt ├── function_closure.phpt ├── function_first_class_callable.phpt ├── function_internal.phpt ├── hooks_throw_exception.phpt ├── hooks_throw_exception_error_handler.phpt ├── hooks_throw_exception_for_failed_call.phpt ├── ini_set.phpt ├── invalid_post_callback_signature.phpt ├── invalid_pre_callback_signature.phpt ├── invalid_pre_callback_signature_with_function.phpt ├── mocks ├── SpanAttribute.php ├── WithSpan.php ├── WithSpanHandler.php └── WithSpanHandlerDumpAttributes.php ├── modify_extra_params.phpt ├── modify_extra_params_internal.phpt ├── modify_named_params.phpt ├── modify_named_params_internal.phpt ├── modify_named_params_invalid.phpt ├── modify_named_params_twice.phpt ├── modify_params.phpt ├── multiple_hooks_modify_params.phpt ├── multiple_hooks_modify_returnvalue.phpt ├── post_hook_return_ignored_without_type.phpt ├── post_hook_returns_cloned_modified_object.phpt ├── post_hook_throws_exception.phpt ├── post_hook_type_error.phpt ├── post_hooks_after_die.phpt ├── reimplemented_interface.phpt ├── return_expanded_params.phpt ├── return_expanded_params_internal.phpt ├── return_params.phpt ├── return_params_internal.phpt ├── return_reduced_params.phpt ├── span_attribute ├── attribute_on_interface.phpt ├── function_params.phpt ├── function_params_non_simple.phpt ├── method_params.phpt └── span_attribute_attribute_priority.phpt ├── static_method.phpt ├── unwind_exit.phpt └── with_span ├── attribute_named_params_passed_to_pre_hook.phpt ├── attribute_on_function.phpt ├── attribute_on_interface.phpt ├── attribute_on_interface_with_params.phpt ├── attribute_on_method.phpt ├── attribute_params_passed_to_pre_hook.phpt ├── attribute_params_skipped_if_hooked.phpt ├── autoloaded_handler.phpt ├── customize_handlers.phpt ├── disable.phpt ├── handler_not_found.phpt └── invalid_callback.phpt /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | labels: bug 5 | --- 6 | 7 | **Describe your environment** Describe any aspect of your environment relevant to the problem, including your php version (`php -v` will tell you your current version), version numbers of installed dependencies, information about your cloud hosting provider, etc. If you're reporting a problem with a specific version of a library in this repo, please check whether the problem has been fixed on master. 8 | 9 | **Steps to reproduce** 10 | Describe exactly how to reproduce the error. Include a code sample if applicable. 11 | 12 | **What is the expected behavior?** 13 | What did you expect to see? 14 | 15 | **What is the actual behavior?** 16 | What did you see instead? 17 | 18 | **Additional context** 19 | Add any other context about the problem here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | labels: feature-request 5 | --- 6 | 7 | Before opening a feature request against this repo, consider whether the feature should/could be implemented in the [other OpenTelemetry client libraries](https://github.com/open-telemetry). If so, please [open an issue in opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification/issues/new) first. 8 | 9 | **Is your feature request related to a problem?** 10 | If so, provide a concise description of the problem. 11 | 12 | **Describe the solution you'd like** 13 | What do you want to happen instead? What is the expected behavior? 14 | 15 | **Describe alternatives you've considered** 16 | Which alternative solutions or features have you considered? 17 | 18 | **Additional context** 19 | Add any other context about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build, test, release 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ main ] 7 | 8 | defaults: 9 | run: 10 | working-directory: ext 11 | 12 | jobs: 13 | linux: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | php: ['8.0', '8.1', '8.2', '8.3', '8.4'] 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | - name: Setup PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: ${{ matrix.php }} 27 | - name: Build 28 | run: | 29 | phpize 30 | ./configure 31 | make 32 | - name: Test 33 | env: 34 | TEST_PHP_ARGS: "-q" #do not try to submit failures 35 | run: make test TESTS=--show-diff 36 | 37 | macos: 38 | runs-on: macos-latest 39 | strategy: 40 | fail-fast: false 41 | matrix: 42 | php: [ '8.0', '8.1', '8.2', '8.3', '8.4' ] 43 | 44 | steps: 45 | - name: Checkout 46 | uses: actions/checkout@v4 47 | 48 | - name: Setup PHP 49 | uses: shivammathur/setup-php@v2 50 | with: 51 | php-version: ${{ matrix.php }} 52 | 53 | - name: build 54 | run: | 55 | phpize 56 | ./configure 57 | make 58 | 59 | - name: test 60 | env: 61 | TEST_PHP_ARGS: "-q" #do not try to submit failures 62 | run: make test TESTS=--show-diff 63 | 64 | pecl-package: 65 | runs-on: ubuntu-latest 66 | container: 67 | image: php:8.3-cli 68 | steps: 69 | - uses: actions/checkout@v4 70 | 71 | - name: Package and copy 72 | run: | 73 | mkdir binaries 74 | pear package-validate 75 | pear package 76 | cp opentelemetry-*.tgz binaries/ 77 | - name: Upload artifacts 78 | uses: actions/upload-artifact@v4 79 | with: 80 | name: opentelemetry-pecl 81 | path: ext/binaries/*.tgz 82 | if-no-files-found: error 83 | 84 | windows: 85 | runs-on: windows-2022 86 | continue-on-error: false 87 | strategy: 88 | fail-fast: false 89 | matrix: 90 | php: ['8.0', '8.1', '8.2', '8.3', '8.4'] 91 | ts: ['ts', 'nts'] 92 | steps: 93 | - name: Checkout 94 | uses: actions/checkout@v4 95 | - name: Install PHP ${{ matrix.php }}-${{ matrix.ts }} 96 | id: setup-php-sdk 97 | uses: php/setup-php-sdk@v0.10 98 | with: 99 | version: ${{ matrix.php }} 100 | arch: x64 101 | ts: ${{ matrix.ts }} 102 | - name: Install dependencies 103 | uses: ilammy/msvc-dev-cmd@v1 104 | with: 105 | arch: x64 106 | toolset: ${{ steps.setup-php-sdk.outputs.toolset }} 107 | - name: Build 108 | run: | 109 | phpize 110 | ./configure --enable-opentelemetry --with-prefix=${{ steps.setup-php-sdk.outputs.prefix }} 111 | nmake 112 | - name: Test 113 | env: 114 | TEST_PHP_ARGS: "-q" 115 | run: nmake test TESTS=--show-diff 116 | - name: Get Compiler Version + Build Name 117 | shell: bash 118 | run: | 119 | case "$VisualStudioVersion" in 120 | 16.*) COMPILER="vs16";; 121 | 17.*) COMPILER="vs17";; 122 | *) echo "Unknown MSVC version: $VisualStudioVersion"; exit 1;; 123 | esac 124 | echo "Detected Compiler: $COMPILER" 125 | SAFE_REF_NAME="${GITHUB_REF_NAME//\//-}" 126 | BUILD_NAME="php_opentelemetry-${SAFE_REF_NAME}-${{matrix.php}}-${{matrix.ts}}-$COMPILER-x64" 127 | echo "Build Name: $BUILD_NAME" 128 | echo "BUILD_NAME=$BUILD_NAME" >> $GITHUB_ENV 129 | 130 | - name: Rename and copy binaries 131 | run: | 132 | md binaries\${{env.BUILD_NAME}} 133 | $file = Get-ChildItem -Path x64 -Recurse -Filter php_opentelemetry.dll 134 | Copy-Item -Path $file.FullName -Destination "binaries\${{env.BUILD_NAME}}\${{env.BUILD_NAME}}.dll" 135 | - name: Find 136 | run: | 137 | Get-ChildItem -Path binaries -Recurse -Force | ForEach-Object { $_.FullName } 138 | - name: Upload artifacts 139 | uses: actions/upload-artifact@v4 140 | with: 141 | name: ${{env.BUILD_NAME}} 142 | path: ext\binaries\${{env.BUILD_NAME}}\${{env.BUILD_NAME}}.dll 143 | if-no-files-found: error 144 | 145 | release-if-tag: 146 | runs-on: ubuntu-latest 147 | if: startsWith(github.ref, 'refs/tags/') 148 | needs: [linux, pecl-package, windows] 149 | continue-on-error: false 150 | steps: 151 | - name: Checkout 152 | uses: actions/checkout@v4 153 | - name: download-artifacts 154 | uses: actions/download-artifact@v4 155 | with: 156 | path: ext/artifacts 157 | - name: zip 158 | run: | 159 | cd artifacts 160 | find . -maxdepth 1 -type d -exec zip -jr {}.zip {} \; 161 | - name: Release 162 | uses: softprops/action-gh-release@v2 163 | with: 164 | generate_release_notes: true 165 | draft: true 166 | files: "ext/artifacts/*.zip" 167 | -------------------------------------------------------------------------------- /.github/workflows/check-style.yml: -------------------------------------------------------------------------------- 1 | name: clang-format 2 | on: [push, pull_request] 3 | jobs: 4 | formatting-check: 5 | name: Formatting Check 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - name: Run clang-format style check 10 | uses: jidicula/clang-format-action@v4.15.0 11 | with: 12 | clang-format-version: '16' 13 | check-path: ext 14 | -------------------------------------------------------------------------------- /.github/workflows/fossa.yml: -------------------------------------------------------------------------------- 1 | name: FOSSA scanning 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | fossa: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 # v4.2.2 16 | 17 | - uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0 18 | with: 19 | api-key: ${{secrets.FOSSA_API_KEY}} 20 | team: OpenTelemetry 21 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Build and test against PHP nightly 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ main ] 7 | schedule: 8 | - cron: '37 5 * * *' 9 | 10 | defaults: 11 | run: 12 | working-directory: ext 13 | 14 | jobs: 15 | nightly: 16 | if: github.repository == 'open-telemetry/opentelemetry-php-instrumentation' 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: 8.5 23 | - name: Build 24 | run: | 25 | phpize 26 | ./configure 27 | make 28 | - name: Test 29 | env: 30 | TEST_PHP_ARGS: "-q" 31 | run: make test TESTS=--show-diff 32 | -------------------------------------------------------------------------------- /.github/workflows/ossf-scorecard.yml: -------------------------------------------------------------------------------- 1 | name: OSSF Scorecard 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | schedule: 8 | - cron: "30 3 * * 0" # once a week 9 | workflow_dispatch: 10 | 11 | permissions: read-all 12 | 13 | jobs: 14 | analysis: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | # Needed for Code scanning upload 18 | security-events: write 19 | # Needed for GitHub OIDC token if publish_results is true 20 | id-token: write 21 | steps: 22 | - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 # v4.2.2 23 | with: 24 | persist-credentials: false 25 | 26 | - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 27 | with: 28 | results_file: results.sarif 29 | results_format: sarif 30 | publish_results: true 31 | 32 | # Upload the results as artifacts (optional). Commenting out will disable 33 | # uploads of run results in SARIF format to the repository Actions tab. 34 | # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts 35 | - name: "Upload artifact" 36 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 37 | with: 38 | name: SARIF file 39 | path: results.sarif 40 | retention-days: 5 41 | 42 | # Upload the results to GitHub's code scanning dashboard (optional). 43 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard 44 | - name: "Upload to code-scanning" 45 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 46 | with: 47 | sarif_file: results.sarif 48 | -------------------------------------------------------------------------------- /.github/workflows/publish-php-debug-images.yml: -------------------------------------------------------------------------------- 1 | name: build-debug-image 2 | on: 3 | push: 4 | paths: 5 | - '.github/workflows/publish-php-debug-images.yml' 6 | schedule: 7 | - cron: "0 0 * * 0" 8 | workflow_dispatch: 9 | jobs: 10 | push_to_registry: 11 | if: github.repository_owner == 'open-telemetry' #do not run in forks 12 | name: Build image 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | php-version: ['8.0', '8.1', '8.2', '8.3'] 17 | os: ['debian', 'alpine'] 18 | runs-on: ubuntu-latest 19 | permissions: 20 | packages: write 21 | contents: read 22 | steps: 23 | 24 | - name: check out the repo 25 | uses: actions/checkout@v4 26 | 27 | - name: Set up Docker Buildx 28 | uses: docker/setup-buildx-action@v3 29 | 30 | - name: Login to GitHub Container Registry 31 | uses: docker/login-action@v3 32 | with: 33 | registry: ghcr.io 34 | username: ${{ github.repository_owner }} 35 | password: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - name: Fetch latest PHP version 38 | run: | 39 | PHP_VERSION=$(curl https://www.php.net/releases/index.php?json\&version=${{ matrix.php-version }}\&max=1 | jq -r 'keys[0]') 40 | echo "PHP_VERSION=${PHP_VERSION}" >> $GITHUB_ENV 41 | echo "Latest version in ${{ matrix.php-version }} is ${PHP_VERSION}" 42 | 43 | - name: Build and push ${{ env.PHP_VERSION }} as ${{ matrix.php-version }}-${{ matrix.os }} to ghcr.io 44 | uses: docker/build-push-action@v6 45 | with: 46 | push: true 47 | file: docker/Dockerfile.${{ matrix.os }} 48 | build-args: | 49 | PHP_VERSION=${{ env.PHP_VERSION }} 50 | tags: ghcr.io/open-telemetry/opentelemetry-php-instrumentation/php:${{ matrix.php-version }}-${{ matrix.os }}-debug 51 | -------------------------------------------------------------------------------- /.github/workflows/split.yml: -------------------------------------------------------------------------------- 1 | name: gitsplit 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - split 7 | release: 8 | types: [published] 9 | create: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | gitsplit: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | - name: Split repositories 20 | uses: docker://jderusse/gitsplit:latest 21 | with: 22 | args: gitsplit 23 | env: 24 | GH_TOKEN: ${{ secrets.GITSPLIT_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode/ 3 | *.tgz 4 | 5 | -------------------------------------------------------------------------------- /.gitsplit.yml: -------------------------------------------------------------------------------- 1 | # Path to a cache directory Used to speed up the split over time by reusing git's objects 2 | cache_url: "/cache/gitsplit" 3 | 4 | # Path to the repository to split (default = current path) 5 | project_url: "https://github.com/open-telemetry/opentelemetry-php-instrumentation.git" 6 | 7 | # List of splits. 8 | splits: 9 | - prefix: "ext" 10 | target: "https://${GH_TOKEN}@github.com/opentelemetry-php/ext-opentelemetry.git" 11 | 12 | # List of references to split (defined as regexp) 13 | origins: 14 | - ^main$ 15 | - ^split$ 16 | - ^\d+\.\d+\.\d+.*$ 17 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | ##################################################### 2 | # 3 | # List of approvers for OpenTelemetry PHP API/SDK 4 | # 5 | ##################################################### 6 | # 7 | # Learn about membership in OpenTelemetry community: 8 | # https://github.com/open-telemetry/community/blob/master/community-membership.md 9 | # 10 | # 11 | # Learn about CODEOWNERS file format: 12 | # https://help.github.com/en/articles/about-code-owners 13 | # 14 | 15 | * @open-telemetry/php-approvers 16 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Setting up the build environment 2 | 3 | By default, an alpine and debian-based docker image with debug enabled is used. 4 | 5 | ```shell 6 | $ PHP_VERSION=x.y.z docker compose build debian 7 | # or 8 | $ PHP_VERSION=x.y.z docker compose build alpine 9 | ``` 10 | 11 | You can add extra configure flags, but some may require extra dependencies to be installed. 12 | 13 | You can also change the PHP version: 14 | 15 | ```shell 16 | $ docker compose build --build-arg PHP_CONFIG_OPTS="--enable-debug --enable-zts" --build-arg PHP_VERSION=8.0.23 [debian|alpine] 17 | ``` 18 | 19 | The latest PHP version can be found on: https://www.php.net/releases/index.php 20 | 21 | # Building the extension 22 | 23 | First, shell into the container: 24 | ```shell 25 | $ docker compose run debian 26 | ``` 27 | 28 | ## Using pear/pecl 29 | 30 | ```shell 31 | $ pear build 32 | ``` 33 | 34 | ## From source code 35 | ```shell 36 | $ phpize 37 | $ ./configure 38 | $ make 39 | $ make test 40 | $ make install 41 | $ make clean 42 | ``` 43 | 44 | This will build `opentelemetry.so` and install it into php modules dir (but not enable it). 45 | 46 | To clean up, especially between builds with different PHP versions and/or build options: 47 | 48 | ```shell 49 | $ git clean -Xn 50 | # looks ok? then 51 | $ git clean -Xf 52 | ``` 53 | 54 | or `make clean` from in the container. 55 | 56 | # Enabling the extension 57 | 58 | ```shell 59 | $ php -dextension=opentelemetry -m 60 | ``` 61 | 62 | Or via .ini: 63 | ```shell 64 | $ echo 'extension=opentelemetry' > $(php-config --ini-dir)/opentelemetry.ini 65 | ``` 66 | 67 | If the extension is successfully installed, you will see it listed in the output of `php -m`. 68 | 69 | # Code formatting 70 | Run `make format` before committing changes, which will run `clang-format -i *.c *.h`. 71 | 72 | # Debugging 73 | 74 | ## Locally 75 | 76 | In order to debug extension, php engine has to be compiled in debug mode. All needed steps 77 | are described on the following page: https://www.zend.com/resources/php-extensions/setting-up-your-php-build-environment 78 | Above page describes building environment for linux. On other systems (like MacOS), some additional steps might be 79 | needed related to installing and configuring some dependencies. 80 | 81 | Next is to add extension to ini file as described above. 82 | 83 | After finishing all above steps, you can start a debugging session. There are few debuggers that 84 | can be used for debugging process, lldb, gdb or visual studio debugger on windows. 85 | To trigger auto instrumentation code you need to invoke observer api functionality. 86 | Following, very simple script can be used as reference example, created and saved as test.php 87 | (this name will be referenced later during debugger invocation): 88 | 89 | ```shell 90 | 94 | ``` 95 | 96 | Now, you can invoke lldb with following arguments: 97 | ```shell 98 | lldb -- $HOME/php-bin/DEBUG/bin/php test.php 99 | ``` 100 | 101 | For gdb, arguments look very similar: 102 | ```shell 103 | gdb --args $HOME/php-bin/DEBUG/bin/php test.php 104 | ``` 105 | 106 | and set breakpoint in function like `opentelemetry_observer_init` just to test if debugger will 107 | stop there by: 108 | ```shell 109 | b opentelemetry_observer_init 110 | ``` 111 | 112 | ## Docker 113 | 114 | You can use docker + compose to debug the extension. 115 | 116 | Run all tests: 117 | ```shell 118 | PHP_VERSION=8.2.8 docker compose build debian 119 | PHP_VERSION=8.2.8 docker compose run debian 120 | phpize 121 | ./configure 122 | make clean 123 | make 124 | make test 125 | ``` 126 | 127 | The docker image has gdb and valgrind installed, to enable debugging and memory-leak checking. 128 | 129 | Run all tests with valgrind: 130 | ```shell 131 | php run-tests.php -d extension=$(pwd)/modules/opentelemetry.so -m 132 | ``` 133 | 134 | Run one test with valgrind: 135 | ```shell 136 | php run-tests.php -d extension=$(pwd)/modules/opentelemetry.so -m tests/.phpt 137 | ``` 138 | 139 | If any tests fail, a `.sh` script is created which you can use 140 | to run the test in isolation, and optionally with `gdb` or `valgrind`: 141 | 142 | ```shell 143 | tests/name_of_test.sh gdb # will start gdb 144 | tests/name_of_test.sh valgrind # will run test and display valgrind report 145 | ``` 146 | 147 | Further reading: https://www.phpinternalsbook.com/php7/memory_management/memory_debugging.html#debugging-memory 148 | 149 | ### gdbserver 150 | 151 | To debug tests running in a docker container, you can use `gdbserver`: 152 | 153 | ```shell 154 | docker build --build-arg PHP_VERSION=8.2.11 -f docker/Dockerfile.debian . -t otel:8.2.11 155 | docker run --rm -it -p "2345:2345" -v $(pwd)/ext:/usr/src/myapp otel:8.2.11 bash 156 | ``` 157 | 158 | Then, inside the container: 159 | 160 | ```shell 161 | phpize 162 | ./configure 163 | make 164 | gdbserver :2345 php -d extension=$(pwd)/modules/opentelemetry.so /path/to/file.php 165 | ``` 166 | 167 | Now, gdbserver should be running and awaiting a connection. Configure your IDE to connect via 168 | `gdb` to `127.0.0.1:2345` and start debugging, which should connect to the waiting server 169 | and start execution. 170 | 171 | # Packaging for PECL 172 | 173 | See https://github.com/opentelemetry-php/dev-tools#pecl-release-tool 174 | 175 | # Usage 176 | 177 | Basic usage is in the `tests/` directory. 178 | 179 | A more advanced example: https://github.com/open-telemetry/opentelemetry-php-contrib/pull/78/files 180 | 181 | # Further reading 182 | 183 | * https://www.phpinternalsbook.com/php7/build_system/building_extensions.html 184 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PHP_VERSION ?= 8.3.10 2 | DISTRO ?= debian 3 | 4 | .DEFAULT_GOAL : help 5 | 6 | help: ## Show this help 7 | @printf "\033[33m%s:\033[0m\n" 'Available commands' 8 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf " \033[32m%-18s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) 9 | all: build-image build test ## Build image, build extension, run tests 10 | build-image: ## Build docker image 11 | PHP_VERSION=$(PHP_VERSION) docker compose build $(DISTRO) 12 | shell: ## Shell 13 | docker compose run $(DISTRO) bash 14 | format: ## Run clang-format (debian-only) 15 | docker compose run debian ./format.sh 16 | clean: ## Clean 17 | docker compose run $(DISTRO) make clean 18 | git-clean: ## git-clean 19 | git clean -Xf 20 | build: ## Build extension 21 | docker compose run $(DISTRO) ./build.sh 22 | test: ## Run tests 23 | docker compose run $(DISTRO) make test 24 | remove-orphans: ## Remove orphaned containers 25 | docker compose down --remove-orphans 26 | .PHONY: clean build test git-clean 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenTelemetry auto-instrumentation extension 2 | 3 | [![Build and test](https://github.com/open-telemetry/opentelemetry-php-instrumentation/actions/workflows/build.yml/badge.svg)](https://github.com/open-telemetry/opentelemetry-php-instrumentation/actions/workflows/build.yml) 4 | 5 | ## Current Project Status 6 | For more information, please consult the documentation of the main [OpenTelemetry PHP project](https://github.com/open-telemetry/opentelemetry-php). 7 | 8 | ## Issues 9 | 10 | Issues have been disabled for this repo in order to help maintain consistency between this repo and the main [OpenTelemetry PHP project](https://github.com/open-telemetry/opentelemetry-php) repo. If you have an issue you'd like to raise about this issue, please use the [OpenTelemetry PHP Issue section](https://github.com/open-telemetry/opentelemetry-php/issues/new/choose). Please prefix the title of the issue with [opentelemetry-php-instrumentation]. 11 | 12 | ## Description 13 | This is a PHP extension for OpenTelemetry, to enable auto-instrumentation. 14 | It is based on [zend_observer](https://www.datadoghq.com/blog/engineering/php-8-observability-baked-right-in/) and requires php8+ 15 | 16 | The extension allows: 17 | 18 | - creating `pre` and `post` hook functions to arbitrary PHP functions and methods, which allows those methods to be wrapped with telemetry 19 | - adding attributes to functions and methods to enable observers at runtime 20 | 21 | In PHP 8.2+, internal/built-in PHP functions can also be observed. 22 | 23 | ## Requirements 24 | - PHP 8+ 25 | - [OpenTelemetry PHP library](https://github.com/open-telemetry/opentelemetry-php) 26 | 27 | ## Installation 28 | 29 | The extension can be installed in all of the usual ways: 30 | 31 | ### pecl 32 | 33 | ```shell 34 | pecl install opentelemetry 35 | ``` 36 | 37 | ### php-extension-installer 38 | 39 | If you are using the [official PHP docker images](https://hub.docker.com/_/php) then you can use 40 | [php-extension-installer](https://github.com/mlocati/docker-php-extension-installer) 41 | 42 | From github: 43 | ```shell 44 | install-php-extensions opentelemetry-php/ext-opentelemetry@main 45 | ``` 46 | 47 | Via pecl/pickle: 48 | ```shell 49 | install-php-extensions opentelemetry[-beta|-stable|-latest] 50 | ``` 51 | 52 | ### Windows 53 | 54 | Pre-built windows binaries are available from the [releases page](https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases) 55 | 56 | See https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2#building_pecl_extensions_with_phpize 57 | for generic advice on building from source under Windows. 58 | 59 | ## Verify that the extension is installed and enabled 60 | 61 | ```shell 62 | php --ri opentelemetry 63 | ``` 64 | 65 | ## Known issues 66 | 67 | ### Conflicting extensions 68 | 69 | The extension can be configured to not run if a conflicting extension is installed. The following extensions 70 | are known to not work when installed alongside OpenTelemetry: 71 | 72 | * SourceGuardian 73 | 74 | If the conflicting extension is a regular PHP extension (i.e, not a 75 | [zend_extension](https://www.phpinternalsbook.com/php7/extensions_design/zend_extensions.html)), you can control 76 | conflicts via the `opentelemetry.conflicts` ini setting. 77 | 78 | If a conflicting extension is found, then the OpenTelemetry extension will disable itself: 79 | 80 | ```shell 81 | php --ri opentelemetry 82 | 83 | Notice: PHP Startup: Conflicting extension found (blackfire), disabling OpenTelemetry in Unknown on line 0 84 | 85 | opentelemetry 86 | 87 | opentelemetry hooks => disabled (conflict) 88 | extension version => 1.0.0beta6 89 | 90 | Directive => Local Value => Master Value 91 | opentelemetry.conflicts => blackfire => blackfire 92 | opentelemetry.validate_hook_functions => On => On 93 | ``` 94 | 95 | ### Invalid pre/post hooks 96 | 97 | Invalid argument types in `pre` and `post` callbacks can cause fatal errors. Runtime checking is performed on the 98 | hook functions to ensure they are compatible. If not, the hook will not be executed and an error will be generated. 99 | 100 | This feature can be disabled by setting the `opentelemetry.validate_hook_functions` ini value to `Off`; 101 | 102 | ### Increasing function argument count 103 | 104 | By default, increasing the number of arguments provided to a function in the pre hook is allowed only if that does 105 | not require the stack frame of the function call to be extended in size. For internal functions, adding arguments 106 | not provided at the callsite always requires stack extension. For PHP functions, it is required only if the 107 | argument is not included in the function definition. 108 | 109 | Extending stack frame automatically can be enabled by setting `opentelemetry.allow_stack_extension` ini value to `On`. 110 | This enables extending the stack frame by up to another 16 arguments. 111 | 112 | ## Usage 113 | 114 | The `pre` method starts and activates a span. The `post` method ends the span after the observed method has finished. 115 | 116 | ```php 117 | spanBuilder($class) 126 | ->startSpan(); 127 | Context::storage()->attach($span->storeInContext(Context::getCurrent())); 128 | }, 129 | post: static function (DemoClass $demo, array $params, $returnValue, ?Throwable $exception) use ($tracer) { 130 | $scope = Context::storage()->scope(); 131 | $scope?->detach(); 132 | $span = Span::fromContext($scope->context()); 133 | $exception && $span->recordException($exception); 134 | $span->setStatus($exception ? StatusCode::STATUS_ERROR : StatusCode::STATUS_OK); 135 | $span->end(); 136 | } 137 | ); 138 | ``` 139 | 140 | There are more examples in the [tests directory](ext/tests/) 141 | 142 | ### Static methods 143 | 144 | Note that if hooking a static class method, the first parameter to `pre` and `post` callbacks is a `string` containing the method's class name. 145 | 146 | ### Caveats 147 | 148 | - Be aware that trivial functions are candidates for optimizations. 149 | Optimizer can optimize them out and replace user function call with more optimal set of instructions (inlining). 150 | In this case hooks will not be invoked as there will be no function. 151 | - Hooks must be registered _before_ a function is first executed. You may encounter race conditions where 152 | the composer autoloader runs code that uses functions you wish to hook prior to the hooks being registered. 153 | 154 | # Modifying parameters, exceptions and return values of the observed function 155 | 156 | ## Parameters 157 | 158 | From a `pre` hook function, you may modify the parameters before they are received by the observed function. 159 | The arguments are passed in as a numerically-indexed array. The returned array from the `pre` hook is used 160 | to modify (_not_ replace) the existing parameters: 161 | 162 | ```php 163 | null, //make first param null 170 | 2 => 'baz', //replace 3rd param 171 | 3 => 'bat', //add 4th param 172 | ]; 173 | } 174 | ); 175 | function hello($one = null, $two = null, $three = null, $four = null) { 176 | var_dump(func_get_args()); 177 | } 178 | 179 | hello('a', 'b', 'c'); 180 | ``` 181 | 182 | gives output: 183 | ``` 184 | array(4) { 185 | [0]=> 186 | NULL 187 | [1]=> 188 | string(1) "b" 189 | [2]=> 190 | string(3) "baz" 191 | [3]=> 192 | string(3) "bat" 193 | } 194 | ``` 195 | 196 | ## Return values 197 | 198 | `post` hook methods can modify the observed function's return value: 199 | 200 | ```php 201 | ++$return); 203 | 204 | function hello(int $val) { 205 | return $val; 206 | } 207 | 208 | var_dump(hello(1)); 209 | ``` 210 | 211 | gives output: 212 | ``` 213 | int(2) 214 | ``` 215 | 216 | *Important*: the post method _must_ provide a return type-hint, otherwise the return value will be ignored. The return type 217 | hint in the example above is `: int`. 218 | 219 | ## Exceptions 220 | 221 | `post` hook methods can modify an exception thrown from the observed function: 222 | 223 | ```php 224 | getMessage()); 237 | var_dump($t->getPrevious()?->getMessage()); 238 | } 239 | ``` 240 | 241 | gives output: 242 | ```php 243 | string(3) "new" 244 | string(8) "original" 245 | ``` 246 | 247 | ## Attribute-based hooking 248 | 249 | By applying attributes to source code, the OpenTelemetry extension can add hooks at runtime. 250 | 251 | Default pre and post hook methods are provided by the OpenTelemetry API: `OpenTelemetry\API\Instrumentation\Handler::pre` 252 | and `::post`. 253 | 254 | This feature is disabled by default, but can be enabled by setting `opentelemetry.attr_hooks_enabled = On` in php.ini 255 | 256 | ## Restrictions 257 | 258 | Attribute-based hooks can only be applied to a function/method that does not already have 259 | hooks applied. 260 | Only one hook can be applied to a function/method, including via interfaces. 261 | 262 | Since the attributes are evaluated at runtime, the extension checks whether a hook already 263 | exists to decide whether it should apply a new runtime hook. 264 | 265 | ## Configuration 266 | 267 | This feature can be configured via `.ini` by modifying the following entries: 268 | 269 | - `opentelemetry.attr_hooks_enabled` - boolean, default Off 270 | - `opentelemetry.attr_pre_handler_function` - FQN of pre method/function 271 | - `opentelemetry.attr_post_handler_function` - FQN of post method/function 272 | 273 | ## `OpenTelemetry\API\Instrumentation\WithSpan` attribute 274 | 275 | This attribute is provided by the OpenTelemetry API can be applied to a function or class method. 276 | 277 | You can also provide optional parameters to the attribute, which control: 278 | - span name 279 | - span kind 280 | - attributes 281 | 282 | ```php 283 | use OpenTelemetry\API\Instrumentation\WithSpan 284 | 285 | class MyClass 286 | { 287 | #[WithSpan] 288 | public function trace_me(): void 289 | { 290 | /* ... */ 291 | } 292 | 293 | #[WithSpan('custom_span_name', SpanKind::KIND_INTERNAL, ['my-attr' => 'value'])] 294 | public function trace_me_with_customization(): void 295 | { 296 | /* ... */ 297 | } 298 | } 299 | 300 | #[WithSpan] 301 | function my_function(): void 302 | { 303 | /* ... */ 304 | } 305 | ``` 306 | 307 | ## `OpenTelemetry\API\Instrumentation\SpanAttribute` attribute 308 | 309 | This attribute should be used in conjunction with `WithSpan`. It is applied to function/method 310 | parameters, and causes those parameters and values to be passed through to the `pre` hook function 311 | where they can be added as trace attributes. 312 | There is one optional parameter, which controls the attribute key. If not set, the parameter name 313 | is used. 314 | 315 | ```php 316 | use OpenTelemetry\API\Instrumentation\WithSpan 317 | use OpenTelemetry\API\Instrumentation\SpanAttribute 318 | 319 | class MyClass 320 | { 321 | #[WithSpan] 322 | public function add_user( 323 | #[SpanAttribute] string $username, 324 | string $password, 325 | #[SpanAttribute('a_better_attribute_name')] string $foo_bar_baz, 326 | ): void 327 | { 328 | /* ... */ 329 | } 330 | ``` 331 | 332 | ## Contributing 333 | See [DEVELOPMENT.md](DEVELOPMENT.md) and https://github.com/open-telemetry/opentelemetry-php/blob/main/CONTRIBUTING.md 334 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-telemetry/ext-opentelemetry", 3 | "description": "Auto-instrumentation extension for OpenTelemetry", 4 | "type": "php-ext", 5 | "minimum-stability": "stable", 6 | "license": "Apache-2.0", 7 | "php-ext": { 8 | "extension-name": "opentelemetry", 9 | "build-path": "ext" 10 | }, 11 | "require": { 12 | "php": "^8.0" 13 | }, 14 | "authors": [ 15 | { 16 | "name": "opentelemetry-php contributors", 17 | "homepage": "https://github.com/open-telemetry/opentelemetry-php-instrumentation/graphs/contributors" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | debian: 3 | build: 4 | context: docker 5 | dockerfile: Dockerfile.debian 6 | args: 7 | PHP_VERSION: ${PHP_VERSION:-8.3.10} 8 | volumes: 9 | - ./ext:/usr/src/myapp 10 | environment: 11 | TEST_PHP_ARGS: "-q" 12 | alpine: 13 | build: 14 | context: docker 15 | dockerfile: Dockerfile.alpine 16 | args: 17 | PHP_VERSION: ${PHP_VERSION:-8.3.10} 18 | volumes: 19 | - ./ext:/usr/src/myapp 20 | environment: 21 | TEST_PHP_ARGS: "-q" 22 | 32bit: 23 | build: 24 | context: docker 25 | dockerfile: Dockerfile.alpine 26 | args: 27 | ALPINE_VERSION: i386/alpine 28 | PHP_VERSION: ${PHP_VERSION:-8.4.0beta4} 29 | volumes: 30 | - ./ext:/usr/src/myapp 31 | environment: 32 | TEST_PHP_ARGS: "-q" 33 | -------------------------------------------------------------------------------- /docker/Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | ARG ALPINE_VERSION=alpine:3.20 2 | FROM ${ALPINE_VERSION} AS builder 3 | WORKDIR /usr/src 4 | 5 | ENV PHPIZE_DEPS="autoconf dpkg-dev dpkg file g++ gcc libc-dev make pkgconf re2c" 6 | 7 | RUN apk add --no-cache \ 8 | ${PHPIZE_DEPS} \ 9 | bash \ 10 | ca-certificates \ 11 | curl \ 12 | tar \ 13 | xz 14 | 15 | RUN apk add --no-cache \ 16 | bison \ 17 | coreutils \ 18 | curl-dev \ 19 | libxml2-dev \ 20 | linux-headers \ 21 | re2c \ 22 | readline-dev \ 23 | sqlite-dev 24 | 25 | ARG PHP_VERSION 26 | ENV PHP_URL="https://github.com/php/php-src/archive/refs/tags/php-${PHP_VERSION}.tar.gz" 27 | 28 | ARG PHP_CONFIG_OPTS="--enable-debug --with-pear --with-zlib" 29 | RUN echo "$PHP_URL" && curl -fsSL -o php.tar.gz "$PHP_URL" \ 30 | && cd /usr/src \ 31 | && mkdir php-src \ 32 | && tar -xzf php.tar.gz -C php-src --strip-components=1 \ 33 | && cd php-src \ 34 | && ./buildconf --force \ 35 | && ./configure ${PHP_CONFIG_OPTS} \ 36 | && make -j $(nproc) \ 37 | && make install 38 | WORKDIR /usr/src/myapp 39 | -------------------------------------------------------------------------------- /docker/Dockerfile.debian: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim 2 | WORKDIR /usr/src 3 | 4 | #clang-format, gdb, valgrind 5 | RUN apt-get update \ 6 | && apt-get install -y \ 7 | curl \ 8 | gnupg2 \ 9 | && curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc \ 10 | && echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" >> /etc/apt/sources.list \ 11 | && echo "deb-src http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" >> /etc/apt/sources.list \ 12 | && apt-get update \ 13 | && apt-get install -y \ 14 | clang-format-18 \ 15 | gdb \ 16 | gdbserver \ 17 | valgrind 18 | 19 | ENV PHPIZE_DEPS="autoconf dpkg-dev dpkg file g++ gcc libc-dev make pkgconf re2c" 20 | 21 | RUN apt-get update -y \ 22 | && apt-get install -y \ 23 | ${PHPIZE_DEPS} \ 24 | libxml2-dev \ 25 | libsqlite3-dev \ 26 | zlib1g-dev 27 | 28 | ARG PHP_VERSION 29 | ENV PHP_URL="https://www.php.net/distributions/php-${PHP_VERSION}.tar.xz" 30 | ENV CFLAGS="-ggdb3" 31 | ENV TEST_PHP_ARGS="-q" 32 | 33 | RUN echo "$PHP_URL" \ 34 | && curl -fsSL -o php.tar.xz "$PHP_URL" \ 35 | && tar -xf php.tar.xz \ 36 | && rm php.tar.xz 37 | 38 | ARG PHP_CONFIG_OPTS="--enable-debug --with-pear --with-zlib" 39 | RUN cd php-${PHP_VERSION} \ 40 | && ./buildconf \ 41 | && ./configure ${PHP_CONFIG_OPTS} \ 42 | && make -j $(nproc) \ 43 | && make install 44 | 45 | WORKDIR /usr/src/myapp 46 | -------------------------------------------------------------------------------- /ext/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveAssignments: 8 | Enabled: false 9 | AcrossEmptyLines: false 10 | AcrossComments: false 11 | AlignCompound: false 12 | PadOperators: true 13 | AlignConsecutiveBitFields: 14 | Enabled: false 15 | AcrossEmptyLines: false 16 | AcrossComments: false 17 | AlignCompound: false 18 | PadOperators: false 19 | AlignConsecutiveDeclarations: 20 | Enabled: false 21 | AcrossEmptyLines: false 22 | AcrossComments: false 23 | AlignCompound: false 24 | PadOperators: false 25 | AlignConsecutiveMacros: 26 | Enabled: false 27 | AcrossEmptyLines: false 28 | AcrossComments: false 29 | AlignCompound: false 30 | PadOperators: false 31 | AlignEscapedNewlines: Right 32 | AlignOperands: Align 33 | AlignTrailingComments: true 34 | AllowAllArgumentsOnNextLine: true 35 | AllowAllParametersOfDeclarationOnNextLine: true 36 | AllowShortEnumsOnASingleLine: true 37 | AllowShortBlocksOnASingleLine: Never 38 | AllowShortCaseLabelsOnASingleLine: false 39 | AllowShortFunctionsOnASingleLine: All 40 | AllowShortLambdasOnASingleLine: All 41 | AllowShortIfStatementsOnASingleLine: Never 42 | AllowShortLoopsOnASingleLine: false 43 | AlwaysBreakAfterDefinitionReturnType: None 44 | AlwaysBreakAfterReturnType: None 45 | AlwaysBreakBeforeMultilineStrings: false 46 | AlwaysBreakTemplateDeclarations: MultiLine 47 | AttributeMacros: 48 | - __capability 49 | BinPackArguments: true 50 | BinPackParameters: true 51 | BraceWrapping: 52 | AfterCaseLabel: false 53 | AfterClass: false 54 | AfterControlStatement: Never 55 | AfterEnum: false 56 | AfterFunction: false 57 | AfterNamespace: false 58 | AfterObjCDeclaration: false 59 | AfterStruct: false 60 | AfterUnion: false 61 | AfterExternBlock: false 62 | BeforeCatch: false 63 | BeforeElse: false 64 | BeforeLambdaBody: false 65 | BeforeWhile: false 66 | IndentBraces: false 67 | SplitEmptyFunction: true 68 | SplitEmptyRecord: true 69 | SplitEmptyNamespace: true 70 | BreakBeforeBinaryOperators: None 71 | BreakBeforeConceptDeclarations: Always 72 | BreakBeforeBraces: Attach 73 | BreakBeforeInheritanceComma: false 74 | BreakInheritanceList: BeforeColon 75 | BreakBeforeTernaryOperators: true 76 | BreakConstructorInitializersBeforeComma: false 77 | BreakConstructorInitializers: BeforeColon 78 | BreakAfterJavaFieldAnnotations: false 79 | BreakStringLiterals: true 80 | ColumnLimit: 80 81 | CommentPragmas: '^ IWYU pragma:' 82 | QualifierAlignment: Leave 83 | CompactNamespaces: false 84 | ConstructorInitializerIndentWidth: 4 85 | ContinuationIndentWidth: 4 86 | Cpp11BracedListStyle: true 87 | DeriveLineEnding: true 88 | DerivePointerAlignment: false 89 | DisableFormat: false 90 | EmptyLineAfterAccessModifier: Never 91 | EmptyLineBeforeAccessModifier: LogicalBlock 92 | ExperimentalAutoDetectBinPacking: false 93 | PackConstructorInitializers: BinPack 94 | BasedOnStyle: '' 95 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 96 | AllowAllConstructorInitializersOnNextLine: true 97 | FixNamespaceComments: true 98 | ForEachMacros: 99 | - foreach 100 | - Q_FOREACH 101 | - BOOST_FOREACH 102 | IfMacros: 103 | - KJ_IF_MAYBE 104 | IncludeBlocks: Preserve 105 | IncludeCategories: 106 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 107 | Priority: 2 108 | SortPriority: 0 109 | CaseSensitive: false 110 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 111 | Priority: 3 112 | SortPriority: 0 113 | CaseSensitive: false 114 | - Regex: '.*' 115 | Priority: 1 116 | SortPriority: 0 117 | CaseSensitive: false 118 | IncludeIsMainRegex: '(Test)?$' 119 | IncludeIsMainSourceRegex: '' 120 | IndentAccessModifiers: false 121 | IndentCaseLabels: false 122 | IndentCaseBlocks: false 123 | IndentGotoLabels: true 124 | IndentPPDirectives: None 125 | IndentExternBlock: AfterExternBlock 126 | IndentRequiresClause: true 127 | IndentWidth: 4 128 | IndentWrappedFunctionNames: false 129 | InsertBraces: false 130 | InsertTrailingCommas: None 131 | JavaScriptQuotes: Leave 132 | JavaScriptWrapImports: true 133 | KeepEmptyLinesAtTheStartOfBlocks: true 134 | LambdaBodyIndentation: Signature 135 | MacroBlockBegin: '(ZEND_PARSE_PARAMETERS_START|ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX|ZEND_BEGIN_MODULE_GLOBALS)' 136 | MacroBlockEnd: '(ZEND_PARSE_PARAMETERS_END|ZEND_END_ARG_INFO|ZEND_END_MODULE_GLOBALS)' 137 | MaxEmptyLinesToKeep: 1 138 | NamespaceIndentation: None 139 | ObjCBinPackProtocolList: Auto 140 | ObjCBlockIndentWidth: 2 141 | ObjCBreakBeforeNestedBlockParam: true 142 | ObjCSpaceAfterProperty: false 143 | ObjCSpaceBeforeProtocolList: true 144 | PenaltyBreakAssignment: 2 145 | PenaltyBreakBeforeFirstCallParameter: 19 146 | PenaltyBreakComment: 300 147 | PenaltyBreakFirstLessLess: 120 148 | PenaltyBreakOpenParenthesis: 0 149 | PenaltyBreakString: 1000 150 | PenaltyBreakTemplateDeclaration: 10 151 | PenaltyExcessCharacter: 1000000 152 | PenaltyReturnTypeOnItsOwnLine: 60 153 | PenaltyIndentedWhitespace: 0 154 | PointerAlignment: Right 155 | PPIndentWidth: -1 156 | ReferenceAlignment: Pointer 157 | ReflowComments: true 158 | RemoveBracesLLVM: false 159 | RequiresClausePosition: OwnLine 160 | SeparateDefinitionBlocks: Leave 161 | ShortNamespaceLines: 1 162 | SortIncludes: false 163 | SortJavaStaticImport: Before 164 | SortUsingDeclarations: true 165 | SpaceAfterCStyleCast: false 166 | SpaceAfterLogicalNot: false 167 | SpaceAfterTemplateKeyword: true 168 | SpaceBeforeAssignmentOperators: true 169 | SpaceBeforeCaseColon: false 170 | SpaceBeforeCpp11BracedList: false 171 | SpaceBeforeCtorInitializerColon: true 172 | SpaceBeforeInheritanceColon: true 173 | SpaceBeforeParens: ControlStatements 174 | SpaceBeforeParensOptions: 175 | AfterControlStatements: true 176 | AfterForeachMacros: true 177 | AfterFunctionDefinitionName: false 178 | AfterFunctionDeclarationName: false 179 | AfterIfMacros: true 180 | AfterOverloadedOperator: false 181 | AfterRequiresInClause: false 182 | AfterRequiresInExpression: false 183 | BeforeNonEmptyParentheses: false 184 | SpaceAroundPointerQualifiers: Default 185 | SpaceBeforeRangeBasedForLoopColon: true 186 | SpaceInEmptyBlock: false 187 | SpaceInEmptyParentheses: false 188 | SpacesBeforeTrailingComments: 1 189 | SpacesInAngles: Never 190 | SpacesInConditionalStatement: false 191 | SpacesInContainerLiterals: true 192 | SpacesInCStyleCastParentheses: false 193 | SpacesInLineCommentPrefix: 194 | Minimum: 1 195 | Maximum: -1 196 | SpacesInParentheses: false 197 | SpacesInSquareBrackets: false 198 | SpaceBeforeSquareBrackets: false 199 | BitFieldColonSpacing: Both 200 | Standard: Latest 201 | StatementAttributeLikeMacros: 202 | - Q_EMIT 203 | StatementMacros: 204 | - Q_UNUSED 205 | - QT_REQUIRE_VERSION 206 | TabWidth: 8 207 | UseCRLF: false 208 | UseTab: Never 209 | WhitespaceSensitiveMacros: 210 | - STRINGIZE 211 | - PP_STRINGIZE 212 | - BOOST_PP_STRINGIZE 213 | - NS_SWIFT_NAME 214 | - CF_SWIFT_NAME 215 | ... 216 | 217 | -------------------------------------------------------------------------------- /ext/.gitignore: -------------------------------------------------------------------------------- 1 | *.lo 2 | *.loT 3 | *.la 4 | .libs 5 | .idea/ 6 | acinclude.m4 7 | aclocal.m4 8 | autom4te.cache 9 | build 10 | composer.json 11 | config.guess 12 | config.h 13 | config.h.in 14 | config.log 15 | config.nice 16 | config.status 17 | config.sub 18 | configure 19 | configure~ 20 | configure.ac 21 | configure.in 22 | include 23 | install-sh 24 | libtool 25 | ltmain.sh 26 | Makefile 27 | Makefile.fragments 28 | Makefile.global 29 | Makefile.objects 30 | missing 31 | mkinstalldirs 32 | modules 33 | php_test_results_*.txt 34 | phpt.* 35 | run-test-info.php 36 | run-tests.php 37 | tests/**/*.diff 38 | tests/**/*.out 39 | tests/**/*.php 40 | !tests/mocks/*.php 41 | tests/**/*.exp 42 | tests/**/*.log 43 | tests/**/*.sh 44 | tests/**/*.db 45 | tests/**/*.mem 46 | tmp-php.ini 47 | config.h.in~ 48 | opentelemetry.dep 49 | otel_observer.dep 50 | -------------------------------------------------------------------------------- /ext/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /ext/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | phpize 3 | ./configure 4 | make -------------------------------------------------------------------------------- /ext/config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 for extension opentelemetry 2 | 3 | dnl Comments in this file start with the string 'dnl'. 4 | dnl Remove where necessary. 5 | 6 | dnl If your extension references something external, use 'with': 7 | 8 | dnl PHP_ARG_WITH([opentelemetry], 9 | dnl [for opentelemetry support], 10 | dnl [AS_HELP_STRING([--with-opentelemetry], 11 | dnl [Include opentelemetry support])]) 12 | 13 | dnl Otherwise use 'enable': 14 | 15 | PHP_ARG_ENABLE([opentelemetry], 16 | [whether to enable opentelemetry support], 17 | [AS_HELP_STRING([--enable-opentelemetry], 18 | [Enable opentelemetry support])], 19 | [no]) 20 | 21 | if test "$PHP_OPENTELEMETRY" != "no"; then 22 | dnl Write more examples of tests here... 23 | 24 | dnl Remove this code block if the library does not support pkg-config. 25 | dnl PKG_CHECK_MODULES([LIBFOO], [foo]) 26 | dnl PHP_EVAL_INCLINE($LIBFOO_CFLAGS) 27 | dnl PHP_EVAL_LIBLINE($LIBFOO_LIBS, OPENTELEMETRY_SHARED_LIBADD) 28 | 29 | dnl If you need to check for a particular library version using PKG_CHECK_MODULES, 30 | dnl you can use comparison operators. For example: 31 | dnl PKG_CHECK_MODULES([LIBFOO], [foo >= 1.2.3]) 32 | dnl PKG_CHECK_MODULES([LIBFOO], [foo < 3.4]) 33 | dnl PKG_CHECK_MODULES([LIBFOO], [foo = 1.2.3]) 34 | 35 | dnl Remove this code block if the library supports pkg-config. 36 | dnl --with-opentelemetry -> check with-path 37 | dnl SEARCH_PATH="/usr/local /usr" # you might want to change this 38 | dnl SEARCH_FOR="/include/opentelemetry.h" # you most likely want to change this 39 | dnl if test -r $PHP_OPENTELEMETRY/$SEARCH_FOR; then # path given as parameter 40 | dnl OPENTELEMETRY_DIR=$PHP_OPENTELEMETRY 41 | dnl else # search default path list 42 | dnl AC_MSG_CHECKING([for opentelemetry files in default path]) 43 | dnl for i in $SEARCH_PATH ; do 44 | dnl if test -r $i/$SEARCH_FOR; then 45 | dnl OPENTELEMETRY_DIR=$i 46 | dnl AC_MSG_RESULT(found in $i) 47 | dnl fi 48 | dnl done 49 | dnl fi 50 | dnl 51 | dnl if test -z "$OPENTELEMETRY_DIR"; then 52 | dnl AC_MSG_RESULT([not found]) 53 | dnl AC_MSG_ERROR([Please reinstall the opentelemetry distribution]) 54 | dnl fi 55 | 56 | dnl Remove this code block if the library supports pkg-config. 57 | dnl --with-opentelemetry -> add include path 58 | dnl PHP_ADD_INCLUDE($OPENTELEMETRY_DIR/include) 59 | 60 | dnl Remove this code block if the library supports pkg-config. 61 | dnl --with-opentelemetry -> check for lib and symbol presence 62 | dnl LIBNAME=OPENTELEMETRY # you may want to change this 63 | dnl LIBSYMBOL=OPENTELEMETRY # you most likely want to change this 64 | 65 | dnl If you need to check for a particular library function (e.g. a conditional 66 | dnl or version-dependent feature) and you are using pkg-config: 67 | dnl PHP_CHECK_LIBRARY($LIBNAME, $LIBSYMBOL, 68 | dnl [ 69 | dnl AC_DEFINE(HAVE_OPENTELEMETRY_FEATURE, 1, [ ]) 70 | dnl ],[ 71 | dnl AC_MSG_ERROR([FEATURE not supported by your opentelemetry library.]) 72 | dnl ], [ 73 | dnl $LIBFOO_LIBS 74 | dnl ]) 75 | 76 | dnl If you need to check for a particular library function (e.g. a conditional 77 | dnl or version-dependent feature) and you are not using pkg-config: 78 | dnl PHP_CHECK_LIBRARY($LIBNAME, $LIBSYMBOL, 79 | dnl [ 80 | dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $OPENTELEMETRY_DIR/$PHP_LIBDIR, OPENTELEMETRY_SHARED_LIBADD) 81 | dnl AC_DEFINE(HAVE_OPENTELEMETRY_FEATURE, 1, [ ]) 82 | dnl ],[ 83 | dnl AC_MSG_ERROR([FEATURE not supported by your opentelemetry library.]) 84 | dnl ],[ 85 | dnl -L$OPENTELEMETRY_DIR/$PHP_LIBDIR -lm 86 | dnl ]) 87 | dnl 88 | dnl PHP_SUBST(OPENTELEMETRY_SHARED_LIBADD) 89 | 90 | dnl In case of no dependencies 91 | AC_DEFINE(HAVE_OPENTELEMETRY, 1, [ Have opentelemetry support ]) 92 | 93 | PHP_NEW_EXTENSION(opentelemetry, opentelemetry.c otel_observer.c, $ext_shared,, "-Wall -Wextra -Werror -Wno-unused-parameter") 94 | fi 95 | -------------------------------------------------------------------------------- /ext/config.w32: -------------------------------------------------------------------------------- 1 | // vim:ft=javascript 2 | 3 | ARG_ENABLE('opentelemetry', 'opentelemetry support', 'no'); 4 | 5 | if (PHP_OPENTELEMETRY != 'no') { 6 | AC_DEFINE('HAVE_OPENTELEMETRY', 1, 'opentelemetry support enabled'); 7 | 8 | EXTENSION('opentelemetry', 'opentelemetry.c otel_observer.c', PHP_OPENTELEMETRY_SHARED, '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1'); 9 | } 10 | -------------------------------------------------------------------------------- /ext/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | clang-format-18 -i *.c *.h 3 | -------------------------------------------------------------------------------- /ext/opentelemetry.c: -------------------------------------------------------------------------------- 1 | 2 | #ifdef HAVE_CONFIG_H 3 | #include "config.h" 4 | #endif 5 | 6 | #include "php.h" 7 | #include "ext/standard/info.h" 8 | #include "php_opentelemetry.h" 9 | #include "opentelemetry_arginfo.h" 10 | #include "otel_observer.h" 11 | #include "stdlib.h" 12 | #include "string.h" 13 | #include "zend_attributes.h" 14 | #include "zend_closures.h" 15 | 16 | static int check_conflict(HashTable *registry, const char *extension_name) { 17 | if (!extension_name || !*extension_name) { 18 | return 0; 19 | } 20 | zend_module_entry *module_entry; 21 | ZEND_HASH_FOREACH_PTR(registry, module_entry) { 22 | if (strcmp(module_entry->name, extension_name) == 0) { 23 | php_error_docref(NULL, E_NOTICE, 24 | "Conflicting extension found (%s), OpenTelemetry " 25 | "extension will be disabled", 26 | extension_name); 27 | return 1; 28 | } 29 | } 30 | ZEND_HASH_FOREACH_END(); 31 | return 0; 32 | } 33 | 34 | static void check_conflicts() { 35 | int conflict_found = 0; 36 | char *input = OTEL_G(conflicts); 37 | 38 | if (!input || !*input) { 39 | return; 40 | } 41 | 42 | HashTable *registry = &module_registry; 43 | const char *s = NULL, *e = input; 44 | /** @see https://github.com/php/php-src/blob/php-8.2.9/Zend/zend_API.c#L3324 45 | */ 46 | while (*e) { 47 | switch (*e) { 48 | case ' ': 49 | case ',': 50 | if (s) { 51 | size_t len = e - s; 52 | char *result = (char *)malloc((len + 1) * sizeof(char)); 53 | strncpy(result, s, len); 54 | result[len] = '\0'; // null terminate 55 | if (check_conflict(registry, result)) { 56 | conflict_found = 1; 57 | } 58 | s = NULL; 59 | } 60 | break; 61 | default: 62 | if (!s) { 63 | s = e; 64 | } 65 | break; 66 | } 67 | e++; 68 | } 69 | if (check_conflict(registry, s)) { 70 | conflict_found = 1; 71 | } 72 | 73 | OTEL_G(disabled) = conflict_found; 74 | } 75 | 76 | ZEND_DECLARE_MODULE_GLOBALS(opentelemetry) 77 | 78 | PHP_INI_BEGIN() 79 | // conflicting extensions. a comma-separated list, eg "ext1,ext2" 80 | STD_PHP_INI_ENTRY("opentelemetry.conflicts", "", PHP_INI_ALL, OnUpdateString, 81 | conflicts, zend_opentelemetry_globals, opentelemetry_globals) 82 | STD_PHP_INI_ENTRY_EX("opentelemetry.validate_hook_functions", "On", PHP_INI_ALL, 83 | OnUpdateBool, validate_hook_functions, 84 | zend_opentelemetry_globals, opentelemetry_globals, 85 | zend_ini_boolean_displayer_cb) 86 | STD_PHP_INI_ENTRY_EX("opentelemetry.allow_stack_extension", "Off", PHP_INI_ALL, 87 | OnUpdateBool, allow_stack_extension, 88 | zend_opentelemetry_globals, opentelemetry_globals, 89 | zend_ini_boolean_displayer_cb) 90 | STD_PHP_INI_ENTRY_EX("opentelemetry.attr_hooks_enabled", "Off", PHP_INI_ALL, 91 | OnUpdateBool, attr_hooks_enabled, 92 | zend_opentelemetry_globals, opentelemetry_globals, 93 | zend_ini_boolean_displayer_cb) 94 | STD_PHP_INI_ENTRY_EX("opentelemetry.display_warnings", "Off", PHP_INI_ALL, 95 | OnUpdateBool, display_warnings, zend_opentelemetry_globals, 96 | opentelemetry_globals, zend_ini_boolean_displayer_cb) 97 | STD_PHP_INI_ENTRY("opentelemetry.attr_pre_handler_function", 98 | "OpenTelemetry\\API\\Instrumentation\\WithSpanHandler::pre", 99 | PHP_INI_ALL, OnUpdateString, pre_handler_function_fqn, 100 | zend_opentelemetry_globals, opentelemetry_globals) 101 | STD_PHP_INI_ENTRY("opentelemetry.attr_post_handler_function", 102 | "OpenTelemetry\\API\\Instrumentation\\WithSpanHandler::post", 103 | PHP_INI_ALL, OnUpdateString, post_handler_function_fqn, 104 | zend_opentelemetry_globals, opentelemetry_globals) 105 | PHP_INI_END() 106 | 107 | PHP_FUNCTION(OpenTelemetry_Instrumentation_hook) { 108 | zend_string *class_name; 109 | zend_string *function_name; 110 | zval *pre = NULL; 111 | zval *post = NULL; 112 | 113 | ZEND_PARSE_PARAMETERS_START(2, 4) 114 | Z_PARAM_STR_OR_NULL(class_name) 115 | Z_PARAM_STR(function_name) 116 | Z_PARAM_OPTIONAL 117 | Z_PARAM_OBJECT_OF_CLASS_OR_NULL(pre, zend_ce_closure) 118 | Z_PARAM_OBJECT_OF_CLASS_OR_NULL(post, zend_ce_closure) 119 | ZEND_PARSE_PARAMETERS_END(); 120 | 121 | RETURN_BOOL(add_observer(class_name, function_name, pre, post)); 122 | } 123 | 124 | PHP_RINIT_FUNCTION(opentelemetry) { 125 | #if defined(ZTS) && defined(COMPILE_DL_OPENTELEMETRY) 126 | ZEND_TSRMLS_CACHE_UPDATE(); 127 | #endif 128 | 129 | observer_globals_init(); 130 | 131 | return SUCCESS; 132 | } 133 | 134 | PHP_RSHUTDOWN_FUNCTION(opentelemetry) { 135 | observer_globals_cleanup(); 136 | 137 | return SUCCESS; 138 | } 139 | 140 | PHP_MINIT_FUNCTION(opentelemetry) { 141 | #if defined(ZTS) && defined(COMPILE_DL_OPENTELEMETRY) 142 | ZEND_TSRMLS_CACHE_UPDATE(); 143 | #endif 144 | 145 | REGISTER_INI_ENTRIES(); 146 | 147 | check_conflicts(); 148 | 149 | if (!OTEL_G(disabled)) { 150 | opentelemetry_observer_init(INIT_FUNC_ARGS_PASSTHRU); 151 | } 152 | 153 | return SUCCESS; 154 | } 155 | 156 | PHP_MSHUTDOWN_FUNCTION(opentelemetry) { 157 | UNREGISTER_INI_ENTRIES(); 158 | 159 | return SUCCESS; 160 | } 161 | 162 | PHP_MINFO_FUNCTION(opentelemetry) { 163 | php_info_print_table_start(); 164 | php_info_print_table_row(2, "opentelemetry hooks", 165 | OTEL_G(disabled) ? "disabled (conflict)" 166 | : "enabled"); 167 | php_info_print_table_row(2, "extension version", PHP_OPENTELEMETRY_VERSION); 168 | php_info_print_table_end(); 169 | DISPLAY_INI_ENTRIES(); 170 | } 171 | 172 | PHP_GINIT_FUNCTION(opentelemetry) { 173 | ZEND_SECURE_ZERO(opentelemetry_globals, sizeof(*opentelemetry_globals)); 174 | } 175 | 176 | zend_module_entry opentelemetry_module_entry = { 177 | STANDARD_MODULE_HEADER, 178 | "opentelemetry", 179 | ext_functions, 180 | PHP_MINIT(opentelemetry), 181 | PHP_MSHUTDOWN(opentelemetry), 182 | PHP_RINIT(opentelemetry), 183 | PHP_RSHUTDOWN(opentelemetry), 184 | PHP_MINFO(opentelemetry), 185 | PHP_OPENTELEMETRY_VERSION, 186 | PHP_MODULE_GLOBALS(opentelemetry), 187 | PHP_GINIT(opentelemetry), 188 | NULL, 189 | NULL, 190 | STANDARD_MODULE_PROPERTIES_EX, 191 | }; 192 | 193 | #ifdef COMPILE_DL_OPENTELEMETRY 194 | #ifdef ZTS 195 | ZEND_TSRMLS_CACHE_DEFINE() 196 | #endif 197 | ZEND_GET_MODULE(opentelemetry) 198 | #endif 199 | -------------------------------------------------------------------------------- /ext/opentelemetry.stub.php: -------------------------------------------------------------------------------- 1 | func->op_array.scope) { 52 | if (execute_data->func->op_array.fn_flags & ZEND_ACC_STATIC) { 53 | zend_class_entry *called_scope = 54 | zend_get_called_scope(execute_data); 55 | ZVAL_STR(zv, called_scope->name); 56 | } else { 57 | zend_object *this = zend_get_this_object(execute_data); 58 | ZVAL_OBJ_COPY(zv, this); 59 | } 60 | } else { 61 | ZVAL_NULL(zv); 62 | } 63 | } 64 | 65 | static inline void func_get_function_name(zval *zv, zend_execute_data *ex) { 66 | ZVAL_STR_COPY(zv, ex->func->op_array.function_name); 67 | } 68 | 69 | static zend_function *find_function(zend_class_entry *ce, zend_string *name) { 70 | zend_function *func; 71 | ZEND_HASH_FOREACH_PTR(&ce->function_table, func) { 72 | if (zend_string_equals(func->common.function_name, name)) { 73 | return func; 74 | } 75 | } 76 | ZEND_HASH_FOREACH_END(); 77 | return NULL; 78 | } 79 | 80 | // find SpanAttribute attribute on a parameter, or on a parameter of 81 | // an interface 82 | static zend_attribute *find_spanattribute_attribute(zend_function *func, 83 | uint32_t i) { 84 | zend_attribute *attr = zend_get_parameter_attribute_str( 85 | func->common.attributes, spanattribute_fqn_lc, 86 | strlen(spanattribute_fqn_lc), i); 87 | 88 | if (attr != NULL) { 89 | return attr; 90 | } 91 | zend_class_entry *ce = func->common.scope; 92 | if (ce && ce->num_interfaces > 0) { 93 | zend_class_entry *interface_ce; 94 | for (uint32_t i = 0; i < ce->num_interfaces; i++) { 95 | interface_ce = ce->interfaces[i]; 96 | if (interface_ce != NULL) { 97 | // does the interface have the function we are looking for? 98 | zend_function *iface_func = 99 | find_function(interface_ce, func->common.function_name); 100 | if (iface_func != NULL) { 101 | // method found, check positional arg for attribute 102 | attr = zend_get_parameter_attribute_str( 103 | iface_func->common.attributes, spanattribute_fqn_lc, 104 | strlen(spanattribute_fqn_lc), i); 105 | if (attr != NULL) { 106 | return attr; 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | return NULL; 114 | } 115 | 116 | // find WithSpan in attributes, or in interface method attributes 117 | static zend_attribute *find_withspan_attribute(zend_function *func) { 118 | zend_attribute *attr; 119 | attr = zend_get_attribute_str(func->common.attributes, withspan_fqn_lc, 120 | strlen(withspan_fqn_lc)); 121 | if (attr != NULL) { 122 | return attr; 123 | } 124 | zend_class_entry *ce = func->common.scope; 125 | // if a method, check interfaces 126 | if (ce && ce->num_interfaces > 0) { 127 | zend_class_entry *interface_ce; 128 | for (uint32_t i = 0; i < ce->num_interfaces; i++) { 129 | interface_ce = ce->interfaces[i]; 130 | if (interface_ce != NULL) { 131 | // does the interface have the function we are looking for? 132 | zend_function *iface_func = 133 | find_function(interface_ce, func->common.function_name); 134 | if (iface_func != NULL) { 135 | // Method found in the interface, now check for attributes 136 | attr = zend_get_attribute_str(iface_func->common.attributes, 137 | withspan_fqn_lc, 138 | strlen(withspan_fqn_lc)); 139 | if (attr) { 140 | return attr; 141 | } 142 | } 143 | } 144 | } 145 | } 146 | return NULL; 147 | } 148 | 149 | static bool func_has_withspan_attribute(zend_execute_data *ex) { 150 | zend_attribute *attr = find_withspan_attribute(ex->func); 151 | 152 | return attr != NULL; 153 | } 154 | 155 | /* 156 | * OpenTelemetry attribute values may only be of limited types 157 | */ 158 | static bool is_valid_attribute_value(zval *val) { 159 | switch (Z_TYPE_P(val)) { 160 | case IS_STRING: 161 | case IS_LONG: 162 | case IS_DOUBLE: 163 | case IS_TRUE: 164 | case IS_FALSE: 165 | case IS_ARRAY: 166 | return true; 167 | default: 168 | return false; 169 | } 170 | } 171 | 172 | // get function args. any args with the 173 | // SpanAttributes attribute are added to the attributes HashTable 174 | static void func_get_args(zval *zv, HashTable *attributes, 175 | zend_execute_data *ex, bool check_for_attributes) { 176 | zval *p, *q; 177 | uint32_t i, first_extra_arg; 178 | uint32_t arg_count = ZEND_CALL_NUM_ARGS(ex); 179 | 180 | // @see 181 | // https://github.com/php/php-src/blob/php-8.1.0/Zend/zend_builtin_functions.c#L235 182 | if (arg_count) { 183 | array_init_size(zv, arg_count); 184 | if (ex->func->type == ZEND_INTERNAL_FUNCTION) { 185 | first_extra_arg = arg_count; 186 | } else { 187 | first_extra_arg = ex->func->op_array.num_args; 188 | } 189 | zend_hash_real_init_packed(Z_ARRVAL_P(zv)); 190 | ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(zv)) { 191 | i = 0; 192 | p = ZEND_CALL_ARG(ex, 1); 193 | if (arg_count > first_extra_arg) { 194 | while (i < first_extra_arg) { 195 | q = p; 196 | if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { 197 | ZVAL_DEREF(q); 198 | if (Z_OPT_REFCOUNTED_P(q)) { 199 | Z_ADDREF_P(q); 200 | } 201 | ZEND_HASH_FILL_SET(q); 202 | } else { 203 | ZEND_HASH_FILL_SET_NULL(); 204 | } 205 | ZEND_HASH_FILL_NEXT(); 206 | p++; 207 | i++; 208 | } 209 | p = ZEND_CALL_VAR_NUM(ex, ex->func->op_array.last_var + 210 | ex->func->op_array.T); 211 | } 212 | while (i < arg_count) { 213 | if (check_for_attributes && 214 | ex->func->type != ZEND_INTERNAL_FUNCTION) { 215 | zend_string *arg_name = ex->func->op_array.vars[i]; 216 | zend_attribute *attribute = 217 | find_spanattribute_attribute(ex->func, i); 218 | if (attribute != NULL && is_valid_attribute_value(p)) { 219 | if (attribute->argc) { 220 | zend_string *key = Z_STR(attribute->args[0].value); 221 | zend_hash_del(attributes, key); 222 | zend_hash_add(attributes, key, p); 223 | } else { 224 | zend_hash_del(attributes, arg_name); 225 | zend_hash_add(attributes, arg_name, p); 226 | } 227 | } 228 | } 229 | q = p; 230 | if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { 231 | ZVAL_DEREF(q); 232 | if (Z_OPT_REFCOUNTED_P(q)) { 233 | Z_ADDREF_P(q); 234 | } 235 | ZEND_HASH_FILL_SET(q); 236 | } else { 237 | ZEND_HASH_FILL_SET_NULL(); 238 | } 239 | ZEND_HASH_FILL_NEXT(); 240 | p++; 241 | i++; 242 | } 243 | } 244 | ZEND_HASH_FILL_END(); 245 | Z_ARRVAL_P(zv)->nNumOfElements = arg_count; 246 | } else { 247 | ZVAL_EMPTY_ARRAY(zv); 248 | } 249 | } 250 | 251 | static uint32_t func_get_arg_index_by_name(zend_execute_data *execute_data, 252 | zend_string *arg_name) { 253 | // @see 254 | // https://github.com/php/php-src/blob/php-8.1.0/Zend/zend_execute.c#L4515 255 | zend_function *fbc = execute_data->func; 256 | uint32_t num_args = fbc->common.num_args; 257 | if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) || 258 | EXPECTED(fbc->common.fn_flags & ZEND_ACC_USER_ARG_INFO)) { 259 | for (uint32_t i = 0; i < num_args; i++) { 260 | zend_arg_info *arg_info = &fbc->op_array.arg_info[i]; 261 | if (zend_string_equals(arg_name, arg_info->name)) { 262 | return i; 263 | } 264 | } 265 | } else { 266 | for (uint32_t i = 0; i < num_args; i++) { 267 | zend_internal_arg_info *arg_info = 268 | &fbc->internal_function.arg_info[i]; 269 | size_t len = strlen(arg_info->name); 270 | if (len == ZSTR_LEN(arg_name) && 271 | !memcmp(arg_info->name, ZSTR_VAL(arg_name), len)) { 272 | return i; 273 | } 274 | } 275 | } 276 | 277 | return (uint32_t)-1; 278 | } 279 | 280 | static inline void func_get_retval(zval *zv, zval *retval) { 281 | if (UNEXPECTED(!retval || Z_ISUNDEF_P(retval))) { 282 | ZVAL_NULL(zv); 283 | } else { 284 | ZVAL_COPY(zv, retval); 285 | } 286 | } 287 | 288 | static inline void func_get_exception(zval *zv) { 289 | zend_object *exception = EG(exception); 290 | if (exception && zend_is_unwind_exit(exception)) { 291 | ZVAL_NULL(zv); 292 | } else if (UNEXPECTED(exception)) { 293 | ZVAL_OBJ_COPY(zv, exception); 294 | } else { 295 | ZVAL_NULL(zv); 296 | } 297 | } 298 | 299 | static inline void func_get_declaring_scope(zval *zv, zend_execute_data *ex) { 300 | if (ex->func->op_array.scope) { 301 | ZVAL_STR_COPY(zv, ex->func->op_array.scope->name); 302 | } else { 303 | ZVAL_NULL(zv); 304 | } 305 | } 306 | 307 | static inline void func_get_filename(zval *zv, zend_execute_data *ex) { 308 | if (ex->func->type != ZEND_INTERNAL_FUNCTION) { 309 | ZVAL_STR_COPY(zv, ex->func->op_array.filename); 310 | } else { 311 | ZVAL_NULL(zv); 312 | } 313 | } 314 | 315 | static inline void func_get_lineno(zval *zv, zend_execute_data *ex) { 316 | if (ex->func->type != ZEND_INTERNAL_FUNCTION) { 317 | ZVAL_LONG(zv, ex->func->op_array.line_start); 318 | } else { 319 | ZVAL_NULL(zv); 320 | } 321 | } 322 | 323 | static inline void func_get_attribute_args(zval *zv, HashTable *attributes, 324 | zend_execute_data *ex) { 325 | if (!OTEL_G(attr_hooks_enabled)) { 326 | ZVAL_EMPTY_ARRAY(zv); 327 | return; 328 | } 329 | zend_attribute *attr = find_withspan_attribute(ex->func); 330 | if (attr == NULL || attr->argc == 0) { 331 | ZVAL_EMPTY_ARRAY(zv); 332 | return; 333 | } 334 | 335 | HashTable *ht; 336 | ALLOC_HASHTABLE(ht); 337 | zend_hash_init(ht, attr->argc, NULL, ZVAL_PTR_DTOR, 0); 338 | zend_attribute_arg arg; 339 | zend_string *key; 340 | 341 | for (uint32_t i = 0; i < attr->argc; i++) { 342 | arg = attr->args[i]; 343 | if (i == 2 || 344 | (arg.name && zend_string_equals_literal(arg.name, "attributes"))) { 345 | // attributes, append to a separate HashTable 346 | if (Z_TYPE(arg.value) == IS_ARRAY) { 347 | zend_hash_clean(attributes); // should already be empty 348 | HashTable *array_ht = Z_ARRVAL_P(&arg.value); 349 | zend_hash_copy(attributes, array_ht, zval_add_ref); 350 | } 351 | } else { 352 | if (arg.name != NULL) { 353 | zend_hash_add(ht, arg.name, &arg.value); 354 | } else { 355 | key = zend_string_init(with_span_attribute_args_keys[i], 356 | strlen(with_span_attribute_args_keys[i]), 357 | 0); 358 | zend_hash_add(ht, key, &arg.value); 359 | zend_string_release(key); 360 | } 361 | } 362 | } 363 | 364 | ZVAL_ARR(zv, ht); 365 | } 366 | 367 | /** 368 | * Check if the object implements or extends the specified class 369 | */ 370 | bool is_object_compatible_with_type_hint(zval *object_zval, 371 | zend_class_entry *type_hint) { 372 | zend_class_entry *object_ce = Z_OBJCE_P(object_zval); 373 | return instanceof_function(object_ce, type_hint); 374 | } 375 | 376 | /** 377 | * Check if pre/post function callback's signature is compatible with 378 | * the expected function signature. 379 | * This is a runtime check, since some parameters are only known at runtime. 380 | * Can be disabled via the opentelemetry.check_hook_functions ini value. 381 | */ 382 | static inline bool is_valid_signature(zend_fcall_info fci, 383 | zend_fcall_info_cache fcc) { 384 | if (OTEL_G(validate_hook_functions) == 0) { 385 | return 1; 386 | } 387 | zend_function *func = fcc.function_handler; 388 | zend_arg_info *arg_info; 389 | zend_type *arg_type; 390 | uint32_t type; 391 | uint32_t type_mask; 392 | 393 | for (uint32_t i = 0; i < func->common.num_args; i++) { 394 | // get type mask of callback argument 395 | arg_info = &func->common.arg_info[i]; 396 | arg_type = &arg_info->type; 397 | type_mask = arg_type->type_mask; 398 | 399 | // get actual value + type 400 | zval param = fci.params[i]; 401 | type = Z_TYPE(fci.params[i]); 402 | 403 | if (type_mask == IS_UNDEF) { 404 | // no type mask -> ok 405 | } else if (Z_TYPE(param) == IS_OBJECT) { 406 | // object special-case handling (check for interfaces, subclasses) 407 | zend_class_entry *ce = Z_OBJCE(param); 408 | if (!is_object_compatible_with_type_hint(¶m, ce)) { 409 | return false; 410 | } 411 | } else if ((type_mask & (1 << type)) == 0) { 412 | // type is not compatible with mask 413 | return false; 414 | } 415 | } 416 | 417 | return true; 418 | } 419 | 420 | static void exception_isolation_start(otel_exception_state *save_state) { 421 | save_state->exception = EG(exception); 422 | save_state->prev_exception = EG(prev_exception); 423 | save_state->opline_before_exception = EG(opline_before_exception); 424 | 425 | EG(exception) = NULL; 426 | EG(prev_exception) = NULL; 427 | EG(opline_before_exception) = NULL; 428 | 429 | // If the hook handler throws an exception, the execute_data of the outer 430 | // frame may have its opline set to an exception handler too. This is done 431 | // before the chance to clear the exception, so opline has to be restored 432 | // to original value. 433 | zend_execute_data *execute_data = EG(current_execute_data); 434 | if (execute_data != NULL) { 435 | save_state->has_opline = true; 436 | save_state->opline = execute_data->opline; 437 | } else { 438 | save_state->has_opline = false; 439 | save_state->opline = NULL; 440 | } 441 | } 442 | 443 | static zend_object *exception_isolation_end(otel_exception_state *save_state) { 444 | zend_object *suppressed = EG(exception); 445 | // NULL this before call to zend_clear_exception, as it would try to jump 446 | // to exception handler then. 447 | EG(exception) = NULL; 448 | 449 | // this clears prev_exception if it was set for any reason 450 | zend_clear_exception(); 451 | 452 | EG(exception) = save_state->exception; 453 | EG(prev_exception) = save_state->prev_exception; 454 | EG(opline_before_exception) = save_state->opline_before_exception; 455 | 456 | zend_execute_data *execute_data = EG(current_execute_data); 457 | if (execute_data != NULL && save_state->has_opline) { 458 | execute_data->opline = save_state->opline; 459 | } 460 | 461 | return suppressed; 462 | } 463 | 464 | static const char *zval_get_chars(zval *zv) { 465 | if (zv != NULL && Z_TYPE_P(zv) == IS_STRING) { 466 | return Z_STRVAL_P(zv); 467 | } 468 | return "null"; 469 | } 470 | 471 | static void exception_isolation_handle_exception(zend_object *suppressed, 472 | zval *class_name, 473 | zval *function_name, 474 | const char *type) { 475 | if (suppressed == NULL) { 476 | return; 477 | } 478 | 479 | zend_class_entry *exception_base = zend_get_exception_base(suppressed); 480 | zval return_value; 481 | zval *message = 482 | zend_read_property_ex(exception_base, suppressed, 483 | ZSTR_KNOWN(ZEND_STR_MESSAGE), 1, &return_value); 484 | 485 | php_error_docref(NULL, E_CORE_WARNING, 486 | "OpenTelemetry: %s threw exception," 487 | " class=%s function=%s message=%s", 488 | type, zval_get_chars(class_name), 489 | zval_get_chars(function_name), zval_get_chars(message)); 490 | 491 | if (message != NULL) { 492 | ZVAL_DEREF(message); 493 | } 494 | 495 | OBJ_RELEASE(suppressed); 496 | } 497 | 498 | static void arg_locator_initialize(otel_arg_locator *arg_locator, 499 | zend_execute_data *execute_data) { 500 | arg_locator->execute_data = execute_data; 501 | 502 | if (execute_data->func->type == ZEND_INTERNAL_FUNCTION) { 503 | // For internal functions, rather than having reserved number of slots 504 | // before auxiliary slots and extra ones after that, internal functions 505 | // have all (and only) arguments provided by call site before auxiliary 506 | // slots, and there is nothing after auxiliary slots. 507 | arg_locator->reserved = ZEND_CALL_NUM_ARGS(execute_data); 508 | } else { 509 | arg_locator->reserved = execute_data->func->op_array.last_var; 510 | } 511 | 512 | arg_locator->provided = ZEND_CALL_NUM_ARGS(execute_data); 513 | arg_locator->auxiliary_slots = execute_data->func->op_array.T; 514 | 515 | arg_locator->extended_used = 0; 516 | arg_locator->extended_start = arg_locator->provided > arg_locator->reserved 517 | ? arg_locator->provided 518 | : arg_locator->reserved; 519 | 520 | if (OTEL_G(allow_stack_extension)) { 521 | arg_locator->extended_max = STACK_EXTENSION_LIMIT; 522 | 523 | size_t slots_left_in_stack = EG(vm_stack_end) - EG(vm_stack_top); 524 | if (slots_left_in_stack < arg_locator->extended_max) { 525 | arg_locator->extended_max = slots_left_in_stack; 526 | } 527 | } else { 528 | arg_locator->extended_max = 0; 529 | } 530 | } 531 | 532 | static zval *arg_locator_get_slot(otel_arg_locator *arg_locator, uint32_t index, 533 | const char **failure_reason) { 534 | 535 | if (index < arg_locator->reserved) { 536 | return ZEND_CALL_ARG(arg_locator->execute_data, index + 1); 537 | } else if (index < arg_locator->provided) { 538 | return ZEND_CALL_ARG(arg_locator->execute_data, 539 | index + arg_locator->auxiliary_slots + 1); 540 | } 541 | 542 | uint32_t extended_index = index - arg_locator->extended_start; 543 | 544 | if (extended_index < arg_locator->extended_max) { 545 | uint32_t extended_index = index - arg_locator->extended_start; 546 | if (extended_index >= arg_locator->extended_used) { 547 | arg_locator->extended_used = extended_index + 1; 548 | } 549 | 550 | return &arg_locator->extended_slots[extended_index]; 551 | } 552 | 553 | if (failure_reason != NULL) { 554 | // Having a hardcoded upper limit allows performing stack 555 | // extension as one step in the end, rather than moving slots around in 556 | // the stack each time a new argument is discovered 557 | if (extended_index >= STACK_EXTENSION_LIMIT) { 558 | *failure_reason = "exceeds built-in stack extension limit"; 559 | } else if (!OTEL_G(allow_stack_extension)) { 560 | *failure_reason = "stack extension must be enabled with " 561 | "opentelemetry.allow_stack_extension option"; 562 | } else { 563 | *failure_reason = "not enough room left in stack page"; 564 | } 565 | } 566 | 567 | return NULL; 568 | } 569 | 570 | static void arg_locator_store_extended(otel_arg_locator *arg_locator) { 571 | if (arg_locator->extended_used == 0) { 572 | return; 573 | } 574 | 575 | // This is safe because extended_max is adjusted to not exceed current stack 576 | // page end 577 | EG(vm_stack_top) += arg_locator->extended_used; 578 | 579 | if (arg_locator->execute_data->func->type == ZEND_INTERNAL_FUNCTION) { 580 | // For internal functions, the additional arguments need to go before 581 | // the auxiliary slots, therefore the auxiliary slots need to be moved 582 | // ahead 583 | zval *target = 584 | ZEND_CALL_ARG(arg_locator->execute_data, arg_locator->provided + 1); 585 | zval *aux_target = target + arg_locator->extended_used; 586 | 587 | memmove(aux_target, target, 588 | sizeof(*aux_target) * arg_locator->auxiliary_slots); 589 | memcpy(target, arg_locator->extended_slots, 590 | sizeof(*target) * arg_locator->extended_used); 591 | } else { 592 | // For PHP functions, the additional arguments go to the end of the 593 | // frame, so nothing else needs to be moved around 594 | zval *target = ZEND_CALL_ARG(arg_locator->execute_data, 595 | arg_locator->extended_start + 596 | arg_locator->auxiliary_slots + 1); 597 | memcpy(target, arg_locator->extended_slots, 598 | sizeof(*target) * arg_locator->extended_used); 599 | } 600 | } 601 | 602 | static void observer_begin(zend_execute_data *execute_data, zend_llist *hooks) { 603 | if (!zend_llist_count(hooks)) { 604 | return; 605 | } 606 | 607 | zval params[8]; 608 | uint32_t param_count = 8; 609 | HashTable *attributes; 610 | ALLOC_HASHTABLE(attributes); 611 | zend_hash_init(attributes, 0, NULL, ZVAL_PTR_DTOR, 0); 612 | bool check_for_attributes = 613 | OTEL_G(attr_hooks_enabled) && func_has_withspan_attribute(execute_data); 614 | 615 | func_get_this_or_called_scope(¶ms[0], execute_data); 616 | func_get_attribute_args(¶ms[6], attributes, execute_data); 617 | func_get_args(¶ms[1], attributes, execute_data, check_for_attributes); 618 | func_get_declaring_scope(¶ms[2], execute_data); 619 | func_get_function_name(¶ms[3], execute_data); 620 | func_get_filename(¶ms[4], execute_data); 621 | func_get_lineno(¶ms[5], execute_data); 622 | 623 | ZVAL_ARR(¶ms[7], attributes); 624 | 625 | for (zend_llist_element *element = hooks->head; element; 626 | element = element->next) { 627 | zend_fcall_info fci = empty_fcall_info; 628 | zend_fcall_info_cache fcc = empty_fcall_info_cache; 629 | if (UNEXPECTED(zend_fcall_info_init((zval *)element->data, 0, &fci, 630 | &fcc, NULL, NULL) != SUCCESS)) { 631 | php_error_docref(NULL, E_WARNING, 632 | "Failed to initialize pre hook callable"); 633 | continue; 634 | } 635 | 636 | zval ret = {.u1.type_info = IS_UNDEF}; 637 | fci.param_count = param_count; 638 | fci.params = params; 639 | fci.named_params = NULL; 640 | fci.retval = &ret; 641 | 642 | if (!is_valid_signature(fci, fcc)) { 643 | php_error_docref(NULL, E_CORE_WARNING, 644 | "OpenTelemetry: pre hook invalid signature," 645 | " class=%s function=%s", 646 | (Z_TYPE_P(¶ms[2]) == IS_NULL) 647 | ? "null" 648 | : Z_STRVAL_P(¶ms[2]), 649 | Z_STRVAL_P(¶ms[3])); 650 | continue; 651 | } 652 | 653 | otel_exception_state save_state; 654 | exception_isolation_start(&save_state); 655 | 656 | if (zend_call_function(&fci, &fcc) == SUCCESS) { 657 | if (Z_TYPE(ret) == IS_ARRAY && 658 | !zend_is_identical(&ret, ¶ms[1])) { 659 | zend_ulong idx; 660 | zend_string *str_idx; 661 | zval *val; 662 | bool invalid_arg_warned = false; 663 | 664 | otel_arg_locator arg_locator; 665 | arg_locator_initialize(&arg_locator, execute_data); 666 | uint32_t args_initialized = arg_locator.provided; 667 | 668 | ZEND_HASH_FOREACH_KEY_VAL(Z_ARR(ret), idx, str_idx, val) { 669 | const char *failure_reason = ""; 670 | 671 | if (str_idx != NULL) { 672 | idx = func_get_arg_index_by_name(execute_data, str_idx); 673 | 674 | if (idx == (uint32_t)-1) { 675 | php_error_docref( 676 | NULL, E_CORE_WARNING, 677 | "OpenTelemetry: pre hook unknown " 678 | "named arg %s, class=%s function=%s", 679 | ZSTR_VAL(str_idx), zval_get_chars(¶ms[2]), 680 | zval_get_chars(¶ms[3])); 681 | continue; 682 | } 683 | } 684 | 685 | zval *target = arg_locator_get_slot(&arg_locator, idx, 686 | &failure_reason); 687 | 688 | if (target == NULL) { 689 | if (invalid_arg_warned) { 690 | continue; 691 | } 692 | 693 | php_error_docref(NULL, E_CORE_WARNING, 694 | "OpenTelemetry: pre hook invalid " 695 | "argument index " ZEND_ULONG_FMT 696 | " - %s, class=%s function=%s", 697 | idx, failure_reason, 698 | zval_get_chars(¶ms[2]), 699 | zval_get_chars(¶ms[3])); 700 | invalid_arg_warned = true; 701 | continue; 702 | } 703 | 704 | if (idx >= args_initialized) { 705 | // This slot was not initialized, need to initialize 706 | // all slots between current and the last initialized 707 | // one 708 | for (uint32_t i = args_initialized; i < idx; i++) { 709 | ZVAL_UNDEF( 710 | arg_locator_get_slot(&arg_locator, i, NULL)); 711 | ZEND_ADD_CALL_FLAG(execute_data, 712 | ZEND_CALL_MAY_HAVE_UNDEF); 713 | } 714 | 715 | args_initialized = idx + 1; 716 | } else { 717 | // This slot was already initialized, need to 718 | // decrement refcount before overwriting 719 | zval_dtor(target); 720 | } 721 | 722 | if (idx >= arg_locator.reserved && Z_REFCOUNTED_P(val)) { 723 | // If there are any "extra parameters" that are 724 | // refcounted, then this flag must be set. While we 725 | // cannot add any new extra parameter slots, this flag 726 | // may not have been present because all the values 727 | // were previously not refcounted 728 | ZEND_ADD_CALL_FLAG(execute_data, 729 | ZEND_CALL_FREE_EXTRA_ARGS); 730 | } 731 | 732 | ZVAL_COPY(target, val); 733 | 734 | if (idx < arg_locator.provided && 735 | Z_TYPE(params[1]) == IS_ARRAY) { 736 | // This index is present in the array provided to begin 737 | // hook, update it in that array as well 738 | Z_TRY_ADDREF_P(val); 739 | zend_hash_index_update(Z_ARR(params[1]), idx, val); 740 | } 741 | } 742 | ZEND_HASH_FOREACH_END(); 743 | 744 | arg_locator_store_extended(&arg_locator); 745 | 746 | // Update provided argument count if begin hook added arguments 747 | // that were not provided originally 748 | if (args_initialized > arg_locator.provided) { 749 | ZEND_CALL_NUM_ARGS(execute_data) = args_initialized; 750 | } 751 | } 752 | } 753 | 754 | zend_object *suppressed = exception_isolation_end(&save_state); 755 | exception_isolation_handle_exception(suppressed, ¶ms[2], ¶ms[3], 756 | "pre hook"); 757 | 758 | zval_dtor(&ret); 759 | } 760 | 761 | if (UNEXPECTED(ZEND_CALL_INFO(execute_data) & ZEND_CALL_MAY_HAVE_UNDEF)) { 762 | zend_object *exception = EG(exception); 763 | EG(exception) = (void *)(uintptr_t)-1; 764 | if (zend_handle_undef_args(execute_data) == FAILURE) { 765 | uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data); 766 | for (uint32_t i = 0; i < arg_count; i++) { 767 | zval *arg = ZEND_CALL_VAR_NUM(execute_data, i); 768 | if (!Z_ISUNDEF_P(arg)) { 769 | continue; 770 | } 771 | 772 | ZVAL_NULL(arg); 773 | } 774 | } 775 | EG(exception) = exception; 776 | } 777 | 778 | for (size_t i = 0; i < param_count; i++) { 779 | zval_dtor(¶ms[i]); 780 | } 781 | } 782 | 783 | static void observer_end(zend_execute_data *execute_data, zval *retval, 784 | zend_llist *hooks) { 785 | if (!zend_llist_count(hooks)) { 786 | return; 787 | } 788 | 789 | zval params[8]; 790 | uint32_t param_count = 8; 791 | 792 | func_get_this_or_called_scope(¶ms[0], execute_data); 793 | func_get_args(¶ms[1], NULL, execute_data, false); 794 | func_get_retval(¶ms[2], retval); 795 | func_get_exception(¶ms[3]); 796 | func_get_declaring_scope(¶ms[4], execute_data); 797 | func_get_function_name(¶ms[5], execute_data); 798 | func_get_filename(¶ms[6], execute_data); 799 | func_get_lineno(¶ms[7], execute_data); 800 | 801 | for (zend_llist_element *element = hooks->tail; element; 802 | element = element->prev) { 803 | zend_fcall_info fci = empty_fcall_info; 804 | zend_fcall_info_cache fcc = empty_fcall_info_cache; 805 | if (UNEXPECTED(zend_fcall_info_init((zval *)element->data, 0, &fci, 806 | &fcc, NULL, NULL) != SUCCESS)) { 807 | php_error_docref(NULL, E_WARNING, 808 | "Failed to initialize post hook callable"); 809 | continue; 810 | } 811 | 812 | zval ret = {.u1.type_info = IS_UNDEF}; 813 | fci.param_count = param_count; 814 | fci.params = params; 815 | fci.named_params = NULL; 816 | fci.retval = &ret; 817 | 818 | if (!is_valid_signature(fci, fcc)) { 819 | php_error_docref(NULL, E_CORE_WARNING, 820 | "OpenTelemetry: post hook invalid signature, " 821 | "class=%s function=%s", 822 | (Z_TYPE_P(¶ms[4]) == IS_NULL) 823 | ? "null" 824 | : Z_STRVAL_P(¶ms[4]), 825 | Z_STRVAL_P(¶ms[5])); 826 | continue; 827 | } 828 | 829 | otel_exception_state save_state; 830 | exception_isolation_start(&save_state); 831 | 832 | if (zend_call_function(&fci, &fcc) == SUCCESS) { 833 | /* TODO rather than ignoring return value if post callback doesn't 834 | have a return type-hint, could we check whether the types are 835 | compatible and allow if they are? */ 836 | if (!Z_ISUNDEF(ret) && 837 | (fcc.function_handler->op_array.fn_flags & 838 | ZEND_ACC_HAS_RETURN_TYPE) && 839 | !(ZEND_TYPE_PURE_MASK( 840 | fcc.function_handler->common.arg_info[-1].type) & 841 | MAY_BE_VOID)) { 842 | if (execute_data->return_value) { 843 | zval_ptr_dtor(execute_data->return_value); 844 | ZVAL_COPY(execute_data->return_value, &ret); 845 | zval_ptr_dtor(¶ms[2]); 846 | ZVAL_COPY_VALUE(¶ms[2], &ret); 847 | ZVAL_UNDEF(&ret); 848 | } 849 | } 850 | } 851 | 852 | zend_object *suppressed = exception_isolation_end(&save_state); 853 | exception_isolation_handle_exception(suppressed, ¶ms[4], ¶ms[5], 854 | "post hook"); 855 | 856 | zval_dtor(&ret); 857 | } 858 | 859 | for (size_t i = 0; i < param_count; i++) { 860 | zval_dtor(¶ms[i]); 861 | } 862 | } 863 | 864 | static void observer_begin_handler(zend_execute_data *execute_data) { 865 | otel_observer *observer = ZEND_OP_ARRAY_EXTENSION( 866 | &execute_data->func->op_array, op_array_extension); 867 | if (!observer || !zend_llist_count(&observer->pre_hooks)) { 868 | return; 869 | } 870 | 871 | observer_begin(execute_data, &observer->pre_hooks); 872 | } 873 | 874 | static void observer_end_handler(zend_execute_data *execute_data, 875 | zval *retval) { 876 | otel_observer *observer = ZEND_OP_ARRAY_EXTENSION( 877 | &execute_data->func->op_array, op_array_extension); 878 | if (!observer || !zend_llist_count(&observer->post_hooks)) { 879 | return; 880 | } 881 | 882 | observer_end(execute_data, retval, &observer->post_hooks); 883 | } 884 | 885 | static void free_observer(otel_observer *observer) { 886 | zend_llist_destroy(&observer->pre_hooks); 887 | zend_llist_destroy(&observer->post_hooks); 888 | efree(observer); 889 | } 890 | 891 | static void init_observer(otel_observer *observer) { 892 | zend_llist_init(&observer->pre_hooks, sizeof(zval), 893 | (llist_dtor_func_t)zval_ptr_dtor, 0); 894 | zend_llist_init(&observer->post_hooks, sizeof(zval), 895 | (llist_dtor_func_t)zval_ptr_dtor, 0); 896 | } 897 | 898 | static otel_observer *create_observer() { 899 | otel_observer *observer = emalloc(sizeof(otel_observer)); 900 | init_observer(observer); 901 | return observer; 902 | } 903 | 904 | static void copy_observer(otel_observer *source, otel_observer *destination) { 905 | destination->pre_hooks = source->pre_hooks; 906 | destination->post_hooks = source->post_hooks; 907 | } 908 | 909 | static bool find_observers(HashTable *ht, zend_string *n, zend_llist *pre_hooks, 910 | zend_llist *post_hooks) { 911 | otel_observer *observer = zend_hash_find_ptr_lc(ht, n); 912 | if (observer) { 913 | for (zend_llist_element *element = observer->pre_hooks.head; element; 914 | element = element->next) { 915 | zval_add_ref((zval *)&element->data); 916 | zend_llist_add_element(pre_hooks, &element->data); 917 | } 918 | for (zend_llist_element *element = observer->post_hooks.head; element; 919 | element = element->next) { 920 | zval_add_ref((zval *)&element->data); 921 | zend_llist_add_element(post_hooks, &element->data); 922 | } 923 | return true; 924 | } 925 | return false; 926 | } 927 | 928 | static void find_class_observers(HashTable *ht, HashTable *type_visited_lookup, 929 | zend_class_entry *ce, zend_llist *pre_hooks, 930 | zend_llist *post_hooks) { 931 | for (; ce; ce = ce->parent) { 932 | // Omit type if it was already visited 933 | if (zend_hash_exists(type_visited_lookup, ce->name)) { 934 | continue; 935 | } 936 | if (find_observers(ht, ce->name, pre_hooks, post_hooks)) { 937 | zend_hash_add_empty_element(type_visited_lookup, ce->name); 938 | } 939 | for (uint32_t i = 0; i < ce->num_interfaces; i++) { 940 | find_class_observers(ht, type_visited_lookup, ce->interfaces[i], 941 | pre_hooks, post_hooks); 942 | } 943 | } 944 | } 945 | 946 | static void find_method_observers(HashTable *ht, zend_class_entry *ce, 947 | zend_string *fn, zend_llist *pre_hooks, 948 | zend_llist *post_hooks) { 949 | // Below hashtable stores information 950 | // whether type was already visited 951 | // This information is used to prevent 952 | // adding hooks more than once in the case 953 | // of extensive class hierarchy 954 | HashTable type_visited_lookup; 955 | zend_hash_init(&type_visited_lookup, 8, NULL, NULL, 0); 956 | HashTable *lookup = zend_hash_find_ptr_lc(ht, fn); 957 | if (lookup) { 958 | find_class_observers(lookup, &type_visited_lookup, ce, pre_hooks, 959 | post_hooks); 960 | } 961 | zend_hash_destroy(&type_visited_lookup); 962 | } 963 | 964 | static zval create_attribute_observer_handler(char *fn) { 965 | zval callable; 966 | ZVAL_STRING(&callable, fn); 967 | 968 | return callable; 969 | } 970 | 971 | static otel_observer *resolve_observer(zend_execute_data *execute_data) { 972 | zend_function *fbc = execute_data->func; 973 | if (!fbc->common.function_name) { 974 | return NULL; 975 | } 976 | bool has_withspan_attribute = func_has_withspan_attribute(execute_data); 977 | 978 | if (OTEL_G(attr_hooks_enabled) == false && has_withspan_attribute && 979 | OTEL_G(display_warnings)) { 980 | php_error_docref(NULL, E_CORE_WARNING, 981 | "OpenTelemetry: WithSpan attribute found but " 982 | "attribute hooks disabled"); 983 | } 984 | 985 | otel_observer observer_instance; 986 | init_observer(&observer_instance); 987 | 988 | if (fbc->op_array.scope) { 989 | find_method_observers(OTEL_G(observer_class_lookup), 990 | fbc->op_array.scope, fbc->common.function_name, 991 | &observer_instance.pre_hooks, 992 | &observer_instance.post_hooks); 993 | } else { 994 | find_observers(OTEL_G(observer_function_lookup), 995 | fbc->common.function_name, &observer_instance.pre_hooks, 996 | &observer_instance.post_hooks); 997 | } 998 | 999 | if (!zend_llist_count(&observer_instance.pre_hooks) && 1000 | !zend_llist_count(&observer_instance.post_hooks)) { 1001 | if (OTEL_G(attr_hooks_enabled) && has_withspan_attribute) { 1002 | // there are no observers registered for this function/method, but 1003 | // it has a WithSpan attribute. Add configured attribute-based 1004 | // pre/post handlers as new observers. 1005 | zval pre = create_attribute_observer_handler( 1006 | OTEL_G(pre_handler_function_fqn)); 1007 | zval post = create_attribute_observer_handler( 1008 | OTEL_G(post_handler_function_fqn)); 1009 | add_observer(fbc->op_array.scope ? fbc->op_array.scope->name : NULL, 1010 | fbc->common.function_name, &pre, &post); 1011 | zval_ptr_dtor(&pre); 1012 | zval_ptr_dtor(&post); 1013 | // re-find to update pre/post hooks 1014 | if (fbc->op_array.scope) { 1015 | find_method_observers( 1016 | OTEL_G(observer_class_lookup), fbc->op_array.scope, 1017 | fbc->common.function_name, &observer_instance.pre_hooks, 1018 | &observer_instance.post_hooks); 1019 | } else { 1020 | find_observers(OTEL_G(observer_function_lookup), 1021 | fbc->common.function_name, 1022 | &observer_instance.pre_hooks, 1023 | &observer_instance.post_hooks); 1024 | } 1025 | 1026 | if (!zend_llist_count(&observer_instance.pre_hooks) && 1027 | !zend_llist_count(&observer_instance.post_hooks)) { 1028 | // failed to add hooks? 1029 | return NULL; 1030 | } 1031 | } else { 1032 | return NULL; 1033 | } 1034 | } 1035 | otel_observer *observer = create_observer(); 1036 | copy_observer(&observer_instance, observer); 1037 | zend_hash_next_index_insert_ptr(OTEL_G(observer_aggregates), observer); 1038 | 1039 | return observer; 1040 | } 1041 | 1042 | static zend_observer_fcall_handlers 1043 | observer_fcall_init(zend_execute_data *execute_data) { 1044 | // This means that either RINIT has not yet been called, or RSHUTDOWN has 1045 | // already been called. The former can happen if another extension that is 1046 | // loaded before this one invokes PHP functions in its RINIT. The latter 1047 | // can happen if a header callback is set or when another extension invokes 1048 | // PHP functions in their RSHUTDOWN. 1049 | if (OTEL_G(observer_class_lookup) == NULL) { 1050 | return (zend_observer_fcall_handlers){NULL, NULL}; 1051 | } 1052 | 1053 | if (op_array_extension == -1) { 1054 | return (zend_observer_fcall_handlers){NULL, NULL}; 1055 | } 1056 | 1057 | otel_observer *observer = resolve_observer(execute_data); 1058 | if (!observer) { 1059 | return (zend_observer_fcall_handlers){NULL, NULL}; 1060 | } 1061 | 1062 | ZEND_OP_ARRAY_EXTENSION(&execute_data->func->op_array, op_array_extension) = 1063 | observer; 1064 | return (zend_observer_fcall_handlers){ 1065 | zend_llist_count(&observer->pre_hooks) ? observer_begin_handler : NULL, 1066 | zend_llist_count(&observer->post_hooks) ? observer_end_handler : NULL, 1067 | }; 1068 | } 1069 | 1070 | static void destroy_observer_lookup(zval *zv) { free_observer(Z_PTR_P(zv)); } 1071 | 1072 | static void destroy_observer_class_lookup(zval *zv) { 1073 | HashTable *table = Z_PTR_P(zv); 1074 | zend_hash_destroy(table); 1075 | FREE_HASHTABLE(table); 1076 | } 1077 | 1078 | static void add_function_observer(HashTable *ht, zend_string *fn, 1079 | zval *pre_hook, zval *post_hook) { 1080 | zend_string *lc = zend_string_tolower(fn); 1081 | otel_observer *observer = zend_hash_find_ptr(ht, lc); 1082 | if (!observer) { 1083 | observer = create_observer(); 1084 | zend_hash_update_ptr(ht, lc, observer); 1085 | } 1086 | zend_string_release(lc); 1087 | 1088 | if (pre_hook) { 1089 | zval_add_ref(pre_hook); 1090 | zend_llist_add_element(&observer->pre_hooks, pre_hook); 1091 | } 1092 | if (post_hook) { 1093 | zval_add_ref(post_hook); 1094 | zend_llist_add_element(&observer->post_hooks, post_hook); 1095 | } 1096 | } 1097 | 1098 | static void add_method_observer(HashTable *ht, zend_string *cn, zend_string *fn, 1099 | zval *pre_hook, zval *post_hook) { 1100 | zend_string *lc = zend_string_tolower(fn); 1101 | HashTable *function_table = zend_hash_find_ptr(ht, lc); 1102 | if (!function_table) { 1103 | ALLOC_HASHTABLE(function_table); 1104 | zend_hash_init(function_table, 8, NULL, destroy_observer_lookup, 0); 1105 | zend_hash_update_ptr(ht, lc, function_table); 1106 | } 1107 | zend_string_release(lc); 1108 | 1109 | add_function_observer(function_table, cn, pre_hook, post_hook); 1110 | } 1111 | 1112 | bool add_observer(zend_string *cn, zend_string *fn, zval *pre_hook, 1113 | zval *post_hook) { 1114 | if (op_array_extension == -1) { 1115 | return false; 1116 | } 1117 | 1118 | if (cn) { 1119 | add_method_observer(OTEL_G(observer_class_lookup), cn, fn, pre_hook, 1120 | post_hook); 1121 | } else { 1122 | add_function_observer(OTEL_G(observer_function_lookup), fn, pre_hook, 1123 | post_hook); 1124 | } 1125 | 1126 | return true; 1127 | } 1128 | 1129 | void observer_globals_init(void) { 1130 | if (!OTEL_G(observer_class_lookup)) { 1131 | ALLOC_HASHTABLE(OTEL_G(observer_class_lookup)); 1132 | zend_hash_init(OTEL_G(observer_class_lookup), 8, NULL, 1133 | destroy_observer_class_lookup, 0); 1134 | } 1135 | if (!OTEL_G(observer_function_lookup)) { 1136 | ALLOC_HASHTABLE(OTEL_G(observer_function_lookup)); 1137 | zend_hash_init(OTEL_G(observer_function_lookup), 8, NULL, 1138 | destroy_observer_lookup, 0); 1139 | } 1140 | if (!OTEL_G(observer_aggregates)) { 1141 | ALLOC_HASHTABLE(OTEL_G(observer_aggregates)); 1142 | zend_hash_init(OTEL_G(observer_aggregates), 8, NULL, 1143 | destroy_observer_lookup, 0); 1144 | } 1145 | } 1146 | 1147 | void observer_globals_cleanup(void) { 1148 | if (OTEL_G(observer_class_lookup)) { 1149 | zend_hash_destroy(OTEL_G(observer_class_lookup)); 1150 | FREE_HASHTABLE(OTEL_G(observer_class_lookup)); 1151 | OTEL_G(observer_class_lookup) = NULL; 1152 | } 1153 | if (OTEL_G(observer_function_lookup)) { 1154 | zend_hash_destroy(OTEL_G(observer_function_lookup)); 1155 | FREE_HASHTABLE(OTEL_G(observer_function_lookup)); 1156 | OTEL_G(observer_function_lookup) = NULL; 1157 | } 1158 | if (OTEL_G(observer_aggregates)) { 1159 | zend_hash_destroy(OTEL_G(observer_aggregates)); 1160 | FREE_HASHTABLE(OTEL_G(observer_aggregates)); 1161 | OTEL_G(observer_aggregates) = NULL; 1162 | } 1163 | } 1164 | 1165 | void opentelemetry_observer_init(INIT_FUNC_ARGS) { 1166 | if (type != MODULE_TEMPORARY) { 1167 | zend_observer_fcall_register(observer_fcall_init); 1168 | op_array_extension = 1169 | zend_get_op_array_extension_handle("opentelemetry"); 1170 | #if PHP_VERSION_ID >= 80400 1171 | zend_get_internal_function_extension_handle("opentelemetry"); 1172 | #endif 1173 | } 1174 | } 1175 | -------------------------------------------------------------------------------- /ext/otel_observer.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef OPENTELEMETRY_OBSERVER_H 3 | #define OPENTELEMETRY_OBSERVER_H 4 | 5 | void opentelemetry_observer_init(INIT_FUNC_ARGS); 6 | void observer_globals_init(void); 7 | void observer_globals_cleanup(void); 8 | 9 | bool add_observer(zend_string *cn, zend_string *fn, zval *pre_hook, 10 | zval *post_hook); 11 | 12 | #endif // OPENTELEMETRY_OBSERVER_H 13 | -------------------------------------------------------------------------------- /ext/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | opentelemetry 4 | pecl.php.net 5 | OpenTelemetry auto-instrumentation support extension 6 | https://github.com/open-telemetry/opentelemetry-php-instrumentation 7 | 8 | Brett McBride 9 | brettmc 10 | brett@example.com 11 | yes 12 | 13 | 14 | Przemek Delewski 15 | pdelewski 16 | pdelewski@example.com 17 | yes 18 | 19 | 2025-05-08 20 | 21 | 22 | 1.1.3 23 | 1.0 24 | 25 | 26 | stable 27 | stable 28 | 29 | Apache 2.0 30 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.1.3 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 8.0.0 128 | 129 | 130 | 1.4.0 131 | 132 | 133 | 134 | opentelemetry 135 | 136 | 137 | 138 | 2023-03-30 139 | 151 | 152 | 2023-04-01 153 | 154 | 155 | 1.0.0beta3 156 | 1.0 157 | 158 | 159 | beta 160 | stable 161 | 162 | Apache 2.0 163 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.0beta3 164 | 165 | 166 | 2023-04-19 167 | 168 | 169 | 1.0.0beta4 170 | 1.0 171 | 172 | 173 | beta 174 | stable 175 | 176 | Apache 2.0 177 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.0beta4 178 | 179 | 180 | 2023-05-10 181 | 182 | 183 | 1.0.0beta5 184 | 1.0 185 | 186 | 187 | beta 188 | stable 189 | 190 | Apache 2.0 191 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.0beta5 192 | 193 | 194 | 2023-06-14 195 | 196 | 197 | 1.0.0beta6 198 | 1.0 199 | 200 | 201 | beta 202 | stable 203 | 204 | Apache 2.0 205 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.0beta6 206 | 207 | 208 | 2023-09-04 209 | 210 | 211 | 1.0.0beta7 212 | 1.0 213 | 214 | 215 | beta 216 | stable 217 | 218 | Apache 2.0 219 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.0beta7 220 | 221 | 222 | 2023-09-12 223 | 224 | 225 | 1.0.0RC1 226 | 1.0 227 | 228 | 229 | beta 230 | stable 231 | 232 | Apache 2.0 233 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.0RC1 234 | 235 | 236 | 2023-10-17 237 | 238 | 239 | 1.0.0RC2 240 | 1.0 241 | 242 | 243 | beta 244 | stable 245 | 246 | Apache 2.0 247 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.0RC2 248 | 249 | 250 | 2023-10-19 251 | 252 | 253 | 1.0.0RC3 254 | 1.0 255 | 256 | 257 | beta 258 | stable 259 | 260 | Apache 2.0 261 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.0RC3 262 | 263 | 264 | 2023-10-25 265 | 266 | 267 | 1.0.0 268 | 1.0 269 | 270 | 271 | stable 272 | stable 273 | 274 | Apache 2.0 275 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.0 276 | 277 | 278 | 2024-01-24 279 | 280 | 281 | 1.0.1beta1 282 | 1.0 283 | 284 | 285 | beta 286 | stable 287 | 288 | Apache 2.0 289 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.1beta1 290 | 291 | 292 | 2024-01-27 293 | 294 | 295 | 1.0.1beta2 296 | 1.0 297 | 298 | 299 | beta 300 | stable 301 | 302 | Apache 2.0 303 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.1beta2 304 | 305 | 306 | 2024-02-04 307 | 308 | 309 | 1.0.1 310 | 1.0 311 | 312 | 313 | stable 314 | stable 315 | 316 | Apache 2.0 317 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.1 318 | 319 | 320 | 2024-02-05 321 | 322 | 323 | 1.0.2beta1 324 | 1.0 325 | 326 | 327 | beta 328 | stable 329 | 330 | Apache 2.0 331 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.2beta1 332 | 333 | 334 | 2024-03-18 335 | 336 | 337 | 1.0.2beta2 338 | 1.0 339 | 340 | 341 | beta 342 | stable 343 | 344 | Apache 2.0 345 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.2beta2 346 | 347 | 348 | 2024-03-24 349 | 350 | 351 | 1.0.2beta3 352 | 1.0 353 | 354 | 355 | beta 356 | stable 357 | 358 | Apache 2.0 359 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.2beta3 360 | 361 | 362 | 2024-04-11 363 | 364 | 365 | 1.0.2 366 | 1.0 367 | 368 | 369 | stable 370 | stable 371 | 372 | Apache 2.0 373 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.2 374 | 375 | 376 | 2024-05-01 377 | 378 | 379 | 1.0.3beta1 380 | 1.0 381 | 382 | 383 | beta 384 | stable 385 | 386 | Apache 2.0 387 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.3beta1 388 | 389 | 390 | 2024-05-04 391 | 392 | 393 | 1.0.3 394 | 1.0 395 | 396 | 397 | stable 398 | stable 399 | 400 | Apache 2.0 401 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.0.3 402 | 403 | 404 | 2024-08-29 405 | 406 | 407 | 1.1.0beta1 408 | 1.0 409 | 410 | 411 | beta 412 | stable 413 | 414 | Apache 2.0 415 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.1.0beta1 416 | 417 | 418 | 2024-09-04 419 | 420 | 421 | 1.1.0beta2 422 | 1.0 423 | 424 | 425 | beta 426 | stable 427 | 428 | Apache 2.0 429 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.1.0beta2 430 | 431 | 432 | 2024-09-09 433 | 434 | 435 | 1.1.0beta3 436 | 1.0 437 | 438 | 439 | beta 440 | stable 441 | 442 | Apache 2.0 443 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.1.0beta3 444 | 445 | 446 | 2024-10-02 447 | 448 | 449 | 1.1.0 450 | 1.0 451 | 452 | 453 | stable 454 | stable 455 | 456 | Apache 2.0 457 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.1.0 458 | 459 | 460 | 2025-01-20 461 | 462 | 463 | 1.1.1 464 | 1.0 465 | 466 | 467 | stable 468 | stable 469 | 470 | Apache 2.0 471 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.1.1 472 | 473 | 474 | 2025-01-22 475 | 476 | 477 | 1.1.2 478 | 1.0 479 | 480 | 481 | stable 482 | stable 483 | 484 | Apache 2.0 485 | See https://github.com/open-telemetry/opentelemetry-php-instrumentation/releases/tag/1.1.2 486 | 487 | 488 | -------------------------------------------------------------------------------- /ext/php_opentelemetry.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef PHP_OPENTELEMETRY_H 3 | #define PHP_OPENTELEMETRY_H 4 | 5 | extern zend_module_entry opentelemetry_module_entry; 6 | #define phpext_opentelemetry_ptr &opentelemetry_module_entry 7 | 8 | ZEND_BEGIN_MODULE_GLOBALS(opentelemetry) 9 | HashTable *observer_class_lookup; 10 | HashTable *observer_function_lookup; 11 | HashTable *observer_aggregates; 12 | int validate_hook_functions; 13 | char *conflicts; 14 | int disabled; // module disabled? (eg due to conflicting extension loaded) 15 | int allow_stack_extension; 16 | int attr_hooks_enabled; // attribute hooking enabled? 17 | int display_warnings; 18 | char *pre_handler_function_fqn; 19 | char *post_handler_function_fqn; 20 | ZEND_END_MODULE_GLOBALS(opentelemetry) 21 | 22 | ZEND_EXTERN_MODULE_GLOBALS(opentelemetry) 23 | 24 | #define OTEL_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(opentelemetry, v) 25 | 26 | #define PHP_OPENTELEMETRY_VERSION "1.1.3" 27 | 28 | #if defined(ZTS) && defined(COMPILE_DL_OPENTELEMETRY) 29 | ZEND_TSRMLS_CACHE_EXTERN() 30 | #endif 31 | 32 | #endif /* PHP_OPENTELEMETRY_H */ 33 | -------------------------------------------------------------------------------- /ext/tests/001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if opentelemetry extension is loaded 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 9 | --EXPECTF-- 10 | The extension "opentelemetry" is available, version %s 11 | -------------------------------------------------------------------------------- /ext/tests/002.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hook returns true 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 11 | --EXPECT-- 12 | bool(true) 13 | -------------------------------------------------------------------------------- /ext/tests/003.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hooks are invoked 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | var_dump('PRE'), fn() => var_dump('POST')); 8 | 9 | function helloWorld() { 10 | var_dump('HELLO'); 11 | } 12 | 13 | helloWorld(); 14 | ?> 15 | --EXPECT-- 16 | string(3) "PRE" 17 | string(5) "HELLO" 18 | string(4) "POST" 19 | -------------------------------------------------------------------------------- /ext/tests/004.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if multiple hooks are invoked 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | var_dump('PRE_1'), fn() => var_dump('POST_1')); 8 | \OpenTelemetry\Instrumentation\hook(null, 'helloWorld', fn() => var_dump('PRE_2'), fn() => var_dump('POST_2')); 9 | 10 | function helloWorld() { 11 | var_dump('CALL'); 12 | } 13 | 14 | helloWorld(); 15 | ?> 16 | --EXPECT-- 17 | string(5) "PRE_1" 18 | string(5) "PRE_2" 19 | string(4) "CALL" 20 | string(6) "POST_2" 21 | string(6) "POST_1" 22 | -------------------------------------------------------------------------------- /ext/tests/005.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hooks receives function information 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | var_dump(func_get_args()), fn() => var_dump(func_get_args())); 8 | 9 | function helloWorld() { 10 | var_dump('CALL'); 11 | } 12 | 13 | helloWorld(); 14 | ?> 15 | --EXPECTF-- 16 | array(8) { 17 | [0]=> 18 | NULL 19 | [1]=> 20 | array(0) { 21 | } 22 | [2]=> 23 | NULL 24 | [3]=> 25 | string(10) "helloWorld" 26 | [4]=> 27 | string(%d) "%s%etests%e005.php" 28 | [5]=> 29 | int(4) 30 | [6]=> 31 | array(0) { 32 | } 33 | [7]=> 34 | array(0) { 35 | } 36 | } 37 | string(4) "CALL" 38 | array(8) { 39 | [0]=> 40 | NULL 41 | [1]=> 42 | array(0) { 43 | } 44 | [2]=> 45 | NULL 46 | [3]=> 47 | NULL 48 | [4]=> 49 | NULL 50 | [5]=> 51 | string(10) "helloWorld" 52 | [6]=> 53 | string(%d) "%s%etests%e005.php" 54 | [7]=> 55 | int(4) 56 | } 57 | -------------------------------------------------------------------------------- /ext/tests/006.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hooks receives arguments and return value 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | var_dump(func_get_args()), fn() => var_dump(func_get_args())); 8 | 9 | function helloWorld(string $a) { 10 | return 42; 11 | } 12 | 13 | helloWorld('a'); 14 | ?> 15 | --EXPECTF-- 16 | array(8) { 17 | [0]=> 18 | NULL 19 | [1]=> 20 | array(1) { 21 | [0]=> 22 | string(1) "a" 23 | } 24 | [2]=> 25 | NULL 26 | [3]=> 27 | string(10) "helloWorld" 28 | [4]=> 29 | string(%d) "%s%etests%e006.php" 30 | [5]=> 31 | int(4) 32 | [6]=> 33 | array(0) { 34 | } 35 | [7]=> 36 | array(0) { 37 | } 38 | } 39 | array(8) { 40 | [0]=> 41 | NULL 42 | [1]=> 43 | array(1) { 44 | [0]=> 45 | string(1) "a" 46 | } 47 | [2]=> 48 | int(42) 49 | [3]=> 50 | NULL 51 | [4]=> 52 | NULL 53 | [5]=> 54 | string(10) "helloWorld" 55 | [6]=> 56 | string(%d) "%s%etests%e006.php" 57 | [7]=> 58 | int(4) 59 | } 60 | -------------------------------------------------------------------------------- /ext/tests/007.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if post hook receives exception 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | var_dump($throwable?->getMessage())); 12 | 13 | function helloWorld() { 14 | throw new Exception('error'); 15 | } 16 | 17 | try { 18 | helloWorld(); 19 | } catch (Exception $e) {} 20 | ?> 21 | --EXPECT-- 22 | string(5) "error" 23 | -------------------------------------------------------------------------------- /ext/tests/008.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can replace arguments 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | ['b']); 8 | 9 | function helloWorld($a) { 10 | var_dump($a); 11 | } 12 | 13 | helloWorld('a'); 14 | ?> 15 | --EXPECT-- 16 | string(1) "b" 17 | -------------------------------------------------------------------------------- /ext/tests/009.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can modify not provided arguments 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | [1 => 'b']); 8 | 9 | function helloWorld($a = null, $b = null) { 10 | var_dump($a, $b); 11 | } 12 | 13 | helloWorld(); 14 | ?> 15 | --EXPECT-- 16 | NULL 17 | string(1) "b" 18 | -------------------------------------------------------------------------------- /ext/tests/010.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if post hook can modify return value 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 17); 8 | 9 | function helloWorld() { 10 | return 42; 11 | } 12 | 13 | var_dump(helloWorld()); 14 | ?> 15 | --EXPECT-- 16 | int(17) 17 | -------------------------------------------------------------------------------- /ext/tests/calling_die_lead_to_segfault.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if calling die or exit will finish gracefully 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 7 | 37 | 38 | --EXPECT-- 39 | exit! 40 | -------------------------------------------------------------------------------- /ext/tests/disable_does_nothing_with_bad_data.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if opentelemetry disable ignores bad input 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --INI-- 6 | opentelemetry.conflicts=, 7 | --FILE-- 8 | 11 | --EXPECTF-- 12 | bool(true) 13 | -------------------------------------------------------------------------------- /ext/tests/disable_hook_function_validation.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Disabling hook validation 3 | --DESCRIPTION-- 4 | The post hook is invalid, because of the type-hint on $object. Runtime checking is disabled, so it is executed and 5 | causes a fatal runtime error. 6 | --EXTENSIONS-- 7 | opentelemetry 8 | --INI-- 9 | opentelemetry.validate_hook_functions=Off 10 | --FILE-- 11 | 'replaced'); 13 | 14 | function hello(int $val) { 15 | return $val; 16 | } 17 | 18 | var_dump(hello(1)); 19 | ?> 20 | --EXPECTF-- 21 | %sArgument #1 ($object) must be of type Exception, null given%a -------------------------------------------------------------------------------- /ext/tests/disabled_with_conflicting_extension.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if opentelemetry extension is loaded but disabled with a conflicting extension 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --INI-- 6 | opentelemetry.conflicts=Core 7 | --FILE-- 8 | 10 | --EXPECTF-- 11 | Notice: PHP Startup: Conflicting extension found (Core), OpenTelemetry extension will be disabled in %s 12 | -------------------------------------------------------------------------------- /ext/tests/expand_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can expand params of function if they are part of function definition 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | null 14 | ); 15 | 16 | function helloWorld($a, $b) { 17 | var_dump(func_get_args()); 18 | } 19 | helloWorld('a'); 20 | ?> 21 | --EXPECT-- 22 | array(2) { 23 | [0]=> 24 | string(1) "a" 25 | [1]=> 26 | string(1) "b" 27 | } 28 | -------------------------------------------------------------------------------- /ext/tests/expand_params_extend.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can expand params of function when that requires extending the stack 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --INI-- 6 | opentelemetry.allow_stack_extension=On 7 | --FILE-- 8 | null 16 | ); 17 | 18 | function helloWorld($a, $b) { 19 | var_dump(func_get_args()); 20 | } 21 | helloWorld('a'); 22 | ?> 23 | --EXPECTF-- 24 | array(8) { 25 | [0]=> 26 | string(1) "a" 27 | [1]=> 28 | string(1) "b" 29 | [2]=> 30 | string(1) "c" 31 | [3]=> 32 | string(1) "d" 33 | [4]=> 34 | string(1) "e" 35 | [5]=> 36 | string(1) "f" 37 | [6]=> 38 | string(1) "g" 39 | [7]=> 40 | string(1) "h" 41 | } 42 | -------------------------------------------------------------------------------- /ext/tests/expand_params_extend_internal.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can expand params of internal function when that requires extending the stack 3 | --SKIPIF-- 4 | = 8.2'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.allow_stack_extension=On 9 | --FILE-- 10 | null 18 | ); 19 | 20 | var_dump(array_slice([1,2,3], 1)); 21 | ?> 22 | --EXPECTF-- 23 | array(1) { 24 | [1]=> 25 | int(2) 26 | } 27 | -------------------------------------------------------------------------------- /ext/tests/expand_params_extend_internal_long.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can expand params of internal function when that requires extending the stack (many params) 3 | --DESCRIPTION-- 4 | This will add MANY extra arguments to an internal function, making it fail with an error. 5 | However, the purpose of this test is to just make sure it does not somehow corrupt the stack 6 | and cause a crash with a large number of extra parameters. 7 | --SKIPIF-- 8 | = 8.2'); ?> 9 | --EXTENSIONS-- 10 | opentelemetry 11 | --INI-- 12 | opentelemetry.allow_stack_extension=On 13 | --FILE-- 14 | null 22 | ); 23 | 24 | var_dump(array_slice([1,2,3], 1)); 25 | ?> 26 | --EXPECTF-- 27 | Fatal error: Uncaught ArgumentCountError: array_slice() expects at most 4 arguments, 13 given in %s 28 | Stack trace: 29 | #0 %s: array_slice(Array, 1, 1, true, true, true, true, true, true, true, true, true, true) 30 | #1 {main} 31 | thrown in %s on line %d -------------------------------------------------------------------------------- /ext/tests/expand_params_extend_limit.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can expand params of function when that requires extending the stack only until hardcoded limit 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --INI-- 6 | opentelemetry.allow_stack_extension=On 7 | --FILE-- 8 | null 16 | ); 17 | 18 | function helloWorld($a, $b) { 19 | var_dump(func_get_args()); 20 | } 21 | helloWorld('a'); 22 | ?> 23 | --EXPECTF-- 24 | Warning: helloWorld(): OpenTelemetry: pre hook invalid argument index 18 - exceeds built-in stack extension limit, class=null function=helloWorld in %s 25 | array(18) { 26 | [0]=> 27 | string(1) "a" 28 | [1]=> 29 | string(1) "b" 30 | [2]=> 31 | string(1) "c" 32 | [3]=> 33 | string(1) "d" 34 | [4]=> 35 | string(1) "e" 36 | [5]=> 37 | string(1) "f" 38 | [6]=> 39 | string(1) "g" 40 | [7]=> 41 | string(1) "h" 42 | [8]=> 43 | string(1) "i" 44 | [9]=> 45 | string(1) "j" 46 | [10]=> 47 | string(1) "k" 48 | [11]=> 49 | string(1) "l" 50 | [12]=> 51 | string(1) "m" 52 | [13]=> 53 | string(1) "n" 54 | [14]=> 55 | string(1) "o" 56 | [15]=> 57 | string(1) "p" 58 | [16]=> 59 | string(1) "q" 60 | [17]=> 61 | string(1) "r" 62 | } 63 | -------------------------------------------------------------------------------- /ext/tests/expand_params_extra.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can expand params of function with extra parameters not provided by call site 3 | --DESCRIPTION-- 4 | Extra parameters for user functions are parameters that were provided at call site but were not present in the function 5 | declaration. The extension only supports modifying existing ones, not adding new ones. Test that a warning is logged if 6 | adding new ones is attempted and that it does not crash. 7 | --EXTENSIONS-- 8 | opentelemetry 9 | --FILE-- 10 | null 18 | ); 19 | 20 | function helloWorld($a, $b) { 21 | var_dump(func_get_args()); 22 | } 23 | helloWorld('a'); 24 | ?> 25 | --EXPECTF-- 26 | Warning: helloWorld(): OpenTelemetry: pre hook invalid argument index 2 - stack extension must be enabled with opentelemetry.allow_stack_extension option, class=null function=helloWorld in %s 27 | array(2) { 28 | [0]=> 29 | string(1) "a" 30 | [1]=> 31 | string(1) "b" 32 | } 33 | -------------------------------------------------------------------------------- /ext/tests/expand_params_internal.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can expand params of internal function 3 | --DESCRIPTION-- 4 | Removing the `post` callback avoids the segfault. The segfault is actually during shutdown, 5 | from `zend_observer_fcall_end_all` (https://github.com/php/php-src/blob/php-8.2.8/Zend/zend_observer.c#L291). 6 | When it traverses back through zend_execute_data, the top-level frame appears corrupted, and any attempt to reference 7 | it is what causes the segfault. 8 | --SKIPIF-- 9 | = 8.2'); ?> 10 | --EXTENSIONS-- 11 | opentelemetry 12 | --FILE-- 13 | null 21 | ); 22 | 23 | var_dump(array_slice([1,2,3], 1)); 24 | ?> 25 | --EXPECTF-- 26 | Warning: array_slice(): OpenTelemetry: pre hook invalid argument index 2 - stack extension must be enabled with opentelemetry.allow_stack_extension option, class=null function=array_slice in %s 27 | array(2) { 28 | [0]=> 29 | int(2) 30 | [1]=> 31 | int(3) 32 | } 33 | -------------------------------------------------------------------------------- /ext/tests/function_closure.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hooks are invoked for closures 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | var_dump('PRE'), fn() => var_dump('POST')); 8 | 9 | function helloWorld() { 10 | var_dump('HELLO'); 11 | } 12 | 13 | Closure::fromCallable('helloWorld')(); 14 | ?> 15 | --EXPECT-- 16 | string(3) "PRE" 17 | string(5) "HELLO" 18 | string(4) "POST" 19 | -------------------------------------------------------------------------------- /ext/tests/function_first_class_callable.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hooks are invoked for first class callables 3 | --SKIPIF-- 4 | 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --FILE-- 8 | var_dump('PRE'), fn() => var_dump('POST')); 10 | 11 | function helloWorld() { 12 | var_dump('HELLO'); 13 | } 14 | 15 | helloWorld(...)(); 16 | ?> 17 | --EXPECT-- 18 | string(3) "PRE" 19 | string(5) "HELLO" 20 | string(4) "POST" 21 | -------------------------------------------------------------------------------- /ext/tests/function_internal.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hooks are invoked for internal functions 3 | --SKIPIF-- 4 | = 8.2'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --FILE-- 8 | var_dump('PRE'), fn() => var_dump('POST')); 10 | 11 | array_map(var_dump(...), ['HELLO']); 12 | ?> 13 | --EXPECT-- 14 | string(3) "PRE" 15 | string(5) "HELLO" 16 | string(4) "POST" 17 | -------------------------------------------------------------------------------- /ext/tests/hooks_throw_exception.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if exceptions thrown in hooks are isolated and logged 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | throw new Exception('thrown in pre'), post: fn() => throw new Exception('thrown in post')); 8 | 9 | function helloWorld() { 10 | var_dump('function'); 11 | throw new Exception('original'); 12 | } 13 | 14 | try { 15 | helloWorld(); 16 | } catch (Exception $e) { 17 | var_dump($e->getMessage()); 18 | var_dump($e->getPrevious()); 19 | } 20 | ?> 21 | --EXPECTF-- 22 | 23 | Warning: helloWorld(): OpenTelemetry: pre hook threw exception, class=null function=helloWorld message=thrown in pre in %s 24 | string(8) "function" 25 | 26 | Warning: helloWorld(): OpenTelemetry: post hook threw exception, class=null function=helloWorld message=thrown in post in %s 27 | string(8) "original" 28 | NULL -------------------------------------------------------------------------------- /ext/tests/hooks_throw_exception_error_handler.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if exceptions thrown in hooks work if custom error handler throws 3 | --DESCRIPTION-- 4 | If the extension internally logs errors/warnings in a way that set_error_handler gets invoked, then any 5 | exceptions/errors may cause the process to crash or hang if raising a throwable was not safe at that moment. 6 | --EXTENSIONS-- 7 | opentelemetry 8 | --FILE-- 9 | 25 | --EXPECTF-- 26 | Warning: helloWorld(): OpenTelemetry: pre hook threw exception, class=null function=helloWorld message=pre in %s 27 | 28 | Fatal error: Uncaught Error: test in %s 29 | Stack trace: 30 | #0 %s: helloWorld() 31 | #1 {main} 32 | thrown in %s -------------------------------------------------------------------------------- /ext/tests/hooks_throw_exception_for_failed_call.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if exceptions thrown in hooks interfere with internal exceptions 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 22 | --EXPECTF-- 23 | 24 | Warning: helloWorld(): OpenTelemetry: pre hook threw exception, class=null function=helloWorld message=pre in %s 25 | 26 | Warning: helloWorld(): OpenTelemetry: post hook threw exception, class=null function=helloWorld message=post in %s 27 | 28 | Fatal error: Uncaught ArgumentCountError: Too few arguments to function helloWorld(), 0 passed in %s 29 | Stack trace: 30 | #0 %s: helloWorld() 31 | #1 {main} 32 | thrown in %s 33 | -------------------------------------------------------------------------------- /ext/tests/ini_set.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if process exits gracefully after using ini_set with an opentelemetry option 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 10 | --EXPECT-- 11 | string(4) "done" 12 | -------------------------------------------------------------------------------- /ext/tests/invalid_post_callback_signature.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test invalid post callback signature 3 | --DESCRIPTION-- 4 | The invalid callback signature should not cause a fatal, so it is checked before execution. If the function signature 5 | is invalid, the callback will not be called. 6 | --EXTENSIONS-- 7 | opentelemetry 8 | --FILE-- 9 | 31 | --EXPECTF-- 32 | string(3) "pre" 33 | string(4) "test" 34 | 35 | Warning: TestClass::test(): OpenTelemetry: post hook invalid signature, class=TestClass function=test in %s on line %d 36 | -------------------------------------------------------------------------------- /ext/tests/invalid_pre_callback_signature.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test invalid pre callback signature 3 | --DESCRIPTION-- 4 | The invalid callback signature should not cause a fatal, so it is checked before execution. If the function signature 5 | is invalid, the callback will not be called and a message will be written to error_log. 6 | --EXTENSIONS-- 7 | opentelemetry 8 | --FILE-- 9 | 31 | --EXPECTF-- 32 | Warning: TestClass::test(): OpenTelemetry: pre hook invalid signature, class=TestClass function=test in %s on line %d 33 | string(4) "test" 34 | string(4) "post" 35 | -------------------------------------------------------------------------------- /ext/tests/invalid_pre_callback_signature_with_function.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test invalid pre callback signature for function without class 3 | --DESCRIPTION-- 4 | The invalid callback signature should not cause a fatal, so it is checked before execution. If the function signature 5 | is invalid, the callback will not be called and a message will be written to error_log. Also tests logging handling of 6 | null class. 7 | --EXTENSIONS-- 8 | opentelemetry 9 | --FILE-- 10 | 30 | --EXPECTF-- 31 | Warning: hello(): OpenTelemetry: pre hook invalid signature, class=null function=hello in %s on line %d 32 | string(5) "hello" 33 | string(4) "post" 34 | -------------------------------------------------------------------------------- /ext/tests/mocks/SpanAttribute.php: -------------------------------------------------------------------------------- 1 | null 18 | ); 19 | 20 | function helloWorld($a, $b) { 21 | var_dump(func_get_args()); 22 | } 23 | helloWorld('a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6'); 24 | ?> 25 | --EXPECT-- 26 | array(7) { 27 | [0]=> 28 | string(2) "b0" 29 | [1]=> 30 | string(2) "b1" 31 | [2]=> 32 | string(2) "b2" 33 | [3]=> 34 | string(2) "b3" 35 | [4]=> 36 | string(2) "b4" 37 | [5]=> 38 | string(2) "b5" 39 | [6]=> 40 | string(2) "b6" 41 | } 42 | -------------------------------------------------------------------------------- /ext/tests/modify_extra_params_internal.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook trying to modify extra params of internal functions crashes 3 | --DESCRIPTION-- 4 | Modifying extra parameters of internal functions is actually not useful at all since providing too many parameters 5 | to an internal function always causes an Error to be thrown anyway. However, the test is still needed to make sure it 6 | does not crash. 7 | --SKIPIF-- 8 | = 8.2'); ?> 9 | --EXTENSIONS-- 10 | opentelemetry 11 | --FILE-- 12 | null 20 | ); 21 | 22 | try { 23 | var_dump(array_slice([1,2,3], 1, 1, false, 'a')); 24 | } catch (Throwable $t) { 25 | var_dump($t->getMessage()); 26 | } 27 | ?> 28 | --EXPECT-- 29 | string(50) "array_slice() expects at most 4 arguments, 5 given" -------------------------------------------------------------------------------- /ext/tests/modify_named_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can modify named params of function 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 'replaced', 13 | ]; 14 | } 15 | ); 16 | function hello($one = null, $two = null, $three = null) { 17 | var_dump(func_get_args()); 18 | } 19 | 20 | hello('a', 'b', 'c'); 21 | ?> 22 | --EXPECT-- 23 | array(3) { 24 | [0]=> 25 | string(1) "a" 26 | [1]=> 27 | string(8) "replaced" 28 | [2]=> 29 | string(1) "c" 30 | } 31 | -------------------------------------------------------------------------------- /ext/tests/modify_named_params_internal.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can modify named params of internal function 3 | --SKIPIF-- 4 | = 8.2'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --FILE-- 8 | 1 15 | ]; 16 | }, 17 | post: fn() => null 18 | ); 19 | 20 | var_dump(array_slice([1,2,3], 1, 2)); 21 | ?> 22 | --EXPECTF-- 23 | array(1) { 24 | [0]=> 25 | int(2) 26 | } 27 | -------------------------------------------------------------------------------- /ext/tests/modify_named_params_invalid.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can try to modify invalid named params of function 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 'replaced', 13 | ]; 14 | } 15 | ); 16 | function hello($one = null, $two = null, $three = null) { 17 | var_dump(func_get_args()); 18 | } 19 | 20 | hello('a', 'b', 'c'); 21 | ?> 22 | --EXPECTF-- 23 | Warning: hello(): OpenTelemetry: pre hook unknown named arg four, class=null function=hello in %s 24 | array(3) { 25 | [0]=> 26 | string(1) "a" 27 | [1]=> 28 | string(1) "b" 29 | [2]=> 30 | string(1) "c" 31 | } 32 | -------------------------------------------------------------------------------- /ext/tests/modify_named_params_twice.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can modify same param via name and index at once 3 | --DESCRIPTION-- 4 | Tests that the last entry for the same param (either by name or index) is applied, and that no crashes or memory leaks 5 | are caused by changing the same parameter in two different ways at once. 6 | --EXTENSIONS-- 7 | opentelemetry 8 | --FILE-- 9 | 'twoindex', 16 | 'two' => 'twoname', 17 | 'three' => 'threename', 18 | 2 => 'threeindex', 19 | ]; 20 | } 21 | ); 22 | function hello($one = null, $two = null, $three = null) { 23 | var_dump(func_get_args()); 24 | } 25 | 26 | hello('a', 'b', 'c'); 27 | ?> 28 | --EXPECT-- 29 | array(3) { 30 | [0]=> 31 | string(1) "a" 32 | [1]=> 33 | string(7) "twoname" 34 | [2]=> 35 | string(10) "threeindex" 36 | } 37 | -------------------------------------------------------------------------------- /ext/tests/modify_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can modify params of function 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | null, //make first param null 13 | 2 => 'baz', //replace 3rd param 14 | 3 => 'bat', //add 4th param 15 | ]; 16 | } 17 | ); 18 | function hello($one = null, $two = null, $three = null, $four = null) { 19 | var_dump(func_get_args()); 20 | } 21 | 22 | hello('a', 'b', 'c'); 23 | ?> 24 | --EXPECT-- 25 | array(4) { 26 | [0]=> 27 | NULL 28 | [1]=> 29 | string(1) "b" 30 | [2]=> 31 | string(3) "baz" 32 | [3]=> 33 | string(3) "bat" 34 | } 35 | -------------------------------------------------------------------------------- /ext/tests/multiple_hooks_modify_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hooks receive modified arguments 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | [++$params[0]]); 8 | \OpenTelemetry\Instrumentation\hook(null, 'helloWorld', fn(mixed $object, array $params) => [++$params[0]]); 9 | 10 | function helloWorld($a) { 11 | var_dump($a); 12 | } 13 | 14 | helloWorld('a'); 15 | ?> 16 | --EXPECT-- 17 | string(1) "c" 18 | -------------------------------------------------------------------------------- /ext/tests/multiple_hooks_modify_returnvalue.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hooks receive modified return value 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | ++$return); 8 | \OpenTelemetry\Instrumentation\hook(null, 'helloWorld', post: fn(mixed $object, array $params, int $return): int => ++$return); 9 | 10 | function helloWorld(int $val): int { 11 | return $val; 12 | } 13 | 14 | var_dump(helloWorld(1)); 15 | ?> 16 | --EXPECT-- 17 | int(3) 18 | -------------------------------------------------------------------------------- /ext/tests/post_hook_return_ignored_without_type.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if post hook return value is ignored if return typehint not supplied 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 'ignored'); 8 | 9 | function helloWorld(string $val) { 10 | return $val; 11 | } 12 | 13 | var_dump(helloWorld('foo')); 14 | ?> 15 | --EXPECT-- 16 | string(3) "foo" 17 | -------------------------------------------------------------------------------- /ext/tests/post_hook_returns_cloned_modified_object.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if post hook can returned modified clone 3 | ----DESCRIPTION-- 4 | A different object might be returned than the one provided to post hook. For example, PSR-7 messages are immutable and modifying 5 | one creates a new instance. 6 | --EXTENSIONS-- 7 | opentelemetry 8 | --FILE-- 9 | a = $a; 16 | } 17 | public function modify(string $value): Foo 18 | { 19 | $new = clone($this); 20 | $new->a = $value; 21 | 22 | return $new; 23 | } 24 | } 25 | 26 | \OpenTelemetry\Instrumentation\hook(null, 'getFoo', null, function($obj, array $params, Foo $foo): Foo { 27 | return $foo->modify('b'); 28 | }); 29 | 30 | function getFoo(): Foo { 31 | return new Foo('a'); 32 | } 33 | 34 | var_dump(getFoo()); 35 | ?> 36 | --EXPECTF-- 37 | object(Foo)#%d (1) { 38 | ["a"]=> 39 | string(1) "b" 40 | } 41 | -------------------------------------------------------------------------------- /ext/tests/post_hook_throws_exception.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if throwing an exception in post hook after IO operation will finish gracefully 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | var_dump('pre'), 11 | fn(object|string|null $scope, array $params, mixed $returnValue, ?Throwable $throwable) => throw new Exception('error')); 12 | 13 | 14 | function helloWorld() { 15 | // below scandir call or any other 16 | // IO operation is necessary to trigger 17 | // segfault. 18 | scandir("."); 19 | } 20 | 21 | try { 22 | helloWorld(); 23 | } catch(Exception) {} 24 | 25 | --EXPECTF-- 26 | string(3) "pre" 27 | 28 | Warning: helloWorld(): OpenTelemetry: post hook threw exception, class=null function=helloWorld message=error in %s -------------------------------------------------------------------------------- /ext/tests/post_hook_type_error.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if type error in post hook is handled 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | var_dump('pre'), 19 | fn(string $scope, array $params, mixed $returnValue, ?Throwable $throwable) => var_dump('post')); //NB invalid type for $scope 20 | 21 | (new Foo())->bar(); 22 | var_dump('baz'); 23 | --EXPECTF-- 24 | string(3) "pre" 25 | string(3) "bar" 26 | 27 | Warning: Foo::bar(): OpenTelemetry: post hook threw exception, class=Foo function=bar message=%sArgument #1 ($scope) must be of type string, Foo given%s 28 | string(3) "baz" -------------------------------------------------------------------------------- /ext/tests/post_hooks_after_die.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Calling die/exit still executes post hooks 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 7 | var_dump('PRE'), fn() => var_dump('POST')); 17 | 18 | goodbye(); 19 | ?> 20 | 21 | --EXPECT-- 22 | string(3) "PRE" 23 | string(7) "goodbye" 24 | string(4) "POST" -------------------------------------------------------------------------------- /ext/tests/reimplemented_interface.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hooks are invoked only once for reimplemented interfaces 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | var_dump('PRE'), fn() => var_dump('POST')); 17 | 18 | (new C)->m(); 19 | ?> 20 | --EXPECT-- 21 | string(3) "PRE" 22 | string(4) "POST" 23 | -------------------------------------------------------------------------------- /ext/tests/return_expanded_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can expand then return $params 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 19 | --EXPECT-- 20 | string(1) "a" 21 | string(1) "b" 22 | -------------------------------------------------------------------------------- /ext/tests/return_expanded_params_internal.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can expand and then return $params of internal function 3 | --DESCRIPTION-- 4 | The existence of a post callback is part of the failure preconditions. 5 | --SKIPIF-- 6 | = 8.2'); ?> 7 | --EXTENSIONS-- 8 | opentelemetry 9 | --FILE-- 10 | null //does not fail without post callback 19 | ); 20 | 21 | var_dump(array_slice(['a', 'b', 'c'], 1)); 22 | ?> 23 | --EXPECTF-- 24 | Warning: array_slice(): OpenTelemetry: pre hook invalid argument index 2 - stack extension must be enabled with opentelemetry.allow_stack_extension option, class=null function=array_slice in %s 25 | array(2) { 26 | [0]=> 27 | string(1) "b" 28 | [1]=> 29 | string(1) "c" 30 | } 31 | -------------------------------------------------------------------------------- /ext/tests/return_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can return $params 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | $params); 8 | 9 | function helloWorld($a) { 10 | var_dump($a); 11 | } 12 | 13 | helloWorld('a'); 14 | ?> 15 | --EXPECT-- 16 | string(1) "a" 17 | -------------------------------------------------------------------------------- /ext/tests/return_params_internal.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can return $params for internal function 3 | --SKIPIF-- 4 | = 8.2'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --FILE-- 8 | $params); 10 | 11 | array_map(var_dump(...), ['HELLO']); 12 | ?> 13 | --EXPECT-- 14 | string(5) "HELLO" 15 | -------------------------------------------------------------------------------- /ext/tests/return_reduced_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if pre hook can reduce then return $params 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 18 | --EXPECT-- 19 | string(1) "a" 20 | NULL 21 | string(1) "c" 22 | -------------------------------------------------------------------------------- /ext/tests/span_attribute/attribute_on_interface.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if SpanAttribute can be applied to interface 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | foo('one', 'two'); 40 | ?> 41 | --EXPECT-- 42 | string(3) "pre" 43 | array(2) { 44 | ["renamed_one_from_interface"]=> 45 | string(3) "one" 46 | ["renamed_two_from_class"]=> 47 | string(3) "two" 48 | } 49 | string(3) "foo" 50 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/span_attribute/function_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if function params can be passed via SpanAttribute 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | 34 | --EXPECT-- 35 | string(3) "pre" 36 | array(5) { 37 | ["renamed_one"]=> 38 | string(3) "one" 39 | ["two"]=> 40 | int(99) 41 | ["renamed_three"]=> 42 | float(3.14159) 43 | ["four"]=> 44 | bool(true) 45 | ["six"]=> 46 | string(3) "six" 47 | } 48 | string(3) "foo" 49 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/span_attribute/function_params_non_simple.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if function non-simple types are ignored 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | 'bar'], 32 | two: new \stdClass(), 33 | three: function(){return 'fn';}, 34 | four: null, 35 | ); 36 | ?> 37 | --EXPECTF-- 38 | string(3) "pre" 39 | array(1) { 40 | ["one"]=> 41 | array(1) { 42 | ["foo"]=> 43 | string(3) "bar" 44 | } 45 | } 46 | string(3) "foo" 47 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/span_attribute/method_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if method params can be passed via SpanAttribute 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | foo('one', 99, 3.14159, true, 'five', 'six'); 37 | ?> 38 | --EXPECT-- 39 | string(3) "pre" 40 | array(5) { 41 | ["renamed_one"]=> 42 | string(3) "one" 43 | ["two"]=> 44 | int(99) 45 | ["renamed_three"]=> 46 | float(3.14159) 47 | ["four"]=> 48 | bool(true) 49 | ["six"]=> 50 | string(3) "six" 51 | } 52 | string(3) "foo" 53 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/span_attribute/span_attribute_attribute_priority.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if attributes from SpanAttribute replace attributes with same name from WithSpan 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | 'one_from_withspan', 'two' => 'two_from_withspan'])] 22 | function foo( 23 | #[SpanAttribute] string $one, 24 | ): void 25 | { 26 | var_dump('foo'); 27 | } 28 | } 29 | 30 | $c = new TestClass(); 31 | $c->foo('one'); 32 | ?> 33 | --EXPECT-- 34 | string(3) "pre" 35 | array(2) { 36 | ["two"]=> 37 | string(17) "two_from_withspan" 38 | ["one"]=> 39 | string(3) "one" 40 | } 41 | string(3) "foo" 42 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/static_method.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check hooking static class methods provides class name as 1st param 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 27 | --EXPECT-- 28 | string(4) "Demo" 29 | string(5) "hello" 30 | string(4) "Demo" 31 | -------------------------------------------------------------------------------- /ext/tests/unwind_exit.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test UnwindExit from die/exit is not exposed to userland code 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --FILE-- 6 | 7 | 29 | 30 | --EXPECT-- 31 | exit! 32 | post 33 | -------------------------------------------------------------------------------- /ext/tests/with_span/attribute_named_params_passed_to_pre_hook.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if named attribute parameters are passed to pre hook 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | foo(); 39 | ?> 40 | --EXPECT-- 41 | array(1) { 42 | ["span_kind"]=> 43 | int(3) 44 | } 45 | string(3) "foo" 46 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/with_span/attribute_on_function.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if custom attribute loaded 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | getAttributes()[0]->getName() == WithSpan::class); 25 | 26 | otel_attr_test(); 27 | ?> 28 | --EXPECT-- 29 | bool(true) 30 | string(3) "pre" 31 | string(4) "test" 32 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/with_span/attribute_on_interface.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if custom attribute can be applied to an interface 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | sayFoo(); 43 | $c->sayBar(); 44 | ?> 45 | --EXPECT-- 46 | string(3) "pre" 47 | string(3) "foo" 48 | string(4) "post" 49 | string(3) "pre" 50 | string(3) "bar" 51 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/with_span/attribute_on_interface_with_params.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if WithSpan can be applied to an interface with attribute args 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | 'bar'])] 33 | function sayFoo(): void; 34 | } 35 | 36 | class TestClass implements TestInterface 37 | { 38 | function sayFoo(): void 39 | { 40 | var_dump('foo'); 41 | } 42 | } 43 | 44 | (new TestClass())->sayFoo(); 45 | ?> 46 | --EXPECT-- 47 | string(3) "pre" 48 | array(2) { 49 | ["name"]=> 50 | string(3) "one" 51 | ["span_kind"]=> 52 | int(99) 53 | } 54 | array(1) { 55 | ["foo"]=> 56 | string(3) "bar" 57 | } 58 | string(3) "foo" 59 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/with_span/attribute_on_method.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if WithSpan can be applied to a method 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | getAttributes()[0]->getName() == WithSpan::class); 28 | 29 | $c = new TestClass(); 30 | $c->sayFoo(); 31 | ?> 32 | --EXPECT-- 33 | bool(true) 34 | string(3) "pre" 35 | string(3) "foo" 36 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/with_span/attribute_params_passed_to_pre_hook.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if WithSpan parameters are passed to pre hook 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | 'value1', 'attr2' => 3.14])] 33 | function foo(): void 34 | { 35 | var_dump('foo'); 36 | } 37 | } 38 | 39 | (new Foo())->foo(); 40 | ?> 41 | --EXPECT-- 42 | array(2) { 43 | ["name"]=> 44 | string(6) "param1" 45 | ["span_kind"]=> 46 | int(99) 47 | } 48 | array(2) { 49 | ["attr1"]=> 50 | string(6) "value1" 51 | ["attr2"]=> 52 | float(3.14) 53 | } 54 | string(3) "foo" 55 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/with_span/attribute_params_skipped_if_hooked.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if hooking a method takes priority over WithSpan 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --DESCRIPTION-- 6 | Attribute-based hooks are only applied if no other hooks are registered on a function or method. 7 | --EXTENSIONS-- 8 | opentelemetry 9 | --INI-- 10 | opentelemetry.attr_hooks_enabled = On 11 | --FILE-- 12 | foo(); 42 | ?> 43 | --EXPECT-- 44 | string(3) "pre" 45 | string(3) "foo" 46 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/with_span/autoloaded_handler.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if WithSpanHandler can be provided by an autoloader 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | sayFoo(); 38 | ?> 39 | --EXPECT-- 40 | string(62) "autoloading: OpenTelemetry\API\Instrumentation\WithSpanHandler" 41 | string(3) "pre" 42 | string(3) "foo" 43 | string(4) "post" -------------------------------------------------------------------------------- /ext/tests/with_span/customize_handlers.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if WithSpan handlers can be changed via config 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | opentelemetry.attr_pre_handler_function = custom_pre 10 | opentelemetry.attr_post_handler_function = custom_post 11 | --FILE-- 12 | 36 | --EXPECT-- 37 | string(10) "custom_pre" 38 | string(11) "custom_post" 39 | string(18) "custom_pre handler" 40 | string(4) "test" 41 | string(19) "custom_post handler" -------------------------------------------------------------------------------- /ext/tests/with_span/disable.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if attribute hooks can be disabled by config 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = Off 9 | opentelemetry.display_warnings = On 10 | --FILE-- 11 | 38 | --EXPECTF-- 39 | Warning: %s: OpenTelemetry: WithSpan attribute found but attribute hooks disabled in Unknown on line %d 40 | string(4) "test" 41 | -------------------------------------------------------------------------------- /ext/tests/with_span/handler_not_found.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check if warning emitted when default handler does not exist 3 | --SKIPIF-- 4 | = 8.1'); ?> 5 | --EXTENSIONS-- 6 | opentelemetry 7 | --INI-- 8 | opentelemetry.attr_hooks_enabled = On 9 | --FILE-- 10 | sayFoo(); 27 | ?> 28 | --EXPECTF-- 29 | 30 | Warning: OpenTelemetry\API\Instrumentation\TestClass::sayFoo(): Failed to initialize pre hook callable in %s 31 | string(3) "foo" 32 | 33 | Warning: OpenTelemetry\API\Instrumentation\TestClass::sayFoo(): Failed to initialize post hook callable in %s -------------------------------------------------------------------------------- /ext/tests/with_span/invalid_callback.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Invalid callback is ignored 3 | --EXTENSIONS-- 4 | opentelemetry 5 | --INI-- 6 | opentelemetry.attr_hooks_enabled = On 7 | opentelemetry.attr_pre_handler_function = "Invalid::pre" 8 | opentelemetry.attr_post_handler_function = "Also\Invalid::post" 9 | --FILE-- 10 | 23 | --EXPECT-- 24 | string(12) "Invalid::pre" 25 | string(18) "Also\Invalid::post" 26 | string(4) "test" --------------------------------------------------------------------------------