├── .dockerignore ├── .github ├── pull_request_template.md └── workflows │ ├── ci-1.x.yml │ ├── ci-2.x.yml │ ├── ci-3.x.yml │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── EXAMPLES.md ├── LICENSE ├── POST_FORMAT.md ├── README.md ├── THANKS.md ├── TODO.md ├── composer.json ├── data └── rollbar.snippet.js ├── phpunit.xml ├── psalm.baseline ├── psalm.xml ├── src ├── Config.php ├── DataBuilder.php ├── DataBuilderInterface.php ├── Defaults.php ├── ErrorWrapper.php ├── FilterInterface.php ├── Handlers │ ├── AbstractHandler.php │ ├── ErrorHandler.php │ ├── ExceptionHandler.php │ └── FatalHandler.php ├── LevelFactory.php ├── Payload │ ├── Body.php │ ├── ContentInterface.php │ ├── Context.php │ ├── Data.php │ ├── EncodedPayload.php │ ├── ExceptionInfo.php │ ├── Frame.php │ ├── Level.php │ ├── Message.php │ ├── Notifier.php │ ├── Payload.php │ ├── Person.php │ ├── Request.php │ ├── Server.php │ ├── TelemetryBody.php │ ├── TelemetryEvent.php │ ├── Trace.php │ └── TraceChain.php ├── Response.php ├── ResponseHandlerInterface.php ├── Rollbar.php ├── RollbarJsHelper.php ├── RollbarLogger.php ├── Scrubber.php ├── ScrubberInterface.php ├── Senders │ ├── AgentSender.php │ ├── CurlSender.php │ ├── FluentSender.php │ └── SenderInterface.php ├── SerializerInterface.php ├── Telemetry │ ├── EventLevel.php │ ├── EventType.php │ ├── Telemeter.php │ └── TelemetryFilterInterface.php ├── TransformerInterface.php ├── Truncation │ ├── AbstractStrategy.php │ ├── FramesStrategy.php │ ├── MinBodyStrategy.php │ ├── RawStrategy.php │ ├── StrategyInterface.php │ ├── StringsStrategy.php │ ├── TelemetryStrategy.php │ └── Truncation.php ├── Utilities.php └── UtilitiesTrait.php └── tests ├── AgentTest.php ├── BaseRollbarTest.php ├── BodyTest.php ├── ConfigTest.php ├── ContextTest.php ├── CurlSenderTest.php ├── DataBuilderTest.php ├── DataTest.php ├── DefaultsTest.php ├── ErrorWrapperTest.php ├── ExceptionInfoTest.php ├── FakeDataBuilder.php ├── FluentTest.php ├── FrameTest.php ├── Handlers ├── ErrorHandlerTest.php └── ExceptionHandlerTest.php ├── JsHelperTest.php ├── LevelFactoryTest.php ├── MessageTest.php ├── NotifierTest.php ├── Payload ├── EncodedPayloadTest.php ├── LevelTest.php ├── TelemetryBodyTest.php └── TelemetryEventTest.php ├── PayloadTest.php ├── Performance ├── MassivePayload.php ├── TestHelpers │ ├── EncodedPayload.php │ └── Truncation.php └── TruncationTest.php ├── PersonTest.php ├── ReadmeTest.php ├── RequestTest.php ├── ResponseTest.php ├── RollbarLoggerTest.php ├── RollbarTest.php ├── ScrubberTest.php ├── ServerTest.php ├── Telemetry └── TelemeterTest.php ├── TestHelpers ├── ArrayLogger.php ├── CustomSerializable.php ├── CustomTruncation.php ├── CycleCheck │ ├── ChildCycleCheck.php │ ├── ChildCycleCheckSerializable.php │ ├── ParentCycleCheck.php │ └── ParentCycleCheckSerializable.php ├── DeprecatedSerializable.php ├── Exceptions │ ├── FiftyFiftyExceptionSampleRate.php │ ├── FiftyFityChildExceptionSampleRate.php │ ├── MidExceptionSampleRate.php │ ├── QuarterExceptionSampleRate.php │ ├── SilentExceptionSampleRate.php │ └── VerboseExceptionSampleRate.php ├── MalformedPayloadDataTransformer.php ├── MockPhpStream.php ├── StdOutLogger.php └── TestTelemetryFilter.php ├── TraceChainTest.php ├── TraceTest.php ├── Truncation ├── FramesStrategyTest.php ├── MinBodyStrategyTest.php ├── RawStrategyTest.php ├── StringsStrategyTest.php ├── TelemetryStrategyTest.php └── TruncationTest.php ├── UtilitiesTest.php ├── VerbosityTest.php └── bootstrap.php /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | vendor 3 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description of the change 2 | 3 | > Please include a summary of the change and which issues are fixed. 4 | > Please also include relevant motivation and context. 5 | 6 | ## Type of change 7 | 8 | - [ ] Bug fix (non-breaking change that fixes an issue) 9 | - [ ] New feature (non-breaking change that adds functionality) 10 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 11 | - [ ] Maintenance 12 | - [ ] New release 13 | 14 | ## Related issues 15 | 16 | > Shortcut stories and GitHub issues (delete irrelevant) 17 | 18 | - Fix [SC-] 19 | - Fix #1 20 | 21 | ## Checklists 22 | 23 | ### Development 24 | 25 | - [ ] Lint rules pass locally 26 | - [ ] The code changed/added as part of this pull request has been covered with tests 27 | - [ ] All tests related to the changed code pass in development 28 | 29 | ### Code review 30 | 31 | - [ ] This pull request has a descriptive title and information useful to a reviewer. There may be a screenshot or screencast attached 32 | - [ ] "Ready for review" label attached to the PR and reviewers assigned 33 | - [ ] Issue from task tracker has a link to this pull request 34 | - [ ] Changes have been reviewed by at least one other engineer 35 | -------------------------------------------------------------------------------- /.github/workflows/ci-1.x.yml: -------------------------------------------------------------------------------- 1 | # CI checks for Rollbar-PHP, version 1.x 2 | # 3 | # Test with act: 4 | # brew install act 5 | # act -P ubuntu-latest=shivammathur/node:latest 6 | # 7 | # @see https://github.com/nektos/act/issues/329 8 | name: CI for Rollbar-PHP, version 1.x 9 | 10 | # Fire this action on pushes to 1.x development branches (the official one, as 11 | # well as development branches within it -- hence the wildcard), and also 1.x 12 | # tags. Also, run every day at 02:42 GMT to catch failures from dependencies 13 | # that update independently. 14 | on: 15 | push: 16 | branches: 17 | - next/1.x/** 18 | tags: 19 | - v1.* 20 | pull_request: 21 | branches: 22 | - next/1.x/** 23 | schedule: 24 | - cron: '42 2 * * *' 25 | 26 | jobs: 27 | # Check that this runs on PHP on all versions we claim to support, on both 28 | # UNIX-like and Windows environments, and that use both the lowest possible 29 | # compatible version as well as the most-recent stable version. This will 30 | # fail-fast by default, so we include our edgiest versions (currently 7.4) 31 | # first as they're most likely to fail. 32 | # @see https://freek.dev/1546 33 | # @see https://www.dereuromark.de/2019/01/04/test-composer-dependencies-with-prefer-lowest/ 34 | # @see https://github.com/actions/starter-workflows/blob/main/ci/php.yml 35 | php-tests: 36 | strategy: 37 | matrix: 38 | # All the versions, OS, and dependency levels we want to support 39 | php: [5.6, 5.5] 40 | dependency: [stable] # TODO: lowest 41 | os: [ubuntu] # TODO: windows, macos 42 | 43 | name: PHP ${{ matrix.php }} on ${{ matrix.os }}, ${{ matrix.dependency }} dependencies preferred 44 | runs-on: ${{ matrix.os }}-latest 45 | steps: 46 | - name: Checkout the next/1.x/main branch during scheduled builds 47 | if: github.ref == 'refs/heads/master' 48 | uses: actions/checkout@v3 49 | with: 50 | ref: 'next/1.x/main' 51 | 52 | - name: Checkout the pushed branch 53 | if: github.ref != 'refs/heads/master' 54 | uses: actions/checkout@v3 55 | 56 | - name: Install PHP and composer environment 57 | uses: shivammathur/setup-php@v2 58 | with: 59 | php-version: ${{ matrix.php }} 60 | extensions: curl 61 | ini-values: 62 | coverage: xdebug 63 | 64 | - name: Get composer cache directory 65 | id: composer-cache 66 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 67 | 68 | - name: Cache dependencies 69 | uses: actions/cache@v3 70 | with: 71 | path: ${{ steps.composer-cache.outputs.dir }} 72 | key: ${{ matrix.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ matrix.dependency }}- 73 | restore-keys: ${{ matrix.os }}-composer-${{ matrix.dependency }}- 74 | 75 | - name: Install dependencies 76 | run: composer update --prefer-${{ matrix.dependency }} --prefer-dist --no-interaction 77 | 78 | - name: Execute tests 79 | run: composer test 80 | -------------------------------------------------------------------------------- /.github/workflows/ci-2.x.yml: -------------------------------------------------------------------------------- 1 | # CI checks for Rollbar-PHP, version 2.x 2 | # 3 | # Test with act: 4 | # brew install act 5 | # act -P ubuntu-latest=shivammathur/node:latest 6 | # 7 | # @see https://github.com/nektos/act/issues/329 8 | name: CI for Rollbar-PHP, version 2.x 9 | 10 | # Fire this action on pushes to 2.x development branches (the official one, as 11 | # well as development branches within it -- hence the wildcard), and also 2.x 12 | # tags. Also, run every day at 02:42 GMT to catch failures from dependencies 13 | # that update independently. 14 | on: 15 | push: 16 | branches: 17 | - next/2.x/** 18 | tags: 19 | - v2.* 20 | pull_request: 21 | branches: 22 | - next/2.x/** 23 | schedule: 24 | - cron: '42 2 * * *' 25 | 26 | jobs: 27 | # Check that this runs on PHP on all versions we claim to support, on both 28 | # UNIX-like and Windows environments, and that use both the lowest possible 29 | # compatible version as well as the most-recent stable version. This will 30 | # fail-fast by default, so we include our edgiest versions (currently 7.4) 31 | # first as they're most likely to fail. 32 | # @see https://freek.dev/1546 33 | # @see https://www.dereuromark.de/2019/01/04/test-composer-dependencies-with-prefer-lowest/ 34 | # @see https://github.com/actions/starter-workflows/blob/main/ci/php.yml 35 | php-tests: 36 | strategy: 37 | matrix: 38 | # All the versions, OS, and dependency levels we want to support 39 | php: [7.4, 7.3, 7.2, 7.1, 7.0] 40 | dependency: [stable] # TODO: lowest 41 | os: [ubuntu] # TODO: windows, macos 42 | # In XDebug 2 and earlier, if XDebug extension was present, the function 43 | # xdebug_get_function_stack() was unconditionally available. Now in XDebug 3 44 | # that function is present only when xdebug.mode includes "develop". Our code 45 | # has paths for with- and without- XDebug, and we want to test both of them. 46 | # However, we require XDebug for code coverage, so before XDebug 3 there was 47 | # no way to test the without-XDebug code path. Now we can, so we do. 48 | # Note: INI values with embedded commas need to have their value quoted. 49 | # @see https://xdebug.org/docs/all_settings#mode 50 | xdebug3-mode: ["xdebug.mode='develop,coverage'", "xdebug.mode=coverage"] 51 | include: 52 | # 7.4 introduced an engine wide flag that disables arguments in 53 | # backtraces. The default in 7.4 is On. However, we have tests that 54 | # rely on arguments being available, so we turn it Off. 55 | - php: 7.4 56 | ini: zend.exception_ignore_args=Off 57 | exclude: 58 | # We only have XDebug 3 in PHP > 7.3, and the ini value will be ignored in 59 | # all earlier versions. We can prune out one of them, because the result of 60 | # both runs in these earlier versions are the same (because the ini directive 61 | # is completely ignored, no matter its value). 62 | # @see https://xdebug.org/docs/compat 63 | - php: 7.2 64 | xdebug3-mode: "xdebug.mode=develop,coverage" 65 | - php: 7.1 66 | xdebug3-mode: "xdebug.mode=develop,coverage" 67 | 68 | name: PHP ${{ matrix.php }} on ${{ matrix.os }}, ${{ matrix.dependency }} dependencies preferred 69 | runs-on: ${{ matrix.os }}-latest 70 | steps: 71 | - name: Checkout the next/2.x/main branch during scheduled builds 72 | if: github.ref == 'refs/heads/master' 73 | uses: actions/checkout@v3 74 | with: 75 | ref: 'next/2.x/main' 76 | 77 | - name: Checkout the pushed branch 78 | if: github.ref != 'refs/heads/master' 79 | uses: actions/checkout@v3 80 | 81 | - name: Install PHP and composer environment 82 | uses: shivammathur/setup-php@v2 83 | with: 84 | php-version: ${{ matrix.php }} 85 | extensions: curl 86 | ini-values: ${{ matrix.ini }}, ${{ matrix.xdebug3-mode }} 87 | coverage: xdebug 88 | 89 | - name: Get composer cache directory 90 | id: composer-cache 91 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 92 | 93 | - name: Cache dependencies 94 | uses: actions/cache@v3 95 | with: 96 | path: ${{ steps.composer-cache.outputs.dir }} 97 | key: ${{ matrix.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ matrix.dependency }}- 98 | restore-keys: ${{ matrix.os }}-composer-${{ matrix.dependency }}- 99 | 100 | - name: Install dependencies 101 | run: composer update --prefer-${{ matrix.dependency }} --prefer-dist --no-interaction 102 | 103 | - name: Execute tests 104 | run: composer test 105 | -------------------------------------------------------------------------------- /.github/workflows/ci-3.x.yml: -------------------------------------------------------------------------------- 1 | # CI checks for Rollbar-PHP, master branch. 2 | # 3 | # Test with act: 4 | # brew install act 5 | # act -P ubuntu-latest=shivammathur/node:latest 6 | # 7 | # @see https://github.com/nektos/act/issues/329 8 | name: CI for Rollbar-PHP, version 3.x 9 | 10 | # Fire this action on pushes to all branches except the development branches 11 | # for older versions of PHP. Thus, all branches assume to target master (and 12 | # will be checked accordingly) unless they begin with next/. Also, run every 13 | # day at 02:42 GMT to catch failures from dependencies that update 14 | # independently. 15 | on: 16 | push: 17 | branches: 18 | - next/3.x/** 19 | tags: 20 | - v3.* 21 | pull_request: 22 | branches: 23 | - next/3.x/** 24 | schedule: 25 | # Every day at 02:42 26 | - cron: '42 2 * * *' 27 | 28 | jobs: 29 | # Check that this runs on PHP on all versions we claim to support, on both 30 | # UNIX-like and Windows environments, and that use both the lowest possible 31 | # compatible version as well as the most-recent stable version. This will 32 | # fail-fast by default, so we include our edgiest versions first as they're 33 | # most likely to fail. 34 | # @see https://freek.dev/1546 35 | # @see https://www.dereuromark.de/2019/01/04/test-composer-dependencies-with-prefer-lowest/ 36 | php-tests: 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | # All the versions, OS, and dependency levels we want to support 41 | os: [ ubuntu ] # TODO: windows, macos 42 | php: [ '8.0', '8.1', '8.2' ] 43 | dependency: [ stable ] 44 | # Our code has paths for with- and without- XDebug, and we want to test 45 | # both of them. 46 | # @see https://xdebug.org/docs/all_settings#mode 47 | xdebug3-mode: ['develop,coverage', 'coverage'] 48 | include: 49 | - php: '8.0' 50 | os: 'ubuntu' 51 | dependency: 'lowest' 52 | xdebug3-mode: 'develop,coverage' 53 | 54 | name: PHP ${{ matrix.php }} on ${{ matrix.os }}, ${{ matrix.dependency }} dependencies preferred, ${{ matrix.xdebug3-mode }} 55 | runs-on: ${{ matrix.os }}-latest 56 | steps: 57 | - name: Checkout the next/3.x/main branch during scheduled builds 58 | if: github.ref == 'refs/heads/master' 59 | uses: actions/checkout@v3 60 | with: 61 | ref: 'next/3.x/main' 62 | 63 | - name: Checkout the pushed branch 64 | if: github.ref != 'refs/heads/master' 65 | uses: actions/checkout@v3 66 | 67 | - name: Install PHP and composer environment 68 | uses: shivammathur/setup-php@v2 69 | with: 70 | php-version: ${{ matrix.php }} 71 | extensions: curl 72 | ini-values: zend.exception_ignore_args=Off, xdebug3-mode="${{ matrix.xdebug3-mode }}" 73 | coverage: xdebug 74 | 75 | - name: Get composer cache directory 76 | id: composer-cache 77 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 78 | 79 | - name: Cache dependencies 80 | uses: actions/cache@v3 81 | with: 82 | path: ${{ steps.composer-cache.outputs.dir }} 83 | key: ${{ matrix.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ matrix.dependency }}- 84 | restore-keys: ${{ matrix.os }}-composer-${{ matrix.dependency }}- 85 | 86 | - name: Install dependencies 87 | run: composer update --prefer-${{ matrix.dependency }} --prefer-dist --no-interaction 88 | 89 | - name: Execute tests 90 | run: composer test 91 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # CI checks for Rollbar-PHP, master branch. 2 | # 3 | # Test with act: 4 | # brew install act 5 | # act -P ubuntu-latest=shivammathur/node:latest 6 | # 7 | # @see https://github.com/nektos/act/issues/329 8 | name: CI for Rollbar-PHP, master 9 | 10 | # Fire this action on pushes to all branches except the development branches 11 | # for older versions of PHP. Thus, all branches assume to target master (and 12 | # will be checked accordingly) unless they begin with next/. Also, run every 13 | # day at 02:42 GMT to catch failures from dependencies that update 14 | # independently. 15 | on: 16 | push: 17 | branches-ignore: 18 | - next/** 19 | pull_request: 20 | branches-ignore: 21 | - next/** 22 | schedule: 23 | - cron: '42 2 * * *' 24 | 25 | jobs: 26 | # Check that this runs on PHP on all versions we claim to support, on both 27 | # UNIX-like and Windows environments, and that use both the lowest possible 28 | # compatible version as well as the most-recent stable version. This will 29 | # fail-fast by default, so we include our edgiest versions (currently 7.4) 30 | # first as they're most likely to fail. 31 | # @see https://freek.dev/1546 32 | # @see https://www.dereuromark.de/2019/01/04/test-composer-dependencies-with-prefer-lowest/ 33 | php-tests: 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | # All the versions, OS, and dependency levels we want to support 38 | os: [ubuntu] # TODO: windows, macos 39 | php: [ '8.1', '8.2', '8.3', '8.4' ] 40 | dependency: [ stable ] 41 | # Our code has paths for with- and without- XDebug, and we want to test 42 | # both of them. 43 | # @see https://xdebug.org/docs/all_settings#mode 44 | xdebug3-mode: ['develop,coverage', 'coverage'] 45 | include: 46 | - php: '8.1' 47 | os: 'ubuntu' 48 | dependency: 'lowest' 49 | xdebug3-mode: 'develop,coverage' 50 | 51 | name: PHP ${{ matrix.php }} on ${{ matrix.os }}, ${{ matrix.dependency }} dependencies preferred, ${{ matrix.xdebug3-mode }} 52 | runs-on: ${{ matrix.os }}-latest 53 | steps: 54 | - name: Checkout the code 55 | uses: actions/checkout@v3 56 | 57 | - name: Install PHP and composer environment 58 | uses: shivammathur/setup-php@v2 59 | with: 60 | php-version: ${{ matrix.php }} 61 | extensions: curl 62 | ini-values: zend.exception_ignore_args=Off, xdebug3-mode="${{ matrix.xdebug3-mode }}" 63 | coverage: xdebug 64 | 65 | - name: Get composer cache directory 66 | id: composer-cache 67 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 68 | 69 | - name: Cache dependencies 70 | uses: actions/cache@v3 71 | with: 72 | path: ${{ steps.composer-cache.outputs.dir }} 73 | key: ${{ matrix.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ matrix.dependency }}- 74 | restore-keys: ${{ matrix.os }}-composer-${{ matrix.dependency }}- 75 | 76 | - name: Install dependencies PHP < 8.4 77 | if: matrix.php != '8.4' 78 | run: composer update --prefer-${{ matrix.dependency }} --prefer-dist --no-interaction 79 | 80 | - name: Execute tests PHP < 8.4 81 | if: matrix.php != '8.4' 82 | run: composer test 83 | 84 | # This is a temporary workaround until vimeo/psalm is updated to support PHP 8.4. 85 | # See https://github.com/vimeo/psalm/issues/11107 86 | - name: Install dependencies PHP 8.4 87 | if: matrix.php == '8.4' 88 | run: composer update --prefer-${{ matrix.dependency }} --prefer-dist --no-interaction --ignore-platform-reqs 89 | 90 | - name: Execute tests PHP 8.4 91 | if: matrix.php == '8.4' 92 | run: | 93 | ./vendor/bin/phpcs --standard=PSR2 ./src ./tests 94 | ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | build 4 | .phpunit.result.cache 5 | .phpunit.cache 6 | 7 | # Files editors may leave around that aren't related to any kind of 8 | # build artifacts 9 | *.swp 10 | *.swo 11 | .idea 12 | .vscode 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM shivammathur/node:focal 2 | 3 | VOLUME [ "/opt/rollbar/rollbar-php" ] 4 | WORKDIR /opt/rollbar/rollbar-php 5 | ENTRYPOINT [ "/bin/bash" ] 6 | 7 | RUN apt-get update \ 8 | && apt-get install -y ca-certificates git vim tree 9 | 10 | RUN spc --php-version "8.0" --extensions "curl" --coverage "xdebug" 11 | -------------------------------------------------------------------------------- /EXAMPLES.md: -------------------------------------------------------------------------------- 1 | A range of examples of using Rollbar PHP is available here: 2 | [Rollbar PHP Examples](https://github.com/rollbar/rollbar-php-examples). 3 | 4 | A Wordpress Plugin is available through Wordpress Admin Panel or through Wordpress 5 | Plugin directory: [Rollbar Wordpress](https://wordpress.org/plugins/rollbar/) 6 | 7 | A Laravel-specific package is available for integrating with Laravel: 8 | [Rollbar Laravel](https://github.com/rollbar/rollbar-php-laravel) 9 | 10 | A CakePHP-specific package is avaliable for integrating with CakePHP 2.x: 11 | [CakeRollbar](https://github.com/tranfuga25s/CakeRollbar) 12 | 13 | A Flow-specific package is available for integrating with Neos Flow: 14 | [m12/flow-rollbar](https://packagist.org/packages/m12/flow-rollbar) 15 | 16 | Yii package: [baibaratsky/yii-rollbar](https://github.com/baibaratsky/yii-rollbar) 17 | 18 | Yii2 package: [baibaratsky/yii2-rollbar](https://github.com/baibaratsky/yii2-rollbar) 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Rollbar, Inc. 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | rollbar-logo 3 |

4 | 5 |

Rollbar PHP SDK

6 | 7 |

8 | Proactively discover, predict, and resolve errors in real-time with Rollbar’s error monitoring platform. Start tracking errors today! 9 |

10 | 11 | 12 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/rollbar/rollbar.svg?style=flat-square)](https://packagist.org/packages/rollbar/rollbar) 13 | [![CI for Rollbar-PHP, master](https://github.com/rollbar/rollbar-php/actions/workflows/ci.yml/badge.svg)](https://github.com/rollbar/rollbar-php/actions/workflows/ci.yml) 14 | [![Total Downloads](https://img.shields.io/packagist/dt/rollbar/rollbar.svg?style=flat-square)](https://packagist.org/packages/rollbar/rollbar) 15 | 16 | --- 17 | 18 | ## Key benefits of using Rollbar PHP SDK are: 19 | - **Frameworks:** Rollbar php supports many popular php frameworks such as Laravel, CodeIgniter, Symfony and many more! 20 | - **Plugins:** Rollbar php has plugin support for Heroku, Wordpress, Rollbar.js and more. 21 | - **Automatic error grouping:** Rollbar aggregates Occurrences caused by the same error into Items that represent application issues. Learn more about reducing log noise. 22 | - **Advanced search:** Filter items by many different properties. Learn more about search. 23 | - **Customizable notifications:** Rollbar supports several messaging and incident management tools where your team can get notified about errors and important events by real-time alerts. Learn more about Rollbar notifications. 24 | 25 | 26 | 27 | # Quickstart 28 | 29 | If you've never used Rollbar before, [sign up for a Rollbar account][signup] 30 | and follow the simple, three-step tour. In no time, you'll be capturing errors 31 | and exceptions thrown in your code. 32 | 33 | If you already have a Rollbar account, [log in to your Rollbar account][login]. 34 | From the Settings > Project Access Token menu, click Create New Access Token. 35 | Copy the `post_client_item` value and paste it into the code below. 36 | 37 | ```php 38 | require 'vendor/autoload.php'; // composer require rollbar/rollbar 39 | 40 | \Rollbar\Rollbar::init([ 41 | 'access_token' => '***', 42 | 'environment' => 'development', 43 | ]); 44 | ``` 45 | 46 | For detailed usage instructions and configuration reference, refer to our 47 | [PHP SDK docs][sdkdoc]. 48 | 49 | [login]: https://rollbar.com/login/ 50 | [sdkdoc]:https://docs.rollbar.com/docs/php 51 | [signup]: https://rollbar.com/signup 52 | 53 | # Getting Help 54 | 55 | * If you have a question, ask in our [Discussion Q&A][q-a] 56 | * To report a bug, raise [an issue][issue] 57 | * For account service, reach out to [support@rollbar.com][support] 58 | 59 | [issue]:https://github.com/rollbar/rollbar-php/issues 60 | [q-a]:https://github.com/rollbar/rollbar-php/discussions/categories/q-a 61 | [support]:mailto:support@rollbar.com 62 | 63 | # Releases, Versions, and PHP Compatibility 64 | 65 | Major releases of this library support major versions of PHP, as follows: 66 | 67 | * For PHP 8, choose the `4.x` or `3.x` branch. 68 | * For PHP 7, choose a `2.x` release. 69 | * For PHP 5, choose a `1.x` release. 70 | 71 | To obtain a release, download an archive from the [Releases] page or use 72 | composer: 73 | 74 | ```sh 75 | # for PHP 8 compatibility 76 | $ composer require rollbar/rollbar:^4 77 | # or 78 | $ composer require rollbar/rollbar:^3 79 | 80 | # for PHP 7 compatibility 81 | $ composer require rollbar/rollbar:^2 82 | 83 | # for PHP 5 compatibility 84 | $ composer require rollbar/rollbar:^1 85 | ``` 86 | 87 | Refer to [CHANGELOG.md] for a complete history. 88 | 89 | [CHANGELOG.md]: ./CHANGELOG.md 90 | [Releases]: https://github.com/rollbar/rollbar-php/releases 91 | 92 | # License 93 | Rollbar-PHP is free software released under the MIT License. See [LICENSE] 94 | for details. 95 | 96 | [LICENSE]: ./LICENSE 97 | -------------------------------------------------------------------------------- /THANKS.md: -------------------------------------------------------------------------------- 1 | Many thanks to the following contributors, by github username and in alphabetical order. For the most recent list, see https://github.com/rollbar/rollbar-php/graphs/contributors 2 | 3 | - [AlexeyMorozov](https://github.com/AlexeyMorozov) 4 | - [alexthegeek](https://github.com/alexthegeek) 5 | - [benjamin-smith](https://github.com/benjamin-smith) 6 | - [chanind](https://github.com/chanind) 7 | - [digilist](https://github.com/digilist) 8 | - [dimkalinux](https://github.com/dimkalinux) 9 | - [diogopms](https://github.com/diogopms) 10 | - [elfif](https://github.com/elfif) 11 | - [ghostal](https://github.com/ghostal) 12 | - [kidk](https://github.com/kidk) 13 | - [lindyhopchris](https://github.com/lindyhopchris) 14 | - [MaffooBristol](https://github.com/MaffooBristol) 15 | - [plusbryan](https://github.com/plusbryan) 16 | - [rekky](https://github.com/rekky) 17 | - [rfink](https://github.com/rfink) 18 | - [steveh](https://github.com/steveh) 19 | - [stof](https://github.com/stof) 20 | - [unix1](https://github.com/unix1) 21 | - [veloper](https://github.com/veloper) 22 | - [vilius-g](https://github.com/vilius-g) 23 | - [violuke](https://github.com/violuke) 24 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 1. Include Code Context 2 | 1. Implement Agent Sender 3 | 1. Get and sanitize function arguments from backtrace: 4 | * You can get argument names like so: http://stackoverflow.com/a/2692514/456188 5 | * You can use `array_combine` to get a kwargs version of the arguments 6 | * You can then sanitize based on argument name -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollbar/rollbar", 3 | "description": "Monitors errors and exceptions and reports them to Rollbar", 4 | "type": "library", 5 | "keywords": ["logging", "debugging", "monitoring", "errors", "exceptions"], 6 | "license": "MIT", 7 | "homepage": "https://github.com/rollbar/rollbar-php", 8 | 9 | "authors": [ 10 | { 11 | "name": "Rollbar, Inc.", 12 | "email": "support@rollbar.com", 13 | "role": "Developer" 14 | } 15 | ], 16 | 17 | "support": { 18 | "email": "support@rollbar.com" 19 | }, 20 | 21 | "autoload": { 22 | "psr-4": { 23 | "Rollbar\\": "src/" 24 | } 25 | }, 26 | 27 | "autoload-dev": { 28 | "psr-4": { 29 | "Rollbar\\": "tests/" 30 | } 31 | }, 32 | 33 | "require": { 34 | "php": ">=8.1.0 <9.0", 35 | "ext-curl": "*", 36 | "psr/log": "^1 || ^2 || ^3", 37 | "monolog/monolog": "^2 || ^3" 38 | }, 39 | 40 | "require-dev": { 41 | "phpunit/phpunit": "^9.6 || ^10.1", 42 | "mockery/mockery": "^1.5.1", 43 | "squizlabs/php_codesniffer": "^3.7", 44 | "phpmd/phpmd" : "^2.13", 45 | "vimeo/psalm": "^5.9" 46 | }, 47 | 48 | "suggest": { 49 | "fluent/logger": "Needed to use the 'fluent' handler for fluentd support" 50 | }, 51 | 52 | "scripts": { 53 | "docker-build": "docker build -t rollbar/rollbar-php:3 .", 54 | "docker-run": "docker run -it -v \"${PWD}\":/opt/rollbar/rollbar-php rollbar/rollbar-php:3", 55 | "test": [ 56 | "phpcs --standard=PSR2 src tests", 57 | "psalm --long-progress", 58 | "phpunit --coverage-clover build/logs/clover.xml" 59 | ], 60 | "fix": "phpcbf --standard=PSR2 src tests", 61 | "get-js-snippet": "ROLLBAR_JS_TAG=$(curl -s https://api.github.com/repos/rollbar/rollbar.js/releases/latest | sed -n 's/\"tag_name\":.*\"\\(.*\\)\",/\\1/p' | sed 's/ *//'); curl -X GET https://raw.githubusercontent.com/rollbar/rollbar.js/$ROLLBAR_JS_TAG/dist/rollbar.snippet.js > data/rollbar.snippet.js", 62 | "performance": "phpunit --coverage-clover build/logs/clover.xml --testsuite 'Rollbar Performance Test Suite'" 63 | }, 64 | 65 | "config": { 66 | "process-timeout": 600 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | ./tests/ 11 | ./tests/Performance/ 12 | ./tests/TestHelpers/ 13 | ./tests/FakeDataBuilder.php 14 | ./tests/bootstrap.php 15 | ./tests/BaseRollbarTest.php 16 | 17 | 18 | 19 | ./tests/Performance/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | src 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /psalm.baseline: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $result 6 | 7 | 8 | 9 | 10 | $toLog 11 | 12 | 13 | 14 | 15 | FluentLogger 16 | FluentLogger 17 | 18 | 19 | $this->fluentLogger 20 | FluentLogger 21 | private $fluentLogger = null; 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/DataBuilderInterface.php: -------------------------------------------------------------------------------- 1 | "E_ERROR", 16 | E_WARNING => "E_WARNING", 17 | E_PARSE => "E_PARSE", 18 | E_NOTICE => "E_NOTICE", 19 | E_CORE_ERROR => "E_CORE_ERROR", 20 | E_CORE_WARNING => "E_CORE_WARNING", 21 | E_COMPILE_ERROR => "E_COMPILE_ERROR", 22 | E_COMPILE_WARNING => "E_COMPILE_WARNING", 23 | E_USER_ERROR => "E_USER_ERROR", 24 | E_USER_WARNING => "E_USER_WARNING", 25 | E_USER_NOTICE => "E_USER_NOTICE", 26 | E_RECOVERABLE_ERROR => "E_RECOVERABLE_ERROR", 27 | E_DEPRECATED => "E_DEPRECATED", 28 | E_USER_DEPRECATED => "E_USER_DEPRECATED" 29 | ); 30 | } 31 | return self::$constName[$const] ?? null; 32 | } 33 | 34 | /** 35 | * Creates the instance from the error data. 36 | * 37 | * @param int $errorLevel The level of the error raised. 38 | * @param string $errorMessage The error message. 39 | * @param string|null $errorFile The filename that the error was raised in. 40 | * @param int|null $errorLine The line number where the error was raised. 41 | * @param array|null $backTrace The stack trace for the error. 42 | * @param Utilities $utilities The configured utilities class. 43 | */ 44 | public function __construct( 45 | public int $errorLevel, 46 | public string $errorMessage, 47 | public ?string $errorFile, 48 | public ?int $errorLine, 49 | public ?array $backTrace, 50 | $utilities 51 | ) { 52 | parent::__construct($this->errorMessage, $this->errorLevel); 53 | $this->utilities = $utilities; 54 | } 55 | 56 | public function getBacktrace() 57 | { 58 | return $this->backTrace; 59 | } 60 | 61 | public function getClassName() 62 | { 63 | $constName = self::getConstName($this->errorLevel) ?: "#$this->errorLevel"; 64 | return "$constName"; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/FilterInterface.php: -------------------------------------------------------------------------------- 1 | logger; 23 | } 24 | 25 | public function registered() 26 | { 27 | return $this->registered; 28 | } 29 | 30 | public function handle(...$args) 31 | { 32 | if (!$this->registered()) { 33 | throw new \Exception(get_class($this) . ' has not been set up.'); 34 | } 35 | } 36 | 37 | public function register() 38 | { 39 | $this->registered = true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Handlers/ErrorHandler.php: -------------------------------------------------------------------------------- 1 | previousHandler = set_error_handler(array($this, 'handle')); 14 | 15 | parent::register(); 16 | } 17 | 18 | public function handle(...$args) 19 | { 20 | parent::handle(...$args); 21 | 22 | if (count($args) < 2) { 23 | throw new \Exception('No $errno or $errstr to be passed to the error handler.'); 24 | } 25 | 26 | $errno = $args[0]; 27 | $errstr = $args[1]; 28 | $errfile = $args[2] ?: null; 29 | $errline = $args[3] ?: null; 30 | 31 | if ($this->previousHandler) { 32 | $stop_processing = ($this->previousHandler)($errno, $errstr, $errfile, $errline); 33 | if ($stop_processing) { 34 | return $stop_processing; 35 | } 36 | } 37 | 38 | if ($this->logger()->shouldIgnoreError($errno)) { 39 | return false; 40 | } 41 | 42 | $exception = $this->logger()-> 43 | getDataBuilder()-> 44 | generateErrorWrapper($errno, $errstr, $errfile, $errline); 45 | $this->logger()->report(Level::ERROR, $exception, isUncaught: true); 46 | 47 | return false; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Handlers/ExceptionHandler.php: -------------------------------------------------------------------------------- 1 | previousHandler = set_exception_handler(array($this, 'handle')); 14 | 15 | parent::register(); 16 | } 17 | 18 | public function handle(...$args) 19 | { 20 | parent::handle(...$args); 21 | 22 | if (count($args) < 1) { 23 | throw new \Exception('No exception to be passed to the exception handler.'); 24 | } 25 | 26 | $exception = $args[0]; 27 | $this->logger()->report(Level::ERROR, $exception, isUncaught: true); 28 | 29 | // if there was no prior handler, then we toss that exception 30 | if ($this->previousHandler === null) { 31 | throw $exception; 32 | } 33 | 34 | // otherwise we overrode a previous handler, so restore it and call it 35 | restore_exception_handler(); 36 | ($this->previousHandler)($exception); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Handlers/FatalHandler.php: -------------------------------------------------------------------------------- 1 | isFatal($lastError)) { 33 | $errno = $lastError['type']; 34 | $errstr = $lastError['message']; 35 | $errfile = $lastError['file']; 36 | $errline = $lastError['line']; 37 | 38 | $exception = $this->logger()-> 39 | getDataBuilder()-> 40 | generateErrorWrapper($errno, $errstr, $errfile, $errline); 41 | $this->logger()->report(Level::CRITICAL, $exception, isUncaught: true); 42 | } 43 | } 44 | 45 | /** 46 | * Check if the error triggered is indeed a fatal error. 47 | * 48 | * @var array $lastError Information fetched from error_get_last(). 49 | * 50 | * @return bool 51 | */ 52 | protected function isFatal($lastError) 53 | { 54 | return 55 | null !== $lastError && 56 | in_array($lastError['type'], self::$fatalErrors, true) && 57 | // don't log uncaught exceptions as they were handled by exceptionHandler() 58 | !(isset($lastError['message']) && 59 | str_starts_with($lastError['message'], 'Uncaught')); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/LevelFactory.php: -------------------------------------------------------------------------------- 1 | Level]. 11 | * 12 | * @var array|null 13 | */ 14 | private static ?array $levels = null; 15 | 16 | /** 17 | * Returns the array of levels as [string => Level]. 18 | * 19 | * @return array 20 | */ 21 | private static function getLevels(): array 22 | { 23 | if (null === self::$levels) { 24 | self::$levels = array( 25 | Level::EMERGENCY => new Level("critical", 100000), 26 | Level::ALERT => new Level("critical", 100000), 27 | Level::CRITICAL => new Level("critical", 100000), 28 | Level::ERROR => new Level("error", 10000), 29 | Level::WARNING => new Level("warning", 1000), 30 | Level::NOTICE => new Level("info", 100), 31 | Level::INFO => new Level("info", 100), 32 | Level::DEBUG => new Level("debug", 10), 33 | ); 34 | } 35 | 36 | return self::$levels; 37 | } 38 | 39 | /** 40 | * Returns the {@see Level} instance for a given log level. If the log level 41 | * is invalid null will be returned. 42 | * 43 | * @param string $name level name 44 | * 45 | * @return Level|null 46 | */ 47 | public static function fromName(string $name): ?Level 48 | { 49 | $name = strtolower($name); 50 | return self::getLevels()[$name] ?? null; 51 | } 52 | 53 | /** 54 | * Check if the provided level is a valid level. 55 | * 56 | * @param string $level 57 | * 58 | * @return bool 59 | */ 60 | public static function isValidLevel(string $level): bool 61 | { 62 | return self::fromName($level) !== null; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Payload/Body.php: -------------------------------------------------------------------------------- 1 | value; 38 | } 39 | 40 | /** 41 | * Sets the main content of the payload body. 42 | * 43 | * @param ContentInterface $value The value to assign to the content of the payload body. 44 | * 45 | * @return self 46 | */ 47 | public function setValue(ContentInterface $value): self 48 | { 49 | $this->value = $value; 50 | return $this; 51 | } 52 | 53 | /** 54 | * Sets the array of extra data. 55 | * 56 | * @param array $extra The array of extra data. 57 | * 58 | * @return self 59 | */ 60 | public function setExtra(array $extra): self 61 | { 62 | $this->extra = $extra; 63 | return $this; 64 | } 65 | 66 | /** 67 | * Returns the array of extra data. 68 | * 69 | * @return array 70 | */ 71 | public function getExtra(): array 72 | { 73 | return $this->extra; 74 | } 75 | 76 | /** 77 | * Returns the array of telemetry events or null if there were none. 78 | * 79 | * @return TelemetryEvent[]|null 80 | * 81 | * @since 4.1.0 82 | */ 83 | public function getTelemetry(): ?array 84 | { 85 | if (empty($this->telemetry)) { 86 | return null; 87 | } 88 | return $this->telemetry; 89 | } 90 | 91 | /** 92 | * Sets the list of telemetry events for this payload body. 93 | * 94 | * @param array|null $telemetry The list of telemetry events or null if there were none. 95 | * 96 | * @return void 97 | * 98 | * @since 4.1.0 99 | */ 100 | public function setTelemetry(?array $telemetry): void 101 | { 102 | $this->telemetry = $telemetry; 103 | } 104 | 105 | /** 106 | * Returns the JSON serializable representation of the payload body. 107 | * 108 | * @return array 109 | * 110 | * @since 4.1.0 Includes the 'telemetry' key, if it is not empty. 111 | */ 112 | public function serialize() 113 | { 114 | $result = array(); 115 | $result[$this->value->getKey()] = $this->value; 116 | 117 | if (!empty($this->extra)) { 118 | $result['extra'] = $this->extra; 119 | } 120 | 121 | if (!empty($this->telemetry)) { 122 | $result['telemetry'] = $this->telemetry; 123 | } 124 | 125 | return $this->utilities()->serializeForRollbarInternal($result, array('extra')); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Payload/ContentInterface.php: -------------------------------------------------------------------------------- 1 | pre; 19 | } 20 | 21 | public function setPre(array $pre): self 22 | { 23 | $this->pre = $pre; 24 | return $this; 25 | } 26 | 27 | public function getPost(): ?array 28 | { 29 | return $this->post; 30 | } 31 | 32 | public function setPost(array $post): self 33 | { 34 | $this->post = $post; 35 | return $this; 36 | } 37 | 38 | public function serialize() 39 | { 40 | $result = array( 41 | "pre" => $this->pre, 42 | "post" => $this->post, 43 | ); 44 | 45 | return $this->utilities()->serializeForRollbarInternal($result); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Payload/EncodedPayload.php: -------------------------------------------------------------------------------- 1 | data; 42 | } 43 | 44 | /** 45 | * Returns the cached size of the encoded data. 46 | * 47 | * @return int 48 | */ 49 | public function size(): int 50 | { 51 | return $this->size; 52 | } 53 | 54 | /** 55 | * Reduces the cached length of the encoded data by the $amount specified. 56 | * 57 | * Note: this does not reduce size of the data, only the cached size. To reduce the size of the payload data the 58 | * payload must be truncated. See {@see Truncation} for more details. 59 | * 60 | * @param int $amount The amount to decrease the cached data length. 61 | * 62 | * @return void 63 | */ 64 | public function decreaseSize(int $amount): void 65 | { 66 | $this->size -= $amount; 67 | } 68 | 69 | /** 70 | * Updates the payload data and JSON serializes it. The serialized data string is cached and can be access via the 71 | * {@see encoded()} method. 72 | * 73 | * @param array|null $data If an array is given it will overwrite any existing payload data prior to serialization. 74 | * If null (the default value) is given the existing payload data will be serialized. 75 | * 76 | * @return void 77 | * @throws Exception If JSON serialization fails. 78 | */ 79 | public function encode(?array $data = null): void 80 | { 81 | if ($data !== null) { 82 | $this->data = $data; 83 | } 84 | 85 | $this->encoded = json_encode( 86 | $this->data, 87 | defined('JSON_PARTIAL_OUTPUT_ON_ERROR') ? JSON_PARTIAL_OUTPUT_ON_ERROR : 0 88 | ); 89 | 90 | if ($this->encoded === false) { 91 | throw new Exception("Payload data could not be encoded to JSON format."); 92 | } 93 | 94 | $this->size = strlen($this->encoded); 95 | } 96 | 97 | /** 98 | * Returns the encoded JSON. 99 | * 100 | * @return string 101 | */ 102 | public function __toString(): string 103 | { 104 | return (string)$this->encoded(); 105 | } 106 | 107 | /** 108 | * Returns the encoded JSON or null if the data to encode was null. 109 | * 110 | * @return string|null 111 | */ 112 | public function encoded(): ?string 113 | { 114 | return $this->encoded; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Payload/ExceptionInfo.php: -------------------------------------------------------------------------------- 1 | class; 22 | } 23 | 24 | public function setClass(string $class): self 25 | { 26 | $this->class = $class; 27 | return $this; 28 | } 29 | 30 | public function getMessage(): string 31 | { 32 | return $this->message; 33 | } 34 | 35 | public function setMessage(string $message): self 36 | { 37 | $this->message = $message; 38 | return $this; 39 | } 40 | 41 | public function getDescription(): ?string 42 | { 43 | return $this->description; 44 | } 45 | 46 | public function setDescription(?string $description): self 47 | { 48 | $this->description = $description; 49 | return $this; 50 | } 51 | 52 | public function serialize() 53 | { 54 | $result = array( 55 | "class" => $this->class, 56 | "message" => $this->message, 57 | "description" => $this->description, 58 | ); 59 | 60 | return $this->utilities()->serializeForRollbarInternal($result); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Payload/Frame.php: -------------------------------------------------------------------------------- 1 | filename; 31 | } 32 | 33 | public function setFilename(?string $filename): self 34 | { 35 | $this->filename = $filename; 36 | return $this; 37 | } 38 | 39 | public function getLineno(): ?int 40 | { 41 | return $this->lineno; 42 | } 43 | 44 | public function setLineno(?int $lineno): self 45 | { 46 | $this->lineno = $lineno; 47 | return $this; 48 | } 49 | 50 | public function getColno(): ?int 51 | { 52 | return $this->colno; 53 | } 54 | 55 | public function setColno(?int $colno): self 56 | { 57 | $this->colno = $colno; 58 | return $this; 59 | } 60 | 61 | public function getMethod(): ?string 62 | { 63 | return $this->method; 64 | } 65 | 66 | public function setMethod(?string $method): self 67 | { 68 | $this->method = $method; 69 | return $this; 70 | } 71 | 72 | public function getCode(): ?string 73 | { 74 | return $this->code; 75 | } 76 | 77 | public function setCode(?string $code): self 78 | { 79 | $this->code = $code; 80 | return $this; 81 | } 82 | 83 | public function getContext(): ?Context 84 | { 85 | return $this->context; 86 | } 87 | 88 | public function setContext(Context $context): self 89 | { 90 | $this->context = $context; 91 | return $this; 92 | } 93 | 94 | public function getArgs(): ?array 95 | { 96 | return $this->args; 97 | } 98 | 99 | public function setArgs(array $args): self 100 | { 101 | $this->args = $args; 102 | return $this; 103 | } 104 | 105 | public function serialize() 106 | { 107 | $result = array( 108 | "filename" => $this->filename, 109 | "lineno" => $this->lineno, 110 | "colno" => $this->colno, 111 | "method" => $this->method, 112 | "code" => $this->code, 113 | "context" => $this->context, 114 | "args" => $this->args 115 | ); 116 | 117 | return $this->utilities()->serializeForRollbarInternal($result); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Payload/Level.php: -------------------------------------------------------------------------------- 1 | level; 43 | } 44 | 45 | /** 46 | * Returns the Rollbar service numeric error level. 47 | * 48 | * @return int 49 | */ 50 | public function toInt(): int 51 | { 52 | return $this->val; 53 | } 54 | 55 | /** 56 | * Returns the serialized Rollbar service level. 57 | * 58 | * @return string 59 | */ 60 | public function serialize() 61 | { 62 | return $this->level; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Payload/Message.php: -------------------------------------------------------------------------------- 1 | body; 25 | } 26 | 27 | public function setBody(string $body): self 28 | { 29 | $this->body = $body; 30 | return $this; 31 | } 32 | 33 | public function getBacktrace(): ?array 34 | { 35 | return $this->backtrace; 36 | } 37 | 38 | public function setBacktrace(?array $backtrace): self 39 | { 40 | $this->backtrace = $backtrace; 41 | return $this; 42 | } 43 | 44 | public function serialize() 45 | { 46 | $toSerialize = array( 47 | "body" => $this->getBody(), 48 | "backtrace" => $this->getBacktrace() 49 | ); 50 | return $this->utilities()->serializeForRollbar($toSerialize); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Payload/Notifier.php: -------------------------------------------------------------------------------- 1 | name; 27 | } 28 | 29 | public function setName(string $name): self 30 | { 31 | $this->name = $name; 32 | return $this; 33 | } 34 | 35 | public function getVersion(): string 36 | { 37 | return $this->version; 38 | } 39 | 40 | public function setVersion(string $version): self 41 | { 42 | $this->version = $version; 43 | return $this; 44 | } 45 | 46 | public function serialize() 47 | { 48 | $result = array( 49 | "name" => $this->name, 50 | "version" => $this->version, 51 | ); 52 | 53 | return $this->utilities()->serializeForRollbarInternal($result); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Payload/Payload.php: -------------------------------------------------------------------------------- 1 | data; 24 | } 25 | 26 | public function setData(Data $data): self 27 | { 28 | $this->data = $data; 29 | return $this; 30 | } 31 | 32 | public function getAccessToken(): string 33 | { 34 | return $this->accessToken; 35 | } 36 | 37 | public function setAccessToken(string $accessToken): self 38 | { 39 | $this->accessToken = $accessToken; 40 | return $this; 41 | } 42 | 43 | public function serialize($maxDepth = -1): array 44 | { 45 | $objectHashes = array(); 46 | $result = array( 47 | "data" => $this->data, 48 | "access_token" => $this->accessToken, 49 | ); 50 | 51 | return $this->utilities()->serializeForRollbar($result, null, $objectHashes, $maxDepth); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Payload/Person.php: -------------------------------------------------------------------------------- 1 | $extra 22 | */ 23 | private array $extra; 24 | 25 | public function __construct( 26 | private string $id, 27 | private ?string $username = null, 28 | private ?string $email = null, 29 | array $extra = [], 30 | ) { 31 | unset($extra['id'], $extra['email'], $extra['username']); 32 | $this->extra = $extra; 33 | } 34 | 35 | public function getId(): string 36 | { 37 | return $this->id; 38 | } 39 | 40 | public function setId(string $id): self 41 | { 42 | $this->id = $id; 43 | return $this; 44 | } 45 | 46 | public function getUsername(): ?string 47 | { 48 | return $this->username; 49 | } 50 | 51 | public function setUsername(?string $username): self 52 | { 53 | $this->username = $username; 54 | return $this; 55 | } 56 | 57 | public function getEmail(): ?string 58 | { 59 | return $this->email; 60 | } 61 | 62 | public function setEmail(?string $email): self 63 | { 64 | $this->email = $email; 65 | return $this; 66 | } 67 | 68 | public function __get($name) 69 | { 70 | return $this->extra[$name] ?? null; 71 | } 72 | 73 | public function __set($name, $val) 74 | { 75 | $this->extra[$name] = $val; 76 | } 77 | 78 | public function serialize() 79 | { 80 | $result = array( 81 | "id" => $this->id, 82 | "username" => $this->username, 83 | "email" => $this->email, 84 | ); 85 | foreach ($this->extra as $key => $val) { 86 | $result[$key] = $val; 87 | } 88 | 89 | return $this->utilities()->serializeForRollbarInternal($result, array_keys($this->extra)); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Payload/Request.php: -------------------------------------------------------------------------------- 1 | url; 30 | } 31 | 32 | public function setUrl(?string $url): self 33 | { 34 | $this->url = $url; 35 | return $this; 36 | } 37 | 38 | public function getMethod(): ?string 39 | { 40 | return $this->method; 41 | } 42 | 43 | public function setMethod(?string $method): self 44 | { 45 | $this->method = $method; 46 | return $this; 47 | } 48 | 49 | public function getHeaders(): ?array 50 | { 51 | return $this->headers; 52 | } 53 | 54 | public function setHeaders(?array $headers = null): self 55 | { 56 | $this->headers = $headers; 57 | return $this; 58 | } 59 | 60 | public function getParams(): ?array 61 | { 62 | return $this->params; 63 | } 64 | 65 | public function setParams(?array $params = null): self 66 | { 67 | $this->params = $params; 68 | return $this; 69 | } 70 | 71 | public function getGet(): ?array 72 | { 73 | return $this->get; 74 | } 75 | 76 | public function setGet(?array $get = null): self 77 | { 78 | $this->get = $get; 79 | return $this; 80 | } 81 | 82 | public function getQueryString(): ?string 83 | { 84 | return $this->queryString; 85 | } 86 | 87 | public function setQueryString(?string $queryString): self 88 | { 89 | $this->queryString = $queryString; 90 | return $this; 91 | } 92 | 93 | public function getPost(): ?array 94 | { 95 | return $this->post; 96 | } 97 | 98 | public function setPost(?array $post = null): self 99 | { 100 | $this->post = $post; 101 | return $this; 102 | } 103 | 104 | public function getBody(): ?string 105 | { 106 | return $this->body; 107 | } 108 | 109 | public function setBody(?string $body): self 110 | { 111 | $this->body = $body; 112 | return $this; 113 | } 114 | 115 | public function getUserIp(): ?string 116 | { 117 | return $this->userIp; 118 | } 119 | 120 | public function setUserIp(?string $userIp): self 121 | { 122 | $this->userIp = $userIp; 123 | return $this; 124 | } 125 | 126 | public function getExtras(): array 127 | { 128 | return $this->extra; 129 | } 130 | 131 | public function setExtras(array $extras): self 132 | { 133 | $this->extra = $extras; 134 | return $this; 135 | } 136 | 137 | public function setSession(array $session): self 138 | { 139 | $this->extra['session'] = $session; 140 | return $this; 141 | } 142 | 143 | public function serialize() 144 | { 145 | $result = array( 146 | "url" => $this->url, 147 | "method" => $this->method, 148 | "headers" => $this->headers, 149 | "params" => $this->params, 150 | "GET" => $this->get, 151 | "query_string" => $this->queryString, 152 | "POST" => $this->post, 153 | "body" => $this->body, 154 | "user_ip" => $this->userIp, 155 | ); 156 | foreach ($this->extra as $key => $val) { 157 | $result[$key] = $val; 158 | } 159 | 160 | return $this->utilities()->serializeForRollbarInternal($result, array_keys($this->extra)); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Payload/Server.php: -------------------------------------------------------------------------------- 1 | host; 25 | } 26 | 27 | public function setHost(?string $host): self 28 | { 29 | $this->host = $host; 30 | return $this; 31 | } 32 | 33 | public function getRoot(): ?string 34 | { 35 | return $this->root; 36 | } 37 | 38 | public function setRoot(?string $root): self 39 | { 40 | $this->root = $root; 41 | return $this; 42 | } 43 | 44 | public function getBranch(): ?string 45 | { 46 | return $this->branch; 47 | } 48 | 49 | public function setBranch(?string $branch): self 50 | { 51 | $this->branch = $branch; 52 | return $this; 53 | } 54 | 55 | public function getCodeVersion(): ?string 56 | { 57 | return $this->codeVersion; 58 | } 59 | 60 | public function setCodeVersion(?string $codeVersion): self 61 | { 62 | $this->codeVersion = $codeVersion; 63 | return $this; 64 | } 65 | 66 | public function setExtras(array $extras): self 67 | { 68 | $this->extra = $extras; 69 | return $this; 70 | } 71 | 72 | public function getExtras(): array 73 | { 74 | return $this->extra; 75 | } 76 | 77 | public function setArgv(array $argv): self 78 | { 79 | $this->extra['argv'] = $argv; 80 | return $this; 81 | } 82 | 83 | public function serialize() 84 | { 85 | $result = array( 86 | "host" => $this->host, 87 | "root" => $this->root, 88 | "branch" => $this->branch, 89 | "code_version" => $this->codeVersion, 90 | ); 91 | foreach ($this->extra as $key => $val) { 92 | $result[$key] = $val; 93 | } 94 | 95 | return $this->utilities()->serializeForRollbarInternal($result, array_keys($this->extra)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Payload/TelemetryBody.php: -------------------------------------------------------------------------------- 1 | extra = $extra; 71 | } 72 | 73 | /** 74 | * Creates a {@see TelemetryBody} instance from an array of data. 75 | * 76 | * The data array may be loosely structured, as only the keys that match the defined keys will be used to create the 77 | * instance. Any undefined keys in data will be stored in the {@see $extra} property. 78 | * 79 | * @param array $data The data to create the {@see TelemetryBody} instance from. 80 | * @return self 81 | * 82 | * @since 4.1.1 83 | */ 84 | public static function fromArray(array $data): self 85 | { 86 | // This filters out any keys that are not accepted by the constructor to prevent duplicate parameter errors from 87 | // named and positional arguments. 88 | $params = array_intersect_key($data, array_flip(self::DEFINED_KEYS)); 89 | // Generates an array of all the keys not used in the constructor. 90 | $extra = array_diff_key($data, $params); 91 | $instance = new self(...$params); 92 | $instance->extra = $extra; 93 | return $instance; 94 | } 95 | 96 | /** 97 | * Returns the array representation of the telemetry body. 98 | * 99 | * @return array 100 | */ 101 | public function serialize(): array 102 | { 103 | // This filters out any null or empty values. 104 | $result = array_filter([ 105 | 'message' => $this->message, 106 | 'method' => $this->method, 107 | 'url' => $this->url, 108 | 'status_code' => $this->status_code, 109 | 'subtype' => $this->subtype, 110 | 'stack' => $this->stack, 111 | 'from' => $this->from, 112 | 'to' => $this->to, 113 | 'start_timestamp_ms' => $this->start_timestamp_ms, 114 | 'end_timestamp_ms' => $this->end_timestamp_ms, 115 | ]); 116 | 117 | if (empty($this->extra)) { 118 | return $result; 119 | } 120 | 121 | // This keeps the extra data from overwriting the defined keys when the extra data is merged into the result. 122 | $extra = array_diff_key($this->extra, array_fill_keys(self::DEFINED_KEYS, null)); 123 | 124 | return $this->utilities()->serializeForRollbarInternal(array_merge($result, $extra)); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Payload/TelemetryEvent.php: -------------------------------------------------------------------------------- 1 | timestamp)) { 48 | $this->timestamp = floor(microtime(true) * 1000); 49 | } 50 | $this->body = is_array($body) ? TelemetryBody::fromArray($body): $body; 51 | } 52 | 53 | public function serialize(): array 54 | { 55 | $result = array_filter([ 56 | 'uuid' => $this->uuid, 57 | 'source' => $this->source, 58 | 'level' => $this->level->value, 59 | 'type' => $this->type->value, 60 | 'body' => $this->body->serialize(), 61 | 'timestamp_ms' => $this->timestamp, 62 | ]); 63 | 64 | return $this->utilities()->serializeForRollbarInternal($result); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Payload/Trace.php: -------------------------------------------------------------------------------- 1 | frames; 25 | } 26 | 27 | public function setFrames(array $frames): self 28 | { 29 | $this->frames = $frames; 30 | return $this; 31 | } 32 | 33 | public function getException(): ExceptionInfo 34 | { 35 | return $this->exception; 36 | } 37 | 38 | public function setException(ExceptionInfo $exception): self 39 | { 40 | $this->exception = $exception; 41 | return $this; 42 | } 43 | 44 | public function serialize() 45 | { 46 | $result = array( 47 | "frames" => $this->frames, 48 | "exception" => $this->exception, 49 | ); 50 | return $this->utilities()->serializeForRollbar($result); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Payload/TraceChain.php: -------------------------------------------------------------------------------- 1 | traces; 21 | } 22 | 23 | public function setTraces(array $traces): self 24 | { 25 | $this->traces = $traces; 26 | return $this; 27 | } 28 | 29 | public function serialize() 30 | { 31 | $mapValue = function ($value) { 32 | if ($value instanceof \Serializable) { 33 | trigger_error("Using the Serializable interface has been deprecated.", E_USER_DEPRECATED); 34 | return $value->serialize(); 35 | } 36 | if ($value instanceof SerializerInterface) { 37 | return $value->serialize(); 38 | } 39 | return $value; 40 | }; 41 | return array_map($mapValue, $this->traces); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | status; 17 | } 18 | 19 | public function getInfo(): mixed 20 | { 21 | return $this->info; 22 | } 23 | 24 | public function getUuid(): string 25 | { 26 | return $this->uuid; 27 | } 28 | 29 | public function wasSuccessful(): bool 30 | { 31 | return $this->status >= 200 && $this->status < 300; 32 | } 33 | 34 | public function getOccurrenceUrl(): ?string 35 | { 36 | if (is_null($this->uuid)) { 37 | return null; 38 | } 39 | if (!$this->wasSuccessful()) { 40 | return null; 41 | } 42 | return "https://rollbar.com/occurrence/uuid/?uuid=" . urlencode($this->uuid); 43 | } 44 | 45 | public function __toString(): string 46 | { 47 | $url = $this->getOccurrenceUrl(); 48 | return "Status: $this->status\n" . 49 | "Body: " . json_encode($this->info) . "\n" . 50 | "URL: $url"; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/ResponseHandlerInterface.php: -------------------------------------------------------------------------------- 1 | addJs($headers, $nonce, $customJs); 33 | } 34 | 35 | /** 36 | * Build Javascript required to include RollbarJS on 37 | * an HTML page 38 | * 39 | * @param array $headers Response headers usually retrieved through 40 | * headers_list() used to verify if nonce should be added to script 41 | * tags based on Content-Security-Policy 42 | * @param string $nonce Content-Security-Policy nonce string if exists 43 | * @param string $customJs Additional JavaScript to add at the end of 44 | * RollbarJs snippet 45 | * 46 | * @return string 47 | */ 48 | public function addJs($headers = null, $nonce = null, $customJs = "") 49 | { 50 | return $this->scriptTag( 51 | $this->configJsTag() . $this->jsSnippet() . ";" . $customJs, 52 | $headers, 53 | $nonce 54 | ); 55 | } 56 | 57 | /** 58 | * Build RollbarJS config script 59 | * 60 | * @return string 61 | */ 62 | public function configJsTag() 63 | { 64 | return "var _rollbarConfig = " . json_encode((object)$this->config) . ";"; 65 | } 66 | 67 | /** 68 | * Build rollbar.snippet.js string 69 | * 70 | * @return string 71 | */ 72 | public function jsSnippet() 73 | { 74 | return file_get_contents( 75 | $this->snippetPath() 76 | ); 77 | } 78 | 79 | /** 80 | * @return string Path to the rollbar.snippet.js 81 | */ 82 | public function snippetPath() 83 | { 84 | return realpath(__DIR__ . "/../data/rollbar.snippet.js"); 85 | } 86 | 87 | /** 88 | * Should JS snippet be added to the HTTP response 89 | * 90 | * @param int $status 91 | * @param array $headers 92 | * 93 | * @return boolean 94 | */ 95 | public function shouldAddJs($status, $headers) 96 | { 97 | return 98 | $status == 200 && 99 | $this->isHtml($headers) && 100 | !$this->hasAttachment($headers); 101 | 102 | /** 103 | * @todo not sure if below two conditions will be applicable 104 | */ 105 | /* !env[JS_IS_INJECTED_KEY] */ 106 | /* && !streaming?(env) */ 107 | } 108 | 109 | /** 110 | * Is the HTTP response a valid HTML response 111 | * 112 | * @param array $headers 113 | * 114 | * @return boolean 115 | */ 116 | public function isHtml($headers) 117 | { 118 | return in_array('Content-Type: text/html', $headers); 119 | } 120 | 121 | /** 122 | * Does the HTTP response include an attachment 123 | * 124 | * @param array $headers 125 | * 126 | * @return boolean 127 | */ 128 | public function hasAttachment($headers) 129 | { 130 | return in_array('Content-Disposition: attachment', $headers); 131 | } 132 | 133 | /** 134 | * Is `nonce` attribute on the script tag needed? 135 | * 136 | * @param array $headers 137 | * 138 | * @return boolean 139 | */ 140 | public function shouldAppendNonce($headers) 141 | { 142 | foreach ($headers as $header) { 143 | if (str_contains($header, 'Content-Security-Policy') && 144 | str_contains($header, "'unsafe-inline'")) { 145 | return true; 146 | } 147 | } 148 | 149 | return false; 150 | } 151 | 152 | /** 153 | * Build safe HTML script tag 154 | * 155 | * @param string $content 156 | * @param array $headers 157 | * @param 158 | * 159 | * @return string 160 | */ 161 | public function scriptTag($content, $headers = null, $nonce = null) 162 | { 163 | if ($headers !== null && $this->shouldAppendNonce($headers)) { 164 | if (!$nonce) { 165 | throw new \Exception( 166 | 'Content-Security-Policy is script-src '. 167 | 'inline-unsafe but nonce value not provided.' 168 | ); 169 | } 170 | 171 | return "\n"; 172 | } 173 | return "\n"; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/ScrubberInterface.php: -------------------------------------------------------------------------------- 1 | agentLogLocation = \Rollbar\Defaults::get()->agentLogLocation(); 21 | if (array_key_exists('agentLogLocation', $opts)) { 22 | $this->utilities()->validateString($opts['agentLogLocation'], 'opts["agentLogLocation"]', null, false); 23 | $this->agentLogLocation = $opts['agentLogLocation']; 24 | } 25 | } 26 | 27 | /** 28 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 29 | */ 30 | public function send(EncodedPayload $payload, string $accessToken): Response 31 | { 32 | if (empty($this->agentLog)) { 33 | $this->loadAgentFile(); 34 | } 35 | fwrite($this->agentLog, $payload->encoded() . "\n"); 36 | 37 | $data = $payload->data(); 38 | $uuid = $data['data']['uuid']; 39 | return new Response(0, "Written to agent file", $uuid); 40 | } 41 | 42 | /** 43 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 44 | */ 45 | public function sendBatch(array $batch, string $accessToken): void 46 | { 47 | if (empty($this->agentLog)) { 48 | $this->loadAgentFile(); 49 | } 50 | foreach ($batch as $payload) { 51 | fwrite($this->agentLog, $payload->encoded() . "\n"); 52 | } 53 | } 54 | 55 | /** 56 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 57 | */ 58 | public function wait(string $accessToken, int $max): void 59 | { 60 | return; 61 | } 62 | 63 | /** 64 | * Returns true if the access token is required by the sender to send the payload. The agent can be configured to 65 | * provide its own access token. But may not have its own, so we are requiring it for now. See 66 | * {@link https://github.com/rollbar/rollbar-php/issues/405} for more details. 67 | * 68 | * @since 4.0.0 69 | * 70 | * @return bool 71 | */ 72 | public function requireAccessToken(): bool 73 | { 74 | return true; 75 | } 76 | 77 | private function loadAgentFile() 78 | { 79 | $filename = $this->agentLogLocation . '/rollbar-relay.' . getmypid() . '.' . microtime(true) . '.rollbar'; 80 | $this->agentLog = fopen($filename, 'a'); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Senders/FluentSender.php: -------------------------------------------------------------------------------- 1 | fluentHost = \Rollbar\Defaults::get()->fluentHost(); 44 | $this->fluentPort = \Rollbar\Defaults::get()->fluentPort(); 45 | $this->fluentTag = \Rollbar\Defaults::get()->fluentTag(); 46 | 47 | if (isset($opts['fluentHost'])) { 48 | $this->utilities()->validateString($opts['fluentHost'], 'opts["fluentHost"]', null, false); 49 | $this->fluentHost = $opts['fluentHost']; 50 | } 51 | 52 | if (isset($opts['fluentPort'])) { 53 | $this->utilities()->validateInteger($opts['fluentPort'], 'opts["fluentPort"]', null, null, false); 54 | $this->fluentPort = $opts['fluentPort']; 55 | } 56 | 57 | if (isset($opts['fluentTag'])) { 58 | $this->utilities()->validateString($opts['fluentTag'], 'opts["fluentTag"]', null, false); 59 | $this->fluentTag = $opts['fluentTag']; 60 | } 61 | } 62 | 63 | 64 | /** 65 | * @param \Rollbar\Payload\EncodedPayload $payload 66 | * @param string $accessToken 67 | * @return Response 68 | * 69 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) Unused parameter is 70 | * intended here to comply with SenderInterface 71 | */ 72 | public function send(EncodedPayload $payload, string $accessToken): Response 73 | { 74 | if (empty($this->fluentLogger)) { 75 | $this->loadFluentLogger(); 76 | } 77 | 78 | $scrubbedPayload = $payload->data(); 79 | 80 | $success = $this->fluentLogger->post($this->fluentTag, $scrubbedPayload); 81 | $status = $success ? 200 : 400; 82 | $info = $success ? 'OK' : 'Bad Request'; 83 | $uuid = $scrubbedPayload['data']['uuid']; 84 | 85 | return new Response($status, $info, $uuid); 86 | } 87 | 88 | public function sendBatch(array $batch, string $accessToken, &$responses = array ()): void 89 | { 90 | $responses = array(); 91 | foreach ($batch as $payload) { 92 | $responses[] = $this->send($payload, $accessToken); 93 | } 94 | } 95 | 96 | /** 97 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 98 | */ 99 | public function wait(string $accessToken, int $max): void 100 | { 101 | return; 102 | } 103 | 104 | /** 105 | * Returns true if the access token is required by the sender to send the payload. The Fluentd service can provide 106 | * its own access token. 107 | * 108 | * @return bool 109 | * @since 4.0.0 110 | */ 111 | public function requireAccessToken(): bool 112 | { 113 | return false; 114 | } 115 | 116 | /** 117 | * Loads the fluent logger 118 | */ 119 | protected function loadFluentLogger() 120 | { 121 | $this->fluentLogger = new FluentLogger($this->fluentHost, $this->fluentPort); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Senders/SenderInterface.php: -------------------------------------------------------------------------------- 1 | includeItemsInTelemetry` config 49 | * option to `false`. 50 | * 2. Ignored Rollbar items are not included in the telemetry data by changing the default value of the 51 | * `telemetry => includeIgnoredItemsInTelemetry` config option to `true`. And the reported item is ignored 52 | * because of its log level or PHP error reporting level. 53 | * 54 | * @param string $level The PSR-3 log level. 55 | * @param string|Stringable $message The message to log. 56 | * @param array $context The context. 57 | * @param bool $ignored Whether the item was ignored as a result of the configuration. If false, then 58 | * the item will not be sent to Rollbar. However, you may still want to include 59 | * it in the telemetry data. 60 | * 61 | * @return bool True if the item should be included in the telemetry queue, false if it should be excluded. 62 | */ 63 | public function includeRollbarItem( 64 | string $level, 65 | string|Stringable $message, 66 | array $context = [], 67 | bool $ignored = false, 68 | ): bool; 69 | 70 | /** 71 | * Returns `true` if the {@see include()} method should be called not only before the event is added to the queue, 72 | * but also before the event is sent to Rollbar. This means the {@see include()} method will be called twice for 73 | * each event. 74 | * 75 | * If this method returns `false`, then the {@see include()} method will only be called before the event is added to 76 | * the queue. 77 | * 78 | * @return bool 79 | */ 80 | public function filterOnRead(): bool; 81 | } 82 | -------------------------------------------------------------------------------- /src/TransformerInterface.php: -------------------------------------------------------------------------------- 1 | data(); 32 | 33 | if (isset($data['data']['body']['trace_chain'])) { 34 | foreach ($data['data']['body']['trace_chain'] as $offset => $value) { 35 | $data['data']['body']['trace_chain'][$offset]['frames'] = self::selectFrames($value['frames']); 36 | } 37 | 38 | $payload->encode($data); 39 | } elseif (isset($data['data']['body']['trace']['frames'])) { 40 | $data['data']['body']['trace']['frames'] = self::selectFrames($data['data']['body']['trace']['frames']); 41 | $payload->encode($data); 42 | } 43 | 44 | return $payload; 45 | } 46 | 47 | /** 48 | * Removes frames from the middle of the stack trace frames. Will keep the number of frames specified by $range at 49 | * the start and end of the array. 50 | * 51 | * This method is also used by {@see MinBodyStrategy}. 52 | * 53 | * @param array $frames The list of stack trace frames. 54 | * @param int $range The number of frames to keep on each end of the frames array. 55 | * 56 | * @return array 57 | */ 58 | public static function selectFrames(array $frames, int $range = self::FRAMES_OPTIMIZATION_RANGE): array 59 | { 60 | if (count($frames) <= $range * 2) { 61 | return $frames; 62 | } 63 | 64 | return array_merge( 65 | array_splice($frames, 0, $range), 66 | array_splice($frames, -$range, $range) 67 | ); 68 | } 69 | 70 | /** 71 | * Returns true if the payload has a trace chain or trace frames. 72 | * 73 | * @param EncodedPayload $payload 74 | * 75 | * @return bool 76 | */ 77 | public function applies(EncodedPayload $payload): bool 78 | { 79 | $payload = $payload->data(); 80 | 81 | if (isset($payload['data']['body']['trace_chain']) || 82 | isset($payload['data']['body']['trace']['frames'])) { 83 | return true; 84 | } 85 | 86 | return false; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Truncation/MinBodyStrategy.php: -------------------------------------------------------------------------------- 1 | data(); 37 | $modified = false; 38 | $traceData = array(); 39 | 40 | if (isset($data['data']['body']['trace'])) { 41 | $traceData = &$data['data']['body']['trace']; 42 | } elseif (isset($data['data']['body']['trace_chain'])) { 43 | $traceData = &$data['data']['body']['trace_chain']; 44 | } 45 | 46 | if (isset($traceData['exception'])) { 47 | // Delete exception description 48 | unset($traceData['exception']['description']); 49 | 50 | // Truncate exception message 51 | $traceData['exception']['message'] = substr( 52 | $traceData['exception']['message'], 53 | 0, 54 | static::EXCEPTION_MESSAGE_LIMIT 55 | ); 56 | 57 | $modified = true; 58 | } 59 | 60 | // Limit trace frames 61 | if (!empty($traceData['frames'])) { 62 | $traceData['frames'] = FramesStrategy::selectFrames( 63 | $traceData['frames'], 64 | static::EXCEPTION_FRAMES_RANGE 65 | ); 66 | 67 | $modified = true; 68 | } 69 | 70 | if ($modified) { 71 | $payload->encode($data); 72 | } 73 | 74 | return $payload; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Truncation/RawStrategy.php: -------------------------------------------------------------------------------- 1 | data(); 37 | $modified = false; 38 | 39 | foreach (static::getThresholds() as $threshold) { 40 | if (!$this->truncation->needsTruncating($payload)) { 41 | break; 42 | } 43 | 44 | if ($this->traverse($data, $threshold, $payload)) { 45 | $modified = true; 46 | } 47 | } 48 | 49 | if ($modified) { 50 | $payload->encode($data); 51 | } 52 | 53 | return $payload; 54 | } 55 | 56 | /** 57 | * Traverse recursively reduces the length of each string to the max length of $threshold. The strings in the $data 58 | * array are truncated in place and not returned. 59 | * 60 | * @param array $data An array that may contain strings needing to be truncated. 61 | * @param int $threshold The maximum length string may be before it is truncated. 62 | * @param EncodedPayload $payload The payload that may need to be truncated. 63 | * 64 | * @return bool Returns true if the data was modified. 65 | */ 66 | protected function traverse(array &$data, int $threshold, EncodedPayload $payload): bool 67 | { 68 | $modified = false; 69 | 70 | foreach ($data as &$value) { 71 | if (is_array($value)) { 72 | if ($this->traverse($value, $threshold, $payload)) { 73 | $modified = true; 74 | } 75 | continue; 76 | } 77 | if (is_string($value) && (($strlen = strlen($value)) > $threshold)) { 78 | $value = substr($value, 0, $threshold); 79 | $modified = true; 80 | $payload->decreaseSize($strlen - $threshold); 81 | } 82 | } 83 | 84 | return $modified; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Truncation/TelemetryStrategy.php: -------------------------------------------------------------------------------- 1 | data(); 56 | 57 | // If telemetry is not enabled, then remove the telemetry data from the payload entirely. 58 | if (null === Rollbar::getTelemeter()) { 59 | unset($data['data']['body']['telemetry']); 60 | $payload->encode($data); 61 | return $payload; 62 | } 63 | 64 | if (!isset($data['data']['body']['telemetry'])) { 65 | return $payload; 66 | } 67 | 68 | $data['data']['body']['telemetry'] = self::selectTelemetry($data['data']['body']['telemetry']); 69 | $payload->encode($data); 70 | 71 | return $payload; 72 | } 73 | 74 | /** 75 | * Returns true if the given payload contains telemetry data. This is irrespective of whether the telemetry is 76 | * enabled or not. 77 | * 78 | * @param EncodedPayload $payload The payload to truncate. 79 | * 80 | * @return bool 81 | * 82 | * @since 4.1.0 83 | */ 84 | public function applies(EncodedPayload $payload): bool 85 | { 86 | // If the payload does not telemetry data, then this strategy does not apply. 87 | return isset($payload->data()['data']['body']['telemetry']); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Truncation/Truncation.php: -------------------------------------------------------------------------------- 1 | getCustomTruncation()) { 38 | $this->registerStrategy($custom); 39 | } 40 | } 41 | 42 | /** 43 | * Adds a new truncation strategy to the list of strategies used to truncate large payloads before they are sent 44 | * over the wire. A strategy registered with this method will be used before any existing ones are used. It does 45 | * not replace or remove existing strategies. 46 | * 47 | * @param string $type The fully qualified class name of a truncation strategy. The strategy must implement the 48 | * {@see StrategyInterface} interface. 49 | * 50 | * @return void 51 | * @throws Exception If the strategy class does not implement {@see StrategyInterface}. 52 | */ 53 | public function registerStrategy(string $type): void 54 | { 55 | if (!class_exists($type)) { 56 | throw new Exception('Truncation strategy "' . $type . '" doesn\'t exist.'); 57 | } 58 | if (!in_array(StrategyInterface::class, class_implements($type))) { 59 | throw new Exception( 60 | 'Truncation strategy "' . $type . '" doesn\'t implement ' . StrategyInterface::class 61 | ); 62 | } 63 | array_unshift(static::$truncationStrategies, $type); 64 | } 65 | 66 | /** 67 | * Applies truncation strategies in order to keep the payload size under 68 | * configured limit. 69 | * 70 | * @param EncodedPayload $payload The payload that may need to be truncated. 71 | * 72 | * @return EncodedPayload 73 | */ 74 | public function truncate(EncodedPayload $payload): EncodedPayload 75 | { 76 | foreach (static::$truncationStrategies as $strategy) { 77 | $strategy = new $strategy($this); 78 | 79 | if (!$strategy->applies($payload)) { 80 | continue; 81 | } 82 | 83 | if (!$this->needsTruncating($payload)) { 84 | break; 85 | } 86 | 87 | $this->config->verboseLogger()->debug('Applying truncation strategy ' . get_class($strategy)); 88 | 89 | $payload = $strategy->execute($payload); 90 | } 91 | 92 | return $payload; 93 | } 94 | 95 | /** 96 | * Check if the payload is too big to be sent 97 | * 98 | * @param EncodedPayload $payload The payload that may need to be truncated. 99 | * 100 | * @return boolean 101 | */ 102 | public function needsTruncating(EncodedPayload $payload): bool 103 | { 104 | return $payload->size() > self::MAX_PAYLOAD_SIZE; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/UtilitiesTrait.php: -------------------------------------------------------------------------------- 1 | path)) { 19 | mkdir($this->path); 20 | } 21 | } 22 | 23 | public function testAgent(): void 24 | { 25 | Rollbar\Rollbar::init(array( 26 | 'access_token' => $this->getTestAccessToken(), 27 | 'environment' => 'testing', 28 | 'agent_log_location' => $this->path, 29 | 'handler' => 'agent' 30 | ), false, false, false); 31 | $logger = Rollbar\Rollbar::logger(); 32 | $logger->info("this is a test"); 33 | $file = fopen($this->path . '/rollbar-relay.' . getmypid() . '.' . microtime() . '.rollbar', 'r'); 34 | $line = fgets($file); 35 | $this->assertStringContainsString('this is a test', $line); 36 | } 37 | 38 | public function tearDown(): void 39 | { 40 | parent::tearDown(); 41 | $this->rrmdir($this->path); 42 | } 43 | 44 | private function rrmdir($dir): void 45 | { 46 | if (!is_dir($dir)) { 47 | return; 48 | } 49 | 50 | $objects = scandir($dir); 51 | foreach ($objects as $object) { 52 | if ($object != "." && $object != "..") { 53 | if (filetype($dir . "/" . $object) == "dir") { 54 | $this->rrmdir($dir . "/" . $object); 55 | } else { 56 | unlink($dir . "/" . $object); 57 | } 58 | } 59 | } 60 | reset($objects); 61 | rmdir($dir); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/BaseRollbarTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($value, $body->getValue()); 14 | 15 | $mock2 = m::mock(ContentInterface::class); 16 | $this->assertEquals($mock2, $body->setValue($mock2)->getValue()); 17 | } 18 | 19 | public function testExtra(): void 20 | { 21 | $value = m::mock(ContentInterface::class) 22 | ->shouldReceive("serialize") 23 | ->andReturn("{CONTENT}") 24 | ->shouldReceive("getKey") 25 | ->andReturn("content_interface") 26 | ->mock(); 27 | $expected = array( 28 | "hello" => "world" 29 | ); 30 | $body = new Body($value, $expected); 31 | $this->assertEquals($body->getExtra(), $expected); 32 | } 33 | 34 | public function testSerialize(): void 35 | { 36 | $value = m::mock(ContentInterface::class) 37 | ->shouldReceive("serialize") 38 | ->andReturn("{CONTENT}") 39 | ->shouldReceive("getKey") 40 | ->andReturn("content_interface") 41 | ->mock(); 42 | $body = new Body($value, array('foo' => 'bar')); 43 | $encoded = json_encode($body->serialize()); 44 | $this->assertEquals( 45 | "{\"content_interface\":\"{CONTENT}\",\"extra\":{\"foo\":\"bar\"}}", 46 | $encoded 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/ContextTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($pre, $context->getPre()); 13 | 14 | $pre2 = array("lineone", "linetwo"); 15 | $this->assertEquals($pre2, $context->setPre($pre2)->getPre()); 16 | } 17 | 18 | public function testContextPost(): void 19 | { 20 | $post = array("four", "five"); 21 | $context = new Context(array(), $post); 22 | $this->assertEquals($post, $context->getPost()); 23 | 24 | $post2 = array("six", "seven", "eight"); 25 | $this->assertEquals($post2, $context->setPost($post2)->getPost()); 26 | } 27 | 28 | public function testEncode(): void 29 | { 30 | $context = new Context(array(), array()); 31 | $encoded = json_encode($context->serialize()); 32 | $this->assertEquals('{"pre":[],"post":[]}', $encoded); 33 | 34 | $context = new Context(array("one"), array("three")); 35 | $encoded = json_encode($context->serialize()); 36 | $this->assertEquals('{"pre":["one"],"post":["three"]}', $encoded); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/CurlSenderTest.php: -------------------------------------------------------------------------------- 1 | $this->getTestAccessToken(), 17 | "environment" => "testing-php", 18 | "endpoint" => "fake-endpoint" 19 | )); 20 | $response = $logger->report(Level::WARNING, "Testing PHP Notifier", array()); 21 | 22 | $this->assertContains( 23 | $response->getInfo(), 24 | array( 25 | "Couldn't resolve host 'fake-endpointitem'", // hack for PHP 5.3 26 | "Could not resolve host: fake-endpointitem", 27 | "Could not resolve: fake-endpointitem (Domain name not found)", 28 | "Empty reply from server" 29 | ) 30 | ); 31 | } 32 | 33 | /** 34 | * This test will fail if the {@see CurlSender::$ipResolve} property is renamed or removed. 35 | */ 36 | public function testIPResolve(): void 37 | { 38 | $sender = new CurlSender([ 39 | 'ip_resolve' => CURL_IPRESOLVE_V4, 40 | ]); 41 | self::assertSame(CURL_IPRESOLVE_V4, self::getPrivateProperty($sender, 'ipResolve')); 42 | 43 | $sender = new CurlSender([]); 44 | self::assertSame(CURL_IPRESOLVE_V4, self::getPrivateProperty($sender, 'ipResolve')); 45 | 46 | $sender = new CurlSender([ 47 | 'ip_resolve' => CURL_IPRESOLVE_V6, 48 | ]); 49 | self::assertSame(CURL_IPRESOLVE_V6, self::getPrivateProperty($sender, 'ipResolve')); 50 | 51 | $sender = new CurlSender([ 52 | 'ip_resolve' => CURL_IPRESOLVE_WHATEVER, 53 | ]); 54 | self::assertSame(CURL_IPRESOLVE_WHATEVER, self::getPrivateProperty($sender, 'ipResolve')); 55 | } 56 | 57 | /** 58 | * Returns the value of a private property of an object. 59 | * 60 | * @param object $object The object from which to get the private property. 61 | * @param string $property The name of the private property to get. 62 | * @return mixed 63 | * @throws ReflectionException If the object or property does not exist. 64 | */ 65 | private static function getPrivateProperty(object $object, string $property): mixed 66 | { 67 | $reflection = new ReflectionClass($object); 68 | $property = $reflection->getProperty($property); 69 | $property->setAccessible(true); 70 | return $property->getValue($object); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/ErrorWrapperTest.php: -------------------------------------------------------------------------------- 1 | 'FAKE BACKTRACE'], 15 | new Utilities 16 | ); 17 | $this->assertEquals(['fake' => 'FAKE BACKTRACE'], $errWrapper->getBacktrace()); 18 | } 19 | 20 | public function testGetClassName(): void 21 | { 22 | $errWrapper = new ErrorWrapper( 23 | E_ERROR, 24 | "Message Content", 25 | null, 26 | null, 27 | null, 28 | new Utilities 29 | ); 30 | $this->assertEquals("E_ERROR", $errWrapper->getClassName()); 31 | 32 | $errWrapper = new ErrorWrapper( 33 | 3, 34 | "Fake Error Number", 35 | null, 36 | null, 37 | null, 38 | new Utilities 39 | ); 40 | $this->assertEquals("#3", $errWrapper->getClassName()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/ExceptionInfoTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($class, $exc->getClass()); 12 | 13 | $this->assertEquals("TestClass", $exc->setClass("TestClass")->getClass()); 14 | } 15 | 16 | public function testMessage(): void 17 | { 18 | $message = "A message"; 19 | $exc = new ExceptionInfo("C", $message); 20 | $this->assertEquals($message, $exc->getMessage()); 21 | 22 | $this->assertEquals("Another", $exc->setMessage("Another")->getMessage()); 23 | } 24 | 25 | public function testDescription(): void 26 | { 27 | $description = "long form"; 28 | $exc = new ExceptionInfo("C", "s", $description); 29 | $this->assertEquals($description, $exc->getDescription()); 30 | 31 | $this->assertEquals("longer form", $exc->setDescription("longer form")->getDescription()); 32 | $this->assertNull($exc->setDescription(null)->getDescription()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/FakeDataBuilder.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 14 | 'Suggested package fluent/logger not installed, skip FluentTest' 15 | ); 16 | } 17 | 18 | $socket = socket_create_listen(null); 19 | socket_getsockname($socket, $address, $port); 20 | 21 | Rollbar::init(array( 22 | 'access_token' => 'ad865e76e7fb496fab096ac07b1dbabb', 23 | 'environment' => 'testing' 24 | ), false, false, false); 25 | $logger = Rollbar::scope(array( 26 | 'batched' => false, 27 | 'fluent_host' => $address, 28 | 'fluent_port' => $port, 29 | 'handler' => 'fluent' 30 | )); 31 | $this->assertEquals(200, $logger->report(Level::INFO, 'this is a test', array())->getStatus()); 32 | 33 | socket_close($socket); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/FrameTest.php: -------------------------------------------------------------------------------- 1 | exception = m::mock(ExceptionInfo::class); 16 | $this->frame = new Frame("tests/FrameTest.php"); 17 | } 18 | 19 | public function testFilename(): void 20 | { 21 | $frame = new Frame("filename.php"); 22 | $this->assertEquals("filename.php", $frame->getFilename()); 23 | $frame->setFilename("other.php"); 24 | $this->assertEquals("other.php", $frame->getFilename()); 25 | } 26 | 27 | public function testLineno(): void 28 | { 29 | $this->frame->setLineno(5); 30 | $this->assertEquals(5, $this->frame->getLineno()); 31 | } 32 | 33 | public function testColno(): void 34 | { 35 | $this->frame->setColno(5); 36 | $this->assertEquals(5, $this->frame->getColno()); 37 | } 38 | 39 | public function testMethod(): void 40 | { 41 | $this->frame->setMethod("method"); 42 | $this->assertEquals("method", $this->frame->getMethod()); 43 | } 44 | 45 | public function testCode(): void 46 | { 47 | $this->frame->setCode("code->whatever()"); 48 | $this->assertEquals("code->whatever()", $this->frame->getCode()); 49 | } 50 | 51 | public function testContext(): void 52 | { 53 | $context = m::mock(Context::class); 54 | $this->frame->setContext($context); 55 | $this->assertEquals($context, $this->frame->getContext()); 56 | } 57 | 58 | public function testArgs(): void 59 | { 60 | $this->frame->setArgs(array()); 61 | $this->assertEquals(array(), $this->frame->getArgs()); 62 | 63 | $this->frame->setArgs(array(1, "hi")); 64 | $this->assertEquals(array(1, "hi"), $this->frame->getArgs()); 65 | } 66 | 67 | public function testEncode(): void 68 | { 69 | $context = m::mock("Rollbar\Payload\Context, Rollbar\SerializerInterface") 70 | ->shouldReceive("serialize") 71 | ->andReturn("{CONTEXT}") 72 | ->mock(); 73 | $this->exception 74 | ->shouldReceive("serialize") 75 | ->andReturn("{EXC}") 76 | ->mock(); 77 | $this->frame->setFilename("rollbar.php") 78 | ->setLineno(1024) 79 | ->setColno(42) 80 | ->setMethod("testEncode()") 81 | ->setCode('$frame->setFilename("rollbar.php")') 82 | ->setContext($context) 83 | ->setArgs(array("hello", "world")); 84 | 85 | $actual = json_encode($this->frame->serialize()); 86 | $expected = '{' . 87 | '"filename":"rollbar.php",' . 88 | '"lineno":1024,"colno":42,' . 89 | '"method":"testEncode()",' . 90 | '"code":"$frame->setFilename(\"rollbar.php\")",' . 91 | '"context":"{CONTEXT}",' . 92 | '"args":["hello","world"]' . 93 | '}'; 94 | 95 | $this->assertEquals($expected, $actual); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/Handlers/ErrorHandlerTest.php: -------------------------------------------------------------------------------- 1 | useErrorHandler to deal with stopping the 10 | * PHPUnit's error handler instead of set_error_handler() 11 | */ 12 | class ErrorHandlerTest extends BaseRollbarTest 13 | { 14 | public function __construct($name = null, $data = array(), $dataName = null) 15 | { 16 | self::$simpleConfig['access_token'] = $this->getTestAccessToken(); 17 | self::$simpleConfig['included_errno'] = E_ALL; 18 | self::$simpleConfig['environment'] = 'test'; 19 | 20 | parent::__construct($name, $data, $dataName); 21 | } 22 | 23 | private static array $simpleConfig = array(); 24 | 25 | public function testPreviousErrorHandler(): void 26 | { 27 | $testCase = $this; 28 | 29 | set_error_handler(function () use ($testCase) { 30 | 31 | $testCase->assertTrue(true, "Previous error handler invoked."); 32 | 33 | set_error_handler(null); 34 | }); 35 | 36 | Rollbar::init(self::$simpleConfig); 37 | 38 | @trigger_error(E_USER_ERROR); 39 | } 40 | 41 | public function testRegister(): void 42 | { 43 | $handler = $this->getMockBuilder(ErrorHandler::class) 44 | ->setConstructorArgs(array(new RollbarLogger(self::$simpleConfig))) 45 | ->onlyMethods(array('handle')) 46 | ->getMock(); 47 | 48 | $handler->expects($this->once()) 49 | ->method('handle'); 50 | 51 | $handler->register(); 52 | 53 | trigger_error(E_USER_ERROR); 54 | } 55 | 56 | public function testHandle(): void 57 | { 58 | $logger = $this->getMockBuilder(RollbarLogger::class) 59 | ->setConstructorArgs(array(self::$simpleConfig)) 60 | ->onlyMethods(array('report')) 61 | ->getMock(); 62 | 63 | $logger->expects($this->once()) 64 | ->method('report'); 65 | 66 | /** 67 | * Disable PHPUnit's error handler as it would get triggered as the 68 | * previously set error handler. No need for that here. 69 | */ 70 | $phpunitHandler = set_error_handler(function () { 71 | }); 72 | 73 | $handler = new ErrorHandler($logger); 74 | $handler->register(); 75 | 76 | $handler->handle(E_USER_ERROR, "", "", ""); 77 | 78 | /** 79 | * Clean up the error handler set up for this test. 80 | */ 81 | set_error_handler($phpunitHandler); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/Handlers/ExceptionHandlerTest.php: -------------------------------------------------------------------------------- 1 | getTestAccessToken(); 13 | self::$simpleConfig['included_errno'] = E_ALL; 14 | self::$simpleConfig['environment'] = 'test'; 15 | 16 | parent::__construct($name, $data, $dataName); 17 | } 18 | 19 | private static array $simpleConfig = array(); 20 | 21 | /** 22 | * It's impossible to throw an uncaught exception with PHPUnit and thus 23 | * trigger the exception handler automatically. To overcome this limitation, 24 | * this test invokes the handle() methd manually with an assertion in the 25 | * previously set exception handler. 26 | */ 27 | public function testPreviousExceptionHandler(): void 28 | { 29 | $testCase = $this; 30 | 31 | set_exception_handler(function () use ($testCase) { 32 | 33 | $testCase->assertTrue(true, "Previous exception handler invoked."); 34 | }); 35 | 36 | $handler = new ExceptionHandler(new RollbarLogger(self::$simpleConfig)); 37 | $handler->register(); 38 | 39 | $handler->handle(new \Exception()); 40 | } 41 | 42 | /** 43 | * It's impossible to throw an uncaught exception with PHPUnit and thus 44 | * trigger the exception handler automatically. To overcome this limitation, 45 | * this test fetches the exception handler set by the setup method with 46 | * set_exception_handler() and invokes it manually with a mock expectation. 47 | */ 48 | public function testSetup(): void 49 | { 50 | $handler = $this->getMockBuilder(ExceptionHandler::class) 51 | ->setConstructorArgs(array(new RollbarLogger(self::$simpleConfig))) 52 | ->onlyMethods(array('handle')) 53 | ->getMock(); 54 | 55 | $handler->expects($this->once()) 56 | ->method('handle'); 57 | 58 | $handler->register(); 59 | 60 | $setExceptionHandler = set_exception_handler(function () { 61 | }); 62 | 63 | $handler = $setExceptionHandler[0]; 64 | $method = $setExceptionHandler[1]; 65 | 66 | $handler->$method(); 67 | } 68 | 69 | public function testHandle(): void 70 | { 71 | set_exception_handler(function () { 72 | }); 73 | 74 | $logger = $this->getMockBuilder(RollbarLogger::class) 75 | ->setConstructorArgs(array(self::$simpleConfig)) 76 | ->onlyMethods(array('report')) 77 | ->getMock(); 78 | 79 | $logger->expects($this->once()) 80 | ->method('report'); 81 | 82 | $handler = new ExceptionHandler($logger); 83 | $handler->register(); 84 | 85 | $handler->handle(new \Exception()); 86 | 87 | set_exception_handler(function () { 88 | }); 89 | } 90 | 91 | /** 92 | * This test is specifically for the deprecated dynamic properties in PHP 8.2. We were setting a property named 93 | * "isUncaught" on the exception object, which is now deprecated. This test ensures that we are no longer setting 94 | * that property. 95 | * 96 | * @return void 97 | */ 98 | public function testDeprecatedDynamicProperties(): void 99 | { 100 | // Set error reporting level and error handler to capture deprecation 101 | // warnings. 102 | $prev = error_reporting(E_ALL); 103 | $errors = array(); 104 | set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$errors) { 105 | $errors[] = array( 106 | 'errno' => $errno, 107 | 'errstr' => $errstr, 108 | 'errfile' => $errfile, 109 | 'errline' => $errline, 110 | ); 111 | }); 112 | $handler = new ExceptionHandler(new RollbarLogger(self::$simpleConfig)); 113 | $handler->register(); 114 | 115 | $handler->handle(new \Exception()); 116 | restore_error_handler(); 117 | error_reporting($prev); 118 | 119 | // self::assertSame used instead of self::assertSame so the contents of 120 | // $errors are printed in the test output. 121 | self::assertSame([], $errors); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/LevelFactoryTest.php: -------------------------------------------------------------------------------- 1 | 'value 1'); 10 | $msg = new Message("Test", $expected); 11 | $this->assertEquals($expected, $msg->getBacktrace()); 12 | } 13 | 14 | public function testBody(): void 15 | { 16 | $msg = new Message("Test"); 17 | $this->assertEquals("Test", $msg->getBody()); 18 | 19 | $this->assertEquals("Test2", $msg->setBody("Test2")->getBody()); 20 | } 21 | 22 | public function testMessageKey(): void 23 | { 24 | $msg = new Message("Test"); 25 | $this->assertEquals("message", $msg->getKey()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/NotifierTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($name, $notifier->getName()); 13 | 14 | $name2 = "RollbarPHP"; 15 | $this->assertEquals($name2, $notifier->setName($name2)->getName()); 16 | } 17 | 18 | public function testVersion(): void 19 | { 20 | $version = Notifier::VERSION; 21 | $notifier = new Notifier("PHP-Rollbar", $version); 22 | $this->assertEquals($version, $notifier->getVersion()); 23 | 24 | $version2 = "0.9"; 25 | $this->assertEquals($version2, $notifier->setVersion($version2)->getVersion()); 26 | } 27 | 28 | public function testDefaultNotifierIsRepresentableAsJson(): void 29 | { 30 | $notifier = Notifier::defaultNotifier()->serialize(); 31 | $encoding = json_encode($notifier, flags: JSON_THROW_ON_ERROR|JSON_FORCE_OBJECT); 32 | $decoding = json_decode($encoding, flags: JSON_THROW_ON_ERROR); 33 | 34 | if (method_exists($this, 'assertObjectHasProperty')) { 35 | # phpUnit 10 36 | $this->assertObjectHasProperty('name', $decoding); 37 | $this->assertObjectHasProperty('version', $decoding); 38 | } else { 39 | # phpUnit 9 40 | $this->assertObjectHasAttribute('name', $decoding); 41 | $this->assertObjectHasAttribute('version', $decoding); 42 | } 43 | } 44 | 45 | public function testDefaultNotifierVersionIsSemVerCompliant(): void 46 | { 47 | // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string 48 | $semVerRegex = '/ 49 | (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*) 50 | (?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*) 51 | (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))? 52 | (?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))? 53 | /x'; 54 | $this->assertMatchesRegularExpression( 55 | $semVerRegex, 56 | Notifier::defaultNotifier()->getVersion() 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Payload/EncodedPayloadTest.php: -------------------------------------------------------------------------------- 1 | "bar" 11 | ); 12 | 13 | $encoded = new EncodedPayload($data); 14 | $encoded->encode(); 15 | 16 | $this->assertEquals($expected, $encoded); 17 | 18 | $expected = '{"new":"bar"}'; 19 | $encoded->encode(array("new" => "bar")); 20 | 21 | $this->assertEquals($expected, $encoded); 22 | } 23 | 24 | /** 25 | * @requires PHP 5.5 26 | */ 27 | public function testEncodeMalformedData(): void 28 | { 29 | $encoded = new EncodedPayload(array( 30 | 'data' => array( 31 | 'body' => array( 32 | 'exception' => array( 33 | 'trace' => array( 34 | 'frames' => fopen('/dev/null', 'r') 35 | ), 36 | ), 37 | 'ecodable1' => true 38 | ), 39 | 'ecodable2' => true 40 | ) 41 | )); 42 | $encoded->encode(); 43 | 44 | $result = json_decode($encoded->encoded(), true); 45 | 46 | if (defined('HHVM_VERSION')) { 47 | $this->assertEmpty($result['data']['body']['exception']['trace']['frames']); 48 | } else { 49 | $this->assertNull($result['data']['body']['exception']['trace']['frames']); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Payload/LevelTest.php: -------------------------------------------------------------------------------- 1 | level = (new LevelFactory())->fromName(Level::ERROR); 12 | } 13 | 14 | public function testInvalidLevelThrowsAnException(): void 15 | { 16 | self::expectException(\Error::class); 17 | $level = Level::TEST(); 18 | } 19 | 20 | public function testLevel(): void 21 | { 22 | $level = Level::CRITICAL; 23 | self::assertNotNull($level); 24 | self::assertSame(Level::CRITICAL, $level); 25 | } 26 | 27 | public function testStringable(): void 28 | { 29 | self::assertSame('error', (string)$this->level); 30 | } 31 | 32 | public function testToInt(): void 33 | { 34 | self::assertSame(10000, $this->level->toInt()); 35 | } 36 | 37 | public function testSerialize(): void 38 | { 39 | self::assertSame('error', $this->level->serialize()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Payload/TelemetryBodyTest.php: -------------------------------------------------------------------------------- 1 | message); 28 | self::assertSame('method', $body->method); 29 | self::assertSame('url', $body->url); 30 | self::assertSame('status', $body->status_code); 31 | self::assertSame('sub', $body->subtype); 32 | self::assertSame('stack', $body->stack); 33 | self::assertSame('from', $body->from); 34 | self::assertSame('to', $body->to); 35 | self::assertSame(42, $body->start_timestamp_ms); 36 | self::assertSame(43, $body->end_timestamp_ms); 37 | self::assertSame([ 38 | 'extraOne' => 'foo', 39 | 'extraTwo' => 'bar', 40 | ], $body->extra); 41 | 42 | // Assert array order does not matter. 43 | $body = new TelemetryBody(...[ 44 | 'message' => 'message', 45 | 'extraOne' => 'foo', 46 | 'stack' => 'stack', 47 | ]); 48 | 49 | self::assertSame('message', $body->message); 50 | self::assertSame('foo', $body->extra['extraOne']); 51 | self::assertSame('stack', $body->stack); 52 | } 53 | 54 | public function testSerialize(): void 55 | { 56 | $body = new TelemetryBody( 57 | message: 'message', 58 | method: 'method', 59 | url: 'url', 60 | status_code: 'status', 61 | subtype: 'sub', 62 | stack: 'stack', 63 | from: 'from', 64 | to: 'to', 65 | start_timestamp_ms: 42, 66 | end_timestamp_ms: 43, 67 | extraOne: 'foo', 68 | extraTwo: 'bar', 69 | ); 70 | 71 | self::assertSame([ 72 | 'message' => 'message', 73 | 'method' => 'method', 74 | 'url' => 'url', 75 | 'status_code' => 'status', 76 | 'subtype' => 'sub', 77 | 'stack' => 'stack', 78 | 'from' => 'from', 79 | 'to' => 'to', 80 | 'start_timestamp_ms' => 42, 81 | 'end_timestamp_ms' => 43, 82 | 'extraOne' => 'foo', 83 | 'extraTwo' => 'bar', 84 | ], $body->serialize()); 85 | } 86 | 87 | public function testEmptyProperties(): void 88 | { 89 | $body = new TelemetryBody(); 90 | self::assertEmpty($body->serialize()); 91 | } 92 | 93 | public function testExtraDoesNotOverrideProperty(): void 94 | { 95 | $body = new TelemetryBody(message: 'foo'); 96 | $body->extra['message'] = 'bar'; 97 | 98 | self::assertSame(['message' => 'foo'], $body->serialize()); 99 | } 100 | 101 | /** 102 | * @since 4.1.1 103 | */ 104 | public function testFromArray(): void 105 | { 106 | $body = TelemetryBody::fromArray([ 107 | 'message' => 'message', 108 | 'method' => 'method', 109 | 'url' => 'url', 110 | 'status_code' => 'status', 111 | 'subtype' => 'sub', 112 | 'stack' => 'stack', 113 | 'from' => 'from', 114 | 'to' => 'to', 115 | 'start_timestamp_ms' => 42, 116 | 'end_timestamp_ms' => 43, 117 | 'extraOne' => 'foo', 118 | 'extraTwo' => 'bar', 119 | ]); 120 | 121 | self::assertSame('message', $body->message); 122 | self::assertSame('method', $body->method); 123 | self::assertSame('url', $body->url); 124 | self::assertSame('status', $body->status_code); 125 | self::assertSame('sub', $body->subtype); 126 | self::assertSame('stack', $body->stack); 127 | self::assertSame('from', $body->from); 128 | self::assertSame('to', $body->to); 129 | self::assertSame(42, $body->start_timestamp_ms); 130 | self::assertSame(43, $body->end_timestamp_ms); 131 | self::assertSame([ 132 | 'extraOne' => 'foo', 133 | 'extraTwo' => 'bar', 134 | ], $body->extra); 135 | } 136 | 137 | /** 138 | * @since 4.1.1 139 | */ 140 | public function testFromArrayNested(): void 141 | { 142 | // Assert that nested/non-standard arrays don't throw an error. 143 | $body = TelemetryBody::fromArray([["data" => "some data"]]); 144 | self::assertSame([["data" => "some data"]], $body->extra); 145 | 146 | // Assert that positional arguments are not passed to named arguments on the constructor. 147 | $body = TelemetryBody::fromArray(["0" => ["id" => "some id"], 'message' => 'test message']); 148 | self::assertSame('test message', $body->message); 149 | self::assertSame(["0" => ["id" => "some id"]], $body->extra); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /tests/Payload/TelemetryEventTest.php: -------------------------------------------------------------------------------- 1 | 'foo']); 17 | $after = floor(microtime(true) * 1000); 18 | 19 | self::assertNotNull($event->timestamp); 20 | self::assertGreaterThanOrEqual($before, $event->timestamp); 21 | self::assertLessThanOrEqual($after, $event->timestamp); 22 | } 23 | 24 | /** 25 | * @since 4.1.1 26 | */ 27 | public function testNestedArrayBody(): void 28 | { 29 | $event = new TelemetryEvent(EventType::Network, EventLevel::Info, [ 30 | 'method' => 'GET', 31 | 'url' => 'https://example.com', 32 | 'status_code' => '200', 33 | [ 34 | 'unstructured' => 'data', 35 | 0 => 'foo', 36 | ], 37 | ]); 38 | 39 | self::assertSame('GET', $event->body->method); 40 | self::assertSame('https://example.com', $event->body->url); 41 | self::assertSame('200', $event->body->status_code); 42 | self::assertSame([ 43 | [ 44 | 'unstructured' => 'data', 45 | 0 => 'foo', 46 | ], 47 | ], $event->body->extra); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/PayloadTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('getAccessToken') 17 | ->andReturn('012345678901234567890123456789ab') 18 | ->mock(); 19 | 20 | $payload = new Payload($data, $config->getAccessToken()); 21 | 22 | $this->assertEquals($data, $payload->getData()); 23 | 24 | $data2 = m::mock(Data::class); 25 | $this->assertEquals($data2, $payload->setData($data2)->getData()); 26 | } 27 | 28 | public function testPayloadAccessToken(): void 29 | { 30 | $accessToken = "012345678901234567890123456789ab"; 31 | $data = m::mock(Data::class); 32 | $config = m::mock(Config::class) 33 | ->shouldReceive('getAccessToken') 34 | ->andReturn($accessToken) 35 | ->mock(); 36 | 37 | $payload = new Payload($data, $accessToken); 38 | $this->assertEquals($accessToken, $payload->getAccessToken()); 39 | 40 | $accessToken = "012345678901234567890123456789ab"; 41 | $config = m::mock(Config::class) 42 | ->shouldReceive('getAccessToken') 43 | ->andReturn($accessToken) 44 | ->mock(); 45 | $payload = new Payload($data, $accessToken); 46 | $this->assertEquals($accessToken, $payload->getAccessToken()); 47 | 48 | $at2 = "ab012345678901234567890123456789"; 49 | $this->assertEquals($at2, $payload->setAccessToken($at2)->getAccessToken()); 50 | } 51 | 52 | public function testEncode(): void 53 | { 54 | $accessToken = $this->getTestAccessToken(); 55 | $data = m::mock('Rollbar\Payload\Data, Rollbar\SerializerInterface') 56 | ->shouldReceive('serialize') 57 | ->andReturn(new \ArrayObject()) 58 | ->mock(); 59 | m::mock(DataBuilder::class) 60 | ->shouldReceive('getScrubFields') 61 | ->andReturn(array()) 62 | ->shouldReceive('scrub') 63 | ->andReturn(new \ArrayObject()) 64 | ->mock(); 65 | 66 | $payload = new Payload($data, $accessToken); 67 | $encoded = json_encode($payload->serialize()); 68 | $json = '{"data":{},"access_token":"'.$accessToken.'"}'; 69 | $this->assertEquals($json, $encoded); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Performance/MassivePayload.php: -------------------------------------------------------------------------------- 1 | generateStringData(); 16 | 17 | $data = $framesTest->executeProvider(); 18 | $data = $data['truncate middle using trace key'][0]; 19 | foreach ($data['data']['body']['trace']['frames'] as $i => $frame) { 20 | $data['data']['body']['trace']['frames'][$i] = $stringData; 21 | } 22 | 23 | return $data; 24 | } 25 | 26 | public function generateStringData(): array 27 | { 28 | $stringsTest = new StringsStrategyTest(); 29 | 30 | $data = $stringsTest->executeProvider(); 31 | 32 | $thresholds = StringsStrategy::getThresholds(); 33 | $biggestThreshold = $thresholds[0]; 34 | 35 | $data = $data['truncate strings to ' . $biggestThreshold][0]; 36 | 37 | return $data['data']['body']['message']['body']['value']; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Performance/TestHelpers/EncodedPayload.php: -------------------------------------------------------------------------------- 1 | strategiesUsed = array(); 15 | 16 | $this->payloadSize = $payload->size(); 17 | 18 | $memUsageBefore = memory_get_peak_usage(true); 19 | $timeBefore = microtime(true) * 1000; 20 | 21 | \Rollbar\Performance\TestHelpers\EncodedPayload::resetEncodingCount(); 22 | 23 | $result = parent::truncate($payload); 24 | 25 | $timeAfter = microtime(true) * 1000; 26 | $memUsageAfter = memory_get_peak_usage(true); 27 | 28 | $this->memoryUsage = $memUsageAfter - $memUsageBefore; 29 | $this->timeUsage = $timeAfter - $timeBefore; 30 | $this->strategiesUsed = array_unique($this->strategiesUsed); 31 | 32 | $this->lastRunOutput = $this->composeLastRunOutput(); 33 | 34 | return $result; 35 | } 36 | 37 | public function getLastRun(): string 38 | { 39 | return $this->lastRunOutput; 40 | } 41 | 42 | public function composeLastRunOutput(): string 43 | { 44 | $output = "\n"; 45 | 46 | $output .= "Payload size: " . 47 | $this->payloadSize . " bytes = " . 48 | round($this->payloadSize / 1024 / 1024, 2) . " MB \n"; 49 | 50 | $output .= "Strategies used: \n" . 51 | (count($this->strategiesUsed) ? implode(", \n", $this->strategiesUsed) : "none") . "\n"; 52 | 53 | $output .= "Encoding triggered: " . 54 | \Rollbar\Performance\TestHelpers\EncodedPayload::getEncodingCount() . "\n"; 55 | 56 | $output .= "Memory usage: " . 57 | $this->memoryUsage . " bytes = " . 58 | round($this->memoryUsage / 1024 / 1024, 2) . " MB\n"; 59 | 60 | $output .= "Execution time: " . $this->timeUsage . " ms\n"; 61 | 62 | return $output; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/PersonTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($id, $person->getId()); 13 | 14 | $id2 = "RollbarPHP"; 15 | $this->assertEquals($id2, $person->setId($id2)->getId()); 16 | } 17 | 18 | public function testUsername(): void 19 | { 20 | $username = "user@rollbar.com"; 21 | $person = new Person("15", $username); 22 | $this->assertEquals($username, $person->getUsername()); 23 | 24 | $username2 = "user-492"; 25 | $this->assertEquals($username2, $person->setUsername($username2)->getUsername()); 26 | } 27 | 28 | public function testEmail(): void 29 | { 30 | $email = "1.0.0"; 31 | $person = new Person("Rollbar_Master", null, $email); 32 | $this->assertEquals($email, $person->getEmail()); 33 | 34 | $email2 = "1.0.1"; 35 | $this->assertEquals($email2, $person->setEmail($email2)->getEmail()); 36 | } 37 | 38 | public function testExtra(): void 39 | { 40 | $person = new Person("42"); 41 | $person->test = "testing"; 42 | $this->assertEquals("testing", $person->test); 43 | } 44 | 45 | public function testExtraId(): void 46 | { 47 | $person = new Person('42', extra: ['id' => 'testing']); 48 | $this->assertEquals('42', $person->getId()); 49 | $this->assertEquals(array('id' => '42'), $person->serialize()); 50 | } 51 | 52 | public function testEncode(): void 53 | { 54 | $person = new Person("1024"); 55 | $person->setUsername("username") 56 | ->setEmail("user@gmail.com"); 57 | $person->Settings = array( 58 | "send_email" => true 59 | ); 60 | $encoded = json_encode($person->serialize()); 61 | $expected ='{"id":"1024","username":"username","email":"user@gmail.com","Settings":{"send_email":true}}'; 62 | $this->assertEquals($expected, $encoded); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/ReadmeTest.php: -------------------------------------------------------------------------------- 1 | $this->getTestAccessToken(), 22 | 'environment' => 'production' 23 | ) 24 | ); 25 | 26 | try { 27 | throw new \Exception('test exception'); 28 | } catch (\Exception $e) { 29 | Rollbar::log(Level::ERROR, $e); 30 | } 31 | 32 | // Message at level 'info' 33 | Rollbar::log(Level::INFO, 'testing info level'); 34 | 35 | // With extra data (3rd arg) and custom payload options (4th arg) 36 | Rollbar::log( 37 | Level::INFO, 38 | 'testing extra data', 39 | array("some_key" => "some value") // key-value additional data 40 | ); 41 | 42 | // If you want to check if logging with Rollbar was successful 43 | $response = Rollbar::report(LevelFactory::fromName(Level::INFO), 'testing wasSuccessful()'); 44 | if (!$response->wasSuccessful()) { 45 | throw new \Exception('logging with Rollbar failed'); 46 | } 47 | 48 | // raises an E_NOTICE which will *not* be reported by the error handler 49 | // $foo = $bar; 50 | 51 | // will be reported by the exception handler 52 | $this->expectException(\Exception::class); 53 | throw new \Exception('testing exception handler'); 54 | } 55 | 56 | public function testSetup1(): void 57 | { 58 | $config = array( 59 | // required 60 | 'access_token' => $this->getTestAccessToken(), 61 | // optional - environment name. any string will do. 62 | 'environment' => 'production', 63 | // optional - path to directory your code is in. used for linking stack traces. 64 | 'root' => '/Users/brian/www/myapp' 65 | ); 66 | Rollbar::init($config); 67 | 68 | $this->assertTrue(true); 69 | } 70 | 71 | public function testSetup2(): void 72 | { 73 | $config = array( 74 | // required 75 | 'access_token' => $this->getTestAccessToken(), 76 | // optional - environment name. any string will do. 77 | 'environment' => 'production', 78 | // optional - path to directory your code is in. used for linking stack traces. 79 | 'root' => '/Users/brian/www/myapp' 80 | ); 81 | 82 | $set_exception_handler = false; 83 | $set_error_handler = false; 84 | Rollbar::init($config, $set_exception_handler, $set_error_handler); 85 | 86 | $this->assertTrue(true); 87 | } 88 | 89 | public function testBasicUsage(): void 90 | { 91 | Rollbar::init( 92 | array( 93 | 'access_token' => $this->getTestAccessToken(), 94 | 'environment' => 'test' 95 | ) 96 | ); 97 | 98 | try { 99 | do_something(); 100 | } catch (\Exception $e) { 101 | $result1 = Rollbar::report(Level::ERROR, $e); 102 | // or 103 | $result2 = Rollbar::report(Level::ERROR, $e, array("my" => "extra", "data" => 42)); 104 | } 105 | 106 | $this->assertEquals(200, $result1->getStatus()); 107 | $this->assertEquals(200, $result2->getStatus()); 108 | } 109 | 110 | public function testBasicUsage2(): void 111 | { 112 | Rollbar::init( 113 | array( 114 | 'access_token' => $this->getTestAccessToken(), 115 | 'environment' => 'test' 116 | ) 117 | ); 118 | 119 | $result1 = Rollbar::report(Level::WARNING, 'could not connect to mysql server'); 120 | $result2 = Rollbar::report( 121 | Level::INFO, 122 | 'Here is a message with some additional data', 123 | array('x' => 10, 'code' => 'blue') 124 | ); 125 | 126 | $this->assertEquals(200, $result1->getStatus()); 127 | $this->assertEquals(200, $result2->getStatus()); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tests/ResponseTest.php: -------------------------------------------------------------------------------- 1 | 5)); 10 | $this->assertEquals(200, $r->getStatus()); 11 | } 12 | 13 | public function testInfo(): void 14 | { 15 | $r = new Response(200, "FAKE INFO"); 16 | $this->assertEquals("FAKE INFO", $r->getInfo()); 17 | } 18 | 19 | public function testUuid(): void 20 | { 21 | $r = new Response(200, "FAKE INFO", "abc123"); 22 | $this->assertEquals("abc123", $r->getUuid()); 23 | } 24 | 25 | public function testWasSuccessful(): void 26 | { 27 | $response = new Response(200, null); 28 | $this->assertTrue($response->wasSuccessful()); 29 | $response = new Response(199, null); 30 | $this->assertFalse($response->wasSuccessful()); 31 | $response = new Response(300, null); 32 | $this->assertFalse($response->wasSuccessful()); 33 | } 34 | 35 | /** 36 | * @testWith ["abc123", "https://rollbar.com/occurrence/uuid/?uuid=abc123"] 37 | * ["a bar", "https://rollbar.com/occurrence/uuid/?uuid=a+bar"] 38 | */ 39 | public function testUrl(string $uuid, string $expectedOccurrenceUrl): void 40 | { 41 | $response = new Response(200, "fake", $uuid); 42 | $this->assertEquals($expectedOccurrenceUrl, $response->getOccurrenceUrl()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/ServerTest.php: -------------------------------------------------------------------------------- 1 | setHost($val); 12 | $this->assertEquals($val, $server->getHost()); 13 | 14 | $val2 = "TEST2"; 15 | $this->assertEquals($val2, $server->setHost($val2)->getHost()); 16 | } 17 | 18 | public function testRoot(): void 19 | { 20 | $val = "TEST"; 21 | $server = new Server(); 22 | $server->setRoot($val); 23 | $this->assertEquals($val, $server->getRoot()); 24 | 25 | $val2 = "TEST2"; 26 | $this->assertEquals($val2, $server->setRoot($val2)->getRoot()); 27 | } 28 | 29 | public function testBranch(): void 30 | { 31 | $val = "TEST"; 32 | $server = new Server(); 33 | $server->setBranch($val); 34 | $this->assertEquals($val, $server->getBranch()); 35 | 36 | $val2 = "TEST2"; 37 | $this->assertEquals($val2, $server->setBranch($val2)->getBranch()); 38 | } 39 | 40 | public function testCodeVersion(): void 41 | { 42 | $val = "TEST"; 43 | $server = new Server(); 44 | $server->setCodeVersion($val); 45 | $this->assertEquals($val, $server->getCodeVersion()); 46 | 47 | $val2 = "TEST2"; 48 | $this->assertEquals($val2, $server->setCodeVersion($val2)->getCodeVersion()); 49 | } 50 | 51 | public function testExtra(): void 52 | { 53 | $server = new Server(); 54 | $server->setExtras(array("test" => "testing")); 55 | $extras = $server->getExtras(); 56 | $this->assertEquals("testing", $extras["test"]); 57 | } 58 | 59 | public function testEncode(): void 60 | { 61 | $server = new Server(); 62 | $server->setHost("server2-ec-us") 63 | ->setRoot("/home/app/testingRollbar") 64 | ->setBranch("master") 65 | ->setCodeVersion("#dca015"); 66 | $extras = array("test" => array(1, 2, "3", array())); 67 | $server->setExtras($extras); 68 | $expected = '{' . 69 | '"host":"server2-ec-us",' . 70 | '"root":"\\/home\\/app\\/testingRollbar",' . 71 | '"branch":"master",' . 72 | '"code_version":"#dca015",' . 73 | '"test":' . 74 | '[' . 75 | '1,' . 76 | '2,' . 77 | '"3",' . 78 | '[]' . 79 | ']' . 80 | '}'; 81 | $this->assertEquals($expected, json_encode($server->serialize())); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/TestHelpers/ArrayLogger.php: -------------------------------------------------------------------------------- 1 | logs[] = self::stringify($level, $message); 36 | } 37 | 38 | /** 39 | * Returns the number of instances of a log in the logs. 40 | * 41 | * @param $level 42 | * @param Stringable|string $message 43 | * 44 | * @return int 45 | */ 46 | public function count($level, Stringable|string $message): int 47 | { 48 | $count = 0; 49 | $str = self::stringify($level, $message); 50 | foreach ($this->logs as $log) { 51 | if ($str === $log) { 52 | $count++; 53 | } 54 | } 55 | return $count; 56 | } 57 | 58 | /** 59 | * Returns the index of the first instance of a matching log in the logs. Returns -1 if no matching log is found. 60 | * 61 | * @param string $level The log level of the message to search for. 62 | * @param Stringable|string $message The message body to search for. 63 | * 64 | * @return int 65 | */ 66 | public function indexOf(string $level, Stringable|string $message): int 67 | { 68 | $str = self::stringify($level, $message); 69 | foreach ($this->logs as $index => $log) { 70 | if ($str === $log) { 71 | return $index; 72 | } 73 | } 74 | return -1; 75 | } 76 | 77 | /** 78 | * Returns the index of the first instance of a matching level and message pattern in the logs. Returns -1 if no 79 | * matching log is found. 80 | * 81 | * @param string $level The log level of the message to search for. You can pass '.+' to match any level. 82 | * @param string $pattern The regex pattern to search for. 83 | * 84 | * @return int 85 | */ 86 | public function indexOfRegex(string $level, string $pattern): int 87 | { 88 | $pattern = '/\[' . $level . '\].*' . $pattern . '/'; 89 | foreach ($this->logs as $index => $log) { 90 | if (preg_match($pattern, $log)) { 91 | return $index; 92 | } 93 | } 94 | return -1; 95 | } 96 | 97 | /** 98 | * Checks the log at the given index to see if it matches the given level and message pattern. Returns true if it 99 | * matches, false otherwise. 100 | * 101 | * @param int $index The index of the log to check. 102 | * @param string $level The log level of the message to search for. You can pass '.+' to match any level. 103 | * @param string $pattern The regex pattern to search for. 104 | * 105 | * @return bool 106 | */ 107 | public function indexMatchesRegex(int $index, string $level, string $pattern): bool 108 | { 109 | if ($index < 0 || $index >= count($this->logs)) { 110 | return false; 111 | } 112 | $log = $this->logs[$index]; 113 | $pattern = '/\[' . $level . '\].*' . $pattern . '/'; 114 | if (preg_match($pattern, $log)) { 115 | return true; 116 | } 117 | return false; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tests/TestHelpers/CustomSerializable.php: -------------------------------------------------------------------------------- 1 | data = $data; 12 | } 13 | 14 | public function serialize(): array 15 | { 16 | throw new \Exception("Not implemented"); 17 | } 18 | 19 | public function unserialize(string $data): void 20 | { 21 | throw new \Exception("Not implemented"); 22 | } 23 | 24 | public function __serialize(): array 25 | { 26 | return $this->data; 27 | } 28 | 29 | public function __unserialize(array $data): void 30 | { 31 | throw new \Exception("Not implemented"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/TestHelpers/CustomTruncation.php: -------------------------------------------------------------------------------- 1 | encode(array( 13 | "data" => array( 14 | "body" => array( 15 | "message" => array( 16 | "body" => array( 17 | "value" => 'Custom truncation test string' 18 | ) 19 | ) 20 | ) 21 | ) 22 | )); 23 | 24 | return $payload; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/TestHelpers/CycleCheck/ChildCycleCheck.php: -------------------------------------------------------------------------------- 1 | parent = $parent; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/TestHelpers/CycleCheck/ChildCycleCheckSerializable.php: -------------------------------------------------------------------------------- 1 | parent = $parent; 12 | } 13 | 14 | public function serialize(): array 15 | { 16 | return array( 17 | "parent" => \Rollbar\Utilities::serializeForRollbarInternal($this->parent) 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/TestHelpers/CycleCheck/ParentCycleCheck.php: -------------------------------------------------------------------------------- 1 | child = new ChildCycleCheck($this); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/TestHelpers/CycleCheck/ParentCycleCheckSerializable.php: -------------------------------------------------------------------------------- 1 | child = new ChildCycleCheck($this); 12 | } 13 | 14 | public function serialize(): array 15 | { 16 | return array( 17 | "child" => \Rollbar\Utilities::serializeForRollbarInternal($this->child) 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/TestHelpers/DeprecatedSerializable.php: -------------------------------------------------------------------------------- 1 | data = $data; 10 | } 11 | 12 | public function serialize() 13 | { 14 | return $this->data; 15 | } 16 | 17 | public function unserialize(string $data) 18 | { 19 | throw new \Exception("Not implemented"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/TestHelpers/Exceptions/FiftyFiftyExceptionSampleRate.php: -------------------------------------------------------------------------------- 1 | makePartial(); 17 | $mock->shouldReceive("serialize")->andReturn(array()); 18 | $mock->setLevel(\Rollbar\LevelFactory::fromName($level)); 19 | $payload->setData($mock); 20 | return $payload; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/TestHelpers/MockPhpStream.php: -------------------------------------------------------------------------------- 1 | length = strlen(self::$data); 34 | } 35 | $length = min($count, self::$length - self::$index); 36 | $data = substr(self::$data, self::$index); 37 | self::$index = self::$index + $length; 38 | return $data; 39 | } 40 | 41 | public function stream_eof(): bool 42 | { 43 | return (self::$index >= self::$length ? true : false); 44 | } 45 | 46 | public function stream_write($data): ?int 47 | { 48 | self::$data = $data; 49 | self::$length = strlen(self::$data); 50 | self::$index = 0; 51 | return self::$length; 52 | } 53 | 54 | // @codingStandardsIgnoreEnd 55 | } 56 | -------------------------------------------------------------------------------- /tests/TestHelpers/StdOutLogger.php: -------------------------------------------------------------------------------- 1 | config = $config; 25 | } 26 | 27 | /** 28 | * @inheritDoc 29 | */ 30 | public function include(TelemetryEvent $event, int $queueSize): bool 31 | { 32 | return $this->includeFunction?->call($this, $event, $queueSize) ?? false; 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | public function includeRollbarItem( 39 | string $level, 40 | Stringable|string $message, 41 | array $context = [], 42 | bool $ignored = false, 43 | ): bool { 44 | return $this->includeRollbarItemFunction?->call($this, $level, $message, $context, $ignored) ?? false; 45 | } 46 | 47 | /** 48 | * @inheritDoc 49 | */ 50 | public function filterOnRead(): bool 51 | { 52 | return $this->filterOnRead; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/TraceChainTest.php: -------------------------------------------------------------------------------- 1 | trace1 = m::mock(Trace::class); 17 | $this->trace2 = m::mock(Trace::class); 18 | } 19 | 20 | public function testTraces(): void 21 | { 22 | $chain = array($this->trace1); 23 | $traceChain = new TraceChain($chain); 24 | $this->assertEquals($chain, $traceChain->getTraces()); 25 | 26 | $traceChain = new TraceChain($chain); 27 | $chain = array($this->trace1, $this->trace2); 28 | $traceChain->setTraces($chain); 29 | $this->assertEquals($chain, $traceChain->getTraces()); 30 | } 31 | 32 | public function testKey(): void 33 | { 34 | $chain = new TraceChain(array($this->trace1)); 35 | $this->assertEquals("trace_chain", $chain->getKey()); 36 | } 37 | 38 | public function testEncode(): void 39 | { 40 | $trace1 = m::mock(Trace::class) 41 | ->shouldReceive("serialize") 42 | ->andReturn("TRACE1") 43 | ->mock(); 44 | $trace2 = m::mock(Trace::class) 45 | ->shouldReceive("serialize") 46 | ->andReturn("TRACE2") 47 | ->mock(); 48 | $chain = new TraceChain(array($trace1, $trace2)); 49 | $this->assertEquals('["TRACE1","TRACE2"]', json_encode($chain->serialize())); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/TraceTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(array(), $trace->getFrames()); 18 | $this->assertEquals($exc, $trace->getException()); 19 | 20 | $trace = new Trace($frames, $exc); 21 | $this->assertEquals($frames, $trace->getFrames()); 22 | $this->assertEquals($exc, $trace->getException()); 23 | } 24 | 25 | public function testFrames(): void 26 | { 27 | $frames = array( 28 | new Frame("one.php"), 29 | new Frame("two.php") 30 | ); 31 | $exc = m::mock(ExceptionInfo::class); 32 | $trace = new Trace(array(), $exc); 33 | $this->assertEquals($frames, $trace->setFrames($frames)->getFrames()); 34 | } 35 | 36 | public function testException(): void 37 | { 38 | $exc = m::mock(ExceptionInfo::class); 39 | $trace = new Trace(array(), $exc); 40 | $this->assertEquals($exc, $trace->getException()); 41 | 42 | $exc2 = m::mock(ExceptionInfo::class); 43 | $this->assertEquals($exc2, $trace->setException($exc2)->getException()); 44 | } 45 | 46 | public function testEncode(): void 47 | { 48 | $value = m::mock("Rollbar\Payload\ExceptionInfo, Rollbar\SerializerInterface") 49 | ->shouldReceive("serialize") 50 | ->andReturn("{EXCEPTION}") 51 | ->mock(); 52 | $trace = new Trace(array(), $value); 53 | $encoded = json_encode($trace->serialize()); 54 | $this->assertEquals("{\"frames\":[],\"exception\":\"{EXCEPTION}\"}", $encoded); 55 | } 56 | 57 | public function testTraceKey(): void 58 | { 59 | $trace = new Trace(array(), m::mock(ExceptionInfo::class)); 60 | $this->assertEquals("trace", $trace->getKey()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Truncation/FramesStrategyTest.php: -------------------------------------------------------------------------------- 1 | $this->getTestAccessToken())); 17 | $truncation = new Truncation($config); 18 | 19 | $strategy = new FramesStrategy($truncation); 20 | 21 | $data = new EncodedPayload($data); 22 | $data->encode(); 23 | 24 | $result = $strategy->execute($data); 25 | 26 | $this->assertEquals($expected, $result->data()); 27 | } 28 | 29 | 30 | /** 31 | * Also used by {@see TruncationTest::truncateProvider()}. 32 | * 33 | * @return array 34 | */ 35 | public static function executeProvider(): array 36 | { 37 | $data = array( 38 | 'nothing to truncate using trace key' => array( 39 | array('data' => array('body' => 40 | array('trace' => array('frames' => range(1, 6))) 41 | )), 42 | array('data' => array('body' => 43 | array('trace' => array('frames' => range(1, 6))) 44 | )) 45 | ), 46 | 'nothing to truncate using trace_chain key' => array( 47 | array('data' => array('body' => 48 | array('trace_chain' => array(array('frames' => range(1, 6)))) 49 | )), 50 | array('data' => array('body' => 51 | array('trace_chain' => array(array('frames' => range(1, 6)))) 52 | )) 53 | ), 54 | 'truncate middle using trace key' => array( 55 | array('data' => array('body' => 56 | array( 57 | 'trace' => array( 58 | 'frames' => range( 59 | 1, 60 | FramesStrategy::FRAMES_OPTIMIZATION_RANGE * 2 + 1 61 | ) 62 | ) 63 | ) 64 | )), 65 | array('data' => array('body' => 66 | array( 67 | 'trace' => array( 68 | 'frames' => array_merge( 69 | range(1, FramesStrategy::FRAMES_OPTIMIZATION_RANGE), 70 | range( 71 | FramesStrategy::FRAMES_OPTIMIZATION_RANGE + 2, 72 | FramesStrategy::FRAMES_OPTIMIZATION_RANGE * 2 + 1 73 | ) 74 | ) 75 | ) 76 | ) 77 | )) 78 | 79 | ), 80 | 'truncate middle using trace_chain key' => array( 81 | array('data' => array('body' => 82 | array( 83 | 'trace_chain' => array( 84 | array( 85 | 'frames' => range( 86 | 1, 87 | FramesStrategy::FRAMES_OPTIMIZATION_RANGE * 2 + 1 88 | ) 89 | ) 90 | ) 91 | ) 92 | )), 93 | array('data' => array('body' => 94 | array( 95 | 'trace_chain' => array( 96 | array( 97 | 'frames' => array_merge( 98 | range(1, FramesStrategy::FRAMES_OPTIMIZATION_RANGE), 99 | range( 100 | FramesStrategy::FRAMES_OPTIMIZATION_RANGE + 2, 101 | FramesStrategy::FRAMES_OPTIMIZATION_RANGE * 2 + 1 102 | ) 103 | ) 104 | ) 105 | ) 106 | ) 107 | )) 108 | 109 | ) 110 | ); 111 | 112 | return $data; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/Truncation/MinBodyStrategyTest.php: -------------------------------------------------------------------------------- 1 | $this->getTestAccessToken())); 18 | $truncation = new Truncation($config); 19 | 20 | $strategy = new MinBodyStrategy($truncation); 21 | 22 | $data = new EncodedPayload($data); 23 | $data->encode(); 24 | 25 | $result = $strategy->execute($data); 26 | 27 | $this->assertEquals($expected, $result->data()); 28 | } 29 | 30 | public static function executeProvider(): array 31 | { 32 | $data = array(); 33 | 34 | $traceData = array( 35 | 'exception' => array( 36 | 'description' => 'Test description', 37 | 'message' => str_repeat( 38 | 'A', 39 | MinBodyStrategy::EXCEPTION_MESSAGE_LIMIT+1 40 | ) 41 | ), 42 | 'frames' => array('Frame 1', 'Frame 2', 'Frame 3') 43 | ); 44 | 45 | $expected = $traceData; 46 | unset($expected['exception']['description']); 47 | $expected['exception']['message'] = str_repeat( 48 | 'A', 49 | MinBodyStrategy::EXCEPTION_MESSAGE_LIMIT 50 | ); 51 | $expected['frames'] = array('Frame 1', 'Frame 3'); 52 | 53 | 54 | $data['trace data set'] = array( 55 | self::payloadStructureProvider(array('trace' => $traceData)), 56 | self::payloadStructureProvider(array('trace' => $expected)) 57 | ); 58 | 59 | $data['trace_chain data set'] = array( 60 | self::payloadStructureProvider(array('trace_chain' => $traceData)), 61 | self::payloadStructureProvider(array('trace_chain' => $expected)) 62 | ); 63 | return $data; 64 | } 65 | 66 | protected static function payloadStructureProvider($body): array 67 | { 68 | return array( 69 | "data" => array( 70 | "body" => $body 71 | ) 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Truncation/RawStrategyTest.php: -------------------------------------------------------------------------------- 1 | 'test data'); 14 | 15 | $config = new Config(array('access_token' => $this->getTestAccessToken())); 16 | $truncation = new Truncation($config); 17 | 18 | $strategy = new RawStrategy($truncation); 19 | 20 | $data = new EncodedPayload($payload); 21 | $data->encode(); 22 | 23 | $result = $strategy->execute($data); 24 | 25 | $this->assertEquals( 26 | strlen(json_encode($payload)), 27 | $result->size() 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Truncation/StringsStrategyTest.php: -------------------------------------------------------------------------------- 1 | $this->getTestAccessToken())); 14 | $truncation = new Truncation($config); 15 | 16 | $strategy = new StringsStrategy($truncation); 17 | 18 | $data = new EncodedPayload($data); 19 | $data->encode(); 20 | 21 | return $strategy->execute($data)->data(); 22 | } 23 | 24 | /** 25 | * @dataProvider executeTruncateNothingProvider 26 | */ 27 | public function testExecuteTruncateNothing($data, $expected): void 28 | { 29 | $result = $this->execute($data); 30 | $this->assertEquals($expected, $result); 31 | } 32 | 33 | /** 34 | * Also used by {@see TruncationTest::truncateProvider()}. 35 | * 36 | * @return array 37 | */ 38 | public static function executeTruncateNothingProvider(): array 39 | { 40 | $data = array(); 41 | 42 | $data["truncate nothing"] = array( 43 | self::payloadStructureProvider(str_repeat("A", 10)), 44 | self::payloadStructureProvider(str_repeat("A", 10)) 45 | ); 46 | 47 | return $data; 48 | } 49 | 50 | /** 51 | * @dataProvider executeArrayProvider 52 | */ 53 | public function testExecuteArray($data, $expectedThreshold): void 54 | { 55 | $result = $this->execute($data); 56 | 57 | foreach ($result['data']['body']['message']['body']['value'] as $key => $toTrim) { 58 | $this->assertTrue( 59 | strlen($toTrim) <= $expectedThreshold, 60 | "The string '$toTrim' was expected to be trimmed to " . $expectedThreshold . " characters. " . 61 | "Actual length: " . strlen($toTrim) 62 | ); 63 | } 64 | } 65 | 66 | /** 67 | * Also used by {@see TruncationTest::truncateProvider()}. 68 | * 69 | * @return array 70 | */ 71 | public static function executeArrayProvider(): array 72 | { 73 | $data = array(); 74 | 75 | $thresholds = StringsStrategy::getThresholds(); 76 | foreach ($thresholds as $threshold) { 77 | $data['truncate strings to ' . $threshold] = self::thresholdTestProvider($threshold); 78 | } 79 | 80 | return $data; 81 | } 82 | 83 | public static function thresholdTestProvider($threshold): array 84 | { 85 | $stringLengthToTrim = $threshold*2; 86 | 87 | $payload = self::payloadStructureProvider(array()); 88 | $payload['data']['body']['message']['body']['value2'] = array(); 89 | 90 | while (strlen(json_encode($payload)) <= Truncation::MAX_PAYLOAD_SIZE) { 91 | $payload['data']['body']['message']['body']['value'] []= 92 | str_repeat('A', $stringLengthToTrim); 93 | $payload['data']['body']['message']['body']['value2'] []= 94 | str_repeat('A', $stringLengthToTrim); 95 | } 96 | 97 | return array($payload, $threshold); 98 | } 99 | 100 | public function executeProvider(): array 101 | { 102 | $data = array(); 103 | 104 | $data["truncate nothing"] = array( 105 | self::payloadStructureProvider(str_repeat("A", 10)), 106 | self::payloadStructureProvider(str_repeat("A", 10)) 107 | ); 108 | 109 | $thresholds = StringsStrategy::getThresholds(); 110 | foreach ($thresholds as $threshold) { 111 | $data['truncate strings to ' . $threshold] = self::thresholdTestProvider($threshold); 112 | } 113 | 114 | return $data; 115 | } 116 | 117 | public static function payloadStructureProvider($message): array 118 | { 119 | return array( 120 | "data" => array( 121 | "body" => array( 122 | "message" => array( 123 | "body" => array( 124 | "value" => $message 125 | ) 126 | ) 127 | ) 128 | ) 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tests/Truncation/TelemetryStrategyTest.php: -------------------------------------------------------------------------------- 1 | $this->getTestAccessToken(), 17 | 'environment' => 'test', 18 | ]); 19 | } 20 | 21 | /** 22 | * @dataProvider executeProvider 23 | */ 24 | public function testExecute(array $data, array $expected): void 25 | { 26 | $config = new Config(['access_token' => $this->getTestAccessToken()]); 27 | $truncation = new Truncation($config); 28 | 29 | $strategy = new TelemetryStrategy($truncation); 30 | 31 | $data = new EncodedPayload($data); 32 | $data->encode(); 33 | 34 | $result = $strategy->execute($data); 35 | 36 | $this->assertEquals($expected, $result->data()); 37 | } 38 | 39 | /** 40 | * @return array 41 | */ 42 | public static function executeProvider(): array 43 | { 44 | return [ 45 | 'nothing to truncate: no telemetry data' => [ 46 | [ 47 | 'data' => [ 48 | 'body' => [], 49 | ], 50 | ], 51 | [ 52 | 'data' => [ 53 | 'body' => [], 54 | ], 55 | ], 56 | ], 57 | 'nothing to truncate: telemetry in range' => [ 58 | [ 59 | 'data' => [ 60 | 'body' => [ 61 | 'telemetry' => range(1, 6), 62 | ], 63 | ], 64 | ], 65 | [ 66 | 'data' => [ 67 | 'body' => [ 68 | 'telemetry' => range(1, 6), 69 | ], 70 | ], 71 | ], 72 | ], 73 | 'truncate middle: telemetry too long' => [ 74 | [ 75 | 'data' => [ 76 | 'body' => [ 77 | 'telemetry' => range(1, TelemetryStrategy::TELEMETRY_OPTIMIZATION_RANGE * 2 + 1), 78 | ], 79 | ], 80 | ], 81 | [ 82 | 'data' => [ 83 | 'body' => [ 84 | 'telemetry' => array_merge( 85 | range(1, TelemetryStrategy::TELEMETRY_OPTIMIZATION_RANGE), 86 | range( 87 | TelemetryStrategy::TELEMETRY_OPTIMIZATION_RANGE + 2, 88 | TelemetryStrategy::TELEMETRY_OPTIMIZATION_RANGE * 2 + 1 89 | ), 90 | ), 91 | ], 92 | ], 93 | ], 94 | ], 95 | ]; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/Truncation/TruncationTest.php: -------------------------------------------------------------------------------- 1 | $this->getTestAccessToken())); 17 | $this->truncate = new Truncation($config); 18 | } 19 | 20 | public function testCustomTruncation(): void 21 | { 22 | $config = new Config(array( 23 | 'access_token' => $this->getTestAccessToken(), 24 | 'custom_truncation' => CustomTruncation::class 25 | )); 26 | $this->truncate = new Truncation($config); 27 | 28 | $data = new EncodedPayload(array( 29 | "data" => array( 30 | "body" => array( 31 | "message" => array( 32 | "body" => array( 33 | "value" => str_repeat('A', 1000 * 1000) 34 | ) 35 | ) 36 | ) 37 | ) 38 | )); 39 | $data->encode(); 40 | 41 | $result = $this->truncate->truncate($data); 42 | 43 | $this->assertStringContainsString('Custom truncation test string', $result); 44 | } 45 | 46 | /** 47 | * @dataProvider truncateProvider 48 | */ 49 | public function testTruncateNoPerformance($data): void 50 | { 51 | $data = new EncodedPayload($data); 52 | $data->encode(); 53 | 54 | $result = $this->truncate->truncate($data); 55 | 56 | $size = strlen(json_encode($result)); 57 | 58 | $this->assertTrue( 59 | $size <= Truncation::MAX_PAYLOAD_SIZE, 60 | "Truncation failed. Payload size exceeds MAX_PAYLOAD_SIZE." 61 | ); 62 | } 63 | 64 | public static function truncateProvider(): array 65 | { 66 | $stringsTest = new StringsStrategyTest("StringsStrategyTest"); 67 | $framesTest = new FramesStrategyTest("FramesStrategyTest"); 68 | 69 | $framesTestData = $framesTest::executeProvider(); 70 | 71 | // Fill up frames with data to go over the allowed payload size limit 72 | $frames = &$framesTestData['truncate middle using trace key'][0]['data']['body']['trace']['frames']; 73 | $stringValue = str_repeat('A', 1024 * 10); 74 | foreach ($frames as $key => $data) { 75 | $frames[$key] = $stringValue; 76 | } 77 | 78 | $frames_body = &$framesTestData['truncate middle using trace_chain key'][0]['data']['body']; 79 | $frames = $frames_body['trace_chain'][0]['frames']; 80 | foreach ($frames as $key => $data) { 81 | $frames[$key] = $stringValue; 82 | } 83 | 84 | $data = array_merge( 85 | $stringsTest::executeTruncateNothingProvider(), 86 | $stringsTest::executeArrayProvider(), 87 | $framesTestData 88 | ); 89 | 90 | return $data; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |