[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_size = 4\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.{yml,yaml}]\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "/doc export-ignore\n/tests export-ignore\n/.* export-ignore\n/phpstan* export-ignore\n/phpunit.xml.dist export-ignore\n/_config.yml export-ignore\n/UPGRADE.md export-ignore\n/.editorconfig export-ignore\n/favicon.ico export-ignore\n/logo.jpg export-ignore\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [Seldaek]\ntidelift: \"packagist/monolog/monolog\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Bug_Report.md",
    "content": "---\nname: Bug Report\nabout: Create a bug report\nlabels: Bug\n---\n\nMonolog version 1|2|3?\n\nWrite your bug report here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Feature.md",
    "content": "---\nname: Feature\nabout: Suggest a new feature or enhancement\nlabels: Feature\n---\n\nWrite your suggestion here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Question.md",
    "content": "---\nname: Question\nabout: Ask a question regarding software usage\nlabels: Support\n---\n\nMonolog version 1|2|3?\n\nWrite your question here.\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Reporting a vulnerability\n\nIf you have found any issues that might have security implications,\nplease send a report privately to j.boggiano@seld.be\n\nDo not report security reports publicly.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\n\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n"
  },
  {
    "path": ".github/workflows/continuous-integration.yml",
    "content": "name: \"Continuous Integration\"\n\non:\n  - push\n  - pull_request\n\npermissions:\n  contents: read\n\nenv:\n  COMPOSER_ROOT_VERSION: dev-main\n\njobs:\n  tests:\n    name: \"CI (PHP ${{ matrix.php-version }}, ${{ matrix.dependencies }} deps)\"\n\n    runs-on: \"${{ matrix.operating-system }}\"\n\n    strategy:\n      fail-fast: false\n\n      matrix:\n        php-version:\n          - \"8.1\"\n          - \"8.2\"\n          - \"8.3\"\n          - \"8.4\"\n          - \"8.5\"\n\n        dependencies: [highest]\n\n        composer-options: [\"\"]\n\n        operating-system:\n          - \"ubuntu-latest\"\n\n        include:\n          - php-version: \"8.1\"\n            dependencies: lowest\n            operating-system: ubuntu-latest\n          - php-version: \"8.5\"\n            dependencies: highest\n            operating-system: ubuntu-latest\n            composer-options: \"--ignore-platform-req=php+\"\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Run CouchDB\n        timeout-minutes: 3\n        continue-on-error: true\n        uses: cobot/couchdb-action@eaaa17cf8baf421e7fb07e2d869f5457bb6a4de5 # main\n        with:\n          couchdb version: '2.3.1'\n\n      - name: Run MongoDB\n        uses: supercharge/mongodb-github-action@315db7fe45ac2880b7758f1933e6e5d59afd5e94 # 1.12.1\n        with:\n          mongodb-version: 5.0\n\n      - uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0\n        with:\n          coverage: \"none\"\n          php-version: \"${{ matrix.php-version }}\"\n          extensions: mongodb, redis, amqp\n          tools: \"composer:v2\"\n          ini-values: \"memory_limit=-1\"\n\n      - name: Add require for mongodb/mongodb to make tests runnable\n        run: 'composer require mongodb/mongodb --dev --no-update'\n\n      - name: \"Change dependencies\"\n        run: |\n          composer require --no-update --no-interaction --dev elasticsearch/elasticsearch:^7\n\n      - uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1\n        with:\n          dependency-versions: \"${{ matrix.dependencies }}\"\n          composer-options: \"${{ matrix.composer-options }}\"\n\n      - name: \"Run tests\"\n        if: \"matrix.php-version >= '8.2'\"\n        run: \"composer exec phpunit -- --exclude-group Elasticsearch --exclude-group Elastica\"\n\n      - name: \"Run tests\"\n        if: \"matrix.php-version == '8.1'\"\n        run: \"composer exec phpunit -- --exclude-group Elasticsearch,Elastica\"\n\n  tests-es-7:\n    name: \"CI with ES ${{ matrix.es-version }} on PHP ${{ matrix.php-version }}\"\n\n    needs: \"tests\"\n\n    runs-on: \"${{ matrix.operating-system }}\"\n\n    strategy:\n      fail-fast: false\n\n      matrix:\n        operating-system:\n          - \"ubuntu-latest\"\n\n        php-version:\n          - \"8.1\"\n\n        dependencies:\n          - \"highest\"\n          - \"lowest\"\n\n        es-version:\n          - \"7.0.0\"\n          - \"7.17.0\"\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      # required for elasticsearch\n      - name: Configure sysctl limits\n        run: |\n          sudo swapoff -a\n          sudo sysctl -w vm.swappiness=1\n          sudo sysctl -w fs.file-max=262144\n          sudo sysctl -w vm.max_map_count=262144\n\n      - name: Run Elasticsearch\n        timeout-minutes: 3\n        uses: elastic/elastic-github-actions/elasticsearch@dc110609b1cb3024477ead739ca23ab547b8b9ff # master\n        with:\n          stack-version: \"${{ matrix.es-version }}\"\n\n      - uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0\n        with:\n          coverage: \"none\"\n          php-version: \"${{ matrix.php-version }}\"\n          extensions: mongodb, redis, amqp\n          tools: \"composer:v2\"\n          ini-values: \"memory_limit=-1\"\n\n      - name: \"Change dependencies\"\n        run: \"composer require --no-update --no-interaction --dev elasticsearch/elasticsearch:^${{ matrix.es-version }}\"\n\n      - uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1\n        with:\n          dependency-versions: \"${{ matrix.dependencies }}\"\n\n      - name: \"Run tests\"\n        run: \"composer exec phpunit -- --group Elasticsearch,Elastica\"\n\n      - name: \"Run tests with psr/log 3\"\n        if: \"contains(matrix.dependencies, 'highest') && matrix.php-version >= '8.0'\"\n        run: |\n          composer remove --no-update --dev graylog2/gelf-php ruflin/elastica elasticsearch/elasticsearch rollbar/rollbar\n          composer require --no-update --no-interaction --dev ruflin/elastica:^7 elasticsearch/elasticsearch:^7\n          composer require --no-update psr/log:^3\n          composer update\n          composer exec phpunit -- --group Elasticsearch,Elastica\n\n  tests-es-8:\n    name: \"CI with ES ${{ matrix.es-version }} on PHP ${{ matrix.php-version }}\"\n\n    needs: \"tests\"\n\n    runs-on: \"${{ matrix.operating-system }}\"\n\n    strategy:\n      fail-fast: false\n\n      matrix:\n        operating-system:\n          - \"ubuntu-latest\"\n\n        php-version:\n          - \"8.1\"\n\n        dependencies:\n          - \"highest\"\n          - \"lowest\"\n\n        es-version:\n          - \"8.0.0\"\n          - \"8.2.0\"\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      # required for elasticsearch\n      - name: Configure sysctl limits\n        run: |\n          sudo swapoff -a\n          sudo sysctl -w vm.swappiness=1\n          sudo sysctl -w fs.file-max=262144\n          sudo sysctl -w vm.max_map_count=262144\n\n      - name: Run Elasticsearch\n        timeout-minutes: 3\n        uses: elastic/elastic-github-actions/elasticsearch@dc110609b1cb3024477ead739ca23ab547b8b9ff # master\n        with:\n          stack-version: \"${{ matrix.es-version }}\"\n\n      - uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0\n        with:\n          coverage: \"none\"\n          php-version: \"${{ matrix.php-version }}\"\n          extensions: mongodb, redis, amqp\n          tools: \"composer:v2\"\n          ini-values: \"memory_limit=-1\"\n\n      - name: \"Change dependencies\"\n        run: |\n          composer remove --no-update --dev graylog2/gelf-php ruflin/elastica elasticsearch/elasticsearch rollbar/rollbar\n          composer require --no-update --no-interaction --dev elasticsearch/elasticsearch:^8 ruflin/elastica:^8\n\n      - uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1\n        with:\n          dependency-versions: \"${{ matrix.dependencies }}\"\n\n      - name: \"Run tests\"\n        run: \"composer exec phpunit -- --group Elasticsearch,Elastica\"\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: \"PHP Lint\"\n\non:\n  - push\n  - pull_request\n\npermissions:\n  contents: read\n\njobs:\n  tests:\n    name: \"Lint\"\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        php-version:\n          - \"8.1\"\n          - \"nightly\"\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0\n        with:\n          php-version: \"${{ matrix.php-version }}\"\n          coverage: none\n\n      - name: \"Lint PHP files\"\n        run: |\n          hasErrors=0\n          for f in $(find src/ tests/ -type f -name '*.php' ! -path '*/vendor/*' ! -path '*/Fixtures/*')\n          do\n            { error=\"$(php -derror_reporting=-1 -ddisplay_errors=1 -l -f $f 2>&1 1>&3 3>&-)\"; } 3>&1;\n            if [ \"$error\" != \"\" ]; then\n              while IFS= read -r line; do echo \"::error file=$f::$line\"; done <<< \"$error\"\n              hasErrors=1\n            fi\n          done\n          if [ $hasErrors -eq 1 ]; then\n            exit 1\n          fi\n"
  },
  {
    "path": ".github/workflows/phpstan.yml",
    "content": "name: \"PHPStan\"\n\non:\n  - push\n  - pull_request\n\npermissions:\n  contents: read\n\nenv:\n  COMPOSER_ROOT_VERSION: dev-main\n\njobs:\n  tests:\n    name: \"PHPStan\"\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        php-version:\n          - \"8.1\"\n          - latest\n\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0\n        with:\n          php-version: \"${{ matrix.php-version }}\"\n          coverage: none\n          extensions: mongodb, redis, amqp\n\n      - name: Add require for mongodb/mongodb to make tests runnable\n        run: \"composer require ${{ env.COMPOSER_FLAGS }} mongodb/mongodb --dev --no-update\"\n\n      - uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1\n        with:\n          dependency-versions: highest\n\n      - name: Run PHPStan\n        run: composer phpstan\n"
  },
  {
    "path": ".gitignore",
    "content": "vendor\ncomposer.phar\nphpunit.xml\ncomposer.lock\n.DS_Store\n.php-cs-fixer.cache\n.hg\n.phpunit.result.cache\n.phpunit.cache\n"
  },
  {
    "path": ".php-cs-fixer.php",
    "content": "<?php\n\n$header = <<<EOF\nThis file is part of the Monolog package.\n\n(c) Jordi Boggiano <j.boggiano@seld.be>\n\nFor the full copyright and license information, please view the LICENSE\nfile that was distributed with this source code.\nEOF;\n\n$finder = PhpCsFixer\\Finder::create()\n    ->files()\n    ->name('*.php')\n    ->exclude('Fixtures')\n    ->in(__DIR__.'/src')\n    ->in(__DIR__.'/tests')\n;\n\n$config = new PhpCsFixer\\Config();\nreturn $config->setRules(array(\n        '@PSR2' => true,\n        // some rules disabled as long as 1.x branch is maintained\n        'array_syntax' => ['syntax' => 'short'],\n        'binary_operator_spaces' => [\n            'default' => null,\n        ],\n        'blank_line_before_statement' => ['statements' => ['continue', 'declare', 'return', 'throw', 'try']],\n        'cast_spaces' => ['space' => 'single'],\n        'header_comment' => ['header' => $header],\n        'include' => true,\n        'class_attributes_separation' => array('elements' => array('method' => 'one', 'trait_import' => 'none')),\n        'native_function_invocation' => true,\n        'no_blank_lines_after_class_opening' => true,\n        'no_blank_lines_after_phpdoc' => true,\n        'no_empty_statement' => true,\n        'no_extra_blank_lines' => true,\n        'no_leading_import_slash' => true,\n        'no_leading_namespace_whitespace' => true,\n        'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],\n        'no_trailing_comma_in_singleline_array' => true,\n        'no_unused_imports' => true,\n        'no_whitespace_in_blank_line' => true,\n        'object_operator_without_whitespace' => true,\n        'phpdoc_align' => true,\n        'phpdoc_indent' => true,\n        'phpdoc_no_access' => true,\n        'phpdoc_no_package' => true,\n        'phpdoc_order' => true,\n        //'phpdoc_scalar' => true,\n        'phpdoc_trim' => true,\n        //'phpdoc_types' => true,\n        'psr_autoloading' => ['dir' => 'src'],\n        'declare_strict_types' => true,\n        'single_blank_line_before_namespace' => true,\n        'standardize_not_equals' => true,\n        'ternary_operator_spaces' => true,\n        'trailing_comma_in_multiline' => true,\n    ))\n    ->setUsingCache(true)\n    ->setRiskyAllowed(true)\n    ->setFinder($finder)\n;\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "### 3.10.0 (2026-01-02)\n\n  * Added automatic directory cleanup in RotatingFileHandler (#2000)\n  * Added timezone-aware file rotation to RotatingFileHandler (#1982)\n  * Added support for mongodb/mongodb 2.0+ (#1998)\n  * Added NoDiscard attribute to TestHandler methods to ensure the result is used (#2013)\n  * Fixed JsonFormatter crashing if __toString throws while normalizing data (#1968)\n  * Fixed PHP 8.5 deprecation warnings (#1997, #2009)\n  * Fixed DeduplicatingHandler collecting duplicate logs if the file cannot be locked (2e97231)\n  * Fixed GelfMessageFormatter to use integers instead of bool for gelf 1.1 support (#1973)\n  * Fixed empty stack traces being output anyway (#1979)\n  * Fixed StreamHandler not reopening the file if the inode changed (#1963)\n  * Fixed TelegramBotHandler sending empty messages (#1992)\n  * Fixed file paths in stack traces containing backslashes on windows, always using / now to unify logs (#1980)\n  * Fixed RotatingFileHandler unlink errors not being suppressed correctly (#1999)\n\n### 3.9.0 (2025-03-24)\n\n  * BC Warning: Fixed SendGridHandler to use the V3 API as V2 is now shut down, but this requires a new API key (#1952)\n  * Deprecated Monolog\\Test\\TestCase in favor of Monolog\\Test\\MonologTestCase (#1953)\n  * Added extension point for NativeMailerHandler::mail (#1948)\n  * Added setHandler method to BufferHandler to modify the nested handler at runtime (#1946)\n  * Fixed date format in ElasticsearchFormatter to use +00:00 vs +0000 tz identifiers (#1942)\n  * Fixed GelfMessageFormatter handling numeric context/extra keys (#1932)\n\n### 3.8.1 (2024-12-05)\n\n  * Deprecated Monolog\\DateTimeImmutable in favor of Monolog\\JsonSerializableDateTimeImmutable (#1928)\n  * Fixed gelf keys not being valid when context/extra data keys have spaces in them (#1927)\n  * Fixed empty lines appearing in the stack traces when a custom formatter returned null (#1925)\n\n### 3.8.0 (2024-11-12)\n\n  * Added `$fileOpenMode` param to `StreamHandler` to define a custom fopen mode to open the log file (#1913)\n  * Fixed PHP 8.4 deprecation notices (#1903)\n  * Added ability to extend/override `IntrospectionProcessor` (#1899)\n  * Added `$timeout` param to `ProcessHandler` to configure the stream_select() timeout to avoid blocking too long (default is 1.0 sec) (#1916)\n  * Fixed JsonFormatter batch handling to normalize records individually to make sure they look the same as if they were handled one by one (#1906)\n  * Fixed `StreamHandler` handling of write failures so that it now closes/reopens the stream and retries the write once before failing (#1882)\n  * Fixed `StreamHandler` error handler causing issues if a stream handler triggers an error (#1866)\n  * Fixed `StreamHandler::reset` not closing the stream, so that it would fail to write in some cases with long running processes (#1862)\n  * Fixed `RotatingFileHandler` issue where rotation does not happen in some long running processes (#1905)\n  * Fixed `JsonFormatter` handling of incomplete classes (#1834)\n  * Fixed `RotatingFileHandler` bug where rotation could sometimes not happen correctly (#1905)\n\n### 3.7.0 (2024-06-28)\n\n  * Added `NormalizerFormatter->setBasePath(...)` (and `JsonFormatter` by extension) that allows removing the project's path from the stack trace output (47e301d3e)\n  * Fixed JsonFormatter handling of incomplete classes (#1834)\n  * Fixed private error handlers causing problems with custom StreamHandler implementations (#1866)\n\n### 3.6.0 (2024-04-12)\n\n  * Added `LineFormatter->setBasePath(...)` that allows removing the project's path from the stack trace output (#1873)\n  * Added `$includeExtra` option in `PsrHandler` to also use extra data to replace placeholder values in the message (#1852)\n  * Added ability to customize what is a duplicated message by extending the `DeduplicationHandler` (#1879)\n  * Added handling for using `GelfMessageFormatter` together with the `AmqpHandler` (#1869)\n  * Added ability to extend `GoogleCloudLoggingFormatter` (#1859)\n  * Fixed `__toString` failures in context data crashing the normalization process (#1868)\n  * Fixed PHP 8.4 deprecation warnings (#1874)\n\n### 3.5.0 (2023-10-27)\n\n  * Added ability to indent stack traces in LineFormatter via e.g. `indentStacktraces('  ')` (#1835)\n  * Added ability to configure a max level name length in LineFormatter via e.g. `setMaxLevelNameLength(3)` (#1850)\n  * Added support for indexed arrays (i.e. `[]` and not `{}` arrays once json serialized) containing inline linebreaks in LineFormatter (#1818)\n  * Added `WithMonologChannel` attribute for integrators to use to configure autowiring (#1847)\n  * Fixed log record `extra` data leaking between handlers that have handler-specific processors set (#1819)\n  * Fixed LogglyHandler issue with record level filtering (#1841)\n  * Fixed display_errors parsing in ErrorHandler which did not support string values (#1804)\n  * Fixed bug where the previous error handler would not be restored in some cases where StreamHandler fails (#1815)\n  * Fixed normalization error when normalizing incomplete classes (#1833)\n\n### 3.4.0 (2023-06-21)\n\n  * Added `LoadAverageProcessor` to track one of the 1, 5 or 15min load averages (#1803)\n  * Added support for priority to the `AsMonologProcessor` attribute (#1797)\n  * Added `TelegramBotHandler` `topic`/`message_thread_id` support (#1802)\n  * Fixed `FingersCrossedHandler` passthruLevel checking (#1801)\n  * Fixed support of yearly and monthly rotation log file to rotate only once a month/year (#1805)\n  * Fixed `TestHandler` method docs (#1794)\n  * Fixed handling of falsey `display_errors` string values (#1804)\n\n### 3.3.1 (2023-02-06)\n\n  * Fixed Logger not being serializable anymore (#1792)\n\n### 3.3.0 (2023-02-06)\n\n  * Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748)\n  * Added `ClosureContextProcessor` to allow delaying the creation of context data by setting a Closure in context which is called when the log record is used (#1745)\n  * Added an ElasticsearchHandler option to set the `op_type` to `create` instead of the default `index` (#1766)\n  * Added support for enum context values in PsrLogMessageProcessor (#1773)\n  * Added graylog2/gelf-php 2.x support (#1747)\n  * Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739)\n  * Fixed GitProcessor not filtering correctly based on Level (#1749)\n  * Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791)\n  * Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758)\n  * Fixed infinite loop detection within Fibers (#1753)\n  * Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781)\n\n### 3.2.0 (2022-07-24)\n\n  * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734)\n  * Marked `Logger` `@final` as it should not be extended, prefer composition or talk to us if you are missing something\n  * Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723)\n  * Added `SyslogFormatter` to output syslog-like files which can be consumed by tools like [lnav](https://lnav.org/) (#1689)\n  * Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733)\n  * Added `GoogleCloudLoggingFormatter` (#1719)\n  * Added support for Predis 2.x (#1732)\n  * Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724)\n  * Fixed serialization/unserialization of handlers to make sure private properties are included (#1727)\n  * Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\\n` or `\\r` sequences (#1720)\n  * Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726)\n  * Fixed PHP 8.2 deprecation warnings (#1722)\n  * Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678)\n\n### 3.1.0 (2022-06-09)\n\n  * Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682)\n  * Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681)\n  * Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670)\n  * Fixed interop issue by removing the need for a return type in ProcessorInterface (#1680)\n  * Marked the reusable `Monolog\\Test\\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677)\n  * Fixed RotatingFileHandler issue when the date format contained slashes (#1671)\n\n### 3.0.0 (2022-05-10)\n\nChanges from RC1\n\n- The `Monolog\\LevelName` enum does not exist anymore, use `Monolog\\Level->getName()` instead.\n\n### 3.0.0-RC1 (2022-05-08)\n\nThis is mostly a cleanup release offering stronger type guarantees for integrators with the\narray->object/enum changes, but there is no big new feature for end users.\n\nSee [UPGRADE notes](UPGRADE.md#300) for details on all breaking changes especially if you are extending/implementing Monolog classes/interfaces.\n\nNoteworthy BC Breaks:\n\n- The minimum supported PHP version is now `8.1.0`.\n- Log records have been converted from an array to a [`Monolog\\LogRecord` object](src/Monolog/LogRecord.php)\n  with public (and mostly readonly) properties. e.g. instead of doing\n  `$record['context']` use `$record->context`.\n  In formatters or handlers if you rather need an array to work with you can use `$record->toArray()`\n  to get back a Monolog 1/2 style record array. This will contain the enum values instead of enum cases\n  in the `level` and `level_name` keys to be more backwards compatible and use simpler data types.\n- `FormatterInterface`, `HandlerInterface`, `ProcessorInterface`, etc. changed to contain `LogRecord $record`\n  instead of `array $record` parameter types. If you want to support multiple Monolog versions this should\n  be possible by type-hinting nothing, or `array|LogRecord` if you support PHP 8.0+. You can then code\n  against the $record using Monolog 2 style as LogRecord implements ArrayAccess for BC.\n  The interfaces do not require a `LogRecord` return type even where it would be applicable, but if you only\n  support Monolog 3 in integration code I would recommend you use `LogRecord` return types wherever fitting\n  to ensure forward compatibility as it may be added in Monolog 4.\n- Log levels are now enums [`Monolog\\Level`](src/Monolog/Level.php) and [`Monolog\\LevelName`](src/Monolog/LevelName.php)\n- Removed deprecated SwiftMailerHandler, migrate to SymfonyMailerHandler instead.\n- `ResettableInterface::reset()` now requires a void return type.\n- All properties have had types added, which may require you to do so as well if you extended\n  a Monolog class and declared the same property.\n\nNew deprecations:\n\n- `Logger::DEBUG`, `Logger::ERROR`, etc. are now deprecated in favor of the `Monolog\\Level` enum.\n  e.g. instead of `Logger::WARNING` use `Level::Warning` if you need to pass the enum case\n  to Monolog or one of its handlers, or `Level::Warning->value` if you need the integer\n  value equal to what `Logger::WARNING` was giving you.\n- `Logger::getLevelName()` is now deprecated.\n\n### 2.10.0 (2024-11-12)\n\n  * Added `$fileOpenMode` to `StreamHandler` to define a custom fopen mode to open the log file (#1913)\n  * Fixed `StreamHandler` handling of write failures so that it now closes/reopens the stream and retries the write once before failing (#1882)\n  * Fixed `StreamHandler` error handler causing issues if a stream handler triggers an error (#1866)\n  * Fixed `JsonFormatter` handling of incomplete classes (#1834)\n  * Fixed `RotatingFileHandler` bug where rotation could sometimes not happen correctly (#1905)\n\n### 2.9.3 (2024-04-12)\n\n  * Fixed PHP 8.4 deprecation warnings (#1874)\n\n### 2.9.2 (2023-10-27)\n\n  * Fixed display_errors parsing in ErrorHandler which did not support string values (#1804)\n  * Fixed bug where the previous error handler would not be restored in some cases where StreamHandler fails (#1815)\n  * Fixed normalization error when normalizing incomplete classes (#1833)\n\n### 2.9.1 (2023-02-06)\n\n  * Fixed Logger not being serializable anymore (#1792)\n\n### 2.9.0 (2023-02-05)\n\n  * Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748)\n  * Added support for enum context values in PsrLogMessageProcessor (#1773)\n  * Added graylog2/gelf-php 2.x support (#1747)\n  * Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739)\n  * Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791)\n  * Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758)\n  * Fixed infinite loop detection within Fibers (#1753)\n  * Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781)\n\n### 2.8.0 (2022-07-24)\n\n  * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734)\n  * Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723)\n  * Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733)\n  * Added `GoogleCloudLoggingFormatter` (#1719)\n  * Added support for Predis 2.x (#1732)\n  * Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724)\n  * Fixed serialization/unserialization of handlers to make sure private properties are included (#1727)\n  * Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\\n` or `\\r` sequences (#1720)\n  * Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726)\n  * Fixed PHP 8.2 deprecation warnings (#1722)\n  * Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678)\n\n### 2.7.0 (2022-06-09)\n\n  * Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682)\n  * Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681)\n  * Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670)\n  * Marked the reusable `Monolog\\Test\\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677)\n  * Fixed RotatingFileHandler issue when the date format contained slashes (#1671)\n\n### 2.6.0 (2022-05-10)\n\n  * Deprecated `SwiftMailerHandler`, use `SymfonyMailerHandler` instead\n  * Added `SymfonyMailerHandler` (#1663)\n  * Added ElasticSearch 8.x support to the ElasticsearchHandler (#1662)\n  * Added a way to filter/modify stack traces in LineFormatter (#1665)\n  * Fixed UdpSocket not being able to reopen/reconnect after close()\n  * Fixed infinite loops if a Handler is triggering logging while handling log records\n\n### 2.5.0 (2022-04-08)\n\n  * Added `callType` to IntrospectionProcessor (#1612)\n  * Fixed AsMonologProcessor syntax to be compatible with PHP 7.2 (#1651)\n\n### 2.4.0 (2022-03-14)\n\n  * Added [`Monolog\\LogRecord`](src/Monolog/LogRecord.php) interface that can be used to type-hint records like `array|\\Monolog\\LogRecord $record` to be forward compatible with the upcoming Monolog 3 changes\n  * Added `includeStacktraces` constructor params to LineFormatter & JsonFormatter (#1603)\n  * Added `persistent`, `timeout`, `writingTimeout`, `connectionTimeout`, `chunkSize` constructor params to SocketHandler and derivatives (#1600)\n  * Added `AsMonologProcessor` PHP attribute which can help autowiring / autoconfiguration of processors if frameworks / integrations decide to make use of it. This is useless when used purely with Monolog (#1637)\n  * Added support for keeping native BSON types as is in MongoDBFormatter (#1620)\n  * Added support for a `user_agent` key in WebProcessor, disabled by default but you can use it by configuring the $extraFields you want (#1613)\n  * Added support for username/userIcon in SlackWebhookHandler (#1617)\n  * Added extension points to BrowserConsoleHandler (#1593)\n  * Added record message/context/extra info to exceptions thrown when a StreamHandler cannot open its stream to avoid completely losing the data logged (#1630)\n  * Fixed error handler signature to accept a null $context which happens with internal PHP errors (#1614)\n  * Fixed a few setter methods not returning `self` (#1609)\n  * Fixed handling of records going over the max Telegram message length (#1616)\n\n### 2.3.5 (2021-10-01)\n\n  * Fixed regression in StreamHandler since 2.3.3 on systems with the memory_limit set to >=20GB (#1592)\n\n### 2.3.4 (2021-09-15)\n\n  * Fixed support for psr/log 3.x (#1589)\n\n### 2.3.3 (2021-09-14)\n\n  * Fixed memory usage when using StreamHandler and calling stream_get_contents on the resource you passed to it (#1578, #1577)\n  * Fixed support for psr/log 2.x (#1587)\n  * Fixed some type annotations\n\n### 2.3.2 (2021-07-23)\n\n  * Fixed compatibility with PHP 7.2 - 7.4 when experiencing PCRE errors (#1568)\n\n### 2.3.1 (2021-07-14)\n\n  * Fixed Utils::getClass handling of anonymous classes not being fully compatible with PHP 8 (#1563)\n  * Fixed some `@inheritDoc` annotations having the wrong case\n\n### 2.3.0 (2021-07-05)\n\n  * Added a ton of PHPStan type annotations as well as type aliases on Monolog\\Logger for Record, Level and LevelName that you can import (#1557)\n  * Added ability to customize date format when using JsonFormatter (#1561)\n  * Fixed FilterHandler not calling reset on its internal handler when reset() is called on it (#1531)\n  * Fixed SyslogUdpHandler not setting the timezone correctly on DateTimeImmutable instances (#1540)\n  * Fixed StreamHandler thread safety - chunk size set to 2GB now to avoid interlacing when doing concurrent writes (#1553)\n\n### 2.2.0 (2020-12-14)\n\n  * Added JSON_PARTIAL_OUTPUT_ON_ERROR to default json encoding flags, to avoid dropping entire context data or even records due to an invalid subset of it somewhere\n  * Added setDateFormat to NormalizerFormatter (and Line/Json formatters by extension) to allow changing this after object creation\n  * Added RedisPubSubHandler to log records to a Redis channel using PUBLISH\n  * Added support for Elastica 7, and deprecated the $type argument of ElasticaFormatter which is not in use anymore as of Elastica 7\n  * Added support for millisecond write timeouts in SocketHandler, you can now pass floats to setWritingTimeout, e.g. 0.2 is 200ms\n  * Added support for unix sockets in SyslogUdpHandler (set $port to 0 to make the $host a unix socket)\n  * Added handleBatch support for TelegramBotHandler\n  * Added RFC5424e extended date format including milliseconds to SyslogUdpHandler\n  * Added support for configuring handlers with numeric level values in strings (coming from e.g. env vars)\n  * Fixed Wildfire/FirePHP/ChromePHP handling of unicode characters\n  * Fixed PHP 8 issues in SyslogUdpHandler\n  * Fixed internal type error when mbstring is missing\n\n### 2.1.1 (2020-07-23)\n\n  * Fixed removing of json encoding options\n  * Fixed type hint of $level not accepting strings in SendGridHandler and OverflowHandler\n  * Fixed SwiftMailerHandler not accepting email templates with an empty subject\n  * Fixed array access on null in RavenHandler\n  * Fixed unique_id in WebProcessor not being disableable\n\n### 2.1.0 (2020-05-22)\n\n  * Added `JSON_INVALID_UTF8_SUBSTITUTE` to default json flags, so that invalid UTF8 characters now get converted to [�](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character) instead of being converted from ISO-8859-15 to UTF8 as it was before, which was hardly a comprehensive solution\n  * Added `$ignoreEmptyContextAndExtra` option to JsonFormatter to skip empty context/extra entirely from the output\n  * Added `$parseMode`, `$disableWebPagePreview` and `$disableNotification` options to TelegramBotHandler\n  * Added tentative support for PHP 8\n  * NormalizerFormatter::addJsonEncodeOption and removeJsonEncodeOption are now public to allow modifying default json flags\n  * Fixed GitProcessor type error when there is no git repo present\n  * Fixed normalization of SoapFault objects containing deeply nested objects as \"detail\"\n  * Fixed support for relative paths in RotatingFileHandler\n\n### 2.0.2 (2019-12-20)\n\n  * Fixed ElasticsearchHandler swallowing exceptions details when failing to index log records\n  * Fixed normalization of SoapFault objects containing non-strings as \"detail\" in LineFormatter\n  * Fixed formatting of resources in JsonFormatter\n  * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services)\n  * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it\n  * Fixed Turkish locale messing up the conversion of level names to their constant values\n\n### 2.0.1 (2019-11-13)\n\n  * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable\n  * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler, OverflowHandler and SamplingHandler\n  * Fixed BrowserConsoleHandler formatting when using multiple styles\n  * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings\n  * Fixed normalization of SoapFault objects containing non-strings as \"detail\"\n  * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding\n  * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB).\n  * Fixed type error in BrowserConsoleHandler when the context array of log records was not associative.\n\n### 2.0.0 (2019-08-30)\n\n  * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release\n  * BC Break: Logger methods log/debug/info/notice/warning/error/critical/alert/emergency now have explicit void return types\n  * Added FallbackGroupHandler which works like the WhatFailureGroupHandler but stops dispatching log records as soon as one handler accepted it\n  * Fixed support for UTF-8 when cutting strings to avoid cutting a multibyte-character in half\n  * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases\n  * Fixed date timezone handling in SyslogUdpHandler\n\n### 2.0.0-beta2 (2019-07-06)\n\n  * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release\n  * BC Break: PHP 7.2 is now the minimum required PHP version.\n  * BC Break: Removed SlackbotHandler, RavenHandler and HipChatHandler, see [UPGRADE.md](UPGRADE.md) for details\n  * Added OverflowHandler which will only flush log records to its nested handler when reaching a certain amount of logs (i.e. only pass through when things go really bad)\n  * Added TelegramBotHandler to log records to a [Telegram](https://core.telegram.org/bots/api) bot account\n  * Added support for JsonSerializable when normalizing exceptions\n  * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler\n  * Added SoapFault details to formatted exceptions\n  * Fixed DeduplicationHandler silently failing to start when file could not be opened\n  * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records\n  * Fixed GelfFormatter losing some data when one attachment was too long\n  * Fixed issue in SignalHandler restarting syscalls functionality\n  * Improved performance of LogglyHandler when sending multiple logs in a single request\n\n### 2.0.0-beta1 (2018-12-08)\n\n  * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release\n  * BC Break: PHP 7.1 is now the minimum required PHP version.\n  * BC Break: Quite a few interface changes, only relevant if you implemented your own handlers/processors/formatters\n  * BC Break: Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn`\n  * BC Break: The record timezone is now set per Logger instance and not statically anymore\n  * BC Break: There is no more default handler configured on empty Logger instances\n  * BC Break: ElasticSearchHandler renamed to ElasticaHandler\n  * BC Break: Various handler-specific breaks, see [UPGRADE.md](UPGRADE.md) for details\n  * Added scalar type hints and return hints in all the places it was possible. Switched strict_types on for more reliability.\n  * Added DateTimeImmutable support, all record datetime are now immutable, and will toString/json serialize with the correct date format, including microseconds (unless disabled)\n  * Added timezone and microseconds to the default date format\n  * Added SendGridHandler to use the SendGrid API to send emails\n  * Added LogmaticHandler to use the Logmatic.io API to store log records\n  * Added SqsHandler to send log records to an AWS SQS queue\n  * Added ElasticsearchHandler to send records via the official ES library. Elastica users should now use ElasticaHandler instead of ElasticSearchHandler\n  * Added NoopHandler which is similar to the NullHandle but does not prevent the bubbling of log records to handlers further down the configuration, useful for temporarily disabling a handler in configuration files\n  * Added ProcessHandler to write log output to the STDIN of a given process\n  * Added HostnameProcessor that adds the machine's hostname to log records\n  * Added a `$dateFormat` option to the PsrLogMessageProcessor which lets you format DateTime instances nicely\n  * Added support for the PHP 7.x `mongodb` extension in the MongoDBHandler\n  * Fixed many minor issues in various handlers, and probably added a few regressions too\n\n### 1.26.1 (2021-05-28)\n\n  * Fixed PHP 8.1 deprecation warning\n\n### 1.26.0 (2020-12-14)\n\n  * Added $dateFormat and $removeUsedContextFields arguments to PsrLogMessageProcessor (backport from 2.x)\n\n### 1.25.5 (2020-07-23)\n\n  * Fixed array access on null in RavenHandler\n  * Fixed unique_id in WebProcessor not being disableable\n\n### 1.25.4 (2020-05-22)\n\n  * Fixed GitProcessor type error when there is no git repo present\n  * Fixed normalization of SoapFault objects containing deeply nested objects as \"detail\"\n  * Fixed support for relative paths in RotatingFileHandler\n\n### 1.25.3 (2019-12-20)\n\n  * Fixed formatting of resources in JsonFormatter\n  * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services)\n  * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it\n  * Fixed Turkish locale messing up the conversion of level names to their constant values\n\n### 1.25.2 (2019-11-13)\n\n  * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable\n  * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler and SamplingHandler\n  * Fixed BrowserConsoleHandler formatting when using multiple styles\n  * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings\n  * Fixed normalization of SoapFault objects containing non-strings as \"detail\"\n  * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding\n\n### 1.25.1 (2019-09-06)\n\n  * Fixed forward-compatible interfaces to be compatible with Monolog 1.x too.\n\n### 1.25.0 (2019-09-06)\n\n  * Deprecated SlackbotHandler, use SlackWebhookHandler or SlackHandler instead\n  * Deprecated RavenHandler, use sentry/sentry 2.x and their Sentry\\Monolog\\Handler instead\n  * Deprecated HipChatHandler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead\n  * Added forward-compatible interfaces and traits FormattableHandlerInterface, FormattableHandlerTrait, ProcessableHandlerInterface, ProcessableHandlerTrait. If you use modern PHP and want to make code compatible with Monolog 1 and 2 this can help. You will have to require at least Monolog 1.25 though.\n  * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler\n  * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records\n  * Fixed issue in SignalHandler restarting syscalls functionality\n  * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases\n  * Fixed ZendMonitorHandler to work with the latest Zend Server versions\n  * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB).\n\n### 1.24.0 (2018-11-05)\n\n  * BC Notice: If you are extending any of the Monolog's Formatters' `normalize` method, make sure you add the new `$depth = 0` argument to your function signature to avoid strict PHP warnings.\n  * Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors\n  * Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers)\n  * Added a way to log signals being received using Monolog\\SignalHandler\n  * Added ability to customize error handling at the Logger level using Logger::setExceptionHandler\n  * Added InsightOpsHandler to migrate users of the LogEntriesHandler\n  * Added protection to NormalizerFormatter against circular and very deep structures, it now stops normalizing at a depth of 9\n  * Added capture of stack traces to ErrorHandler when logging PHP errors\n  * Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts\n  * Added forwarding of context info to FluentdFormatter\n  * Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example\n  * Added ability to extend/override BrowserConsoleHandler\n  * Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility\n  * Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility\n  * Dropped official support for HHVM in test builds\n  * Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain\n  * Fixed naming of fields in Slack handler, all field names are now capitalized in all cases\n  * Fixed HipChatHandler bug where slack dropped messages randomly\n  * Fixed normalization of objects in Slack handlers\n  * Fixed support for PHP7's Throwable in NewRelicHandler\n  * Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory\n  * Fixed table row styling issues in HtmlFormatter\n  * Fixed RavenHandler dropping the message when logging exception\n  * Fixed WhatFailureGroupHandler skipping processors when using handleBatch\n    and implement it where possible\n  * Fixed display of anonymous class names\n\n### 1.23.0 (2017-06-19)\n\n  * Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument\n  * Fixed GelfHandler truncation to be per field and not per message\n  * Fixed compatibility issue with PHP <5.3.6\n  * Fixed support for headless Chrome in ChromePHPHandler\n  * Fixed support for latest Aws SDK in DynamoDbHandler\n  * Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler\n\n### 1.22.1 (2017-03-13)\n\n  * Fixed lots of minor issues in the new Slack integrations\n  * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces\n\n### 1.22.0 (2016-11-26)\n\n  * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily\n  * Added MercurialProcessor to add mercurial revision and branch names to log records\n  * Added support for AWS SDK v3 in DynamoDbHandler\n  * Fixed fatal errors occurring when normalizing generators that have been fully consumed\n  * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix)\n  * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore\n  * Fixed SyslogUdpHandler to avoid sending empty frames\n  * Fixed a few PHP 7.0 and 7.1 compatibility issues\n\n### 1.21.0 (2016-07-29)\n\n  * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues\n  * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order\n  * Added ability to format the main line of text the SlackHandler sends by explicitly setting a formatter on the handler\n  * Added information about SoapFault instances in NormalizerFormatter\n  * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level\n\n### 1.20.0 (2016-07-02)\n\n  * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy\n  * Added StreamHandler::getUrl to retrieve the stream's URL\n  * Added ability to override addRow/addTitle in HtmlFormatter\n  * Added the $context to context information when the ErrorHandler handles a regular php error\n  * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d\n  * Fixed WhatFailureGroupHandler to work with PHP7 throwables\n  * Fixed a few minor bugs\n\n### 1.19.0 (2016-04-12)\n\n  * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed\n  * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors\n  * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler\n  * Fixed HipChatHandler handling of long messages\n\n### 1.18.2 (2016-04-02)\n\n  * Fixed ElasticaFormatter to use more precise dates\n  * Fixed GelfMessageFormatter sending too long messages\n\n### 1.18.1 (2016-03-13)\n\n  * Fixed SlackHandler bug where slack dropped messages randomly\n  * Fixed RedisHandler issue when using with the PHPRedis extension\n  * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension\n  * Fixed BrowserConsoleHandler regression\n\n### 1.18.0 (2016-03-01)\n\n  * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond\n  * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames\n  * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name\n  * Added FluentdFormatter for the Fluentd unix socket protocol\n  * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed\n  * Added support for replacing context sub-keys using `%context.*%` in LineFormatter\n  * Added support for `payload` context value in RollbarHandler\n  * Added setRelease to RavenHandler to describe the application version, sent with every log\n  * Added support for `fingerprint` context value in RavenHandler\n  * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed\n  * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()`\n  * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places\n\n### 1.17.2 (2015-10-14)\n\n  * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers\n  * Fixed SlackHandler handling to use slack functionalities better\n  * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id\n  * Fixed 5.3 compatibility regression\n\n### 1.17.1 (2015-08-31)\n\n  * Fixed RollbarHandler triggering PHP notices\n\n### 1.17.0 (2015-08-30)\n\n  * Added support for `checksum` and `release` context/extra values in RavenHandler\n  * Added better support for exceptions in RollbarHandler\n  * Added UidProcessor::getUid\n  * Added support for showing the resource type in NormalizedFormatter\n  * Fixed IntrospectionProcessor triggering PHP notices\n\n### 1.16.0 (2015-08-09)\n\n  * Added IFTTTHandler to notify ifttt.com triggers\n  * Added Logger::setHandlers() to allow setting/replacing all handlers\n  * Added $capSize in RedisHandler to cap the log size\n  * Fixed StreamHandler creation of directory to only trigger when the first log write happens\n  * Fixed bug in the handling of curl failures\n  * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler\n  * Fixed missing fatal errors records with handlers that need to be closed to flush log records\n  * Fixed TagProcessor::addTags support for associative arrays\n\n### 1.15.0 (2015-07-12)\n\n  * Added addTags and setTags methods to change a TagProcessor\n  * Added automatic creation of directories if they are missing for a StreamHandler to open a log file\n  * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure\n  * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used\n  * Fixed HTML/JS escaping in BrowserConsoleHandler\n  * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only)\n\n### 1.14.0 (2015-06-19)\n\n  * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library\n  * Added support for objects implementing __toString in the NormalizerFormatter\n  * Added support for HipChat's v2 API in HipChatHandler\n  * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app\n  * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true)\n  * Fixed curl errors being silently suppressed\n\n### 1.13.1 (2015-03-09)\n\n  * Fixed regression in HipChat requiring a new token to be created\n\n### 1.13.0 (2015-03-05)\n\n  * Added Registry::hasLogger to check for the presence of a logger instance\n  * Added context.user support to RavenHandler\n  * Added HipChat API v2 support in the HipChatHandler\n  * Added NativeMailerHandler::addParameter to pass params to the mail() process\n  * Added context data to SlackHandler when $includeContextAndExtra is true\n  * Added ability to customize the Swift_Message per-email in SwiftMailerHandler\n  * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided\n  * Fixed serialization of INF and NaN values in Normalizer and LineFormatter\n\n### 1.12.0 (2014-12-29)\n\n  * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers.\n  * Added PsrHandler to forward records to another PSR-3 logger\n  * Added SamplingHandler to wrap around a handler and include only every Nth record\n  * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now)\n  * Added exception codes in the output of most formatters\n  * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line)\n  * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data\n  * Added $host to HipChatHandler for users of private instances\n  * Added $transactionName to NewRelicHandler and support for a transaction_name context value\n  * Fixed MandrillHandler to avoid outputting API call responses\n  * Fixed some non-standard behaviors in SyslogUdpHandler\n\n### 1.11.0 (2014-09-30)\n\n  * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names\n  * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails\n  * Added MandrillHandler to send emails via the Mandrillapp.com API\n  * Added SlackHandler to log records to a Slack.com account\n  * Added FleepHookHandler to log records to a Fleep.io account\n  * Added LogglyHandler::addTag to allow adding tags to an existing handler\n  * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end\n  * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing\n  * Added support for PhpAmqpLib in the AmqpHandler\n  * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs\n  * Added support for adding extra fields from $_SERVER in the WebProcessor\n  * Fixed support for non-string values in PrsLogMessageProcessor\n  * Fixed SwiftMailer messages being sent with the wrong date in long running scripts\n  * Fixed minor PHP 5.6 compatibility issues\n  * Fixed BufferHandler::close being called twice\n\n### 1.10.0 (2014-06-04)\n\n  * Added Logger::getHandlers() and Logger::getProcessors() methods\n  * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached\n  * Added support for extra data in NewRelicHandler\n  * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines\n\n### 1.9.1 (2014-04-24)\n\n  * Fixed regression in RotatingFileHandler file permissions\n  * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records\n  * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative\n\n### 1.9.0 (2014-04-20)\n\n  * Added LogEntriesHandler to send logs to a LogEntries account\n  * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler\n  * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes\n  * Added support for table formatting in FirePHPHandler via the table context key\n  * Added a TagProcessor to add tags to records, and support for tags in RavenHandler\n  * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files\n  * Added sound support to the PushoverHandler\n  * Fixed multi-threading support in StreamHandler\n  * Fixed empty headers issue when ChromePHPHandler received no records\n  * Fixed default format of the ErrorLogHandler\n\n### 1.8.0 (2014-03-23)\n\n  * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them\n  * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output\n  * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler\n  * Added FlowdockHandler to send logs to a Flowdock account\n  * Added RollbarHandler to send logs to a Rollbar account\n  * Added HtmlFormatter to send prettier log emails with colors for each log level\n  * Added GitProcessor to add the current branch/commit to extra record data\n  * Added a Monolog\\Registry class to allow easier global access to pre-configured loggers\n  * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement\n  * Added support for HHVM\n  * Added support for Loggly batch uploads\n  * Added support for tweaking the content type and encoding in NativeMailerHandler\n  * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor\n  * Fixed batch request support in GelfHandler\n\n### 1.7.0 (2013-11-14)\n\n  * Added ElasticSearchHandler to send logs to an Elastic Search server\n  * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB\n  * Added SyslogUdpHandler to send logs to a remote syslogd server\n  * Added LogglyHandler to send logs to a Loggly account\n  * Added $level to IntrospectionProcessor so it only adds backtraces when needed\n  * Added $version to LogstashFormatter to allow using the new v1 Logstash format\n  * Added $appName to NewRelicHandler\n  * Added configuration of Pushover notification retries/expiry\n  * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default\n  * Added chainability to most setters for all handlers\n  * Fixed RavenHandler batch processing so it takes the message from the record with highest priority\n  * Fixed HipChatHandler batch processing so it sends all messages at once\n  * Fixed issues with eAccelerator\n  * Fixed and improved many small things\n\n### 1.6.0 (2013-07-29)\n\n  * Added HipChatHandler to send logs to a HipChat chat room\n  * Added ErrorLogHandler to send logs to PHP's error_log function\n  * Added NewRelicHandler to send logs to NewRelic's service\n  * Added Monolog\\ErrorHandler helper class to register a Logger as exception/error/fatal handler\n  * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel\n  * Added stack traces output when normalizing exceptions (json output & co)\n  * Added Monolog\\Logger::API constant (currently 1)\n  * Added support for ChromePHP's v4.0 extension\n  * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel\n  * Added support for sending messages to multiple users at once with the PushoverHandler\n  * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler)\n  * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now\n  * Fixed issue in RotatingFileHandler when an open_basedir restriction is active\n  * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0\n  * Fixed SyslogHandler issue when many were used concurrently with different facilities\n\n### 1.5.0 (2013-04-23)\n\n  * Added ProcessIdProcessor to inject the PID in log records\n  * Added UidProcessor to inject a unique identifier to all log records of one request/run\n  * Added support for previous exceptions in the LineFormatter exception serialization\n  * Added Monolog\\Logger::getLevels() to get all available levels\n  * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle\n\n### 1.4.1 (2013-04-01)\n\n  * Fixed exception formatting in the LineFormatter to be more minimalistic\n  * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0\n  * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days\n  * Fixed WebProcessor array access so it checks for data presence\n  * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors\n\n### 1.4.0 (2013-02-13)\n\n  * Added RedisHandler to log to Redis via the Predis library or the phpredis extension\n  * Added ZendMonitorHandler to log to the Zend Server monitor\n  * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor\n  * Added `$useSSL` option to the PushoverHandler which is enabled by default\n  * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously\n  * Fixed header injection capability in the NativeMailHandler\n\n### 1.3.1 (2013-01-11)\n\n  * Fixed LogstashFormatter to be usable with stream handlers\n  * Fixed GelfMessageFormatter levels on Windows\n\n### 1.3.0 (2013-01-08)\n\n  * Added PSR-3 compliance, the `Monolog\\Logger` class is now an instance of `Psr\\Log\\LoggerInterface`\n  * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance\n  * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash)\n  * Added PushoverHandler to send mobile notifications\n  * Added CouchDBHandler and DoctrineCouchDBHandler\n  * Added RavenHandler to send data to Sentry servers\n  * Added support for the new MongoClient class in MongoDBHandler\n  * Added microsecond precision to log records' timestamps\n  * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing\n    the oldest entries\n  * Fixed normalization of objects with cyclic references\n\n### 1.2.1 (2012-08-29)\n\n  * Added new $logopts arg to SyslogHandler to provide custom openlog options\n  * Fixed fatal error in SyslogHandler\n\n### 1.2.0 (2012-08-18)\n\n  * Added AmqpHandler (for use with AMQP servers)\n  * Added CubeHandler\n  * Added NativeMailerHandler::addHeader() to send custom headers in mails\n  * Added the possibility to specify more than one recipient in NativeMailerHandler\n  * Added the possibility to specify float timeouts in SocketHandler\n  * Added NOTICE and EMERGENCY levels to conform with RFC 5424\n  * Fixed the log records to use the php default timezone instead of UTC\n  * Fixed BufferHandler not being flushed properly on PHP fatal errors\n  * Fixed normalization of exotic resource types\n  * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog\n\n### 1.1.0 (2012-04-23)\n\n  * Added Monolog\\Logger::isHandling() to check if a handler will\n    handle the given log level\n  * Added ChromePHPHandler\n  * Added MongoDBHandler\n  * Added GelfHandler (for use with Graylog2 servers)\n  * Added SocketHandler (for use with syslog-ng for example)\n  * Added NormalizerFormatter\n  * Added the possibility to change the activation strategy of the FingersCrossedHandler\n  * Added possibility to show microseconds in logs\n  * Added `server` and `referer` to WebProcessor output\n\n### 1.0.2 (2011-10-24)\n\n  * Fixed bug in IE with large response headers and FirePHPHandler\n\n### 1.0.1 (2011-08-25)\n\n  * Added MemoryPeakUsageProcessor and MemoryUsageProcessor\n  * Added Monolog\\Logger::getName() to get a logger's channel name\n\n### 1.0.0 (2011-07-06)\n\n  * Added IntrospectionProcessor to get info from where the logger was called\n  * Fixed WebProcessor in CLI\n\n### 1.0.0-RC1 (2011-07-01)\n\n  * Initial release\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2011-2020 Jordi Boggiano\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><img src=\"logo.jpg\" alt=\"Monolog\" width=\"400\"></p>\n\n# Monolog - Logging for PHP [![Continuous Integration](https://github.com/Seldaek/monolog/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/Seldaek/monolog/actions)\n\n[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)\n[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)\n\n>**Note** This is the **documentation for Monolog 3.x**, if you are using older releases\n>see the documentation for [Monolog 2.x](https://github.com/Seldaek/monolog/blob/2.x/README.md) or [Monolog 1.x](https://github.com/Seldaek/monolog/blob/1.x/README.md)\n\nMonolog sends your logs to files, sockets, inboxes, databases and various\nweb services. See the complete list of handlers below. Special handlers\nallow you to build advanced logging strategies.\n\nThis library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)\ninterface that you can type-hint against in your own libraries to keep\na maximum of interoperability. You can also use it in your applications to\nmake sure you can always use another compatible logger at a later time.\nAs of 1.11.0 Monolog public APIs will also accept PSR-3 log levels.\nInternally Monolog still uses its own level scheme since it predates PSR-3.\n\n<div align=\"center\">\n  <hr>\n  <sup><b>Sponsored by:</b></sup>\n  <br>\n  <a href=\"https://betterstack.com\">\n    <div>\n      <img src=\"https://github.com/Seldaek/monolog/assets/183678/7de58ce0-2fa2-45c0-b3e8-e60cebb3c4cf\" width=\"200\" alt=\"Better Stack\">\n    </div>\n    <div>\n      Better Stack lets you centralize, search, and visualize your logs.\n    </div>\n  </a>\n  <br>\n  <hr>\n</div>\n\n## Installation\n\nInstall the latest version with\n\n```bash\ncomposer require monolog/monolog\n```\n\n## Basic Usage\n\n```php\n<?php\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Monolog\\Handler\\StreamHandler;\n\n// create a log channel\n$log = new Logger('name');\n$log->pushHandler(new StreamHandler('path/to/your.log', Level::Warning));\n\n// add records to the log\n$log->warning('Foo');\n$log->error('Bar');\n```\n\n## Documentation\n\n- [Usage Instructions](doc/01-usage.md)\n- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md)\n- [Utility Classes](doc/03-utilities.md)\n- [Extending Monolog](doc/04-extending.md)\n- [Log Record Structure](doc/message-structure.md)\n\n## Support Monolog Financially\n\nGet supported Monolog and help fund the project with the [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-monolog-monolog?utm_source=packagist-monolog-monolog&utm_medium=referral&utm_campaign=enterprise) or via [GitHub sponsorship](https://github.com/sponsors/Seldaek).\n\nTidelift delivers commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use.\n\n## Third Party Packages\n\nThird party handlers, formatters and processors are\n[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You\ncan also add your own there if you publish one.\n\n## About\n\n### Requirements\n\n- Monolog `^3.0` works with PHP 8.1 or above.\n- Monolog `^2.5` works with PHP 7.2 or above.\n- Monolog `^1.25` works with PHP 5.3 up to 8.1, but is not very maintained anymore and will not receive PHP support fixes anymore.\n\n### Support\n\nMonolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 or 3 where possible to benefit from all the latest features and fixes.\n\n### Submitting bugs and feature requests\n\nBugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues)\n\n### Framework Integrations\n\n- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)\n  can be used very easily with Monolog since it implements the interface.\n- [Symfony](http://symfony.com) comes out of the box with Monolog.\n- [Laravel](http://laravel.com/) comes out of the box with Monolog.\n- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog.\n- [PPI](https://github.com/ppi/framework) comes out of the box with Monolog.\n- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin.\n- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog.\n- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog.\n- [Nette Framework](http://nette.org/en/) is usable with Monolog via the [contributte/monolog](https://github.com/contributte/monolog) or [orisai/nette-monolog](https://github.com/orisai/nette-monolog) extensions.\n- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog.\n- [FuelPHP](http://fuelphp.com/) comes out of the box with Monolog.\n- [Equip Framework](https://github.com/equip/framework) comes out of the box with Monolog.\n- [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) or [yii2-psr-log-target](https://github.com/samdark/yii2-psr-log-target) plugins.\n- [Hawkbit Micro Framework](https://github.com/HawkBitPhp/hawkbit) comes out of the box with Monolog.\n- [SilverStripe 4](https://www.silverstripe.org/) comes out of the box with Monolog.\n- [Drupal](https://www.drupal.org/) is usable with Monolog via the [monolog](https://www.drupal.org/project/monolog) module.\n- [Aimeos ecommerce framework](https://aimeos.org/) is usable with Monolog via the [ai-monolog](https://github.com/aimeos/ai-monolog) extension.\n- [Magento](https://magento.com/) comes out of the box with Monolog.\n- [Spiral Framework](https://spiral.dev) comes out of the box with Monolog bridge.\n- [WebFramework](https://web-framework.com/) comes out of the box with Monolog.\n\n### Author\n\nJordi Boggiano - <j.boggiano@seld.be> - <http://twitter.com/seldaek><br />\nSee also the list of [contributors](https://github.com/Seldaek/monolog/contributors) who participated in this project.\n\n### License\n\nMonolog is licensed under the MIT License - see the [LICENSE](LICENSE) file for details\n\n### Acknowledgements\n\nThis library is heavily inspired by Python's [Logbook](https://logbook.readthedocs.io/en/stable/)\nlibrary, although most concepts have been adjusted to fit to the PHP world.\n"
  },
  {
    "path": "UPGRADE.md",
    "content": "### 4.0.0\n\nOverall / notable changes:\n\n- Monolog\\DateTimeImmutable has been removed in favor of Monolog\\JsonSerializableDateTimeImmutable.\n\n### 3.0.0\n\nOverall / notable changes:\n\n- The minimum supported PHP version is now `8.1.0`.\n- `Monolog\\Logger::API` can be used to distinguish between a Monolog `3`, `2` or `1`\n  install when writing integration code.\n- Log records have been converted from an array to a [`Monolog\\LogRecord` object](src/Monolog/LogRecord.php)\n  with public (and mostly readonly) properties. e.g. instead of doing\n  `$record['context']` use `$record->context`.\n  In formatters or handlers if you rather need an array to work with you can use `$record->toArray()`\n  to get back a Monolog 1/2 style record array. This will contain the enum values instead of enum cases\n  in the `level` and `level_name` keys to be more backwards compatible and use simpler data types.\n- `FormatterInterface`, `HandlerInterface`, `ProcessorInterface`, etc. changed to contain `LogRecord $record`\n  instead of `array $record` parameter types. If you want to support multiple Monolog versions this should\n  be possible by type-hinting nothing, or `array|LogRecord` if you support PHP 8.0+. You can then code\n  against the $record using Monolog 2 style as LogRecord implements ArrayAccess for BC.\n  The interfaces do not require a `LogRecord` return type even where it would be applicable, but if you only\n  support Monolog 3 in integration code I would recommend you use `LogRecord` return types wherever fitting\n  to ensure forward compatibility as it may be added in Monolog 4.\n- Log levels are now stored as an enum [`Monolog\\Level`](src/Monolog/Level.php)\n- All properties have had types added, which may require you to do so as well if you extended\n  a Monolog class and declared the same property.\n\n#### Logger\n\n- `Logger::DEBUG`, `Logger::ERROR`, etc. are now deprecated in favor of the `Level` enum.\n  e.g. instead of `Logger::WARNING` use `Level::Warning` if you need to pass the enum case\n  to Monolog or one of its handlers, or `Level::Warning->value` if you need the integer\n  value equal to what `Logger::WARNING` was giving you.\n- `Logger::$levels` has been removed.\n- `Logger::getLevels` has been removed in favor of `Monolog\\Level::VALUES` or `Monolog\\Level::cases()`.\n- `setExceptionHandler` now requires a `Closure` instance and not just any `callable`.\n\n#### HtmlFormatter\n\n- If you redefined colors in the `$logLevels` property you must now override the\n  `getLevelColor` method instead.\n\n#### NormalizerFormatter\n\n- A new `normalizeRecord` method is available as an extension point which is called\n  only when converting the LogRecord to an array. You may need this if you overrode\n  `format` previously as `parent::format` now needs to receive a LogRecord still\n  so you cannot modify it before.\n\n#### AbstractSyslogHandler\n\n- If you redefined syslog levels in the `$logLevels` property you must now override the\n  `toSyslogPriority` method instead.\n\n#### DynamoDbHandler\n\n- Dropped support for AWS SDK v2\n\n#### FilterHandler\n\n- The factory callable to lazy load the nested handler must now be a `Closure` instance\n  and not just a `callable`.\n\n#### FingersCrossedHandler\n\n- The factory callable to lazy load the nested handler must now be a `Closure` instance\n  and not just a `callable`.\n\n#### GelfHandler\n\n- Dropped support for Gelf <1.1 and added support for graylog2/gelf-php v2.x. File, level\n  and facility are now passed in as additional fields (#1664)[https://github.com/Seldaek/monolog/pull/1664].\n\n#### RollbarHandler\n\n- If you redefined rollbar levels in the `$logLevels` property you must now override the\n  `toRollbarLevel` method instead.\n\n#### SamplingHandler\n\n- The factory callable to lazy load the nested handler must now be a `Closure` instance\n  and not just a `callable`.\n\n#### SwiftMailerHandler\n\n- Removed deprecated SwiftMailer handler, migrate to SymfonyMailerHandler instead.\n\n#### ZendMonitorHandler\n\n- If you redefined zend monitor levels in the `$levelMap` property you must now override the\n  `toZendMonitorLevel` method instead.\n\n#### ResettableInterface\n\n- `reset()` now requires a void return type.\n\n### 2.0.0\n\n- `Monolog\\Logger::API` can be used to distinguish between a Monolog `1` and `2`\n  install of Monolog when writing integration code.\n\n- Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`)\n  methods as well as `emerg`, `crit`, `err` and `warn`.\n\n- DateTime are now formatted with a timezone and microseconds (unless disabled).\n  Various formatters and log output might be affected, which may mess with log parsing\n  in some cases.\n\n- The `datetime` in every record array is now a DateTimeImmutable, not that you\n  should have been modifying these anyway.\n\n- The timezone is now set per Logger instance and not statically, either\n  via ->setTimezone or passed in the constructor. Calls to Logger::setTimezone\n  should be converted.\n\n- `HandlerInterface` has been split off and two new interfaces now exist for\n  more granular controls: `ProcessableHandlerInterface` and\n  `FormattableHandlerInterface`. Handlers not extending `AbstractHandler`\n  should make sure to implement the relevant interfaces.\n\n- `HandlerInterface` now requires the `close` method to be implemented. This\n  only impacts you if you implement the interface yourself, but you can extend\n  the new `Monolog\\Handler\\Handler` base class too.\n\n- There is no more default handler configured on empty Logger instances, if\n  you were relying on that you will not get any output anymore, make sure to\n  configure the handler you need.\n\n#### LogglyFormatter\n\n- The records' `datetime` is not sent anymore. Only `timestamp` is sent to Loggly.\n\n#### AmqpHandler\n\n- Log levels are not shortened to 4 characters anymore. e.g. a warning record\n  will be sent using the `warning.channel` routing key instead of `warn.channel`\n  as in 1.x.\n- The exchange name does not default to 'log' anymore, and it is completely ignored\n  now for the AMQP extension users. Only PHPAmqpLib uses it if provided.\n\n#### RotatingFileHandler\n\n- The file name format must now contain `{date}` and the date format must be set\n  to one of the predefined FILE_PER_* constants to avoid issues with file rotation.\n  See `setFilenameFormat`.\n\n#### LogstashFormatter\n\n- Removed Logstash V0 support\n- Context/extra prefix has been removed in favor of letting users configure the exact key being sent\n- Context/extra data are now sent as an object instead of single keys\n\n#### HipChatHandler\n\n- Removed deprecated HipChat handler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead\n\n#### SlackbotHandler\n\n- Removed deprecated SlackbotHandler handler, use SlackWebhookHandler or SlackHandler instead\n\n#### RavenHandler\n\n- Removed deprecated RavenHandler handler, use sentry/sentry 2.x and their Sentry\\Monolog\\Handler instead\n\n#### ElasticSearchHandler\n\n- As support for the official Elasticsearch library was added, the former ElasticSearchHandler has been\n  renamed to ElasticaHandler and the new one added as ElasticsearchHandler.\n"
  },
  {
    "path": "_config.yml",
    "content": "theme: jekyll-theme-slate"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"monolog/monolog\",\n    \"description\": \"Sends your logs to files, sockets, inboxes, databases and various web services\",\n    \"keywords\": [\"log\", \"logging\", \"psr-3\"],\n    \"homepage\": \"https://github.com/Seldaek/monolog\",\n    \"type\": \"library\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Jordi Boggiano\",\n            \"email\": \"j.boggiano@seld.be\",\n            \"homepage\": \"https://seld.be\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.1\",\n        \"psr/log\": \"^2.0 || ^3.0\"\n    },\n    \"require-dev\": {\n        \"ext-json\": \"*\",\n        \"aws/aws-sdk-php\": \"^3.0\",\n        \"doctrine/couchdb\": \"~1.0@dev\",\n        \"elasticsearch/elasticsearch\": \"^7 || ^8\",\n        \"graylog2/gelf-php\": \"^1.4.2 || ^2.0\",\n        \"guzzlehttp/guzzle\": \"^7.4.5\",\n        \"guzzlehttp/psr7\": \"^2.2\",\n        \"mongodb/mongodb\": \"^1.8 || ^2.0\",\n        \"php-amqplib/php-amqplib\": \"~2.4 || ^3\",\n        \"php-console/php-console\": \"^3.1.8\",\n        \"phpstan/phpstan\": \"^2\",\n        \"phpstan/phpstan-deprecation-rules\": \"^2\",\n        \"phpstan/phpstan-strict-rules\": \"^2\",\n        \"phpunit/phpunit\": \"^10.5.17 || ^11.0.7\",\n        \"predis/predis\": \"^1.1 || ^2\",\n        \"rollbar/rollbar\": \"^4.0\",\n        \"ruflin/elastica\": \"^7 || ^8\",\n        \"symfony/mailer\": \"^5.4 || ^6\",\n        \"symfony/mime\": \"^5.4 || ^6\"\n    },\n    \"suggest\": {\n        \"graylog2/gelf-php\": \"Allow sending log messages to a GrayLog2 server\",\n        \"doctrine/couchdb\": \"Allow sending log messages to a CouchDB server\",\n        \"ruflin/elastica\": \"Allow sending log messages to an Elastic Search server\",\n        \"elasticsearch/elasticsearch\": \"Allow sending log messages to an Elasticsearch server via official client\",\n        \"php-amqplib/php-amqplib\": \"Allow sending log messages to an AMQP server using php-amqplib\",\n        \"ext-amqp\": \"Allow sending log messages to an AMQP server (1.0+ required)\",\n        \"ext-mongodb\": \"Allow sending log messages to a MongoDB server (via driver)\",\n        \"mongodb/mongodb\": \"Allow sending log messages to a MongoDB server (via library)\",\n        \"aws/aws-sdk-php\": \"Allow sending log messages to AWS services like DynamoDB\",\n        \"rollbar/rollbar\": \"Allow sending log messages to Rollbar\",\n        \"ext-mbstring\": \"Allow to work properly with unicode symbols\",\n        \"ext-sockets\": \"Allow sending log messages to a Syslog server (via UDP driver)\",\n        \"ext-curl\": \"Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler\",\n        \"ext-openssl\": \"Required to send log messages using SSL\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\"Monolog\\\\\": \"src/Monolog\"}\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\"Monolog\\\\\": \"tests/Monolog\"}\n    },\n    \"provide\": {\n        \"psr/log-implementation\": \"3.0.0\"\n    },\n    \"extra\": {\n        \"branch-alias\": {\n            \"dev-main\": \"3.x-dev\"\n        }\n    },\n    \"scripts\": {\n        \"test\": \"@php vendor/bin/phpunit\",\n        \"phpstan\": \"@php vendor/bin/phpstan analyse\"\n    },\n    \"config\": {\n        \"lock\": false,\n        \"sort-packages\": true,\n        \"platform-check\": false,\n        \"allow-plugins\": {\n            \"php-http/discovery\": false\n        }\n    }\n}\n"
  },
  {
    "path": "doc/01-usage.md",
    "content": "# Using Monolog\n\n- [Installation](#installation)\n- [Core Concepts](#core-concepts)\n- [Log Levels](#log-levels)\n- [Configuring a logger](#configuring-a-logger)\n- [Adding extra data in the records](#adding-extra-data-in-the-records)\n- [Leveraging channels](#leveraging-channels)\n- [Customizing the log format](#customizing-the-log-format)\n- [Long running processes and avoiding memory leaks](#long-running-processes-and-avoiding-memory-leaks)\n\n## Installation\n\nMonolog is available on Packagist ([monolog/monolog](http://packagist.org/packages/monolog/monolog))\nand as such installable via [Composer](http://getcomposer.org/).\n\n```bash\ncomposer require monolog/monolog\n```\n\n## Core Concepts\n\nEvery `Logger` instance has a channel (name) and a stack of handlers. Whenever\nyou add a [record](message-structure.md) to the logger, it traverses the handler stack. Each handler\ndecides whether it fully handled the record, and if so, the propagation of the\nrecord ends there.\n\nThis allows for flexible logging setups, for example having a `StreamHandler` at\nthe bottom of the stack that will log anything to disk, and on top of that add\na `MailHandler` that will send emails only when an error message is logged.\nHandlers also have a `$bubble` property which defines whether they block the\nrecord or not if they handled it. In this example, setting the `MailHandler`'s\n`$bubble` argument to false means that records handled by the `MailHandler` will\nnot propagate to the `StreamHandler` anymore.\n\nYou can create many `Logger`s, each defining a channel (e.g.: db, request,\nrouter, ..) and each of them combining various handlers, which can be shared\nor not. The channel is reflected in the logs and allows you to easily see or\nfilter records.\n\nEach Handler also has a Formatter, a default one with settings that make sense\nwill be created if you don't set one. The formatters normalize and format\nincoming records so that they can be used by the handlers to output useful\ninformation.\n\nCustom severity levels are not available. Only the eight\n[RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424) levels (debug, info, notice,\nwarning, error, critical, alert, emergency) are present for basic filtering\npurposes, but for sorting and other use cases that would require\nflexibility, you should add Processors to the Logger that can add extra\ninformation (tags, user ip, ..) to the records before they are handled.\n\n## Log Levels\n\nMonolog supports the logging levels described by [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424).\n\n- **DEBUG** (100): Detailed debug information.\n\n- **INFO** (200): Interesting events. Examples: User logs in, SQL logs.\n\n- **NOTICE** (250): Normal but significant events.\n\n- **WARNING** (300): Exceptional occurrences that are not errors. Examples:\n  Use of deprecated APIs, poor use of an API, undesirable things that are not\n  necessarily wrong.\n\n- **ERROR** (400): Runtime errors that do not require immediate action but\n  should typically be logged and monitored.\n\n- **CRITICAL** (500): Critical conditions. Example: Application component\n  unavailable, unexpected exception.\n\n- **ALERT** (550): Action must be taken immediately. Example: Entire website\n  down, database unavailable, etc. This should trigger the SMS alerts and wake\n  you up.\n\n- **EMERGENCY** (600): Emergency: system is unusable.\n\n## Configuring a logger\n\nHere is a basic setup to log to a file and to firephp on the DEBUG level:\n\n```php\n<?php\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Monolog\\Handler\\StreamHandler;\nuse Monolog\\Handler\\FirePHPHandler;\n\n// Create the logger\n$logger = new Logger('my_logger');\n// Now add some handlers\n$logger->pushHandler(new StreamHandler(__DIR__.'/my_app.log', Level::Debug));\n$logger->pushHandler(new FirePHPHandler());\n\n// You can now use your logger\n$logger->info('My logger is now ready');\n```\n\nLet's explain it. The first step is to create the logger instance which will\nbe used in your code. The argument is a channel name, which is useful when\nyou use several loggers (see below for more details about it).\n\nThe logger itself does not know how to handle a record. It delegates it to\nsome handlers. The code above registers two handlers in the stack to allow\nhandling records in two different ways.\n\nNote that the FirePHPHandler is called first as it is added on top of the\nstack. This allows you to temporarily add a logger with bubbling disabled if\nyou want to override other configured loggers.\n\n## Adding extra data in the records\n\nMonolog provides two different ways to add extra information along the simple\ntextual message.\n\n### Using the logging context\n\nThe first way is the context, allowing to pass an array of data along the\nrecord:\n\n```php\n<?php\n\n$logger->info('Adding a new user', ['username' => 'Seldaek']);\n```\n\nSimple handlers (like the StreamHandler for instance) will simply format\nthe array to a string but richer handlers can take advantage of the context\n(FirePHP is able to display arrays in a pretty way for instance).\n\n### Using processors\n\nThe second way is to add extra data for all records by using a processor.\nProcessors can be any callable. They will get the record as parameter and\nmust return it after having eventually changed the `extra` part of it. Let's\nwrite a processor adding some dummy data in the record:\n\n```php\n<?php\n\n$logger->pushProcessor(function ($record) {\n    $record->extra['dummy'] = 'Hello world!';\n\n    return $record;\n});\n```\n\nMonolog provides some built-in processors that can be used in your project.\nLook at the [dedicated chapter](https://github.com/Seldaek/monolog/blob/main/doc/02-handlers-formatters-processors.md#processors) for the list.\n\n> Tip: processors can also be registered on a specific handler instead of\n  the logger to apply only for this handler.\n\n## Leveraging channels\n\nChannels are a great way to identify to which part of the application a record\nis related. This is useful in big applications (and is leveraged by\nMonologBundle in Symfony).\n\nPicture two loggers sharing a handler that writes to a single log file.\nChannels would allow you to identify the logger that issued every record.\nYou can easily grep through the log files filtering this or that channel.\n\n```php\n<?php\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Monolog\\Handler\\StreamHandler;\nuse Monolog\\Handler\\FirePHPHandler;\n\n// Create some handlers\n$stream = new StreamHandler(__DIR__.'/my_app.log', Level::Debug);\n$firephp = new FirePHPHandler();\n\n// Create the main logger of the app\n$logger = new Logger('my_logger');\n$logger->pushHandler($stream);\n$logger->pushHandler($firephp);\n\n// Create a logger for the security-related stuff with a different channel\n$securityLogger = new Logger('security');\n$securityLogger->pushHandler($stream);\n$securityLogger->pushHandler($firephp);\n\n// Or clone the first one to only change the channel\n$securityLogger = $logger->withName('security');\n```\n\n## Customizing the log format\n\nIn Monolog it's easy to customize the format of the logs written into files,\nsockets, mails, databases and other handlers; by the use of \"Formatters\".\n\nAs mentioned before, a *Formatter* is attached to a *Handler*, and as a general convention, most of the handlers use the\n```php\n$record->formatted\n```\nproperty in the log record to store its formatted value.\n\nYou can choose between predefined formatter classes or write your own (e.g. a multiline text file for human-readable output).\n\n> Note:\n>\n> A very useful formatter to look at, is the `LineFormatter`.\n>\n> This formatter, as its name might indicate, is able to return a lineal string representation of the log record provided.\n>\n> It is also capable to interpolate values from the log record, into the output format template used by the formatter to generate\n> the final result, and in order to do it, you need to provide the log record values you are interested in, in the output template\n> string using the form %value%, e.g: \"'%context.foo% => %extra.foo%'\" , in this example the values `$record->context[\"foo\"]`\n> and `$record->extra[\"foo\"]` will be rendered as part of the final result.\n\nIn the following example, we demonstrate how to:\n1. Create a `LineFormatter` instance and set a custom output format template.\n2. Create a new *Handler*.\n3. Attach the *Formatter* to the *Handler*.\n4. Create a new *Logger* object.\n5. Attach the *Handler* to the *Logger* object.\n\n```php\n<?php\n\n// the default date format is \"Y-m-d\\TH:i:sP\"\n$dateFormat = \"Y n j, g:i a\";\n\n// the default output format is \"[%datetime%] %channel%.%level_name%: %message% %context% %extra%\\n\"\n// we now change the default output format according to our needs.\n$output = \"%datetime% > %level_name% > %message% %context% %extra%\\n\";\n\n// finally, create a formatter\n$formatter = new LineFormatter($output, $dateFormat);\n\n// Create a handler\n$stream = new StreamHandler(__DIR__.'/my_app.log', Level::Debug);\n$stream->setFormatter($formatter);\n\n// bind it to a logger object\n$securityLogger = new Logger('security');\n$securityLogger->pushHandler($stream);\n```\n\nYou may also reuse the same formatter between multiple handlers and share those\nhandlers between multiple loggers.\n\n## Long running processes and avoiding memory leaks\n\nWhen logging lots of data or especially when running background workers which\nare long-lived processes and do lots of logging over long periods of time, the\nmemory usage of buffered handlers like FingersCrossedHandler or BufferHandler\ncan rise quickly.\n\nMonolog provides the `ResettableInterface` for this use case, allowing you to\nend a log cycle and get things back to their initial state.\n\nCalling `$logger->reset();` means flushing/cleaning all buffers, resetting internal\nstate, and getting it back to a state in which it can receive log records again.\n\nThis is the conceptual equivalent of ending a web request, and can be done\nbetween every background job you process, or whenever appropriate. It reduces memory\nusage and also helps keep logs focused on the task at hand, avoiding log leaks\nbetween different jobs.\n\n[Handlers, Formatters and Processors](02-handlers-formatters-processors.md) &rarr;\n"
  },
  {
    "path": "doc/02-handlers-formatters-processors.md",
    "content": "# Handlers, Formatters and Processors\n\n- [Handlers](#handlers)\n  - [Log to files and syslog](#log-to-files-and-syslog)\n  - [Send alerts and emails](#send-alerts-and-emails)\n  - [Log specific servers and networked logging](#log-specific-servers-and-networked-logging)\n  - [Logging in development](#logging-in-development)\n  - [Log to databases](#log-to-databases)\n  - [Wrappers / Special Handlers](#wrappers--special-handlers)\n- [Formatters](#formatters)\n- [Processors](#processors)\n- [Third Party Packages](#third-party-packages)\n\n## Handlers\n\n### Log to files and syslog\n\n- [_StreamHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/StreamHandler.php): Logs records into any PHP stream, use this for log files.\n- [_RotatingFileHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/RotatingFileHandler.php): Logs records to a file and creates one log file per day.\n  It will also delete files older than `$maxFiles`. You should use\n  [logrotate](https://linux.die.net/man/8/logrotate) for high profile\n  setups though, this is just meant as a quick and dirty solution.\n- [_SyslogHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/SyslogHandler.php): Logs records to the syslog.\n- [_ErrorLogHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/ErrorLogHandler.php): Logs records to PHP's\n  [`error_log()`](https://www.php.net/manual/en/function.error-log.php) function.\n- [_ProcessHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/ProcessHandler.php): Logs records to the [STDIN](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_.28stdin.29) of any process, specified by a command.\n\n### Send alerts and emails\n\n- [_NativeMailerHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/NativeMailerHandler.php): Sends emails using PHP's\n  [`mail()`](http://php.net/manual/en/function.mail.php) function.\n- [_SymfonyMailerHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/SymfonyMailerHandler.php): Sends emails using a [`symfony/mailer`](https://symfony.com/doc/current/mailer.html) instance.\n- [_PushoverHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/PushoverHandler.php): Sends mobile notifications via the [Pushover](https://www.pushover.net/) API.\n- [_SlackWebhookHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/SlackWebhookHandler.php): Logs records to a [Slack](https://www.slack.com/) account using Slack Webhooks.\n- [_SlackHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/SlackHandler.php): Logs records to a [Slack](https://www.slack.com/) account using the Slack API (complex setup).\n- [_SendGridHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/SendGridHandler.php): Sends emails via the SendGrid API.\n- [_MandrillHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/MandrillHandler.php): Sends emails via the [`Mandrill API`](https://mandrillapp.com/api/docs/) using a [`Swift_Message`](http://swiftmailer.org/) instance.\n- [_FleepHookHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/FleepHookHandler.php): Logs records to a [Fleep](https://fleep.io/) conversation using Webhooks.\n- [_IFTTTHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/IFTTTHandler.php): Notifies an [IFTTT](https://ifttt.com/maker) trigger with the log channel, level name and message.\n- [_TelegramBotHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/TelegramBotHandler.php): Logs records to a [Telegram](https://core.telegram.org/bots/api) bot account.\n- [_HipChatHandler_](https://github.com/Seldaek/monolog/blob/1.x/src/Monolog/Handler/HipChatHandler.php): Logs records to a [HipChat](http://hipchat.com) chat room using its API. **Deprecated** and removed in Monolog 2.0, use Slack handlers instead, see [Atlassian's announcement](https://www.atlassian.com/partnerships/slack)\n- [_SwiftMailerHandler_](https://github.com/Seldaek/monolog/blob/2.x/src/Monolog/Handler/SwiftMailerHandler.php): Sends emails using a [`Swift_Mailer`](http://swiftmailer.org/) instance. **Deprecated** and removed in Monolog 3.0. Use SymfonyMailerHandler instead.\n\n### Log specific servers and networked logging\n\n- [_SocketHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/SocketHandler.php): Logs records to [sockets](http://php.net/fsockopen), use this\n  for UNIX and TCP sockets. See an [example](sockets.md).\n- [_AmqpHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/AmqpHandler.php): Logs records to an [AMQP](http://www.amqp.org/) compatible\n  server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+) or\n  [php-amqplib](https://github.com/php-amqplib/php-amqplib) library.\n- [_GelfHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/GelfHandler.php): Logs records to a [Graylog2](http://www.graylog2.org) server.\n  Requires package [graylog2/gelf-php](https://github.com/bzikarsky/gelf-php).\n- [_ZendMonitorHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/ZendMonitorHandler.php): Logs records to the Zend Monitor present in Zend Server.\n- [_NewRelicHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/NewRelicHandler.php): Logs records to a [NewRelic](http://newrelic.com/) application.\n- [_LogglyHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/LogglyHandler.php): Logs records to a [Loggly](http://www.loggly.com/) account.\n- [_RollbarHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/RollbarHandler.php): Logs records to a [Rollbar](https://rollbar.com/) account.\n- [_SyslogUdpHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/SyslogUdpHandler.php): Logs records to a remote [Syslogd](http://www.rsyslog.com/) server.\n- [_LogEntriesHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/LogEntriesHandler.php): Logs records to a [LogEntries](http://logentries.com/) account.\n- [_InsightOpsHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/InsightOpsHandler.php): Logs records to an [InsightOps](https://www.rapid7.com/products/insightops/) account.\n- [_LogmaticHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/LogmaticHandler.php): Logs records to a [Logmatic](http://logmatic.io/) account.\n- [_SqsHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/SqsHandler.php): Logs records to an [AWS SQS](http://docs.aws.amazon.com/aws-sdk-php/v2/guide/service-sqs.html) queue.\n- [_RavenHandler_](https://github.com/Seldaek/monolog/blob/1.x/src/Monolog/Handler/RavenHandler.php): Logs records to a [Sentry](http://getsentry.com/) server using\n  [raven](https://packagist.org/packages/raven/raven). **Deprecated** and removed in Monolog 2.0, use sentry/sentry 2.x and the [Sentry\\Monolog\\Handler](https://github.com/getsentry/sentry-php/blob/master/src/Monolog/Handler.php) class instead.\n\n### Logging in development\n\n- [_FirePHPHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/FirePHPHandler.php): Handler for [FirePHP](http://www.firephp.org/), providing\n  inline `console` messages within [FireBug](http://getfirebug.com/).\n- [_ChromePHPHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/ChromePHPHandler.php): Handler for [ChromePHP](http://www.chromephp.com/), providing\n  inline `console` messages within Chrome.\n- [_BrowserConsoleHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/BrowserConsoleHandler.php): Handler to send logs to browser's Javascript `console` with\n  no browser extension required. Most browsers supporting `console` API are supported.\n\n### Log to databases\n\n- [_RedisHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/RedisHandler.php): Logs records to a [redis](http://redis.io) server's key via RPUSH.\n- [_RedisPubSubHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/RedisPubSubHandler.php): Logs records to a [redis](http://redis.io) server's channel via PUBLISH.\n- [_MongoDBHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/MongoDBHandler.php): Handler to write records in MongoDB via a\n  [Mongo](http://pecl.php.net/package/mongo) extension connection.\n- [_CouchDBHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/CouchDBHandler.php): Logs records to a CouchDB server.\n- [_DoctrineCouchDBHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/DoctrineCouchDBHandler.php): Logs records to a CouchDB server via the Doctrine CouchDB ODM.\n- [_ElasticaHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/ElasticaHandler.php): Logs records to an Elasticsearch server using [ruflin/elastica](https://elastica.io/).\n- [_ElasticsearchHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/ElasticsearchHandler.php): Logs records to an Elasticsearch server.\n- [_DynamoDbHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/DynamoDbHandler.php): Logs records to a DynamoDB table with the [AWS SDK](https://github.com/aws/aws-sdk-php).\n\n### Wrappers / Special Handlers\n\n- [_FingersCrossedHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/FingersCrossedHandler.php): A very interesting wrapper. It takes a handler as\n  a parameter and will accumulate log records of all levels until a record\n  exceeds the defined severity level. At which point it delivers all records,\n  including those of lower severity, to the handler it wraps. This means that\n  until an error actually happens you will not see anything in your logs, but\n  when it happens you will have the full information, including debug and info\n  records. This provides you with all the information you need, but only when\n  you need it.\n- [_DeduplicationHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/DeduplicationHandler.php): Useful if you are sending notifications or emails\n  when critical errors occur. It takes a handler as a parameter and will\n  accumulate log records of all levels until the end of the request (or\n  `flush()` is called). At that point it delivers all records to the handler\n  it wraps, but only if the records are unique over a given time period\n  (60seconds by default). If the records are duplicates they are simply\n  discarded. The main use of this is in case of critical failure like if your\n  database is unreachable for example all your requests will fail and that\n  can result in a lot of notifications being sent. Adding this handler reduces\n  the amount of notifications to a manageable level.\n- [_WhatFailureGroupHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/WhatFailureGroupHandler.php): This handler extends the _GroupHandler_ ignoring\n   exceptions raised by each child handler. This allows you to ignore issues\n   where a remote tcp connection may have died but you do not want your entire\n   application to crash and may wish to continue to log to other handlers.\n- [_FallbackGroupHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/FallbackGroupHandler.php): This handler extends the _GroupHandler_ ignoring\n  exceptions raised by each child handler, until one has handled without throwing.\n  This allows you to ignore issues where a remote tcp connection may have died\n  but you do not want your entire application to crash and may wish to continue\n  to attempt logging to other handlers, until one does not throw an exception.\n- [_BufferHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/BufferHandler.php): This handler will buffer all the log records it receives\n  until `close()` is called at which point it will call `handleBatch()` on the\n  handler it wraps with all the log messages at once. This is very useful to\n  send an email with all records at once for example instead of having one mail\n  for every log record.\n- [_GroupHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/GroupHandler.php): This handler groups other handlers. Every record received is\n  sent to all the handlers it is configured with.\n- [_FilterHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/FilterHandler.php): This handler only lets records of the given levels through\n   to the wrapped handler.\n- [_SamplingHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/SamplingHandler.php): Wraps around another handler and lets you sample records\n   if you only want to store some of them.\n- [_NoopHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/NoopHandler.php): This handler handles anything by doing nothing. It does not stop\n  processing the rest of the stack. This can be used for testing, or to disable a handler when overriding a configuration.\n- [_NullHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/NullHandler.php): Any record it can handle will be thrown away. This can be used\n  to put on top of an existing handler stack to disable it temporarily.\n- [_PsrHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/PsrHandler.php): Can be used to forward log records to an existing PSR-3 logger\n- [_TestHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/TestHandler.php): Used for testing, it records everything that is sent to it and\n  has accessors to read out the information.\n- [_HandlerWrapper_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/HandlerWrapper.php): A simple handler wrapper you can inherit from to create\n your own wrappers easily.\n- [_OverflowHandler_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Handler/OverflowHandler.php): This handler will buffer all the log messages it\n  receives, up until a configured threshold of number of messages of a certain level is reached, after it will pass all\n  log messages to the wrapped handler. Useful for applying in batch processing when you're only interested in significant\n  failures instead of minor, single erroneous events.\n\n## Formatters\n\n- [_LineFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/LineFormatter.php): Formats a log record into a one-line string.\n- [_HtmlFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/HtmlFormatter.php): Used to format log records into a human readable html table, mainly suitable for emails.\n- [_NormalizerFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/NormalizerFormatter.php): Normalizes objects/resources down to strings so a record can easily be serialized/encoded.\n- [_ScalarFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/ScalarFormatter.php): Used to format log records into an associative array of scalar values.\n- [_JsonFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/JsonFormatter.php): Encodes a log record into json.\n- [_WildfireFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/WildfireFormatter.php): Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler.\n- [_ChromePHPFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/ChromePHPFormatter.php): Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler.\n- [_GelfMessageFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/GelfMessageFormatter.php): Used to format log records into Gelf message instances, only useful for the GelfHandler.\n- [_LogstashFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/LogstashFormatter.php): Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/latest).\n- [_ElasticaFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/ElasticaFormatter.php): Used to format log records into an Elastica\\Document object, only useful for the ElasticaHandler.\n- [_ElasticsearchFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/ElasticsearchFormatter.php): Used to add index and type keys to log records, only useful for the ElasticsearchHandler.\n- [_LogglyFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/LogglyFormatter.php): Used to format log records into Loggly messages, only useful for the LogglyHandler.\n- [_MongoDBFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/MongoDBFormatter.php): Converts \\DateTime instances to \\MongoDate and objects recursively to arrays, only useful with the MongoDBHandler.\n- [_LogmaticFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/LogmaticFormatter.php): Used to format log records to [Logmatic](http://logmatic.io/) messages, only useful for the LogmaticHandler.\n- [_FluentdFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/FluentdFormatter.php): Used to format log records to [Fluentd](https://www.fluentd.org/) logs, only useful with the SocketHandler.\n- [_GoogleCloudLoggingFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php): Used to format log records for Google Cloud Logging. It works like a JsonFormatter with some minor tweaks.\n- [_SyslogFormatter_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Formatter/SyslogFormatter.php): Used to format log records in RFC 5424 / syslog format. This can be used to output a syslog-style file that can then be consumed by tools like [lnav](https://lnav.org/).\n\n## Processors\n\n- [_PsrLogMessageProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/PsrLogMessageProcessor.php): Processes a log record's message according to PSR-3 rules, replacing `{foo}` with the value from `$context['foo']`.\n- [_LoadAverageProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/LoadAverageProcessor.php): Adds the current system load average to a log record.\n- [_ClosureContextProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/ClosureContextProcessor.php): Allows delaying the creation of context data by setting a Closure in context which is called when the log record is used\n- [_IntrospectionProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/IntrospectionProcessor.php): Adds the line/file/class/method from which the log call originated.\n- [_WebProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/WebProcessor.php): Adds the current request URI, request method and client IP to a log record.\n- [_MemoryUsageProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/MemoryUsageProcessor.php): Adds the current memory usage to a log record.\n- [_MemoryPeakUsageProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/MemoryPeakUsageProcessor.php): Adds the peak memory usage to a log record.\n- [_ProcessIdProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/ProcessIdProcessor.php): Adds the process id to a log record.\n- [_UidProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/UidProcessor.php): Adds a unique identifier to a log record.\n- [_GitProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/GitProcessor.php): Adds the current git branch and commit to a log record.\n- [_MercurialProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/MercurialProcessor.php): Adds the current hg branch and commit to a log record.\n- [_TagProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/TagProcessor.php): Adds an array of predefined tags to a log record.\n- [_HostnameProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/HostnameProcessor.php): Adds the current hostname to a log record.\n\n## Third Party Packages\n\nThird party handlers, formatters and processors are\n[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You\ncan also add your own there if you publish one.\n\n&larr; [Usage](01-usage.md) |  [Utility classes](03-utilities.md) &rarr;\n"
  },
  {
    "path": "doc/03-utilities.md",
    "content": "# Utilities\n\n- _Registry_: The `Monolog\\Registry` class lets you configure global loggers that you\n  can then statically access from anywhere. It is not really a best practice but can\n  help in some older codebases or for ease of use.\n- _ErrorHandler_: The `Monolog\\ErrorHandler` class allows you to easily register\n  a Logger instance as an exception handler, error handler or fatal error handler.\n- _SignalHandler_: The `Monolog\\SignalHandler` class allows you to easily register\n  a Logger instance as a POSIX signal handler.\n- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log\n  level is reached.\n- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain\n  log level is reached, depending on which channel received the log record.\n\n&larr; [Handlers, Formatters and Processors](02-handlers-formatters-processors.md) |  [Extending Monolog](04-extending.md) &rarr;\n"
  },
  {
    "path": "doc/04-extending.md",
    "content": "# Extending Monolog\n\nMonolog is fully extensible, allowing you to adapt your logger to your needs.\n\n## Understanding log records\n\nSee [the page about log records](message-structure.md) to learn what makes up\na log record before going further. This is essential to understand as all\nHandlers/Formatters/Processors need to deal with log records in one way or\nanother.\n\n## Writing your own handler\n\nMonolog provides many built-in handlers. But if the one you need does not\nexist, you can write it and use it in your logger. The only requirement is\nto implement `Monolog\\Handler\\HandlerInterface`.\n\nLet's write a PDOHandler to log records to a database. We will extend the\nabstract class provided by Monolog to keep things DRY.\n\n```php\n<?php\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Monolog\\LogRecord;\nuse Monolog\\Handler\\AbstractProcessingHandler;\n\nclass PDOHandler extends AbstractProcessingHandler\n{\n    private bool $initialized = false;\n    private PDO $pdo;\n    private PDOStatement $statement;\n\n    public function __construct(PDO $pdo, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        $this->pdo = $pdo;\n        parent::__construct($level, $bubble);\n    }\n\n    protected function write(LogRecord $record): void\n    {\n        if (!$this->initialized) {\n            $this->initialize();\n        }\n\n        $this->statement->execute(array(\n            'channel' => $record->channel,\n            'level' => $record->level,\n            'message' => $record->formatted,\n            'time' => $record->datetime->format('U'),\n        ));\n    }\n\n    private function initialize()\n    {\n        $this->pdo->exec(\n            'CREATE TABLE IF NOT EXISTS monolog '\n            .'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)'\n        );\n        $this->statement = $this->pdo->prepare(\n            'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)'\n        );\n\n        $this->initialized = true;\n    }\n}\n```\n\nYou can now use this handler in your logger:\n\n```php\n<?php\n\n$logger->pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite')));\n\n// You can now use your logger\n$logger->info('My logger is now ready');\n```\n\nThe `Monolog\\Handler\\AbstractProcessingHandler` class provides most of the\nlogic needed for the handler, including the use of processors and the formatting\nof the record (which is why we use ``$record->formatted`` instead of ``$record->message``).\n\n&larr; [Utility classes](03-utilities.md)\n"
  },
  {
    "path": "doc/message-structure.md",
    "content": "# Log message structure\n\nWithin monolog log messages are passed around as [Monolog\\LogRecord](../src/Monolog/LogRecord.php) objects,\nfor example to processors or handlers.\n\nThe table below describes the properties available.\n\nproperty   | type                      | description\n-----------|---------------------------|-------------------------------------------------------------------------------\nmessage    | string                    | The log message. When the `PsrLogMessageProcessor` is used this string may contain placeholders that will be replaced by variables from the context, e.g., \"User {username} logged in\" with `['username' => 'John']` as context will be written as \"User John logged in\".\nlevel      | Monolog\\Level case        | Severity of the log message. See log levels described in [01-usage.md](01-usage.md#log-levels).\ncontext    | array                     | Arbitrary data passed with the construction of the message. For example the username of the current user or their IP address.\nchannel    | string                    | The channel this message was logged to. This is the name that was passed when the logger was created with `new Logger('channel')`.\ndatetime   | Monolog\\JsonSerializableDateTimeImmutable | Date and time when the message was logged. Class extends `\\DateTimeImmutable`.\nextra      | array                     | A placeholder array where processors can put additional data. Always available, but empty if there are no processors registered.\n\nAt first glance `context` and `extra` look very similar, and they are in the sense that they both carry arbitrary data that is related to the log message somehow.\nThe main difference is that `context` can be supplied in user land (it is the 3rd parameter to `Psr\\Log\\LoggerInterface` methods) whereas `extra` is internal only\nand can be filled by processors. The reason processors write to `extra` and not to `context` is to prevent overriding any user-provided data in `context`.\n\nAll properties except `extra` are read-only.\n\n> Note: For BC reasons with Monolog 1 and 2 which used arrays, `LogRecord` implements `ArrayAccess` so you can access the above properties\n> using `$record['message']` for example, with the notable exception of `level->getName()` which must be referred to as `level_name` for BC.\n"
  },
  {
    "path": "doc/sockets.md",
    "content": "Sockets Handler\n===============\n\nThis handler allows you to write your logs to sockets using [fsockopen](http://php.net/fsockopen)\nor [pfsockopen](http://php.net/pfsockopen).\n\nPersistent sockets are mainly useful in web environments where you gain some performance not closing/opening\nthe connections between requests.\n\nYou can use a `unix://` prefix to access unix sockets and `udp://` to open UDP sockets instead of the default TCP.\n\nBasic Example\n-------------\n\n```php\n<?php\n\nuse Monolog\\Logger;\nuse Monolog\\Handler\\SocketHandler;\n\n// Create the logger\n$logger = new Logger('my_logger');\n\n// Create the handler\n$handler = new SocketHandler('unix:///var/log/httpd_app_log.socket');\n$handler->setPersistent(true);\n\n// Now add the handler\n$logger->pushHandler($handler, Level::Debug);\n\n// You can now use your logger\n$logger->info('My logger is now ready');\n\n```\n\nIn this example, using syslog-ng, you should see the log on the log server:\n\n    cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] []\n"
  },
  {
    "path": "phpstan-baseline-8.2.neon",
    "content": "parameters:\n\tignoreErrors:\n\t\t-\n\t\t\tmessage: \"#^Call to deprecated function utf8_encode\\\\(\\\\)\\\\.$#\"\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Utils.php\n"
  },
  {
    "path": "phpstan-baseline.neon",
    "content": "parameters:\n\tignoreErrors:\n\t\t-\n\t\t\trawMessage: 'Property Monolog\\ErrorHandler::$reservedMemory is never read, only written.'\n\t\t\tidentifier: property.onlyWritten\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/ErrorHandler.php\n\n\t\t-\n\t\t\trawMessage: 'Return type (array<array<mixed>|bool|float|int|object|string|null>|bool|float|int|object|string|null) of method Monolog\\Formatter\\JsonFormatter::normalize() should be covariant with return type (array<array<mixed>|bool|float|int|string|null>|bool|float|int|string|null) of method Monolog\\Formatter\\NormalizerFormatter::normalize()'\n\t\t\tidentifier: method.childReturnType\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Formatter/JsonFormatter.php\n\n\t\t-\n\t\t\trawMessage: 'Return type (array<array<mixed>|bool|float|int|stdClass|string|null>) of method Monolog\\Formatter\\JsonFormatter::normalizeRecord() should be covariant with return type (array<array<mixed>|bool|float|int|string|null>) of method Monolog\\Formatter\\NormalizerFormatter::normalizeRecord()'\n\t\t\tidentifier: method.childReturnType\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Formatter/JsonFormatter.php\n\n\t\t-\n\t\t\trawMessage: 'Return type (string) of method Monolog\\Formatter\\LineFormatter::normalizeException() should be compatible with return type (array<array<array<string>|int|string>|int|string>) of method Monolog\\Formatter\\NormalizerFormatter::normalizeException()'\n\t\t\tidentifier: method.childReturnType\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Formatter/LineFormatter.php\n\n\t\t-\n\t\t\trawMessage: 'Method Monolog\\Formatter\\NormalizerFormatter::normalizeException() should return array<array<array<string>|int|string>|int|string> but returns array<string, array<array<array<string>|int|string>|int|string>|int|string>.'\n\t\t\tidentifier: return.type\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Formatter/NormalizerFormatter.php\n\n\t\t-\n\t\t\trawMessage: 'Return type (array<array<mixed>|bool|float|int|string|null>|bool|float|int|object|string|null) of method Monolog\\Formatter\\WildfireFormatter::normalize() should be covariant with return type (array<array<mixed>|bool|float|int|string|null>|bool|float|int|string|null) of method Monolog\\Formatter\\NormalizerFormatter::normalize()'\n\t\t\tidentifier: method.childReturnType\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Formatter/WildfireFormatter.php\n\n\t\t-\n\t\t\trawMessage: Access to constant VERSION on an unknown class Elasticsearch\\Client.\n\t\t\tidentifier: class.notFound\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Handler/ElasticsearchHandler.php\n\n\t\t-\n\t\t\trawMessage: 'Call to method bulk() on an unknown class Elasticsearch\\Client.'\n\t\t\tidentifier: class.notFound\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Handler/ElasticsearchHandler.php\n\n\t\t-\n\t\t\trawMessage: 'Method Monolog\\Handler\\ElasticsearchHandler::createExceptionFromError() should return Throwable but returns Elasticsearch\\Common\\Exceptions\\RuntimeException.'\n\t\t\tidentifier: return.type\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Handler/ElasticsearchHandler.php\n\n\t\t-\n\t\t\trawMessage: 'Method Monolog\\Handler\\ElasticsearchHandler::createExceptionFromResponses() should return Throwable but returns Elasticsearch\\Common\\Exceptions\\RuntimeException.'\n\t\t\tidentifier: return.type\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Handler/ElasticsearchHandler.php\n\n\t\t-\n\t\t\trawMessage: 'Parameter $client of method Monolog\\Handler\\ElasticsearchHandler::__construct() has invalid type Elasticsearch\\Client.'\n\t\t\tidentifier: class.notFound\n\t\t\tcount: 2\n\t\t\tpath: src/Monolog/Handler/ElasticsearchHandler.php\n\n\t\t-\n\t\t\trawMessage: Property Monolog\\Handler\\ElasticsearchHandler::$client has unknown class Elasticsearch\\Client as its type.\n\t\t\tidentifier: class.notFound\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Handler/ElasticsearchHandler.php\n\n\t\t-\n\t\t\trawMessage: Short ternary operator is not allowed. Use null coalesce operator if applicable or consider using long ternary.\n\t\t\tidentifier: ternary.shortNotAllowed\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Handler/FingersCrossedHandler.php\n\n\t\t-\n\t\t\trawMessage: 'Call to method setBody() on an unknown class Swift_Message.'\n\t\t\tidentifier: class.notFound\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Handler/MandrillHandler.php\n\n\t\t-\n\t\t\trawMessage: 'Call to method setDate() on an unknown class Swift_Message.'\n\t\t\tidentifier: class.notFound\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Handler/MandrillHandler.php\n\n\t\t-\n\t\t\trawMessage: Class Swift_Message not found.\n\t\t\tidentifier: class.notFound\n\t\t\tcount: 2\n\t\t\tpath: src/Monolog/Handler/MandrillHandler.php\n\n\t\t-\n\t\t\trawMessage: Cloning object of an unknown class Swift_Message.\n\t\t\tidentifier: class.notFound\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Handler/MandrillHandler.php\n\n\t\t-\n\t\t\trawMessage: 'Parameter $message of method Monolog\\Handler\\MandrillHandler::__construct() has invalid type Swift_Message.'\n\t\t\tidentifier: class.notFound\n\t\t\tcount: 3\n\t\t\tpath: src/Monolog/Handler/MandrillHandler.php\n\n\t\t-\n\t\t\trawMessage: Property Monolog\\Handler\\MandrillHandler::$message has unknown class Swift_Message as its type.\n\t\t\tidentifier: class.notFound\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Handler/MandrillHandler.php\n\n\t\t-\n\t\t\trawMessage: 'Variable property access on $this(Monolog\\LogRecord).'\n\t\t\tidentifier: property.dynamicName\n\t\t\tcount: 4\n\t\t\tpath: src/Monolog/LogRecord.php\n\n\t\t-\n\t\t\trawMessage: 'Cannot assign offset Fiber to WeakMap<Fiber<mixed, mixed, mixed, mixed>, int>.'\n\t\t\tidentifier: offsetAssign.dimType\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Logger.php\n\n\t\t-\n\t\t\trawMessage: 'Parameter #1 $level (''alert''|''critical''|''debug''|''emergency''|''error''|''info''|''notice''|''warning''|Monolog\\Level) of method Monolog\\Logger::log() should be contravariant with parameter $level (mixed) of method Psr\\Log\\LoggerInterface::log()'\n\t\t\tidentifier: method.childParameterType\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Logger.php\n\n\t\t-\n\t\t\trawMessage: 'Variable property access on $this(Monolog\\Logger).'\n\t\t\tidentifier: property.dynamicName\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Logger.php\n\n\t\t-\n\t\t\trawMessage: 'Parameter #1 $length of function random_bytes expects int<1, max>, int given.'\n\t\t\tidentifier: argument.type\n\t\t\tcount: 1\n\t\t\tpath: src/Monolog/Processor/UidProcessor.php\n"
  },
  {
    "path": "phpstan-ignore-by-php-version.neon.php",
    "content": "<?php declare(strict_types = 1);\n\n$includes = [];\nif (PHP_VERSION_ID >= 80200) {\n    $includes[] = __DIR__ . '/phpstan-baseline-8.2.neon';\n}\n\n$config['includes'] = $includes;\n\nreturn $config;\n"
  },
  {
    "path": "phpstan.neon.dist",
    "content": "parameters:\n    level: 8\n\n    treatPhpDocTypesAsCertain: false\n    reportUnmatchedIgnoredErrors: true\n\n    paths:\n        - src/\n#        - tests/\n\n    excludePaths:\n        - 'src/Monolog/Handler/PHPConsoleHandler.php'\n\n    ignoreErrors:\n        - '#zend_monitor_|ZEND_MONITOR_#'\n        - '#MongoDB\\\\(Client|Collection)#'\n\nincludes:\n    - phpstan-baseline.neon\n    - phpstan-ignore-by-php-version.neon.php\n    - phar://phpstan.phar/conf/bleedingEdge.neon\n    - vendor/phpstan/phpstan-strict-rules/rules.neon\n    - vendor/phpstan/phpstan-deprecation-rules/rules.neon\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n         bootstrap=\"tests/bootstrap.php\"\n         colors=\"true\"\n         displayDetailsOnTestsThatTriggerWarnings=\"true\"\n         displayDetailsOnSkippedTests=\"true\"\n         displayDetailsOnTestsThatTriggerNotices=\"true\"\n         beStrictAboutTestsThatDoNotTestAnything=\"false\">\n  <testsuites>\n    <testsuite name=\"Monolog Test Suite\">\n      <directory>tests/Monolog/</directory>\n    </testsuite>\n  </testsuites>\n\n  <coverage/>\n\n  <php>\n    <ini name=\"date.timezone\" value=\"UTC\"/>\n  </php>\n\n  <source>\n    <include>\n      <directory suffix=\".php\">src/Monolog/</directory>\n    </include>\n  </source>\n\n</phpunit>\n"
  },
  {
    "path": "src/Monolog/Attribute/AsMonologProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Attribute;\n\n/**\n * A reusable attribute to help configure a class or a method as a processor.\n *\n * Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer.\n *\n * Using it with the Monolog library only has no effect at all: processors should still be turned into a callable if\n * needed and manually pushed to the loggers and to the processable handlers.\n */\n#[\\Attribute(\\Attribute::TARGET_CLASS | \\Attribute::TARGET_METHOD | \\Attribute::IS_REPEATABLE)]\nclass AsMonologProcessor\n{\n    /**\n     * @param string|null $channel  The logging channel the processor should be pushed to.\n     * @param string|null $handler  The handler the processor should be pushed to.\n     * @param string|null $method   The method that processes the records (if the attribute is used at the class level).\n     * @param int|null    $priority The priority of the processor so the order can be determined.\n     */\n    public function __construct(\n        public readonly ?string $channel = null,\n        public readonly ?string $handler = null,\n        public readonly ?string $method = null,\n        public readonly ?int $priority = null\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Attribute/WithMonologChannel.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Attribute;\n\n/**\n * A reusable attribute to help configure a class as expecting a given logger channel.\n *\n * Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer.\n *\n * Using it with the Monolog library only has no effect at all: wiring the logger instance into\n * other classes is not managed by Monolog.\n */\n#[\\Attribute(\\Attribute::TARGET_CLASS)]\nfinal class WithMonologChannel\n{\n    public function __construct(\n        public readonly string $channel\n    ) {\n    }\n}\n"
  },
  {
    "path": "src/Monolog/DateTimeImmutable.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nclass_alias(JsonSerializableDateTimeImmutable::class, 'Monolog\\DateTimeImmutable');\n\n// @phpstan-ignore-next-line\nif (false) {\n    /**\n     * @deprecated Use \\Monolog\\JsonSerializableDateTimeImmutable instead.\n     */\n    class DateTimeImmutable extends JsonSerializableDateTimeImmutable\n    {\n    }\n}\n"
  },
  {
    "path": "src/Monolog/ErrorHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse Closure;\nuse Psr\\Log\\LoggerInterface;\nuse Psr\\Log\\LogLevel;\n\n/**\n * Monolog error handler\n *\n * A facility to enable logging of runtime errors, exceptions and fatal errors.\n *\n * Quick setup: <code>ErrorHandler::register($logger);</code>\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass ErrorHandler\n{\n    private Closure|null $previousExceptionHandler = null;\n\n    /** @var array<class-string, LogLevel::*> an array of class name to LogLevel::* constant mapping */\n    private array $uncaughtExceptionLevelMap = [];\n\n    /** @var Closure|true|null */\n    private Closure|bool|null $previousErrorHandler = null;\n\n    /** @var array<int, LogLevel::*> an array of E_* constant to LogLevel::* constant mapping */\n    private array $errorLevelMap = [];\n\n    private bool $handleOnlyReportedErrors = true;\n\n    private bool $hasFatalErrorHandler = false;\n\n    private string $fatalLevel = LogLevel::ALERT;\n\n    private string|null $reservedMemory = null;\n\n    /** @var ?array{type: int, message: string, file: string, line: int, trace: mixed} */\n    private array|null $lastFatalData = null;\n\n    private const FATAL_ERRORS = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];\n\n    public function __construct(\n        private LoggerInterface $logger\n    ) {\n    }\n\n    /**\n     * Registers a new ErrorHandler for a given Logger\n     *\n     * By default it will handle errors, exceptions and fatal errors\n     *\n     * @param  array<int, LogLevel::*>|false          $errorLevelMap     an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling\n     * @param  array<class-string, LogLevel::*>|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling\n     * @param  LogLevel::*|null|false                 $fatalLevel        a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling\n     * @return static\n     */\n    public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self\n    {\n        /** @phpstan-ignore-next-line */\n        $handler = new static($logger);\n        if ($errorLevelMap !== false) {\n            $handler->registerErrorHandler($errorLevelMap);\n        }\n        if ($exceptionLevelMap !== false) {\n            $handler->registerExceptionHandler($exceptionLevelMap);\n        }\n        if ($fatalLevel !== false) {\n            $handler->registerFatalHandler($fatalLevel);\n        }\n\n        return $handler;\n    }\n\n    /**\n     * @param  array<class-string, LogLevel::*> $levelMap an array of class name to LogLevel::* constant mapping\n     * @return $this\n     */\n    public function registerExceptionHandler(array $levelMap = [], bool $callPrevious = true): self\n    {\n        $prev = set_exception_handler(function (\\Throwable $e): void {\n            $this->handleException($e);\n        });\n        $this->uncaughtExceptionLevelMap = $levelMap;\n        foreach ($this->defaultExceptionLevelMap() as $class => $level) {\n            if (!isset($this->uncaughtExceptionLevelMap[$class])) {\n                $this->uncaughtExceptionLevelMap[$class] = $level;\n            }\n        }\n        if ($callPrevious && null !== $prev) {\n            $this->previousExceptionHandler = $prev(...);\n        }\n\n        return $this;\n    }\n\n    /**\n     * @param  array<int, LogLevel::*> $levelMap an array of E_* constant to LogLevel::* constant mapping\n     * @return $this\n     */\n    public function registerErrorHandler(array $levelMap = [], bool $callPrevious = true, int $errorTypes = -1, bool $handleOnlyReportedErrors = true): self\n    {\n        $prev = set_error_handler($this->handleError(...), $errorTypes);\n        $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);\n        if ($callPrevious) {\n            $this->previousErrorHandler = $prev !== null ? $prev(...) : true;\n        } else {\n            $this->previousErrorHandler = null;\n        }\n\n        $this->handleOnlyReportedErrors = $handleOnlyReportedErrors;\n\n        return $this;\n    }\n\n    /**\n     * @param  LogLevel::*|null $level              a LogLevel::* constant, null to use the default LogLevel::ALERT\n     * @param  int              $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done\n     * @return $this\n     */\n    public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self\n    {\n        register_shutdown_function($this->handleFatalError(...));\n\n        $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);\n        $this->fatalLevel = null === $level ? LogLevel::ALERT : $level;\n        $this->hasFatalErrorHandler = true;\n\n        return $this;\n    }\n\n    /**\n     * @return array<class-string, LogLevel::*>\n     */\n    protected function defaultExceptionLevelMap(): array\n    {\n        return [\n            'ParseError' => LogLevel::CRITICAL,\n            'Throwable' => LogLevel::ERROR,\n        ];\n    }\n\n    /**\n     * @return array<int, LogLevel::*>\n     */\n    protected function defaultErrorLevelMap(): array\n    {\n        return [\n            E_ERROR             => LogLevel::CRITICAL,\n            E_WARNING           => LogLevel::WARNING,\n            E_PARSE             => LogLevel::ALERT,\n            E_NOTICE            => LogLevel::NOTICE,\n            E_CORE_ERROR        => LogLevel::CRITICAL,\n            E_CORE_WARNING      => LogLevel::WARNING,\n            E_COMPILE_ERROR     => LogLevel::ALERT,\n            E_COMPILE_WARNING   => LogLevel::WARNING,\n            E_USER_ERROR        => LogLevel::ERROR,\n            E_USER_WARNING      => LogLevel::WARNING,\n            E_USER_NOTICE       => LogLevel::NOTICE,\n            2048                => LogLevel::NOTICE, // E_STRICT\n            E_RECOVERABLE_ERROR => LogLevel::ERROR,\n            E_DEPRECATED        => LogLevel::NOTICE,\n            E_USER_DEPRECATED   => LogLevel::NOTICE,\n        ];\n    }\n\n    private function handleException(\\Throwable $e): never\n    {\n        $level = LogLevel::ERROR;\n        foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) {\n            if ($e instanceof $class) {\n                $level = $candidate;\n                break;\n            }\n        }\n\n        $this->logger->log(\n            $level,\n            sprintf('Uncaught Exception %s: \"%s\" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()),\n            ['exception' => $e]\n        );\n\n        if (null !== $this->previousExceptionHandler) {\n            ($this->previousExceptionHandler)($e);\n        }\n\n        if (!headers_sent() && \\in_array(strtolower((string) \\ini_get('display_errors')), ['0', '', 'false', 'off', 'none', 'no'], true)) {\n            http_response_code(500);\n        }\n\n        exit(255);\n    }\n\n    private function handleError(int $code, string $message, string $file = '', int $line = 0): bool\n    {\n        if ($this->handleOnlyReportedErrors && 0 === (error_reporting() & $code)) {\n            return false;\n        }\n\n        // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries\n        if (!$this->hasFatalErrorHandler || !\\in_array($code, self::FATAL_ERRORS, true)) {\n            $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL;\n            $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]);\n        } else {\n            $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);\n            array_shift($trace); // Exclude handleError from trace\n            $this->lastFatalData = ['type' => $code, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $trace];\n        }\n\n        if ($this->previousErrorHandler === true) {\n            return false;\n        }\n        if ($this->previousErrorHandler instanceof Closure) {\n            return (bool) ($this->previousErrorHandler)($code, $message, $file, $line);\n        }\n\n        return true;\n    }\n\n    /**\n     * @private\n     */\n    public function handleFatalError(): void\n    {\n        $this->reservedMemory = '';\n\n        if (\\is_array($this->lastFatalData)) {\n            $lastError = $this->lastFatalData;\n        } else {\n            $lastError = error_get_last();\n        }\n        if (\\is_array($lastError) && \\in_array($lastError['type'], self::FATAL_ERRORS, true)) {\n            $trace = $lastError['trace'] ?? null;\n            $this->logger->log(\n                $this->fatalLevel,\n                'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],\n                ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $trace]\n            );\n\n            if ($this->logger instanceof Logger) {\n                foreach ($this->logger->getHandlers() as $handler) {\n                    $handler->close();\n                }\n            }\n        }\n    }\n\n    private static function codeToString(int $code): string\n    {\n        return match ($code) {\n            E_ERROR => 'E_ERROR',\n            E_WARNING => 'E_WARNING',\n            E_PARSE => 'E_PARSE',\n            E_NOTICE => 'E_NOTICE',\n            E_CORE_ERROR => 'E_CORE_ERROR',\n            E_CORE_WARNING => 'E_CORE_WARNING',\n            E_COMPILE_ERROR => 'E_COMPILE_ERROR',\n            E_COMPILE_WARNING => 'E_COMPILE_WARNING',\n            E_USER_ERROR => 'E_USER_ERROR',\n            E_USER_WARNING => 'E_USER_WARNING',\n            E_USER_NOTICE => 'E_USER_NOTICE',\n            2048 => 'E_STRICT',\n            E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',\n            E_DEPRECATED => 'E_DEPRECATED',\n            E_USER_DEPRECATED => 'E_USER_DEPRECATED',\n            default => 'Unknown PHP error',\n        };\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/ChromePHPFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * Formats a log message according to the ChromePHP array format\n *\n * @author Christophe Coevoet <stof@notk.org>\n */\nclass ChromePHPFormatter implements FormatterInterface\n{\n    /**\n     * Translates Monolog log levels to Wildfire levels.\n     *\n     * @return 'log'|'info'|'warn'|'error'\n     */\n    private function toWildfireLevel(Level $level): string\n    {\n        return match ($level) {\n            Level::Debug     => 'log',\n            Level::Info      => 'info',\n            Level::Notice    => 'info',\n            Level::Warning   => 'warn',\n            Level::Error     => 'error',\n            Level::Critical  => 'error',\n            Level::Alert     => 'error',\n            Level::Emergency => 'error',\n        };\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function format(LogRecord $record)\n    {\n        // Retrieve the line and file if set and remove them from the formatted extra\n        $backtrace = 'unknown';\n        if (isset($record->extra['file'], $record->extra['line'])) {\n            $backtrace = $record->extra['file'].' : '.$record->extra['line'];\n            unset($record->extra['file'], $record->extra['line']);\n        }\n\n        $message = ['message' => $record->message];\n        if (\\count($record->context) > 0) {\n            $message['context'] = $record->context;\n        }\n        if (\\count($record->extra) > 0) {\n            $message['extra'] = $record->extra;\n        }\n        if (\\count($message) === 1) {\n            $message = reset($message);\n        }\n\n        return [\n            $record->channel,\n            $message,\n            $backtrace,\n            $this->toWildfireLevel($record->level),\n        ];\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function formatBatch(array $records)\n    {\n        $formatted = [];\n\n        foreach ($records as $record) {\n            $formatted[] = $this->format($record);\n        }\n\n        return $formatted;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/ElasticaFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Elastica\\Document;\nuse Monolog\\LogRecord;\n\n/**\n * Format a log message into an Elastica Document\n *\n * @author Jelle Vink <jelle.vink@gmail.com>\n */\nclass ElasticaFormatter extends NormalizerFormatter\n{\n    /**\n     * @var string Elastic search index name\n     */\n    protected string $index;\n\n    /**\n     * @var string|null Elastic search document type\n     */\n    protected string|null $type;\n\n    /**\n     * @param string  $index Elastic Search index name\n     * @param ?string $type  Elastic Search document type, deprecated as of Elastica 7\n     */\n    public function __construct(string $index, ?string $type)\n    {\n        // elasticsearch requires a ISO 8601 format date with optional millisecond precision.\n        parent::__construct('Y-m-d\\TH:i:s.uP');\n\n        $this->index = $index;\n        $this->type = $type;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function format(LogRecord $record)\n    {\n        $record = parent::format($record);\n\n        return $this->getDocument($record);\n    }\n\n    public function getIndex(): string\n    {\n        return $this->index;\n    }\n\n    /**\n     * @deprecated since Elastica 7 type has no effect\n     */\n    public function getType(): string\n    {\n        /** @phpstan-ignore-next-line */\n        return $this->type;\n    }\n\n    /**\n     * Convert a log message into an Elastica Document\n     *\n     * @param mixed[] $record\n     */\n    protected function getDocument(array $record): Document\n    {\n        $document = new Document();\n        $document->setData($record);\n        $document->setIndex($this->index);\n\n        return $document;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/ElasticsearchFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse DateTimeInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Format a log message into an Elasticsearch record\n *\n * @author Avtandil Kikabidze <akalongman@gmail.com>\n */\nclass ElasticsearchFormatter extends NormalizerFormatter\n{\n    /**\n     * @var string Elasticsearch index name\n     */\n    protected string $index;\n\n    /**\n     * @var string Elasticsearch record type\n     */\n    protected string $type;\n\n    /**\n     * @param string $index Elasticsearch index name\n     * @param string $type  Elasticsearch record type\n     */\n    public function __construct(string $index, string $type)\n    {\n        // Elasticsearch requires an ISO 8601 format date with optional millisecond precision.\n        parent::__construct(DateTimeInterface::ATOM);\n\n        $this->index = $index;\n        $this->type = $type;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function format(LogRecord $record)\n    {\n        $record = parent::format($record);\n\n        return $this->getDocument($record);\n    }\n\n    /**\n     * Getter index\n     */\n    public function getIndex(): string\n    {\n        return $this->index;\n    }\n\n    /**\n     * Getter type\n     */\n    public function getType(): string\n    {\n        return $this->type;\n    }\n\n    /**\n     * Convert a log message into an Elasticsearch record\n     *\n     * @param  mixed[] $record Log message\n     * @return mixed[]\n     */\n    protected function getDocument(array $record): array\n    {\n        $record['_index'] = $this->index;\n        $record['_type'] = $this->type;\n\n        return $record;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/FlowdockFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\LogRecord;\n\n/**\n * formats the record to be used in the FlowdockHandler\n *\n * @author Dominik Liebler <liebler.dominik@gmail.com>\n * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4\n */\nclass FlowdockFormatter implements FormatterInterface\n{\n    private string $source;\n\n    private string $sourceEmail;\n\n    public function __construct(string $source, string $sourceEmail)\n    {\n        $this->source = $source;\n        $this->sourceEmail = $sourceEmail;\n    }\n\n    /**\n     * @inheritDoc\n     *\n     * @return mixed[]\n     */\n    public function format(LogRecord $record): array\n    {\n        $tags = [\n            '#logs',\n            '#' . $record->level->toPsrLogLevel(),\n            '#' . $record->channel,\n        ];\n\n        foreach ($record->extra as $value) {\n            $tags[] = '#' . $value;\n        }\n\n        $subject = sprintf(\n            'in %s: %s - %s',\n            $this->source,\n            $record->level->getName(),\n            $this->getShortMessage($record->message)\n        );\n\n        return [\n            'source' => $this->source,\n            'from_address' => $this->sourceEmail,\n            'subject' => $subject,\n            'content' => $record->message,\n            'tags' => $tags,\n            'project' => $this->source,\n        ];\n    }\n\n    /**\n     * @inheritDoc\n     *\n     * @return mixed[][]\n     */\n    public function formatBatch(array $records): array\n    {\n        $formatted = [];\n\n        foreach ($records as $record) {\n            $formatted[] = $this->format($record);\n        }\n\n        return $formatted;\n    }\n\n    public function getShortMessage(string $message): string\n    {\n        static $hasMbString;\n\n        if (null === $hasMbString) {\n            $hasMbString = \\function_exists('mb_strlen');\n        }\n\n        $maxLength = 45;\n\n        if ($hasMbString) {\n            if (mb_strlen($message, 'UTF-8') > $maxLength) {\n                $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...';\n            }\n        } else {\n            if (\\strlen($message) > $maxLength) {\n                $message = substr($message, 0, $maxLength - 4) . ' ...';\n            }\n        }\n\n        return $message;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/FluentdFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Class FluentdFormatter\n *\n * Serializes a log message to Fluentd unix socket protocol\n *\n * Fluentd config:\n *\n * <source>\n *  type unix\n *  path /var/run/td-agent/td-agent.sock\n * </source>\n *\n * Monolog setup:\n *\n * $logger = new Monolog\\Logger('fluent.tag');\n * $fluentHandler = new Monolog\\Handler\\SocketHandler('unix:///var/run/td-agent/td-agent.sock');\n * $fluentHandler->setFormatter(new Monolog\\Formatter\\FluentdFormatter());\n * $logger->pushHandler($fluentHandler);\n *\n * @author Andrius Putna <fordnox@gmail.com>\n */\nclass FluentdFormatter implements FormatterInterface\n{\n    /**\n     * @var bool $levelTag should message level be a part of the fluentd tag\n     */\n    protected bool $levelTag = false;\n\n    public function __construct(bool $levelTag = false)\n    {\n        $this->levelTag = $levelTag;\n    }\n\n    public function isUsingLevelsInTag(): bool\n    {\n        return $this->levelTag;\n    }\n\n    public function format(LogRecord $record): string\n    {\n        $tag = $record->channel;\n        if ($this->levelTag) {\n            $tag .= '.' . $record->level->toPsrLogLevel();\n        }\n\n        $message = [\n            'message' => $record->message,\n            'context' => $record->context,\n            'extra' => $record->extra,\n        ];\n\n        if (!$this->levelTag) {\n            $message['level'] = $record->level->value;\n            $message['level_name'] = $record->level->getName();\n        }\n\n        return Utils::jsonEncode([$tag, $record->datetime->getTimestamp(), $message]);\n    }\n\n    public function formatBatch(array $records): string\n    {\n        $message = '';\n        foreach ($records as $record) {\n            $message .= $this->format($record);\n        }\n\n        return $message;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/FormatterInterface.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\LogRecord;\n\n/**\n * Interface for formatters\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\ninterface FormatterInterface\n{\n    /**\n     * Formats a log record.\n     *\n     * @param  LogRecord $record A record to format\n     * @return mixed     The formatted record\n     */\n    public function format(LogRecord $record);\n\n    /**\n     * Formats a set of log records.\n     *\n     * @param  array<LogRecord> $records A set of records to format\n     * @return mixed            The formatted set of records\n     */\n    public function formatBatch(array $records);\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/GelfMessageFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Gelf\\Message;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Serializes a log message to GELF\n * @see http://docs.graylog.org/en/latest/pages/gelf.html\n *\n * @author Matt Lehner <mlehner@gmail.com>\n */\nclass GelfMessageFormatter extends NormalizerFormatter\n{\n    protected const DEFAULT_MAX_LENGTH = 32766;\n\n    /**\n     * @var string the name of the system for the Gelf log message\n     */\n    protected string $systemName;\n\n    /**\n     * @var string a prefix for 'extra' fields from the Monolog record (optional)\n     */\n    protected string $extraPrefix;\n\n    /**\n     * @var string a prefix for 'context' fields from the Monolog record (optional)\n     */\n    protected string $contextPrefix;\n\n    /**\n     * @var int max length per field\n     */\n    protected int $maxLength;\n\n    /**\n     * Translates Monolog log levels to Graylog2 log priorities.\n     */\n    private function getGraylog2Priority(Level $level): int\n    {\n        return match ($level) {\n            Level::Debug     => 7,\n            Level::Info      => 6,\n            Level::Notice    => 5,\n            Level::Warning   => 4,\n            Level::Error     => 3,\n            Level::Critical  => 2,\n            Level::Alert     => 1,\n            Level::Emergency => 0,\n        };\n    }\n\n    /**\n     * @throws \\RuntimeException\n     */\n    public function __construct(?string $systemName = null, ?string $extraPrefix = null, string $contextPrefix = 'ctxt_', ?int $maxLength = null)\n    {\n        if (!class_exists(Message::class)) {\n            throw new \\RuntimeException('Composer package graylog2/gelf-php is required to use Monolog\\'s GelfMessageFormatter');\n        }\n\n        parent::__construct('U.u');\n\n        $this->systemName = (null === $systemName || $systemName === '') ? (string) gethostname() : $systemName;\n\n        $this->extraPrefix = null === $extraPrefix ? '' : $extraPrefix;\n        $this->contextPrefix = $contextPrefix;\n        $this->maxLength = null === $maxLength ? self::DEFAULT_MAX_LENGTH : $maxLength;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function format(LogRecord $record): Message\n    {\n        $context = $extra = [];\n        if (isset($record->context)) {\n            /** @var array<array<mixed>|bool|float|int|string|null> $context */\n            $context = parent::normalize($record->context);\n        }\n        if (isset($record->extra)) {\n            /** @var array<array<mixed>|bool|float|int|string|null> $extra */\n            $extra = parent::normalize($record->extra);\n        }\n\n        $message = new Message();\n        $message\n            ->setTimestamp($record->datetime)\n            ->setShortMessage($record->message)\n            ->setHost($this->systemName)\n            ->setLevel($this->getGraylog2Priority($record->level));\n\n        // message length + system name length + 200 for padding / metadata\n        $len = 200 + \\strlen($record->message) + \\strlen($this->systemName);\n\n        if ($len > $this->maxLength) {\n            $message->setShortMessage(Utils::substr($record->message, 0, $this->maxLength));\n        }\n\n        if (isset($record->channel)) {\n            $message->setAdditional('facility', $record->channel);\n        }\n\n        foreach ($extra as $key => $val) {\n            $key = (string) preg_replace('#[^\\w.-]#', '-', (string) $key);\n            $val = \\is_bool($val) ? ($val ? 1 : 0) : $val;\n            $val = \\is_scalar($val) || null === $val ? $val : $this->toJson($val);\n            $len = \\strlen($this->extraPrefix . $key . $val);\n            if ($len > $this->maxLength) {\n                $message->setAdditional($this->extraPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength));\n\n                continue;\n            }\n            $message->setAdditional($this->extraPrefix . $key, $val);\n        }\n\n        foreach ($context as $key => $val) {\n            $key = (string) preg_replace('#[^\\w.-]#', '-', (string) $key);\n            $val = \\is_bool($val) ? ($val ? 1 : 0) : $val;\n            $val = \\is_scalar($val) || null === $val ? $val : $this->toJson($val);\n            $len = \\strlen($this->contextPrefix . $key . $val);\n            if ($len > $this->maxLength) {\n                $message->setAdditional($this->contextPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength));\n\n                continue;\n            }\n            $message->setAdditional($this->contextPrefix . $key, $val);\n        }\n\n        if (!$message->hasAdditional('file') && isset($context['exception']['file'])) {\n            if (1 === preg_match(\"/^(.+):([0-9]+)$/\", $context['exception']['file'], $matches)) {\n                $message->setAdditional('file', $matches[1]);\n                $message->setAdditional('line', $matches[2]);\n            }\n        }\n\n        return $message;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/GoogleCloudLoggingFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse DateTimeInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Encodes message information into JSON in a format compatible with Cloud logging.\n *\n * @see https://cloud.google.com/logging/docs/structured-logging\n * @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry\n *\n * @author Luís Cobucci <lcobucci@gmail.com>\n */\nclass GoogleCloudLoggingFormatter extends JsonFormatter\n{\n    protected function normalizeRecord(LogRecord $record): array\n    {\n        $normalized = parent::normalizeRecord($record);\n\n        // Re-key level for GCP logging\n        $normalized['severity'] = $normalized['level_name'];\n        $normalized['time'] = $record->datetime->format(DateTimeInterface::RFC3339_EXTENDED);\n\n        // Remove keys that are not used by GCP\n        unset($normalized['level'], $normalized['level_name'], $normalized['datetime']);\n\n        return $normalized;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/HtmlFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Formats incoming records into an HTML table\n *\n * This is especially useful for html email logging\n *\n * @author Tiago Brito <tlfbrito@gmail.com>\n */\nclass HtmlFormatter extends NormalizerFormatter\n{\n    /**\n     * Translates Monolog log levels to html color priorities.\n     */\n    protected function getLevelColor(Level $level): string\n    {\n        return match ($level) {\n            Level::Debug     => '#CCCCCC',\n            Level::Info      => '#28A745',\n            Level::Notice    => '#17A2B8',\n            Level::Warning   => '#FFC107',\n            Level::Error     => '#FD7E14',\n            Level::Critical  => '#DC3545',\n            Level::Alert     => '#821722',\n            Level::Emergency => '#000000',\n        };\n    }\n\n    /**\n     * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format\n     */\n    public function __construct(?string $dateFormat = null)\n    {\n        parent::__construct($dateFormat);\n    }\n\n    /**\n     * Creates an HTML table row\n     *\n     * @param string $th       Row header content\n     * @param string $td       Row standard cell content\n     * @param bool   $escapeTd false if td content must not be html escaped\n     */\n    protected function addRow(string $th, string $td = ' ', bool $escapeTd = true): string\n    {\n        $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8');\n        if ($escapeTd) {\n            $td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>';\n        }\n\n        return \"<tr style=\\\"padding: 4px;text-align: left;\\\">\\n<th style=\\\"vertical-align: top;background: #ccc;color: #000\\\" width=\\\"100\\\">$th:</th>\\n<td style=\\\"padding: 4px;text-align: left;vertical-align: top;background: #eee;color: #000\\\">\".$td.\"</td>\\n</tr>\";\n    }\n\n    /**\n     * Create a HTML h1 tag\n     *\n     * @param string $title Text to be in the h1\n     */\n    protected function addTitle(string $title, Level $level): string\n    {\n        $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');\n\n        return '<h1 style=\"background: '.$this->getLevelColor($level).';color: #ffffff;padding: 5px;\" class=\"monolog-output\">'.$title.'</h1>';\n    }\n\n    /**\n     * Formats a log record.\n     *\n     * @return string The formatted record\n     */\n    public function format(LogRecord $record): string\n    {\n        $output = $this->addTitle($record->level->getName(), $record->level);\n        $output .= '<table cellspacing=\"1\" width=\"100%\" class=\"monolog-output\">';\n\n        $output .= $this->addRow('Message', $record->message);\n        $output .= $this->addRow('Time', $this->formatDate($record->datetime));\n        $output .= $this->addRow('Channel', $record->channel);\n        if (\\count($record->context) > 0) {\n            $embeddedTable = '<table cellspacing=\"1\" width=\"100%\">';\n            foreach ($record->context as $key => $value) {\n                $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value));\n            }\n            $embeddedTable .= '</table>';\n            $output .= $this->addRow('Context', $embeddedTable, false);\n        }\n        if (\\count($record->extra) > 0) {\n            $embeddedTable = '<table cellspacing=\"1\" width=\"100%\">';\n            foreach ($record->extra as $key => $value) {\n                $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value));\n            }\n            $embeddedTable .= '</table>';\n            $output .= $this->addRow('Extra', $embeddedTable, false);\n        }\n\n        return $output.'</table>';\n    }\n\n    /**\n     * Formats a set of log records.\n     *\n     * @return string The formatted set of records\n     */\n    public function formatBatch(array $records): string\n    {\n        $message = '';\n        foreach ($records as $record) {\n            $message .= $this->format($record);\n        }\n\n        return $message;\n    }\n\n    /**\n     * @param mixed $data\n     */\n    protected function convertToString($data): string\n    {\n        if (null === $data || \\is_scalar($data)) {\n            return (string) $data;\n        }\n\n        $data = $this->normalize($data);\n\n        return Utils::jsonEncode($data, JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, true);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/JsonFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Stringable;\nuse Throwable;\nuse Monolog\\LogRecord;\n\n/**\n * Encodes whatever record data is passed to it as json\n *\n * This can be useful to log to databases or remote APIs\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass JsonFormatter extends NormalizerFormatter\n{\n    public const BATCH_MODE_JSON = 1;\n    public const BATCH_MODE_NEWLINES = 2;\n\n    /** @var self::BATCH_MODE_* */\n    protected int $batchMode;\n\n    protected bool $appendNewline;\n\n    protected bool $ignoreEmptyContextAndExtra;\n\n    protected bool $includeStacktraces = false;\n\n    /**\n     * @param self::BATCH_MODE_* $batchMode\n     */\n    public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false)\n    {\n        $this->batchMode = $batchMode;\n        $this->appendNewline = $appendNewline;\n        $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;\n        $this->includeStacktraces = $includeStacktraces;\n\n        parent::__construct();\n    }\n\n    /**\n     * The batch mode option configures the formatting style for\n     * multiple records. By default, multiple records will be\n     * formatted as a JSON-encoded array. However, for\n     * compatibility with some API endpoints, alternative styles\n     * are available.\n     */\n    public function getBatchMode(): int\n    {\n        return $this->batchMode;\n    }\n\n    /**\n     * True if newlines are appended to every formatted record\n     */\n    public function isAppendingNewlines(): bool\n    {\n        return $this->appendNewline;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function format(LogRecord $record): string\n    {\n        $normalized = $this->normalizeRecord($record);\n\n        return $this->toJson($normalized, true) . ($this->appendNewline ? \"\\n\" : '');\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function formatBatch(array $records): string\n    {\n        return match ($this->batchMode) {\n            static::BATCH_MODE_NEWLINES => $this->formatBatchNewlines($records),\n            default => $this->formatBatchJson($records),\n        };\n    }\n\n    /**\n     * @return $this\n     */\n    public function includeStacktraces(bool $include = true): self\n    {\n        $this->includeStacktraces = $include;\n\n        return $this;\n    }\n\n    /**\n     * @return array<array<mixed>|bool|float|int|\\stdClass|string|null>\n     */\n    protected function normalizeRecord(LogRecord $record): array\n    {\n        $normalized = parent::normalizeRecord($record);\n\n        if (isset($normalized['context']) && $normalized['context'] === []) {\n            if ($this->ignoreEmptyContextAndExtra) {\n                unset($normalized['context']);\n            } else {\n                $normalized['context'] = new \\stdClass;\n            }\n        }\n        if (isset($normalized['extra']) && $normalized['extra'] === []) {\n            if ($this->ignoreEmptyContextAndExtra) {\n                unset($normalized['extra']);\n            } else {\n                $normalized['extra'] = new \\stdClass;\n            }\n        }\n\n        return $normalized;\n    }\n\n    /**\n     * Return a JSON-encoded array of records.\n     *\n     * @phpstan-param LogRecord[] $records\n     */\n    protected function formatBatchJson(array $records): string\n    {\n        $formatted = array_map(fn (LogRecord $record) => $this->normalizeRecord($record), $records);\n\n        return $this->toJson($formatted, true);\n    }\n\n    /**\n     * Use new lines to separate records instead of a\n     * JSON-encoded array.\n     *\n     * @phpstan-param LogRecord[] $records\n     */\n    protected function formatBatchNewlines(array $records): string\n    {\n        $oldNewline = $this->appendNewline;\n        $this->appendNewline = false;\n        $formatted = array_map(fn (LogRecord $record) => $this->format($record), $records);\n        $this->appendNewline = $oldNewline;\n\n        return implode(\"\\n\", $formatted);\n    }\n\n    /**\n     * Normalizes given $data.\n     *\n     * @return null|scalar|array<mixed[]|scalar|null|object>|object\n     */\n    protected function normalize(mixed $data, int $depth = 0): mixed\n    {\n        if ($depth > $this->maxNormalizeDepth) {\n            return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization';\n        }\n\n        if (\\is_array($data)) {\n            $normalized = [];\n\n            $count = 1;\n            foreach ($data as $key => $value) {\n                if ($count++ > $this->maxNormalizeItemCount) {\n                    $normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.\\count($data).' total), aborting normalization';\n                    break;\n                }\n\n                $normalized[$key] = $this->normalize($value, $depth + 1);\n            }\n\n            return $normalized;\n        }\n\n        if (\\is_object($data)) {\n            if ($data instanceof \\DateTimeInterface) {\n                return $this->formatDate($data);\n            }\n\n            if ($data instanceof Throwable) {\n                return $this->normalizeException($data, $depth);\n            }\n\n            // if the object has specific json serializability we want to make sure we skip the __toString treatment below\n            if ($data instanceof \\JsonSerializable) {\n                return $data;\n            }\n\n            if ($data instanceof Stringable) {\n                try {\n                    return $data->__toString();\n                } catch (Throwable) {\n                    return $data::class;\n                }\n            }\n\n            if (\\get_class($data) === '__PHP_Incomplete_Class') {\n                return new \\ArrayObject($data);\n            }\n\n            return $data;\n        }\n\n        if (\\is_resource($data)) {\n            return parent::normalize($data);\n        }\n\n        return $data;\n    }\n\n    /**\n     * Normalizes given exception with or without its own stack trace based on\n     * `includeStacktraces` property.\n     *\n     * @return array<array-key, string|int|array<string|int|array<string>>>\n     */\n    protected function normalizeException(Throwable $e, int $depth = 0): array\n    {\n        $data = parent::normalizeException($e, $depth);\n        if (!$this->includeStacktraces) {\n            unset($data['trace']);\n        }\n\n        return $data;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/LineFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Closure;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Formats incoming records into a one-line string\n *\n * This is especially useful for logging to files\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n * @author Christophe Coevoet <stof@notk.org>\n */\nclass LineFormatter extends NormalizerFormatter\n{\n    public const SIMPLE_FORMAT = \"[%datetime%] %channel%.%level_name%: %message% %context% %extra%\\n\";\n\n    protected string $format;\n    protected bool $allowInlineLineBreaks;\n    protected bool $ignoreEmptyContextAndExtra;\n    protected bool $includeStacktraces;\n    protected ?int $maxLevelNameLength = null;\n    protected string $indentStacktraces = '';\n    protected Closure|null $stacktracesParser = null;\n    protected string $basePath = '';\n\n    /**\n     * @param string|null $format                The format of the message\n     * @param string|null $dateFormat            The format of the timestamp: one supported by DateTime::format\n     * @param bool        $allowInlineLineBreaks Whether to allow inline line breaks in log entries\n     */\n    public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false)\n    {\n        $this->format = $format === null ? static::SIMPLE_FORMAT : $format;\n        $this->allowInlineLineBreaks = $allowInlineLineBreaks;\n        $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;\n        $this->includeStacktraces($includeStacktraces);\n        parent::__construct($dateFormat);\n    }\n\n    /**\n     * Setting a base path will hide the base path from exception and stack trace file names to shorten them\n     * @return $this\n     */\n    public function setBasePath(string $path = ''): self\n    {\n        if ($path !== '') {\n            $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;\n        }\n\n        $this->basePath = $path;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function includeStacktraces(bool $include = true, ?Closure $parser = null): self\n    {\n        $this->includeStacktraces = $include;\n        if ($this->includeStacktraces) {\n            $this->allowInlineLineBreaks = true;\n            $this->stacktracesParser = $parser;\n        }\n\n        return $this;\n    }\n\n    /**\n     * Indent stack traces to separate them a bit from the main log record messages\n     *\n     * @param  string $indent The string used to indent, for example \"    \"\n     * @return $this\n     */\n    public function indentStacktraces(string $indent): self\n    {\n        $this->indentStacktraces = $indent;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function allowInlineLineBreaks(bool $allow = true): self\n    {\n        $this->allowInlineLineBreaks = $allow;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function ignoreEmptyContextAndExtra(bool $ignore = true): self\n    {\n        $this->ignoreEmptyContextAndExtra = $ignore;\n\n        return $this;\n    }\n\n    /**\n     * Allows cutting the level name to get fixed-length levels like INF for INFO, ERR for ERROR if you set this to 3 for example\n     *\n     * @param  int|null $maxLevelNameLength Maximum characters for the level name. Set null for infinite length (default)\n     * @return $this\n     */\n    public function setMaxLevelNameLength(?int $maxLevelNameLength = null): self\n    {\n        $this->maxLevelNameLength = $maxLevelNameLength;\n\n        return $this;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function format(LogRecord $record): string\n    {\n        $vars = parent::format($record);\n\n        if ($this->maxLevelNameLength !== null) {\n            $vars['level_name'] = substr($vars['level_name'], 0, $this->maxLevelNameLength);\n        }\n\n        $output = $this->format;\n        foreach ($vars['extra'] as $var => $val) {\n            if (false !== strpos($output, '%extra.'.$var.'%')) {\n                $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);\n                unset($vars['extra'][$var]);\n            }\n        }\n\n        foreach ($vars['context'] as $var => $val) {\n            if (false !== strpos($output, '%context.'.$var.'%')) {\n                $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);\n                unset($vars['context'][$var]);\n            }\n        }\n\n        if ($this->ignoreEmptyContextAndExtra) {\n            if (\\count($vars['context']) === 0) {\n                unset($vars['context']);\n                $output = str_replace('%context%', '', $output);\n            }\n\n            if (\\count($vars['extra']) === 0) {\n                unset($vars['extra']);\n                $output = str_replace('%extra%', '', $output);\n            }\n        }\n\n        foreach ($vars as $var => $val) {\n            if (false !== strpos($output, '%'.$var.'%')) {\n                $output = str_replace('%'.$var.'%', $this->stringify($val), $output);\n            }\n        }\n\n        // remove leftover %extra.xxx% and %context.xxx% if any\n        if (false !== strpos($output, '%')) {\n            $output = preg_replace('/%(?:extra|context)\\..+?%/', '', $output);\n            if (null === $output) {\n                $pcreErrorCode = preg_last_error();\n\n                throw new \\RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . preg_last_error_msg());\n            }\n        }\n\n        return $output;\n    }\n\n    public function formatBatch(array $records): string\n    {\n        $message = '';\n        foreach ($records as $record) {\n            $message .= $this->format($record);\n        }\n\n        return $message;\n    }\n\n    /**\n     * @param mixed $value\n     */\n    public function stringify($value): string\n    {\n        return $this->replaceNewlines($this->convertToString($value));\n    }\n\n    protected function normalizeException(\\Throwable $e, int $depth = 0): string\n    {\n        $str = $this->formatException($e);\n\n        $previous = $e->getPrevious();\n        while ($previous instanceof \\Throwable) {\n            $depth++;\n            if ($depth > $this->maxNormalizeDepth) {\n                $str .= \"\\n[previous exception] Over \" . $this->maxNormalizeDepth . ' levels deep, aborting normalization';\n                break;\n            }\n            $str .= \"\\n[previous exception] \" . $this->formatException($previous);\n            $previous = $previous->getPrevious();\n        }\n\n        return $str;\n    }\n\n    /**\n     * @param mixed $data\n     */\n    protected function convertToString($data): string\n    {\n        if (null === $data || \\is_bool($data)) {\n            return var_export($data, true);\n        }\n\n        if (\\is_scalar($data)) {\n            return (string) $data;\n        }\n\n        return $this->toJson($data, true);\n    }\n\n    protected function replaceNewlines(string $str): string\n    {\n        if ($this->allowInlineLineBreaks) {\n            if (0 === strpos($str, '{') || 0 === strpos($str, '[')) {\n                $str = preg_replace('/(?<!\\\\\\\\)\\\\\\\\[rn]/', \"\\n\", $str);\n                if (null === $str) {\n                    $pcreErrorCode = preg_last_error();\n\n                    throw new \\RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . preg_last_error_msg());\n                }\n            }\n\n            return $str;\n        }\n\n        return str_replace([\"\\r\\n\", \"\\r\", \"\\n\"], ' ', $str);\n    }\n\n    private function formatException(\\Throwable $e): string\n    {\n        $str = '[object] (' . Utils::getClass($e) . '(code: ' . $e->getCode();\n        if ($e instanceof \\SoapFault) {\n            if (isset($e->faultcode)) {\n                $str .= ' faultcode: ' . $e->faultcode;\n            }\n\n            if (isset($e->faultactor)) {\n                $str .= ' faultactor: ' . $e->faultactor;\n            }\n\n            if (isset($e->detail)) {\n                if (\\is_string($e->detail)) {\n                    $str .= ' detail: ' . $e->detail;\n                } elseif (\\is_object($e->detail) || \\is_array($e->detail)) {\n                    $str .= ' detail: ' . $this->toJson($e->detail, true);\n                }\n            }\n        }\n\n        $file = $e->getFile();\n        if ($this->basePath !== '') {\n            $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file);\n        }\n\n        $str .= '): ' . $e->getMessage() . ' at ' . strtr((string) $file, DIRECTORY_SEPARATOR, '/') . ':' . $e->getLine() . ')';\n\n        if ($this->includeStacktraces) {\n            $str .= $this->stacktracesParser($e);\n        }\n\n        return $str;\n    }\n\n    private function stacktracesParser(\\Throwable $e): string\n    {\n        $trace = $e->getTraceAsString();\n\n        if ($this->basePath !== '') {\n            $trace = preg_replace('{^(#\\d+ )' . preg_quote($this->basePath) . '}m', '$1', $trace) ?? $trace;\n        }\n\n        if ($this->stacktracesParser !== null) {\n            $trace = $this->stacktracesParserCustom($trace);\n        }\n\n        if ($this->indentStacktraces !== '') {\n            $trace = str_replace(\"\\n\", \"\\n{$this->indentStacktraces}\", $trace);\n        }\n\n        if (trim($trace) === '') {\n            return '';\n        }\n\n        return \"\\n{$this->indentStacktraces}[stacktrace]\\n{$this->indentStacktraces}\" . strtr($trace, DIRECTORY_SEPARATOR, '/') . \"\\n\";\n    }\n\n    private function stacktracesParserCustom(string $trace): string\n    {\n        return implode(\"\\n\", array_filter(array_map($this->stacktracesParser, explode(\"\\n\", $trace)), fn ($line) => is_string($line) && trim($line) !== ''));\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/LogglyFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\LogRecord;\n\n/**\n * Encodes message information into JSON in a format compatible with Loggly.\n *\n * @author Adam Pancutt <adam@pancutt.com>\n */\nclass LogglyFormatter extends JsonFormatter\n{\n    /**\n     * Overrides the default batch mode to new lines for compatibility with the\n     * Loggly bulk API.\n     */\n    public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $appendNewline = false)\n    {\n        parent::__construct($batchMode, $appendNewline);\n    }\n\n    /**\n     * Appends the 'timestamp' parameter for indexing by Loggly.\n     *\n     * @see https://www.loggly.com/docs/automated-parsing/#json\n     * @see \\Monolog\\Formatter\\JsonFormatter::format()\n     */\n    protected function normalizeRecord(LogRecord $record): array\n    {\n        $recordData = parent::normalizeRecord($record);\n\n        $recordData[\"timestamp\"] = $record->datetime->format(\"Y-m-d\\TH:i:s.uO\");\n        unset($recordData[\"datetime\"]);\n\n        return $recordData;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/LogmaticFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\LogRecord;\n\n/**\n * Encodes message information into JSON in a format compatible with Logmatic.\n *\n * @author Julien Breux <julien.breux@gmail.com>\n */\nclass LogmaticFormatter extends JsonFormatter\n{\n    protected const MARKERS = [\"sourcecode\", \"php\"];\n\n    protected string $hostname = '';\n\n    protected string $appName = '';\n\n    /**\n     * @return $this\n     */\n    public function setHostname(string $hostname): self\n    {\n        $this->hostname = $hostname;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function setAppName(string $appName): self\n    {\n        $this->appName = $appName;\n\n        return $this;\n    }\n\n    /**\n     * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic.\n     *\n     * @see http://doc.logmatic.io/docs/basics-to-send-data\n     * @see \\Monolog\\Formatter\\JsonFormatter::format()\n     */\n    public function normalizeRecord(LogRecord $record): array\n    {\n        $record = parent::normalizeRecord($record);\n\n        if ($this->hostname !== '') {\n            $record[\"hostname\"] = $this->hostname;\n        }\n        if ($this->appName !== '') {\n            $record[\"appname\"] = $this->appName;\n        }\n\n        $record[\"@marker\"] = static::MARKERS;\n\n        return $record;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/LogstashFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\LogRecord;\n\n/**\n * Serializes a log message to Logstash Event Format\n *\n * @see https://www.elastic.co/products/logstash\n * @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java\n *\n * @author Tim Mower <timothy.mower@gmail.com>\n */\nclass LogstashFormatter extends NormalizerFormatter\n{\n    /**\n     * @var string the name of the system for the Logstash log message, used to fill the @source field\n     */\n    protected string $systemName;\n\n    /**\n     * @var string an application name for the Logstash log message, used to fill the @type field\n     */\n    protected string $applicationName;\n\n    /**\n     * @var string the key for 'extra' fields from the Monolog record\n     */\n    protected string $extraKey;\n\n    /**\n     * @var string the key for 'context' fields from the Monolog record\n     */\n    protected string $contextKey;\n\n    /**\n     * @param string      $applicationName The application that sends the data, used as the \"type\" field of logstash\n     * @param string|null $systemName      The system/machine name, used as the \"source\" field of logstash, defaults to the hostname of the machine\n     * @param string      $extraKey        The key for extra keys inside logstash \"fields\", defaults to extra\n     * @param string      $contextKey      The key for context keys inside logstash \"fields\", defaults to context\n     */\n    public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context')\n    {\n        // logstash requires a ISO 8601 format date with optional millisecond precision.\n        parent::__construct('Y-m-d\\TH:i:s.uP');\n\n        $this->systemName = $systemName === null ? (string) gethostname() : $systemName;\n        $this->applicationName = $applicationName;\n        $this->extraKey = $extraKey;\n        $this->contextKey = $contextKey;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function format(LogRecord $record): string\n    {\n        $recordData = parent::format($record);\n\n        $message = [\n            '@timestamp' => $recordData['datetime'],\n            '@version' => 1,\n            'host' => $this->systemName,\n        ];\n        if (isset($recordData['message'])) {\n            $message['message'] = $recordData['message'];\n        }\n        if (isset($recordData['channel'])) {\n            $message['type'] = $recordData['channel'];\n            $message['channel'] = $recordData['channel'];\n        }\n        if (isset($recordData['level_name'])) {\n            $message['level'] = $recordData['level_name'];\n        }\n        if (isset($recordData['level'])) {\n            $message['monolog_level'] = $recordData['level'];\n        }\n        if ('' !== $this->applicationName) {\n            $message['type'] = $this->applicationName;\n        }\n        if (\\count($recordData['extra']) > 0) {\n            $message[$this->extraKey] = $recordData['extra'];\n        }\n        if (\\count($recordData['context']) > 0) {\n            $message[$this->contextKey] = $recordData['context'];\n        }\n\n        return $this->toJson($message) . \"\\n\";\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/MongoDBFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse MongoDB\\BSON\\Type;\nuse MongoDB\\BSON\\UTCDateTime;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Formats a record for use with the MongoDBHandler.\n *\n * @author Florian Plattner <me@florianplattner.de>\n */\nclass MongoDBFormatter implements FormatterInterface\n{\n    private bool $exceptionTraceAsString;\n    private int $maxNestingLevel;\n\n    /**\n     * @param int  $maxNestingLevel        0 means infinite nesting, the $record itself is level 1, $record->context is 2\n     * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings\n     */\n    public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true)\n    {\n        $this->maxNestingLevel = max($maxNestingLevel, 0);\n        $this->exceptionTraceAsString = $exceptionTraceAsString;\n    }\n\n    /**\n     * @inheritDoc\n     *\n     * @return mixed[]\n     */\n    public function format(LogRecord $record): array\n    {\n        /** @var mixed[] $res */\n        $res = $this->formatArray($record->toArray());\n\n        return $res;\n    }\n\n    /**\n     * @inheritDoc\n     *\n     * @return array<mixed[]>\n     */\n    public function formatBatch(array $records): array\n    {\n        $formatted = [];\n        foreach ($records as $key => $record) {\n            $formatted[$key] = $this->format($record);\n        }\n\n        return $formatted;\n    }\n\n    /**\n     * @param  mixed[]        $array\n     * @return mixed[]|string Array except when max nesting level is reached then a string \"[...]\"\n     */\n    protected function formatArray(array $array, int $nestingLevel = 0)\n    {\n        if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) {\n            return '[...]';\n        }\n\n        foreach ($array as $name => $value) {\n            if ($value instanceof \\DateTimeInterface) {\n                $array[$name] = $this->formatDate($value, $nestingLevel + 1);\n            } elseif ($value instanceof \\Throwable) {\n                $array[$name] = $this->formatException($value, $nestingLevel + 1);\n            } elseif (\\is_array($value)) {\n                $array[$name] = $this->formatArray($value, $nestingLevel + 1);\n            } elseif (\\is_object($value) && !$value instanceof Type) {\n                $array[$name] = $this->formatObject($value, $nestingLevel + 1);\n            }\n        }\n\n        return $array;\n    }\n\n    /**\n     * @param  mixed          $value\n     * @return mixed[]|string\n     */\n    protected function formatObject($value, int $nestingLevel)\n    {\n        $objectVars = get_object_vars($value);\n        $objectVars['class'] = Utils::getClass($value);\n\n        return $this->formatArray($objectVars, $nestingLevel);\n    }\n\n    /**\n     * @return mixed[]|string\n     */\n    protected function formatException(\\Throwable $exception, int $nestingLevel)\n    {\n        $formattedException = [\n            'class' => Utils::getClass($exception),\n            'message' => $exception->getMessage(),\n            'code' => (int) $exception->getCode(),\n            'file' => $exception->getFile() . ':' . $exception->getLine(),\n        ];\n\n        if ($this->exceptionTraceAsString === true) {\n            $formattedException['trace'] = $exception->getTraceAsString();\n        } else {\n            $formattedException['trace'] = $exception->getTrace();\n        }\n\n        return $this->formatArray($formattedException, $nestingLevel);\n    }\n\n    protected function formatDate(\\DateTimeInterface $value, int $nestingLevel): UTCDateTime\n    {\n        return new UTCDateTime((int) floor(((float) $value->format('U.u')) * 1000));\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/NormalizerFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\JsonSerializableDateTimeImmutable;\nuse Monolog\\Utils;\nuse Throwable;\nuse Monolog\\LogRecord;\n\n/**\n * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass NormalizerFormatter implements FormatterInterface\n{\n    public const SIMPLE_DATE = \"Y-m-d\\TH:i:sP\";\n\n    protected string $dateFormat;\n    protected int $maxNormalizeDepth = 9;\n    protected int $maxNormalizeItemCount = 1000;\n    protected ?int $maxTraceLength = null;\n\n    private int $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS;\n\n    protected string $basePath = '';\n\n    /**\n     * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format\n     */\n    public function __construct(?string $dateFormat = null)\n    {\n        $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function format(LogRecord $record)\n    {\n        return $this->normalizeRecord($record);\n    }\n\n    /**\n     * Normalize an arbitrary value to a scalar|array|null\n     *\n     * @return null|scalar|array<mixed[]|scalar|null>\n     */\n    public function normalizeValue(mixed $data): mixed\n    {\n        return $this->normalize($data);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function formatBatch(array $records)\n    {\n        foreach ($records as $key => $record) {\n            $records[$key] = $this->format($record);\n        }\n\n        return $records;\n    }\n\n    public function getDateFormat(): string\n    {\n        return $this->dateFormat;\n    }\n\n    /**\n     * @return $this\n     */\n    public function setDateFormat(string $dateFormat): self\n    {\n        $this->dateFormat = $dateFormat;\n\n        return $this;\n    }\n\n    /**\n     * The maximum number of normalization levels to go through\n     */\n    public function getMaxNormalizeDepth(): int\n    {\n        return $this->maxNormalizeDepth;\n    }\n\n    /**\n     * @return $this\n     */\n    public function setMaxNormalizeDepth(int $maxNormalizeDepth): self\n    {\n        $this->maxNormalizeDepth = $maxNormalizeDepth;\n\n        return $this;\n    }\n\n    /**\n     * The maximum number of items to normalize per level\n     */\n    public function getMaxNormalizeItemCount(): int\n    {\n        return $this->maxNormalizeItemCount;\n    }\n\n    /**\n     * @return $this\n     */\n    public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): self\n    {\n        $this->maxNormalizeItemCount = $maxNormalizeItemCount;\n\n        return $this;\n    }\n\n    /**\n     * The maximum number of stack trace frames to include\n     */\n    public function getMaxTraceLength(): ?int\n    {\n        return $this->maxTraceLength;\n    }\n\n    /**\n     * @return $this\n     */\n    public function setMaxTraceLength(?int $maxTraceLength): self\n    {\n        $this->maxTraceLength = $maxTraceLength;\n\n        return $this;\n    }\n\n    /**\n     * Enables `json_encode` pretty print.\n     *\n     * @return $this\n     */\n    public function setJsonPrettyPrint(bool $enable): self\n    {\n        if ($enable) {\n            $this->jsonEncodeOptions |= JSON_PRETTY_PRINT;\n        } else {\n            $this->jsonEncodeOptions &= ~JSON_PRETTY_PRINT;\n        }\n\n        return $this;\n    }\n\n    /**\n     * Setting a base path will hide the base path from exception and stack trace file names to shorten them\n     * @return $this\n     */\n    public function setBasePath(string $path = ''): self\n    {\n        if ($path !== '') {\n            $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;\n        }\n\n        $this->basePath = $path;\n\n        return $this;\n    }\n\n    /**\n     * Provided as extension point\n     *\n     * Because normalize is called with sub-values of context data etc, normalizeRecord can be\n     * extended when data needs to be appended on the record array but not to other normalized data.\n     *\n     * @return array<mixed[]|scalar|null>\n     */\n    protected function normalizeRecord(LogRecord $record): array\n    {\n        /** @var array<mixed[]|scalar|null> $normalized */\n        $normalized = $this->normalize($record->toArray());\n\n        return $normalized;\n    }\n\n    /**\n     * @return null|scalar|array<mixed[]|scalar|null>\n     */\n    protected function normalize(mixed $data, int $depth = 0): mixed\n    {\n        if ($depth > $this->maxNormalizeDepth) {\n            return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization';\n        }\n\n        if (null === $data || \\is_scalar($data)) {\n            if (\\is_float($data)) {\n                if (is_infinite($data)) {\n                    return ($data > 0 ? '' : '-') . 'INF';\n                }\n                if (is_nan($data)) {\n                    return 'NaN';\n                }\n            }\n\n            return $data;\n        }\n\n        if (\\is_array($data)) {\n            $normalized = [];\n\n            $count = 1;\n            foreach ($data as $key => $value) {\n                if ($count++ > $this->maxNormalizeItemCount) {\n                    $normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items ('.\\count($data).' total), aborting normalization';\n                    break;\n                }\n\n                $normalized[$key] = $this->normalize($value, $depth + 1);\n            }\n\n            return $normalized;\n        }\n\n        if ($data instanceof \\DateTimeInterface) {\n            return $this->formatDate($data);\n        }\n\n        if (\\is_object($data)) {\n            if ($data instanceof Throwable) {\n                return $this->normalizeException($data, $depth);\n            }\n\n            if ($data instanceof \\JsonSerializable) {\n                /** @var null|scalar|array<mixed[]|scalar|null> $value */\n                $value = $data->jsonSerialize();\n            } elseif (\\get_class($data) === '__PHP_Incomplete_Class') {\n                $accessor = new \\ArrayObject($data);\n                $value = (string) $accessor['__PHP_Incomplete_Class_Name'];\n            } elseif (method_exists($data, '__toString')) {\n                try {\n                    /** @var string $value */\n                    $value = $data->__toString();\n                } catch (\\Throwable) {\n                    // if the toString method is failing, use the default behavior\n                    /** @var null|scalar|array<mixed[]|scalar|null> $value */\n                    $value = json_decode($this->toJson($data, true), true);\n                }\n            } else {\n                // the rest is normalized by json encoding and decoding it\n                /** @var null|scalar|array<mixed[]|scalar|null> $value */\n                $value = json_decode($this->toJson($data, true), true);\n            }\n\n            return [Utils::getClass($data) => $value];\n        }\n\n        if (\\is_resource($data)) {\n            return sprintf('[resource(%s)]', get_resource_type($data));\n        }\n\n        return '[unknown('.\\gettype($data).')]';\n    }\n\n    /**\n     * @return array<array-key, string|int|array<string|int|array<string>>>\n     */\n    protected function normalizeException(Throwable $e, int $depth = 0)\n    {\n        if ($depth > $this->maxNormalizeDepth) {\n            return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'];\n        }\n\n        if ($e instanceof \\JsonSerializable) {\n            return (array) $e->jsonSerialize();\n        }\n\n        $file = $e->getFile();\n        if ($this->basePath !== '') {\n            $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file);\n        }\n\n        $data = [\n            'class' => Utils::getClass($e),\n            'message' => $e->getMessage(),\n            'code' => (int) $e->getCode(),\n            'file' => $file.':'.$e->getLine(),\n        ];\n\n        if ($e instanceof \\SoapFault) {\n            if (isset($e->faultcode)) {\n                $data['faultcode'] = $e->faultcode;\n            }\n\n            if (isset($e->faultactor)) {\n                $data['faultactor'] = $e->faultactor;\n            }\n\n            if (isset($e->detail)) {\n                if (\\is_string($e->detail)) {\n                    $data['detail'] = $e->detail;\n                } elseif (\\is_object($e->detail) || \\is_array($e->detail)) {\n                    $data['detail'] = $this->toJson($e->detail, true);\n                }\n            }\n        }\n\n        $trace = array_slice($e->getTrace(), 0, $this->maxTraceLength);\n        foreach ($trace as $frame) {\n            if (isset($frame['file'], $frame['line'])) {\n                $file = $frame['file'];\n                if ($this->basePath !== '') {\n                    $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file);\n                }\n                $data['trace'][] = $file.':'.$frame['line'];\n            }\n        }\n\n        if (($previous = $e->getPrevious()) instanceof \\Throwable) {\n            $data['previous'] = $this->normalizeException($previous, $depth + 1);\n        }\n\n        return $data;\n    }\n\n    /**\n     * Return the JSON representation of a value\n     *\n     * @param  mixed             $data\n     * @throws \\RuntimeException if encoding fails and errors are not ignored\n     * @return string            if encoding fails and ignoreErrors is true 'null' is returned\n     */\n    protected function toJson($data, bool $ignoreErrors = false): string\n    {\n        return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors);\n    }\n\n    protected function formatDate(\\DateTimeInterface $date): string\n    {\n        // in case the date format isn't custom then we defer to the custom JsonSerializableDateTimeImmutable\n        // formatting logic, which will pick the right format based on whether useMicroseconds is on\n        if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof JsonSerializableDateTimeImmutable) {\n            return (string) $date;\n        }\n\n        return $date->format($this->dateFormat);\n    }\n\n    /**\n     * @return $this\n     */\n    public function addJsonEncodeOption(int $option): self\n    {\n        $this->jsonEncodeOptions |= $option;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function removeJsonEncodeOption(int $option): self\n    {\n        $this->jsonEncodeOptions &= ~$option;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/ScalarFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\LogRecord;\n\n/**\n * Formats data into an associative array of scalar (+ null) values.\n * Objects and arrays will be JSON encoded.\n *\n * @author Andrew Lawson <adlawson@gmail.com>\n */\nclass ScalarFormatter extends NormalizerFormatter\n{\n    /**\n     * @inheritDoc\n     *\n     * @phpstan-return array<string, scalar|null> $record\n     */\n    public function format(LogRecord $record): array\n    {\n        $result = [];\n        foreach ($record->toArray() as $key => $value) {\n            $result[$key] = $this->toScalar($value);\n        }\n\n        return $result;\n    }\n\n    protected function toScalar(mixed $value): string|int|float|bool|null\n    {\n        $normalized = $this->normalize($value);\n\n        if (\\is_array($normalized)) {\n            return $this->toJson($normalized, true);\n        }\n\n        return $normalized;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/SyslogFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * Serializes a log message according to RFC 5424\n *\n * @author Dalibor Karlović <dalibor.karlovic@sigwin.hr>\n * @author Renat Gabdullin <renatobyj@gmail.com>\n */\nclass SyslogFormatter extends LineFormatter\n{\n    private const SYSLOG_FACILITY_USER = 1;\n    private const FORMAT = \"<%extra.priority%>1 %datetime% %extra.hostname% %extra.app-name% %extra.procid% %channel% %extra.structured-data% %level_name%: %message% %context% %extra%\\n\";\n    private const NILVALUE = '-';\n\n    private string $hostname;\n    private int $procid;\n\n    public function __construct(private string $applicationName = self::NILVALUE)\n    {\n        parent::__construct(self::FORMAT, 'Y-m-d\\TH:i:s.uP', true, true);\n        $this->hostname = (string) gethostname();\n        $this->procid = (int) getmypid();\n    }\n\n    public function format(LogRecord $record): string\n    {\n        $record->extra = $this->formatExtra($record);\n\n        return parent::format($record);\n    }\n\n    /**\n     * @return array<string, mixed>\n     */\n    private function formatExtra(LogRecord $record): array\n    {\n        $extra = $record->extra;\n        $extra['app-name'] = $this->applicationName;\n        $extra['hostname'] = $this->hostname;\n        $extra['procid'] = $this->procid;\n        $extra['priority'] = self::calculatePriority($record->level);\n        $extra['structured-data'] = self::NILVALUE;\n\n        return $extra;\n    }\n\n    private static function calculatePriority(Level $level): int\n    {\n        return (self::SYSLOG_FACILITY_USER * 8) + $level->toRFC5424Level();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Formatter/WildfireFormatter.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * Serializes a log message according to Wildfire's header requirements\n *\n * @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>\n * @author Christophe Coevoet <stof@notk.org>\n * @author Kirill chEbba Chebunin <iam@chebba.org>\n */\nclass WildfireFormatter extends NormalizerFormatter\n{\n    /**\n     * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format\n     */\n    public function __construct(?string $dateFormat = null)\n    {\n        parent::__construct($dateFormat);\n\n        // http headers do not like non-ISO-8559-1 characters\n        $this->removeJsonEncodeOption(JSON_UNESCAPED_UNICODE);\n    }\n\n    /**\n     * Translates Monolog log levels to Wildfire levels.\n     *\n     * @return 'LOG'|'INFO'|'WARN'|'ERROR'\n     */\n    private function toWildfireLevel(Level $level): string\n    {\n        return match ($level) {\n            Level::Debug     => 'LOG',\n            Level::Info      => 'INFO',\n            Level::Notice    => 'INFO',\n            Level::Warning   => 'WARN',\n            Level::Error     => 'ERROR',\n            Level::Critical  => 'ERROR',\n            Level::Alert     => 'ERROR',\n            Level::Emergency => 'ERROR',\n        };\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function format(LogRecord $record): string\n    {\n        // Retrieve the line and file if set and remove them from the formatted extra\n        $file = $line = '';\n        if (isset($record->extra['file'])) {\n            $file = $record->extra['file'];\n            unset($record->extra['file']);\n        }\n        if (isset($record->extra['line'])) {\n            $line = $record->extra['line'];\n            unset($record->extra['line']);\n        }\n\n        $message = ['message' => $record->message];\n        $handleError = false;\n        if (\\count($record->context) > 0) {\n            $message['context'] = $this->normalize($record->context);\n            $handleError = true;\n        }\n        if (\\count($record->extra) > 0) {\n            $message['extra'] = $this->normalize($record->extra);\n            $handleError = true;\n        }\n        if (\\count($message) === 1) {\n            $message = reset($message);\n        }\n\n        if (is_array($message) && isset($message['context']) && \\is_array($message['context']) && isset($message['context']['table'])) {\n            $type  = 'TABLE';\n            $label = $record->channel .': '. $record->message;\n            $message = $message['context']['table'];\n        } else {\n            $type  = $this->toWildfireLevel($record->level);\n            $label = $record->channel;\n        }\n\n        // Create JSON object describing the appearance of the message in the console\n        $json = $this->toJson([\n            [\n                'Type'  => $type,\n                'File'  => $file,\n                'Line'  => $line,\n                'Label' => $label,\n            ],\n            $message,\n        ], $handleError);\n\n        // The message itself is a serialization of the above JSON object + it's length\n        return sprintf(\n            '%d|%s|',\n            \\strlen($json),\n            $json\n        );\n    }\n\n    /**\n     * @inheritDoc\n     *\n     * @phpstan-return never\n     */\n    public function formatBatch(array $records)\n    {\n        throw new \\BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter');\n    }\n\n    /**\n     * @inheritDoc\n     *\n     * @return null|scalar|array<mixed[]|scalar|null>|object\n     */\n    protected function normalize(mixed $data, int $depth = 0): mixed\n    {\n        if (\\is_object($data) && !$data instanceof \\DateTimeInterface) {\n            return $data;\n        }\n\n        return parent::normalize($data, $depth);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/AbstractHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Monolog\\ResettableInterface;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\LogRecord;\n\n/**\n * Base Handler class providing basic level/bubble support\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nabstract class AbstractHandler extends Handler implements ResettableInterface\n{\n    protected Level $level = Level::Debug;\n    protected bool $bubble = true;\n\n    /**\n     * @param int|string|Level|LogLevel::* $level  The minimum logging level at which this handler will be triggered\n     * @param bool                         $bubble Whether the messages that are handled can bubble up the stack or not\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        $this->setLevel($level);\n        $this->bubble = $bubble;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function isHandling(LogRecord $record): bool\n    {\n        return $record->level->value >= $this->level->value;\n    }\n\n    /**\n     * Sets minimum logging level at which this handler will be triggered.\n     *\n     * @param  Level|LogLevel::* $level Level or level name\n     * @return $this\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public function setLevel(int|string|Level $level): self\n    {\n        $this->level = Logger::toMonologLevel($level);\n\n        return $this;\n    }\n\n    /**\n     * Gets minimum logging level at which this handler will be triggered.\n     */\n    public function getLevel(): Level\n    {\n        return $this->level;\n    }\n\n    /**\n     * Sets the bubbling behavior.\n     *\n     * @param  bool  $bubble true means that this handler allows bubbling.\n     *                       false means that bubbling is not permitted.\n     * @return $this\n     */\n    public function setBubble(bool $bubble): self\n    {\n        $this->bubble = $bubble;\n\n        return $this;\n    }\n\n    /**\n     * Gets the bubbling behavior.\n     *\n     * @return bool true means that this handler allows bubbling.\n     *              false means that bubbling is not permitted.\n     */\n    public function getBubble(): bool\n    {\n        return $this->bubble;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function reset(): void\n    {\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/AbstractProcessingHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\LogRecord;\n\n/**\n * Base Handler class providing the Handler structure, including processors and formatters\n *\n * Classes extending it should (in most cases) only implement write($record)\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n * @author Christophe Coevoet <stof@notk.org>\n */\nabstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface\n{\n    use ProcessableHandlerTrait;\n    use FormattableHandlerTrait;\n\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        if (!$this->isHandling($record)) {\n            return false;\n        }\n\n        if (\\count($this->processors) > 0) {\n            $record = $this->processRecord($record);\n        }\n\n        $record->formatted = $this->getFormatter()->format($record);\n\n        $this->write($record);\n\n        return false === $this->bubble;\n    }\n\n    /**\n     * Writes the (already formatted) record down to the log of the implementing handler\n     */\n    abstract protected function write(LogRecord $record): void;\n\n    public function reset(): void\n    {\n        parent::reset();\n\n        $this->resetProcessors();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/AbstractSyslogHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\LineFormatter;\n\n/**\n * Common syslog functionality\n */\nabstract class AbstractSyslogHandler extends AbstractProcessingHandler\n{\n    protected int $facility;\n\n    /**\n     * List of valid log facility names.\n     * @var array<string, int>\n     */\n    protected array $facilities = [\n        'auth'     => \\LOG_AUTH,\n        'authpriv' => \\LOG_AUTHPRIV,\n        'cron'     => \\LOG_CRON,\n        'daemon'   => \\LOG_DAEMON,\n        'kern'     => \\LOG_KERN,\n        'lpr'      => \\LOG_LPR,\n        'mail'     => \\LOG_MAIL,\n        'news'     => \\LOG_NEWS,\n        'syslog'   => \\LOG_SYSLOG,\n        'user'     => \\LOG_USER,\n        'uucp'     => \\LOG_UUCP,\n    ];\n\n    /**\n     * Translates Monolog log levels to syslog log priorities.\n     */\n    protected function toSyslogPriority(Level $level): int\n    {\n        return $level->toRFC5424Level();\n    }\n\n    /**\n     * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant\n     */\n    public function __construct(string|int $facility = \\LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        parent::__construct($level, $bubble);\n\n        if (!\\defined('PHP_WINDOWS_VERSION_BUILD')) {\n            $this->facilities['local0'] = \\LOG_LOCAL0;\n            $this->facilities['local1'] = \\LOG_LOCAL1;\n            $this->facilities['local2'] = \\LOG_LOCAL2;\n            $this->facilities['local3'] = \\LOG_LOCAL3;\n            $this->facilities['local4'] = \\LOG_LOCAL4;\n            $this->facilities['local5'] = \\LOG_LOCAL5;\n            $this->facilities['local6'] = \\LOG_LOCAL6;\n            $this->facilities['local7'] = \\LOG_LOCAL7;\n        } else {\n            $this->facilities['local0'] = 128; // LOG_LOCAL0\n            $this->facilities['local1'] = 136; // LOG_LOCAL1\n            $this->facilities['local2'] = 144; // LOG_LOCAL2\n            $this->facilities['local3'] = 152; // LOG_LOCAL3\n            $this->facilities['local4'] = 160; // LOG_LOCAL4\n            $this->facilities['local5'] = 168; // LOG_LOCAL5\n            $this->facilities['local6'] = 176; // LOG_LOCAL6\n            $this->facilities['local7'] = 184; // LOG_LOCAL7\n        }\n\n        // convert textual description of facility to syslog constant\n        if (\\is_string($facility) && \\array_key_exists(strtolower($facility), $this->facilities)) {\n            $facility = $this->facilities[strtolower($facility)];\n        } elseif (!\\in_array($facility, array_values($this->facilities), true)) {\n            throw new \\UnexpectedValueException('Unknown facility value \"'.$facility.'\" given');\n        }\n\n        $this->facility = $facility;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%');\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/AmqpHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Gelf\\Message as GelfMessage;\nuse Monolog\\Level;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\JsonFormatter;\nuse PhpAmqpLib\\Message\\AMQPMessage;\nuse PhpAmqpLib\\Channel\\AMQPChannel;\nuse AMQPExchange;\nuse Monolog\\LogRecord;\n\nclass AmqpHandler extends AbstractProcessingHandler\n{\n    protected AMQPExchange|AMQPChannel $exchange;\n\n    /** @var array<string, mixed> */\n    private array $extraAttributes = [];\n\n    protected string $exchangeName;\n\n    /**\n     * @param AMQPExchange|AMQPChannel $exchange     AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use\n     * @param string|null              $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only\n     */\n    public function __construct(AMQPExchange|AMQPChannel $exchange, ?string $exchangeName = null, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        if ($exchange instanceof AMQPChannel) {\n            $this->exchangeName = (string) $exchangeName;\n        } elseif ($exchangeName !== null) {\n            @trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED);\n        }\n        $this->exchange = $exchange;\n\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * @return array<string, mixed>\n     */\n    public function getExtraAttributes(): array\n    {\n        return $this->extraAttributes;\n    }\n\n    /**\n     * Configure extra attributes to pass to the AMQPExchange (if you are using the amqp extension)\n     *\n     * @param  array<string, mixed> $extraAttributes One of content_type, content_encoding,\n     *                                               message_id, user_id, app_id, delivery_mode,\n     *                                               priority, timestamp, expiration, type\n     *                                               or reply_to, headers.\n     * @return $this\n     */\n    public function setExtraAttributes(array $extraAttributes): self\n    {\n        $this->extraAttributes = $extraAttributes;\n\n        return $this;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $data = $record->formatted;\n        $routingKey = $this->getRoutingKey($record);\n\n        if($data instanceof GelfMessage) {\n            $data = json_encode($data->toArray());\n        }\n\n        if ($this->exchange instanceof AMQPExchange) {\n            $attributes = [\n                'delivery_mode' => 2,\n                'content_type'  => 'application/json',\n            ];\n            if (\\count($this->extraAttributes) > 0) {\n                $attributes = array_merge($attributes, $this->extraAttributes);\n            }\n            $this->exchange->publish(\n                $data,\n                $routingKey,\n                0,\n                $attributes\n            );\n        } else {\n            $this->exchange->basic_publish(\n                $this->createAmqpMessage($data),\n                $this->exchangeName,\n                $routingKey\n            );\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        if ($this->exchange instanceof AMQPExchange) {\n            parent::handleBatch($records);\n\n            return;\n        }\n\n        foreach ($records as $record) {\n            if (!$this->isHandling($record)) {\n                continue;\n            }\n\n            $record = $this->processRecord($record);\n            $data = $this->getFormatter()->format($record);\n\n            if($data instanceof GelfMessage) {\n                $data = json_encode($data->toArray());\n            }\n\n            $this->exchange->batch_basic_publish(\n                $this->createAmqpMessage($data),\n                $this->exchangeName,\n                $this->getRoutingKey($record)\n            );\n        }\n\n        $this->exchange->publish_batch();\n    }\n\n    /**\n     * Gets the routing key for the AMQP exchange\n     */\n    protected function getRoutingKey(LogRecord $record): string\n    {\n        $routingKey = sprintf('%s.%s', $record->level->name, $record->channel);\n\n        return strtolower($routingKey);\n    }\n\n    private function createAmqpMessage(string $data): AMQPMessage\n    {\n        $attributes = [\n            'delivery_mode' => 2,\n            'content_type' => 'application/json',\n        ];\n        if (\\count($this->extraAttributes) > 0) {\n            $attributes = array_merge($attributes, $this->extraAttributes);\n        }\n\n        return new AMQPMessage($data, $attributes);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/BrowserConsoleHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\nuse Monolog\\Level;\n\nuse function headers_list;\nuse function stripos;\n\n/**\n * Handler sending logs to browser's javascript console with no browser extension required\n *\n * @author Olivier Poitrey <rs@dailymotion.com>\n */\nclass BrowserConsoleHandler extends AbstractProcessingHandler\n{\n    protected static bool $initialized = false;\n\n    /** @var LogRecord[] */\n    protected static array $records = [];\n\n    protected const FORMAT_HTML = 'html';\n    protected const FORMAT_JS = 'js';\n    protected const FORMAT_UNKNOWN = 'unknown';\n\n    /**\n     * @inheritDoc\n     *\n     * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format.\n     *\n     * Example of formatted string:\n     *\n     *     You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white}\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%');\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        // Accumulate records\n        static::$records[] = $record;\n\n        // Register shutdown handler if not already done\n        if (!static::$initialized) {\n            static::$initialized = true;\n            $this->registerShutdownFunction();\n        }\n    }\n\n    /**\n     * Convert records to javascript console commands and send it to the browser.\n     * This method is automatically called on PHP shutdown if output is HTML or Javascript.\n     */\n    public static function send(): void\n    {\n        $format = static::getResponseFormat();\n        if ($format === self::FORMAT_UNKNOWN) {\n            return;\n        }\n\n        if (\\count(static::$records) > 0) {\n            if ($format === self::FORMAT_HTML) {\n                static::writeOutput('<script>' . self::generateScript() . '</script>');\n            } else { // js format\n                static::writeOutput(self::generateScript());\n            }\n            static::resetStatic();\n        }\n    }\n\n    public function close(): void\n    {\n        self::resetStatic();\n    }\n\n    public function reset(): void\n    {\n        parent::reset();\n\n        self::resetStatic();\n    }\n\n    /**\n     * Forget all logged records\n     */\n    public static function resetStatic(): void\n    {\n        static::$records = [];\n    }\n\n    /**\n     * Wrapper for register_shutdown_function to allow overriding\n     */\n    protected function registerShutdownFunction(): void\n    {\n        if (PHP_SAPI !== 'cli') {\n            register_shutdown_function(['Monolog\\Handler\\BrowserConsoleHandler', 'send']);\n        }\n    }\n\n    /**\n     * Wrapper for echo to allow overriding\n     */\n    protected static function writeOutput(string $str): void\n    {\n        echo $str;\n    }\n\n    /**\n     * Checks the format of the response\n     *\n     * If Content-Type is set to application/javascript or text/javascript -> js\n     * If Content-Type is set to text/html, or is unset -> html\n     * If Content-Type is anything else -> unknown\n     *\n     * @return string One of 'js', 'html' or 'unknown'\n     * @phpstan-return self::FORMAT_*\n     */\n    protected static function getResponseFormat(): string\n    {\n        // Check content type\n        foreach (headers_list() as $header) {\n            if (stripos($header, 'content-type:') === 0) {\n                return static::getResponseFormatFromContentType($header);\n            }\n        }\n\n        return self::FORMAT_HTML;\n    }\n\n    /**\n     * @return string One of 'js', 'html' or 'unknown'\n     * @phpstan-return self::FORMAT_*\n     */\n    protected static function getResponseFormatFromContentType(string $contentType): string\n    {\n        // This handler only works with HTML and javascript outputs\n        // text/javascript is obsolete in favour of application/javascript, but still used\n        if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) {\n            return self::FORMAT_JS;\n        }\n\n        if (stripos($contentType, 'text/html') !== false) {\n            return self::FORMAT_HTML;\n        }\n\n        return self::FORMAT_UNKNOWN;\n    }\n\n    private static function generateScript(): string\n    {\n        $script = [];\n        foreach (static::$records as $record) {\n            $context = self::dump('Context', $record->context);\n            $extra = self::dump('Extra', $record->extra);\n\n            if (\\count($context) === 0 && \\count($extra) === 0) {\n                $script[] = self::call_array(self::getConsoleMethodForLevel($record->level), self::handleStyles($record->formatted));\n            } else {\n                $script = array_merge(\n                    $script,\n                    [self::call_array('groupCollapsed', self::handleStyles($record->formatted))],\n                    $context,\n                    $extra,\n                    [self::call('groupEnd')]\n                );\n            }\n        }\n\n        return \"(function (c) {if (c && c.groupCollapsed) {\\n\" . implode(\"\\n\", $script) . \"\\n}})(console);\";\n    }\n\n    private static function getConsoleMethodForLevel(Level $level): string\n    {\n        return match ($level) {\n            Level::Debug => 'debug',\n            Level::Info, Level::Notice => 'info',\n            Level::Warning => 'warn',\n            Level::Error, Level::Critical, Level::Alert, Level::Emergency => 'error',\n        };\n    }\n\n    /**\n     * @return string[]\n     */\n    private static function handleStyles(string $formatted): array\n    {\n        $args = [];\n        $format = '%c' . $formatted;\n        preg_match_all('/\\[\\[(.*?)\\]\\]\\{([^}]*)\\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);\n\n        foreach (array_reverse($matches) as $match) {\n            $args[] = '\"font-weight: normal\"';\n            $args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0]));\n\n            $pos = $match[0][1];\n            $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + \\strlen($match[0][0]));\n        }\n\n        $args[] = self::quote('font-weight: normal');\n        $args[] = self::quote($format);\n\n        return array_reverse($args);\n    }\n\n    private static function handleCustomStyles(string $style, string $string): string\n    {\n        static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'];\n        static $labels = [];\n\n        $style = preg_replace_callback('/macro\\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) {\n            if (trim($m[1]) === 'autolabel') {\n                // Format the string as a label with consistent auto assigned background color\n                if (!isset($labels[$string])) {\n                    $labels[$string] = $colors[\\count($labels) % \\count($colors)];\n                }\n                $color = $labels[$string];\n\n                return \"background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px\";\n            }\n\n            return $m[1];\n        }, $style);\n\n        if (null === $style) {\n            $pcreErrorCode = preg_last_error();\n\n            throw new \\RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . preg_last_error_msg());\n        }\n\n        return $style;\n    }\n\n    /**\n     * @param  mixed[] $dict\n     * @return mixed[]\n     */\n    private static function dump(string $title, array $dict): array\n    {\n        $script = [];\n        $dict = array_filter($dict, fn ($value) => $value !== null);\n        if (\\count($dict) === 0) {\n            return $script;\n        }\n        $script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title));\n        foreach ($dict as $key => $value) {\n            $value = json_encode($value);\n            if (false === $value) {\n                $value = self::quote('');\n            }\n            $script[] = self::call('log', self::quote('%s: %o'), self::quote((string) $key), $value);\n        }\n\n        return $script;\n    }\n\n    private static function quote(string $arg): string\n    {\n        return '\"' . addcslashes($arg, \"\\\"\\n\\\\\") . '\"';\n    }\n\n    /**\n     * @param mixed $args\n     */\n    private static function call(...$args): string\n    {\n        $method = array_shift($args);\n        if (!\\is_string($method)) {\n            throw new \\UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true));\n        }\n\n        return self::call_array($method, $args);\n    }\n\n    /**\n     * @param mixed[] $args\n     */\n    private static function call_array(string $method, array $args): string\n    {\n        return 'c.' . $method . '(' . implode(', ', $args) . ');';\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/BufferHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\ResettableInterface;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Buffers all records until closing the handler and then pass them as batch.\n *\n * This is useful for a MailHandler to send only one mail per request instead of\n * sending one per log message.\n *\n * @author Christophe Coevoet <stof@notk.org>\n */\nclass BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface\n{\n    use ProcessableHandlerTrait;\n\n    protected HandlerInterface $handler;\n\n    protected int $bufferSize = 0;\n\n    protected int $bufferLimit;\n\n    protected bool $flushOnOverflow;\n\n    /** @var LogRecord[] */\n    protected array $buffer = [];\n\n    protected bool $initialized = false;\n\n    /**\n     * @param HandlerInterface $handler         Handler.\n     * @param int              $bufferLimit     How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.\n     * @param bool             $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded\n     */\n    public function __construct(HandlerInterface $handler, int $bufferLimit = 0, int|string|Level $level = Level::Debug, bool $bubble = true, bool $flushOnOverflow = false)\n    {\n        parent::__construct($level, $bubble);\n        $this->handler = $handler;\n        $this->bufferLimit = $bufferLimit;\n        $this->flushOnOverflow = $flushOnOverflow;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        if ($record->level->isLowerThan($this->level)) {\n            return false;\n        }\n\n        if (!$this->initialized) {\n            // __destructor() doesn't get called on Fatal errors\n            register_shutdown_function([$this, 'close']);\n            $this->initialized = true;\n        }\n\n        if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) {\n            if ($this->flushOnOverflow) {\n                $this->flush();\n            } else {\n                array_shift($this->buffer);\n                $this->bufferSize--;\n            }\n        }\n\n        if (\\count($this->processors) > 0) {\n            $record = $this->processRecord($record);\n        }\n\n        $this->buffer[] = $record;\n        $this->bufferSize++;\n\n        return false === $this->bubble;\n    }\n\n    public function flush(): void\n    {\n        if ($this->bufferSize === 0) {\n            return;\n        }\n\n        $this->handler->handleBatch($this->buffer);\n        $this->clear();\n    }\n\n    public function __destruct()\n    {\n        // suppress the parent behavior since we already have register_shutdown_function()\n        // to call close(), and the reference contained there will prevent this from being\n        // GC'd until the end of the request\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function close(): void\n    {\n        $this->flush();\n\n        $this->handler->close();\n    }\n\n    /**\n     * Clears the buffer without flushing any messages down to the wrapped handler.\n     */\n    public function clear(): void\n    {\n        $this->bufferSize = 0;\n        $this->buffer = [];\n    }\n\n    public function reset(): void\n    {\n        $this->flush();\n\n        parent::reset();\n\n        $this->resetProcessors();\n\n        if ($this->handler instanceof ResettableInterface) {\n            $this->handler->reset();\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        if ($this->handler instanceof FormattableHandlerInterface) {\n            $this->handler->setFormatter($formatter);\n\n            return $this;\n        }\n\n        throw new \\UnexpectedValueException('The nested handler of type '.\\get_class($this->handler).' does not support formatters.');\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getFormatter(): FormatterInterface\n    {\n        if ($this->handler instanceof FormattableHandlerInterface) {\n            return $this->handler->getFormatter();\n        }\n\n        throw new \\UnexpectedValueException('The nested handler of type '.\\get_class($this->handler).' does not support formatters.');\n    }\n\n    public function setHandler(HandlerInterface $handler): void\n    {\n        $this->handler = $handler;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/ChromePHPHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\ChromePHPFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\nuse Monolog\\JsonSerializableDateTimeImmutable;\n\n/**\n * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)\n *\n * This also works out of the box with Firefox 43+\n *\n * @author Christophe Coevoet <stof@notk.org>\n */\nclass ChromePHPHandler extends AbstractProcessingHandler\n{\n    use WebRequestRecognizerTrait;\n\n    /**\n     * Version of the extension\n     */\n    protected const VERSION = '4.0';\n\n    /**\n     * Header name\n     */\n    protected const HEADER_NAME = 'X-ChromeLogger-Data';\n\n    /**\n     * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+)\n     */\n    protected const USER_AGENT_REGEX = '{\\b(?:Chrome/\\d+(?:\\.\\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\\d|\\d{3,})(?:\\.\\d)*)\\b}';\n\n    protected static bool $initialized = false;\n\n    /**\n     * Tracks whether we sent too much data\n     *\n     * Chrome limits the headers to 4KB, so when we sent 3KB we stop sending\n     */\n    protected static bool $overflowed = false;\n\n    /** @var mixed[] */\n    protected static array $json = [\n        'version' => self::VERSION,\n        'columns' => ['label', 'log', 'backtrace', 'type'],\n        'rows' => [],\n    ];\n\n    protected static bool $sendHeaders = true;\n\n    public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        if (!$this->isWebRequest()) {\n            return;\n        }\n\n        $messages = [];\n\n        foreach ($records as $record) {\n            if ($record->level < $this->level) {\n                continue;\n            }\n\n            $message = $this->processRecord($record);\n            $messages[] = $message;\n        }\n\n        if (\\count($messages) > 0) {\n            $messages = $this->getFormatter()->formatBatch($messages);\n            self::$json['rows'] = array_merge(self::$json['rows'], $messages);\n            $this->send();\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new ChromePHPFormatter();\n    }\n\n    /**\n     * Creates & sends header for a record\n     *\n     * @see sendHeader()\n     * @see send()\n     */\n    protected function write(LogRecord $record): void\n    {\n        if (!$this->isWebRequest()) {\n            return;\n        }\n\n        self::$json['rows'][] = $record->formatted;\n\n        $this->send();\n    }\n\n    /**\n     * Sends the log header\n     *\n     * @see sendHeader()\n     */\n    protected function send(): void\n    {\n        if (self::$overflowed || !self::$sendHeaders) {\n            return;\n        }\n\n        if (!self::$initialized) {\n            self::$initialized = true;\n\n            self::$sendHeaders = $this->headersAccepted();\n            if (!self::$sendHeaders) {\n                return;\n            }\n\n            self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? '';\n        }\n\n        $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true);\n        $data = base64_encode($json);\n        if (\\strlen($data) > 3 * 1024) {\n            self::$overflowed = true;\n\n            $record = new LogRecord(\n                message: 'Incomplete logs, chrome header size limit reached',\n                level: Level::Warning,\n                channel: 'monolog',\n                datetime: new JsonSerializableDateTimeImmutable(true),\n            );\n            self::$json['rows'][\\count(self::$json['rows']) - 1] = $this->getFormatter()->format($record);\n            $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true);\n            $data = base64_encode($json);\n        }\n\n        if (trim($data) !== '') {\n            $this->sendHeader(static::HEADER_NAME, $data);\n        }\n    }\n\n    /**\n     * Send header string to the client\n     */\n    protected function sendHeader(string $header, string $content): void\n    {\n        if (!headers_sent() && self::$sendHeaders) {\n            header(sprintf('%s: %s', $header, $content));\n        }\n    }\n\n    /**\n     * Verifies if the headers are accepted by the current user agent\n     */\n    protected function headersAccepted(): bool\n    {\n        if (!isset($_SERVER['HTTP_USER_AGENT'])) {\n            return false;\n        }\n\n        return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/CouchDBHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\JsonFormatter;\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * CouchDB handler\n *\n * @author Markus Bachmann <markus.bachmann@bachi.biz>\n * @phpstan-type Options array{\n *     host: string,\n *     port: int,\n *     dbname: string,\n *     username: string|null,\n *     password: string|null\n * }\n * @phpstan-type InputOptions array{\n *     host?: string,\n *     port?: int,\n *     dbname?: string,\n *     username?: string|null,\n *     password?: string|null\n * }\n */\nclass CouchDBHandler extends AbstractProcessingHandler\n{\n    /**\n     * @var mixed[]\n     * @phpstan-var Options\n     */\n    private array $options;\n\n    /**\n     * @param mixed[] $options\n     *\n     * @phpstan-param InputOptions $options\n     */\n    public function __construct(array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        $this->options = array_merge([\n            'host'     => 'localhost',\n            'port'     => 5984,\n            'dbname'   => 'logger',\n            'username' => null,\n            'password' => null,\n        ], $options);\n\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $basicAuth = null;\n        if (null !== $this->options['username'] && null !== $this->options['password']) {\n            $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']);\n        }\n\n        $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname'];\n        $context = stream_context_create([\n            'http' => [\n                'method'        => 'POST',\n                'content'       => $record->formatted,\n                'ignore_errors' => true,\n                'max_redirects' => 0,\n                'header'        => 'Content-type: application/json',\n            ],\n        ]);\n\n        if (false === @file_get_contents($url, false, $context)) {\n            throw new \\RuntimeException(sprintf('Could not connect to %s', $url));\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/CubeHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Logs to Cube.\n *\n * @link https://github.com/square/cube/wiki\n * @author Wan Chen <kami@kamisama.me>\n * @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4\n */\nclass CubeHandler extends AbstractProcessingHandler\n{\n    private ?\\Socket $udpConnection = null;\n    private ?\\CurlHandle $httpConnection = null;\n    private string $scheme;\n    private string $host;\n    private int $port;\n    /** @var string[] */\n    private array $acceptedSchemes = ['http', 'udp'];\n\n    /**\n     * Create a Cube handler\n     *\n     * @throws \\UnexpectedValueException when given url is not a valid url.\n     *                                   A valid url must consist of three parts : protocol://host:port\n     *                                   Only valid protocols used by Cube are http and udp\n     */\n    public function __construct(string $url, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        $urlInfo = parse_url($url);\n\n        if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) {\n            throw new \\UnexpectedValueException('URL \"'.$url.'\" is not valid');\n        }\n\n        if (!\\in_array($urlInfo['scheme'], $this->acceptedSchemes, true)) {\n            throw new \\UnexpectedValueException(\n                'Invalid protocol (' . $urlInfo['scheme']  . ').'\n                . ' Valid options are ' . implode(', ', $this->acceptedSchemes)\n            );\n        }\n\n        $this->scheme = $urlInfo['scheme'];\n        $this->host = $urlInfo['host'];\n        $this->port = $urlInfo['port'];\n\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * Establish a connection to an UDP socket\n     *\n     * @throws \\LogicException           when unable to connect to the socket\n     * @throws MissingExtensionException when there is no socket extension\n     */\n    protected function connectUdp(): void\n    {\n        if (!\\extension_loaded('sockets')) {\n            throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler');\n        }\n\n        $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0);\n        if (false === $udpConnection) {\n            throw new \\LogicException('Unable to create a socket');\n        }\n\n        $this->udpConnection = $udpConnection;\n        if (!socket_connect($this->udpConnection, $this->host, $this->port)) {\n            throw new \\LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);\n        }\n    }\n\n    /**\n     * Establish a connection to an http server\n     *\n     * @throws \\LogicException           when unable to connect to the socket\n     * @throws MissingExtensionException when no curl extension\n     */\n    protected function connectHttp(): void\n    {\n        if (!\\extension_loaded('curl')) {\n            throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler');\n        }\n\n        $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');\n        if (false === $httpConnection) {\n            throw new \\LogicException('Unable to connect to ' . $this->host . ':' . $this->port);\n        }\n\n        $this->httpConnection = $httpConnection;\n        curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, \"POST\");\n        curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $date = $record->datetime;\n\n        $data = ['time' => $date->format('Y-m-d\\TH:i:s.uO')];\n        $context = $record->context;\n\n        if (isset($context['type'])) {\n            $data['type'] = $context['type'];\n            unset($context['type']);\n        } else {\n            $data['type'] = $record->channel;\n        }\n\n        $data['data'] = $context;\n        $data['data']['level'] = $record->level;\n\n        if ($this->scheme === 'http') {\n            $this->writeHttp(Utils::jsonEncode($data));\n        } else {\n            $this->writeUdp(Utils::jsonEncode($data));\n        }\n    }\n\n    private function writeUdp(string $data): void\n    {\n        if (null === $this->udpConnection) {\n            $this->connectUdp();\n        }\n\n        if (null === $this->udpConnection) {\n            throw new \\LogicException('No UDP socket could be opened');\n        }\n\n        socket_send($this->udpConnection, $data, \\strlen($data), 0);\n    }\n\n    private function writeHttp(string $data): void\n    {\n        if (null === $this->httpConnection) {\n            $this->connectHttp();\n        }\n\n        if (null === $this->httpConnection) {\n            throw new \\LogicException('No connection could be established');\n        }\n\n        curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');\n        curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [\n            'Content-Type: application/json',\n            'Content-Length: ' . \\strlen('['.$data.']'),\n        ]);\n\n        Curl\\Util::execute($this->httpConnection, 5);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/Curl/Util.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler\\Curl;\n\nuse CurlHandle;\n\n/**\n * This class is marked as internal and it is not under the BC promise of the package.\n *\n * @internal\n */\nfinal class Util\n{\n    /** @var array<int> */\n    private static array $retriableErrorCodes = [\n        CURLE_COULDNT_RESOLVE_HOST,\n        CURLE_COULDNT_CONNECT,\n        CURLE_HTTP_NOT_FOUND,\n        CURLE_READ_ERROR,\n        CURLE_OPERATION_TIMEOUTED,\n        CURLE_HTTP_POST_ERROR,\n        CURLE_SSL_CONNECT_ERROR,\n    ];\n\n    /**\n     * Executes a CURL request with optional retries and exception on failure\n     *\n     * @param  CurlHandle  $ch curl handler\n     * @return bool|string @see curl_exec\n     */\n    public static function execute(CurlHandle $ch, int $retries = 5): bool|string\n    {\n        while ($retries > 0) {\n            $retries--;\n            $curlResponse = curl_exec($ch);\n            if ($curlResponse === false) {\n                $curlErrno = curl_errno($ch);\n\n                if (false === \\in_array($curlErrno, self::$retriableErrorCodes, true) || $retries === 0) {\n                    $curlError = curl_error($ch);\n\n                    throw new \\RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError));\n                }\n                continue;\n            }\n\n            return $curlResponse;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/DeduplicationHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\LogRecord;\n\n/**\n * Simple handler wrapper that deduplicates log records across multiple requests\n *\n * It also includes the BufferHandler functionality and will buffer\n * all messages until the end of the request or flush() is called.\n *\n * This works by storing all log records' messages above $deduplicationLevel\n * to the file specified by $deduplicationStore. When further logs come in at the end of the\n * request (or when flush() is called), all those above $deduplicationLevel are checked\n * against the existing stored logs. If they match and the timestamps in the stored log is\n * not older than $time seconds, the new log record is discarded. If no log record is new, the\n * whole data set is discarded.\n *\n * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers\n * that send messages to people, to avoid spamming with the same message over and over in case of\n * a major component failure like a database server being down which makes all requests fail in the\n * same way.\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass DeduplicationHandler extends BufferHandler\n{\n    protected string $deduplicationStore;\n\n    protected Level $deduplicationLevel;\n\n    protected int $time;\n    protected bool $gc = false;\n\n    /**\n     * @param HandlerInterface             $handler            Handler.\n     * @param string|null                  $deduplicationStore The file/path where the deduplication log should be kept\n     * @param int|string|Level|LogLevel::* $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes\n     * @param int                          $time               The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through\n     * @param bool                         $bubble             Whether the messages that are handled can bubble up the stack or not\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $deduplicationLevel\n     */\n    public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, int|string|Level $deduplicationLevel = Level::Error, int $time = 60, bool $bubble = true)\n    {\n        parent::__construct($handler, 0, Level::Debug, $bubble, false);\n\n        $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore;\n        $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel);\n        $this->time = $time;\n    }\n\n    public function flush(): void\n    {\n        if ($this->bufferSize === 0) {\n            return;\n        }\n\n        $store = null;\n\n        if (file_exists($this->deduplicationStore)) {\n            $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);\n        }\n\n        $passthru = null;\n\n        foreach ($this->buffer as $record) {\n            if ($record->level->value >= $this->deduplicationLevel->value) {\n                $passthru = $passthru === true || !\\is_array($store) || !$this->isDuplicate($store, $record);\n                if ($passthru) {\n                    $line = $this->buildDeduplicationStoreEntry($record);\n                    file_put_contents($this->deduplicationStore, $line . \"\\n\", FILE_APPEND | LOCK_EX);\n                    if (!\\is_array($store)) {\n                        $store = [];\n                    }\n                    $store[] = $line;\n                }\n            }\n        }\n\n        // default of null is valid as well as if no record matches duplicationLevel we just pass through\n        if ($passthru === true || $passthru === null) {\n            $this->handler->handleBatch($this->buffer);\n        }\n\n        $this->clear();\n\n        if ($this->gc) {\n            $this->collectLogs();\n        }\n    }\n\n    /**\n     * If there is a store entry older than e.g. a day, this method should set `$this->gc` to `true` to trigger garbage collection.\n     * @param string[] $store The deduplication store\n     */\n    protected function isDuplicate(array $store, LogRecord $record): bool\n    {\n        $timestampValidity = $record->datetime->getTimestamp() - $this->time;\n        $expectedMessage = preg_replace('{[\\r\\n].*}', '', $record->message);\n        $yesterday = time() - 86400;\n\n        for ($i = \\count($store) - 1; $i >= 0; $i--) {\n            $parts = explode(':', $store[$i], 3);\n\n            if (\\count($parts) < 3) {\n                // Skip invalid/incomplete lines (e.g. partially written due to concurrent access)\n                continue;\n            }\n\n            [$timestamp, $level, $message] = $parts;\n\n            if ($level === $record->level->getName() && $message === $expectedMessage && $timestamp > $timestampValidity) {\n                return true;\n            }\n\n            if ($timestamp < $yesterday) {\n                $this->gc = true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * @return string The given record serialized as a single line of text\n     */\n    protected function buildDeduplicationStoreEntry(LogRecord $record): string\n    {\n        return $record->datetime->getTimestamp() . ':' . $record->level->getName() . ':' . preg_replace('{[\\r\\n].*}', '', $record->message);\n    }\n\n    private function collectLogs(): void\n    {\n        if (!file_exists($this->deduplicationStore)) {\n            return;\n        }\n\n        $handle = fopen($this->deduplicationStore, 'rw+');\n\n        if (false === $handle) {\n            throw new \\RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore);\n        }\n\n        if (false === flock($handle, LOCK_EX)) {\n            return;\n        }\n        $validLogs = [];\n\n        $timestampValidity = time() - $this->time;\n\n        while (!feof($handle)) {\n            $log = fgets($handle);\n            if (\\is_string($log) && '' !== $log && substr($log, 0, 10) >= $timestampValidity) {\n                $validLogs[] = $log;\n            }\n        }\n\n        ftruncate($handle, 0);\n        rewind($handle);\n        foreach ($validLogs as $log) {\n            fwrite($handle, $log);\n        }\n\n        flock($handle, LOCK_UN);\n        fclose($handle);\n\n        $this->gc = false;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/DoctrineCouchDBHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\NormalizerFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Doctrine\\CouchDB\\CouchDBClient;\nuse Monolog\\LogRecord;\n\n/**\n * CouchDB handler for Doctrine CouchDB ODM\n *\n * @author Markus Bachmann <markus.bachmann@bachi.biz>\n */\nclass DoctrineCouchDBHandler extends AbstractProcessingHandler\n{\n    private CouchDBClient $client;\n\n    public function __construct(CouchDBClient $client, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        $this->client = $client;\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->client->postDocument($record->formatted);\n    }\n\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new NormalizerFormatter;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/DynamoDbHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Aws\\Sdk;\nuse Aws\\DynamoDb\\DynamoDbClient;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Aws\\DynamoDb\\Marshaler;\nuse Monolog\\Formatter\\ScalarFormatter;\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/)\n *\n * @link https://github.com/aws/aws-sdk-php/\n * @author Andrew Lawson <adlawson@gmail.com>\n */\nclass DynamoDbHandler extends AbstractProcessingHandler\n{\n    public const DATE_FORMAT = 'Y-m-d\\TH:i:s.uO';\n\n    protected DynamoDbClient $client;\n\n    protected string $table;\n\n    protected Marshaler $marshaler;\n\n    public function __construct(DynamoDbClient $client, string $table, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        $this->marshaler = new Marshaler;\n\n        $this->client = $client;\n        $this->table = $table;\n\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $filtered = $this->filterEmptyFields($record->formatted);\n        $formatted = $this->marshaler->marshalItem($filtered);\n\n        $this->client->putItem([\n            'TableName' => $this->table,\n            'Item' => $formatted,\n        ]);\n    }\n\n    /**\n     * @param  mixed[] $record\n     * @return mixed[]\n     */\n    protected function filterEmptyFields(array $record): array\n    {\n        return array_filter($record, function ($value) {\n            return [] !== $value;\n        });\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new ScalarFormatter(self::DATE_FORMAT);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/ElasticaHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Elastic\\Transport\\Exception\\TransportException;\nuse Elastica\\Document;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\ElasticaFormatter;\nuse Monolog\\Level;\nuse Elastica\\Client;\nuse Elastica\\Exception\\ExceptionInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Elastic Search handler\n *\n * Usage example:\n *\n *    $client = new \\Elastica\\Client();\n *    $options = array(\n *        'index' => 'elastic_index_name',\n *        'type' => 'elastic_doc_type', Types have been removed in Elastica 7\n *    );\n *    $handler = new ElasticaHandler($client, $options);\n *    $log = new Logger('application');\n *    $log->pushHandler($handler);\n *\n * @author Jelle Vink <jelle.vink@gmail.com>\n * @phpstan-type Options array{\n *     index: string,\n *     type: string,\n *     ignore_error: bool\n * }\n * @phpstan-type InputOptions array{\n *     index?: string,\n *     type?: string,\n *     ignore_error?: bool\n * }\n */\nclass ElasticaHandler extends AbstractProcessingHandler\n{\n    protected Client $client;\n\n    /**\n     * @var mixed[] Handler config options\n     * @phpstan-var Options\n     */\n    protected array $options;\n\n    /**\n     * @param Client  $client  Elastica Client object\n     * @param mixed[] $options Handler configuration\n     *\n     * @phpstan-param InputOptions $options\n     */\n    public function __construct(Client $client, array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        parent::__construct($level, $bubble);\n        $this->client = $client;\n        $this->options = array_merge(\n            [\n                'index'          => 'monolog',      // Elastic index name\n                'type'           => 'record',       // Elastic document type\n                'ignore_error'   => false,          // Suppress Elastica exceptions\n            ],\n            $options\n        );\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->bulkSend([$record->formatted]);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        if ($formatter instanceof ElasticaFormatter) {\n            return parent::setFormatter($formatter);\n        }\n\n        throw new \\InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter');\n    }\n\n    /**\n     * @return mixed[]\n     *\n     * @phpstan-return Options\n     */\n    public function getOptions(): array\n    {\n        return $this->options;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new ElasticaFormatter($this->options['index'], $this->options['type']);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        $documents = $this->getFormatter()->formatBatch($records);\n        $this->bulkSend($documents);\n    }\n\n    /**\n     * Use Elasticsearch bulk API to send list of documents\n     *\n     * @param Document[] $documents\n     *\n     * @throws \\RuntimeException\n     */\n    protected function bulkSend(array $documents): void\n    {\n        try {\n            $this->client->addDocuments($documents);\n        } catch (ExceptionInterface | TransportException $e) {\n            if (!$this->options['ignore_error']) {\n                throw new \\RuntimeException(\"Error sending messages to Elasticsearch\", 0, $e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/ElasticsearchHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Elastic\\Elasticsearch\\Response\\Elasticsearch;\nuse Throwable;\nuse RuntimeException;\nuse Monolog\\Level;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\ElasticsearchFormatter;\nuse InvalidArgumentException;\nuse Elasticsearch\\Common\\Exceptions\\RuntimeException as ElasticsearchRuntimeException;\nuse Elasticsearch\\Client;\nuse Monolog\\LogRecord;\nuse Elastic\\Elasticsearch\\Exception\\InvalidArgumentException as ElasticInvalidArgumentException;\nuse Elastic\\Elasticsearch\\Client as Client8;\n\n/**\n * Elasticsearch handler\n *\n * @link https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html\n *\n * Simple usage example:\n *\n *    $client = \\Elasticsearch\\ClientBuilder::create()\n *        ->setHosts($hosts)\n *        ->build();\n *\n *    $options = array(\n *        'index' => 'elastic_index_name',\n *        'type'  => 'elastic_doc_type',\n *    );\n *    $handler = new ElasticsearchHandler($client, $options);\n *    $log = new Logger('application');\n *    $log->pushHandler($handler);\n *\n * @author Avtandil Kikabidze <akalongman@gmail.com>\n * @phpstan-type Options array{\n *     index: string,\n *     type: string,\n *     ignore_error: bool,\n *     op_type: 'index'|'create'\n * }\n * @phpstan-type InputOptions array{\n *     index?: string,\n *     type?: string,\n *     ignore_error?: bool,\n *     op_type?: 'index'|'create'\n * }\n */\nclass ElasticsearchHandler extends AbstractProcessingHandler\n{\n    protected Client|Client8 $client;\n\n    /**\n     * @var mixed[] Handler config options\n     * @phpstan-var Options\n     */\n    protected array $options;\n\n    /**\n     * @var bool\n     */\n    private $needsType;\n\n    /**\n     * @param Client|Client8 $client  Elasticsearch Client object\n     * @param mixed[]        $options Handler configuration\n     *\n     * @phpstan-param InputOptions $options\n     */\n    public function __construct(Client|Client8 $client, array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        parent::__construct($level, $bubble);\n        $this->client = $client;\n        $this->options = array_merge(\n            [\n                'index'        => 'monolog', // Elastic index name\n                'type'         => '_doc',    // Elastic document type\n                'ignore_error' => false,     // Suppress Elasticsearch exceptions\n                'op_type'      => 'index',   // Elastic op_type (index or create) (https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#docs-index-api-op_type)\n            ],\n            $options\n        );\n\n        if ($client instanceof Client8 || $client::VERSION[0] === '7') {\n            $this->needsType = false;\n            // force the type to _doc for ES8/ES7\n            $this->options['type'] = '_doc';\n        } else {\n            $this->needsType = true;\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->bulkSend([$record->formatted]);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        if ($formatter instanceof ElasticsearchFormatter) {\n            return parent::setFormatter($formatter);\n        }\n\n        throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter');\n    }\n\n    /**\n     * Getter options\n     *\n     * @return mixed[]\n     *\n     * @phpstan-return Options\n     */\n    public function getOptions(): array\n    {\n        return $this->options;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new ElasticsearchFormatter($this->options['index'], $this->options['type']);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        $documents = $this->getFormatter()->formatBatch($records);\n        $this->bulkSend($documents);\n    }\n\n    /**\n     * Use Elasticsearch bulk API to send list of documents\n     *\n     * @param  array<array<mixed>> $records Records + _index/_type keys\n     * @throws \\RuntimeException\n     */\n    protected function bulkSend(array $records): void\n    {\n        try {\n            $params = [\n                'body' => [],\n            ];\n\n            foreach ($records as $record) {\n                $params['body'][] = [\n                    $this->options['op_type'] => $this->needsType ? [\n                        '_index' => $record['_index'],\n                        '_type'  => $record['_type'],\n                    ] : [\n                        '_index' => $record['_index'],\n                    ],\n                ];\n                unset($record['_index'], $record['_type']);\n\n                $params['body'][] = $record;\n            }\n\n            /** @var Elasticsearch */\n            $responses = $this->client->bulk($params);\n\n            if ($responses['errors'] === true) {\n                throw $this->createExceptionFromResponses($responses);\n            }\n        } catch (Throwable $e) {\n            if (! $this->options['ignore_error']) {\n                throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e);\n            }\n        }\n    }\n\n    /**\n     * Creates elasticsearch exception from responses array\n     *\n     * Only the first error is converted into an exception.\n     *\n     * @param mixed[]|Elasticsearch $responses returned by $this->client->bulk()\n     */\n    protected function createExceptionFromResponses($responses): Throwable\n    {\n        foreach ($responses['items'] ?? [] as $item) {\n            if (isset($item['index']['error'])) {\n                return $this->createExceptionFromError($item['index']['error']);\n            }\n        }\n\n        if (class_exists(ElasticInvalidArgumentException::class)) {\n            return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.');\n        }\n\n        if (class_exists(ElasticsearchRuntimeException::class)) {\n            return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.');\n        }\n\n        throw new \\LogicException('Unsupported elastic search client version');\n    }\n\n    /**\n     * Creates elasticsearch exception from error array\n     *\n     * @param mixed[] $error\n     */\n    protected function createExceptionFromError(array $error): Throwable\n    {\n        $previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null;\n\n        if (class_exists(ElasticInvalidArgumentException::class)) {\n            return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous);\n        }\n\n        if (class_exists(ElasticsearchRuntimeException::class)) {\n            return new ElasticsearchRuntimeException($error['type'].': '.$error['reason'], 0, $previous);\n        }\n\n        throw new \\LogicException('Unsupported elastic search client version');\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/ErrorLogHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Stores to PHP error_log() handler.\n *\n * @author Elan Ruusamäe <glen@delfi.ee>\n */\nclass ErrorLogHandler extends AbstractProcessingHandler\n{\n    public const OPERATING_SYSTEM = 0;\n    public const SAPI = 4;\n\n    /** @var 0|4 */\n    protected int $messageType;\n    protected bool $expandNewlines;\n\n    /**\n     * @param 0|4 $messageType    Says where the error should go.\n     * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries\n     *\n     * @throws \\InvalidArgumentException If an unsupported message type is set\n     */\n    public function __construct(int $messageType = self::OPERATING_SYSTEM, int|string|Level $level = Level::Debug, bool $bubble = true, bool $expandNewlines = false)\n    {\n        parent::__construct($level, $bubble);\n\n        if (false === \\in_array($messageType, self::getAvailableTypes(), true)) {\n            $message = sprintf('The given message type \"%s\" is not supported', print_r($messageType, true));\n\n            throw new \\InvalidArgumentException($message);\n        }\n\n        $this->messageType = $messageType;\n        $this->expandNewlines = $expandNewlines;\n    }\n\n    /**\n     * @return int[] With all available types\n     */\n    public static function getAvailableTypes(): array\n    {\n        return [\n            self::OPERATING_SYSTEM,\n            self::SAPI,\n        ];\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%');\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        if (!$this->expandNewlines) {\n            error_log((string) $record->formatted, $this->messageType);\n\n            return;\n        }\n\n        $lines = preg_split('{[\\r\\n]+}', (string) $record->formatted);\n        if ($lines === false) {\n            $pcreErrorCode = preg_last_error();\n\n            throw new \\RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. preg_last_error_msg());\n        }\n        foreach ($lines as $line) {\n            error_log($line, $this->messageType);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FallbackGroupHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Throwable;\nuse Monolog\\LogRecord;\n\n/**\n * Forwards records to at most one handler\n *\n * If a handler fails, the exception is suppressed and the record is forwarded to the next handler.\n *\n * As soon as one handler handles a record successfully, the handling stops there.\n */\nclass FallbackGroupHandler extends GroupHandler\n{\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        if (\\count($this->processors) > 0) {\n            $record = $this->processRecord($record);\n        }\n        foreach ($this->handlers as $handler) {\n            try {\n                $handler->handle(clone $record);\n                break;\n            } catch (Throwable $e) {\n                // What throwable?\n            }\n        }\n\n        return false === $this->bubble;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        if (\\count($this->processors) > 0) {\n            $processed = [];\n            foreach ($records as $record) {\n                $processed[] = $this->processRecord($record);\n            }\n            $records = $processed;\n        }\n\n        foreach ($this->handlers as $handler) {\n            try {\n                $handler->handleBatch(array_map(fn ($record) => clone $record, $records));\n                break;\n            } catch (Throwable $e) {\n                // What throwable?\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FilterHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Closure;\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Monolog\\ResettableInterface;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\LogRecord;\n\n/**\n * Simple handler wrapper that filters records based on a list of levels\n *\n * It can be configured with an exact list of levels to allow, or a min/max level.\n *\n * @author Hennadiy Verkh\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface\n{\n    use ProcessableHandlerTrait;\n\n    /**\n     * Handler or factory Closure($record, $this)\n     *\n     * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface\n     */\n    protected Closure|HandlerInterface $handler;\n\n    /**\n     * Minimum level for logs that are passed to handler\n     *\n     * @var bool[] Map of Level value => true\n     * @phpstan-var array<value-of<Level::VALUES>, true>\n     */\n    protected array $acceptedLevels;\n\n    /**\n     * Whether the messages that are handled can bubble up the stack or not\n     */\n    protected bool $bubble;\n\n    /**\n     * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler\n     *\n     * @param Closure|HandlerInterface                             $handler        Handler or factory Closure($record|null, $filterHandler).\n     * @param int|string|Level|array<int|string|Level|LogLevel::*> $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided\n     * @param int|string|Level|LogLevel::*                         $maxLevel       Maximum level to accept, only used if $minLevelOrList is not an array\n     * @param bool                                                 $bubble         Whether the messages that are handled can bubble up the stack or not\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::*|array<value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::*> $minLevelOrList\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $maxLevel\n     */\n    public function __construct(Closure|HandlerInterface $handler, int|string|Level|array $minLevelOrList = Level::Debug, int|string|Level $maxLevel = Level::Emergency, bool $bubble = true)\n    {\n        $this->handler  = $handler;\n        $this->bubble   = $bubble;\n        $this->setAcceptedLevels($minLevelOrList, $maxLevel);\n    }\n\n    /**\n     * @phpstan-return list<Level> List of levels\n     */\n    public function getAcceptedLevels(): array\n    {\n        return array_map(fn (int $level) => Level::from($level), array_keys($this->acceptedLevels));\n    }\n\n    /**\n     * @param  int|string|Level|LogLevel::*|array<int|string|Level|LogLevel::*> $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided\n     * @param  int|string|Level|LogLevel::*                                     $maxLevel       Maximum level or level name to accept, only used if $minLevelOrList is not an array\n     * @return $this\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::*|array<value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::*> $minLevelOrList\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $maxLevel\n     */\n    public function setAcceptedLevels(int|string|Level|array $minLevelOrList = Level::Debug, int|string|Level $maxLevel = Level::Emergency): self\n    {\n        if (\\is_array($minLevelOrList)) {\n            $acceptedLevels = array_map(Logger::toMonologLevel(...), $minLevelOrList);\n        } else {\n            $minLevelOrList = Logger::toMonologLevel($minLevelOrList);\n            $maxLevel = Logger::toMonologLevel($maxLevel);\n            $acceptedLevels = array_values(array_filter(Level::cases(), fn (Level $level) => $level->value >= $minLevelOrList->value && $level->value <= $maxLevel->value));\n        }\n        $this->acceptedLevels = [];\n        foreach ($acceptedLevels as $level) {\n            $this->acceptedLevels[$level->value] = true;\n        }\n\n        return $this;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function isHandling(LogRecord $record): bool\n    {\n        return isset($this->acceptedLevels[$record->level->value]);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        if (!$this->isHandling($record)) {\n            return false;\n        }\n\n        if (\\count($this->processors) > 0) {\n            $record = $this->processRecord($record);\n        }\n\n        $this->getHandler($record)->handle($record);\n\n        return false === $this->bubble;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        $filtered = [];\n        foreach ($records as $record) {\n            if ($this->isHandling($record)) {\n                $filtered[] = $record;\n            }\n        }\n\n        if (\\count($filtered) > 0) {\n            $this->getHandler($filtered[\\count($filtered) - 1])->handleBatch($filtered);\n        }\n    }\n\n    /**\n     * Return the nested handler\n     *\n     * If the handler was provided as a factory, this will trigger the handler's instantiation.\n     */\n    public function getHandler(LogRecord|null $record = null): HandlerInterface\n    {\n        if (!$this->handler instanceof HandlerInterface) {\n            $handler = ($this->handler)($record, $this);\n            if (!$handler instanceof HandlerInterface) {\n                throw new \\RuntimeException(\"The factory Closure should return a HandlerInterface\");\n            }\n            $this->handler = $handler;\n        }\n\n        return $this->handler;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        $handler = $this->getHandler();\n        if ($handler instanceof FormattableHandlerInterface) {\n            $handler->setFormatter($formatter);\n\n            return $this;\n        }\n\n        throw new \\UnexpectedValueException('The nested handler of type '.\\get_class($handler).' does not support formatters.');\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getFormatter(): FormatterInterface\n    {\n        $handler = $this->getHandler();\n        if ($handler instanceof FormattableHandlerInterface) {\n            return $handler->getFormatter();\n        }\n\n        throw new \\UnexpectedValueException('The nested handler of type '.\\get_class($handler).' does not support formatters.');\n    }\n\n    public function reset(): void\n    {\n        $this->resetProcessors();\n\n        if ($this->getHandler() instanceof ResettableInterface) {\n            $this->getHandler()->reset();\n        }\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler\\FingersCrossed;\n\nuse Monolog\\LogRecord;\n\n/**\n * Interface for activation strategies for the FingersCrossedHandler.\n *\n * @author Johannes M. Schmitt <schmittjoh@gmail.com>\n */\ninterface ActivationStrategyInterface\n{\n    /**\n     * Returns whether the given record activates the handler.\n     */\n    public function isHandlerActivated(LogRecord $record): bool;\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler\\FingersCrossed;\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\LogRecord;\n\n/**\n * Channel and Error level based monolog activation strategy. Allows to trigger activation\n * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except\n * for records of the 'sql' channel; those should trigger activation on level 'WARN'.\n *\n * Example:\n *\n * <code>\n *   $activationStrategy = new ChannelLevelActivationStrategy(\n *       Level::Critical,\n *       array(\n *           'request' => Level::Alert,\n *           'sensitive' => Level::Error,\n *       )\n *   );\n *   $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy);\n * </code>\n *\n * @author Mike Meessen <netmikey@gmail.com>\n */\nclass ChannelLevelActivationStrategy implements ActivationStrategyInterface\n{\n    private Level $defaultActionLevel;\n\n    /**\n     * @var array<string, Level>\n     */\n    private array $channelToActionLevel;\n\n    /**\n     * @param int|string|Level|LogLevel::*                $defaultActionLevel   The default action level to be used if the record's category doesn't match any\n     * @param array<string, int|string|Level|LogLevel::*> $channelToActionLevel An array that maps channel names to action levels.\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $defaultActionLevel\n     * @phpstan-param array<string, value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::*> $channelToActionLevel\n     */\n    public function __construct(int|string|Level $defaultActionLevel, array $channelToActionLevel = [])\n    {\n        $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel);\n        $this->channelToActionLevel = array_map(Logger::toMonologLevel(...), $channelToActionLevel);\n    }\n\n    public function isHandlerActivated(LogRecord $record): bool\n    {\n        if (isset($this->channelToActionLevel[$record->channel])) {\n            return $record->level->value >= $this->channelToActionLevel[$record->channel]->value;\n        }\n\n        return $record->level->value >= $this->defaultActionLevel->value;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler\\FingersCrossed;\n\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\nuse Monolog\\Logger;\nuse Psr\\Log\\LogLevel;\n\n/**\n * Error level based activation strategy.\n *\n * @author Johannes M. Schmitt <schmittjoh@gmail.com>\n */\nclass ErrorLevelActivationStrategy implements ActivationStrategyInterface\n{\n    private Level $actionLevel;\n\n    /**\n     * @param int|string|Level $actionLevel Level or name or value\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $actionLevel\n     */\n    public function __construct(int|string|Level $actionLevel)\n    {\n        $this->actionLevel = Logger::toMonologLevel($actionLevel);\n    }\n\n    public function isHandlerActivated(LogRecord $record): bool\n    {\n        return $record->level->value >= $this->actionLevel->value;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FingersCrossedHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Closure;\nuse Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy;\nuse Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface;\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Monolog\\ResettableInterface;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\LogRecord;\n\n/**\n * Buffers all records until a certain level is reached\n *\n * The advantage of this approach is that you don't get any clutter in your log files.\n * Only requests which actually trigger an error (or whatever your actionLevel is) will be\n * in the logs, but they will contain all records, not only those above the level threshold.\n *\n * You can then have a passthruLevel as well which means that at the end of the request,\n * even if it did not get activated, it will still send through log records of e.g. at least a\n * warning level.\n *\n * You can find the various activation strategies in the\n * Monolog\\Handler\\FingersCrossed\\ namespace.\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface\n{\n    use ProcessableHandlerTrait;\n\n    /**\n     * Handler or factory Closure($record, $this)\n     *\n     * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface\n     */\n    protected Closure|HandlerInterface $handler;\n\n    protected ActivationStrategyInterface $activationStrategy;\n\n    protected bool $buffering = true;\n\n    protected int $bufferSize;\n\n    /** @var LogRecord[] */\n    protected array $buffer = [];\n\n    protected bool $stopBuffering;\n\n    protected Level|null $passthruLevel = null;\n\n    protected bool $bubble;\n\n    /**\n     * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler\n     *\n     * @param Closure|HandlerInterface          $handler            Handler or factory Closure($record|null, $fingersCrossedHandler).\n     * @param int|string|Level|LogLevel::*|null $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated\n     * @param int                               $bufferSize         How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.\n     * @param bool                              $bubble             Whether the messages that are handled can bubble up the stack or not\n     * @param bool                              $stopBuffering      Whether the handler should stop buffering after being triggered (default true)\n     * @param int|string|Level|LogLevel::*|null $passthruLevel      Minimum level to always flush to handler on close, even if strategy not triggered\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::*|ActivationStrategyInterface|null $activationStrategy\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::*|null $passthruLevel\n     */\n    public function __construct(Closure|HandlerInterface $handler, int|string|Level|ActivationStrategyInterface|null $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, int|string|Level|null $passthruLevel = null)\n    {\n        if (null === $activationStrategy) {\n            $activationStrategy = new ErrorLevelActivationStrategy(Level::Warning);\n        }\n\n        // convert simple int activationStrategy to an object\n        if (!$activationStrategy instanceof ActivationStrategyInterface) {\n            $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy);\n        }\n\n        $this->handler = $handler;\n        $this->activationStrategy = $activationStrategy;\n        $this->bufferSize = $bufferSize;\n        $this->bubble = $bubble;\n        $this->stopBuffering = $stopBuffering;\n\n        if ($passthruLevel !== null) {\n            $this->passthruLevel = Logger::toMonologLevel($passthruLevel);\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function isHandling(LogRecord $record): bool\n    {\n        return true;\n    }\n\n    /**\n     * Manually activate this logger regardless of the activation strategy\n     */\n    public function activate(): void\n    {\n        if ($this->stopBuffering) {\n            $this->buffering = false;\n        }\n\n        $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer);\n        $this->buffer = [];\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        if (\\count($this->processors) > 0) {\n            $record = $this->processRecord($record);\n        }\n\n        if ($this->buffering) {\n            $this->buffer[] = $record;\n            if ($this->bufferSize > 0 && \\count($this->buffer) > $this->bufferSize) {\n                array_shift($this->buffer);\n            }\n            if ($this->activationStrategy->isHandlerActivated($record)) {\n                $this->activate();\n            }\n        } else {\n            $this->getHandler($record)->handle($record);\n        }\n\n        return false === $this->bubble;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function close(): void\n    {\n        $this->flushBuffer();\n\n        $this->getHandler()->close();\n    }\n\n    public function reset(): void\n    {\n        $this->flushBuffer();\n\n        $this->resetProcessors();\n\n        if ($this->getHandler() instanceof ResettableInterface) {\n            $this->getHandler()->reset();\n        }\n    }\n\n    /**\n     * Clears the buffer without flushing any messages down to the wrapped handler.\n     *\n     * It also resets the handler to its initial buffering state.\n     */\n    public function clear(): void\n    {\n        $this->buffer = [];\n        $this->reset();\n    }\n\n    /**\n     * Resets the state of the handler. Stops forwarding records to the wrapped handler.\n     */\n    private function flushBuffer(): void\n    {\n        if (null !== $this->passthruLevel) {\n            $passthruLevel = $this->passthruLevel;\n            $this->buffer = array_filter($this->buffer, static function ($record) use ($passthruLevel) {\n                return $passthruLevel->includes($record->level);\n            });\n            if (\\count($this->buffer) > 0) {\n                $this->getHandler(end($this->buffer))->handleBatch($this->buffer);\n            }\n        }\n\n        $this->buffer = [];\n        $this->buffering = true;\n    }\n\n    /**\n     * Return the nested handler\n     *\n     * If the handler was provided as a factory, this will trigger the handler's instantiation.\n     */\n    public function getHandler(LogRecord|null $record = null): HandlerInterface\n    {\n        if (!$this->handler instanceof HandlerInterface) {\n            $handler = ($this->handler)($record, $this);\n            if (!$handler instanceof HandlerInterface) {\n                throw new \\RuntimeException(\"The factory Closure should return a HandlerInterface\");\n            }\n            $this->handler = $handler;\n        }\n\n        return $this->handler;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        $handler = $this->getHandler();\n        if ($handler instanceof FormattableHandlerInterface) {\n            $handler->setFormatter($formatter);\n\n            return $this;\n        }\n\n        throw new \\UnexpectedValueException('The nested handler of type '.\\get_class($handler).' does not support formatters.');\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getFormatter(): FormatterInterface\n    {\n        $handler = $this->getHandler();\n        if ($handler instanceof FormattableHandlerInterface) {\n            return $handler->getFormatter();\n        }\n\n        throw new \\UnexpectedValueException('The nested handler of type '.\\get_class($handler).' does not support formatters.');\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FirePHPHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\WildfireFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol.\n *\n * @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>\n */\nclass FirePHPHandler extends AbstractProcessingHandler\n{\n    use WebRequestRecognizerTrait;\n\n    /**\n     * WildFire JSON header message format\n     */\n    protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2';\n\n    /**\n     * FirePHP structure for parsing messages & their presentation\n     */\n    protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1';\n\n    /**\n     * Must reference a \"known\" plugin, otherwise headers won't display in FirePHP\n     */\n    protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3';\n\n    /**\n     * Header prefix for Wildfire to recognize & parse headers\n     */\n    protected const HEADER_PREFIX = 'X-Wf';\n\n    /**\n     * Whether or not Wildfire vendor-specific headers have been generated & sent yet\n     */\n    protected static bool $initialized = false;\n\n    /**\n     * Shared static message index between potentially multiple handlers\n     */\n    protected static int $messageIndex = 1;\n\n    protected static bool $sendHeaders = true;\n\n    /**\n     * Base header creation function used by init headers & record headers\n     *\n     * @param array<int|string> $meta    Wildfire Plugin, Protocol & Structure Indexes\n     * @param string            $message Log message\n     *\n     * @return array<string, string> Complete header string ready for the client as key and message as value\n     *\n     * @phpstan-return non-empty-array<string, string>\n     */\n    protected function createHeader(array $meta, string $message): array\n    {\n        $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta));\n\n        return [$header => $message];\n    }\n\n    /**\n     * Creates message header from record\n     *\n     * @return array<string, string>\n     *\n     * @phpstan-return non-empty-array<string, string>\n     *\n     * @see createHeader()\n     */\n    protected function createRecordHeader(LogRecord $record): array\n    {\n        // Wildfire is extensible to support multiple protocols & plugins in a single request,\n        // but we're not taking advantage of that (yet), so we're using \"1\" for simplicity's sake.\n        return $this->createHeader(\n            [1, 1, 1, self::$messageIndex++],\n            $record->formatted\n        );\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new WildfireFormatter();\n    }\n\n    /**\n     * Wildfire initialization headers to enable message parsing\n     *\n     * @see createHeader()\n     * @see sendHeader()\n     *\n     * @return array<string, string>\n     */\n    protected function getInitHeaders(): array\n    {\n        // Initial payload consists of required headers for Wildfire\n        return array_merge(\n            $this->createHeader(['Protocol', 1], static::PROTOCOL_URI),\n            $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI),\n            $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI)\n        );\n    }\n\n    /**\n     * Send header string to the client\n     */\n    protected function sendHeader(string $header, string $content): void\n    {\n        if (!headers_sent() && self::$sendHeaders) {\n            header(sprintf('%s: %s', $header, $content));\n        }\n    }\n\n    /**\n     * Creates & sends header for a record, ensuring init headers have been sent prior\n     *\n     * @see sendHeader()\n     * @see sendInitHeaders()\n     */\n    protected function write(LogRecord $record): void\n    {\n        if (!self::$sendHeaders || !$this->isWebRequest()) {\n            return;\n        }\n\n        // WildFire-specific headers must be sent prior to any messages\n        if (!self::$initialized) {\n            self::$initialized = true;\n\n            self::$sendHeaders = $this->headersAccepted();\n            if (!self::$sendHeaders) {\n                return;\n            }\n\n            foreach ($this->getInitHeaders() as $header => $content) {\n                $this->sendHeader($header, $content);\n            }\n        }\n\n        $header = $this->createRecordHeader($record);\n        if (trim(current($header)) !== '') {\n            $this->sendHeader(key($header), current($header));\n        }\n    }\n\n    /**\n     * Verifies if the headers are accepted by the current user agent\n     */\n    protected function headersAccepted(): bool\n    {\n        if (isset($_SERVER['HTTP_USER_AGENT']) && 1 === preg_match('{\\bFirePHP/\\d+\\.\\d+\\b}', $_SERVER['HTTP_USER_AGENT'])) {\n            return true;\n        }\n\n        return isset($_SERVER['HTTP_X_FIREPHP_VERSION']);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FleepHookHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * Sends logs to Fleep.io using Webhook integrations\n *\n * You'll need a Fleep.io account to use this handler.\n *\n * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation\n * @author Ando Roots <ando@sqroot.eu>\n */\nclass FleepHookHandler extends SocketHandler\n{\n    protected const FLEEP_HOST = 'fleep.io';\n\n    protected const FLEEP_HOOK_URI = '/hook/';\n\n    /**\n     * @var string Webhook token (specifies the conversation where logs are sent)\n     */\n    protected string $token;\n\n    /**\n     * Construct a new Fleep.io Handler.\n     *\n     * For instructions on how to create a new web hook in your conversations\n     * see https://fleep.io/integrations/webhooks/\n     *\n     * @param  string                    $token Webhook token\n     * @throws MissingExtensionException if OpenSSL is missing\n     */\n    public function __construct(\n        string $token,\n        $level = Level::Debug,\n        bool $bubble = true,\n        bool $persistent = false,\n        float $timeout = 0.0,\n        float $writingTimeout = 10.0,\n        ?float $connectionTimeout = null,\n        ?int $chunkSize = null\n    ) {\n        if (!\\extension_loaded('openssl')) {\n            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler');\n        }\n\n        $this->token = $token;\n\n        $connectionString = 'ssl://' . static::FLEEP_HOST . ':443';\n        parent::__construct(\n            $connectionString,\n            $level,\n            $bubble,\n            $persistent,\n            $timeout,\n            $writingTimeout,\n            $connectionTimeout,\n            $chunkSize\n        );\n    }\n\n    /**\n     * Returns the default formatter to use with this handler\n     *\n     * Overloaded to remove empty context and extra arrays from the end of the log message.\n     *\n     * @return LineFormatter\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new LineFormatter(null, null, true, true);\n    }\n\n    /**\n     * Handles a log record\n     */\n    public function write(LogRecord $record): void\n    {\n        parent::write($record);\n        $this->closeSocket();\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function generateDataStream(LogRecord $record): string\n    {\n        $content = $this->buildContent($record);\n\n        return $this->buildHeader($content) . $content;\n    }\n\n    /**\n     * Builds the header of the API Call\n     */\n    private function buildHeader(string $content): string\n    {\n        $header = \"POST \" . static::FLEEP_HOOK_URI . $this->token . \" HTTP/1.1\\r\\n\";\n        $header .= \"Host: \" . static::FLEEP_HOST . \"\\r\\n\";\n        $header .= \"Content-Type: application/x-www-form-urlencoded\\r\\n\";\n        $header .= \"Content-Length: \" . \\strlen($content) . \"\\r\\n\";\n        $header .= \"\\r\\n\";\n\n        return $header;\n    }\n\n    /**\n     * Builds the body of API call\n     */\n    private function buildContent(LogRecord $record): string\n    {\n        $dataArray = [\n            'message' => $record->formatted,\n        ];\n\n        return http_build_query($dataArray);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FlowdockHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\Formatter\\FlowdockFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Sends notifications through the Flowdock push API\n *\n * This must be configured with a FlowdockFormatter instance via setFormatter()\n *\n * Notes:\n * API token - Flowdock API token\n *\n * @author Dominik Liebler <liebler.dominik@gmail.com>\n * @see https://www.flowdock.com/api/push\n * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4\n */\nclass FlowdockHandler extends SocketHandler\n{\n    protected string $apiToken;\n\n    /**\n     * @throws MissingExtensionException if OpenSSL is missing\n     */\n    public function __construct(\n        string $apiToken,\n        $level = Level::Debug,\n        bool $bubble = true,\n        bool $persistent = false,\n        float $timeout = 0.0,\n        float $writingTimeout = 10.0,\n        ?float $connectionTimeout = null,\n        ?int $chunkSize = null\n    ) {\n        if (!\\extension_loaded('openssl')) {\n            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler');\n        }\n\n        parent::__construct(\n            'ssl://api.flowdock.com:443',\n            $level,\n            $bubble,\n            $persistent,\n            $timeout,\n            $writingTimeout,\n            $connectionTimeout,\n            $chunkSize\n        );\n        $this->apiToken = $apiToken;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        if (!$formatter instanceof FlowdockFormatter) {\n            throw new \\InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\\Formatter\\FlowdockFormatter to function correctly');\n        }\n\n        return parent::setFormatter($formatter);\n    }\n\n    /**\n     * Gets the default formatter.\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        throw new \\InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\\Formatter\\FlowdockFormatter to function correctly');\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        parent::write($record);\n\n        $this->closeSocket();\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function generateDataStream(LogRecord $record): string\n    {\n        $content = $this->buildContent($record);\n\n        return $this->buildHeader($content) . $content;\n    }\n\n    /**\n     * Builds the body of API call\n     */\n    private function buildContent(LogRecord $record): string\n    {\n        return Utils::jsonEncode($record->formatted);\n    }\n\n    /**\n     * Builds the header of the API Call\n     */\n    private function buildHeader(string $content): string\n    {\n        $header = \"POST /v1/messages/team_inbox/\" . $this->apiToken . \" HTTP/1.1\\r\\n\";\n        $header .= \"Host: api.flowdock.com\\r\\n\";\n        $header .= \"Content-Type: application/json\\r\\n\";\n        $header .= \"Content-Length: \" . \\strlen($content) . \"\\r\\n\";\n        $header .= \"\\r\\n\";\n\n        return $header;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FormattableHandlerInterface.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FormatterInterface;\n\n/**\n * Interface to describe loggers that have a formatter\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\ninterface FormattableHandlerInterface\n{\n    /**\n     * Sets the formatter.\n     *\n     * @return HandlerInterface self\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface;\n\n    /**\n     * Gets the formatter.\n     */\n    public function getFormatter(): FormatterInterface;\n}\n"
  },
  {
    "path": "src/Monolog/Handler/FormattableHandlerTrait.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\LineFormatter;\n\n/**\n * Helper trait for implementing FormattableInterface\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\ntrait FormattableHandlerTrait\n{\n    protected FormatterInterface|null $formatter = null;\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        $this->formatter = $formatter;\n\n        return $this;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getFormatter(): FormatterInterface\n    {\n        if (null === $this->formatter) {\n            $this->formatter = $this->getDefaultFormatter();\n        }\n\n        return $this->formatter;\n    }\n\n    /**\n     * Gets the default formatter.\n     *\n     * Overwrite this if the LineFormatter is not a good default for your handler.\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new LineFormatter();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/GelfHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Gelf\\PublisherInterface;\nuse Monolog\\Level;\nuse Monolog\\Formatter\\GelfMessageFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Handler to send messages to a Graylog2 (http://www.graylog2.org) server\n *\n * @author Matt Lehner <mlehner@gmail.com>\n * @author Benjamin Zikarsky <benjamin@zikarsky.de>\n */\nclass GelfHandler extends AbstractProcessingHandler\n{\n    /**\n     * @var PublisherInterface the publisher object that sends the message to the server\n     */\n    protected PublisherInterface $publisher;\n\n    /**\n     * @param PublisherInterface $publisher a gelf publisher object\n     */\n    public function __construct(PublisherInterface $publisher, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        parent::__construct($level, $bubble);\n\n        $this->publisher = $publisher;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->publisher->publish($record->formatted);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new GelfMessageFormatter();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/GroupHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\ResettableInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Forwards records to multiple handlers\n *\n * @author Lenar Lõhmus <lenar@city.ee>\n */\nclass GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface\n{\n    use ProcessableHandlerTrait;\n\n    /** @var HandlerInterface[] */\n    protected array $handlers;\n    protected bool $bubble;\n\n    /**\n     * @param HandlerInterface[] $handlers Array of Handlers.\n     * @param bool               $bubble   Whether the messages that are handled can bubble up the stack or not\n     *\n     * @throws \\InvalidArgumentException if an unsupported handler is set\n     */\n    public function __construct(array $handlers, bool $bubble = true)\n    {\n        foreach ($handlers as $handler) {\n            if (!$handler instanceof HandlerInterface) {\n                throw new \\InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.');\n            }\n        }\n\n        $this->handlers = $handlers;\n        $this->bubble = $bubble;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function isHandling(LogRecord $record): bool\n    {\n        foreach ($this->handlers as $handler) {\n            if ($handler->isHandling($record)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        if (\\count($this->processors) > 0) {\n            $record = $this->processRecord($record);\n        }\n\n        foreach ($this->handlers as $handler) {\n            $handler->handle(clone $record);\n        }\n\n        return false === $this->bubble;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        if (\\count($this->processors) > 0) {\n            $processed = [];\n            foreach ($records as $record) {\n                $processed[] = $this->processRecord($record);\n            }\n            $records = $processed;\n        }\n\n        foreach ($this->handlers as $handler) {\n            $handler->handleBatch(array_map(fn ($record) => clone $record, $records));\n        }\n    }\n\n    public function reset(): void\n    {\n        $this->resetProcessors();\n\n        foreach ($this->handlers as $handler) {\n            if ($handler instanceof ResettableInterface) {\n                $handler->reset();\n            }\n        }\n    }\n\n    public function close(): void\n    {\n        parent::close();\n\n        foreach ($this->handlers as $handler) {\n            $handler->close();\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        foreach ($this->handlers as $handler) {\n            if ($handler instanceof FormattableHandlerInterface) {\n                $handler->setFormatter($formatter);\n            }\n        }\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/Handler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\n/**\n * Base Handler class providing basic close() support as well as handleBatch\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nabstract class Handler implements HandlerInterface\n{\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        foreach ($records as $record) {\n            $this->handle($record);\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function close(): void\n    {\n    }\n\n    public function __destruct()\n    {\n        try {\n            $this->close();\n        } catch (\\Throwable $e) {\n            // do nothing\n        }\n    }\n\n    public function __serialize(): array\n    {\n        $this->close();\n\n        return (array) $this;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/HandlerInterface.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\LogRecord;\n\n/**\n * Interface that all Monolog Handlers must implement\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\ninterface HandlerInterface\n{\n    /**\n     * Checks whether the given record will be handled by this handler.\n     *\n     * This is mostly done for performance reasons, to avoid calling processors for nothing.\n     *\n     * Handlers should still check the record levels within handle(), returning false in isHandling()\n     * is no guarantee that handle() will not be called, and isHandling() might not be called\n     * for a given record.\n     *\n     * @param LogRecord $record Partial log record having only a level initialized\n     */\n    public function isHandling(LogRecord $record): bool;\n\n    /**\n     * Handles a record.\n     *\n     * All records may be passed to this method, and the handler should discard\n     * those that it does not want to handle.\n     *\n     * The return value of this function controls the bubbling process of the handler stack.\n     * Unless the bubbling is interrupted (by returning true), the Logger class will keep on\n     * calling further handlers in the stack with a given log record.\n     *\n     * @param  LogRecord $record The record to handle\n     * @return bool      true means that this handler handled the record, and that bubbling is not permitted.\n     *                   false means the record was either not processed or that this handler allows bubbling.\n     */\n    public function handle(LogRecord $record): bool;\n\n    /**\n     * Handles a set of records at once.\n     *\n     * @param array<LogRecord> $records The records to handle\n     */\n    public function handleBatch(array $records): void;\n\n    /**\n     * Closes the handler.\n     *\n     * Ends a log cycle and frees all resources used by the handler.\n     *\n     * Closing a Handler means flushing all buffers and freeing any open resources/handles.\n     *\n     * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage)\n     * and ideally handlers should be able to reopen themselves on handle() after they have been closed.\n     *\n     * This is useful at the end of a request and will be called automatically when the object\n     * is destroyed if you extend Monolog\\Handler\\Handler.\n     *\n     * If you are thinking of calling this method yourself, most likely you should be\n     * calling ResettableInterface::reset instead. Have a look.\n     */\n    public function close(): void;\n}\n"
  },
  {
    "path": "src/Monolog/Handler/HandlerWrapper.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\ResettableInterface;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\LogRecord;\n\n/**\n * This simple wrapper class can be used to extend handlers functionality.\n *\n * Example: A custom filtering that can be applied to any handler.\n *\n * Inherit from this class and override handle() like this:\n *\n *   public function handle(LogRecord $record)\n *   {\n *        if ($record meets certain conditions) {\n *            return false;\n *        }\n *        return $this->handler->handle($record);\n *   }\n *\n * @author Alexey Karapetov <alexey@karapetov.com>\n */\nclass HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface\n{\n    protected HandlerInterface $handler;\n\n    public function __construct(HandlerInterface $handler)\n    {\n        $this->handler = $handler;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function isHandling(LogRecord $record): bool\n    {\n        return $this->handler->isHandling($record);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        return $this->handler->handle($record);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        $this->handler->handleBatch($records);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function close(): void\n    {\n        $this->handler->close();\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function pushProcessor(callable $callback): HandlerInterface\n    {\n        if ($this->handler instanceof ProcessableHandlerInterface) {\n            $this->handler->pushProcessor($callback);\n\n            return $this;\n        }\n\n        throw new \\LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function popProcessor(): callable\n    {\n        if ($this->handler instanceof ProcessableHandlerInterface) {\n            return $this->handler->popProcessor();\n        }\n\n        throw new \\LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        if ($this->handler instanceof FormattableHandlerInterface) {\n            $this->handler->setFormatter($formatter);\n\n            return $this;\n        }\n\n        throw new \\LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getFormatter(): FormatterInterface\n    {\n        if ($this->handler instanceof FormattableHandlerInterface) {\n            return $this->handler->getFormatter();\n        }\n\n        throw new \\LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class);\n    }\n\n    public function reset(): void\n    {\n        if ($this->handler instanceof ResettableInterface) {\n            $this->handler->reset();\n        }\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/IFTTTHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * IFTTTHandler uses cURL to trigger IFTTT Maker actions\n *\n * Register a secret key and trigger/event name at https://ifttt.com/maker\n *\n * value1 will be the channel from monolog's Logger constructor,\n * value2 will be the level name (ERROR, WARNING, ..)\n * value3 will be the log record's message\n *\n * @author Nehal Patel <nehal@nehalpatel.me>\n */\nclass IFTTTHandler extends AbstractProcessingHandler\n{\n    private string $eventName;\n    private string $secretKey;\n\n    /**\n     * @param string $eventName The name of the IFTTT Maker event that should be triggered\n     * @param string $secretKey A valid IFTTT secret key\n     *\n     * @throws MissingExtensionException If the curl extension is missing\n     */\n    public function __construct(string $eventName, string $secretKey, int|string|Level $level = Level::Error, bool $bubble = true)\n    {\n        if (!\\extension_loaded('curl')) {\n            throw new MissingExtensionException('The curl extension is needed to use the IFTTTHandler');\n        }\n\n        $this->eventName = $eventName;\n        $this->secretKey = $secretKey;\n\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function write(LogRecord $record): void\n    {\n        $postData = [\n            \"value1\" => $record->channel,\n            \"value2\" => $record[\"level_name\"],\n            \"value3\" => $record->message,\n        ];\n        $postString = Utils::jsonEncode($postData);\n\n        $ch = curl_init();\n        curl_setopt($ch, CURLOPT_URL, \"https://maker.ifttt.com/trigger/\" . $this->eventName . \"/with/key/\" . $this->secretKey);\n        curl_setopt($ch, CURLOPT_POST, true);\n        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n        curl_setopt($ch, CURLOPT_POSTFIELDS, $postString);\n        curl_setopt($ch, CURLOPT_HTTPHEADER, [\n            \"Content-Type: application/json\",\n        ]);\n\n        Curl\\Util::execute($ch);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/InsightOpsHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * Inspired on LogEntriesHandler.\n *\n * @author Robert Kaufmann III <rok3@rok3.me>\n * @author Gabriel Machado <gabriel.ms1@hotmail.com>\n */\nclass InsightOpsHandler extends SocketHandler\n{\n    protected string $logToken;\n\n    /**\n     * @param string $token  Log token supplied by InsightOps\n     * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'.\n     * @param bool   $useSSL Whether or not SSL encryption should be used\n     *\n     * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing\n     */\n    public function __construct(\n        string $token,\n        string $region = 'us',\n        bool $useSSL = true,\n        $level = Level::Debug,\n        bool $bubble = true,\n        bool $persistent = false,\n        float $timeout = 0.0,\n        float $writingTimeout = 10.0,\n        ?float $connectionTimeout = null,\n        ?int $chunkSize = null\n    ) {\n        if ($useSSL && !\\extension_loaded('openssl')) {\n            throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for InsightOpsHandler');\n        }\n\n        $endpoint = $useSSL\n            ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443'\n            : $region . '.data.logs.insight.rapid7.com:80';\n\n        parent::__construct(\n            $endpoint,\n            $level,\n            $bubble,\n            $persistent,\n            $timeout,\n            $writingTimeout,\n            $connectionTimeout,\n            $chunkSize\n        );\n        $this->logToken = $token;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function generateDataStream(LogRecord $record): string\n    {\n        return $this->logToken . ' ' . $record->formatted;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/LogEntriesHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * @author Robert Kaufmann III <rok3@rok3.me>\n */\nclass LogEntriesHandler extends SocketHandler\n{\n    protected string $logToken;\n\n    /**\n     * @param string $token  Log token supplied by LogEntries\n     * @param bool   $useSSL Whether or not SSL encryption should be used.\n     * @param string $host   Custom hostname to send the data to if needed\n     *\n     * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing\n     */\n    public function __construct(\n        string $token,\n        bool $useSSL = true,\n        $level = Level::Debug,\n        bool $bubble = true,\n        string $host = 'data.logentries.com',\n        bool $persistent = false,\n        float $timeout = 0.0,\n        float $writingTimeout = 10.0,\n        ?float $connectionTimeout = null,\n        ?int $chunkSize = null\n    ) {\n        if ($useSSL && !\\extension_loaded('openssl')) {\n            throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');\n        }\n\n        $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80';\n        parent::__construct(\n            $endpoint,\n            $level,\n            $bubble,\n            $persistent,\n            $timeout,\n            $writingTimeout,\n            $connectionTimeout,\n            $chunkSize\n        );\n        $this->logToken = $token;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function generateDataStream(LogRecord $record): string\n    {\n        return $this->logToken . ' ' . $record->formatted;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/LogglyHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\LogglyFormatter;\nuse CurlHandle;\nuse Monolog\\LogRecord;\n\n/**\n * Sends errors to Loggly.\n *\n * @author Przemek Sobstel <przemek@sobstel.org>\n * @author Adam Pancutt <adam@pancutt.com>\n * @author Gregory Barchard <gregory@barchard.net>\n */\nclass LogglyHandler extends AbstractProcessingHandler\n{\n    protected const HOST = 'logs-01.loggly.com';\n    protected const ENDPOINT_SINGLE = 'inputs';\n    protected const ENDPOINT_BATCH = 'bulk';\n\n    /**\n     * Caches the curl handlers for every given endpoint.\n     *\n     * @var CurlHandle[]\n     */\n    protected array $curlHandlers = [];\n\n    protected string $token;\n\n    /** @var string[] */\n    protected array $tag = [];\n\n    /**\n     * @param string $token API token supplied by Loggly\n     *\n     * @throws MissingExtensionException If the curl extension is missing\n     */\n    public function __construct(string $token, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        if (!\\extension_loaded('curl')) {\n            throw new MissingExtensionException('The curl extension is needed to use the LogglyHandler');\n        }\n\n        $this->token = $token;\n\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * Loads and returns the shared curl handler for the given endpoint.\n     */\n    protected function getCurlHandler(string $endpoint): CurlHandle\n    {\n        if (!\\array_key_exists($endpoint, $this->curlHandlers)) {\n            $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint);\n        }\n\n        return $this->curlHandlers[$endpoint];\n    }\n\n    /**\n     * Starts a fresh curl session for the given endpoint and returns its handler.\n     */\n    private function loadCurlHandle(string $endpoint): CurlHandle\n    {\n        $url = sprintf(\"https://%s/%s/%s/\", static::HOST, $endpoint, $this->token);\n\n        $ch = curl_init();\n\n        curl_setopt($ch, CURLOPT_URL, $url);\n        curl_setopt($ch, CURLOPT_POST, true);\n        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n\n        return $ch;\n    }\n\n    /**\n     * @param  string[]|string $tag\n     * @return $this\n     */\n    public function setTag(string|array $tag): self\n    {\n        if ('' === $tag || [] === $tag) {\n            $this->tag = [];\n        } else {\n            $this->tag = \\is_array($tag) ? $tag : [$tag];\n        }\n\n        return $this;\n    }\n\n    /**\n     * @param  string[]|string $tag\n     * @return $this\n     */\n    public function addTag(string|array $tag): self\n    {\n        if ('' !== $tag) {\n            $tag = \\is_array($tag) ? $tag : [$tag];\n            $this->tag = array_unique(array_merge($this->tag, $tag));\n        }\n\n        return $this;\n    }\n\n    protected function write(LogRecord $record): void\n    {\n        $this->send($record->formatted, static::ENDPOINT_SINGLE);\n    }\n\n    public function handleBatch(array $records): void\n    {\n        $level = $this->level;\n\n        $records = array_filter($records, function ($record) use ($level) {\n            return ($record->level->value >= $level->value);\n        });\n\n        if (\\count($records) > 0) {\n            $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH);\n        }\n    }\n\n    protected function send(string $data, string $endpoint): void\n    {\n        $ch = $this->getCurlHandler($endpoint);\n\n        $headers = ['Content-Type: application/json'];\n\n        if (\\count($this->tag) > 0) {\n            $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag);\n        }\n\n        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);\n        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);\n\n        Curl\\Util::execute($ch, 5);\n    }\n\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new LogglyFormatter();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/LogmaticHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\LogmaticFormatter;\nuse Monolog\\LogRecord;\n\n/**\n * @author Julien Breux <julien.breux@gmail.com>\n */\nclass LogmaticHandler extends SocketHandler\n{\n    private string $logToken;\n\n    private string $hostname;\n\n    private string $appName;\n\n    /**\n     * @param string $token    Log token supplied by Logmatic.\n     * @param string $hostname Host name supplied by Logmatic.\n     * @param string $appName  Application name supplied by Logmatic.\n     * @param bool   $useSSL   Whether or not SSL encryption should be used.\n     *\n     * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing\n     */\n    public function __construct(\n        string $token,\n        string $hostname = '',\n        string $appName = '',\n        bool $useSSL = true,\n        $level = Level::Debug,\n        bool $bubble = true,\n        bool $persistent = false,\n        float $timeout = 0.0,\n        float $writingTimeout = 10.0,\n        ?float $connectionTimeout = null,\n        ?int $chunkSize = null\n    ) {\n        if ($useSSL && !\\extension_loaded('openssl')) {\n            throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler');\n        }\n\n        $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514';\n        $endpoint .= '/v1/';\n\n        parent::__construct(\n            $endpoint,\n            $level,\n            $bubble,\n            $persistent,\n            $timeout,\n            $writingTimeout,\n            $connectionTimeout,\n            $chunkSize\n        );\n\n        $this->logToken = $token;\n        $this->hostname = $hostname;\n        $this->appName  = $appName;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function generateDataStream(LogRecord $record): string\n    {\n        return $this->logToken . ' ' . $record->formatted;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        $formatter = new LogmaticFormatter();\n\n        if ($this->hostname !== '') {\n            $formatter->setHostname($this->hostname);\n        }\n        if ($this->appName !== '') {\n            $formatter->setAppName($this->appName);\n        }\n\n        return $formatter;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/MailHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\HtmlFormatter;\nuse Monolog\\LogRecord;\n\n/**\n * Base class for all mail handlers\n *\n * @author Gyula Sallai\n */\nabstract class MailHandler extends AbstractProcessingHandler\n{\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        $messages = [];\n\n        foreach ($records as $record) {\n            if ($record->level->isLowerThan($this->level)) {\n                continue;\n            }\n\n            $message = $this->processRecord($record);\n            $messages[] = $message;\n        }\n\n        if (\\count($messages) > 0) {\n            $this->send((string) $this->getFormatter()->formatBatch($messages), $messages);\n        }\n    }\n\n    /**\n     * Send a mail with the given content\n     *\n     * @param string $content formatted email body to be sent\n     * @param array  $records the array of log records that formed this content\n     *\n     * @phpstan-param non-empty-array<LogRecord> $records\n     */\n    abstract protected function send(string $content, array $records): void;\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->send((string) $record->formatted, [$record]);\n    }\n\n    /**\n     * @phpstan-param non-empty-array<LogRecord> $records\n     */\n    protected function getHighestRecord(array $records): LogRecord\n    {\n        $highestRecord = null;\n        foreach ($records as $record) {\n            if ($highestRecord === null || $record->level->isHigherThan($highestRecord->level)) {\n                $highestRecord = $record;\n            }\n        }\n\n        return $highestRecord;\n    }\n\n    protected function isHtmlBody(string $body): bool\n    {\n        return ($body[0] ?? null) === '<';\n    }\n\n    /**\n     * Gets the default formatter.\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new HtmlFormatter();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/MandrillHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Swift;\nuse Swift_Message;\n\n/**\n * MandrillHandler uses cURL to send the emails to the Mandrill API\n *\n * @author Adam Nicholson <adamnicholson10@gmail.com>\n */\nclass MandrillHandler extends MailHandler\n{\n    protected Swift_Message $message;\n    protected string $apiKey;\n\n    /**\n     * @phpstan-param (Swift_Message|callable(): Swift_Message) $message\n     *\n     * @param string                 $apiKey  A valid Mandrill API key\n     * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced\n     *\n     * @throws \\InvalidArgumentException if not a Swift Message is set\n     */\n    public function __construct(string $apiKey, callable|Swift_Message $message, int|string|Level $level = Level::Error, bool $bubble = true)\n    {\n        parent::__construct($level, $bubble);\n\n        if (!$message instanceof Swift_Message) {\n            $message = $message();\n        }\n        if (!$message instanceof Swift_Message) {\n            throw new \\InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it');\n        }\n        $this->message = $message;\n        $this->apiKey = $apiKey;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function send(string $content, array $records): void\n    {\n        $mime = 'text/plain';\n        if ($this->isHtmlBody($content)) {\n            $mime = 'text/html';\n        }\n\n        $message = clone $this->message;\n        $message->setBody($content, $mime);\n        /** @phpstan-ignore-next-line */\n        if (version_compare(Swift::VERSION, '6.0.0', '>=')) {\n            $message->setDate(new \\DateTimeImmutable());\n        } else {\n            /** @phpstan-ignore-next-line */\n            $message->setDate(time());\n        }\n\n        $ch = curl_init();\n\n        curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json');\n        curl_setopt($ch, CURLOPT_POST, true);\n        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([\n            'key' => $this->apiKey,\n            'raw_message' => (string) $message,\n            'async' => false,\n        ]));\n\n        Curl\\Util::execute($ch);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/MissingExtensionException.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\n/**\n * Exception can be thrown if an extension for a handler is missing\n *\n * @author Christian Bergau <cbergau86@gmail.com>\n */\nclass MissingExtensionException extends \\Exception\n{\n}\n"
  },
  {
    "path": "src/Monolog/Handler/MongoDBHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse MongoDB\\Client;\nuse MongoDB\\Collection;\nuse MongoDB\\Driver\\BulkWrite;\nuse MongoDB\\Driver\\Manager;\nuse Monolog\\Level;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\MongoDBFormatter;\nuse Monolog\\LogRecord;\n\n/**\n * Logs to a MongoDB database.\n *\n * Usage example:\n *\n *   $log = new \\Monolog\\Logger('application');\n *   $client = new \\MongoDB\\Client('mongodb://localhost:27017');\n *   $mongodb = new \\Monolog\\Handler\\MongoDBHandler($client, 'logs', 'prod');\n *   $log->pushHandler($mongodb);\n *\n * The above examples uses the MongoDB PHP library's client class; however, the\n * MongoDB\\Driver\\Manager class from ext-mongodb is also supported.\n */\nclass MongoDBHandler extends AbstractProcessingHandler\n{\n    private Collection $collection;\n\n    private Client|Manager $manager;\n\n    private string|null $namespace = null;\n\n    /**\n     * Constructor.\n     *\n     * @param Client|Manager $mongodb    MongoDB library or driver client\n     * @param string         $database   Database name\n     * @param string         $collection Collection name\n     */\n    public function __construct(Client|Manager $mongodb, string $database, string $collection, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        if ($mongodb instanceof Client) {\n            $this->collection = method_exists($mongodb, 'getCollection') ? $mongodb->getCollection($database, $collection) : $mongodb->selectCollection($database, $collection);\n        } else {\n            $this->manager = $mongodb;\n            $this->namespace = $database . '.' . $collection;\n        }\n\n        parent::__construct($level, $bubble);\n    }\n\n    protected function write(LogRecord $record): void\n    {\n        if (isset($this->collection)) {\n            $this->collection->insertOne($record->formatted);\n        }\n\n        if (isset($this->manager, $this->namespace)) {\n            $bulk = new BulkWrite;\n            $bulk->insert($record->formatted);\n            $this->manager->executeBulkWrite($this->namespace, $bulk);\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new MongoDBFormatter;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/NativeMailerHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\LineFormatter;\n\n/**\n * NativeMailerHandler uses the mail() function to send the emails\n *\n * @author Christophe Coevoet <stof@notk.org>\n * @author Mark Garrett <mark@moderndeveloperllc.com>\n */\nclass NativeMailerHandler extends MailHandler\n{\n    /**\n     * The email addresses to which the message will be sent\n     * @var string[]\n     */\n    protected array $to;\n\n    /**\n     * The subject of the email\n     */\n    protected string $subject;\n\n    /**\n     * Optional headers for the message\n     * @var string[]\n     */\n    protected array $headers = [];\n\n    /**\n     * Optional parameters for the message\n     * @var string[]\n     */\n    protected array $parameters = [];\n\n    /**\n     * The wordwrap length for the message\n     */\n    protected int $maxColumnWidth;\n\n    /**\n     * The Content-type for the message\n     */\n    protected string|null $contentType = null;\n\n    /**\n     * The encoding for the message\n     */\n    protected string $encoding = 'utf-8';\n\n    /**\n     * @param string|string[] $to             The receiver of the mail\n     * @param string          $subject        The subject of the mail\n     * @param string          $from           The sender of the mail\n     * @param int             $maxColumnWidth The maximum column width that the message lines will have\n     */\n    public function __construct(string|array $to, string $subject, string $from, int|string|Level $level = Level::Error, bool $bubble = true, int $maxColumnWidth = 70)\n    {\n        parent::__construct($level, $bubble);\n        $this->to = (array) $to;\n        $this->subject = $subject;\n        $this->addHeader(sprintf('From: %s', $from));\n        $this->maxColumnWidth = $maxColumnWidth;\n    }\n\n    /**\n     * Add headers to the message\n     *\n     * @param  string|string[] $headers Custom added headers\n     * @return $this\n     */\n    public function addHeader($headers): self\n    {\n        foreach ((array) $headers as $header) {\n            if (strpos($header, \"\\n\") !== false || strpos($header, \"\\r\") !== false) {\n                throw new \\InvalidArgumentException('Headers can not contain newline characters for security reasons');\n            }\n            $this->headers[] = $header;\n        }\n\n        return $this;\n    }\n\n    /**\n     * Add parameters to the message\n     *\n     * @param  string|string[] $parameters Custom added parameters\n     * @return $this\n     */\n    public function addParameter($parameters): self\n    {\n        $this->parameters = array_merge($this->parameters, (array) $parameters);\n\n        return $this;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function send(string $content, array $records): void\n    {\n        $contentType = $this->getContentType() ?? ($this->isHtmlBody($content) ? 'text/html' : 'text/plain');\n\n        if ($contentType !== 'text/html') {\n            $content = wordwrap($content, $this->maxColumnWidth);\n        }\n\n        $headers = ltrim(implode(\"\\r\\n\", $this->headers) . \"\\r\\n\", \"\\r\\n\");\n        $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . \"\\r\\n\";\n        if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) {\n            $headers .= 'MIME-Version: 1.0' . \"\\r\\n\";\n        }\n\n        $subjectFormatter = new LineFormatter($this->subject);\n        $subject = $subjectFormatter->format($this->getHighestRecord($records));\n\n        $parameters = implode(' ', $this->parameters);\n        foreach ($this->to as $to) {\n            $this->mail($to, $subject, $content, $headers, $parameters);\n        }\n    }\n\n    public function getContentType(): ?string\n    {\n        return $this->contentType;\n    }\n\n    public function getEncoding(): string\n    {\n        return $this->encoding;\n    }\n\n    /**\n     * @param  string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages.\n     * @return $this\n     */\n    public function setContentType(string $contentType): self\n    {\n        if (strpos($contentType, \"\\n\") !== false || strpos($contentType, \"\\r\") !== false) {\n            throw new \\InvalidArgumentException('The content type can not contain newline characters to prevent email header injection');\n        }\n\n        $this->contentType = $contentType;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function setEncoding(string $encoding): self\n    {\n        if (strpos($encoding, \"\\n\") !== false || strpos($encoding, \"\\r\") !== false) {\n            throw new \\InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection');\n        }\n\n        $this->encoding = $encoding;\n\n        return $this;\n    }\n\n\n    protected function mail(string $to, string $subject, string $content, string $headers, string $parameters): void\n    {\n        mail($to, $subject, $content, $headers, $parameters);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/NewRelicHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\Formatter\\NormalizerFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Class to record a log on a NewRelic application.\n * Enabling New Relic High Security mode may prevent capture of useful information.\n *\n * This handler requires a NormalizerFormatter to function and expects an array in $record->formatted\n *\n * @see https://docs.newrelic.com/docs/agents/php-agent\n * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security\n */\nclass NewRelicHandler extends AbstractProcessingHandler\n{\n    /**\n     * @inheritDoc\n     */\n    public function __construct(\n        int|string|Level $level = Level::Error,\n        bool $bubble = true,\n\n        /**\n         * Name of the New Relic application that will receive logs from this handler.\n         */\n        protected string|null $appName = null,\n\n        /**\n         * Some context and extra data is passed into the handler as arrays of values. Do we send them as is\n         * (useful if we are using the API), or explode them for display on the NewRelic RPM website?\n         */\n        protected bool $explodeArrays = false,\n\n        /**\n         * Name of the current transaction\n         */\n        protected string|null $transactionName = null\n    ) {\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        if (!$this->isNewRelicEnabled()) {\n            throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler');\n        }\n\n        if (null !== ($appName = $this->getAppName($record->context))) {\n            $this->setNewRelicAppName($appName);\n        }\n\n        if (null !== ($transactionName = $this->getTransactionName($record->context))) {\n            $this->setNewRelicTransactionName($transactionName);\n            unset($record->formatted['context']['transaction_name']);\n        }\n\n        if (isset($record->context['exception']) && $record->context['exception'] instanceof \\Throwable) {\n            newrelic_notice_error($record->message, $record->context['exception']);\n            unset($record->formatted['context']['exception']);\n        } else {\n            newrelic_notice_error($record->message);\n        }\n\n        if (isset($record->formatted['context']) && \\is_array($record->formatted['context'])) {\n            foreach ($record->formatted['context'] as $key => $parameter) {\n                if (\\is_array($parameter) && $this->explodeArrays) {\n                    foreach ($parameter as $paramKey => $paramValue) {\n                        $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue);\n                    }\n                } else {\n                    $this->setNewRelicParameter('context_' . $key, $parameter);\n                }\n            }\n        }\n\n        if (isset($record->formatted['extra']) && \\is_array($record->formatted['extra'])) {\n            foreach ($record->formatted['extra'] as $key => $parameter) {\n                if (\\is_array($parameter) && $this->explodeArrays) {\n                    foreach ($parameter as $paramKey => $paramValue) {\n                        $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue);\n                    }\n                } else {\n                    $this->setNewRelicParameter('extra_' . $key, $parameter);\n                }\n            }\n        }\n    }\n\n    /**\n     * Checks whether the NewRelic extension is enabled in the system.\n     */\n    protected function isNewRelicEnabled(): bool\n    {\n        return \\extension_loaded('newrelic');\n    }\n\n    /**\n     * Returns the appname where this log should be sent. Each log can override the default appname, set in this\n     * handler's constructor, by providing the appname in it's context.\n     *\n     * @param mixed[] $context\n     */\n    protected function getAppName(array $context): ?string\n    {\n        if (isset($context['appname'])) {\n            return $context['appname'];\n        }\n\n        return $this->appName;\n    }\n\n    /**\n     * Returns the name of the current transaction. Each log can override the default transaction name, set in this\n     * handler's constructor, by providing the transaction_name in it's context\n     *\n     * @param mixed[] $context\n     */\n    protected function getTransactionName(array $context): ?string\n    {\n        if (isset($context['transaction_name'])) {\n            return $context['transaction_name'];\n        }\n\n        return $this->transactionName;\n    }\n\n    /**\n     * Sets the NewRelic application that should receive this log.\n     */\n    protected function setNewRelicAppName(string $appName): void\n    {\n        newrelic_set_appname($appName);\n    }\n\n    /**\n     * Overwrites the name of the current transaction\n     */\n    protected function setNewRelicTransactionName(string $transactionName): void\n    {\n        newrelic_name_transaction($transactionName);\n    }\n\n    /**\n     * @param mixed $value\n     */\n    protected function setNewRelicParameter(string $key, $value): void\n    {\n        if (null === $value || \\is_scalar($value)) {\n            newrelic_add_custom_parameter($key, $value);\n        } else {\n            newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true));\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new NormalizerFormatter();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/NoopHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\LogRecord;\n\n/**\n * No-op\n *\n * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack.\n * This can be used for testing, or to disable a handler when overriding a configuration without\n * influencing the rest of the stack.\n *\n * @author Roel Harbers <roelharbers@gmail.com>\n */\nclass NoopHandler extends Handler\n{\n    /**\n     * @inheritDoc\n     */\n    public function isHandling(LogRecord $record): bool\n    {\n        return true;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/NullHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\Logger;\nuse Monolog\\LogRecord;\n\n/**\n * Blackhole\n *\n * Any record it can handle will be thrown away. This can be used\n * to put on top of an existing stack to override it temporarily.\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass NullHandler extends Handler\n{\n    private Level $level;\n\n    /**\n     * @param string|int|Level $level The minimum logging level at which this handler will be triggered\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public function __construct(string|int|Level $level = Level::Debug)\n    {\n        $this->level = Logger::toMonologLevel($level);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function isHandling(LogRecord $record): bool\n    {\n        return $record->level->value >= $this->level->value;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        return $record->level->value >= $this->level->value;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/OverflowHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Handler to only pass log messages when a certain threshold of number of messages is reached.\n *\n * This can be useful in cases of processing a batch of data, but you're for example only interested\n * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right?\n *\n * Usage example:\n *\n * ```\n *   $log = new Logger('application');\n *   $handler = new SomeHandler(...)\n *\n *   // Pass all warnings to the handler when more than 10 & all error messages when more then 5\n *   $overflow = new OverflowHandler($handler, [Level::Warning->value => 10, Level::Error->value => 5]);\n *\n *   $log->pushHandler($overflow);\n *```\n *\n * @author Kris Buist <krisbuist@gmail.com>\n */\nclass OverflowHandler extends AbstractHandler implements FormattableHandlerInterface\n{\n    private HandlerInterface $handler;\n\n    /** @var array<int, int> */\n    private array $thresholdMap = [];\n\n    /**\n     * Buffer of all messages passed to the handler before the threshold was reached\n     *\n     * @var mixed[][]\n     */\n    private array $buffer = [];\n\n    /**\n     * @param array<int, int> $thresholdMap Dictionary of log level value => threshold\n     */\n    public function __construct(\n        HandlerInterface $handler,\n        array $thresholdMap = [],\n        $level = Level::Debug,\n        bool $bubble = true\n    ) {\n        $this->handler = $handler;\n        foreach ($thresholdMap as $thresholdLevel => $threshold) {\n            $this->thresholdMap[$thresholdLevel] = $threshold;\n        }\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * Handles a record.\n     *\n     * All records may be passed to this method, and the handler should discard\n     * those that it does not want to handle.\n     *\n     * The return value of this function controls the bubbling process of the handler stack.\n     * Unless the bubbling is interrupted (by returning true), the Logger class will keep on\n     * calling further handlers in the stack with a given log record.\n     *\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        if ($record->level->isLowerThan($this->level)) {\n            return false;\n        }\n\n        $level = $record->level->value;\n\n        if (!isset($this->thresholdMap[$level])) {\n            $this->thresholdMap[$level] = 0;\n        }\n\n        if ($this->thresholdMap[$level] > 0) {\n            // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1\n            $this->thresholdMap[$level]--;\n            $this->buffer[$level][] = $record;\n\n            return false === $this->bubble;\n        }\n\n        if ($this->thresholdMap[$level] === 0) {\n            // This current message is breaking the threshold. Flush the buffer and continue handling the current record\n            foreach ($this->buffer[$level] ?? [] as $buffered) {\n                $this->handler->handle($buffered);\n            }\n            $this->thresholdMap[$level]--;\n            unset($this->buffer[$level]);\n        }\n\n        $this->handler->handle($record);\n\n        return false === $this->bubble;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        if ($this->handler instanceof FormattableHandlerInterface) {\n            $this->handler->setFormatter($formatter);\n\n            return $this;\n        }\n\n        throw new \\UnexpectedValueException('The nested handler of type '.\\get_class($this->handler).' does not support formatters.');\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getFormatter(): FormatterInterface\n    {\n        if ($this->handler instanceof FormattableHandlerInterface) {\n            return $this->handler->getFormatter();\n        }\n\n        throw new \\UnexpectedValueException('The nested handler of type '.\\get_class($this->handler).' does not support formatters.');\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/PHPConsoleHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse PhpConsole\\Connector;\nuse PhpConsole\\Handler as VendorPhpConsoleHandler;\nuse PhpConsole\\Helper;\nuse Monolog\\LogRecord;\nuse PhpConsole\\Storage;\n\n/**\n * Monolog handler for Google Chrome extension \"PHP Console\"\n *\n * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely\n *\n * Usage:\n * 1. Install Google Chrome extension [now dead and removed from the chrome store]\n * 2. See overview https://github.com/barbushin/php-console#overview\n * 3. Install PHP Console library https://github.com/barbushin/php-console#installation\n * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png)\n *\n *      $logger = new \\Monolog\\Logger('all', array(new \\Monolog\\Handler\\PHPConsoleHandler()));\n *      \\Monolog\\ErrorHandler::register($logger);\n *      echo $undefinedVar;\n *      $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012));\n *      PC::debug($_SERVER); // PHP Console debugger for any type of vars\n *\n * @author Sergey Barbushin https://www.linkedin.com/in/barbushin\n * @phpstan-type Options array{\n *     enabled: bool,\n *     classesPartialsTraceIgnore: string[],\n *     debugTagsKeysInContext: array<int|string>,\n *     useOwnErrorsHandler: bool,\n *     useOwnExceptionsHandler: bool,\n *     sourcesBasePath: string|null,\n *     registerHelper: bool,\n *     serverEncoding: string|null,\n *     headersLimit: int|null,\n *     password: string|null,\n *     enableSslOnlyMode: bool,\n *     ipMasks: string[],\n *     enableEvalListener: bool,\n *     dumperDetectCallbacks: bool,\n *     dumperLevelLimit: int,\n *     dumperItemsCountLimit: int,\n *     dumperItemSizeLimit: int,\n *     dumperDumpSizeLimit: int,\n *     detectDumpTraceAndSource: bool,\n *     dataStorage: Storage|null\n * }\n * @phpstan-type InputOptions array{\n *     enabled?: bool,\n *     classesPartialsTraceIgnore?: string[],\n *     debugTagsKeysInContext?: array<int|string>,\n *     useOwnErrorsHandler?: bool,\n *     useOwnExceptionsHandler?: bool,\n *     sourcesBasePath?: string|null,\n *     registerHelper?: bool,\n *     serverEncoding?: string|null,\n *     headersLimit?: int|null,\n *     password?: string|null,\n *     enableSslOnlyMode?: bool,\n *     ipMasks?: string[],\n *     enableEvalListener?: bool,\n *     dumperDetectCallbacks?: bool,\n *     dumperLevelLimit?: int,\n *     dumperItemsCountLimit?: int,\n *     dumperItemSizeLimit?: int,\n *     dumperDumpSizeLimit?: int,\n *     detectDumpTraceAndSource?: bool,\n *     dataStorage?: Storage|null\n * }\n *\n * @deprecated Since 2.8.0 and 3.2.0, PHPConsole is abandoned and thus we will drop this handler in Monolog 4\n */\nclass PHPConsoleHandler extends AbstractProcessingHandler\n{\n    /**\n     * @phpstan-var Options\n     */\n    private array $options = [\n        'enabled' => true, // bool Is PHP Console server enabled\n        'classesPartialsTraceIgnore' => ['Monolog\\\\'], // array Hide calls of classes started with...\n        'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled\n        'useOwnErrorsHandler' => false, // bool Enable errors handling\n        'useOwnExceptionsHandler' => false, // bool Enable exceptions handling\n        'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths\n        'registerHelper' => true, // bool Register PhpConsole\\Helper that allows short debug calls like PC::debug($var, 'ta.g.s')\n        'serverEncoding' => null, // string|null Server internal encoding\n        'headersLimit' => null, // int|null Set headers size limit for your web-server\n        'password' => null, // string|null Protect PHP Console connection by password\n        'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed\n        'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1')\n        'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required)\n        'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings\n        'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level\n        'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number\n        'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item\n        'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON\n        'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug\n        'dataStorage' => null, // \\PhpConsole\\Storage|null Fixes problem with custom $_SESSION handler (see https://github.com/barbushin/php-console#troubleshooting-with-_session-handler-overridden-in-some-frameworks)\n    ];\n\n    private Connector $connector;\n\n    /**\n     * @param  array<string, mixed> $options   See \\Monolog\\Handler\\PHPConsoleHandler::$options for more details\n     * @param  Connector|null       $connector Instance of \\PhpConsole\\Connector class (optional)\n     * @throws \\RuntimeException\n     * @phpstan-param InputOptions $options\n     */\n    public function __construct(array $options = [], ?Connector $connector = null, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        if (!class_exists('PhpConsole\\Connector')) {\n            throw new \\RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation');\n        }\n        parent::__construct($level, $bubble);\n        $this->options = $this->initOptions($options);\n        $this->connector = $this->initConnector($connector);\n    }\n\n    /**\n     * @param  array<string, mixed> $options\n     * @return array<string, mixed>\n     *\n     * @phpstan-param InputOptions $options\n     * @phpstan-return Options\n     */\n    private function initOptions(array $options): array\n    {\n        $wrongOptions = array_diff(array_keys($options), array_keys($this->options));\n        if (\\count($wrongOptions) > 0) {\n            throw new \\RuntimeException('Unknown options: ' . implode(', ', $wrongOptions));\n        }\n\n        return array_replace($this->options, $options);\n    }\n\n    private function initConnector(?Connector $connector = null): Connector\n    {\n        if (null === $connector) {\n            if ($this->options['dataStorage'] instanceof Storage) {\n                Connector::setPostponeStorage($this->options['dataStorage']);\n            }\n            $connector = Connector::getInstance();\n        }\n\n        if ($this->options['registerHelper'] && !Helper::isRegistered()) {\n            Helper::register();\n        }\n\n        if ($this->options['enabled'] && $connector->isActiveClient()) {\n            if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) {\n                $handler = VendorPhpConsoleHandler::getInstance();\n                $handler->setHandleErrors($this->options['useOwnErrorsHandler']);\n                $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']);\n                $handler->start();\n            }\n            if (null !== $this->options['sourcesBasePath']) {\n                $connector->setSourcesBasePath($this->options['sourcesBasePath']);\n            }\n            if (null !== $this->options['serverEncoding']) {\n                $connector->setServerEncoding($this->options['serverEncoding']);\n            }\n            if (null !== $this->options['password']) {\n                $connector->setPassword($this->options['password']);\n            }\n            if ($this->options['enableSslOnlyMode']) {\n                $connector->enableSslOnlyMode();\n            }\n            if (\\count($this->options['ipMasks']) > 0) {\n                $connector->setAllowedIpMasks($this->options['ipMasks']);\n            }\n            if (null !== $this->options['headersLimit'] && $this->options['headersLimit'] > 0) {\n                $connector->setHeadersLimit($this->options['headersLimit']);\n            }\n            if ($this->options['detectDumpTraceAndSource']) {\n                $connector->getDebugDispatcher()->detectTraceAndSource = true;\n            }\n            $dumper = $connector->getDumper();\n            $dumper->levelLimit = $this->options['dumperLevelLimit'];\n            $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit'];\n            $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit'];\n            $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit'];\n            $dumper->detectCallbacks = $this->options['dumperDetectCallbacks'];\n            if ($this->options['enableEvalListener']) {\n                $connector->startEvalRequestsListener();\n            }\n        }\n\n        return $connector;\n    }\n\n    public function getConnector(): Connector\n    {\n        return $this->connector;\n    }\n\n    /**\n     * @return array<string, mixed>\n     */\n    public function getOptions(): array\n    {\n        return $this->options;\n    }\n\n    public function handle(LogRecord $record): bool\n    {\n        if ($this->options['enabled'] && $this->connector->isActiveClient()) {\n            return parent::handle($record);\n        }\n\n        return !$this->bubble;\n    }\n\n    /**\n     * Writes the record down to the log of the implementing handler\n     */\n    protected function write(LogRecord $record): void\n    {\n        if ($record->level->isLowerThan(Level::Notice)) {\n            $this->handleDebugRecord($record);\n        } elseif (isset($record->context['exception']) && $record->context['exception'] instanceof \\Throwable) {\n            $this->handleExceptionRecord($record);\n        } else {\n            $this->handleErrorRecord($record);\n        }\n    }\n\n    private function handleDebugRecord(LogRecord $record): void\n    {\n        [$tags, $filteredContext] = $this->getRecordTags($record);\n        $message = $record->message;\n        if (\\count($filteredContext) > 0) {\n            $message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(array_filter($filteredContext)), null, true);\n        }\n        $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']);\n    }\n\n    private function handleExceptionRecord(LogRecord $record): void\n    {\n        $this->connector->getErrorsDispatcher()->dispatchException($record->context['exception']);\n    }\n\n    private function handleErrorRecord(LogRecord $record): void\n    {\n        $context = $record->context;\n\n        $this->connector->getErrorsDispatcher()->dispatchError(\n            $context['code'] ?? null,\n            $context['message'] ?? $record->message,\n            $context['file'] ?? null,\n            $context['line'] ?? null,\n            $this->options['classesPartialsTraceIgnore']\n        );\n    }\n\n    /**\n     * @return array{string, mixed[]}\n     */\n    private function getRecordTags(LogRecord $record): array\n    {\n        $tags = null;\n        $filteredContext = [];\n        if ($record->context !== []) {\n            $filteredContext = $record->context;\n            foreach ($this->options['debugTagsKeysInContext'] as $key) {\n                if (isset($filteredContext[$key])) {\n                    $tags = $filteredContext[$key];\n                    if ($key === 0) {\n                        array_shift($filteredContext);\n                    } else {\n                        unset($filteredContext[$key]);\n                    }\n                    break;\n                }\n            }\n        }\n\n        return [$tags ?? $record->level->toPsrLogLevel(), $filteredContext];\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new LineFormatter('%message%');\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/ProcessHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * Stores to STDIN of any process, specified by a command.\n *\n * Usage example:\n * <pre>\n * $log = new Logger('myLogger');\n * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));\n * </pre>\n *\n * @author Kolja Zuelsdorf <koljaz@web.de>\n */\nclass ProcessHandler extends AbstractProcessingHandler\n{\n    /**\n     * Holds the process to receive data on its STDIN.\n     *\n     * @var resource|bool|null\n     */\n    private $process;\n\n    private string $command;\n\n    private ?string $cwd;\n\n    /**\n     * @var resource[]\n     */\n    private array $pipes = [];\n\n    private float $timeout;\n\n    /**\n     * @var array<int, list<string>>\n     */\n    protected const DESCRIPTOR_SPEC = [\n        0 => ['pipe', 'r'],  // STDIN is a pipe that the child will read from\n        1 => ['pipe', 'w'],  // STDOUT is a pipe that the child will write to\n        2 => ['pipe', 'w'],  // STDERR is a pipe to catch the any errors\n    ];\n\n    /**\n     * @param  string                    $command Command for the process to start. Absolute paths are recommended,\n     *                                            especially if you do not use the $cwd parameter.\n     * @param  string|null               $cwd     \"Current working directory\" (CWD) for the process to be executed in.\n     * @param  float                     $timeout The maximum timeout (in seconds) for the stream_select() function.\n     * @throws \\InvalidArgumentException\n     */\n    public function __construct(string $command, int|string|Level $level = Level::Debug, bool $bubble = true, ?string $cwd = null, float $timeout = 1.0)\n    {\n        if ($command === '') {\n            throw new \\InvalidArgumentException('The command argument must be a non-empty string.');\n        }\n        if ($cwd === '') {\n            throw new \\InvalidArgumentException('The optional CWD argument must be a non-empty string or null.');\n        }\n\n        parent::__construct($level, $bubble);\n\n        $this->command = $command;\n        $this->cwd = $cwd;\n        $this->timeout = $timeout;\n    }\n\n    /**\n     * Writes the record down to the log of the implementing handler\n     *\n     * @throws \\UnexpectedValueException\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->ensureProcessIsStarted();\n\n        $this->writeProcessInput($record->formatted);\n\n        $errors = $this->readProcessErrors();\n        if ($errors !== '') {\n            throw new \\UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors));\n        }\n    }\n\n    /**\n     * Makes sure that the process is actually started, and if not, starts it,\n     * assigns the stream pipes, and handles startup errors, if any.\n     */\n    private function ensureProcessIsStarted(): void\n    {\n        if (\\is_resource($this->process) === false) {\n            $this->startProcess();\n\n            $this->handleStartupErrors();\n        }\n    }\n\n    /**\n     * Starts the actual process and sets all streams to non-blocking.\n     */\n    private function startProcess(): void\n    {\n        $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd);\n\n        foreach ($this->pipes as $pipe) {\n            stream_set_blocking($pipe, false);\n        }\n    }\n\n    /**\n     * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any.\n     *\n     * @throws \\UnexpectedValueException\n     */\n    private function handleStartupErrors(): void\n    {\n        $selected = $this->selectErrorStream();\n        if (false === $selected) {\n            throw new \\UnexpectedValueException('Something went wrong while selecting a stream.');\n        }\n\n        $errors = $this->readProcessErrors();\n\n        if (\\is_resource($this->process) === false || $errors !== '') {\n            throw new \\UnexpectedValueException(\n                sprintf('The process \"%s\" could not be opened: ' . $errors, $this->command)\n            );\n        }\n    }\n\n    /**\n     * Selects the STDERR stream.\n     *\n     * @return int|bool\n     */\n    protected function selectErrorStream()\n    {\n        $empty = [];\n        $errorPipes = [$this->pipes[2]];\n\n        $seconds = (int) $this->timeout;\n        return stream_select($errorPipes, $empty, $empty, $seconds, (int) (($this->timeout - $seconds) * 1000000));\n    }\n\n    /**\n     * Reads the errors of the process, if there are any.\n     *\n     * @codeCoverageIgnore\n     * @return string Empty string if there are no errors.\n     */\n    protected function readProcessErrors(): string\n    {\n        return (string) stream_get_contents($this->pipes[2]);\n    }\n\n    /**\n     * Writes to the input stream of the opened process.\n     *\n     * @codeCoverageIgnore\n     */\n    protected function writeProcessInput(string $string): void\n    {\n        fwrite($this->pipes[0], $string);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function close(): void\n    {\n        if (\\is_resource($this->process)) {\n            foreach ($this->pipes as $pipe) {\n                fclose($pipe);\n            }\n            proc_close($this->process);\n            $this->process = null;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/ProcessableHandlerInterface.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Processor\\ProcessorInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Interface to describe loggers that have processors\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\ninterface ProcessableHandlerInterface\n{\n    /**\n     * Adds a processor in the stack.\n     *\n     * @phpstan-param ProcessorInterface|(callable(LogRecord): LogRecord) $callback\n     *\n     * @param  ProcessorInterface|callable $callback\n     * @return HandlerInterface            self\n     */\n    public function pushProcessor(callable $callback): HandlerInterface;\n\n    /**\n     * Removes the processor on top of the stack and returns it.\n     *\n     * @phpstan-return ProcessorInterface|(callable(LogRecord): LogRecord) $callback\n     *\n     * @throws \\LogicException             In case the processor stack is empty\n     * @return callable|ProcessorInterface\n     */\n    public function popProcessor(): callable;\n}\n"
  },
  {
    "path": "src/Monolog/Handler/ProcessableHandlerTrait.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\ResettableInterface;\nuse Monolog\\Processor\\ProcessorInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Helper trait for implementing ProcessableInterface\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\ntrait ProcessableHandlerTrait\n{\n    /**\n     * @var callable[]\n     * @phpstan-var array<(callable(LogRecord): LogRecord)|ProcessorInterface>\n     */\n    protected array $processors = [];\n\n    /**\n     * @inheritDoc\n     */\n    public function pushProcessor(callable $callback): HandlerInterface\n    {\n        array_unshift($this->processors, $callback);\n\n        return $this;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function popProcessor(): callable\n    {\n        if (\\count($this->processors) === 0) {\n            throw new \\LogicException('You tried to pop from an empty processor stack.');\n        }\n\n        return array_shift($this->processors);\n    }\n\n    protected function processRecord(LogRecord $record): LogRecord\n    {\n        foreach ($this->processors as $processor) {\n            $record = $processor($record);\n        }\n\n        return $record;\n    }\n\n    protected function resetProcessors(): void\n    {\n        foreach ($this->processors as $processor) {\n            if ($processor instanceof ResettableInterface) {\n                $processor->reset();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/PsrHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Psr\\Log\\LoggerInterface;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Proxies log messages to an existing PSR-3 compliant logger.\n *\n * If a formatter is configured, the formatter's output MUST be a string and the\n * formatted message will be fed to the wrapped PSR logger instead of the original\n * log record's message.\n *\n * @author Michael Moussa <michael.moussa@gmail.com>\n */\nclass PsrHandler extends AbstractHandler implements FormattableHandlerInterface\n{\n    /**\n     * PSR-3 compliant logger\n     */\n    protected LoggerInterface $logger;\n\n    protected FormatterInterface|null $formatter = null;\n    private bool $includeExtra;\n\n    /**\n     * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied\n     */\n    public function __construct(LoggerInterface $logger, int|string|Level $level = Level::Debug, bool $bubble = true, bool $includeExtra = false)\n    {\n        parent::__construct($level, $bubble);\n\n        $this->logger = $logger;\n        $this->includeExtra = $includeExtra;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        if (!$this->isHandling($record)) {\n            return false;\n        }\n\n        $message = $this->formatter !== null\n            ? (string) $this->formatter->format($record)\n            : $record->message;\n\n        $context = $this->includeExtra\n            ? [...$record->extra, ...$record->context]\n            : $record->context;\n\n        $this->logger->log($record->level->toPsrLogLevel(), $message, $context);\n\n        return false === $this->bubble;\n    }\n\n    /**\n     * Sets the formatter.\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        $this->formatter = $formatter;\n\n        return $this;\n    }\n\n    /**\n     * Gets the formatter.\n     */\n    public function getFormatter(): FormatterInterface\n    {\n        if ($this->formatter === null) {\n            throw new \\LogicException('No formatter has been set and this handler does not have a default formatter');\n        }\n\n        return $this->formatter;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/PushoverHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Monolog\\Utils;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\LogRecord;\n\n/**\n * Sends notifications through the pushover api to mobile phones\n *\n * @author Sebastian Göttschkes <sebastian.goettschkes@googlemail.com>\n * @see    https://www.pushover.net/api\n */\nclass PushoverHandler extends SocketHandler\n{\n    private string $token;\n\n    /** @var array<int|string> */\n    private array $users;\n\n    private string $title;\n\n    private string|int|null $user = null;\n\n    private int $retry;\n\n    private int $expire;\n\n    private Level $highPriorityLevel;\n\n    private Level $emergencyLevel;\n\n    private bool $useFormattedMessage = false;\n\n    /**\n     * All parameters that can be sent to Pushover\n     * @see https://pushover.net/api\n     * @var array<string, bool>\n     */\n    private array $parameterNames = [\n        'token' => true,\n        'user' => true,\n        'message' => true,\n        'device' => true,\n        'title' => true,\n        'url' => true,\n        'url_title' => true,\n        'priority' => true,\n        'timestamp' => true,\n        'sound' => true,\n        'retry' => true,\n        'expire' => true,\n        'callback' => true,\n    ];\n\n    /**\n     * Sounds the api supports by default\n     * @see https://pushover.net/api#sounds\n     * @var string[]\n     */\n    private array $sounds = [\n        'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming',\n        'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb',\n        'persistent', 'echo', 'updown', 'none',\n    ];\n\n    /**\n     * @param string       $token  Pushover api token\n     * @param string|array $users  Pushover user id or array of ids the message will be sent to\n     * @param string|null  $title  Title sent to the Pushover API\n     * @param bool         $useSSL Whether to connect via SSL. Required when pushing messages to users that are not\n     *                             the pushover.net app owner. OpenSSL is required for this option.\n     * @param int          $retry  The retry parameter specifies how often (in seconds) the Pushover servers will\n     *                             send the same notification to the user.\n     * @param int          $expire The expire parameter specifies how many seconds your notification will continue\n     *                             to be retried for (every retry seconds).\n     *\n     * @param int|string|Level|LogLevel::* $highPriorityLevel The minimum logging level at which this handler will start\n     *                                                        sending \"high priority\" requests to the Pushover API\n     * @param int|string|Level|LogLevel::* $emergencyLevel    The minimum logging level at which this handler will start\n     *                                                        sending \"emergency\" requests to the Pushover API\n     *\n     *\n     * @phpstan-param string|array<int|string>    $users\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $highPriorityLevel\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $emergencyLevel\n     */\n    public function __construct(\n        string $token,\n        $users,\n        ?string $title = null,\n        int|string|Level $level = Level::Critical,\n        bool $bubble = true,\n        bool $useSSL = true,\n        int|string|Level $highPriorityLevel = Level::Critical,\n        int|string|Level $emergencyLevel = Level::Emergency,\n        int $retry = 30,\n        int $expire = 25200,\n        bool $persistent = false,\n        float $timeout = 0.0,\n        float $writingTimeout = 10.0,\n        ?float $connectionTimeout = null,\n        ?int $chunkSize = null\n    ) {\n        $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80';\n        parent::__construct(\n            $connectionString,\n            $level,\n            $bubble,\n            $persistent,\n            $timeout,\n            $writingTimeout,\n            $connectionTimeout,\n            $chunkSize\n        );\n\n        $this->token = $token;\n        $this->users = (array) $users;\n        $this->title = $title ?? (string) gethostname();\n        $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel);\n        $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel);\n        $this->retry = $retry;\n        $this->expire = $expire;\n    }\n\n    protected function generateDataStream(LogRecord $record): string\n    {\n        $content = $this->buildContent($record);\n\n        return $this->buildHeader($content) . $content;\n    }\n\n    private function buildContent(LogRecord $record): string\n    {\n        // Pushover has a limit of 512 characters on title and message combined.\n        $maxMessageLength = 512 - \\strlen($this->title);\n\n        $message = ($this->useFormattedMessage) ? $record->formatted : $record->message;\n        $message = Utils::substr($message, 0, $maxMessageLength);\n\n        $timestamp = $record->datetime->getTimestamp();\n\n        $dataArray = [\n            'token' => $this->token,\n            'user' => $this->user,\n            'message' => $message,\n            'title' => $this->title,\n            'timestamp' => $timestamp,\n        ];\n\n        if ($record->level->value >= $this->emergencyLevel->value) {\n            $dataArray['priority'] = 2;\n            $dataArray['retry'] = $this->retry;\n            $dataArray['expire'] = $this->expire;\n        } elseif ($record->level->value >= $this->highPriorityLevel->value) {\n            $dataArray['priority'] = 1;\n        }\n\n        // First determine the available parameters\n        $context = array_intersect_key($record->context, $this->parameterNames);\n        $extra = array_intersect_key($record->extra, $this->parameterNames);\n\n        // Least important info should be merged with subsequent info\n        $dataArray = array_merge($extra, $context, $dataArray);\n\n        // Only pass sounds that are supported by the API\n        if (isset($dataArray['sound']) && !\\in_array($dataArray['sound'], $this->sounds, true)) {\n            unset($dataArray['sound']);\n        }\n\n        return http_build_query($dataArray);\n    }\n\n    private function buildHeader(string $content): string\n    {\n        $header = \"POST /1/messages.json HTTP/1.1\\r\\n\";\n        $header .= \"Host: api.pushover.net\\r\\n\";\n        $header .= \"Content-Type: application/x-www-form-urlencoded\\r\\n\";\n        $header .= \"Content-Length: \" . \\strlen($content) . \"\\r\\n\";\n        $header .= \"\\r\\n\";\n\n        return $header;\n    }\n\n    protected function write(LogRecord $record): void\n    {\n        foreach ($this->users as $user) {\n            $this->user = $user;\n\n            parent::write($record);\n            $this->closeSocket();\n        }\n\n        $this->user = null;\n    }\n\n    /**\n     * @param  int|string|Level|LogLevel::* $level\n     * @return $this\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public function setHighPriorityLevel(int|string|Level $level): self\n    {\n        $this->highPriorityLevel = Logger::toMonologLevel($level);\n\n        return $this;\n    }\n\n    /**\n     * @param  int|string|Level|LogLevel::* $level\n     * @return $this\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public function setEmergencyLevel(int|string|Level $level): self\n    {\n        $this->emergencyLevel = Logger::toMonologLevel($level);\n\n        return $this;\n    }\n\n    /**\n     * Use the formatted message?\n     *\n     * @return $this\n     */\n    public function useFormattedMessage(bool $useFormattedMessage): self\n    {\n        $this->useFormattedMessage = $useFormattedMessage;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/RedisHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\nuse Predis\\Client as Predis;\nuse Redis;\n\n/**\n * Logs to a Redis key using rpush\n *\n * usage example:\n *\n *   $log = new Logger('application');\n *   $redis = new RedisHandler(new Predis\\Client(\"tcp://localhost:6379\"), \"logs\");\n *   $log->pushHandler($redis);\n *\n * @author Thomas Tourlourat <thomas@tourlourat.com>\n */\nclass RedisHandler extends AbstractProcessingHandler\n{\n    /** @var Predis<Predis>|Redis */\n    private Predis|Redis $redisClient;\n    private string $redisKey;\n    protected int $capSize;\n\n    /**\n     * @param Predis<Predis>|Redis $redis   The redis instance\n     * @param string               $key     The key name to push records to\n     * @param int                  $capSize Number of entries to limit list size to, 0 = unlimited\n     */\n    public function __construct(Predis|Redis $redis, string $key, int|string|Level $level = Level::Debug, bool $bubble = true, int $capSize = 0)\n    {\n        $this->redisClient = $redis;\n        $this->redisKey = $key;\n        $this->capSize = $capSize;\n\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        if ($this->capSize > 0) {\n            $this->writeCapped($record);\n        } else {\n            $this->redisClient->rpush($this->redisKey, $record->formatted);\n        }\n    }\n\n    /**\n     * Write and cap the collection\n     * Writes the record to the redis list and caps its\n     */\n    protected function writeCapped(LogRecord $record): void\n    {\n        if ($this->redisClient instanceof Redis) {\n            $mode = \\defined('Redis::MULTI') ? Redis::MULTI : 1;\n            $this->redisClient->multi($mode)\n                ->rPush($this->redisKey, $record->formatted)\n                ->ltrim($this->redisKey, -$this->capSize, -1)\n                ->exec();\n        } else {\n            $redisKey = $this->redisKey;\n            $capSize = $this->capSize;\n            $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) {\n                $tx->rpush($redisKey, $record->formatted);\n                $tx->ltrim($redisKey, -$capSize, -1);\n            });\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new LineFormatter();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/RedisPubSubHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\nuse Predis\\Client as Predis;\nuse Redis;\n\n/**\n * Sends the message to a Redis Pub/Sub channel using PUBLISH\n *\n * usage example:\n *\n *   $log = new Logger('application');\n *   $redis = new RedisPubSubHandler(new Predis\\Client(\"tcp://localhost:6379\"), \"logs\", Level::Warning);\n *   $log->pushHandler($redis);\n *\n * @author Gaëtan Faugère <gaetan@fauge.re>\n */\nclass RedisPubSubHandler extends AbstractProcessingHandler\n{\n    /** @var Predis<Predis>|Redis */\n    private Predis|Redis $redisClient;\n    private string $channelKey;\n\n    /**\n     * @param Predis<Predis>|Redis $redis The redis instance\n     * @param string               $key   The channel key to publish records to\n     */\n    public function __construct(Predis|Redis $redis, string $key, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        $this->redisClient = $redis;\n        $this->channelKey = $key;\n\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->redisClient->publish($this->channelKey, $record->formatted);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function getDefaultFormatter(): FormatterInterface\n    {\n        return new LineFormatter();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/RollbarHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Rollbar\\RollbarLogger;\nuse Throwable;\nuse Monolog\\LogRecord;\n\n/**\n * Sends errors to Rollbar\n *\n * If the context data contains a `payload` key, that is used as an array\n * of payload options to RollbarLogger's log method.\n *\n * Rollbar's context info will contain the context + extra keys from the log record\n * merged, and then on top of that a few keys:\n *\n *  - level (rollbar level name)\n *  - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8)\n *  - channel\n *  - datetime (unix timestamp)\n *\n * @author Paul Statezny <paulstatezny@gmail.com>\n */\nclass RollbarHandler extends AbstractProcessingHandler\n{\n    protected RollbarLogger $rollbarLogger;\n\n    /**\n     * Records whether any log records have been added since the last flush of the rollbar notifier\n     */\n    private bool $hasRecords = false;\n\n    protected bool $initialized = false;\n\n    /**\n     * @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token\n     */\n    public function __construct(RollbarLogger $rollbarLogger, int|string|Level $level = Level::Error, bool $bubble = true)\n    {\n        $this->rollbarLogger = $rollbarLogger;\n\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * Translates Monolog log levels to Rollbar levels.\n     *\n     * @return 'debug'|'info'|'warning'|'error'|'critical'\n     */\n    protected function toRollbarLevel(Level $level): string\n    {\n        return match ($level) {\n            Level::Debug     => 'debug',\n            Level::Info      => 'info',\n            Level::Notice    => 'info',\n            Level::Warning   => 'warning',\n            Level::Error     => 'error',\n            Level::Critical  => 'critical',\n            Level::Alert     => 'critical',\n            Level::Emergency => 'critical',\n        };\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        if (!$this->initialized) {\n            // __destructor() doesn't get called on Fatal errors\n            register_shutdown_function([$this, 'close']);\n            $this->initialized = true;\n        }\n\n        $context = $record->context;\n        $context = array_merge($context, $record->extra, [\n            'level' => $this->toRollbarLevel($record->level),\n            'monolog_level' => $record->level->getName(),\n            'channel' => $record->channel,\n            'datetime' => $record->datetime->format('U'),\n        ]);\n\n        if (isset($context['exception']) && $context['exception'] instanceof Throwable) {\n            $exception = $context['exception'];\n            unset($context['exception']);\n            $toLog = $exception;\n        } else {\n            $toLog = $record->message;\n        }\n\n        $this->rollbarLogger->log($context['level'], $toLog, $context);\n\n        $this->hasRecords = true;\n    }\n\n    public function flush(): void\n    {\n        if ($this->hasRecords) {\n            $this->rollbarLogger->flush();\n            $this->hasRecords = false;\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function close(): void\n    {\n        $this->flush();\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function reset(): void\n    {\n        $this->flush();\n\n        parent::reset();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/RotatingFileHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse DateTimeZone;\nuse InvalidArgumentException;\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Stores logs to files that are rotated every day and a limited number of files are kept.\n *\n * This rotation is only intended to be used as a workaround. Using logrotate to\n * handle the rotation is strongly encouraged when you can use it.\n *\n * @author Christophe Coevoet <stof@notk.org>\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass RotatingFileHandler extends StreamHandler\n{\n    public const FILE_PER_DAY = 'Y-m-d';\n    public const FILE_PER_MONTH = 'Y-m';\n    public const FILE_PER_YEAR = 'Y';\n\n    protected string $filename;\n    protected int $maxFiles;\n    protected bool|null $mustRotate = null;\n    protected \\DateTimeImmutable $nextRotation;\n    protected string $filenameFormat;\n    protected string $dateFormat;\n    protected DateTimeZone|null $timezone = null;\n\n    /**\n     * @param int      $maxFiles       The maximal amount of files to keep (0 means unlimited)\n     * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write)\n     * @param bool     $useLocking     Try to lock log file before doing any writes\n     */\n    public function __construct(string $filename, int $maxFiles = 0, int|string|Level $level = Level::Debug, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false, string $dateFormat = self::FILE_PER_DAY, string $filenameFormat  = '{filename}-{date}', DateTimeZone|null $timezone = null)\n    {\n        $this->filename = Utils::canonicalizePath($filename);\n        $this->maxFiles = $maxFiles;\n        $this->setFilenameFormat($filenameFormat, $dateFormat);\n        $this->nextRotation = $this->getNextRotation();\n        $this->timezone = $timezone;\n\n        parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function close(): void\n    {\n        parent::close();\n\n        if (true === $this->mustRotate) {\n            $this->rotate();\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function reset(): void\n    {\n        parent::reset();\n    }\n\n    /**\n     * @return $this\n     */\n    public function setFilenameFormat(string $filenameFormat, string $dateFormat): self\n    {\n        $this->setDateFormat($dateFormat);\n        if (substr_count($filenameFormat, '{date}') === 0) {\n            throw new InvalidArgumentException(\n                'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.'\n            );\n        }\n        $this->filenameFormat = $filenameFormat;\n        $this->url = $this->getTimedFilename();\n        $this->close();\n\n        return $this;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        // on the first record written, if the log is new, we rotate (once per day) after the log has been written so that the new file exists\n        if (null === $this->mustRotate) {\n            $this->mustRotate = null === $this->url || !file_exists($this->url);\n        }\n\n        // if the next rotation is expired, then we rotate immediately\n        if ($this->nextRotation <= $record->datetime) {\n            $this->mustRotate = true;\n            $this->close(); // triggers rotation\n        }\n\n        parent::write($record);\n\n        if (true === $this->mustRotate) {\n            $this->close(); // triggers rotation\n        }\n    }\n\n    /**\n     * Rotates the files.\n     */\n    protected function rotate(): void\n    {\n        // update filename\n        $this->url = $this->getTimedFilename();\n        $this->nextRotation = $this->getNextRotation();\n\n        $this->mustRotate = false;\n\n        // skip GC of old logs if files are unlimited\n        if (0 === $this->maxFiles) {\n            return;\n        }\n\n        $logFiles = glob($this->getGlobPattern());\n        if (false === $logFiles) {\n            // failed to glob\n            return;\n        }\n\n        if ($this->maxFiles >= \\count($logFiles)) {\n            // no files to remove\n            return;\n        }\n\n        // Sorting the files by name to remove the older ones\n        usort($logFiles, function ($a, $b) {\n            return strcmp($b, $a);\n        });\n\n        $basePath = dirname($this->filename);\n\n        foreach (\\array_slice($logFiles, $this->maxFiles) as $file) {\n            if (is_writable($file)) {\n                // suppress errors here as unlink() might fail if two processes\n                // are cleaning up/rotating at the same time\n                set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool {\n                    return true;\n                });\n                unlink($file);\n\n                $dir = dirname($file);\n                while ($dir !== $basePath) {\n                    $entries = scandir($dir);\n                    if ($entries === false || \\count(array_diff($entries, ['.', '..'])) > 0) {\n                        break;\n                    }\n\n                    rmdir($dir);\n                    $dir = dirname($dir);\n                }\n                restore_error_handler();\n            }\n        }\n    }\n\n    protected function getTimedFilename(): string\n    {\n        $fileInfo = pathinfo($this->filename);\n        $timedFilename = str_replace(\n            ['{filename}', '{date}'],\n            [$fileInfo['filename'], (new \\DateTimeImmutable(timezone: $this->timezone))->format($this->dateFormat)],\n            ($fileInfo['dirname'] ?? '') . '/' . $this->filenameFormat\n        );\n\n        if (isset($fileInfo['extension'])) {\n            $timedFilename .= '.'.$fileInfo['extension'];\n        }\n\n        return $timedFilename;\n    }\n\n    protected function getGlobPattern(): string\n    {\n        $fileInfo = pathinfo($this->filename);\n        $glob = str_replace(\n            ['{filename}', '{date}'],\n            [$fileInfo['filename'], str_replace(\n                ['Y', 'y', 'm', 'd'],\n                ['[0-9][0-9][0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]'],\n                $this->dateFormat\n            )],\n            ($fileInfo['dirname'] ?? '') . '/' . $this->filenameFormat\n        );\n        if (isset($fileInfo['extension'])) {\n            $glob .= '.'.$fileInfo['extension'];\n        }\n\n        return $glob;\n    }\n\n    protected function setDateFormat(string $dateFormat): void\n    {\n        if (0 === preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) {\n            throw new InvalidArgumentException(\n                'Invalid date format - format must be one of '.\n                'RotatingFileHandler::FILE_PER_DAY (\"Y-m-d\"), RotatingFileHandler::FILE_PER_MONTH (\"Y-m\") '.\n                'or RotatingFileHandler::FILE_PER_YEAR (\"Y\"), or you can set one of the '.\n                'date formats using slashes, underscores and/or dots instead of dashes.'\n            );\n        }\n        $this->dateFormat = $dateFormat;\n    }\n\n    protected function getNextRotation(): \\DateTimeImmutable\n    {\n        return match (str_replace(['/','_','.'], '-', $this->dateFormat)) {\n            self::FILE_PER_MONTH => (new \\DateTimeImmutable('first day of next month'))->setTime(0, 0, 0),\n            self::FILE_PER_YEAR => (new \\DateTimeImmutable('first day of January next year'))->setTime(0, 0, 0),\n            default => (new \\DateTimeImmutable('tomorrow'))->setTime(0, 0, 0),\n        };\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/SamplingHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Closure;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Sampling handler\n *\n * A sampled event stream can be useful for logging high frequency events in\n * a production environment where you only need an idea of what is happening\n * and are not concerned with capturing every occurrence. Since the decision to\n * handle or not handle a particular event is determined randomly, the\n * resulting sampled log is not guaranteed to contain 1/N of the events that\n * occurred in the application, but based on the Law of large numbers, it will\n * tend to be close to this ratio with a large number of attempts.\n *\n * @author Bryan Davis <bd808@wikimedia.org>\n * @author Kunal Mehta <legoktm@gmail.com>\n */\nclass SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface\n{\n    use ProcessableHandlerTrait;\n\n    /**\n     * Handler or factory Closure($record, $this)\n     *\n     * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface\n     */\n    protected Closure|HandlerInterface $handler;\n\n    protected int $factor;\n\n    /**\n     * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler\n     *\n     * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $samplingHandler).\n     * @param int                      $factor  Sample factor (e.g. 10 means every ~10th record is sampled)\n     */\n    public function __construct(Closure|HandlerInterface $handler, int $factor)\n    {\n        parent::__construct();\n        $this->handler = $handler;\n        $this->factor = $factor;\n    }\n\n    public function isHandling(LogRecord $record): bool\n    {\n        return $this->getHandler($record)->isHandling($record);\n    }\n\n    public function handle(LogRecord $record): bool\n    {\n        if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) {\n            if (\\count($this->processors) > 0) {\n                $record = $this->processRecord($record);\n            }\n\n            $this->getHandler($record)->handle($record);\n        }\n\n        return false === $this->bubble;\n    }\n\n    /**\n     * Return the nested handler\n     *\n     * If the handler was provided as a factory, this will trigger the handler's instantiation.\n     */\n    public function getHandler(LogRecord|null $record = null): HandlerInterface\n    {\n        if (!$this->handler instanceof HandlerInterface) {\n            $handler = ($this->handler)($record, $this);\n            if (!$handler instanceof HandlerInterface) {\n                throw new \\RuntimeException(\"The factory Closure should return a HandlerInterface\");\n            }\n            $this->handler = $handler;\n        }\n\n        return $this->handler;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        $handler = $this->getHandler();\n        if ($handler instanceof FormattableHandlerInterface) {\n            $handler->setFormatter($formatter);\n\n            return $this;\n        }\n\n        throw new \\UnexpectedValueException('The nested handler of type '.\\get_class($handler).' does not support formatters.');\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getFormatter(): FormatterInterface\n    {\n        $handler = $this->getHandler();\n        if ($handler instanceof FormattableHandlerInterface) {\n            return $handler->getFormatter();\n        }\n\n        throw new \\UnexpectedValueException('The nested handler of type '.\\get_class($handler).' does not support formatters.');\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/SendGridHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Utils;\n\n/**\n * SendGridHandler uses the SendGrid API v3 function to send Log emails, more information in https://www.twilio.com/docs/sendgrid/for-developers/sending-email/api-getting-started\n *\n * @author Ricardo Fontanelli <ricardo.fontanelli@hotmail.com>\n */\nclass SendGridHandler extends MailHandler\n{\n    /**\n     * The SendGrid API User\n     * @deprecated this is not used anymore as of SendGrid API v3\n     */\n    protected string $apiUser;\n    /**\n     * The email addresses to which the message will be sent\n     * @var string[]\n     */\n    protected array $to;\n\n    /**\n     * @param string|null $apiUser Unused user as of SendGrid API v3, you can pass null or any string\n     * @param list<string>|string $to\n     * @param non-empty-string $apiHost Allows you to use another endpoint (e.g. api.eu.sendgrid.com)\n     * @throws MissingExtensionException If the curl extension is missing\n     */\n    public function __construct(\n        string|null $apiUser,\n        protected string $apiKey,\n        protected string $from,\n        array|string $to,\n        protected string $subject,\n        int|string|Level $level = Level::Error,\n        bool $bubble = true,\n        /** @var non-empty-string */\n        private readonly string $apiHost = 'api.sendgrid.com',\n    ) {\n        if (!\\extension_loaded('curl')) {\n            throw new MissingExtensionException('The curl extension is needed to use the SendGridHandler');\n        }\n\n        $this->to = (array) $to;\n        // @phpstan-ignore property.deprecated\n        $this->apiUser = $apiUser ?? '';\n        parent::__construct($level, $bubble);\n    }\n\n    protected function send(string $content, array $records): void\n    {\n        $body = [];\n        $body['personalizations'] = [];\n        $body['from']['email'] = $this->from;\n        foreach ($this->to as $recipient) {\n            $body['personalizations'][]['to'][]['email'] = $recipient;\n        }\n        $body['subject'] = $this->subject;\n\n        if ($this->isHtmlBody($content)) {\n            $body['content'][] = [\n                'type' => 'text/html',\n                'value' => $content,\n            ];\n        } else {\n            $body['content'][] = [\n                'type' => 'text/plain',\n                'value' => $content,\n            ];\n        }\n        $ch = curl_init();\n        curl_setopt($ch, CURLOPT_HTTPHEADER, [\n            'Content-Type: application/json',\n            'Authorization: Bearer '.$this->apiKey,\n        ]);\n        curl_setopt($ch, CURLOPT_URL, 'https://'.$this->apiHost.'/v3/mail/send');\n        curl_setopt($ch, CURLOPT_POST, true);\n        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n        curl_setopt($ch, CURLOPT_POSTFIELDS, Utils::jsonEncode($body));\n\n        Curl\\Util::execute($ch, 2);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/Slack/SlackRecord.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler\\Slack;\n\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\Formatter\\NormalizerFormatter;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Slack record utility helping to log to Slack webhooks or API.\n *\n * @author Greg Kedzierski <greg@gregkedzierski.com>\n * @author Haralan Dobrev <hkdobrev@gmail.com>\n * @see    https://api.slack.com/incoming-webhooks\n * @see    https://api.slack.com/docs/message-attachments\n */\nclass SlackRecord\n{\n    public const COLOR_DANGER = 'danger';\n\n    public const COLOR_WARNING = 'warning';\n\n    public const COLOR_GOOD = 'good';\n\n    public const COLOR_DEFAULT = '#e3e4e6';\n\n    /**\n     * Slack channel (encoded ID or name)\n     */\n    private string|null $channel;\n\n    /**\n     * Name of a bot\n     */\n    private string|null $username;\n\n    /**\n     * User icon e.g. 'ghost', 'http://example.com/user.png'\n     */\n    private string|null $userIcon;\n\n    /**\n     * Whether the message should be added to Slack as attachment (plain text otherwise)\n     */\n    private bool $useAttachment;\n\n    /**\n     * Whether the the context/extra messages added to Slack as attachments are in a short style\n     */\n    private bool $useShortAttachment;\n\n    /**\n     * Whether the attachment should include context and extra data\n     */\n    private bool $includeContextAndExtra;\n\n    /**\n     * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2']\n     * @var string[]\n     */\n    private array $excludeFields;\n\n    private FormatterInterface|null $formatter;\n\n    private NormalizerFormatter $normalizerFormatter;\n\n    /**\n     * @param string[] $excludeFields\n     */\n    public function __construct(\n        ?string $channel = null,\n        ?string $username = null,\n        bool $useAttachment = true,\n        ?string $userIcon = null,\n        bool $useShortAttachment = false,\n        bool $includeContextAndExtra = false,\n        array $excludeFields = [],\n        FormatterInterface|null $formatter = null\n    ) {\n        $this\n            ->setChannel($channel)\n            ->setUsername($username)\n            ->useAttachment($useAttachment)\n            ->setUserIcon($userIcon)\n            ->useShortAttachment($useShortAttachment)\n            ->includeContextAndExtra($includeContextAndExtra)\n            ->excludeFields($excludeFields)\n            ->setFormatter($formatter);\n\n        if ($this->includeContextAndExtra) {\n            $this->normalizerFormatter = new NormalizerFormatter();\n        }\n    }\n\n    /**\n     * Returns required data in format that Slack\n     * is expecting.\n     *\n     * @phpstan-return mixed[]\n     */\n    public function getSlackData(LogRecord $record): array\n    {\n        $dataArray = [];\n\n        if ($this->username !== null) {\n            $dataArray['username'] = $this->username;\n        }\n\n        if ($this->channel !== null) {\n            $dataArray['channel'] = $this->channel;\n        }\n\n        if ($this->formatter !== null && !$this->useAttachment) {\n            $message = $this->formatter->format($record);\n        } else {\n            $message = $record->message;\n        }\n\n        $recordData = $this->removeExcludedFields($record);\n\n        if ($this->useAttachment) {\n            $attachment = [\n                'fallback'  => $message,\n                'text'      => $message,\n                'color'     => $this->getAttachmentColor($record->level),\n                'fields'    => [],\n                'mrkdwn_in' => ['fields'],\n                'ts'        => $recordData['datetime']->getTimestamp(),\n                'footer'      => $this->username,\n                'footer_icon' => $this->userIcon,\n            ];\n\n            if ($this->useShortAttachment) {\n                $attachment['title'] = $recordData['level_name'];\n            } else {\n                $attachment['title'] = 'Message';\n                $attachment['fields'][] = $this->generateAttachmentField('Level', $recordData['level_name']);\n            }\n\n            if ($this->includeContextAndExtra) {\n                foreach (['extra', 'context'] as $key) {\n                    if (!isset($recordData[$key]) || \\count($recordData[$key]) === 0) {\n                        continue;\n                    }\n\n                    if ($this->useShortAttachment) {\n                        $attachment['fields'][] = $this->generateAttachmentField(\n                            $key,\n                            $recordData[$key]\n                        );\n                    } else {\n                        // Add all extra fields as individual fields in attachment\n                        $attachment['fields'] = array_merge(\n                            $attachment['fields'],\n                            $this->generateAttachmentFields($recordData[$key])\n                        );\n                    }\n                }\n            }\n\n            $dataArray['attachments'] = [$attachment];\n        } else {\n            $dataArray['text'] = $message;\n        }\n\n        if ($this->userIcon !== null) {\n            if (false !== ($iconUrl = filter_var($this->userIcon, FILTER_VALIDATE_URL))) {\n                $dataArray['icon_url'] = $iconUrl;\n            } else {\n                $dataArray['icon_emoji'] = \":{$this->userIcon}:\";\n            }\n        }\n\n        return $dataArray;\n    }\n\n    /**\n     * Returns a Slack message attachment color associated with\n     * provided level.\n     */\n    public function getAttachmentColor(Level $level): string\n    {\n        return match ($level) {\n            Level::Error, Level::Critical, Level::Alert, Level::Emergency => static::COLOR_DANGER,\n            Level::Warning => static::COLOR_WARNING,\n            Level::Info, Level::Notice => static::COLOR_GOOD,\n            Level::Debug => static::COLOR_DEFAULT\n        };\n    }\n\n    /**\n     * Stringifies an array of key/value pairs to be used in attachment fields\n     *\n     * @param mixed[] $fields\n     */\n    public function stringify(array $fields): string\n    {\n        /** @var array<array<mixed>|bool|float|int|string|null> $normalized */\n        $normalized = $this->normalizerFormatter->normalizeValue($fields);\n\n        $hasSecondDimension = \\count(array_filter($normalized, 'is_array')) > 0;\n        $hasOnlyNonNumericKeys = \\count(array_filter(array_keys($normalized), 'is_numeric')) === 0;\n\n        return $hasSecondDimension || $hasOnlyNonNumericKeys\n            ? Utils::jsonEncode($normalized, JSON_PRETTY_PRINT|Utils::DEFAULT_JSON_FLAGS)\n            : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS);\n    }\n\n    /**\n     * Channel used by the bot when posting\n     *\n     * @param  ?string $channel\n     * @return $this\n     */\n    public function setChannel(?string $channel = null): self\n    {\n        $this->channel = $channel;\n\n        return $this;\n    }\n\n    /**\n     * Username used by the bot when posting\n     *\n     * @param  ?string $username\n     * @return $this\n     */\n    public function setUsername(?string $username = null): self\n    {\n        $this->username = $username;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function useAttachment(bool $useAttachment = true): self\n    {\n        $this->useAttachment = $useAttachment;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function setUserIcon(?string $userIcon = null): self\n    {\n        $this->userIcon = $userIcon;\n\n        if (\\is_string($userIcon)) {\n            $this->userIcon = trim($userIcon, ':');\n        }\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function useShortAttachment(bool $useShortAttachment = false): self\n    {\n        $this->useShortAttachment = $useShortAttachment;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function includeContextAndExtra(bool $includeContextAndExtra = false): self\n    {\n        $this->includeContextAndExtra = $includeContextAndExtra;\n\n        if ($this->includeContextAndExtra) {\n            $this->normalizerFormatter = new NormalizerFormatter();\n        }\n\n        return $this;\n    }\n\n    /**\n     * @param  string[] $excludeFields\n     * @return $this\n     */\n    public function excludeFields(array $excludeFields = []): self\n    {\n        $this->excludeFields = $excludeFields;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function setFormatter(?FormatterInterface $formatter = null): self\n    {\n        $this->formatter = $formatter;\n\n        return $this;\n    }\n\n    /**\n     * Generates attachment field\n     *\n     * @param string|mixed[] $value\n     *\n     * @return array{title: string, value: string, short: false}\n     */\n    private function generateAttachmentField(string $title, $value): array\n    {\n        $value = \\is_array($value)\n            ? sprintf('```%s```', substr($this->stringify($value), 0, 1990))\n            : $value;\n\n        return [\n            'title' => ucfirst($title),\n            'value' => $value,\n            'short' => false,\n        ];\n    }\n\n    /**\n     * Generates a collection of attachment fields from array\n     *\n     * @param mixed[] $data\n     *\n     * @return array<array{title: string, value: string, short: false}>\n     */\n    private function generateAttachmentFields(array $data): array\n    {\n        /** @var array<array<mixed>|string> $normalized */\n        $normalized = $this->normalizerFormatter->normalizeValue($data);\n\n        $fields = [];\n        foreach ($normalized as $key => $value) {\n            $fields[] = $this->generateAttachmentField((string) $key, $value);\n        }\n\n        return $fields;\n    }\n\n    /**\n     * Get a copy of record with fields excluded according to $this->excludeFields\n     *\n     * @return mixed[]\n     */\n    private function removeExcludedFields(LogRecord $record): array\n    {\n        $recordData = $record->toArray();\n        foreach ($this->excludeFields as $field) {\n            $keys = explode('.', $field);\n            $node = &$recordData;\n            $lastKey = end($keys);\n            foreach ($keys as $key) {\n                if (!isset($node[$key])) {\n                    break;\n                }\n                if ($lastKey === $key) {\n                    unset($node[$key]);\n                    break;\n                }\n                $node = &$node[$key];\n            }\n        }\n\n        return $recordData;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/SlackHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\Handler\\Slack\\SlackRecord;\nuse Monolog\\LogRecord;\n\n/**\n * Sends notifications through Slack API\n *\n * @author Greg Kedzierski <greg@gregkedzierski.com>\n * @see    https://api.slack.com/\n */\nclass SlackHandler extends SocketHandler\n{\n    /**\n     * Slack API token\n     */\n    private string $token;\n\n    /**\n     * Instance of the SlackRecord util class preparing data for Slack API.\n     */\n    private SlackRecord $slackRecord;\n\n    /**\n     * @param  string                    $token                  Slack API token\n     * @param  string                    $channel                Slack channel (encoded ID or name)\n     * @param  string|null               $username               Name of a bot\n     * @param  bool                      $useAttachment          Whether the message should be added to Slack as attachment (plain text otherwise)\n     * @param  string|null               $iconEmoji              The emoji name to use (or null)\n     * @param  bool                      $useShortAttachment     Whether the context/extra messages added to Slack as attachments are in a short style\n     * @param  bool                      $includeContextAndExtra Whether the attachment should include context and extra data\n     * @param  string[]                  $excludeFields          Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2']\n     * @throws MissingExtensionException If no OpenSSL PHP extension configured\n     */\n    public function __construct(\n        string $token,\n        string $channel,\n        ?string $username = null,\n        bool $useAttachment = true,\n        ?string $iconEmoji = null,\n        $level = Level::Critical,\n        bool $bubble = true,\n        bool $useShortAttachment = false,\n        bool $includeContextAndExtra = false,\n        array $excludeFields = [],\n        bool $persistent = false,\n        float $timeout = 0.0,\n        float $writingTimeout = 10.0,\n        ?float $connectionTimeout = null,\n        ?int $chunkSize = null\n    ) {\n        if (!\\extension_loaded('openssl')) {\n            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler');\n        }\n\n        parent::__construct(\n            'ssl://slack.com:443',\n            $level,\n            $bubble,\n            $persistent,\n            $timeout,\n            $writingTimeout,\n            $connectionTimeout,\n            $chunkSize\n        );\n\n        $this->slackRecord = new SlackRecord(\n            $channel,\n            $username,\n            $useAttachment,\n            $iconEmoji,\n            $useShortAttachment,\n            $includeContextAndExtra,\n            $excludeFields\n        );\n\n        $this->token = $token;\n    }\n\n    public function getSlackRecord(): SlackRecord\n    {\n        return $this->slackRecord;\n    }\n\n    public function getToken(): string\n    {\n        return $this->token;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function generateDataStream(LogRecord $record): string\n    {\n        $content = $this->buildContent($record);\n\n        return $this->buildHeader($content) . $content;\n    }\n\n    /**\n     * Builds the body of API call\n     */\n    private function buildContent(LogRecord $record): string\n    {\n        $dataArray = $this->prepareContentData($record);\n\n        return http_build_query($dataArray);\n    }\n\n    /**\n     * @return string[]\n     */\n    protected function prepareContentData(LogRecord $record): array\n    {\n        $dataArray = $this->slackRecord->getSlackData($record);\n        $dataArray['token'] = $this->token;\n\n        if (isset($dataArray['attachments']) && \\is_array($dataArray['attachments']) && \\count($dataArray['attachments']) > 0) {\n            $dataArray['attachments'] = Utils::jsonEncode($dataArray['attachments']);\n        }\n\n        return $dataArray;\n    }\n\n    /**\n     * Builds the header of the API Call\n     */\n    private function buildHeader(string $content): string\n    {\n        $header = \"POST /api/chat.postMessage HTTP/1.1\\r\\n\";\n        $header .= \"Host: slack.com\\r\\n\";\n        $header .= \"Content-Type: application/x-www-form-urlencoded\\r\\n\";\n        $header .= \"Content-Length: \" . \\strlen($content) . \"\\r\\n\";\n        $header .= \"\\r\\n\";\n\n        return $header;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        parent::write($record);\n        $this->finalizeWrite();\n    }\n\n    /**\n     * Finalizes the request by reading some bytes and then closing the socket\n     *\n     * If we do not read some but close the socket too early, slack sometimes\n     * drops the request entirely.\n     */\n    protected function finalizeWrite(): void\n    {\n        $res = $this->getResource();\n        if (\\is_resource($res)) {\n            @fread($res, 2048);\n        }\n        $this->closeSocket();\n    }\n\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        parent::setFormatter($formatter);\n        $this->slackRecord->setFormatter($formatter);\n\n        return $this;\n    }\n\n    public function getFormatter(): FormatterInterface\n    {\n        $formatter = parent::getFormatter();\n        $this->slackRecord->setFormatter($formatter);\n\n        return $formatter;\n    }\n\n    /**\n     * Channel used by the bot when posting\n     *\n     * @return $this\n     */\n    public function setChannel(string $channel): self\n    {\n        $this->slackRecord->setChannel($channel);\n\n        return $this;\n    }\n\n    /**\n     * Username used by the bot when posting\n     *\n     * @return $this\n     */\n    public function setUsername(string $username): self\n    {\n        $this->slackRecord->setUsername($username);\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function useAttachment(bool $useAttachment): self\n    {\n        $this->slackRecord->useAttachment($useAttachment);\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function setIconEmoji(string $iconEmoji): self\n    {\n        $this->slackRecord->setUserIcon($iconEmoji);\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function useShortAttachment(bool $useShortAttachment): self\n    {\n        $this->slackRecord->useShortAttachment($useShortAttachment);\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function includeContextAndExtra(bool $includeContextAndExtra): self\n    {\n        $this->slackRecord->includeContextAndExtra($includeContextAndExtra);\n\n        return $this;\n    }\n\n    /**\n     * @param  string[] $excludeFields\n     * @return $this\n     */\n    public function excludeFields(array $excludeFields): self\n    {\n        $this->slackRecord->excludeFields($excludeFields);\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/SlackWebhookHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\Handler\\Slack\\SlackRecord;\nuse Monolog\\LogRecord;\n\n/**\n * Sends notifications through Slack Webhooks\n *\n * @author Haralan Dobrev <hkdobrev@gmail.com>\n * @see    https://api.slack.com/incoming-webhooks\n */\nclass SlackWebhookHandler extends AbstractProcessingHandler\n{\n    /**\n     * Slack Webhook token\n     *\n     * @var non-empty-string\n     */\n    private string $webhookUrl;\n\n    /**\n     * Instance of the SlackRecord util class preparing data for Slack API.\n     */\n    private SlackRecord $slackRecord;\n\n    /**\n     * @param non-empty-string $webhookUrl             Slack Webhook URL\n     * @param string|null $channel                Slack channel (encoded ID or name)\n     * @param string|null $username               Name of a bot\n     * @param bool        $useAttachment          Whether the message should be added to Slack as attachment (plain text otherwise)\n     * @param string|null $iconEmoji              The emoji name to use (or null)\n     * @param bool        $useShortAttachment     Whether the the context/extra messages added to Slack as attachments are in a short style\n     * @param bool        $includeContextAndExtra Whether the attachment should include context and extra data\n     * @param string[]    $excludeFields          Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2']\n     *\n     * @throws MissingExtensionException If the curl extension is missing\n     */\n    public function __construct(\n        string $webhookUrl,\n        ?string $channel = null,\n        ?string $username = null,\n        bool $useAttachment = true,\n        ?string $iconEmoji = null,\n        bool $useShortAttachment = false,\n        bool $includeContextAndExtra = false,\n        $level = Level::Critical,\n        bool $bubble = true,\n        array $excludeFields = []\n    ) {\n        if (!\\extension_loaded('curl')) {\n            throw new MissingExtensionException('The curl extension is needed to use the SlackWebhookHandler');\n        }\n\n        parent::__construct($level, $bubble);\n\n        $this->webhookUrl = $webhookUrl;\n\n        $this->slackRecord = new SlackRecord(\n            $channel,\n            $username,\n            $useAttachment,\n            $iconEmoji,\n            $useShortAttachment,\n            $includeContextAndExtra,\n            $excludeFields\n        );\n    }\n\n    public function getSlackRecord(): SlackRecord\n    {\n        return $this->slackRecord;\n    }\n\n    public function getWebhookUrl(): string\n    {\n        return $this->webhookUrl;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $postData = $this->slackRecord->getSlackData($record);\n        $postString = Utils::jsonEncode($postData);\n\n        $ch = curl_init();\n        $options = [\n            CURLOPT_URL => $this->webhookUrl,\n            CURLOPT_POST => true,\n            CURLOPT_RETURNTRANSFER => true,\n            CURLOPT_HTTPHEADER => ['Content-type: application/json'],\n            CURLOPT_POSTFIELDS => $postString,\n        ];\n\n        curl_setopt_array($ch, $options);\n\n        Curl\\Util::execute($ch);\n    }\n\n    public function setFormatter(FormatterInterface $formatter): HandlerInterface\n    {\n        parent::setFormatter($formatter);\n        $this->slackRecord->setFormatter($formatter);\n\n        return $this;\n    }\n\n    public function getFormatter(): FormatterInterface\n    {\n        $formatter = parent::getFormatter();\n        $this->slackRecord->setFormatter($formatter);\n\n        return $formatter;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/SocketHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * Stores to any socket - uses fsockopen() or pfsockopen().\n *\n * @author Pablo de Leon Belloc <pablolb@gmail.com>\n * @see    http://php.net/manual/en/function.fsockopen.php\n */\nclass SocketHandler extends AbstractProcessingHandler\n{\n    private string $connectionString;\n    private float $connectionTimeout;\n    /** @var resource|null */\n    private $resource;\n    private float $timeout;\n    private float $writingTimeout;\n    private int|null $lastSentBytes = null;\n    private int|null $chunkSize;\n    private bool $persistent;\n    private int|null $errno = null;\n    private string|null $errstr = null;\n    private float|null $lastWritingAt = null;\n\n    /**\n     * @param string     $connectionString  Socket connection string\n     * @param bool       $persistent        Flag to enable/disable persistent connections\n     * @param float      $timeout           Socket timeout to wait until the request is being aborted\n     * @param float      $writingTimeout    Socket timeout to wait until the request should've been sent/written\n     * @param float|null $connectionTimeout Socket connect timeout to wait until the connection should've been\n     *                                      established\n     * @param int|null   $chunkSize         Sets the chunk size. Only has effect during connection in the writing cycle\n     *\n     * @throws \\InvalidArgumentException If an invalid timeout value (less than 0) is passed.\n     */\n    public function __construct(\n        string $connectionString,\n        $level = Level::Debug,\n        bool $bubble = true,\n        bool $persistent = false,\n        float $timeout = 0.0,\n        float $writingTimeout = 10.0,\n        ?float $connectionTimeout = null,\n        ?int $chunkSize = null\n    ) {\n        parent::__construct($level, $bubble);\n        $this->connectionString = $connectionString;\n\n        if ($connectionTimeout !== null) {\n            $this->validateTimeout($connectionTimeout);\n        }\n\n        $this->connectionTimeout = $connectionTimeout ?? (float) \\ini_get('default_socket_timeout');\n        $this->persistent = $persistent;\n        $this->validateTimeout($timeout);\n        $this->timeout = $timeout;\n        $this->validateTimeout($writingTimeout);\n        $this->writingTimeout = $writingTimeout;\n        $this->chunkSize = $chunkSize;\n    }\n\n    /**\n     * Connect (if necessary) and write to the socket\n     *\n     * @inheritDoc\n     *\n     * @throws \\UnexpectedValueException\n     * @throws \\RuntimeException\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->connectIfNotConnected();\n        $data = $this->generateDataStream($record);\n        $this->writeToSocket($data);\n    }\n\n    /**\n     * We will not close a PersistentSocket instance so it can be reused in other requests.\n     */\n    public function close(): void\n    {\n        if (!$this->isPersistent()) {\n            $this->closeSocket();\n        }\n    }\n\n    /**\n     * Close socket, if open\n     */\n    public function closeSocket(): void\n    {\n        if (\\is_resource($this->resource)) {\n            fclose($this->resource);\n            $this->resource = null;\n        }\n    }\n\n    /**\n     * Set socket connection to be persistent. It only has effect before the connection is initiated.\n     *\n     * @return $this\n     */\n    public function setPersistent(bool $persistent): self\n    {\n        $this->persistent = $persistent;\n\n        return $this;\n    }\n\n    /**\n     * Set connection timeout.  Only has effect before we connect.\n     *\n     * @see http://php.net/manual/en/function.fsockopen.php\n     * @return $this\n     */\n    public function setConnectionTimeout(float $seconds): self\n    {\n        $this->validateTimeout($seconds);\n        $this->connectionTimeout = $seconds;\n\n        return $this;\n    }\n\n    /**\n     * Set write timeout. Only has effect before we connect.\n     *\n     * @see http://php.net/manual/en/function.stream-set-timeout.php\n     * @return $this\n     */\n    public function setTimeout(float $seconds): self\n    {\n        $this->validateTimeout($seconds);\n        $this->timeout = $seconds;\n\n        return $this;\n    }\n\n    /**\n     * Set writing timeout. Only has effect during connection in the writing cycle.\n     *\n     * @param  float $seconds 0 for no timeout\n     * @return $this\n     */\n    public function setWritingTimeout(float $seconds): self\n    {\n        $this->validateTimeout($seconds);\n        $this->writingTimeout = $seconds;\n\n        return $this;\n    }\n\n    /**\n     * Set chunk size. Only has effect during connection in the writing cycle.\n     *\n     * @return $this\n     */\n    public function setChunkSize(int $bytes): self\n    {\n        $this->chunkSize = $bytes;\n\n        return $this;\n    }\n\n    /**\n     * Get current connection string\n     */\n    public function getConnectionString(): string\n    {\n        return $this->connectionString;\n    }\n\n    /**\n     * Get persistent setting\n     */\n    public function isPersistent(): bool\n    {\n        return $this->persistent;\n    }\n\n    /**\n     * Get current connection timeout setting\n     */\n    public function getConnectionTimeout(): float\n    {\n        return $this->connectionTimeout;\n    }\n\n    /**\n     * Get current in-transfer timeout\n     */\n    public function getTimeout(): float\n    {\n        return $this->timeout;\n    }\n\n    /**\n     * Get current local writing timeout\n     */\n    public function getWritingTimeout(): float\n    {\n        return $this->writingTimeout;\n    }\n\n    /**\n     * Get current chunk size\n     */\n    public function getChunkSize(): ?int\n    {\n        return $this->chunkSize;\n    }\n\n    /**\n     * Check to see if the socket is currently available.\n     *\n     * UDP might appear to be connected but might fail when writing.  See http://php.net/fsockopen for details.\n     */\n    public function isConnected(): bool\n    {\n        return \\is_resource($this->resource)\n            && !feof($this->resource);  // on TCP - other party can close connection.\n    }\n\n    /**\n     * Wrapper to allow mocking\n     *\n     * @return resource|false\n     */\n    protected function pfsockopen()\n    {\n        return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout);\n    }\n\n    /**\n     * Wrapper to allow mocking\n     *\n     * @return resource|false\n     */\n    protected function fsockopen()\n    {\n        return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout);\n    }\n\n    /**\n     * Wrapper to allow mocking\n     *\n     * @see http://php.net/manual/en/function.stream-set-timeout.php\n     */\n    protected function streamSetTimeout(): bool\n    {\n        $seconds = floor($this->timeout);\n        $microseconds = round(($this->timeout - $seconds) * 1e6);\n\n        if (!\\is_resource($this->resource)) {\n            throw new \\LogicException('streamSetTimeout called but $this->resource is not a resource');\n        }\n\n        return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds);\n    }\n\n    /**\n     * Wrapper to allow mocking\n     *\n     * @see http://php.net/manual/en/function.stream-set-chunk-size.php\n     *\n     * @return int|false\n     */\n    protected function streamSetChunkSize(): int|bool\n    {\n        if (!\\is_resource($this->resource)) {\n            throw new \\LogicException('streamSetChunkSize called but $this->resource is not a resource');\n        }\n\n        if (null === $this->chunkSize) {\n            throw new \\LogicException('streamSetChunkSize called but $this->chunkSize is not set');\n        }\n\n        return stream_set_chunk_size($this->resource, $this->chunkSize);\n    }\n\n    /**\n     * Wrapper to allow mocking\n     *\n     * @return int|false\n     */\n    protected function fwrite(string $data): int|bool\n    {\n        if (!\\is_resource($this->resource)) {\n            throw new \\LogicException('fwrite called but $this->resource is not a resource');\n        }\n\n        return @fwrite($this->resource, $data);\n    }\n\n    /**\n     * Wrapper to allow mocking\n     *\n     * @return mixed[]|bool\n     */\n    protected function streamGetMetadata(): array|bool\n    {\n        if (!\\is_resource($this->resource)) {\n            throw new \\LogicException('streamGetMetadata called but $this->resource is not a resource');\n        }\n\n        return stream_get_meta_data($this->resource);\n    }\n\n    private function validateTimeout(float $value): void\n    {\n        if ($value < 0) {\n            throw new \\InvalidArgumentException(\"Timeout must be 0 or a positive float (got $value)\");\n        }\n    }\n\n    private function connectIfNotConnected(): void\n    {\n        if ($this->isConnected()) {\n            return;\n        }\n        $this->connect();\n    }\n\n    protected function generateDataStream(LogRecord $record): string\n    {\n        return (string) $record->formatted;\n    }\n\n    /**\n     * @return resource|null\n     */\n    protected function getResource()\n    {\n        return $this->resource;\n    }\n\n    private function connect(): void\n    {\n        $this->createSocketResource();\n        $this->setSocketTimeout();\n        $this->setStreamChunkSize();\n    }\n\n    private function createSocketResource(): void\n    {\n        if ($this->isPersistent()) {\n            $resource = $this->pfsockopen();\n        } else {\n            $resource = $this->fsockopen();\n        }\n        if (\\is_bool($resource)) {\n            throw new \\UnexpectedValueException(\"Failed connecting to $this->connectionString ($this->errno: $this->errstr)\");\n        }\n        $this->resource = $resource;\n    }\n\n    private function setSocketTimeout(): void\n    {\n        if (!$this->streamSetTimeout()) {\n            throw new \\UnexpectedValueException(\"Failed setting timeout with stream_set_timeout()\");\n        }\n    }\n\n    private function setStreamChunkSize(): void\n    {\n        if (null !== $this->chunkSize && false === $this->streamSetChunkSize()) {\n            throw new \\UnexpectedValueException(\"Failed setting chunk size with stream_set_chunk_size()\");\n        }\n    }\n\n    private function writeToSocket(string $data): void\n    {\n        $length = \\strlen($data);\n        $sent = 0;\n        $this->lastSentBytes = $sent;\n        while ($this->isConnected() && $sent < $length) {\n            if (0 === $sent) {\n                $chunk = $this->fwrite($data);\n            } else {\n                $chunk = $this->fwrite(substr($data, $sent));\n            }\n            if ($chunk === false) {\n                throw new \\RuntimeException(\"Could not write to socket\");\n            }\n            $sent += $chunk;\n            $socketInfo = $this->streamGetMetadata();\n            if (\\is_array($socketInfo) && (bool) $socketInfo['timed_out']) {\n                throw new \\RuntimeException(\"Write timed-out\");\n            }\n\n            if ($this->writingIsTimedOut($sent)) {\n                throw new \\RuntimeException(\"Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)\");\n            }\n        }\n        if (!$this->isConnected() && $sent < $length) {\n            throw new \\RuntimeException(\"End-of-file reached, probably we got disconnected (sent $sent of $length)\");\n        }\n    }\n\n    private function writingIsTimedOut(int $sent): bool\n    {\n        // convert to ms\n        if (0.0 === $this->writingTimeout) {\n            return false;\n        }\n\n        if ($sent !== $this->lastSentBytes) {\n            $this->lastWritingAt = microtime(true);\n            $this->lastSentBytes = $sent;\n\n            return false;\n        } else {\n            usleep(100);\n        }\n\n        if ((microtime(true) - (float) $this->lastWritingAt) >= $this->writingTimeout) {\n            $this->closeSocket();\n\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/SqsHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Aws\\Sqs\\SqsClient;\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Writes to any sqs queue.\n *\n * @author Martijn van Calker <git@amvc.nl>\n */\nclass SqsHandler extends AbstractProcessingHandler\n{\n    /** 256 KB in bytes - maximum message size in SQS */\n    protected const MAX_MESSAGE_SIZE = 262144;\n    /** 100 KB in bytes - head message size for new error log */\n    protected const HEAD_MESSAGE_SIZE = 102400;\n\n    private SqsClient $client;\n    private string $queueUrl;\n\n    public function __construct(SqsClient $sqsClient, string $queueUrl, int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        parent::__construct($level, $bubble);\n\n        $this->client = $sqsClient;\n        $this->queueUrl = $queueUrl;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        if (!isset($record->formatted) || 'string' !== \\gettype($record->formatted)) {\n            throw new \\InvalidArgumentException('SqsHandler accepts only formatted records as a string' . Utils::getRecordMessageForException($record));\n        }\n\n        $messageBody = $record->formatted;\n        if (\\strlen($messageBody) >= static::MAX_MESSAGE_SIZE) {\n            $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE);\n        }\n\n        $this->client->sendMessage([\n            'QueueUrl' => $this->queueUrl,\n            'MessageBody' => $messageBody,\n        ]);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/StreamHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Stores to any stream resource\n *\n * Can be used to store into php://stderr, remote and local files, etc.\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass StreamHandler extends AbstractProcessingHandler\n{\n    protected const MAX_CHUNK_SIZE = 2147483647;\n    /** 10MB */\n    protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024;\n    protected int $streamChunkSize;\n    /** @var resource|null */\n    protected $stream;\n    protected string|null $url = null;\n    private string|null $errorMessage = null;\n    protected int|null $filePermission;\n    protected bool $useLocking;\n    protected string $fileOpenMode;\n    /** @var true|null */\n    private bool|null $dirCreated = null;\n    private bool $retrying = false;\n    private int|null $inodeUrl = null;\n\n    /**\n     * @param resource|string $stream         If a missing path can't be created, an UnexpectedValueException will be thrown on first write\n     * @param int|null        $filePermission Optional file permissions (default (0644) are only for owner read/write)\n     * @param bool            $useLocking     Try to lock log file before doing any writes\n     * @param string          $fileOpenMode   The fopen() mode used when opening a file, if $stream is a file path\n     *\n     * @throws \\InvalidArgumentException If stream is not a resource or string\n     */\n    public function __construct($stream, int|string|Level $level = Level::Debug, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false, string $fileOpenMode = 'a')\n    {\n        parent::__construct($level, $bubble);\n\n        if (($phpMemoryLimit = Utils::expandIniShorthandBytes(\\ini_get('memory_limit'))) !== false) {\n            if ($phpMemoryLimit > 0) {\n                // use max 10% of allowed memory for the chunk size, and at least 100KB\n                $this->streamChunkSize = min(static::MAX_CHUNK_SIZE, max((int) ($phpMemoryLimit / 10), 100 * 1024));\n            } else {\n                // memory is unlimited, set to the default 10MB\n                $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE;\n            }\n        } else {\n            // no memory limit information, set to the default 10MB\n            $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE;\n        }\n\n        if (\\is_resource($stream)) {\n            $this->stream = $stream;\n\n            stream_set_chunk_size($this->stream, $this->streamChunkSize);\n        } elseif (\\is_string($stream)) {\n            $this->url = Utils::canonicalizePath($stream);\n        } else {\n            throw new \\InvalidArgumentException('A stream must either be a resource or a string.');\n        }\n\n        $this->fileOpenMode = $fileOpenMode;\n        $this->filePermission = $filePermission;\n        $this->useLocking = $useLocking;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function reset(): void\n    {\n        parent::reset();\n\n        // auto-close on reset to make sure we periodically close the file in long running processes\n        // as long as they correctly call reset() between jobs\n        if ($this->url !== null && $this->url !== 'php://memory') {\n            $this->close();\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function close(): void\n    {\n        if (null !== $this->url && \\is_resource($this->stream)) {\n            fclose($this->stream);\n        }\n        $this->stream = null;\n        $this->dirCreated = null;\n    }\n\n    /**\n     * Return the currently active stream if it is open\n     *\n     * @return resource|null\n     */\n    public function getStream()\n    {\n        return $this->stream;\n    }\n\n    /**\n     * Return the stream URL if it was configured with a URL and not an active resource\n     */\n    public function getUrl(): ?string\n    {\n        return $this->url;\n    }\n\n    public function getStreamChunkSize(): int\n    {\n        return $this->streamChunkSize;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        if ($this->hasUrlInodeWasChanged()) {\n            $this->close();\n            $this->write($record);\n\n            return;\n        }\n\n        if (!\\is_resource($this->stream)) {\n            $url = $this->url;\n            if (null === $url || '' === $url) {\n                throw new \\LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().' . Utils::getRecordMessageForException($record));\n            }\n            $this->createDir($url);\n            $this->errorMessage = null;\n            set_error_handler($this->customErrorHandler(...));\n\n            try {\n                $stream = fopen($url, $this->fileOpenMode);\n                if ($this->filePermission !== null) {\n                    @chmod($url, $this->filePermission);\n                }\n            } finally {\n                restore_error_handler();\n            }\n            if (!\\is_resource($stream)) {\n                $this->stream = null;\n\n                throw new \\UnexpectedValueException(sprintf('The stream or file \"%s\" could not be opened in append mode: '.$this->errorMessage, $url) . Utils::getRecordMessageForException($record));\n            }\n            stream_set_chunk_size($stream, $this->streamChunkSize);\n            $this->stream = $stream;\n            $this->inodeUrl = $this->getInodeFromUrl();\n        }\n\n        $stream = $this->stream;\n        if ($this->useLocking) {\n            // ignoring errors here, there's not much we can do about them\n            flock($stream, LOCK_EX);\n        }\n\n        $this->errorMessage = null;\n        set_error_handler($this->customErrorHandler(...));\n        try {\n            $this->streamWrite($stream, $record);\n        } finally {\n            restore_error_handler();\n        }\n        if ($this->errorMessage !== null) {\n            $error = $this->errorMessage;\n            // close the resource if possible to reopen it, and retry the failed write\n            if (!$this->retrying && $this->url !== null && $this->url !== 'php://memory') {\n                $this->retrying = true;\n                $this->close();\n                $this->write($record);\n\n                return;\n            }\n\n            throw new \\UnexpectedValueException('Writing to the log file failed: '.$error . Utils::getRecordMessageForException($record));\n        }\n\n        $this->retrying = false;\n        if ($this->useLocking) {\n            flock($stream, LOCK_UN);\n        }\n    }\n\n    /**\n     * Write to stream\n     * @param resource $stream\n     */\n    protected function streamWrite($stream, LogRecord $record): void\n    {\n        fwrite($stream, (string) $record->formatted);\n    }\n\n    /**\n     * @return true\n     */\n    private function customErrorHandler(int $code, string $msg): bool\n    {\n        $this->errorMessage = preg_replace('{^(fopen|mkdir|fwrite)\\(.*?\\): }', '', $msg);\n\n        return true;\n    }\n\n    private function getDirFromStream(string $stream): ?string\n    {\n        $pos = strpos($stream, '://');\n        if ($pos === false) {\n            return \\dirname($stream);\n        }\n\n        if ('file://' === substr($stream, 0, 7)) {\n            return \\dirname(substr($stream, 7));\n        }\n\n        return null;\n    }\n\n    private function createDir(string $url): void\n    {\n        // Do not try to create dir if it has already been tried.\n        if (true === $this->dirCreated) {\n            return;\n        }\n\n        $dir = $this->getDirFromStream($url);\n        if (null !== $dir && !is_dir($dir)) {\n            $this->errorMessage = null;\n            set_error_handler(function (...$args) {\n                return $this->customErrorHandler(...$args);\n            });\n            $status = mkdir($dir, 0777, true);\n            restore_error_handler();\n            if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage, 'File exists') === false) {\n                throw new \\UnexpectedValueException(sprintf('There is no existing directory at \"%s\" and it could not be created: '.$this->errorMessage, $dir));\n            }\n        }\n        $this->dirCreated = true;\n    }\n\n    private function getInodeFromUrl(): ?int\n    {\n        if ($this->url === null || str_starts_with($this->url, 'php://')) {\n            return null;\n        }\n\n        $inode = @fileinode($this->url);\n\n        return $inode === false ? null : $inode;\n    }\n\n    private function hasUrlInodeWasChanged(): bool\n    {\n        if ($this->inodeUrl === null || $this->retrying || $this->inodeUrl === $this->getInodeFromUrl()) {\n            return false;\n        }\n\n        $this->retrying = true;\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/SymfonyMailerHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Closure;\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\nuse Monolog\\Utils;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\LineFormatter;\nuse Symfony\\Component\\Mailer\\MailerInterface;\nuse Symfony\\Component\\Mailer\\Transport\\TransportInterface;\nuse Symfony\\Component\\Mime\\Email;\n\n/**\n * SymfonyMailerHandler uses Symfony's Mailer component to send the emails\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass SymfonyMailerHandler extends MailHandler\n{\n    protected MailerInterface|TransportInterface $mailer;\n    /** @var Email|Closure(string, LogRecord[]): Email */\n    private Email|Closure $emailTemplate;\n\n    /**\n     * @phpstan-param Email|Closure(string, LogRecord[]): Email $email\n     *\n     * @param MailerInterface|TransportInterface $mailer The mailer to use\n     * @param Closure|Email                      $email  An email template, the subject/body will be replaced\n     */\n    public function __construct($mailer, Email|Closure $email, int|string|Level $level = Level::Error, bool $bubble = true)\n    {\n        parent::__construct($level, $bubble);\n\n        $this->mailer = $mailer;\n        $this->emailTemplate = $email;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    protected function send(string $content, array $records): void\n    {\n        $this->mailer->send($this->buildMessage($content, $records));\n    }\n\n    /**\n     * Gets the formatter for the Swift_Message subject.\n     *\n     * @param string|null $format The format of the subject\n     */\n    protected function getSubjectFormatter(?string $format): FormatterInterface\n    {\n        return new LineFormatter($format);\n    }\n\n    /**\n     * Creates instance of Email to be sent\n     *\n     * @param string      $content formatted email body to be sent\n     * @param LogRecord[] $records Log records that formed the content\n     */\n    protected function buildMessage(string $content, array $records): Email\n    {\n        $message = null;\n        if ($this->emailTemplate instanceof Email) {\n            $message = clone $this->emailTemplate;\n        } elseif (\\is_callable($this->emailTemplate)) {\n            $message = ($this->emailTemplate)($content, $records);\n        }\n\n        if (!$message instanceof Email) {\n            $record = reset($records);\n\n            throw new \\InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record instanceof LogRecord ? Utils::getRecordMessageForException($record) : ''));\n        }\n\n        if (\\count($records) > 0) {\n            $subjectFormatter = $this->getSubjectFormatter($message->getSubject());\n            $message->subject($subjectFormatter->format($this->getHighestRecord($records)));\n        }\n\n        if ($this->isHtmlBody($content)) {\n            if (null !== ($charset = $message->getHtmlCharset())) {\n                $message->html($content, $charset);\n            } else {\n                $message->html($content);\n            }\n        } else {\n            if (null !== ($charset = $message->getTextCharset())) {\n                $message->text($content, $charset);\n            } else {\n                $message->text($content);\n            }\n        }\n\n        return $message->date(new \\DateTimeImmutable());\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/SyslogHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * Logs to syslog service.\n *\n * usage example:\n *\n *   $log = new Logger('application');\n *   $syslog = new SyslogHandler('myfacility', 'local6');\n *   $formatter = new LineFormatter(\"%channel%.%level_name%: %message% %extra%\");\n *   $syslog->setFormatter($formatter);\n *   $log->pushHandler($syslog);\n *\n * @author Sven Paulus <sven@karlsruhe.org>\n */\nclass SyslogHandler extends AbstractSyslogHandler\n{\n    protected string $ident;\n    protected int $logopts;\n\n    /**\n     * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant\n     * @param int        $logopts  Option flags for the openlog() call, defaults to LOG_PID\n     */\n    public function __construct(string $ident, string|int $facility = LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true, int $logopts = LOG_PID)\n    {\n        parent::__construct($facility, $level, $bubble);\n\n        $this->ident = $ident;\n        $this->logopts = $logopts;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function close(): void\n    {\n        closelog();\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        openlog($this->ident, $this->logopts, $this->facility);\n        syslog($this->toSyslogPriority($record->level), (string) $record->formatted);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/SyslogUdp/UdpSocket.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler\\SyslogUdp;\n\nuse Monolog\\Utils;\nuse Socket;\n\nclass UdpSocket\n{\n    protected const DATAGRAM_MAX_LENGTH = 65023;\n\n    protected string $ip;\n    protected int $port;\n    protected ?Socket $socket = null;\n\n    public function __construct(string $ip, int $port = 514)\n    {\n        $this->ip = $ip;\n        $this->port = $port;\n    }\n\n    public function write(string $line, string $header = \"\"): void\n    {\n        $this->send($this->assembleMessage($line, $header));\n    }\n\n    public function close(): void\n    {\n        if ($this->socket instanceof Socket) {\n            socket_close($this->socket);\n            $this->socket = null;\n        }\n    }\n\n    protected function getSocket(): Socket\n    {\n        if (null !== $this->socket) {\n            return $this->socket;\n        }\n\n        $domain = AF_INET;\n        $protocol = SOL_UDP;\n        // Check if we are using unix sockets.\n        if ($this->port === 0) {\n            $domain = AF_UNIX;\n            $protocol = IPPROTO_IP;\n        }\n\n        $socket = socket_create($domain, SOCK_DGRAM, $protocol);\n        if ($socket instanceof Socket) {\n            return $this->socket = $socket;\n        }\n\n        throw new \\RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' could not be opened via socket_create');\n    }\n\n    protected function send(string $chunk): void\n    {\n        socket_sendto($this->getSocket(), $chunk, \\strlen($chunk), $flags = 0, $this->ip, $this->port);\n    }\n\n    protected function assembleMessage(string $line, string $header): string\n    {\n        $chunkSize = static::DATAGRAM_MAX_LENGTH - \\strlen($header);\n\n        return $header . Utils::substr($line, 0, $chunkSize);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/SyslogUdpHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse DateTimeInterface;\nuse Monolog\\Handler\\SyslogUdp\\UdpSocket;\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\nuse Monolog\\Utils;\n\n/**\n * A Handler for logging to a remote syslogd server.\n *\n * @author Jesper Skovgaard Nielsen <nulpunkt@gmail.com>\n * @author Dominik Kukacka <dominik.kukacka@gmail.com>\n */\nclass SyslogUdpHandler extends AbstractSyslogHandler\n{\n    const RFC3164 = 0;\n    const RFC5424 = 1;\n    const RFC5424e = 2;\n\n    /** @var array<self::RFC*, string> */\n    private array $dateFormats = [\n        self::RFC3164 => 'M d H:i:s',\n        self::RFC5424 => \\DateTime::RFC3339,\n        self::RFC5424e => \\DateTime::RFC3339_EXTENDED,\n    ];\n\n    protected UdpSocket $socket;\n    protected string $ident;\n    /** @var self::RFC* */\n    protected int $rfc;\n\n    /**\n     * @param  string                    $host     Either IP/hostname or a path to a unix socket (port must be 0 then)\n     * @param  int                       $port     Port number, or 0 if $host is a unix socket\n     * @param  string|int                $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant\n     * @param  bool                      $bubble   Whether the messages that are handled can bubble up the stack or not\n     * @param  string                    $ident    Program name or tag for each log message.\n     * @param  int                       $rfc      RFC to format the message for.\n     * @throws MissingExtensionException when there is no socket extension\n     *\n     * @phpstan-param self::RFC* $rfc\n     */\n    public function __construct(string $host, int $port = 514, string|int $facility = LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true, string $ident = 'php', int $rfc = self::RFC5424)\n    {\n        if (!\\extension_loaded('sockets')) {\n            throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler');\n        }\n\n        parent::__construct($facility, $level, $bubble);\n\n        $this->ident = $ident;\n        $this->rfc = $rfc;\n\n        $this->socket = new UdpSocket($host, $port);\n    }\n\n    protected function write(LogRecord $record): void\n    {\n        $lines = $this->splitMessageIntoLines($record->formatted);\n\n        $header = $this->makeCommonSyslogHeader($this->toSyslogPriority($record->level), $record->datetime);\n\n        foreach ($lines as $line) {\n            $this->socket->write($line, $header);\n        }\n    }\n\n    public function close(): void\n    {\n        $this->socket->close();\n    }\n\n    /**\n     * @param  string|string[] $message\n     * @return string[]\n     */\n    private function splitMessageIntoLines($message): array\n    {\n        if (\\is_array($message)) {\n            $message = implode(\"\\n\", $message);\n        }\n\n        $lines = preg_split('/$\\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY);\n        if (false === $lines) {\n            $pcreErrorCode = preg_last_error();\n\n            throw new \\RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . preg_last_error_msg());\n        }\n\n        return $lines;\n    }\n\n    /**\n     * Make common syslog header (see rfc5424 or rfc3164)\n     */\n    protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime): string\n    {\n        $priority = $severity + $this->facility;\n\n        $pid = getmypid();\n        if (false === $pid) {\n            $pid = '-';\n        }\n\n        $hostname = gethostname();\n        if (false === $hostname) {\n            $hostname = '-';\n        }\n\n        if ($this->rfc === self::RFC3164) {\n            // see https://github.com/phpstan/phpstan/issues/5348\n            // @phpstan-ignore-next-line\n            $dateNew = $datetime->setTimezone(new \\DateTimeZone('UTC'));\n            $date = $dateNew->format($this->dateFormats[$this->rfc]);\n\n            return \"<$priority>\" .\n                $date . \" \" .\n                $hostname . \" \" .\n                $this->ident . \"[\" . $pid . \"]: \";\n        }\n\n        $date = $datetime->format($this->dateFormats[$this->rfc]);\n\n        return \"<$priority>1 \" .\n            $date . \" \" .\n            $hostname . \" \" .\n            $this->ident . \" \" .\n            $pid . \" - - \";\n    }\n\n    /**\n     * Inject your own socket, mainly used for testing\n     *\n     * @return $this\n     */\n    public function setSocket(UdpSocket $socket): self\n    {\n        $this->socket = $socket;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/TelegramBotHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse RuntimeException;\nuse Monolog\\Level;\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Handler sends logs to Telegram using Telegram Bot API.\n *\n * How to use:\n *  1) Create a Telegram bot with https://telegram.me/BotFather;\n *  2) Create a Telegram channel or a group where logs will be recorded;\n *  3) Add the created bot from step 1 to the created channel/group from step 2.\n *\n * In order to create an instance of TelegramBotHandler use\n *  1. The Telegram bot API key from step 1\n *  2. The channel name with the `@` prefix if you created a public channel (e.g. `@my_public_channel`),\n *     or the channel ID with the `-100` prefix if you created a private channel (e.g. `-1001234567890`),\n *     or the group ID from step 2 (e.g. `-1234567890`).\n *\n * @link https://core.telegram.org/bots/api\n *\n * @author Mazur Alexandr <alexandrmazur96@gmail.com>\n */\nclass TelegramBotHandler extends AbstractProcessingHandler\n{\n    private const BOT_API = 'https://api.telegram.org/bot';\n\n    /**\n     * The available values of parseMode according to the Telegram api documentation\n     */\n    private const AVAILABLE_PARSE_MODES = [\n        'HTML',\n        'MarkdownV2',\n        'Markdown', // legacy mode without underline and strikethrough, use MarkdownV2 instead\n    ];\n\n    /**\n     * The maximum number of characters allowed in a message according to the Telegram api documentation\n     */\n    private const MAX_MESSAGE_LENGTH = 4096;\n\n    /**\n     * Telegram bot access token provided by BotFather.\n     * Create telegram bot with https://telegram.me/BotFather and use access token from it.\n     */\n    private string $apiKey;\n\n    /**\n     * Telegram channel name.\n     * Since to start with '@' symbol as prefix.\n     */\n    private string $channel;\n\n    /**\n     * The kind of formatting that is used for the message.\n     * See available options at https://core.telegram.org/bots/api#formatting-options\n     * or in AVAILABLE_PARSE_MODES\n     */\n    private string|null $parseMode;\n\n    /**\n     * Disables link previews for links in the message.\n     */\n    private bool|null $disableWebPagePreview;\n\n    /**\n     * Sends the message silently. Users will receive a notification with no sound.\n     */\n    private bool|null $disableNotification;\n\n    /**\n     * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages.\n     * False - truncates a message that is too long.\n     */\n    private bool $splitLongMessages;\n\n    /**\n     * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests).\n     */\n    private bool $delayBetweenMessages;\n\n    /**\n     * Telegram message thread id, unique identifier for the target message thread (topic) of the forum; for forum supergroups only\n     * See how to get the `message_thread_id` https://stackoverflow.com/a/75178418\n     */\n    private int|null $topic;\n\n    /**\n     * @param  string                    $apiKey               Telegram bot access token provided by BotFather\n     * @param  string                    $channel              Telegram channel name\n     * @param  bool                      $splitLongMessages    Split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages\n     * @param  bool                      $delayBetweenMessages Adds delay between sending a split message according to Telegram API\n     * @param  int                       $topic                Telegram message thread id, unique identifier for the target message thread (topic) of the forum\n     * @throws MissingExtensionException If the curl extension is missing\n     */\n    public function __construct(\n        string $apiKey,\n        string $channel,\n        $level = Level::Debug,\n        bool   $bubble = true,\n        ?string $parseMode = null,\n        ?bool   $disableWebPagePreview = null,\n        ?bool   $disableNotification = null,\n        bool   $splitLongMessages = false,\n        bool   $delayBetweenMessages = false,\n        ?int   $topic = null\n    ) {\n        if (!\\extension_loaded('curl')) {\n            throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler');\n        }\n\n        parent::__construct($level, $bubble);\n\n        $this->apiKey = $apiKey;\n        $this->channel = $channel;\n        $this->setParseMode($parseMode);\n        $this->disableWebPagePreview($disableWebPagePreview);\n        $this->disableNotification($disableNotification);\n        $this->splitLongMessages($splitLongMessages);\n        $this->delayBetweenMessages($delayBetweenMessages);\n        $this->setTopic($topic);\n    }\n\n    /**\n     * @return $this\n     */\n    public function setParseMode(string|null $parseMode = null): self\n    {\n        if ($parseMode !== null && !\\in_array($parseMode, self::AVAILABLE_PARSE_MODES, true)) {\n            throw new \\InvalidArgumentException('Unknown parseMode, use one of these: ' . implode(', ', self::AVAILABLE_PARSE_MODES) . '.');\n        }\n\n        $this->parseMode = $parseMode;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function disableWebPagePreview(bool|null $disableWebPagePreview = null): self\n    {\n        $this->disableWebPagePreview = $disableWebPagePreview;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function disableNotification(bool|null $disableNotification = null): self\n    {\n        $this->disableNotification = $disableNotification;\n\n        return $this;\n    }\n\n    /**\n     * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages.\n     * False - truncates a message that is too long.\n     *\n     * @return $this\n     */\n    public function splitLongMessages(bool $splitLongMessages = false): self\n    {\n        $this->splitLongMessages = $splitLongMessages;\n\n        return $this;\n    }\n\n    /**\n     * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests).\n     *\n     * @return $this\n     */\n    public function delayBetweenMessages(bool $delayBetweenMessages = false): self\n    {\n        $this->delayBetweenMessages = $delayBetweenMessages;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function setTopic(?int $topic = null): self\n    {\n        $this->topic = $topic;\n\n        return $this;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        $messages = [];\n\n        foreach ($records as $record) {\n            if (!$this->isHandling($record)) {\n                continue;\n            }\n\n            if (\\count($this->processors) > 0) {\n                $record = $this->processRecord($record);\n            }\n\n            $messages[] = $record;\n        }\n\n        if (\\count($messages) > 0) {\n            $this->send((string) $this->getFormatter()->formatBatch($messages));\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->send($record->formatted);\n    }\n\n    /**\n     * Send request to @link https://api.telegram.org/bot on SendMessage action.\n     */\n    protected function send(string $message): void\n    {\n        $messages = $this->handleMessageLength($message);\n\n        foreach ($messages as $key => $msg) {\n            if ($this->delayBetweenMessages && $key > 0) {\n                sleep(1);\n            }\n\n            $this->sendCurl($msg);\n        }\n    }\n\n    protected function sendCurl(string $message): void\n    {\n        if ('' === trim($message)) {\n            return;\n        }\n        \n        $ch = curl_init();\n        $url = self::BOT_API . $this->apiKey . '/SendMessage';\n        curl_setopt($ch, CURLOPT_URL, $url);\n        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);\n        $params = [\n            'text' => $message,\n            'chat_id' => $this->channel,\n            'parse_mode' => $this->parseMode,\n            'disable_web_page_preview' => $this->disableWebPagePreview,\n            'disable_notification' => $this->disableNotification,\n        ];\n        if ($this->topic !== null) {\n            $params['message_thread_id'] = $this->topic;\n        }\n        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));\n\n        $result = Curl\\Util::execute($ch);\n        if (!\\is_string($result)) {\n            throw new RuntimeException('Telegram API error. Description: No response');\n        }\n        $result = json_decode($result, true);\n\n        if ($result['ok'] === false) {\n            throw new RuntimeException('Telegram API error. Description: ' . $result['description']);\n        }\n    }\n\n    /**\n     * Handle a message that is too long: truncates or splits into several\n     * @return string[]\n     */\n    private function handleMessageLength(string $message): array\n    {\n        $truncatedMarker = ' (…truncated)';\n        if (!$this->splitLongMessages && \\strlen($message) > self::MAX_MESSAGE_LENGTH) {\n            return [Utils::substr($message, 0, self::MAX_MESSAGE_LENGTH - \\strlen($truncatedMarker)) . $truncatedMarker];\n        }\n\n        return str_split($message, self::MAX_MESSAGE_LENGTH);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/TestHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\LogRecord;\nuse NoDiscard;\n\n/**\n * Used for testing purposes.\n *\n * It records all records and gives you access to them for verification.\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n *\n * @method bool hasEmergency(array{message: string, context?: mixed[]}|string $recordAssertions)\n * @method bool hasAlert(array{message: string, context?: mixed[]}|string $recordAssertions)\n * @method bool hasCritical(array{message: string, context?: mixed[]}|string $recordAssertions)\n * @method bool hasError(array{message: string, context?: mixed[]}|string $recordAssertions)\n * @method bool hasWarning(array{message: string, context?: mixed[]}|string $recordAssertions)\n * @method bool hasNotice(array{message: string, context?: mixed[]}|string $recordAssertions)\n * @method bool hasInfo(array{message: string, context?: mixed[]}|string $recordAssertions)\n * @method bool hasDebug(array{message: string, context?: mixed[]}|string $recordAssertions)\n *\n * @method bool hasEmergencyRecords()\n * @method bool hasAlertRecords()\n * @method bool hasCriticalRecords()\n * @method bool hasErrorRecords()\n * @method bool hasWarningRecords()\n * @method bool hasNoticeRecords()\n * @method bool hasInfoRecords()\n * @method bool hasDebugRecords()\n *\n * @method bool hasEmergencyThatContains(string $message)\n * @method bool hasAlertThatContains(string $message)\n * @method bool hasCriticalThatContains(string $message)\n * @method bool hasErrorThatContains(string $message)\n * @method bool hasWarningThatContains(string $message)\n * @method bool hasNoticeThatContains(string $message)\n * @method bool hasInfoThatContains(string $message)\n * @method bool hasDebugThatContains(string $message)\n *\n * @method bool hasEmergencyThatMatches(string $regex)\n * @method bool hasAlertThatMatches(string $regex)\n * @method bool hasCriticalThatMatches(string $regex)\n * @method bool hasErrorThatMatches(string $regex)\n * @method bool hasWarningThatMatches(string $regex)\n * @method bool hasNoticeThatMatches(string $regex)\n * @method bool hasInfoThatMatches(string $regex)\n * @method bool hasDebugThatMatches(string $regex)\n *\n * @method bool hasEmergencyThatPasses(callable $predicate)\n * @method bool hasAlertThatPasses(callable $predicate)\n * @method bool hasCriticalThatPasses(callable $predicate)\n * @method bool hasErrorThatPasses(callable $predicate)\n * @method bool hasWarningThatPasses(callable $predicate)\n * @method bool hasNoticeThatPasses(callable $predicate)\n * @method bool hasInfoThatPasses(callable $predicate)\n * @method bool hasDebugThatPasses(callable $predicate)\n */\nclass TestHandler extends AbstractProcessingHandler\n{\n    /** @var LogRecord[] */\n    protected array $records = [];\n    /** @phpstan-var array<value-of<Level::VALUES>, LogRecord[]> */\n    protected array $recordsByLevel = [];\n    private bool $skipReset = false;\n\n    /**\n     * @return array<LogRecord>\n     */\n    #[NoDiscard]\n    public function getRecords(): array\n    {\n        return $this->records;\n    }\n\n    public function clear(): void\n    {\n        $this->records = [];\n        $this->recordsByLevel = [];\n    }\n\n    public function reset(): void\n    {\n        if (!$this->skipReset) {\n            $this->clear();\n        }\n    }\n\n    public function setSkipReset(bool $skipReset): void\n    {\n        $this->skipReset = $skipReset;\n    }\n\n    /**\n     * @param int|string|Level|LogLevel::* $level Logging level value or name\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    #[NoDiscard]\n    public function hasRecords(int|string|Level $level): bool\n    {\n        return isset($this->recordsByLevel[Logger::toMonologLevel($level)->value]);\n    }\n\n    /**\n     * @param string|array $recordAssertions Either a message string or an array containing message and optionally context keys that will be checked against all records\n     *\n     * @phpstan-param array{message: string, context?: mixed[]}|string $recordAssertions\n     */\n    #[NoDiscard]\n    public function hasRecord(string|array $recordAssertions, Level $level): bool\n    {\n        if (\\is_string($recordAssertions)) {\n            $recordAssertions = ['message' => $recordAssertions];\n        }\n\n        return $this->hasRecordThatPasses(function (LogRecord $rec) use ($recordAssertions) {\n            if ($rec->message !== $recordAssertions['message']) {\n                return false;\n            }\n            if (isset($recordAssertions['context']) && $rec->context !== $recordAssertions['context']) {\n                return false;\n            }\n\n            return true;\n        }, $level);\n    }\n\n    #[NoDiscard]\n    public function hasRecordThatContains(string $message, Level $level): bool\n    {\n        return $this->hasRecordThatPasses(fn (LogRecord $rec) => str_contains($rec->message, $message), $level);\n    }\n\n    #[NoDiscard]\n    public function hasRecordThatMatches(string $regex, Level $level): bool\n    {\n        return $this->hasRecordThatPasses(fn (LogRecord $rec) => preg_match($regex, $rec->message) > 0, $level);\n    }\n\n    /**\n     * @phpstan-param callable(LogRecord, int): mixed $predicate\n     */\n    #[NoDiscard]\n    public function hasRecordThatPasses(callable $predicate, Level $level): bool\n    {\n        $level = Logger::toMonologLevel($level);\n\n        if (!isset($this->recordsByLevel[$level->value])) {\n            return false;\n        }\n\n        foreach ($this->recordsByLevel[$level->value] as $i => $rec) {\n            if ((bool) $predicate($rec, $i)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->recordsByLevel[$record->level->value][] = $record;\n        $this->records[] = $record;\n    }\n\n    /**\n     * @param mixed[] $args\n     */\n    #[NoDiscard]\n    public function __call(string $method, array $args): bool\n    {\n        if ((bool) preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches)) {\n            $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];\n            $level = \\constant(Level::class.'::' . $matches[2]);\n            $callback = [$this, $genericMethod];\n            if (\\is_callable($callback)) {\n                $args[] = $level;\n\n                return \\call_user_func_array($callback, $args);\n            }\n        }\n\n        throw new \\BadMethodCallException('Call to undefined method ' . \\get_class($this) . '::' . $method . '()');\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/WebRequestRecognizerTrait.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\ntrait WebRequestRecognizerTrait\n{\n    /**\n     * Checks if PHP's serving a web request\n     */\n    protected function isWebRequest(): bool\n    {\n        return 'cli' !== \\PHP_SAPI && 'phpdbg' !== \\PHP_SAPI;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/WhatFailureGroupHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\LogRecord;\nuse Throwable;\n\n/**\n * Forwards records to multiple handlers suppressing failures of each handler\n * and continuing through to give every handler a chance to succeed.\n *\n * @author Craig D'Amelio <craig@damelio.ca>\n */\nclass WhatFailureGroupHandler extends GroupHandler\n{\n    /**\n     * @inheritDoc\n     */\n    public function handle(LogRecord $record): bool\n    {\n        if (\\count($this->processors) > 0) {\n            $record = $this->processRecord($record);\n        }\n\n        foreach ($this->handlers as $handler) {\n            try {\n                $handler->handle(clone $record);\n            } catch (Throwable) {\n                // What failure?\n            }\n        }\n\n        return false === $this->bubble;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function handleBatch(array $records): void\n    {\n        if (\\count($this->processors) > 0) {\n            $processed = [];\n            foreach ($records as $record) {\n                $processed[] = $this->processRecord($record);\n            }\n            $records = $processed;\n        }\n\n        foreach ($this->handlers as $handler) {\n            try {\n                $handler->handleBatch(array_map(fn ($record) => clone $record, $records));\n            } catch (Throwable) {\n                // What failure?\n            }\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function close(): void\n    {\n        foreach ($this->handlers as $handler) {\n            try {\n                $handler->close();\n            } catch (\\Throwable $e) {\n                // What failure?\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Handler/ZendMonitorHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FormatterInterface;\nuse Monolog\\Formatter\\NormalizerFormatter;\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\n/**\n * Handler sending logs to Zend Monitor\n *\n * @author  Christian Bergau <cbergau86@gmail.com>\n * @author  Jason Davis <happydude@jasondavis.net>\n */\nclass ZendMonitorHandler extends AbstractProcessingHandler\n{\n    /**\n     * @throws MissingExtensionException\n     */\n    public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true)\n    {\n        if (!\\function_exists('zend_monitor_custom_event')) {\n            throw new MissingExtensionException(\n                'You must have Zend Server installed with Zend Monitor enabled in order to use this handler'\n            );\n        }\n\n        parent::__construct($level, $bubble);\n    }\n\n    /**\n     * Translates Monolog log levels to ZendMonitor levels.\n     */\n    protected function toZendMonitorLevel(Level $level): int\n    {\n        return match ($level) {\n            Level::Debug     => \\ZEND_MONITOR_EVENT_SEVERITY_INFO,\n            Level::Info      => \\ZEND_MONITOR_EVENT_SEVERITY_INFO,\n            Level::Notice    => \\ZEND_MONITOR_EVENT_SEVERITY_INFO,\n            Level::Warning   => \\ZEND_MONITOR_EVENT_SEVERITY_WARNING,\n            Level::Error     => \\ZEND_MONITOR_EVENT_SEVERITY_ERROR,\n            Level::Critical  => \\ZEND_MONITOR_EVENT_SEVERITY_ERROR,\n            Level::Alert     => \\ZEND_MONITOR_EVENT_SEVERITY_ERROR,\n            Level::Emergency => \\ZEND_MONITOR_EVENT_SEVERITY_ERROR,\n        };\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        $this->writeZendMonitorCustomEvent(\n            $record->level->getName(),\n            $record->message,\n            $record->formatted,\n            $this->toZendMonitorLevel($record->level)\n        );\n    }\n\n    /**\n     * Write to Zend Monitor Events\n     * @param string       $type      Text displayed in \"Class Name (custom)\" field\n     * @param string       $message   Text displayed in \"Error String\"\n     * @param array<mixed> $formatted Displayed in Custom Variables tab\n     * @param int          $severity  Set the event severity level (-1,0,1)\n     */\n    protected function writeZendMonitorCustomEvent(string $type, string $message, array $formatted, int $severity): void\n    {\n        zend_monitor_custom_event($type, $message, $formatted, $severity);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function getDefaultFormatter(): FormatterInterface\n    {\n        return new NormalizerFormatter();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/JsonSerializableDateTimeImmutable.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse DateTimeZone;\n\n/**\n * Overrides default json encoding of date time objects\n *\n * @author Menno Holtkamp\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass JsonSerializableDateTimeImmutable extends \\DateTimeImmutable implements \\JsonSerializable\n{\n    private bool $useMicroseconds;\n\n    public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null)\n    {\n        $this->useMicroseconds = $useMicroseconds;\n\n        // if you like to use a custom time to pass to Logger::addRecord directly,\n        // call modify() or setTimestamp() on this instance to change the date after creating it\n        parent::__construct('now', $timezone);\n    }\n\n    public function jsonSerialize(): string\n    {\n        if ($this->useMicroseconds) {\n            return $this->format('Y-m-d\\TH:i:s.uP');\n        }\n\n        return $this->format('Y-m-d\\TH:i:sP');\n    }\n\n    public function __toString(): string\n    {\n        return $this->jsonSerialize();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Level.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse Psr\\Log\\LogLevel;\n\n/**\n * Represents the log levels\n *\n * Monolog supports the logging levels described by RFC 5424 {@see https://datatracker.ietf.org/doc/html/rfc5424}\n * but due to BC the severity values used internally are not 0-7.\n *\n * To get the level name/value out of a Level there are several options:\n *\n * - Use ->getName() to get the standard Monolog name which is full uppercased (e.g. \"DEBUG\")\n * - Use ->toPsrLogLevel() to get the standard PSR-3 name which is full lowercased (e.g. \"debug\")\n * - Use ->toRFC5424Level() to get the standard RFC 5424 value (e.g. 7 for debug, 0 for emergency)\n * - Use ->name to get the enum case's name which is capitalized (e.g. \"Debug\")\n *\n * To get the internal value for filtering, if the includes/isLowerThan/isHigherThan methods are\n * not enough, you can use ->value to get the enum case's integer value.\n */\nenum Level: int\n{\n    /**\n     * Detailed debug information\n     */\n    case Debug = 100;\n\n    /**\n     * Interesting events\n     *\n     * Examples: User logs in, SQL logs.\n     */\n    case Info = 200;\n\n    /**\n     * Uncommon events\n     */\n    case Notice = 250;\n\n    /**\n     * Exceptional occurrences that are not errors\n     *\n     * Examples: Use of deprecated APIs, poor use of an API,\n     * undesirable things that are not necessarily wrong.\n     */\n    case Warning = 300;\n\n    /**\n     * Runtime errors\n     */\n    case Error = 400;\n\n    /**\n     * Critical conditions\n     *\n     * Example: Application component unavailable, unexpected exception.\n     */\n    case Critical = 500;\n\n    /**\n     * Action must be taken immediately\n     *\n     * Example: Entire website down, database unavailable, etc.\n     * This should trigger the SMS alerts and wake you up.\n     */\n    case Alert = 550;\n\n    /**\n     * Urgent alert.\n     */\n    case Emergency = 600;\n\n    /**\n     * @param  value-of<self::NAMES>|LogLevel::*|'Debug'|'Info'|'Notice'|'Warning'|'Error'|'Critical'|'Alert'|'Emergency' $name\n     * @return static\n     */\n    public static function fromName(string $name): self\n    {\n        return match (strtolower($name)) {\n            'debug' => self::Debug,\n            'info' => self::Info,\n            'notice' => self::Notice,\n            'warning' => self::Warning,\n            'error' => self::Error,\n            'critical' => self::Critical,\n            'alert' => self::Alert,\n            'emergency' => self::Emergency,\n        };\n    }\n\n    /**\n     * @param  value-of<self::VALUES> $value\n     * @return static\n     */\n    public static function fromValue(int $value): self\n    {\n        return self::from($value);\n    }\n\n    /**\n     * Returns true if the passed $level is higher or equal to $this\n     */\n    public function includes(Level $level): bool\n    {\n        return $this->value <= $level->value;\n    }\n\n    public function isHigherThan(Level $level): bool\n    {\n        return $this->value > $level->value;\n    }\n\n    public function isLowerThan(Level $level): bool\n    {\n        return $this->value < $level->value;\n    }\n\n    /**\n     * Returns the monolog standardized all-capitals name of the level\n     *\n     * Use this instead of $level->name which returns the enum case name (e.g. Debug vs DEBUG if you use getName())\n     *\n     * @return value-of<self::NAMES>\n     */\n    public function getName(): string\n    {\n        return match ($this) {\n            self::Debug => 'DEBUG',\n            self::Info => 'INFO',\n            self::Notice => 'NOTICE',\n            self::Warning => 'WARNING',\n            self::Error => 'ERROR',\n            self::Critical => 'CRITICAL',\n            self::Alert => 'ALERT',\n            self::Emergency => 'EMERGENCY',\n        };\n    }\n\n    /**\n     * Returns the PSR-3 level matching this instance\n     *\n     * @phpstan-return \\Psr\\Log\\LogLevel::*\n     */\n    public function toPsrLogLevel(): string\n    {\n        return match ($this) {\n            self::Debug => LogLevel::DEBUG,\n            self::Info => LogLevel::INFO,\n            self::Notice => LogLevel::NOTICE,\n            self::Warning => LogLevel::WARNING,\n            self::Error => LogLevel::ERROR,\n            self::Critical => LogLevel::CRITICAL,\n            self::Alert => LogLevel::ALERT,\n            self::Emergency => LogLevel::EMERGENCY,\n        };\n    }\n\n    /**\n     * Returns the RFC 5424 level matching this instance\n     *\n     * @phpstan-return int<0, 7>\n     */\n    public function toRFC5424Level(): int\n    {\n        return match ($this) {\n            self::Debug => 7,\n            self::Info => 6,\n            self::Notice => 5,\n            self::Warning => 4,\n            self::Error => 3,\n            self::Critical => 2,\n            self::Alert => 1,\n            self::Emergency => 0,\n        };\n    }\n\n    public const VALUES = [\n        100,\n        200,\n        250,\n        300,\n        400,\n        500,\n        550,\n        600,\n    ];\n\n    public const NAMES = [\n        'DEBUG',\n        'INFO',\n        'NOTICE',\n        'WARNING',\n        'ERROR',\n        'CRITICAL',\n        'ALERT',\n        'EMERGENCY',\n    ];\n}\n"
  },
  {
    "path": "src/Monolog/LogRecord.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse ArrayAccess;\n\n/**\n * Monolog log record\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n * @template-implements ArrayAccess<'message'|'level'|'context'|'level_name'|'channel'|'datetime'|'extra'|'formatted', int|string|\\DateTimeImmutable|array<mixed>>\n */\nclass LogRecord implements ArrayAccess\n{\n    private const MODIFIABLE_FIELDS = [\n        'extra' => true,\n        'formatted' => true,\n    ];\n\n    public function __construct(\n        public readonly \\DateTimeImmutable $datetime,\n        public readonly string $channel,\n        public readonly Level $level,\n        public readonly string $message,\n        /** @var array<mixed> */\n        public readonly array $context = [],\n        /** @var array<mixed> */\n        public array $extra = [],\n        public mixed $formatted = null,\n    ) {\n    }\n\n    public function offsetSet(mixed $offset, mixed $value): void\n    {\n        if ($offset === 'extra') {\n            if (!\\is_array($value)) {\n                throw new \\InvalidArgumentException('extra must be an array');\n            }\n\n            $this->extra = $value;\n\n            return;\n        }\n\n        if ($offset === 'formatted') {\n            $this->formatted = $value;\n\n            return;\n        }\n\n        throw new \\LogicException('Unsupported operation: setting '.$offset);\n    }\n\n    public function offsetExists(mixed $offset): bool\n    {\n        if ($offset === 'level_name') {\n            return true;\n        }\n\n        return isset($this->{$offset});\n    }\n\n    public function offsetUnset(mixed $offset): void\n    {\n        throw new \\LogicException('Unsupported operation');\n    }\n\n    public function &offsetGet(mixed $offset): mixed\n    {\n        // handle special cases for the level enum\n        if ($offset === 'level_name') {\n            // avoid returning readonly props by ref as this is illegal\n            $copy = $this->level->getName();\n\n            return $copy;\n        }\n        if ($offset === 'level') {\n            // avoid returning readonly props by ref as this is illegal\n            $copy = $this->level->value;\n\n            return $copy;\n        }\n\n        if (isset(self::MODIFIABLE_FIELDS[$offset])) {\n            return $this->{$offset};\n        }\n\n        // avoid returning readonly props by ref as this is illegal\n        $copy = $this->{$offset};\n\n        return $copy;\n    }\n\n    /**\n     * @phpstan-return array{message: string, context: mixed[], level: value-of<Level::VALUES>, level_name: value-of<Level::NAMES>, channel: string, datetime: \\DateTimeImmutable, extra: mixed[]}\n     */\n    public function toArray(): array\n    {\n        return [\n            'message' => $this->message,\n            'context' => $this->context,\n            'level' => $this->level->value,\n            'level_name' => $this->level->getName(),\n            'channel' => $this->channel,\n            'datetime' => $this->datetime,\n            'extra' => $this->extra,\n        ];\n    }\n\n    public function with(mixed ...$args): self\n    {\n        foreach (['message', 'context', 'level', 'channel', 'datetime', 'extra'] as $prop) {\n            $args[$prop] ??= $this->{$prop};\n        }\n\n        return new self(...$args);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Logger.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse Closure;\nuse DateTimeZone;\nuse Fiber;\nuse Monolog\\Handler\\HandlerInterface;\nuse Monolog\\Processor\\ProcessorInterface;\nuse Psr\\Log\\LoggerInterface;\nuse Psr\\Log\\InvalidArgumentException;\nuse Psr\\Log\\LogLevel;\nuse Throwable;\nuse Stringable;\nuse WeakMap;\n\n/**\n * Monolog log channel\n *\n * It contains a stack of Handlers and a stack of Processors,\n * and uses them to store records that are added to it.\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n * @final\n */\nclass Logger implements LoggerInterface, ResettableInterface\n{\n    /**\n     * Detailed debug information\n     *\n     * @deprecated Use \\Monolog\\Level::Debug\n     */\n    public const DEBUG = 100;\n\n    /**\n     * Interesting events\n     *\n     * Examples: User logs in, SQL logs.\n     *\n     * @deprecated Use \\Monolog\\Level::Info\n     */\n    public const INFO = 200;\n\n    /**\n     * Uncommon events\n     *\n     * @deprecated Use \\Monolog\\Level::Notice\n     */\n    public const NOTICE = 250;\n\n    /**\n     * Exceptional occurrences that are not errors\n     *\n     * Examples: Use of deprecated APIs, poor use of an API,\n     * undesirable things that are not necessarily wrong.\n     *\n     * @deprecated Use \\Monolog\\Level::Warning\n     */\n    public const WARNING = 300;\n\n    /**\n     * Runtime errors\n     *\n     * @deprecated Use \\Monolog\\Level::Error\n     */\n    public const ERROR = 400;\n\n    /**\n     * Critical conditions\n     *\n     * Example: Application component unavailable, unexpected exception.\n     *\n     * @deprecated Use \\Monolog\\Level::Critical\n     */\n    public const CRITICAL = 500;\n\n    /**\n     * Action must be taken immediately\n     *\n     * Example: Entire website down, database unavailable, etc.\n     * This should trigger the SMS alerts and wake you up.\n     *\n     * @deprecated Use \\Monolog\\Level::Alert\n     */\n    public const ALERT = 550;\n\n    /**\n     * Urgent alert.\n     *\n     * @deprecated Use \\Monolog\\Level::Emergency\n     */\n    public const EMERGENCY = 600;\n\n    /**\n     * Monolog API version\n     *\n     * This is only bumped when API breaks are done and should\n     * follow the major version of the library\n     */\n    public const API = 3;\n\n    /**\n     * Mapping between levels numbers defined in RFC 5424 and Monolog ones\n     *\n     * @phpstan-var array<int, Level> $rfc_5424_levels\n     */\n    private const RFC_5424_LEVELS = [\n        7 => Level::Debug,\n        6 => Level::Info,\n        5 => Level::Notice,\n        4 => Level::Warning,\n        3 => Level::Error,\n        2 => Level::Critical,\n        1 => Level::Alert,\n        0 => Level::Emergency,\n    ];\n\n    protected string $name;\n\n    /**\n     * The handler stack\n     *\n     * @var list<HandlerInterface>\n     */\n    protected array $handlers;\n\n    /**\n     * Processors that will process all log records\n     *\n     * To process records of a single handler instead, add the processor on that specific handler\n     *\n     * @var array<(callable(LogRecord): LogRecord)|ProcessorInterface>\n     */\n    protected array $processors;\n\n    protected bool $microsecondTimestamps = true;\n\n    protected DateTimeZone $timezone;\n\n    protected Closure|null $exceptionHandler = null;\n\n    /**\n     * Keeps track of depth to prevent infinite logging loops\n     */\n    private int $logDepth = 0;\n\n    /**\n     * @var WeakMap<Fiber<mixed, mixed, mixed, mixed>, int> Keeps track of depth inside fibers to prevent infinite logging loops\n     */\n    private WeakMap $fiberLogDepth;\n\n    /**\n     * Whether to detect infinite logging loops\n     * This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this\n     */\n    private bool $detectCycles = true;\n\n    /**\n     * @param string             $name       The logging channel, a simple descriptive name that is attached to all log records\n     * @param list<HandlerInterface> $handlers   Optional stack of handlers, the first one in the array is called first, etc.\n     * @param callable[]         $processors Optional array of processors\n     * @param DateTimeZone|null  $timezone   Optional timezone, if not provided date_default_timezone_get() will be used\n     *\n     * @phpstan-param array<(callable(LogRecord): LogRecord)|ProcessorInterface> $processors\n     */\n    public function __construct(string $name, array $handlers = [], array $processors = [], DateTimeZone|null $timezone = null)\n    {\n        $this->name = $name;\n        $this->setHandlers($handlers);\n        $this->processors = $processors;\n        $this->timezone = $timezone ?? new DateTimeZone(date_default_timezone_get());\n        $this->fiberLogDepth = new \\WeakMap();\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    /**\n     * Return a new cloned instance with the name changed\n     *\n     * @return static\n     */\n    public function withName(string $name): self\n    {\n        $new = clone $this;\n        $new->name = $name;\n\n        return $new;\n    }\n\n    /**\n     * Pushes a handler on to the stack.\n     *\n     * @return $this\n     */\n    public function pushHandler(HandlerInterface $handler): self\n    {\n        array_unshift($this->handlers, $handler);\n\n        return $this;\n    }\n\n    /**\n     * Pops a handler from the stack\n     *\n     * @throws \\LogicException If empty handler stack\n     */\n    public function popHandler(): HandlerInterface\n    {\n        if (0 === \\count($this->handlers)) {\n            throw new \\LogicException('You tried to pop from an empty handler stack.');\n        }\n\n        return array_shift($this->handlers);\n    }\n\n    /**\n     * Set handlers, replacing all existing ones.\n     *\n     * If a map is passed, keys will be ignored.\n     *\n     * @param  list<HandlerInterface> $handlers\n     * @return $this\n     */\n    public function setHandlers(array $handlers): self\n    {\n        $this->handlers = [];\n        foreach (array_reverse($handlers) as $handler) {\n            $this->pushHandler($handler);\n        }\n\n        return $this;\n    }\n\n    /**\n     * @return list<HandlerInterface>\n     */\n    public function getHandlers(): array\n    {\n        return $this->handlers;\n    }\n\n    /**\n     * Adds a processor on to the stack.\n     *\n     * @phpstan-param ProcessorInterface|(callable(LogRecord): LogRecord) $callback\n     * @return $this\n     */\n    public function pushProcessor(ProcessorInterface|callable $callback): self\n    {\n        array_unshift($this->processors, $callback);\n\n        return $this;\n    }\n\n    /**\n     * Removes the processor on top of the stack and returns it.\n     *\n     * @phpstan-return ProcessorInterface|(callable(LogRecord): LogRecord)\n     * @throws \\LogicException If empty processor stack\n     */\n    public function popProcessor(): callable\n    {\n        if (0 === \\count($this->processors)) {\n            throw new \\LogicException('You tried to pop from an empty processor stack.');\n        }\n\n        return array_shift($this->processors);\n    }\n\n    /**\n     * @return callable[]\n     * @phpstan-return array<ProcessorInterface|(callable(LogRecord): LogRecord)>\n     */\n    public function getProcessors(): array\n    {\n        return $this->processors;\n    }\n\n    /**\n     * Control the use of microsecond resolution timestamps in the 'datetime'\n     * member of new records.\n     *\n     * As of PHP7.1 microseconds are always included by the engine, so\n     * there is no performance penalty and Monolog 2 enabled microseconds\n     * by default. This function lets you disable them though in case you want\n     * to suppress microseconds from the output.\n     *\n     * @param  bool  $micro True to use microtime() to create timestamps\n     * @return $this\n     */\n    public function useMicrosecondTimestamps(bool $micro): self\n    {\n        $this->microsecondTimestamps = $micro;\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function useLoggingLoopDetection(bool $detectCycles): self\n    {\n        $this->detectCycles = $detectCycles;\n\n        return $this;\n    }\n\n    /**\n     * Adds a log record.\n     *\n     * @param  int                    $level    The logging level (a Monolog or RFC 5424 level)\n     * @param  string                 $message  The log message\n     * @param  mixed[]                $context  The log context\n     * @param  JsonSerializableDateTimeImmutable|null $datetime Optional log date to log into the past or future\n     *\n     * @return bool                   Whether the record has been processed\n     *\n     * @phpstan-param value-of<Level::VALUES>|Level $level\n     */\n    public function addRecord(int|Level $level, string $message, array $context = [], JsonSerializableDateTimeImmutable|null $datetime = null): bool\n    {\n        if (\\is_int($level) && isset(self::RFC_5424_LEVELS[$level])) {\n            $level = self::RFC_5424_LEVELS[$level];\n        }\n\n        if ($this->detectCycles) {\n            if (null !== ($fiber = Fiber::getCurrent())) {\n                $logDepth = $this->fiberLogDepth[$fiber] = ($this->fiberLogDepth[$fiber] ?? 0) + 1;\n            } else {\n                $logDepth = ++$this->logDepth;\n            }\n        } else {\n            $logDepth = 0;\n        }\n\n        if ($logDepth === 3) {\n            $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.');\n\n            return false;\n        } elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above\n            return false;\n        }\n\n        try {\n            $recordInitialized = \\count($this->processors) === 0;\n\n            $record = new LogRecord(\n                datetime: $datetime ?? new JsonSerializableDateTimeImmutable($this->microsecondTimestamps, $this->timezone),\n                channel: $this->name,\n                level: self::toMonologLevel($level),\n                message: $message,\n                context: $context,\n                extra: [],\n            );\n            $handled = false;\n\n            foreach ($this->handlers as $handler) {\n                if (false === $recordInitialized) {\n                    // skip initializing the record as long as no handler is going to handle it\n                    if (!$handler->isHandling($record)) {\n                        continue;\n                    }\n\n                    try {\n                        foreach ($this->processors as $processor) {\n                            $record = $processor($record);\n                        }\n                        $recordInitialized = true;\n                    } catch (Throwable $e) {\n                        $this->handleException($e, $record);\n\n                        return true;\n                    }\n                }\n\n                // once the record is initialized, send it to all handlers as long as the bubbling chain is not interrupted\n                try {\n                    $handled = true;\n                    if (true === $handler->handle(clone $record)) {\n                        break;\n                    }\n                } catch (Throwable $e) {\n                    $this->handleException($e, $record);\n\n                    return true;\n                }\n            }\n\n            return $handled;\n        } finally {\n            if ($this->detectCycles) {\n                if (isset($fiber)) {\n                    $this->fiberLogDepth[$fiber]--;\n                } else {\n                    $this->logDepth--;\n                }\n            }\n        }\n    }\n\n    /**\n     * Ends a log cycle and frees all resources used by handlers.\n     *\n     * Closing a Handler means flushing all buffers and freeing any open resources/handles.\n     * Handlers that have been closed should be able to accept log records again and re-open\n     * themselves on demand, but this may not always be possible depending on implementation.\n     *\n     * This is useful at the end of a request and will be called automatically on every handler\n     * when they get destructed.\n     */\n    public function close(): void\n    {\n        foreach ($this->handlers as $handler) {\n            $handler->close();\n        }\n    }\n\n    /**\n     * Ends a log cycle and resets all handlers and processors to their initial state.\n     *\n     * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal\n     * state, and getting it back to a state in which it can receive log records again.\n     *\n     * This is useful in case you want to avoid logs leaking between two requests or jobs when you\n     * have a long running process like a worker or an application server serving multiple requests\n     * in one process.\n     */\n    public function reset(): void\n    {\n        foreach ($this->handlers as $handler) {\n            if ($handler instanceof ResettableInterface) {\n                $handler->reset();\n            }\n        }\n\n        foreach ($this->processors as $processor) {\n            if ($processor instanceof ResettableInterface) {\n                $processor->reset();\n            }\n        }\n    }\n\n    /**\n     * Gets the name of the logging level as a string.\n     *\n     * This still returns a string instead of a Level for BC, but new code should not rely on this method.\n     *\n     * @throws \\Psr\\Log\\InvalidArgumentException If level is not defined\n     *\n     * @phpstan-param  value-of<Level::VALUES>|Level $level\n     * @phpstan-return value-of<Level::NAMES>\n     *\n     * @deprecated Since 3.0, use {@see toMonologLevel} or {@see \\Monolog\\Level->getName()} instead\n     */\n    public static function getLevelName(int|Level $level): string\n    {\n        return self::toMonologLevel($level)->getName();\n    }\n\n    /**\n     * Converts PSR-3 levels to Monolog ones if necessary\n     *\n     * @param  int|string|Level|LogLevel::*      $level Level number (monolog) or name (PSR-3)\n     * @throws \\Psr\\Log\\InvalidArgumentException If level is not defined\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public static function toMonologLevel(string|int|Level $level): Level\n    {\n        if ($level instanceof Level) {\n            return $level;\n        }\n\n        if (\\is_string($level)) {\n            if (is_numeric($level)) {\n                $levelEnum = Level::tryFrom((int) $level);\n                if ($levelEnum === null) {\n                    throw new InvalidArgumentException('Level \"'.$level.'\" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES));\n                }\n\n                return $levelEnum;\n            }\n\n            // Contains first char of all log levels and avoids using strtoupper() which may have\n            // strange results depending on locale (for example, \"i\" will become \"İ\" in Turkish locale)\n            $upper = strtr(substr($level, 0, 1), 'dinweca', 'DINWECA') . strtolower(substr($level, 1));\n            if (\\defined(Level::class.'::'.$upper)) {\n                return \\constant(Level::class . '::' . $upper);\n            }\n\n            throw new InvalidArgumentException('Level \"'.$level.'\" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES));\n        }\n\n        $levelEnum = Level::tryFrom($level);\n        if ($levelEnum === null) {\n            throw new InvalidArgumentException('Level \"'.var_export($level, true).'\" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES));\n        }\n\n        return $levelEnum;\n    }\n\n    /**\n     * Checks whether the Logger has a handler that listens on the given level\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public function isHandling(int|string|Level $level): bool\n    {\n        $record = new LogRecord(\n            datetime: new JsonSerializableDateTimeImmutable($this->microsecondTimestamps, $this->timezone),\n            channel: $this->name,\n            message: '',\n            level: self::toMonologLevel($level),\n        );\n\n        foreach ($this->handlers as $handler) {\n            if ($handler->isHandling($record)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Set a custom exception handler that will be called if adding a new record fails\n     *\n     * The Closure will receive an exception object and the record that failed to be logged\n     *\n     * @return $this\n     */\n    public function setExceptionHandler(Closure|null $callback): self\n    {\n        $this->exceptionHandler = $callback;\n\n        return $this;\n    }\n\n    public function getExceptionHandler(): Closure|null\n    {\n        return $this->exceptionHandler;\n    }\n\n    /**\n     * Adds a log record at an arbitrary level.\n     *\n     * This method allows for compatibility with common interfaces.\n     *\n     * @param mixed             $level   The log level (a Monolog, PSR-3 or RFC 5424 level)\n     * @param string|Stringable $message The log message\n     * @param mixed[]           $context The log context\n     *\n     * @phpstan-param Level|LogLevel::* $level\n     */\n    public function log($level, string|\\Stringable $message, array $context = []): void\n    {\n        if (!$level instanceof Level) {\n            if (!\\is_string($level) && !\\is_int($level)) {\n                throw new \\InvalidArgumentException('$level is expected to be a string, int or '.Level::class.' instance');\n            }\n\n            if (isset(self::RFC_5424_LEVELS[$level])) {\n                $level = self::RFC_5424_LEVELS[$level];\n            }\n\n            $level = static::toMonologLevel($level);\n        }\n\n        $this->addRecord($level, (string) $message, $context);\n    }\n\n    /**\n     * Adds a log record at the DEBUG level.\n     *\n     * This method allows for compatibility with common interfaces.\n     *\n     * @param string|Stringable $message The log message\n     * @param mixed[]           $context The log context\n     */\n    public function debug(string|\\Stringable $message, array $context = []): void\n    {\n        $this->addRecord(Level::Debug, (string) $message, $context);\n    }\n\n    /**\n     * Adds a log record at the INFO level.\n     *\n     * This method allows for compatibility with common interfaces.\n     *\n     * @param string|Stringable $message The log message\n     * @param mixed[]           $context The log context\n     */\n    public function info(string|\\Stringable $message, array $context = []): void\n    {\n        $this->addRecord(Level::Info, (string) $message, $context);\n    }\n\n    /**\n     * Adds a log record at the NOTICE level.\n     *\n     * This method allows for compatibility with common interfaces.\n     *\n     * @param string|Stringable $message The log message\n     * @param mixed[]           $context The log context\n     */\n    public function notice(string|\\Stringable $message, array $context = []): void\n    {\n        $this->addRecord(Level::Notice, (string) $message, $context);\n    }\n\n    /**\n     * Adds a log record at the WARNING level.\n     *\n     * This method allows for compatibility with common interfaces.\n     *\n     * @param string|Stringable $message The log message\n     * @param mixed[]           $context The log context\n     */\n    public function warning(string|\\Stringable $message, array $context = []): void\n    {\n        $this->addRecord(Level::Warning, (string) $message, $context);\n    }\n\n    /**\n     * Adds a log record at the ERROR level.\n     *\n     * This method allows for compatibility with common interfaces.\n     *\n     * @param string|Stringable $message The log message\n     * @param mixed[]           $context The log context\n     */\n    public function error(string|\\Stringable $message, array $context = []): void\n    {\n        $this->addRecord(Level::Error, (string) $message, $context);\n    }\n\n    /**\n     * Adds a log record at the CRITICAL level.\n     *\n     * This method allows for compatibility with common interfaces.\n     *\n     * @param string|Stringable $message The log message\n     * @param mixed[]           $context The log context\n     */\n    public function critical(string|\\Stringable $message, array $context = []): void\n    {\n        $this->addRecord(Level::Critical, (string) $message, $context);\n    }\n\n    /**\n     * Adds a log record at the ALERT level.\n     *\n     * This method allows for compatibility with common interfaces.\n     *\n     * @param string|Stringable $message The log message\n     * @param mixed[]           $context The log context\n     */\n    public function alert(string|\\Stringable $message, array $context = []): void\n    {\n        $this->addRecord(Level::Alert, (string) $message, $context);\n    }\n\n    /**\n     * Adds a log record at the EMERGENCY level.\n     *\n     * This method allows for compatibility with common interfaces.\n     *\n     * @param string|Stringable $message The log message\n     * @param mixed[]           $context The log context\n     */\n    public function emergency(string|\\Stringable $message, array $context = []): void\n    {\n        $this->addRecord(Level::Emergency, (string) $message, $context);\n    }\n\n    /**\n     * Sets the timezone to be used for the timestamp of log records.\n     *\n     * @return $this\n     */\n    public function setTimezone(DateTimeZone $tz): self\n    {\n        $this->timezone = $tz;\n\n        return $this;\n    }\n\n    /**\n     * Returns the timezone to be used for the timestamp of log records.\n     */\n    public function getTimezone(): DateTimeZone\n    {\n        return $this->timezone;\n    }\n\n    /**\n     * Delegates exception management to the custom exception handler,\n     * or throws the exception if no custom handler is set.\n     */\n    protected function handleException(Throwable $e, LogRecord $record): void\n    {\n        if (null === $this->exceptionHandler) {\n            throw $e;\n        }\n\n        ($this->exceptionHandler)($e, $record);\n    }\n\n    /**\n     * @return array<string, mixed>\n     */\n    public function __serialize(): array\n    {\n        return [\n            'name' => $this->name,\n            'handlers' => $this->handlers,\n            'processors' => $this->processors,\n            'microsecondTimestamps' => $this->microsecondTimestamps,\n            'timezone' => $this->timezone,\n            'exceptionHandler' => $this->exceptionHandler,\n            'logDepth' => $this->logDepth,\n            'detectCycles' => $this->detectCycles,\n        ];\n    }\n\n    /**\n     * @param array<string, mixed> $data\n     */\n    public function __unserialize(array $data): void\n    {\n        foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) {\n            if (isset($data[$property])) {\n                $this->$property = $data[$property];\n            }\n        }\n\n        $this->fiberLogDepth = new \\WeakMap();\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/ClosureContextProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\LogRecord;\n\n/**\n * Generates a context from a Closure if the Closure is the only value\n * in the context\n *\n * It helps reduce the performance impact of debug logs if they do\n * need to create lots of context information. If this processor is added\n * on the correct handler the context data will only be generated\n * when the logs are actually logged to that handler, which is useful when\n * using FingersCrossedHandler or other filtering handlers to conditionally\n * log records.\n */\nclass ClosureContextProcessor implements ProcessorInterface\n{\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        $context = $record->context;\n        if (isset($context[0]) && 1 === \\count($context) && $context[0] instanceof \\Closure) {\n            try {\n                $context = $context[0]();\n            } catch (\\Throwable $e) {\n                $context = [\n                    'error_on_context_generation' => $e->getMessage(),\n                    'exception' => $e,\n                ];\n            }\n\n            if (!\\is_array($context)) {\n                $context = [$context];\n            }\n\n            $record = $record->with(context: $context);\n        }\n\n        return $record;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/GitProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\LogRecord;\n\n/**\n * Injects Git branch and Git commit SHA in all records\n *\n * @author Nick Otter\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass GitProcessor implements ProcessorInterface\n{\n    private Level $level;\n    /** @var array{branch: string, commit: string}|array<never>|null */\n    private static $cache = null;\n\n    /**\n     * @param int|string|Level|LogLevel::* $level The minimum logging level at which this Processor will be triggered\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public function __construct(int|string|Level $level = Level::Debug)\n    {\n        $this->level = Logger::toMonologLevel($level);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        // return if the level is not high enough\n        if ($record->level->isLowerThan($this->level)) {\n            return $record;\n        }\n\n        $record->extra['git'] = self::getGitInfo();\n\n        return $record;\n    }\n\n    /**\n     * @return array{branch: string, commit: string}|array<never>\n     */\n    private static function getGitInfo(): array\n    {\n        if (self::$cache !== null) {\n            return self::$cache;\n        }\n\n        $branches = shell_exec('git branch -v --no-abbrev');\n        if (\\is_string($branches) && 1 === preg_match('{^\\* (.+?)\\s+([a-f0-9]{40})(?:\\s|$)}m', $branches, $matches)) {\n            return self::$cache = [\n                'branch' => $matches[1],\n                'commit' => $matches[2],\n            ];\n        }\n\n        return self::$cache = [];\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/HostnameProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\LogRecord;\n\n/**\n * Injects value of gethostname in all records\n */\nclass HostnameProcessor implements ProcessorInterface\n{\n    private static string $host;\n\n    public function __construct()\n    {\n        self::$host = (string) gethostname();\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        $record->extra['hostname'] = self::$host;\n\n        return $record;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/IntrospectionProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\LogRecord;\n\n/**\n * Injects line/file:class/function where the log message came from\n *\n * Warning: This only works if the handler processes the logs directly.\n * If you put the processor on a handler that is behind a FingersCrossedHandler\n * for example, the processor will only be called once the trigger level is reached,\n * and all the log records will have the same file/line/.. data from the call that\n * triggered the FingersCrossedHandler.\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass IntrospectionProcessor implements ProcessorInterface\n{\n    protected Level $level;\n\n    /** @var string[] */\n    protected array $skipClassesPartials;\n\n    protected int $skipStackFramesCount;\n\n    protected const SKIP_FUNCTIONS = [\n        'call_user_func',\n        'call_user_func_array',\n    ];\n\n    protected const SKIP_CLASSES = [\n        'Monolog\\\\',\n    ];\n\n    /**\n     * @param string|int|Level $level               The minimum logging level at which this Processor will be triggered\n     * @param string[]         $skipClassesPartials\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public function __construct(int|string|Level $level = Level::Debug, array $skipClassesPartials = [], int $skipStackFramesCount = 0)\n    {\n        $this->level = Logger::toMonologLevel($level);\n        $this->skipClassesPartials = array_merge(static::SKIP_CLASSES, $skipClassesPartials);\n        $this->skipStackFramesCount = $skipStackFramesCount;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        // return if the level is not high enough\n        if ($record->level->isLowerThan($this->level)) {\n            return $record;\n        }\n\n        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);\n\n        // skip first since it's always the current method\n        array_shift($trace);\n        // the call_user_func call is also skipped\n        array_shift($trace);\n\n        $i = 0;\n\n        while ($this->isTraceClassOrSkippedFunction($trace, $i)) {\n            if (isset($trace[$i]['class'])) {\n                foreach ($this->skipClassesPartials as $part) {\n                    if (strpos($trace[$i]['class'], $part) !== false) {\n                        $i++;\n\n                        continue 2;\n                    }\n                }\n            } elseif (\\in_array($trace[$i]['function'], self::SKIP_FUNCTIONS, true)) {\n                $i++;\n\n                continue;\n            }\n\n            break;\n        }\n\n        $i += $this->skipStackFramesCount;\n\n        // we should have the call source now\n        $record->extra = array_merge(\n            $record->extra,\n            [\n                'file'      => $trace[$i - 1]['file'] ?? null,\n                'line'      => $trace[$i - 1]['line'] ?? null,\n                'class'     => $trace[$i]['class'] ?? null,\n                'callType'  => $trace[$i]['type'] ?? null,\n                'function'  => $trace[$i]['function'] ?? null,\n            ]\n        );\n\n        return $record;\n    }\n\n    /**\n     * @param array<mixed> $trace\n     */\n    private function isTraceClassOrSkippedFunction(array $trace, int $index): bool\n    {\n        if (!isset($trace[$index])) {\n            return false;\n        }\n\n        return isset($trace[$index]['class']) || \\in_array($trace[$index]['function'], self::SKIP_FUNCTIONS, true);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/LoadAverageProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\LogRecord;\n\n/**\n * Injects sys_getloadavg in all records @see https://www.php.net/manual/en/function.sys-getloadavg.php\n *\n * @author Johan Vlaar <johan.vlaar.1994@gmail.com>\n */\nclass LoadAverageProcessor implements ProcessorInterface\n{\n    public const LOAD_1_MINUTE = 0;\n    public const LOAD_5_MINUTE = 1;\n    public const LOAD_15_MINUTE = 2;\n\n    private const AVAILABLE_LOAD = [\n        self::LOAD_1_MINUTE,\n        self::LOAD_5_MINUTE,\n        self::LOAD_15_MINUTE,\n    ];\n\n    /**\n     * @var int\n     */\n    protected $avgSystemLoad;\n\n    /**\n     * @param self::LOAD_* $avgSystemLoad\n     */\n    public function __construct(int $avgSystemLoad = self::LOAD_1_MINUTE)\n    {\n        if (!\\in_array($avgSystemLoad, self::AVAILABLE_LOAD, true)) {\n            throw new \\InvalidArgumentException(sprintf('Invalid average system load: `%s`', $avgSystemLoad));\n        }\n        $this->avgSystemLoad = $avgSystemLoad;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        if (!\\function_exists('sys_getloadavg')) {\n            return $record;\n        }\n        $usage = sys_getloadavg();\n        if (false === $usage) {\n            return $record;\n        }\n\n        $record->extra['load_average'] = $usage[$this->avgSystemLoad];\n\n        return $record;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/MemoryPeakUsageProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\LogRecord;\n\n/**\n * Injects memory_get_peak_usage in all records\n *\n * @see Monolog\\Processor\\MemoryProcessor::__construct() for options\n * @author Rob Jensen\n */\nclass MemoryPeakUsageProcessor extends MemoryProcessor\n{\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        $usage = memory_get_peak_usage($this->realUsage);\n\n        if ($this->useFormatting) {\n            $usage = $this->formatBytes($usage);\n        }\n\n        $record->extra['memory_peak_usage'] = $usage;\n\n        return $record;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/MemoryProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\n/**\n * Some methods that are common for all memory processors\n *\n * @author Rob Jensen\n */\nabstract class MemoryProcessor implements ProcessorInterface\n{\n    /**\n     * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported.\n     */\n    protected bool $realUsage;\n\n    /**\n     * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size)\n     */\n    protected bool $useFormatting;\n\n    /**\n     * @param bool $realUsage     Set this to true to get the real size of memory allocated from system.\n     * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size)\n     */\n    public function __construct(bool $realUsage = true, bool $useFormatting = true)\n    {\n        $this->realUsage = $realUsage;\n        $this->useFormatting = $useFormatting;\n    }\n\n    /**\n     * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is\n     *\n     * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int\n     */\n    protected function formatBytes(int $bytes)\n    {\n        if (!$this->useFormatting) {\n            return $bytes;\n        }\n\n        if ($bytes > 1024 * 1024) {\n            return round($bytes / 1024 / 1024, 2).' MB';\n        } elseif ($bytes > 1024) {\n            return round($bytes / 1024, 2).' KB';\n        }\n\n        return $bytes . ' B';\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/MemoryUsageProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\LogRecord;\n\n/**\n * Injects memory_get_usage in all records\n *\n * @see Monolog\\Processor\\MemoryProcessor::__construct() for options\n * @author Rob Jensen\n */\nclass MemoryUsageProcessor extends MemoryProcessor\n{\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        $usage = memory_get_usage($this->realUsage);\n\n        if ($this->useFormatting) {\n            $usage = $this->formatBytes($usage);\n        }\n\n        $record->extra['memory_usage'] = $usage;\n\n        return $record;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/MercurialProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Psr\\Log\\LogLevel;\nuse Monolog\\LogRecord;\n\n/**\n * Injects Hg branch and Hg revision number in all records\n *\n * @author Jonathan A. Schweder <jonathanschweder@gmail.com>\n */\nclass MercurialProcessor implements ProcessorInterface\n{\n    private Level $level;\n    /** @var array{branch: string, revision: string}|array<never>|null */\n    private static $cache = null;\n\n    /**\n     * @param int|string|Level $level The minimum logging level at which this Processor will be triggered\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public function __construct(int|string|Level $level = Level::Debug)\n    {\n        $this->level = Logger::toMonologLevel($level);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        // return if the level is not high enough\n        if ($record->level->isLowerThan($this->level)) {\n            return $record;\n        }\n\n        $record->extra['hg'] = self::getMercurialInfo();\n\n        return $record;\n    }\n\n    /**\n     * @return array{branch: string, revision: string}|array<never>\n     */\n    private static function getMercurialInfo(): array\n    {\n        if (self::$cache !== null) {\n            return self::$cache;\n        }\n\n        $result = explode(' ', trim((string) shell_exec('hg id -nb')));\n        if (\\count($result) >= 3) {\n            return self::$cache = [\n                'branch' => $result[1],\n                'revision' => $result[2],\n            ];\n        }\n        if (\\count($result) === 2) {\n            return self::$cache = [\n                'branch' => $result[1],\n                'revision' => $result[0],\n            ];\n        }\n\n        return self::$cache = [];\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/ProcessIdProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\LogRecord;\n\n/**\n * Adds value of getmypid into records\n *\n * @author Andreas Hörnicke\n */\nclass ProcessIdProcessor implements ProcessorInterface\n{\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        $record->extra['process_id'] = getmypid();\n\n        return $record;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/ProcessorInterface.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\LogRecord;\n\n/**\n * An optional interface to allow labelling Monolog processors.\n *\n * @author Nicolas Grekas <p@tchwork.com>\n */\ninterface ProcessorInterface\n{\n    /**\n     * @return LogRecord The processed record\n     */\n    public function __invoke(LogRecord $record);\n}\n"
  },
  {
    "path": "src/Monolog/Processor/PsrLogMessageProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\Utils;\nuse Monolog\\LogRecord;\n\n/**\n * Processes a record's message according to PSR-3 rules\n *\n * It replaces {foo} with the value from $context['foo']\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass PsrLogMessageProcessor implements ProcessorInterface\n{\n    public const SIMPLE_DATE = \"Y-m-d\\TH:i:s.uP\";\n\n    private ?string $dateFormat;\n\n    private bool $removeUsedContextFields;\n\n    /**\n     * @param string|null $dateFormat              The format of the timestamp: one supported by DateTime::format\n     * @param bool        $removeUsedContextFields If set to true the fields interpolated into message gets unset\n     */\n    public function __construct(?string $dateFormat = null, bool $removeUsedContextFields = false)\n    {\n        $this->dateFormat = $dateFormat;\n        $this->removeUsedContextFields = $removeUsedContextFields;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        if (false === strpos($record->message, '{')) {\n            return $record;\n        }\n\n        $replacements = [];\n        $context = $record->context;\n\n        foreach ($context as $key => $val) {\n            $placeholder = '{' . $key . '}';\n            if (strpos($record->message, $placeholder) === false) {\n                continue;\n            }\n\n            if (null === $val || \\is_scalar($val) || (\\is_object($val) && method_exists($val, \"__toString\"))) {\n                $replacements[$placeholder] = $val;\n            } elseif ($val instanceof \\DateTimeInterface) {\n                if (null === $this->dateFormat && $val instanceof \\Monolog\\JsonSerializableDateTimeImmutable) {\n                    // handle monolog dates using __toString if no specific dateFormat was asked for\n                    // so that it follows the useMicroseconds flag\n                    $replacements[$placeholder] = (string) $val;\n                } else {\n                    $replacements[$placeholder] = $val->format($this->dateFormat ?? static::SIMPLE_DATE);\n                }\n            } elseif ($val instanceof \\UnitEnum) {\n                $replacements[$placeholder] = $val instanceof \\BackedEnum ? $val->value : $val->name;\n            } elseif (\\is_object($val)) {\n                $replacements[$placeholder] = '[object '.Utils::getClass($val).']';\n            } elseif (\\is_array($val)) {\n                $replacements[$placeholder] = 'array'.Utils::jsonEncode($val, null, true);\n            } else {\n                $replacements[$placeholder] = '['.\\gettype($val).']';\n            }\n\n            if ($this->removeUsedContextFields) {\n                unset($context[$key]);\n            }\n        }\n\n        return $record->with(message: strtr($record->message, $replacements), context: $context);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/TagProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\LogRecord;\n\n/**\n * Adds a tags array into record\n *\n * @author Martijn Riemers\n */\nclass TagProcessor implements ProcessorInterface\n{\n    /** @var string[] */\n    private array $tags;\n\n    /**\n     * @param string[] $tags\n     */\n    public function __construct(array $tags = [])\n    {\n        $this->setTags($tags);\n    }\n\n    /**\n     * @param  string[] $tags\n     * @return $this\n     */\n    public function addTags(array $tags = []): self\n    {\n        $this->tags = array_merge($this->tags, $tags);\n\n        return $this;\n    }\n\n    /**\n     * @param  string[] $tags\n     * @return $this\n     */\n    public function setTags(array $tags = []): self\n    {\n        $this->tags = $tags;\n\n        return $this;\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        $record->extra['tags'] = $this->tags;\n\n        return $record;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/UidProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\ResettableInterface;\nuse Monolog\\LogRecord;\n\n/**\n * Adds a unique identifier into records\n *\n * @author Simon Mönch <sm@webfactory.de>\n */\nclass UidProcessor implements ProcessorInterface, ResettableInterface\n{\n    /** @var non-empty-string */\n    private string $uid;\n\n    /**\n     * @param int<1, 32> $length\n     */\n    public function __construct(int $length = 7)\n    {\n        if ($length > 32 || $length < 1) {\n            throw new \\InvalidArgumentException('The uid length must be an integer between 1 and 32');\n        }\n\n        $this->uid = $this->generateUid($length);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        $record->extra['uid'] = $this->uid;\n\n        return $record;\n    }\n\n    public function getUid(): string\n    {\n        return $this->uid;\n    }\n\n    public function reset(): void\n    {\n        $this->uid = $this->generateUid(\\strlen($this->uid));\n    }\n\n    /**\n     * @param  positive-int     $length\n     * @return non-empty-string\n     */\n    private function generateUid(int $length): string\n    {\n        return substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Processor/WebProcessor.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse ArrayAccess;\nuse Monolog\\LogRecord;\n\n/**\n * Injects url/method and remote IP of the current web request in all records\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass WebProcessor implements ProcessorInterface\n{\n    /**\n     * @var array<string, mixed>|ArrayAccess<string, mixed>\n     */\n    protected array|ArrayAccess $serverData;\n\n    /**\n     * Default fields\n     *\n     * Array is structured as [key in record.extra => key in $serverData]\n     *\n     * @var array<string, string>\n     */\n    protected array $extraFields = [\n        'url'         => 'REQUEST_URI',\n        'ip'          => 'REMOTE_ADDR',\n        'http_method' => 'REQUEST_METHOD',\n        'server'      => 'SERVER_NAME',\n        'referrer'    => 'HTTP_REFERER',\n        'user_agent'  => 'HTTP_USER_AGENT',\n    ];\n\n    /**\n     * @param array<string, mixed>|ArrayAccess<string, mixed>|null $serverData  Array or object w/ ArrayAccess that provides access to the $_SERVER data\n     * @param array<string, string>|array<string>|null             $extraFields Field names and the related key inside $serverData to be added (or just a list of field names to use the default configured $serverData mapping). If not provided it defaults to: [url, ip, http_method, server, referrer] + unique_id if present in server data\n     */\n    public function __construct(array|ArrayAccess|null $serverData = null, array|null $extraFields = null)\n    {\n        if (null === $serverData) {\n            $this->serverData = &$_SERVER;\n        } else {\n            $this->serverData = $serverData;\n        }\n\n        $defaultEnabled = ['url', 'ip', 'http_method', 'server', 'referrer'];\n        if (isset($this->serverData['UNIQUE_ID'])) {\n            $this->extraFields['unique_id'] = 'UNIQUE_ID';\n            $defaultEnabled[] = 'unique_id';\n        }\n\n        if (null === $extraFields) {\n            $extraFields = $defaultEnabled;\n        }\n        if (isset($extraFields[0])) {\n            foreach (array_keys($this->extraFields) as $fieldName) {\n                if (!\\in_array($fieldName, $extraFields, true)) {\n                    unset($this->extraFields[$fieldName]);\n                }\n            }\n        } else {\n            $this->extraFields = $extraFields;\n        }\n    }\n\n    /**\n     * @inheritDoc\n     */\n    public function __invoke(LogRecord $record): LogRecord\n    {\n        // skip processing if for some reason request data\n        // is not present (CLI or wonky SAPIs)\n        if (!isset($this->serverData['REQUEST_URI'])) {\n            return $record;\n        }\n\n        $record->extra = $this->appendExtraFields($record->extra);\n\n        return $record;\n    }\n\n    /**\n     * @return $this\n     */\n    public function addExtraField(string $extraName, string $serverName): self\n    {\n        $this->extraFields[$extraName] = $serverName;\n\n        return $this;\n    }\n\n    /**\n     * @param  mixed[] $extra\n     * @return mixed[]\n     */\n    private function appendExtraFields(array $extra): array\n    {\n        foreach ($this->extraFields as $extraName => $serverName) {\n            $extra[$extraName] = $this->serverData[$serverName] ?? null;\n        }\n\n        return $extra;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Registry.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse InvalidArgumentException;\n\n/**\n * Monolog log registry\n *\n * Allows to get `Logger` instances in the global scope\n * via static method calls on this class.\n *\n * <code>\n * $application = new Monolog\\Logger('application');\n * $api = new Monolog\\Logger('api');\n *\n * Monolog\\Registry::addLogger($application);\n * Monolog\\Registry::addLogger($api);\n *\n * function testLogger()\n * {\n *     Monolog\\Registry::api()->error('Sent to $api Logger instance');\n *     Monolog\\Registry::application()->error('Sent to $application Logger instance');\n * }\n * </code>\n *\n * @author Tomas Tatarko <tomas@tatarko.sk>\n */\nclass Registry\n{\n    /**\n     * List of all loggers in the registry (by named indexes)\n     *\n     * @var Logger[]\n     */\n    private static array $loggers = [];\n\n    /**\n     * Adds new logging channel to the registry\n     *\n     * @param  Logger                    $logger    Instance of the logging channel\n     * @param  string|null               $name      Name of the logging channel ($logger->getName() by default)\n     * @param  bool                      $overwrite Overwrite instance in the registry if the given name already exists?\n     * @throws \\InvalidArgumentException If $overwrite set to false and named Logger instance already exists\n     */\n    public static function addLogger(Logger $logger, ?string $name = null, bool $overwrite = false): void\n    {\n        $name = $name ?? $logger->getName();\n\n        if (isset(self::$loggers[$name]) && !$overwrite) {\n            throw new InvalidArgumentException('Logger with the given name already exists');\n        }\n\n        self::$loggers[$name] = $logger;\n    }\n\n    /**\n     * Checks if such logging channel exists by name or instance\n     *\n     * @param string|Logger $logger Name or logger instance\n     */\n    public static function hasLogger($logger): bool\n    {\n        if ($logger instanceof Logger) {\n            $index = array_search($logger, self::$loggers, true);\n\n            return false !== $index;\n        }\n\n        return isset(self::$loggers[$logger]);\n    }\n\n    /**\n     * Removes instance from registry by name or instance\n     *\n     * @param string|Logger $logger Name or logger instance\n     */\n    public static function removeLogger($logger): void\n    {\n        if ($logger instanceof Logger) {\n            if (false !== ($idx = array_search($logger, self::$loggers, true))) {\n                unset(self::$loggers[$idx]);\n            }\n        } else {\n            unset(self::$loggers[$logger]);\n        }\n    }\n\n    /**\n     * Clears the registry\n     */\n    public static function clear(): void\n    {\n        self::$loggers = [];\n    }\n\n    /**\n     * Gets Logger instance from the registry\n     *\n     * @param  string                    $name Name of the requested Logger instance\n     * @throws \\InvalidArgumentException If named Logger instance is not in the registry\n     */\n    public static function getInstance(string $name): Logger\n    {\n        if (!isset(self::$loggers[$name])) {\n            throw new InvalidArgumentException(sprintf('Requested \"%s\" logger instance is not in the registry', $name));\n        }\n\n        return self::$loggers[$name];\n    }\n\n    /**\n     * Gets Logger instance from the registry via static method call\n     *\n     * @param  string                    $name      Name of the requested Logger instance\n     * @param  mixed[]                   $arguments Arguments passed to static method call\n     * @throws \\InvalidArgumentException If named Logger instance is not in the registry\n     * @return Logger                    Requested instance of Logger\n     */\n    public static function __callStatic(string $name, array $arguments): Logger\n    {\n        return self::getInstance($name);\n    }\n}\n"
  },
  {
    "path": "src/Monolog/ResettableInterface.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\n/**\n * Handler or Processor implementing this interface will be reset when Logger::reset() is called.\n *\n * Resetting ends a log cycle gets them back to their initial state.\n *\n * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal\n * state, and getting it back to a state in which it can receive log records again.\n *\n * This is useful in case you want to avoid logs leaking between two requests or jobs when you\n * have a long running process like a worker or an application server serving multiple requests\n * in one process.\n *\n * @author Grégoire Pineau <lyrixx@lyrixx.info>\n */\ninterface ResettableInterface\n{\n    public function reset(): void;\n}\n"
  },
  {
    "path": "src/Monolog/SignalHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse Psr\\Log\\LoggerInterface;\nuse Psr\\Log\\LogLevel;\nuse ReflectionExtension;\n\n/**\n * Monolog POSIX signal handler\n *\n * @author Robert Gust-Bardon <robert@gust-bardon.org>\n */\nclass SignalHandler\n{\n    private LoggerInterface $logger;\n\n    /** @var array<int, callable|string|int> SIG_DFL, SIG_IGN or previous callable */\n    private array $previousSignalHandler = [];\n    /** @var array<int, \\Psr\\Log\\LogLevel::*> */\n    private array $signalLevelMap = [];\n    /** @var array<int, bool> */\n    private array $signalRestartSyscalls = [];\n\n    public function __construct(LoggerInterface $logger)\n    {\n        $this->logger = $logger;\n    }\n\n    /**\n     * @param  int|string|Level $level Level or level name\n     * @return $this\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    public function registerSignalHandler(int $signo, int|string|Level $level = LogLevel::CRITICAL, bool $callPrevious = true, bool $restartSyscalls = true, ?bool $async = true): self\n    {\n        if (!\\extension_loaded('pcntl') || !\\function_exists('pcntl_signal')) {\n            return $this;\n        }\n\n        $level = Logger::toMonologLevel($level)->toPsrLogLevel();\n\n        if ($callPrevious) {\n            $handler = pcntl_signal_get_handler($signo);\n            $this->previousSignalHandler[$signo] = $handler;\n        } else {\n            unset($this->previousSignalHandler[$signo]);\n        }\n        $this->signalLevelMap[$signo] = $level;\n        $this->signalRestartSyscalls[$signo] = $restartSyscalls;\n\n        if ($async !== null) {\n            pcntl_async_signals($async);\n        }\n\n        pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls);\n\n        return $this;\n    }\n\n    /**\n     * @param mixed $siginfo\n     */\n    public function handleSignal(int $signo, $siginfo = null): void\n    {\n        /** @var array<int, string> $signals */\n        static $signals = [];\n\n        if (\\count($signals) === 0 && \\extension_loaded('pcntl')) {\n            $pcntl = new ReflectionExtension('pcntl');\n            foreach ($pcntl->getConstants() as $name => $value) {\n                if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && \\is_int($value)) {\n                    $signals[$value] = $name;\n                }\n            }\n        }\n\n        $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL;\n        $signal = $signals[$signo] ?? $signo;\n        $context = $siginfo ?? [];\n        $this->logger->log($level, sprintf('Program received signal %s', $signal), $context);\n\n        if (!isset($this->previousSignalHandler[$signo])) {\n            return;\n        }\n\n        if ($this->previousSignalHandler[$signo] === SIG_DFL) {\n            if (\\extension_loaded('pcntl') && \\function_exists('pcntl_signal') && \\function_exists('pcntl_sigprocmask') && \\function_exists('pcntl_signal_dispatch')\n                && \\extension_loaded('posix') && \\function_exists('posix_getpid') && \\function_exists('posix_kill')\n            ) {\n                $restartSyscalls = $this->signalRestartSyscalls[$signo] ?? true;\n                pcntl_signal($signo, SIG_DFL, $restartSyscalls);\n                pcntl_sigprocmask(SIG_UNBLOCK, [$signo], $oldset);\n                posix_kill(posix_getpid(), $signo);\n                pcntl_signal_dispatch();\n                pcntl_sigprocmask(SIG_SETMASK, $oldset);\n                pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls);\n            }\n        } elseif (\\is_callable($this->previousSignalHandler[$signo])) {\n            $this->previousSignalHandler[$signo]($signo, $siginfo);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Test/MonologTestCase.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Test;\n\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse Monolog\\LogRecord;\nuse Monolog\\JsonSerializableDateTimeImmutable;\nuse Monolog\\Formatter\\FormatterInterface;\nuse Psr\\Log\\LogLevel;\n\n/**\n * Lets you easily generate log records and a dummy formatter for testing purposes\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n */\nclass MonologTestCase extends \\PHPUnit\\Framework\\TestCase\n{\n    /**\n     * @param array<mixed> $context\n     * @param array<mixed> $extra\n     *\n     * @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level\n     */\n    protected function getRecord(int|string|Level $level = Level::Warning, string|\\Stringable $message = 'test', array $context = [], string $channel = 'test', \\DateTimeImmutable $datetime = new JsonSerializableDateTimeImmutable(true), array $extra = []): LogRecord\n    {\n        return new LogRecord(\n            message: (string) $message,\n            context: $context,\n            level: Logger::toMonologLevel($level),\n            channel: $channel,\n            datetime: $datetime,\n            extra: $extra,\n        );\n    }\n\n    /**\n     * @phpstan-return list<LogRecord>\n     */\n    protected function getMultipleRecords(): array\n    {\n        return [\n            $this->getRecord(Level::Debug, 'debug message 1'),\n            $this->getRecord(Level::Debug, 'debug message 2'),\n            $this->getRecord(Level::Info, 'information'),\n            $this->getRecord(Level::Warning, 'warning'),\n            $this->getRecord(Level::Error, 'error'),\n        ];\n    }\n\n    protected function getIdentityFormatter(): FormatterInterface\n    {\n        $formatter = $this->createMock(FormatterInterface::class);\n        $formatter->expects(self::any())\n            ->method('format')\n            ->willReturnCallback(function ($record) {\n                return $record->message;\n            });\n\n        return $formatter;\n    }\n}\n"
  },
  {
    "path": "src/Monolog/Test/TestCase.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Test;\n\n/**\n * Lets you easily generate log records and a dummy formatter for testing purposes\n *\n * @author Jordi Boggiano <j.boggiano@seld.be>\n *\n * @deprecated use MonologTestCase instead.\n */\nclass TestCase extends MonologTestCase\n{\n}\n"
  },
  {
    "path": "src/Monolog/Utils.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nfinal class Utils\n{\n    const DEFAULT_JSON_FLAGS = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR;\n\n    public static function getClass(object $object): string\n    {\n        $class = \\get_class($object);\n\n        if (false === ($pos = strpos($class, \"@anonymous\\0\"))) {\n            return $class;\n        }\n\n        if (false === ($parent = get_parent_class($class))) {\n            return substr($class, 0, $pos + 10);\n        }\n\n        return $parent . '@anonymous';\n    }\n\n    public static function substr(string $string, int $start, ?int $length = null): string\n    {\n        if (\\extension_loaded('mbstring')) {\n            return mb_strcut($string, $start, $length);\n        }\n\n        return substr($string, $start, (null === $length) ? \\strlen($string) : $length);\n    }\n\n    /**\n     * Makes sure if a relative path is passed in it is turned into an absolute path\n     *\n     * @param string $streamUrl stream URL or path without protocol\n     */\n    public static function canonicalizePath(string $streamUrl): string\n    {\n        $prefix = '';\n        if ('file://' === substr($streamUrl, 0, 7)) {\n            $streamUrl = substr($streamUrl, 7);\n            $prefix = 'file://';\n        }\n\n        // other type of stream, not supported\n        if (false !== strpos($streamUrl, '://')) {\n            return $streamUrl;\n        }\n\n        // already absolute\n        if (substr($streamUrl, 0, 1) === '/' || substr($streamUrl, 1, 1) === ':' || substr($streamUrl, 0, 2) === '\\\\\\\\') {\n            return $prefix.$streamUrl;\n        }\n\n        $streamUrl = getcwd() . '/' . $streamUrl;\n\n        return $prefix.$streamUrl;\n    }\n\n    /**\n     * Return the JSON representation of a value\n     *\n     * @param  mixed             $data\n     * @param  int               $encodeFlags  flags to pass to json encode, defaults to DEFAULT_JSON_FLAGS\n     * @param  bool              $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, \"null\" is returned which is valid json for null\n     * @throws \\RuntimeException if encoding fails and errors are not ignored\n     * @return string            when errors are ignored and the encoding fails, \"null\" is returned which is valid json for null\n     */\n    public static function jsonEncode($data, ?int $encodeFlags = null, bool $ignoreErrors = false): string\n    {\n        if (null === $encodeFlags) {\n            $encodeFlags = self::DEFAULT_JSON_FLAGS;\n        }\n\n        if ($ignoreErrors) {\n            $json = @json_encode($data, $encodeFlags);\n            if (false === $json) {\n                return 'null';\n            }\n\n            return $json;\n        }\n\n        $json = json_encode($data, $encodeFlags);\n        if (false === $json) {\n            $json = self::handleJsonError(json_last_error(), $data);\n        }\n\n        return $json;\n    }\n\n    /**\n     * Handle a json_encode failure.\n     *\n     * If the failure is due to invalid string encoding, try to clean the\n     * input and encode again. If the second encoding attempt fails, the\n     * initial error is not encoding related or the input can't be cleaned then\n     * raise a descriptive exception.\n     *\n     * @param  int               $code        return code of json_last_error function\n     * @param  mixed             $data        data that was meant to be encoded\n     * @param  int               $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION\n     * @throws \\RuntimeException if failure can't be corrected\n     * @return string            JSON encoded data after error correction\n     */\n    public static function handleJsonError(int $code, $data, ?int $encodeFlags = null): string\n    {\n        if ($code !== JSON_ERROR_UTF8) {\n            self::throwEncodeError($code, $data);\n        }\n\n        if (\\is_string($data)) {\n            self::detectAndCleanUtf8($data);\n        } elseif (\\is_array($data)) {\n            array_walk_recursive($data, ['Monolog\\Utils', 'detectAndCleanUtf8']);\n        } else {\n            self::throwEncodeError($code, $data);\n        }\n\n        if (null === $encodeFlags) {\n            $encodeFlags = self::DEFAULT_JSON_FLAGS;\n        }\n\n        $json = json_encode($data, $encodeFlags);\n\n        if ($json === false) {\n            self::throwEncodeError(json_last_error(), $data);\n        }\n\n        return $json;\n    }\n\n    /**\n     * Throws an exception according to a given code with a customized message\n     *\n     * @param  int               $code return code of json_last_error function\n     * @param  mixed             $data data that was meant to be encoded\n     * @throws \\RuntimeException\n     */\n    private static function throwEncodeError(int $code, $data): never\n    {\n        $msg = match ($code) {\n            JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',\n            JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch',\n            JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',\n            JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',\n            default => 'Unknown error',\n        };\n\n        throw new \\RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));\n    }\n\n    /**\n     * Detect invalid UTF-8 string characters and convert to valid UTF-8.\n     *\n     * Valid UTF-8 input will be left unmodified, but strings containing\n     * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed\n     * original encoding of ISO-8859-15. This conversion may result in\n     * incorrect output if the actual encoding was not ISO-8859-15, but it\n     * will be clean UTF-8 output and will not rely on expensive and fragile\n     * detection algorithms.\n     *\n     * Function converts the input in place in the passed variable so that it\n     * can be used as a callback for array_walk_recursive.\n     *\n     * @param mixed $data Input to check and convert if needed, passed by ref\n     */\n    private static function detectAndCleanUtf8(&$data): void\n    {\n        if (\\is_string($data) && preg_match('//u', $data) !== 1) {\n            $data = preg_replace_callback(\n                '/[\\x80-\\xFF]+/',\n                function (array $m): string {\n                    return \\function_exists('mb_convert_encoding')\n                        ? mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1')\n                        : (\\function_exists('utf8_encode') ? utf8_encode($m[0]) : '');\n                },\n                $data\n            );\n            if (!\\is_string($data)) {\n                $pcreErrorCode = preg_last_error();\n\n                throw new \\RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . preg_last_error_msg());\n            }\n            $data = str_replace(\n                ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'],\n                ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'],\n                $data\n            );\n        }\n    }\n\n    /**\n     * Converts a string with a valid 'memory_limit' format, to bytes.\n     *\n     * @param  string|false $val\n     * @return int|false    Returns an integer representing bytes. Returns FALSE in case of error.\n     */\n    public static function expandIniShorthandBytes($val)\n    {\n        if (!\\is_string($val)) {\n            return false;\n        }\n\n        // support -1\n        if ((int) $val < 0) {\n            return (int) $val;\n        }\n\n        if (!(bool) preg_match('/^\\s*(?<val>\\d+)(?:\\.\\d+)?\\s*(?<unit>[gmk]?)\\s*$/i', $val, $match)) {\n            return false;\n        }\n\n        $val = (int) $match['val'];\n        switch (strtolower($match['unit'])) {\n            case 'g':\n                $val *= 1024;\n                // no break\n            case 'm':\n                $val *= 1024;\n                // no break\n            case 'k':\n                $val *= 1024;\n        }\n\n        return $val;\n    }\n\n    public static function getRecordMessageForException(LogRecord $record): string\n    {\n        $context = '';\n        $extra = '';\n\n        try {\n            if (\\count($record->context) > 0) {\n                $context = \"\\nContext: \" . json_encode($record->context, JSON_THROW_ON_ERROR);\n            }\n            if (\\count($record->extra) > 0) {\n                $extra = \"\\nExtra: \" . json_encode($record->extra, JSON_THROW_ON_ERROR);\n            }\n        } catch (\\Throwable $e) {\n            // noop\n        }\n\n        return \"\\nThe exception occurred while attempting to log: \" . $record->message . $context . $extra;\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Attribute/AsMonologProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Attribute;\n\n/**\n * @requires PHP 8.0\n */\nfinal class AsMonologProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function test(): void\n    {\n        $asMonologProcessor = new AsMonologProcessor('channel', 'handler', 'method', -10);\n        $this->assertSame('channel', $asMonologProcessor->channel);\n        $this->assertSame('handler', $asMonologProcessor->handler);\n        $this->assertSame('method', $asMonologProcessor->method);\n        $this->assertSame(-10, $asMonologProcessor->priority);\n\n        $asMonologProcessor = new AsMonologProcessor(null, null, null, null);\n        $this->assertNull($asMonologProcessor->channel);\n        $this->assertNull($asMonologProcessor->handler);\n        $this->assertNull($asMonologProcessor->method);\n        $this->assertNull($asMonologProcessor->priority);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Attribute/WithMonologChannelTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Attribute;\n\nclass WithMonologChannelTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function test(): void\n    {\n        $attribute = new WithMonologChannel('fixture');\n        $this->assertSame('fixture', $attribute->channel);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/ErrorHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse Monolog\\Handler\\TestHandler;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\WithoutErrorHandler;\nuse Psr\\Log\\LogLevel;\n\nclass ErrorHandlerTest extends \\PHPUnit\\Framework\\TestCase\n{\n    public function testRegister()\n    {\n        $logger = new Logger('test', [$handler = new TestHandler]);\n\n        $this->assertInstanceOf(ErrorHandler::class, ErrorHandler::register($logger, false, false, false));\n    }\n\n    #[WithoutErrorHandler]\n    public function testHandleError()\n    {\n        $logger = new Logger('test', [$handler = new TestHandler]);\n        $errHandler = new ErrorHandler($logger);\n\n        $phpunitHandler = set_error_handler($prevHandler = function () {\n        });\n\n        try {\n            $errHandler->registerErrorHandler([], true);\n            $prop = $this->getPrivatePropertyValue($errHandler, 'previousErrorHandler');\n            $this->assertTrue(\\is_callable($prop));\n            $this->assertSame($prevHandler, $prop);\n\n            $resHandler = $errHandler->registerErrorHandler([E_USER_NOTICE => LogLevel::EMERGENCY], false);\n            $this->assertSame($errHandler, $resHandler);\n            trigger_error('Foo', E_USER_ERROR);\n            $this->assertCount(1, $handler->getRecords());\n            $this->assertTrue($handler->hasErrorRecords());\n            trigger_error('Foo', E_USER_NOTICE);\n            $this->assertCount(2, $handler->getRecords());\n            // check that the remapping of notice to emergency above worked\n            $this->assertTrue($handler->hasEmergencyRecords());\n            $this->assertFalse($handler->hasNoticeRecords());\n        } finally {\n            // restore previous handler\n            set_error_handler($phpunitHandler);\n        }\n    }\n\n    public static function fatalHandlerProvider()\n    {\n        return [\n            [null, 10, str_repeat(' ', 1024 * 10), LogLevel::ALERT],\n            [LogLevel::DEBUG, 15, str_repeat(' ', 1024 * 15), LogLevel::DEBUG],\n        ];\n    }\n\n    protected function getPrivatePropertyValue($instance, $property)\n    {\n        $ref = new \\ReflectionClass(\\get_class($instance));\n        $prop = $ref->getProperty($property);\n\n        return $prop->getValue($instance);\n    }\n\n    #[DataProvider('fatalHandlerProvider')]\n    #[WithoutErrorHandler]\n    public function testFatalHandler(\n        $level,\n        $reservedMemorySize,\n        $expectedReservedMemory,\n        $expectedFatalLevel\n    ) {\n        $logger = new Logger('test', [$handler = new TestHandler]);\n        $errHandler = new ErrorHandler($logger);\n        $res = $errHandler->registerFatalHandler($level, $reservedMemorySize);\n\n        $this->assertSame($res, $errHandler);\n        $this->assertTrue($this->getPrivatePropertyValue($errHandler, 'hasFatalErrorHandler'));\n        $this->assertEquals($expectedReservedMemory, $this->getPrivatePropertyValue($errHandler, 'reservedMemory'));\n        $this->assertEquals($expectedFatalLevel, $this->getPrivatePropertyValue($errHandler, 'fatalLevel'));\n    }\n\n    #[WithoutErrorHandler]\n    public function testHandleException()\n    {\n        $logger = new Logger('test', [$handler = new TestHandler]);\n        $errHandler = new ErrorHandler($logger);\n\n        $resHandler = $errHandler->registerExceptionHandler($map = ['Monolog\\CustomTestException' => LogLevel::DEBUG, 'TypeError' => LogLevel::NOTICE, 'Throwable' => LogLevel::WARNING], false);\n        $this->assertSame($errHandler, $resHandler);\n\n        $map['ParseError'] = LogLevel::CRITICAL;\n        $prop = $this->getPrivatePropertyValue($errHandler, 'uncaughtExceptionLevelMap');\n        $this->assertSame($map, $prop);\n\n        $errHandler->registerExceptionHandler([], true);\n        $prop = $this->getPrivatePropertyValue($errHandler, 'previousExceptionHandler');\n        $this->assertTrue(\\is_callable($prop));\n\n        restore_exception_handler();\n        restore_exception_handler();\n    }\n\n    public function testCodeToString()\n    {\n        $method = new \\ReflectionMethod(ErrorHandler::class, 'codeToString');\n\n        $this->assertEquals('E_ERROR', $method->invokeArgs(null, [E_ERROR]));\n        $this->assertEquals('E_WARNING', $method->invokeArgs(null, [E_WARNING]));\n        $this->assertEquals('E_PARSE', $method->invokeArgs(null, [E_PARSE]));\n        $this->assertEquals('E_NOTICE', $method->invokeArgs(null, [E_NOTICE]));\n        $this->assertEquals('E_CORE_ERROR', $method->invokeArgs(null, [E_CORE_ERROR]));\n        $this->assertEquals('E_CORE_WARNING', $method->invokeArgs(null, [E_CORE_WARNING]));\n        $this->assertEquals('E_COMPILE_ERROR', $method->invokeArgs(null, [E_COMPILE_ERROR]));\n        $this->assertEquals('E_COMPILE_WARNING', $method->invokeArgs(null, [E_COMPILE_WARNING]));\n        $this->assertEquals('E_USER_ERROR', $method->invokeArgs(null, [E_USER_ERROR]));\n        $this->assertEquals('E_USER_WARNING', $method->invokeArgs(null, [E_USER_WARNING]));\n        $this->assertEquals('E_USER_NOTICE', $method->invokeArgs(null, [E_USER_NOTICE]));\n        $this->assertEquals('E_STRICT', $method->invokeArgs(null, [2048]));\n        $this->assertEquals('E_RECOVERABLE_ERROR', $method->invokeArgs(null, [E_RECOVERABLE_ERROR]));\n        $this->assertEquals('E_DEPRECATED', $method->invokeArgs(null, [E_DEPRECATED]));\n        $this->assertEquals('E_USER_DEPRECATED', $method->invokeArgs(null, [E_USER_DEPRECATED]));\n\n        $this->assertEquals('Unknown PHP error', $method->invokeArgs(null, [E_ALL]));\n    }\n}\n\nclass CustomTestException extends \\Exception\n{\n}\nclass CustomCustomException extends CustomTestException\n{\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/ChromePHPFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\Test\\MonologTestCase;\n\nclass ChromePHPFormatterTest extends MonologTestCase\n{\n    /**\n     * @covers Monolog\\Formatter\\ChromePHPFormatter::format\n     */\n    public function testDefaultFormat()\n    {\n        $formatter = new ChromePHPFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger'],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['ip' => '127.0.0.1'],\n        );\n\n        $message = $formatter->format($record);\n\n        $this->assertEquals(\n            [\n                'meh',\n                [\n                    'message' => 'log',\n                    'context' => ['from' => 'logger'],\n                    'extra' => ['ip' => '127.0.0.1'],\n                ],\n                'unknown',\n                'error',\n            ],\n            $message\n        );\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\ChromePHPFormatter::format\n     */\n    public function testFormatWithFileAndLine()\n    {\n        $formatter = new ChromePHPFormatter();\n        $record = $this->getRecord(\n            Level::Critical,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger'],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['ip' => '127.0.0.1', 'file' => 'test', 'line' => 14],\n        );\n\n        $message = $formatter->format($record);\n\n        $this->assertEquals(\n            [\n                'meh',\n                [\n                    'message' => 'log',\n                    'context' => ['from' => 'logger'],\n                    'extra' => ['ip' => '127.0.0.1'],\n                ],\n                'test : 14',\n                'error',\n            ],\n            $message\n        );\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\ChromePHPFormatter::format\n     */\n    public function testFormatWithoutContext()\n    {\n        $formatter = new ChromePHPFormatter();\n        $record = $this->getRecord(\n            Level::Debug,\n            'log',\n            channel: 'meh',\n            datetime: new \\DateTimeImmutable(\"@0\"),\n        );\n\n        $message = $formatter->format($record);\n\n        $this->assertEquals(\n            [\n                'meh',\n                'log',\n                'unknown',\n                'log',\n            ],\n            $message\n        );\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\ChromePHPFormatter::formatBatch\n     */\n    public function testBatchFormatThrowException()\n    {\n        $formatter = new ChromePHPFormatter();\n        $records = [\n            $this->getRecord(\n                Level::Info,\n                'log',\n                channel: 'meh',\n                datetime: new \\DateTimeImmutable(\"@0\"),\n            ),\n            $this->getRecord(\n                Level::Warning,\n                'log2',\n                channel: 'foo',\n                datetime: new \\DateTimeImmutable(\"@0\"),\n            ),\n        ];\n\n        $this->assertEquals(\n            [\n                [\n                    'meh',\n                    'log',\n                    'unknown',\n                    'info',\n                ],\n                [\n                    'foo',\n                    'log2',\n                    'unknown',\n                    'warn',\n                ],\n            ],\n            $formatter->formatBatch($records)\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/ElasticaFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\Test\\MonologTestCase;\n\nclass ElasticaFormatterTest extends MonologTestCase\n{\n    public function setUp(): void\n    {\n        if (!class_exists(\"Elastica\\Document\")) {\n            $this->markTestSkipped(\"ruflin/elastica not installed\");\n        }\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\ElasticaFormatter::__construct\n     * @covers Monolog\\Formatter\\ElasticaFormatter::format\n     * @covers Monolog\\Formatter\\ElasticaFormatter::getDocument\n     */\n    public function testFormat()\n    {\n        // test log message\n        $msg = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['foo' => 7, 'bar', 'class' => new \\stdClass],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n        );\n\n        // expected values\n        $expected = $msg->toArray();\n        $expected['datetime'] = '1970-01-01T00:00:00.000000+00:00';\n        $expected['context'] = [\n            'class' => ['stdClass' => []],\n            'foo' => 7,\n            0 => 'bar',\n        ];\n\n        // format log message\n        $formatter = new ElasticaFormatter('my_index', 'doc_type');\n        $doc = $formatter->format($msg);\n        $this->assertInstanceOf('Elastica\\Document', $doc);\n\n        // Document parameters\n        $this->assertEquals('my_index', $doc->getIndex());\n        if (method_exists($doc, 'getType')) {\n            $this->assertEquals('doc_type', $doc->getType());\n        }\n\n        // Document data values\n        $data = $doc->getData();\n        foreach (array_keys($expected) as $key) {\n            $this->assertEquals($expected[$key], $data[$key]);\n        }\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\ElasticaFormatter::getIndex\n     * @covers Monolog\\Formatter\\ElasticaFormatter::getType\n     */\n    public function testGetters()\n    {\n        $formatter = new ElasticaFormatter('my_index', 'doc_type');\n        $this->assertEquals('my_index', $formatter->getIndex());\n        $this->assertEquals('doc_type', $formatter->getType());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/ElasticsearchFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\Test\\MonologTestCase;\n\nclass ElasticsearchFormatterTest extends MonologTestCase\n{\n    /**\n     * @covers Monolog\\Formatter\\ElasticsearchFormatter::__construct\n     * @covers Monolog\\Formatter\\ElasticsearchFormatter::format\n     * @covers Monolog\\Formatter\\ElasticsearchFormatter::getDocument\n     */\n    public function testFormat()\n    {\n        // Test log message\n        $msg = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['foo' => 7, 'bar', 'class' => new \\stdClass],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n        );\n\n        // Expected values\n        $expected = $msg->toArray();\n        $expected['datetime'] = '1970-01-01T00:00:00+00:00';\n        $expected['context'] = [\n            'class' => ['stdClass' => []],\n            'foo' => 7,\n            0 => 'bar',\n        ];\n\n        // Format log message\n        $formatter = new ElasticsearchFormatter('my_index', 'doc_type');\n        $doc = $formatter->format($msg);\n        $this->assertIsArray($doc);\n\n        // Record parameters\n        $this->assertEquals('my_index', $doc['_index']);\n        $this->assertEquals('doc_type', $doc['_type']);\n\n        // Record data values\n        foreach (array_keys($expected) as $key) {\n            $this->assertEquals($expected[$key], $doc[$key]);\n        }\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\ElasticsearchFormatter::getIndex\n     * @covers Monolog\\Formatter\\ElasticsearchFormatter::getType\n     */\n    public function testGetters()\n    {\n        $formatter = new ElasticsearchFormatter('my_index', 'doc_type');\n        $this->assertEquals('my_index', $formatter->getIndex());\n        $this->assertEquals('doc_type', $formatter->getType());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/FlowdockFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\Test\\MonologTestCase;\n\nclass FlowdockFormatterTest extends MonologTestCase\n{\n    /**\n     * @covers Monolog\\Formatter\\FlowdockFormatter::format\n     */\n    public function testFormat()\n    {\n        $formatter = new FlowdockFormatter('test_source', 'source@test.com');\n        $record = $this->getRecord();\n\n        $expected = [\n            'source' => 'test_source',\n            'from_address' => 'source@test.com',\n            'subject' => 'in test_source: WARNING - test',\n            'content' => 'test',\n            'tags' => ['#logs', '#warning', '#test'],\n            'project' => 'test_source',\n        ];\n        $formatted = $formatter->format($record);\n\n        $this->assertEquals($expected, $formatted);\n    }\n\n    /**\n     * @ covers Monolog\\Formatter\\FlowdockFormatter::formatBatch\n     */\n    public function testFormatBatch()\n    {\n        $formatter = new FlowdockFormatter('test_source', 'source@test.com');\n        $records = [\n            $this->getRecord(Level::Warning),\n            $this->getRecord(Level::Debug),\n        ];\n        $formatted = $formatter->formatBatch($records);\n\n        $this->assertArrayHasKey('from_address', $formatted[0]);\n        $this->assertArrayHasKey('from_address', $formatted[1]);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/FluentdFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\Test\\MonologTestCase;\n\nclass FluentdFormatterTest extends MonologTestCase\n{\n    /**\n     * @covers Monolog\\Formatter\\FluentdFormatter::__construct\n     * @covers Monolog\\Formatter\\FluentdFormatter::isUsingLevelsInTag\n     */\n    public function testConstruct()\n    {\n        $formatter = new FluentdFormatter();\n        $this->assertEquals(false, $formatter->isUsingLevelsInTag());\n        $formatter = new FluentdFormatter(false);\n        $this->assertEquals(false, $formatter->isUsingLevelsInTag());\n        $formatter = new FluentdFormatter(true);\n        $this->assertEquals(true, $formatter->isUsingLevelsInTag());\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\FluentdFormatter::format\n     */\n    public function testFormat()\n    {\n        $record = $this->getRecord(Level::Warning, datetime: new \\DateTimeImmutable(\"@0\"));\n\n        $formatter = new FluentdFormatter();\n        $this->assertEquals(\n            '[\"test\",0,{\"message\":\"test\",\"context\":[],\"extra\":[],\"level\":300,\"level_name\":\"WARNING\"}]',\n            $formatter->format($record)\n        );\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\FluentdFormatter::format\n     */\n    public function testFormatWithTag()\n    {\n        $record = $this->getRecord(Level::Error, datetime: new \\DateTimeImmutable(\"@0\"));\n\n        $formatter = new FluentdFormatter(true);\n        $this->assertEquals(\n            '[\"test.error\",0,{\"message\":\"test\",\"context\":[],\"extra\":[]}]',\n            $formatter->format($record)\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/GelfMessageFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\Test\\MonologTestCase;\n\nclass GelfMessageFormatterTest extends MonologTestCase\n{\n    public function setUp(): void\n    {\n        if (!class_exists('\\Gelf\\Message')) {\n            $this->markTestSkipped(\"graylog2/gelf-php is not installed\");\n        }\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\GelfMessageFormatter::format\n     */\n    public function testDefaultFormatter()\n    {\n        $formatter = new GelfMessageFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            datetime: new \\DateTimeImmutable(\"@0\"),\n        );\n\n        $message = $formatter->format($record);\n\n        $this->assertInstanceOf('Gelf\\Message', $message);\n        $this->assertEquals(0, $message->getTimestamp());\n        $this->assertEquals('log', $message->getShortMessage());\n        $this->assertEquals('meh', $message->getAdditional('facility'));\n        $this->assertEquals(false, $message->hasAdditional('line'));\n        $this->assertEquals(false, $message->hasAdditional('file'));\n        $this->assertEquals($this->isLegacy() ? 3 : 'error', $message->getLevel());\n        $this->assertNotEmpty($message->getHost());\n\n        $formatter = new GelfMessageFormatter('mysystem');\n\n        $message = $formatter->format($record);\n\n        $this->assertInstanceOf('Gelf\\Message', $message);\n        $this->assertEquals('mysystem', $message->getHost());\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\GelfMessageFormatter::format\n     */\n    public function testFormatWithFileAndLine()\n    {\n        $formatter = new GelfMessageFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger'],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['file' => 'test', 'line' => 14, 0 => 'foo'],\n        );\n\n        $message = $formatter->format($record);\n\n        $this->assertInstanceOf('Gelf\\Message', $message);\n        $this->assertEquals('test', $message->getAdditional('file'));\n        $this->assertEquals(14, $message->getAdditional('line'));\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\GelfMessageFormatter::format\n     */\n    public function testFormatWithContext()\n    {\n        $formatter = new GelfMessageFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger', 'trueBool' => true, 'falseBool' => false],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['key' => 'pair'],\n        );\n\n        $message = $formatter->format($record);\n\n        $this->assertInstanceOf('Gelf\\Message', $message);\n\n        $message_array = $message->toArray();\n\n        $this->assertArrayHasKey('_ctxt_from', $message_array);\n        $this->assertEquals('logger', $message_array['_ctxt_from']);\n        $this->assertArrayHasKey('_ctxt_trueBool', $message_array);\n        $this->assertEquals(1, $message_array['_ctxt_trueBool']);\n        $this->assertArrayHasKey('_ctxt_falseBool', $message_array);\n        $this->assertEquals(0, $message_array['_ctxt_falseBool']);\n\n        // Test with extraPrefix\n        $formatter = new GelfMessageFormatter(null, null, 'CTX');\n        $message = $formatter->format($record);\n\n        $this->assertInstanceOf('Gelf\\Message', $message);\n\n        $message_array = $message->toArray();\n\n        $this->assertArrayHasKey('_CTXfrom', $message_array);\n        $this->assertEquals('logger', $message_array['_CTXfrom']);\n        $this->assertArrayHasKey('_CTXtrueBool', $message_array);\n        $this->assertEquals(1, $message_array['_CTXtrueBool']);\n        $this->assertArrayHasKey('_CTXfalseBool', $message_array);\n        $this->assertEquals(0, $message_array['_CTXfalseBool']);\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\GelfMessageFormatter::format\n     */\n    public function testFormatWithContextContainingException()\n    {\n        $formatter = new GelfMessageFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger', 'exception' => [\n                'class' => '\\Exception',\n                'file'  => '/some/file/in/dir.php:56',\n                'trace' => ['/some/file/1.php:23', '/some/file/2.php:3'],\n            ]],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n        );\n\n        $message = $formatter->format($record);\n\n        $this->assertInstanceOf('Gelf\\Message', $message);\n\n        $this->assertEquals(\"/some/file/in/dir.php\", $message->getAdditional('file'));\n        $this->assertEquals(\"56\", $message->getAdditional('line'));\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\GelfMessageFormatter::format\n     */\n    public function testFormatWithExtra()\n    {\n        $formatter = new GelfMessageFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger'],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['key' => 'pair', 'trueBool' => true, 'falseBool' => false],\n        );\n\n        $message = $formatter->format($record);\n\n        $this->assertInstanceOf('Gelf\\Message', $message);\n\n        $message_array = $message->toArray();\n\n        $this->assertArrayHasKey('_key', $message_array);\n        $this->assertEquals('pair', $message_array['_key']);\n        $this->assertArrayHasKey('_trueBool', $message_array);\n        $this->assertEquals(1, $message_array['_trueBool']);\n        $this->assertArrayHasKey('_falseBool', $message_array);\n        $this->assertEquals(0, $message_array['_falseBool']);\n\n        // Test with extraPrefix\n        $formatter = new GelfMessageFormatter(null, 'EXT');\n        $message = $formatter->format($record);\n\n        $this->assertInstanceOf('Gelf\\Message', $message);\n\n        $message_array = $message->toArray();\n\n        $this->assertArrayHasKey('_EXTkey', $message_array);\n        $this->assertEquals('pair', $message_array['_EXTkey']);\n        $this->assertArrayHasKey('_EXTtrueBool', $message_array);\n        $this->assertEquals(1, $message_array['_EXTtrueBool']);\n        $this->assertArrayHasKey('_EXTfalseBool', $message_array);\n        $this->assertEquals(0, $message_array['_EXTfalseBool']);\n    }\n\n    public function testFormatWithLargeData()\n    {\n        $formatter = new GelfMessageFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['exception' => str_repeat(' ', 32767)],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['key' => str_repeat(' ', 32767)],\n        );\n        $message = $formatter->format($record);\n        $messageArray = $message->toArray();\n\n        // 200 for padding + metadata\n        $length = 200;\n\n        foreach ($messageArray as $key => $value) {\n            if (!\\in_array($key, ['level', 'timestamp']) && \\is_string($value)) {\n                $length += \\strlen($value);\n            }\n        }\n\n        $this->assertLessThanOrEqual(65792, $length, 'The message length is no longer than the maximum allowed length');\n    }\n\n    public function testFormatWithUnlimitedLength()\n    {\n        $formatter = new GelfMessageFormatter('LONG_SYSTEM_NAME', null, 'ctxt_', PHP_INT_MAX);\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['exception' => str_repeat(' ', 32767 * 2)],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['key' => str_repeat(' ', 32767 * 2)],\n        );\n        $message = $formatter->format($record);\n        $messageArray = $message->toArray();\n\n        // 200 for padding + metadata\n        $length = 200;\n\n        foreach ($messageArray as $key => $value) {\n            if (!\\in_array($key, ['level', 'timestamp'])) {\n                $length += \\strlen($value);\n            }\n        }\n\n        $this->assertGreaterThanOrEqual(131289, $length, 'The message should not be truncated');\n    }\n\n    public function testFormatWithLargeCyrillicData()\n    {\n        $formatter = new GelfMessageFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            str_repeat('в', 32767),\n            channel: 'meh',\n            context: ['exception' => str_repeat('а', 32767)],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['key' => str_repeat('б', 32767)],\n        );\n        $message = $formatter->format($record);\n        $messageArray = $message->toArray();\n\n        $messageString = json_encode($messageArray);\n\n        $this->assertIsString($messageString);\n    }\n\n    private function isLegacy()\n    {\n        return interface_exists('\\Gelf\\IMessagePublisher');\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/GoogleCloudLoggingFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse DateTimeInterface;\nuse Monolog\\Test\\MonologTestCase;\nuse function json_decode;\n\nclass GoogleCloudLoggingFormatterTest extends MonologTestCase\n{\n    /**\n     * @test\n     *\n     * @covers \\Monolog\\Formatter\\JsonFormatter\n     * @covers \\Monolog\\Formatter\\GoogleCloudLoggingFormatter::normalizeRecord\n     */\n    public function formatProvidesRfc3339Timestamps(): void\n    {\n        $formatter = new GoogleCloudLoggingFormatter();\n        $record = $this->getRecord();\n\n        $formatted_decoded = json_decode($formatter->format($record), true);\n        $this->assertArrayNotHasKey(\"datetime\", $formatted_decoded);\n        $this->assertArrayHasKey(\"time\", $formatted_decoded);\n        $this->assertSame($record->datetime->format(DateTimeInterface::RFC3339_EXTENDED), $formatted_decoded[\"time\"]);\n    }\n\n    /**\n     * @test\n     *\n     * @covers \\Monolog\\Formatter\\JsonFormatter\n     * @covers \\Monolog\\Formatter\\GoogleCloudLoggingFormatter::normalizeRecord\n     */\n    public function formatIntroducesLogSeverity(): void\n    {\n        $formatter = new GoogleCloudLoggingFormatter();\n        $record = $this->getRecord();\n\n        $formatted_decoded = json_decode($formatter->format($record), true);\n        $this->assertArrayNotHasKey(\"level\", $formatted_decoded);\n        $this->assertArrayNotHasKey(\"level_name\", $formatted_decoded);\n        $this->assertArrayHasKey(\"severity\", $formatted_decoded);\n        $this->assertSame($record->level->getName(), $formatted_decoded[\"severity\"]);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/JsonFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Exception;\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\nuse JsonSerializable;\nuse Monolog\\Test\\MonologTestCase;\n\nclass JsonFormatterTest extends MonologTestCase\n{\n    /**\n     * @covers Monolog\\Formatter\\JsonFormatter::__construct\n     * @covers Monolog\\Formatter\\JsonFormatter::getBatchMode\n     * @covers Monolog\\Formatter\\JsonFormatter::isAppendingNewlines\n     */\n    public function testConstruct()\n    {\n        $formatter = new JsonFormatter();\n        $this->assertEquals(JsonFormatter::BATCH_MODE_JSON, $formatter->getBatchMode());\n        $this->assertEquals(true, $formatter->isAppendingNewlines());\n        $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES, false);\n        $this->assertEquals(JsonFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode());\n        $this->assertEquals(false, $formatter->isAppendingNewlines());\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\JsonFormatter::format\n     */\n    public function testFormat()\n    {\n        $formatter = new JsonFormatter();\n        $record = $this->getRecord();\n        $this->assertEquals(json_encode($record->toArray(), JSON_FORCE_OBJECT).\"\\n\", $formatter->format($record));\n\n        $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);\n        $record = $this->getRecord();\n        $this->assertEquals('{\"message\":\"test\",\"context\":{},\"level\":300,\"level_name\":\"WARNING\",\"channel\":\"test\",\"datetime\":\"'.$record->datetime->format('Y-m-d\\TH:i:s.uP').'\",\"extra\":{}}', $formatter->format($record));\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\JsonFormatter::format\n     */\n    public function testFormatWithPrettyPrint()\n    {\n        $formatter = new JsonFormatter();\n        $formatter->setJsonPrettyPrint(true);\n        $record = $this->getRecord();\n        $this->assertEquals(json_encode($record->toArray(), JSON_PRETTY_PRINT | JSON_FORCE_OBJECT).\"\\n\", $formatter->format($record));\n\n        $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);\n        $formatter->setJsonPrettyPrint(true);\n        $record = $this->getRecord();\n        $this->assertEquals(\n            '{\n    \"message\": \"test\",\n    \"context\": {},\n    \"level\": 300,\n    \"level_name\": \"WARNING\",\n    \"channel\": \"test\",\n    \"datetime\": \"'.$record->datetime->format('Y-m-d\\TH:i:s.uP').'\",\n    \"extra\": {}\n}',\n            $formatter->format($record)\n        );\n\n        $formatter->setJsonPrettyPrint(false);\n        $record = $this->getRecord();\n        $this->assertEquals('{\"message\":\"test\",\"context\":{},\"level\":300,\"level_name\":\"WARNING\",\"channel\":\"test\",\"datetime\":\"'.$record->datetime->format('Y-m-d\\TH:i:s.uP').'\",\"extra\":{}}', $formatter->format($record));\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\JsonFormatter::formatBatch\n     * @covers Monolog\\Formatter\\JsonFormatter::formatBatchJson\n     */\n    public function testFormatBatch()\n    {\n        $formatter = new JsonFormatter();\n        $records = [\n            $this->getRecord(Level::Warning),\n            $this->getRecord(Level::Debug),\n        ];\n        $expected = array_map(fn (LogRecord $record) => json_encode($record->toArray(), JSON_FORCE_OBJECT), $records);\n        $this->assertEquals('['.implode(',', $expected).']', $formatter->formatBatch($records));\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\JsonFormatter::formatBatch\n     * @covers Monolog\\Formatter\\JsonFormatter::formatBatchNewlines\n     */\n    public function testFormatBatchNewlines()\n    {\n        $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_NEWLINES);\n        $records = [\n            $this->getRecord(Level::Warning),\n            $this->getRecord(Level::Debug),\n        ];\n        $expected = array_map(fn (LogRecord $record) => json_encode($record->toArray(), JSON_FORCE_OBJECT), $records);\n        $this->assertEquals(implode(\"\\n\", $expected), $formatter->formatBatch($records));\n    }\n\n    public function testDefFormatWithException()\n    {\n        $formatter = new JsonFormatter();\n        $exception = new \\RuntimeException('Foo');\n        $formattedException = $this->formatException($exception);\n\n        $message = $this->formatRecordWithExceptionInContext($formatter, $exception);\n\n        $this->assertContextContainsFormattedException($formattedException, $message);\n    }\n\n    public function testBasePathWithException(): void\n    {\n        $formatter = new JsonFormatter();\n        $formatter->setBasePath(\\dirname(\\dirname(\\dirname(__DIR__))));\n        $exception = new \\RuntimeException('Foo');\n\n        $message = $this->formatRecordWithExceptionInContext($formatter, $exception);\n\n        $parsed = json_decode($message, true);\n        self::assertSame('tests/Monolog/Formatter/JsonFormatterTest.php:' . (__LINE__ - 5), $parsed['context']['exception']['file']);\n    }\n\n    public function testDefFormatWithPreviousException()\n    {\n        $formatter = new JsonFormatter();\n        $exception = new \\RuntimeException('Foo', 0, new \\LogicException('Wut?'));\n        $formattedPrevException = $this->formatException($exception->getPrevious());\n        $formattedException = $this->formatException($exception, $formattedPrevException);\n\n        $message = $this->formatRecordWithExceptionInContext($formatter, $exception);\n\n        $this->assertContextContainsFormattedException($formattedException, $message);\n    }\n\n    public function testDefFormatWithThrowable()\n    {\n        $formatter = new JsonFormatter();\n        $throwable = new \\Error('Foo');\n        $formattedThrowable = $this->formatException($throwable);\n\n        $message = $this->formatRecordWithExceptionInContext($formatter, $throwable);\n\n        $this->assertContextContainsFormattedException($formattedThrowable, $message);\n    }\n\n    public function testMaxNormalizeDepth()\n    {\n        $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, true);\n        $formatter->setMaxNormalizeDepth(1);\n        $throwable = new \\Error('Foo');\n\n        $message = $this->formatRecordWithExceptionInContext($formatter, $throwable);\n\n        $this->assertContextContainsFormattedException('\"Over 1 levels deep, aborting normalization\"', $message);\n    }\n\n    public function testMaxNormalizeItemCountWith0ItemsMax()\n    {\n        $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, true);\n        $formatter->setMaxNormalizeDepth(9);\n        $formatter->setMaxNormalizeItemCount(0);\n        $throwable = new \\Error('Foo');\n\n        $message = $this->formatRecordWithExceptionInContext($formatter, $throwable);\n\n        $this->assertEquals(\n            '{\"...\":\"Over 0 items (7 total), aborting normalization\"}'.\"\\n\",\n            $message\n        );\n    }\n\n    public function testMaxNormalizeItemCountWith2ItemsMax()\n    {\n        $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, true);\n        $formatter->setMaxNormalizeDepth(9);\n        $formatter->setMaxNormalizeItemCount(2);\n        $throwable = new \\Error('Foo');\n\n        $message = $this->formatRecordWithExceptionInContext($formatter, $throwable);\n\n        $this->assertEquals(\n            '{\"message\":\"foobar\",\"context\":{\"exception\":{\"class\":\"Error\",\"message\":\"Foo\",\"code\":0,\"file\":\"'.__FILE__.':'.(__LINE__ - 5).'\"}},\"...\":\"Over 2 items (7 total), aborting normalization\"}'.\"\\n\",\n            $message\n        );\n    }\n\n    public function testDefFormatWithResource()\n    {\n        $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);\n        $record = $this->getRecord(\n            context: ['field_resource' => opendir(__DIR__)],\n        );\n        $this->assertEquals('{\"message\":\"test\",\"context\":{\"field_resource\":\"[resource(stream)]\"},\"level\":300,\"level_name\":\"WARNING\",\"channel\":\"test\",\"datetime\":\"'.$record->datetime->format('Y-m-d\\TH:i:s.uP').'\",\"extra\":{}}', $formatter->format($record));\n    }\n\n    /**\n     * @internal param string $exception\n     */\n    private function assertContextContainsFormattedException(string $expected, string $actual)\n    {\n        $this->assertEquals(\n            '{\"message\":\"foobar\",\"context\":{\"exception\":'.$expected.'},\"level\":500,\"level_name\":\"CRITICAL\",\"channel\":\"core\",\"datetime\":\"2022-02-22T00:00:00+00:00\",\"extra\":{}}'.\"\\n\",\n            $actual\n        );\n    }\n\n    private function formatRecordWithExceptionInContext(JsonFormatter $formatter, \\Throwable $exception): string\n    {\n        $message = $formatter->format($this->getRecord(\n            Level::Critical,\n            'foobar',\n            channel: 'core',\n            context: ['exception' => $exception],\n            datetime: new \\DateTimeImmutable('2022-02-22 00:00:00'),\n        ));\n\n        return $message;\n    }\n\n    /**\n     * @param \\Exception|\\Throwable $exception\n     */\n    private function formatExceptionFilePathWithLine($exception): string\n    {\n        $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;\n        $path = substr(json_encode($exception->getFile(), $options), 1, -1);\n\n        return $path . ':' . $exception->getLine();\n    }\n\n    /**\n     * @param \\Exception|\\Throwable $exception\n     */\n    private function formatException($exception, ?string $previous = null): string\n    {\n        $formattedException =\n            '{\"class\":\"' . \\get_class($exception) .\n            '\",\"message\":\"' . $exception->getMessage() .\n            '\",\"code\":' . $exception->getCode() .\n            ',\"file\":\"' . $this->formatExceptionFilePathWithLine($exception) .\n            ($previous ? '\",\"previous\":' . $previous : '\"') .\n            '}';\n\n        return $formattedException;\n    }\n\n    public function testNormalizeHandleLargeArraysWithExactly1000Items()\n    {\n        $formatter = new NormalizerFormatter();\n        $largeArray = range(1, 1000);\n\n        $res = $formatter->format($this->getRecord(\n            Level::Critical,\n            'bar',\n            channel: 'test',\n            context: [$largeArray],\n        ));\n\n        $this->assertCount(1000, $res['context'][0]);\n        $this->assertArrayNotHasKey('...', $res['context'][0]);\n    }\n\n    public function testNormalizeHandleLargeArrays()\n    {\n        $formatter = new NormalizerFormatter();\n        $largeArray = range(1, 2000);\n\n        $res = $formatter->format($this->getRecord(\n            Level::Critical,\n            'bar',\n            channel: 'test',\n            context: [$largeArray],\n        ));\n\n        $this->assertCount(1001, $res['context'][0]);\n        $this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']);\n    }\n\n    public function testCanNormalizeIncompleteObject(): void\n    {\n        $serialized = \"O:17:\\\"Monolog\\TestClass\\\":1:{s:23:\\\"\\x00Monolog\\TestClass\\x00name\\\";s:4:\\\"test\\\";}\";\n        $object = unserialize($serialized);\n\n        $formatter = new JsonFormatter();\n        $record = $this->getRecord(context: ['object' => $object], datetime: new \\DateTimeImmutable('2022-02-22 00:00:00'));\n        $result = $formatter->format($record);\n\n        self::assertSame('{\"message\":\"test\",\"context\":{\"object\":{\"__PHP_Incomplete_Class_Name\":\"Monolog\\\\\\\\TestClass\"}},\"level\":300,\"level_name\":\"WARNING\",\"channel\":\"test\",\"datetime\":\"2022-02-22T00:00:00+00:00\",\"extra\":{}}'.\"\\n\", $result);\n    }\n\n    public function testEmptyContextAndExtraFieldsCanBeIgnored()\n    {\n        $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, true, true);\n\n        $record = $formatter->format($this->getRecord(\n            Level::Debug,\n            'Testing',\n            channel: 'test',\n            datetime: new \\DateTimeImmutable('2022-02-22 00:00:00'),\n        ));\n\n        $this->assertSame(\n            '{\"message\":\"Testing\",\"level\":100,\"level_name\":\"DEBUG\",\"channel\":\"test\",\"datetime\":\"2022-02-22T00:00:00+00:00\"}'.\"\\n\",\n            $record\n        );\n    }\n\n    public function testFormatObjects()\n    {\n        $formatter = new JsonFormatter();\n\n        $record = $formatter->format($this->getRecord(\n            Level::Debug,\n            'Testing',\n            channel: 'test',\n            datetime: new \\DateTimeImmutable('2022-02-22 00:00:00'),\n            context: [\n                'public' => new TestJsonNormPublic,\n                'private' => new TestJsonNormPrivate,\n                'withToStringAndJson' => new TestJsonNormWithToStringAndJson,\n                'withToString' => new TestJsonNormWithToString,\n            ],\n        ));\n\n        $this->assertSame(\n            '{\"message\":\"Testing\",\"context\":{\"public\":{\"foo\":\"fooValue\"},\"private\":{},\"withToStringAndJson\":[\"json serialized\"],\"withToString\":\"stringified\"},\"level\":100,\"level_name\":\"DEBUG\",\"channel\":\"test\",\"datetime\":\"2022-02-22T00:00:00+00:00\",\"extra\":{}}'.\"\\n\",\n            $record\n        );\n    }\n\n    public function testNormalizeHandleExceptionInToString(): void\n    {\n        $formatter = new JsonFormatter();\n\n        $res = $formatter->format($this->getRecord(\n            Level::Critical,\n            'bar',\n            datetime: new \\DateTimeImmutable('2025-05-19 00:00:00'),\n            channel: 'test',\n            context: ['object' => new TestJsonNormWithFailingToString],\n        ));\n\n        $this->assertSame(\n            '{\"message\":\"bar\",\"context\":{\"object\":\"Monolog\\\\\\\\Formatter\\\\\\\\TestJsonNormWithFailingToString\"},\"level\":500,\"level_name\":\"CRITICAL\",\"channel\":\"test\",\"datetime\":\"2025-05-19T00:00:00+00:00\",\"extra\":{}}'.\"\\n\",\n            $res,\n        );\n    }\n}\n\nclass TestJsonNormPublic\n{\n    public $foo = 'fooValue';\n}\n\nclass TestJsonNormPrivate\n{\n    private $foo = 'fooValue';\n}\n\nclass TestJsonNormWithToStringAndJson implements JsonSerializable\n{\n    public function jsonSerialize(): mixed\n    {\n        return ['json serialized'];\n    }\n\n    public function __toString()\n    {\n        return 'SHOULD NOT SHOW UP';\n    }\n}\n\nclass TestJsonNormWithToString\n{\n    public function __toString()\n    {\n        return 'stringified';\n    }\n}\n\nclass TestJsonNormWithFailingToString\n{\n    public function __toString()\n    {\n        throw new Exception('Whatever');\n    }\n}"
  },
  {
    "path": "tests/Monolog/Formatter/LineFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Test\\MonologTestCase;\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse RuntimeException;\n\n/**\n * @covers Monolog\\Formatter\\LineFormatter\n */\nclass LineFormatterTest extends MonologTestCase\n{\n    public function testDefFormatWithString()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $message = $formatter->format($this->getRecord(\n            Level::Warning,\n            'foo',\n            channel: 'log',\n        ));\n        $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo [] []'.\"\\n\", $message);\n    }\n\n    public function testDefFormatWithArrayContext()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $message = $formatter->format($this->getRecord(\n            Level::Error,\n            'foo',\n            channel: 'meh',\n            context: [\n                'foo' => 'bar',\n                'baz' => 'qux',\n                'bool' => false,\n                'null' => null,\n            ],\n        ));\n        $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foo {\"foo\":\"bar\",\"baz\":\"qux\",\"bool\":false,\"null\":null} []'.\"\\n\", $message);\n    }\n\n    public function testDefFormatExtras()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $message = $formatter->format($this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            extra: ['ip' => '127.0.0.1'],\n        ));\n        $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] {\"ip\":\"127.0.0.1\"}'.\"\\n\", $message);\n    }\n\n    public function testFormatExtras()\n    {\n        $formatter = new LineFormatter(\"[%datetime%] %channel%.%level_name%: %message% %context% %extra.file% %extra%\\n\", 'Y-m-d');\n        $message = $formatter->format($this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            extra: ['ip' => '127.0.0.1', 'file' => 'test'],\n        ));\n        $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] test {\"ip\":\"127.0.0.1\"}'.\"\\n\", $message);\n    }\n\n    public function testContextAndExtraOptionallyNotShownIfEmpty()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d', false, true);\n        $message = $formatter->format($this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n        ));\n        $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log  '.\"\\n\", $message);\n    }\n\n    public function testContextAndExtraReplacement()\n    {\n        $formatter = new LineFormatter('%context.foo% => %extra.foo%');\n        $message = $formatter->format($this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['foo' => 'bar'],\n            extra: ['foo' => 'xbar'],\n        ));\n\n        $this->assertEquals('bar => xbar', $message);\n    }\n\n    public function testDefFormatWithObject()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $message = $formatter->format($this->getRecord(\n            Level::Error,\n            'foobar',\n            channel: 'meh',\n            context: [],\n            extra: ['foo' => new TestFoo, 'bar' => new TestBar, 'baz' => [], 'res' => fopen('php://memory', 'rb')],\n        ));\n\n        $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {\"foo\":{\"Monolog\\\\\\\\Formatter\\\\\\\\TestFoo\":{\"foo\":\"fooValue\"}},\"bar\":{\"Monolog\\\\\\\\Formatter\\\\\\\\TestBar\":\"bar\"},\"baz\":[],\"res\":\"[resource(stream)]\"}'.\"\\n\", $message);\n    }\n\n    public function testDefFormatWithException()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $message = $formatter->format($this->getRecord(\n            Level::Critical,\n            'foobar',\n            channel: 'core',\n            context: ['exception' => new \\RuntimeException('Foo')],\n        ));\n\n        $path = str_replace('\\\\/', '/', json_encode(__FILE__));\n\n        $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {\"exception\":\"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 5).')\"} []'.\"\\n\", $message);\n    }\n\n    public function testDefFormatWithExceptionAndStacktrace()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $formatter->includeStacktraces();\n        $message = $formatter->format($this->getRecord(\n            Level::Critical,\n            'foobar',\n            channel: 'core',\n            context: ['exception' => new \\RuntimeException('Foo')],\n        ));\n\n        $path = str_replace('\\\\/', '/', json_encode(__FILE__));\n\n        $this->assertMatchesRegularExpression('{^\\['.date('Y-m-d').'] core\\.CRITICAL: foobar \\{\"exception\":\"\\[object] \\(RuntimeException\\(code: 0\\): Foo at '.preg_quote(substr($path, 1, -1)).':'.(__LINE__ - 5).'\\)\\n\\[stacktrace]\\n#0}', $message);\n    }\n\n    public function testInlineLineBreaksRespectsEscapedBackslashes()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $formatter->allowInlineLineBreaks();\n\n        self::assertSame('{\"test\":\"foo'.\"\\n\".'bar\\\\\\\\name-with-n\"}', $formatter->stringify([\"test\" => \"foo\\nbar\\\\name-with-n\"]));\n        self::assertSame('[\"indexed'.\"\\n\".'arrays'.\"\\n\".'without'.\"\\n\".'key\",\"foo'.\"\\n\".'bar\\\\\\\\name-with-n\"]', $formatter->stringify([\"indexed\\narrays\\nwithout\\nkey\", \"foo\\nbar\\\\name-with-n\"]));\n        self::assertSame('[{\"first\":\"multi-dimensional'.\"\\n\".'arrays\"},{\"second\":\"foo'.\"\\n\".'bar\\\\\\\\name-with-n\"}]', $formatter->stringify([[\"first\" => \"multi-dimensional\\narrays\"], [\"second\" => \"foo\\nbar\\\\name-with-n\"]]));\n    }\n\n    public function testDefFormatWithExceptionAndStacktraceParserFull()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $formatter->includeStacktraces(true, function ($line) {\n            return $line;\n        });\n\n        $message = $formatter->format($this->getRecord(Level::Critical, context: ['exception' => new \\RuntimeException('Foo')]));\n\n        $trace = explode('[stacktrace]', $message, 2)[1];\n\n        $this->assertStringContainsString('TestSuite.php', $trace);\n        $this->assertStringContainsString('TestRunner.php', $trace);\n    }\n\n    public function testDefFormatWithExceptionAndStacktraceParserCustom()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $formatter->includeStacktraces(true, function ($line) {\n            if (strpos($line, 'TestSuite.php') === false) {\n                return $line;\n            }\n        });\n\n        $message = $formatter->format($this->getRecord(Level::Critical, context: ['exception' => new \\RuntimeException('Foo')]));\n\n        $trace = explode('[stacktrace]', $message, 2)[1];\n\n        $this->assertStringNotContainsString('TestSuite.php', $trace);\n        $this->assertStringContainsString('TestRunner.php', $trace);\n    }\n\n    public function testDefFormatWithExceptionAndStacktraceParserEmpty()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $formatter->includeStacktraces(true, function ($line) {\n            return null;\n        });\n\n        $message = $formatter->format($this->getRecord(Level::Critical, context: ['exception' => new \\RuntimeException('Foo')]));\n        $this->assertStringNotContainsString('[stacktrace]', $message);\n        $this->assertStringEndsWith('\"} []' . PHP_EOL, $message);\n    }\n\n    public function testDefFormatWithPreviousException()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $previous = new \\LogicException('Wut?');\n        $message = $formatter->format($this->getRecord(\n            Level::Critical,\n            'foobar',\n            channel: 'core',\n            context: ['exception' => new \\RuntimeException('Foo', 0, $previous)],\n        ));\n\n        $path = str_replace('\\\\/', '/', json_encode(__FILE__));\n\n        $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {\"exception\":\"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 5).')\\n[previous exception] [object] (LogicException(code: 0): Wut? at '.substr($path, 1, -1).':'.(__LINE__ - 10).')\"} []'.\"\\n\", $message);\n    }\n\n    public function testDefFormatWithSoapFaultException()\n    {\n        if (!class_exists('SoapFault')) {\n            $this->markTestSkipped('Requires the soap extension');\n        }\n\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $message = $formatter->format($this->getRecord(\n            Level::Critical,\n            'foobar',\n            channel: 'core',\n            context: ['exception' => new \\SoapFault('foo', 'bar', 'hello', 'world')],\n        ));\n\n        $path = str_replace('\\\\/', '/', json_encode(__FILE__));\n\n        $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {\"exception\":\"[object] (SoapFault(code: 0 faultcode: foo faultactor: hello detail: world): bar at '.substr($path, 1, -1).':'.(__LINE__ - 5).')\"} []'.\"\\n\", $message);\n\n        $message = $formatter->format($this->getRecord(\n            Level::Critical,\n            'foobar',\n            channel: 'core',\n            context: ['exception' => new \\SoapFault('foo', 'bar', 'hello', (object) ['bar' => (object) ['biz' => 'baz'], 'foo' => 'world'])],\n        ));\n\n        $path = str_replace('\\\\/', '/', json_encode(__FILE__));\n\n        $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {\"exception\":\"[object] (SoapFault(code: 0 faultcode: foo faultactor: hello detail: {\\\"bar\\\":{\\\"biz\\\":\\\"baz\\\"},\\\"foo\\\":\\\"world\\\"}): bar at '.substr($path, 1, -1).':'.(__LINE__ - 5).')\"} []'.\"\\n\", $message);\n    }\n\n    public function testBatchFormat()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $message = $formatter->formatBatch([\n            $this->getRecord(\n                Level::Critical,\n                'bar',\n                channel: 'test',\n            ),\n            $this->getRecord(\n                Level::Warning,\n                'foo',\n                channel: 'log',\n            ),\n        ]);\n        $this->assertEquals('['.date('Y-m-d').'] test.CRITICAL: bar [] []'.\"\\n\".'['.date('Y-m-d').'] log.WARNING: foo [] []'.\"\\n\", $message);\n    }\n\n    public function testFormatShouldStripInlineLineBreaks()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d');\n        $message = $formatter->format($this->getRecord(message: \"foo\\nbar\"));\n\n        $this->assertMatchesRegularExpression('/foo bar/', $message);\n    }\n\n    public function testFormatShouldNotStripInlineLineBreaksWhenFlagIsSet()\n    {\n        $formatter = new LineFormatter(null, 'Y-m-d', true);\n        $message = $formatter->format($this->getRecord(message: \"foo\\nbar\"));\n\n        $this->assertMatchesRegularExpression('/foo\\nbar/', $message);\n    }\n\n    public function testIndentStackTraces(): void\n    {\n        $formatter = new LineFormatter();\n        $formatter->includeStacktraces();\n        //$formatter->allowInlineLineBreaks();\n        $formatter->indentStackTraces('    ');\n        $message = $formatter->format($this->getRecord(message: \"foo\", context: ['exception' => new RuntimeException('lala')]));\n\n        $this->assertStringContainsString('    [stacktrace]', $message);\n        $this->assertStringContainsString('    #0', $message);\n        $this->assertStringContainsString('    #1', $message);\n    }\n\n    public function testBasePath(): void\n    {\n        $formatter = new LineFormatter();\n        $formatter->includeStacktraces();\n        $formatter->setBasePath(\\dirname(\\dirname(\\dirname(__DIR__))));\n        $formatter->indentStackTraces('    ');\n        $message = $formatter->format($this->getRecord(message: \"foo\", context: ['exception' => new RuntimeException('lala')]));\n\n        $this->assertStringContainsString('    [stacktrace]', $message);\n        $this->assertStringContainsString('    #0 vendor/phpunit/phpunit/src/Framework/TestCase.php', $message);\n        $this->assertStringContainsString('    #1 vendor/phpunit/phpunit/', $message);\n    }\n\n    #[DataProvider('providerMaxLevelNameLength')]\n    public function testMaxLevelNameLength(?int $maxLength, Level $logLevel, string $expectedLevelName): void\n    {\n        $formatter = new LineFormatter();\n        $formatter->setMaxLevelNameLength($maxLength);\n        $message = $formatter->format($this->getRecord(message: \"foo\\nbar\", level: $logLevel));\n\n        $this->assertStringContainsString(\"test.$expectedLevelName:\", $message);\n    }\n\n    public static function providerMaxLevelNameLength(): array\n    {\n        return [\n            'info_no_max_length' => [\n                'maxLength' => null,\n                'logLevel' => Level::Info,\n                'expectedLevelName' => 'INFO',\n            ],\n\n            'error_max_length_3' => [\n                'maxLength' => 3,\n                'logLevel' => Level::Error,\n                'expectedLevelName' => 'ERR',\n            ],\n\n            'debug_max_length_2' => [\n                'maxLength' => 2,\n                'logLevel' => Level::Debug,\n                'expectedLevelName' => 'DE',\n            ],\n        ];\n    }\n}\n\nclass TestFoo\n{\n    public string $foo = 'fooValue';\n}\n\nclass TestBar\n{\n    public function __toString()\n    {\n        return 'bar';\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/LogglyFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Test\\MonologTestCase;\n\nclass LogglyFormatterTest extends MonologTestCase\n{\n    /**\n     * @covers Monolog\\Formatter\\LogglyFormatter::__construct\n     */\n    public function testConstruct()\n    {\n        $formatter = new LogglyFormatter();\n        $this->assertEquals(LogglyFormatter::BATCH_MODE_NEWLINES, $formatter->getBatchMode());\n        $formatter = new LogglyFormatter(LogglyFormatter::BATCH_MODE_JSON);\n        $this->assertEquals(LogglyFormatter::BATCH_MODE_JSON, $formatter->getBatchMode());\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\LogglyFormatter::format\n     */\n    public function testFormat()\n    {\n        $formatter = new LogglyFormatter();\n        $record = $this->getRecord();\n        $formatted_decoded = json_decode($formatter->format($record), true);\n        $this->assertArrayNotHasKey(\"datetime\", $formatted_decoded);\n        $this->assertArrayHasKey(\"timestamp\", $formatted_decoded);\n        $this->assertEquals($record->datetime->format('Y-m-d\\TH:i:s.uO'), $formatted_decoded[\"timestamp\"]);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/LogmaticFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Test\\MonologTestCase;\n\n/**\n * @author Julien Breux <julien.breux@gmail.com>\n */\nclass LogmaticFormatterTest extends MonologTestCase\n{\n    /**\n     * @covers Monolog\\Formatter\\LogmaticFormatter::format\n     */\n    public function testFormat()\n    {\n        $formatter = new LogmaticFormatter();\n        $formatter->setHostname('testHostname');\n        $formatter->setAppName('testAppname');\n        $record = $this->getRecord();\n        $formatted_decoded = json_decode($formatter->format($record), true);\n        $this->assertArrayHasKey('hostname', $formatted_decoded);\n        $this->assertArrayHasKey('appname', $formatted_decoded);\n        $this->assertEquals('testHostname', $formatted_decoded['hostname']);\n        $this->assertEquals('testAppname', $formatted_decoded['appname']);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/LogstashFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\nuse Monolog\\Test\\MonologTestCase;\n\nclass LogstashFormatterTest extends MonologTestCase\n{\n    /**\n     * @covers Monolog\\Formatter\\LogstashFormatter::format\n     */\n    public function testDefaultFormatterV1()\n    {\n        $formatter = new LogstashFormatter('test', 'hostname');\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            datetime: new \\DateTimeImmutable(\"@0\"),\n        );\n\n        $message = json_decode($formatter->format($record), true);\n\n        $this->assertEquals(\"1970-01-01T00:00:00.000000+00:00\", $message['@timestamp']);\n        $this->assertEquals(\"1\", $message['@version']);\n        $this->assertEquals('log', $message['message']);\n        $this->assertEquals('meh', $message['channel']);\n        $this->assertEquals(Level::Error->getName(), $message['level']);\n        $this->assertEquals(Level::Error->value, $message['monolog_level']);\n        $this->assertEquals('test', $message['type']);\n        $this->assertEquals('hostname', $message['host']);\n\n        $formatter = new LogstashFormatter('mysystem');\n\n        $message = json_decode($formatter->format($record), true);\n\n        $this->assertEquals('mysystem', $message['type']);\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\LogstashFormatter::format\n     */\n    public function testFormatWithFileAndLineV1()\n    {\n        $formatter = new LogstashFormatter('test');\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger'],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['file' => 'test', 'line' => 14],\n        );\n\n        $message = json_decode($formatter->format($record), true);\n\n        $this->assertEquals('test', $message['extra']['file']);\n        $this->assertEquals(14, $message['extra']['line']);\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\LogstashFormatter::format\n     */\n    public function testFormatWithContextV1()\n    {\n        $formatter = new LogstashFormatter('test');\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger'],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['key' => 'pair'],\n        );\n\n        $message = json_decode($formatter->format($record), true);\n\n        $this->assertArrayHasKey('context', $message);\n        $this->assertArrayHasKey('from', $message['context']);\n        $this->assertEquals('logger', $message['context']['from']);\n\n        // Test with extraPrefix\n        $formatter = new LogstashFormatter('test', null, 'extra', 'CTX');\n        $message = json_decode($formatter->format($record), true);\n\n        $this->assertArrayHasKey('CTX', $message);\n        $this->assertArrayHasKey('from', $message['CTX']);\n        $this->assertEquals('logger', $message['CTX']['from']);\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\LogstashFormatter::format\n     */\n    public function testFormatWithExtraV1()\n    {\n        $formatter = new LogstashFormatter('test');\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger'],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['key' => 'pair'],\n        );\n\n        $message = json_decode($formatter->format($record), true);\n\n        $this->assertArrayHasKey('extra', $message);\n        $this->assertArrayHasKey('key', $message['extra']);\n        $this->assertEquals('pair', $message['extra']['key']);\n\n        // Test with extraPrefix\n        $formatter = new LogstashFormatter('test', null, 'EXTRA');\n        $message = json_decode($formatter->format($record), true);\n\n        $this->assertArrayHasKey('EXTRA', $message);\n        $this->assertArrayHasKey('key', $message['EXTRA']);\n        $this->assertEquals('pair', $message['EXTRA']['key']);\n    }\n\n    public function testFormatWithApplicationNameV1()\n    {\n        $formatter = new LogstashFormatter('app', 'test');\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger'],\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: ['key' => 'pair'],\n        );\n\n        $message = json_decode($formatter->format($record), true);\n\n        $this->assertArrayHasKey('type', $message);\n        $this->assertEquals('app', $message['type']);\n    }\n\n    public function testFormatWithLatin9Data()\n    {\n        $formatter = new LogstashFormatter('test', 'hostname');\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: '¯\\_(ツ)_/¯',\n            datetime: new \\DateTimeImmutable(\"@0\"),\n            extra: [\n                'user_agent' => \"\\xD6WN; FBCR/OrangeEspa\\xF1a; Vers\\xE3o/4.0; F\\xE4rist\",\n            ],\n        );\n\n        $message = json_decode($formatter->format($record), true);\n\n        $this->assertEquals(\"1970-01-01T00:00:00.000000+00:00\", $message['@timestamp']);\n        $this->assertEquals('log', $message['message']);\n        $this->assertEquals('¯\\_(ツ)_/¯', $message['channel']);\n        $this->assertEquals('ERROR', $message['level']);\n        $this->assertEquals('test', $message['type']);\n        $this->assertEquals('hostname', $message['host']);\n        $this->assertEquals('�WN; FBCR/OrangeEspa�a; Vers�o/4.0; F�rist', $message['extra']['user_agent']);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/MongoDBFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse MongoDB\\BSON\\ObjectId;\nuse MongoDB\\BSON\\Regex;\nuse MongoDB\\BSON\\UTCDateTime;\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\RequiresPhpExtension;\n\n/**\n * @author Florian Plattner <me@florianplattner.de>\n */\n#[RequiresPhpExtension('mongodb')]\nclass MongoDBFormatterTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public static function constructArgumentProvider()\n    {\n        return [\n            [1, true, 1, true],\n            [0, false, 0, false],\n        ];\n    }\n\n    #[DataProvider('constructArgumentProvider')]\n    public function testConstruct($traceDepth, $traceAsString, $expectedTraceDepth, $expectedTraceAsString)\n    {\n        $formatter = new MongoDBFormatter($traceDepth, $traceAsString);\n\n        $reflTrace = new \\ReflectionProperty($formatter, 'exceptionTraceAsString');\n        $this->assertEquals($expectedTraceAsString, $reflTrace->getValue($formatter));\n\n        $reflDepth = new \\ReflectionProperty($formatter, 'maxNestingLevel');\n        $this->assertEquals($expectedTraceDepth, $reflDepth->getValue($formatter));\n    }\n\n    public function testSimpleFormat()\n    {\n        $record = $this->getRecord(\n            message: 'some log message',\n            level: Level::Warning,\n            channel: 'test',\n            datetime: new \\DateTimeImmutable('2016-01-21T21:11:30.123456+00:00'),\n        );\n\n        $formatter = new MongoDBFormatter();\n        $formattedRecord = $formatter->format($record);\n\n        $this->assertCount(7, $formattedRecord);\n        $this->assertEquals('some log message', $formattedRecord['message']);\n        $this->assertEquals([], $formattedRecord['context']);\n        $this->assertEquals(Level::Warning->value, $formattedRecord['level']);\n        $this->assertEquals(Level::Warning->getName(), $formattedRecord['level_name']);\n        $this->assertEquals('test', $formattedRecord['channel']);\n        $this->assertInstanceOf(UTCDateTime::class, $formattedRecord['datetime']);\n        $this->assertEquals('1453410690123', $formattedRecord['datetime']->__toString());\n        $this->assertEquals([], $formattedRecord['extra']);\n    }\n\n    public function testRecursiveFormat()\n    {\n        $someObject = new \\stdClass();\n        $someObject->foo = 'something';\n        $someObject->bar = 'stuff';\n\n        $record = $this->getRecord(\n            message: 'some log message',\n            context: [\n                'stuff' => new \\DateTimeImmutable('1969-01-21T21:11:30.213000+00:00'),\n                'some_object' => $someObject,\n                'context_string' => 'some string',\n                'context_int' => 123456,\n                'except' => new \\Exception('exception message', 987),\n            ],\n            level: Level::Warning,\n            channel: 'test',\n            datetime: new \\DateTimeImmutable('2016-01-21T21:11:30.213000+00:00'),\n        );\n\n        $formatter = new MongoDBFormatter();\n        $formattedRecord = $formatter->format($record);\n\n        $this->assertCount(5, $formattedRecord['context']);\n        $this->assertInstanceOf(UTCDateTime::class, $formattedRecord['context']['stuff']);\n        $this->assertEquals('-29731710213', $formattedRecord['context']['stuff']->__toString());\n        $this->assertEquals(\n            [\n                'foo' => 'something',\n                'bar' => 'stuff',\n                'class' => 'stdClass',\n            ],\n            $formattedRecord['context']['some_object']\n        );\n        $this->assertEquals('some string', $formattedRecord['context']['context_string']);\n        $this->assertEquals(123456, $formattedRecord['context']['context_int']);\n\n        $this->assertCount(5, $formattedRecord['context']['except']);\n        $this->assertEquals('exception message', $formattedRecord['context']['except']['message']);\n        $this->assertEquals(987, $formattedRecord['context']['except']['code']);\n        $this->assertIsString($formattedRecord['context']['except']['file']);\n        $this->assertIsInt($formattedRecord['context']['except']['code']);\n        $this->assertIsString($formattedRecord['context']['except']['trace']);\n        $this->assertEquals('Exception', $formattedRecord['context']['except']['class']);\n    }\n\n    public function testFormatDepthArray()\n    {\n        $record = $this->getRecord(\n            message: 'some log message',\n            context: [\n                'nest2' => [\n                    'property' => 'anything',\n                    'nest3' => [\n                        'nest4' => 'value',\n                        'property' => 'nothing',\n                    ],\n                ],\n            ],\n            level: Level::Warning,\n            channel: 'test',\n            datetime: new \\DateTimeImmutable('2016-01-21T21:11:30.123456+00:00'),\n        );\n\n        $formatter = new MongoDBFormatter(2);\n        $formattedResult = $formatter->format($record);\n\n        $this->assertEquals(\n            [\n                'nest2' => [\n                    'property' => 'anything',\n                    'nest3' => '[...]',\n                ],\n            ],\n            $formattedResult['context']\n        );\n    }\n\n    public function testFormatDepthArrayInfiniteNesting()\n    {\n        $record = $this->getRecord(\n            message: 'some log message',\n            context: [\n                'nest2' => [\n                    'property' => 'something',\n                    'nest3' => [\n                        'property' => 'anything',\n                        'nest4' => [\n                            'property' => 'nothing',\n                        ],\n                    ],\n                ],\n            ],\n            level: Level::Warning,\n            channel: 'test',\n            datetime: new \\DateTimeImmutable('2016-01-21T21:11:30.123456+00:00'),\n        );\n\n        $formatter = new MongoDBFormatter(0);\n        $formattedResult = $formatter->format($record);\n\n        $this->assertEquals(\n            [\n                'nest2' => [\n                    'property' => 'something',\n                    'nest3' => [\n                        'property' => 'anything',\n                        'nest4' => [\n                            'property' => 'nothing',\n                        ],\n                    ],\n                ],\n            ],\n            $formattedResult['context']\n        );\n    }\n\n    public function testFormatDepthObjects()\n    {\n        $someObject = new \\stdClass();\n        $someObject->property = 'anything';\n        $someObject->nest3 = new \\stdClass();\n        $someObject->nest3->property = 'nothing';\n        $someObject->nest3->nest4 = 'invisible';\n\n        $record = $this->getRecord(\n            message: 'some log message',\n            context: [\n                'nest2' => $someObject,\n            ],\n            level: Level::Warning,\n            channel: 'test',\n            datetime: new \\DateTimeImmutable('2016-01-21T21:11:30.123456+00:00'),\n        );\n\n        $formatter = new MongoDBFormatter(2, true);\n        $formattedResult = $formatter->format($record);\n\n        $this->assertEquals(\n            [\n                'nest2' => [\n                    'property' => 'anything',\n                    'nest3' => '[...]',\n                    'class' => 'stdClass',\n                ],\n            ],\n            $formattedResult['context']\n        );\n    }\n\n    public function testFormatDepthException()\n    {\n        $record = $this->getRecord(\n            message: 'some log message',\n            context: [\n                'nest2' => new \\Exception('exception message', 987),\n            ],\n            level: Level::Warning,\n            channel: 'test',\n            datetime: new \\DateTimeImmutable('2016-01-21T21:11:30.123456+00:00'),\n        );\n\n        $formatter = new MongoDBFormatter(2, false);\n        $formattedRecord = $formatter->format($record);\n\n        $this->assertEquals('exception message', $formattedRecord['context']['nest2']['message']);\n        $this->assertEquals(987, $formattedRecord['context']['nest2']['code']);\n        $this->assertEquals('[...]', $formattedRecord['context']['nest2']['trace']);\n    }\n\n    public function testBsonTypes()\n    {\n        $record = $this->getRecord(\n            message: 'some log message',\n            context: [\n                'objectid' => new ObjectId(),\n                'nest' => [\n                    'timestamp' => new UTCDateTime(),\n                    'regex' => new Regex('pattern'),\n                ],\n            ],\n            level: Level::Warning,\n            channel: 'test',\n            datetime: new \\DateTimeImmutable('2016-01-21T21:11:30.123456+00:00'),\n        );\n\n        $formatter = new MongoDBFormatter();\n        $formattedRecord = $formatter->format($record);\n\n        $this->assertInstanceOf(ObjectId::class, $formattedRecord['context']['objectid']);\n        $this->assertInstanceOf(UTCDateTime::class, $formattedRecord['context']['nest']['timestamp']);\n        $this->assertInstanceOf(Regex::class, $formattedRecord['context']['nest']['regex']);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/NormalizerFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\n\n/**\n * @covers Monolog\\Formatter\\NormalizerFormatter\n */\nclass NormalizerFormatterTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testFormat()\n    {\n        $formatter = new NormalizerFormatter('Y-m-d');\n        $formatted = $formatter->format($this->getRecord(\n            Level::Error,\n            'foo',\n            channel: 'meh',\n            extra: ['foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => [], 'res' => fopen('php://memory', 'rb')],\n            context: [\n                'foo' => 'bar',\n                'baz' => 'qux',\n                'inf' => INF,\n                '-inf' => -INF,\n                'nan' => acos(4),\n            ],\n        ));\n\n        $this->assertEquals([\n            'level_name' => Level::Error->getName(),\n            'level' => Level::Error->value,\n            'channel' => 'meh',\n            'message' => 'foo',\n            'datetime' => date('Y-m-d'),\n            'extra' => [\n                'foo' => ['Monolog\\\\Formatter\\\\TestFooNorm' => [\"foo\" => \"fooValue\"]],\n                'bar' => ['Monolog\\\\Formatter\\\\TestBarNorm' => 'bar'],\n                'baz' => [],\n                'res' => '[resource(stream)]',\n            ],\n            'context' => [\n                'foo' => 'bar',\n                'baz' => 'qux',\n                'inf' => 'INF',\n                '-inf' => '-INF',\n                'nan' => 'NaN',\n            ],\n        ], $formatted);\n    }\n\n    public function testFormatExceptions()\n    {\n        $formatter = new NormalizerFormatter('Y-m-d');\n        $e = new \\LogicException('bar');\n        $e2 = new \\RuntimeException('foo', 0, $e);\n        $formatted = $formatter->normalizeValue([\n            'exception' => $e2,\n        ]);\n\n        $this->assertGreaterThan(5, \\count($formatted['exception']['trace']));\n        $this->assertTrue(isset($formatted['exception']['previous']));\n        unset($formatted['exception']['trace'], $formatted['exception']['previous']);\n\n        $this->assertEquals([\n            'exception' => [\n                'class'   => \\get_class($e2),\n                'message' => $e2->getMessage(),\n                'code'    => $e2->getCode(),\n                'file'    => $e2->getFile().':'.$e2->getLine(),\n            ],\n        ], $formatted);\n    }\n\n    public function testFormatExceptionWithBasePath(): void\n    {\n        $formatter = new NormalizerFormatter('Y-m-d');\n        $formatter->setBasePath(\\dirname(\\dirname(\\dirname(__DIR__))));\n        $e = new \\LogicException('bar');\n        $formatted = $formatter->normalizeValue([\n            'exception' => $e,\n        ]);\n\n        self::assertSame('tests/Monolog/Formatter/NormalizerFormatterTest.php:' . (__LINE__ - 5), $formatted['exception']['file']);\n        self::assertStringStartsWith('vendor/phpunit/phpunit/src/Framework/TestCase.php:', $formatted['exception']['trace'][0]);\n        self::assertStringStartsWith('vendor/phpunit/phpunit/src/Framework/TestCase.php:', $formatted['exception']['trace'][1]);\n    }\n\n    public function testFormatSoapFaultException()\n    {\n        if (!class_exists('SoapFault')) {\n            $this->markTestSkipped('Requires the soap extension');\n        }\n\n        $formatter = new NormalizerFormatter('Y-m-d');\n        $e = new \\SoapFault('foo', 'bar', 'hello', 'world');\n        $formatted = $formatter->normalizeValue([\n            'exception' => $e,\n        ]);\n\n        unset($formatted['exception']['trace']);\n\n        $this->assertEquals([\n            'exception' => [\n                'class' => 'SoapFault',\n                'message' => 'bar',\n                'code' => 0,\n                'file' => $e->getFile().':'.$e->getLine(),\n                'faultcode' => 'foo',\n                'faultactor' => 'hello',\n                'detail' => 'world',\n            ],\n        ], $formatted);\n\n        $formatter = new NormalizerFormatter('Y-m-d');\n        $e = new \\SoapFault('foo', 'bar', 'hello', (object) ['bar' => (object) ['biz' => 'baz'], 'foo' => 'world']);\n        $formatted = $formatter->normalizeValue([\n            'exception' => $e,\n        ]);\n\n        unset($formatted['exception']['trace']);\n\n        $this->assertEquals([\n            'exception' => [\n                'class' => 'SoapFault',\n                'message' => 'bar',\n                'code' => 0,\n                'file' => $e->getFile().':'.$e->getLine(),\n                'faultcode' => 'foo',\n                'faultactor' => 'hello',\n                'detail' => '{\"bar\":{\"biz\":\"baz\"},\"foo\":\"world\"}',\n            ],\n        ], $formatted);\n    }\n\n    public function testFormatToStringExceptionHandle()\n    {\n        $formatter = new NormalizerFormatter('Y-m-d');\n        $formatted = $formatter->format($this->getRecord(context: [\n            'myObject' => new TestToStringError(),\n        ]));\n        $this->assertEquals(\n            [\n                'level_name' => Level::Warning->getName(),\n                'level' => Level::Warning->value,\n                'channel' => 'test',\n                'message' => 'test',\n                'context' => [\n                    'myObject' => [\n                        TestToStringError::class => [],\n                    ],\n                ],\n                'datetime' => date('Y-m-d'),\n                'extra' => [],\n            ],\n            $formatted\n        );\n    }\n\n    public function testBatchFormat()\n    {\n        $formatter = new NormalizerFormatter('Y-m-d');\n        $formatted = $formatter->formatBatch([\n            $this->getRecord(Level::Critical, 'bar', channel: 'test'),\n            $this->getRecord(Level::Warning, 'foo', channel: 'log'),\n        ]);\n        $this->assertEquals([\n            [\n                'level_name' => Level::Critical->getName(),\n                'level' => Level::Critical->value,\n                'channel' => 'test',\n                'message' => 'bar',\n                'context' => [],\n                'datetime' => date('Y-m-d'),\n                'extra' => [],\n            ],\n            [\n                'level_name' => Level::Warning->getName(),\n                'level' => Level::Warning->value,\n                'channel' => 'log',\n                'message' => 'foo',\n                'context' => [],\n                'datetime' => date('Y-m-d'),\n                'extra' => [],\n            ],\n        ], $formatted);\n    }\n\n    /**\n     * Test issue #137\n     */\n    public function testIgnoresRecursiveObjectReferences()\n    {\n        // set up the recursion\n        $foo = new \\stdClass();\n        $bar = new \\stdClass();\n\n        $foo->bar = $bar;\n        $bar->foo = $foo;\n\n        // set an error handler to assert that the error is not raised anymore\n        $that = $this;\n        set_error_handler(function ($level, $message, $file, $line, $context) use ($that) {\n            if (error_reporting() & $level) {\n                restore_error_handler();\n                $that->fail(\"$message should not be raised\");\n            }\n\n            return true;\n        });\n\n        $formatter = new NormalizerFormatter();\n        $reflMethod = new \\ReflectionMethod($formatter, 'toJson');\n        $res = $reflMethod->invoke($formatter, [$foo, $bar], true);\n\n        restore_error_handler();\n\n        $this->assertEquals('[{\"bar\":{\"foo\":null}},{\"foo\":{\"bar\":null}}]', $res);\n    }\n\n    public function testCanNormalizeReferences()\n    {\n        $formatter = new NormalizerFormatter();\n        $x = ['foo' => 'bar'];\n        $y = ['x' => &$x];\n        $x['y'] = &$y;\n        $formatter->normalizeValue($y);\n    }\n\n    public function testToJsonIgnoresInvalidTypes()\n    {\n        // set up the invalid data\n        $resource = fopen(__FILE__, 'r');\n\n        // set an error handler to assert that the error is not raised anymore\n        $that = $this;\n        set_error_handler(function ($level, $message, $file, $line, $context) use ($that) {\n            if (error_reporting() & $level) {\n                restore_error_handler();\n                $that->fail(\"$message should not be raised\");\n            }\n\n            return true;\n        });\n\n        $formatter = new NormalizerFormatter();\n        $reflMethod = new \\ReflectionMethod($formatter, 'toJson');\n        $res = $reflMethod->invoke($formatter, [$resource], true);\n\n        restore_error_handler();\n\n        $this->assertEquals('[null]', $res);\n    }\n\n    public function testNormalizeHandleLargeArraysWithExactly1000Items()\n    {\n        $formatter = new NormalizerFormatter();\n        $largeArray = range(1, 1000);\n\n        $res = $formatter->format($this->getRecord(\n            Level::Critical,\n            'bar',\n            channel: 'test',\n            context: [$largeArray],\n        ));\n\n        $this->assertCount(1000, $res['context'][0]);\n        $this->assertArrayNotHasKey('...', $res['context'][0]);\n    }\n\n    public function testNormalizeHandleLargeArrays()\n    {\n        $formatter = new NormalizerFormatter();\n        $largeArray = range(1, 2000);\n\n        $res = $formatter->format($this->getRecord(\n            Level::Critical,\n            'bar',\n            channel: 'test',\n            context: [$largeArray],\n        ));\n\n        $this->assertCount(1001, $res['context'][0]);\n        $this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']);\n    }\n\n    public function testIgnoresInvalidEncoding()\n    {\n        $formatter = new NormalizerFormatter();\n        $reflMethod = new \\ReflectionMethod($formatter, 'toJson');\n\n        // send an invalid unicode sequence as a object that can't be cleaned\n        $record = new \\stdClass;\n        $record->message = \"\\xB1\\x31\";\n\n        $this->assertsame('{\"message\":\"�1\"}', $reflMethod->invoke($formatter, $record));\n    }\n\n    public function testConvertsInvalidEncodingAsLatin9()\n    {\n        $formatter = new NormalizerFormatter();\n        $reflMethod = new \\ReflectionMethod($formatter, 'toJson');\n\n        $res = $reflMethod->invoke($formatter, ['message' => \"\\xA4\\xA6\\xA8\\xB4\\xB8\\xBC\\xBD\\xBE\"]);\n\n        $this->assertSame('{\"message\":\"��������\"}', $res);\n    }\n\n    public function testMaxNormalizeDepth()\n    {\n        $formatter = new NormalizerFormatter();\n        $formatter->setMaxNormalizeDepth(1);\n        $throwable = new \\Error('Foo');\n\n        $message = $this->formatRecordWithExceptionInContext($formatter, $throwable);\n        $this->assertEquals(\n            'Over 1 levels deep, aborting normalization',\n            $message['context']['exception']\n        );\n    }\n\n    public function testMaxNormalizeItemCountWith0ItemsMax()\n    {\n        $formatter = new NormalizerFormatter();\n        $formatter->setMaxNormalizeDepth(9);\n        $formatter->setMaxNormalizeItemCount(0);\n        $throwable = new \\Error('Foo');\n\n        $message = $this->formatRecordWithExceptionInContext($formatter, $throwable);\n        $this->assertEquals(\n            [\"...\" => \"Over 0 items (7 total), aborting normalization\"],\n            $message\n        );\n    }\n\n    public function testMaxNormalizeItemCountWith2ItemsMax()\n    {\n        $formatter = new NormalizerFormatter();\n        $formatter->setMaxNormalizeDepth(9);\n        $formatter->setMaxNormalizeItemCount(2);\n        $throwable = new \\Error('Foo');\n\n        $message = $this->formatRecordWithExceptionInContext($formatter, $throwable);\n\n        unset($message['context']['exception']['trace']);\n        unset($message['context']['exception']['file']);\n        $this->assertEquals(\n            [\n                \"message\" => \"foobar\",\n                \"context\" => ['exception' => [\n                    'class' => 'Error',\n                    'message' => 'Foo',\n                    'code' => 0,\n               ]],\n                \"...\" => \"Over 2 items (7 total), aborting normalization\",\n            ],\n            $message\n        );\n    }\n\n    public function testExceptionTraceWithArgs()\n    {\n        try {\n            // This will contain $resource and $wrappedResource as arguments in the trace item\n            $resource = fopen('php://memory', 'rw+');\n            fwrite($resource, 'test_resource');\n            $wrappedResource = new TestFooNorm;\n            $wrappedResource->foo = $resource;\n            // Just do something stupid with a resource/wrapped resource as argument\n            $arr = [$wrappedResource, $resource];\n            // modifying the array inside throws a \"usort(): Array was modified by the user comparison function\"\n            usort($arr, function ($a, $b) {\n                throw new \\ErrorException('Foo');\n            });\n        } catch (\\Throwable $e) {\n        }\n\n        $formatter = new NormalizerFormatter();\n        $record = $this->getRecord(context: ['exception' => $e]);\n        $result = $formatter->format($record);\n\n        // See https://github.com/php/php-src/issues/8810 fixed in PHP 8.2\n        $offset = PHP_VERSION_ID >= 80200 ? 13 : 11;\n        $this->assertSame(\n            __FILE__.':'.(__LINE__ - $offset),\n            $result['context']['exception']['trace'][0]\n        );\n    }\n\n    private function formatRecordWithExceptionInContext(NormalizerFormatter $formatter, \\Throwable $exception): array\n    {\n        $message = $formatter->format($this->getRecord(\n            Level::Critical,\n            'foobar',\n            channel: 'core',\n            context: ['exception' => $exception],\n        ));\n\n        return $message;\n    }\n\n    public function testExceptionTraceDoesNotLeakCallUserFuncArgs()\n    {\n        try {\n            $arg = new TestInfoLeak;\n            \\call_user_func([$this, 'throwHelper'], $arg, $dt = new \\DateTime());\n        } catch (\\Exception $e) {\n        }\n\n        $formatter = new NormalizerFormatter();\n        $record = $this->getRecord(context: ['exception' => $e]);\n        $result = $formatter->format($record);\n\n        $this->assertSame(\n            __FILE__ .':'.(__LINE__-9),\n            $result['context']['exception']['trace'][0]\n        );\n    }\n\n    public function testCanNormalizeIncompleteObject(): void\n    {\n        $serialized = \"O:17:\\\"Monolog\\TestClass\\\":1:{s:23:\\\"\\x00Monolog\\TestClass\\x00name\\\";s:4:\\\"test\\\";}\";\n        $object = unserialize($serialized);\n\n        $formatter = new NormalizerFormatter();\n        $record = $this->getRecord(context: ['object' => $object]);\n        $result = $formatter->format($record);\n\n        $this->assertEquals([\n            '__PHP_Incomplete_Class' => 'Monolog\\\\TestClass',\n        ], $result['context']['object']);\n    }\n\n    public function testMaxTraceLengthDefault()\n    {\n        $formatter = new NormalizerFormatter();\n        $this->assertNull($formatter->getMaxTraceLength());\n    }\n\n    public function testMaxTraceLengthSetter()\n    {\n        $formatter = new NormalizerFormatter();\n        $formatter->setMaxTraceLength(5);\n        $this->assertEquals(5, $formatter->getMaxTraceLength());\n\n        $formatter->setMaxTraceLength(null);\n        $this->assertNull($formatter->getMaxTraceLength());\n    }\n\n    public function testMaxTraceLengthLimitsTrace()\n    {\n        $formatter = new NormalizerFormatter();\n        $formatter->setMaxTraceLength(2);\n\n        $exception = $this->createDeepTraceException();\n        $formatted = $formatter->normalizeValue(['exception' => $exception]);\n\n        $this->assertArrayHasKey('trace', $formatted['exception']);\n        $this->assertCount(2, $formatted['exception']['trace']);\n    }\n\n    public function testMaxTraceLengthZeroDoesNotIncludeTrace()\n    {\n        $formatter = new NormalizerFormatter();\n        $formatter->setMaxTraceLength(0);\n\n        $exception = $this->createDeepTraceException();\n        $formatted = $formatter->normalizeValue(['exception' => $exception]);\n\n        $this->assertArrayNotHasKey('trace', $formatted['exception']);\n    }\n\n    public function testMaxTraceLengthNullAllowsUnlimited()\n    {\n        $formatter = new NormalizerFormatter();\n        $formatter->setMaxTraceLength(null);\n\n        $exception = $this->createDeepTraceException();\n        $formatted = $formatter->normalizeValue(['exception' => $exception]);\n\n        $this->assertArrayHasKey('trace', $formatted['exception']);\n        $this->assertGreaterThan(2, count($formatted['exception']['trace']));\n    }\n\n    public function testMaxTraceLengthWithPreviousException()\n    {\n        $formatter = new NormalizerFormatter();\n        $formatter->setMaxTraceLength(1);\n\n        $previous = new \\RuntimeException('Previous exception');\n        $exception = new \\LogicException('Main exception', 0, $previous);\n        $formatted = $formatter->normalizeValue(['exception' => $exception]);\n\n        $this->assertArrayHasKey('trace', $formatted['exception']);\n        $this->assertCount(1, $formatted['exception']['trace']);\n        $this->assertArrayHasKey('previous', $formatted['exception']);\n        $this->assertArrayHasKey('trace', $formatted['exception']['previous']);\n        $this->assertCount(1, $formatted['exception']['previous']['trace']);\n    }\n\n    public function testMaxTraceLengthWithBasePath()\n    {\n        $formatter = new NormalizerFormatter();\n        $formatter->setMaxTraceLength(3);\n        $formatter->setBasePath(dirname(__DIR__, 3));\n\n        $exception = $this->createDeepTraceException();\n        $formatted = $formatter->normalizeValue(['exception' => $exception]);\n\n        $this->assertArrayHasKey('trace', $formatted['exception']);\n        $this->assertCount(3, $formatted['exception']['trace']);\n\n        foreach ($formatted['exception']['trace'] as $traceItem) {\n            $this->assertStringNotContainsString(dirname(__DIR__, 3), $traceItem);\n        }\n    }\n\n    private function createDeepTraceException(): \\Exception\n    {\n        $createException = function(int $depth) use (&$createException): \\Exception {\n            if ($depth <= 0) {\n                return new \\RuntimeException('Test exception with deep trace');\n            }\n            return $createException($depth - 1);\n        };\n\n        return $createException(3);\n    }\n\n    private function throwHelper($arg)\n    {\n        throw new \\RuntimeException('Thrown');\n    }\n}\n\nclass TestFooNorm\n{\n    public $foo = 'fooValue';\n}\n\nclass TestBarNorm\n{\n    public function __toString()\n    {\n        return 'bar';\n    }\n}\n\nclass TestStreamFoo\n{\n    public $foo;\n    public $resource;\n\n    public function __construct($resource)\n    {\n        $this->resource = $resource;\n        $this->foo = 'BAR';\n    }\n\n    public function __toString()\n    {\n        fseek($this->resource, 0);\n\n        return $this->foo . ' - ' . (string) stream_get_contents($this->resource);\n    }\n}\n\nclass TestToStringError\n{\n    public function __toString()\n    {\n        throw new \\RuntimeException('Could not convert to string');\n    }\n}\n\nclass TestInfoLeak\n{\n    public function __toString()\n    {\n        return 'Sensitive information';\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/ScalarFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\JsonSerializableDateTimeImmutable;\n\nclass ScalarFormatterTest extends \\Monolog\\Test\\MonologTestCase\n{\n    private ScalarFormatter $formatter;\n\n    public function setUp(): void\n    {\n        $this->formatter = new ScalarFormatter();\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->formatter);\n    }\n\n    public function buildTrace(\\Exception $e)\n    {\n        $data = [];\n        $trace = $e->getTrace();\n        foreach ($trace as $frame) {\n            if (isset($frame['file'])) {\n                $data[] = $frame['file'].':'.$frame['line'];\n            }\n        }\n\n        return $data;\n    }\n\n    public function encodeJson($data)\n    {\n        return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);\n    }\n\n    public function testFormat()\n    {\n        $exception = new \\Exception('foo');\n        $formatted = $this->formatter->format($this->getRecord(context: [\n            'foo' => 'string',\n            'bar' => 1,\n            'baz' => false,\n            'bam' => [1, 2, 3],\n            'bat' => ['foo' => 'bar'],\n            'bap' => $dt = new JsonSerializableDateTimeImmutable(true),\n            'ban' => $exception,\n        ]));\n\n        $this->assertSame($this->encodeJson([\n            'foo' => 'string',\n            'bar' => 1,\n            'baz' => false,\n            'bam' => [1, 2, 3],\n            'bat' => ['foo' => 'bar'],\n            'bap' => (string) $dt,\n            'ban' => [\n                'class'   => \\get_class($exception),\n                'message' => $exception->getMessage(),\n                'code'    => $exception->getCode(),\n                'file'    => $exception->getFile() . ':' . $exception->getLine(),\n                'trace'   => $this->buildTrace($exception),\n            ],\n        ]), $formatted['context']);\n    }\n\n    public function testFormatWithErrorContext()\n    {\n        $context = ['file' => 'foo', 'line' => 1];\n        $formatted = $this->formatter->format($this->getRecord(\n            context: $context,\n        ));\n\n        $this->assertSame($this->encodeJson($context), $formatted['context']);\n    }\n\n    public function testFormatWithExceptionContext()\n    {\n        $exception = new \\Exception('foo');\n        $formatted = $this->formatter->format($this->getRecord(context: [\n            'exception' => $exception,\n        ]));\n\n        $this->assertSame($this->encodeJson([\n            'exception' => [\n                'class'   => \\get_class($exception),\n                'message' => $exception->getMessage(),\n                'code'    => $exception->getCode(),\n                'file'    => $exception->getFile() . ':' . $exception->getLine(),\n                'trace'   => $this->buildTrace($exception),\n            ],\n        ]), $formatted['context']);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/SyslogFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse DateTimeImmutable;\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass SyslogFormatterTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @param mixed[] $context\n     * @param mixed[] $extra\n     */\n    #[DataProvider('formatDataProvider')]\n    public function testFormat(\n        string $expected,\n        DateTimeImmutable $dateTime,\n        string $channel,\n        Level $level,\n        string $message,\n        ?string $appName = null,\n        array $context = [],\n        array $extra = []\n    ): void {\n        if ($appName !== null) {\n            $formatter = new SyslogFormatter($appName);\n        } else {\n            $formatter = new SyslogFormatter();\n        }\n\n        $record = new LogRecord(\n            datetime: $dateTime,\n            channel: $channel,\n            level: $level,\n            message: $message,\n            context: $context,\n            extra: $extra\n        );\n\n        $message = $formatter->format($record);\n\n        $this->assertEquals($expected, $message);\n    }\n\n    /**\n     * @return mixed[]\n     */\n    public static function formatDataProvider(): array\n    {\n        return [\n            'error' => [\n                'expected' => \"<11>1 1970-01-01T00:00:00.000000+00:00 \" . gethostname() . \" - \" . getmypid() .\" meh - ERROR: log  \\n\",\n                'dateTime' => new DateTimeImmutable(\"@0\"),\n                'channel' => 'meh',\n                'level' => Level::Error,\n                'message' => 'log',\n            ],\n            'info' => [\n                'expected' => \"<11>1 1970-01-01T00:00:00.000000+00:00 \" . gethostname() . \" - \" . getmypid() .\" meh - ERROR: log  \\n\",\n                'dateTime' => new DateTimeImmutable(\"@0\"),\n                'channel' => 'meh',\n                'level' => Level::Error,\n                'message' => 'log',\n            ],\n            'with app name' => [\n                'expected' => \"<11>1 1970-01-01T00:00:00.000000+00:00 \" . gethostname() . \" my-app \" . getmypid() .\" meh - ERROR: log  \\n\",\n                'dateTime' => new DateTimeImmutable(\"@0\"),\n                'channel' => 'meh',\n                'level' => Level::Error,\n                'message' => 'log',\n                'appName' => 'my-app',\n            ],\n            'with context' => [\n                'expected' => \"<11>1 1970-01-01T00:00:00.000000+00:00 \" . gethostname() . \" - \" . getmypid() .\" meh - ERROR: log {\\\"additional-context\\\":\\\"test\\\"} \\n\",\n                'dateTime' => new DateTimeImmutable(\"@0\"),\n                'channel' => 'meh',\n                'level' => Level::Error,\n                'message' => 'log',\n                'appName' => null,\n                'context' => ['additional-context' => 'test'],\n            ],\n            'with extra' => [\n                'expected' => \"<11>1 1970-01-01T00:00:00.000000+00:00 \" . gethostname() . \" - \" . getmypid() .\" meh - ERROR: log  {\\\"userId\\\":1}\\n\",\n                'dateTime' => new DateTimeImmutable(\"@0\"),\n                'channel' => 'meh',\n                'level' => Level::Error,\n                'message' => 'log',\n                'appName' => null,\n                'context' => [],\n                'extra' => ['userId' => 1],\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Formatter/WildfireFormatterTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Formatter;\n\nuse Monolog\\Level;\n\nclass WildfireFormatterTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Formatter\\WildfireFormatter::format\n     */\n    public function testDefaultFormat()\n    {\n        $wildfire = new WildfireFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger'],\n            extra: ['ip' => '127.0.0.1'],\n        );\n\n        $message = $wildfire->format($record);\n\n        $this->assertEquals(\n            '125|[{\"Type\":\"ERROR\",\"File\":\"\",\"Line\":\"\",\"Label\":\"meh\"},'\n                .'{\"message\":\"log\",\"context\":{\"from\":\"logger\"},\"extra\":{\"ip\":\"127.0.0.1\"}}]|',\n            $message\n        );\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\WildfireFormatter::format\n     */\n    public function testFormatWithFileAndLine()\n    {\n        $wildfire = new WildfireFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n            context: ['from' => 'logger'],\n            extra: ['ip' => '127.0.0.1', 'file' => 'test', 'line' => 14],\n        );\n\n        $message = $wildfire->format($record);\n\n        $this->assertEquals(\n            '129|[{\"Type\":\"ERROR\",\"File\":\"test\",\"Line\":14,\"Label\":\"meh\"},'\n                .'{\"message\":\"log\",\"context\":{\"from\":\"logger\"},\"extra\":{\"ip\":\"127.0.0.1\"}}]|',\n            $message\n        );\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\WildfireFormatter::format\n     */\n    public function testFormatWithoutContext()\n    {\n        $wildfire = new WildfireFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n        );\n\n        $message = $wildfire->format($record);\n\n        $this->assertEquals(\n            '58|[{\"Type\":\"ERROR\",\"File\":\"\",\"Line\":\"\",\"Label\":\"meh\"},\"log\"]|',\n            $message\n        );\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\WildfireFormatter::formatBatch\n     */\n    public function testBatchFormatThrowException()\n    {\n        $this->expectException(\\BadMethodCallException::class);\n\n        $wildfire = new WildfireFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'log',\n            channel: 'meh',\n        );\n\n        $wildfire->formatBatch([$record]);\n    }\n\n    /**\n     * @covers Monolog\\Formatter\\WildfireFormatter::format\n     */\n    public function testTableFormat()\n    {\n        $wildfire = new WildfireFormatter();\n        $record = $this->getRecord(\n            Level::Error,\n            'table-message',\n            channel: 'table-channel',\n            context: [\n                'table' => [\n                    ['col1', 'col2', 'col3'],\n                    ['val1', 'val2', 'val3'],\n                    ['foo1', 'foo2', 'foo3'],\n                    ['bar1', 'bar2', 'bar3'],\n                ],\n            ],\n        );\n\n        $message = $wildfire->format($record);\n\n        $this->assertEquals(\n            '171|[{\"Type\":\"TABLE\",\"File\":\"\",\"Line\":\"\",\"Label\":\"table-channel: table-message\"},[[\"col1\",\"col2\",\"col3\"],[\"val1\",\"val2\",\"val3\"],[\"foo1\",\"foo2\",\"foo3\"],[\"bar1\",\"bar2\",\"bar3\"]]]|',\n            $message\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/AbstractHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\nclass AbstractHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Handler\\AbstractHandler::__construct\n     * @covers Monolog\\Handler\\AbstractHandler::getLevel\n     * @covers Monolog\\Handler\\AbstractHandler::setLevel\n     * @covers Monolog\\Handler\\AbstractHandler::getBubble\n     * @covers Monolog\\Handler\\AbstractHandler::setBubble\n     */\n    public function testConstructAndGetSet()\n    {\n        $handler = $this->getMockBuilder('Monolog\\Handler\\AbstractHandler')->setConstructorArgs([Level::Warning, false])->onlyMethods(['handle'])->getMock();\n        $this->assertEquals(Level::Warning, $handler->getLevel());\n        $this->assertEquals(false, $handler->getBubble());\n\n        $handler->setLevel(Level::Error);\n        $handler->setBubble(true);\n        $this->assertEquals(Level::Error, $handler->getLevel());\n        $this->assertEquals(true, $handler->getBubble());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\AbstractHandler::handleBatch\n     */\n    public function testHandleBatch()\n    {\n        $handler = $this->createPartialMock('Monolog\\Handler\\AbstractHandler', ['handle']);\n        $handler->expects($this->exactly(2))\n            ->method('handle');\n        $handler->handleBatch([$this->getRecord(), $this->getRecord()]);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\AbstractHandler::isHandling\n     */\n    public function testIsHandling()\n    {\n        $handler = $this->getMockBuilder('Monolog\\Handler\\AbstractHandler')->setConstructorArgs([Level::Warning, false])->onlyMethods(['handle'])->getMock();\n        $this->assertTrue($handler->isHandling($this->getRecord()));\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Debug)));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\AbstractHandler::__construct\n     */\n    public function testHandlesPsrStyleLevels()\n    {\n        $handler = $this->getMockBuilder('Monolog\\Handler\\AbstractHandler')->setConstructorArgs(['warning', false])->onlyMethods(['handle'])->getMock();\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Debug)));\n        $handler->setLevel('debug');\n        $this->assertTrue($handler->isHandling($this->getRecord(Level::Debug)));\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/AbstractProcessingHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Processor\\WebProcessor;\nuse Monolog\\Formatter\\LineFormatter;\n\nclass AbstractProcessingHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Handler\\FormattableHandlerTrait::getFormatter\n     * @covers Monolog\\Handler\\FormattableHandlerTrait::setFormatter\n     */\n    public function testConstructAndGetSet()\n    {\n        $handler = $this->getMockBuilder('Monolog\\Handler\\AbstractProcessingHandler')->setConstructorArgs([Level::Warning, false])->onlyMethods(['write'])->getMock();\n        $handler->setFormatter($formatter = new LineFormatter);\n        $this->assertSame($formatter, $handler->getFormatter());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\AbstractProcessingHandler::handle\n     */\n    public function testHandleLowerLevelMessage()\n    {\n        $handler = $this->getMockBuilder('Monolog\\Handler\\AbstractProcessingHandler')->setConstructorArgs([Level::Warning, true])->onlyMethods(['write'])->getMock();\n        $this->assertFalse($handler->handle($this->getRecord(Level::Debug)));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\AbstractProcessingHandler::handle\n     */\n    public function testHandleBubbling()\n    {\n        $handler = $this->getMockBuilder('Monolog\\Handler\\AbstractProcessingHandler')->setConstructorArgs([Level::Debug, true])->onlyMethods(['write'])->getMock();\n        $this->assertFalse($handler->handle($this->getRecord()));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\AbstractProcessingHandler::handle\n     */\n    public function testHandleNotBubbling()\n    {\n        $handler = $this->getMockBuilder('Monolog\\Handler\\AbstractProcessingHandler')->setConstructorArgs([Level::Debug, false])->onlyMethods(['write'])->getMock();\n        $this->assertTrue($handler->handle($this->getRecord()));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\AbstractProcessingHandler::handle\n     */\n    public function testHandleIsFalseWhenNotHandled()\n    {\n        $handler = $this->getMockBuilder('Monolog\\Handler\\AbstractProcessingHandler')->setConstructorArgs([Level::Warning, false])->onlyMethods(['write'])->getMock();\n        $this->assertTrue($handler->handle($this->getRecord()));\n        $this->assertFalse($handler->handle($this->getRecord(Level::Debug)));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\AbstractProcessingHandler::processRecord\n     */\n    public function testProcessRecord()\n    {\n        $handler = $this->createPartialMock('Monolog\\Handler\\AbstractProcessingHandler', ['write']);\n        $handler->pushProcessor(new WebProcessor([\n            'REQUEST_URI' => '',\n            'REQUEST_METHOD' => '',\n            'REMOTE_ADDR' => '',\n            'SERVER_NAME' => '',\n            'UNIQUE_ID' => '',\n        ]));\n        $handledRecord = null;\n        $handler->expects($this->once())\n            ->method('write')\n            ->willReturnCallback(function ($record) use (&$handledRecord) {\n                $handledRecord = $record;\n            })\n        ;\n        $handler->handle($this->getRecord());\n        $this->assertEquals(6, \\count($handledRecord['extra']));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ProcessableHandlerTrait::pushProcessor\n     * @covers Monolog\\Handler\\ProcessableHandlerTrait::popProcessor\n     */\n    public function testPushPopProcessor()\n    {\n        $logger = $this->createPartialMock('Monolog\\Handler\\AbstractProcessingHandler', ['write']);\n        $processor1 = new WebProcessor;\n        $processor2 = new WebProcessor;\n\n        $logger->pushProcessor($processor1);\n        $logger->pushProcessor($processor2);\n\n        $this->assertEquals($processor2, $logger->popProcessor());\n        $this->assertEquals($processor1, $logger->popProcessor());\n\n        $this->expectException(\\LogicException::class);\n\n        $logger->popProcessor();\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ProcessableHandlerTrait::pushProcessor\n     */\n    public function testPushProcessorWithNonCallable()\n    {\n        $handler = $this->createPartialMock('Monolog\\Handler\\AbstractProcessingHandler', ['write']);\n\n        $this->expectException(\\TypeError::class);\n\n        $handler->pushProcessor(new \\stdClass());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FormattableHandlerTrait::getFormatter\n     * @covers Monolog\\Handler\\FormattableHandlerTrait::getDefaultFormatter\n     */\n    public function testGetFormatterInitializesDefault()\n    {\n        $handler = $this->createPartialMock('Monolog\\Handler\\AbstractProcessingHandler', ['write']);\n        $this->assertInstanceOf(LineFormatter::class, $handler->getFormatter());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/AmqpHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PhpAmqpLib\\Message\\AMQPMessage;\n\n/**\n * @covers Monolog\\Handler\\RotatingFileHandler\n */\nclass AmqpHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testHandleAmqpExt()\n    {\n        if (!class_exists('AMQPConnection') || !class_exists('AMQPExchange')) {\n            $this->markTestSkipped(\"amqp-php not installed\");\n        }\n\n        if (!class_exists('AMQPChannel')) {\n            $this->markTestSkipped(\"Please update AMQP to version >= 1.0\");\n        }\n\n        $messages = [];\n\n        $exchange = $this->getMockBuilder('AMQPExchange')\n            ->onlyMethods(['publish', 'setName'])\n            ->disableOriginalConstructor()\n            ->getMock();\n\n        $exchange->expects($this->any())\n            ->method('publish')\n            ->willReturnCallback(function ($message, $routing_key, $flags = 0, $attributes = []) use (&$messages) {\n                $messages[] = [$message, $routing_key, $flags, $attributes];\n            })\n        ;\n\n        $handler = new AmqpHandler($exchange);\n\n        $record = $this->getRecord(Level::Warning, 'test', ['data' => new \\stdClass, 'foo' => 34]);\n\n        $expected = [\n            [\n                'message' => 'test',\n                'context' => [\n                    'data' => [],\n                    'foo' => 34,\n                ],\n                'level' => 300,\n                'level_name' => 'WARNING',\n                'channel' => 'test',\n                'extra' => [],\n            ],\n            'warning.test',\n            0,\n            [\n                'delivery_mode' => 2,\n                'content_type' => 'application/json',\n            ],\n        ];\n\n        $handler->handle($record);\n\n        $this->assertCount(1, $messages);\n        $messages[0][0] = json_decode($messages[0][0], true);\n        unset($messages[0][0]['datetime']);\n        $this->assertEquals($expected, $messages[0]);\n    }\n\n    public function testHandlePhpAmqpLib()\n    {\n        if (!class_exists('PhpAmqpLib\\Channel\\AMQPChannel')) {\n            $this->markTestSkipped(\"php-amqplib not installed\");\n        }\n\n        $messages = [];\n\n        $methodsToMock = ['basic_publish'];\n        if (method_exists('PhpAmqpLib\\Channel\\AMQPChannel', '__destruct')) {\n            $methodsToMock[] = '__destruct';\n        }\n\n        $exchange = $this->getMockBuilder('PhpAmqpLib\\Channel\\AMQPChannel')\n            ->onlyMethods($methodsToMock)\n            ->disableOriginalConstructor()\n            ->getMock();\n\n        $exchange->expects($this->any())\n            ->method('basic_publish')\n            ->willReturnCallback(function (AMQPMessage $msg, $exchange = \"\", $routing_key = \"\", $mandatory = false, $immediate = false, $ticket = null) use (&$messages) {\n                $messages[] = [$msg, $exchange, $routing_key, $mandatory, $immediate, $ticket];\n            })\n        ;\n\n        $handler = new AmqpHandler($exchange, 'log');\n\n        $record = $this->getRecord(Level::Warning, 'test', ['data' => new \\stdClass, 'foo' => 34]);\n\n        $expected = [\n            [\n                'message' => 'test',\n                'context' => [\n                    'data' => [],\n                    'foo' => 34,\n                ],\n                'level' => 300,\n                'level_name' => 'WARNING',\n                'channel' => 'test',\n                'extra' => [],\n            ],\n            'log',\n            'warning.test',\n            false,\n            false,\n            null,\n            [\n                'delivery_mode' => 2,\n                'content_type' => 'application/json',\n            ],\n        ];\n\n        $handler->handle($record);\n\n        $this->assertCount(1, $messages);\n\n        /* @var $msg AMQPMessage */\n        $msg = $messages[0][0];\n        $messages[0][0] = json_decode($msg->body, true);\n        $messages[0][] = $msg->get_properties();\n        unset($messages[0][0]['datetime']);\n\n        $this->assertEquals($expected, $messages[0]);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/BrowserConsoleHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\n/**\n * @covers Monolog\\Handler\\BrowserConsoleHandlerTest\n */\nclass BrowserConsoleHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    protected function setUp(): void\n    {\n        BrowserConsoleHandler::resetStatic();\n    }\n\n    protected function generateScript()\n    {\n        $reflMethod = new \\ReflectionMethod('Monolog\\Handler\\BrowserConsoleHandler', 'generateScript');\n\n        return $reflMethod->invoke(null);\n    }\n\n    public function testStyling()\n    {\n        $handler = new BrowserConsoleHandler();\n        $handler->setFormatter($this->getIdentityFormatter());\n\n        $handler->handle($this->getRecord(Level::Debug, 'foo[[bar]]{color: red}'));\n\n        $expected = <<<EOF\n(function (c) {if (c && c.groupCollapsed) {\nc.debug(\"%cfoo%cbar%c\", \"font-weight: normal\", \"color: red\", \"font-weight: normal\");\n}})(console);\nEOF;\n\n        $this->assertEquals($expected, $this->generateScript());\n    }\n\n    public function testStylingMultiple()\n    {\n        $handler = new BrowserConsoleHandler();\n        $handler->setFormatter($this->getIdentityFormatter());\n\n        $handler->handle($this->getRecord(Level::Debug, 'foo[[bar]]{color: red}[[baz]]{color: blue}'));\n\n        $expected = <<<EOF\n(function (c) {if (c && c.groupCollapsed) {\nc.debug(\"%cfoo%cbar%c%cbaz%c\", \"font-weight: normal\", \"color: red\", \"font-weight: normal\", \"color: blue\", \"font-weight: normal\");\n}})(console);\nEOF;\n\n        $this->assertEquals($expected, $this->generateScript());\n    }\n\n    public function testEscaping()\n    {\n        $handler = new BrowserConsoleHandler();\n        $handler->setFormatter($this->getIdentityFormatter());\n\n        $handler->handle($this->getRecord(Level::Debug, \"[foo] [[\\\"bar\\n[baz]\\\"]]{color: red}\"));\n\n        $expected = <<<EOF\n(function (c) {if (c && c.groupCollapsed) {\nc.debug(\"%c[foo] %c\\\"bar\\\\n[baz]\\\"%c\", \"font-weight: normal\", \"color: red\", \"font-weight: normal\");\n}})(console);\nEOF;\n\n        $this->assertEquals($expected, $this->generateScript());\n    }\n\n    public function testAutolabel()\n    {\n        $handler = new BrowserConsoleHandler();\n        $handler->setFormatter($this->getIdentityFormatter());\n\n        $handler->handle($this->getRecord(Level::Debug, '[[foo]]{macro: autolabel}'));\n        $handler->handle($this->getRecord(Level::Debug, '[[bar]]{macro: autolabel}'));\n        $handler->handle($this->getRecord(Level::Debug, '[[foo]]{macro: autolabel}'));\n\n        $expected = <<<EOF\n(function (c) {if (c && c.groupCollapsed) {\nc.debug(\"%c%cfoo%c\", \"font-weight: normal\", \"background-color: blue; color: white; border-radius: 3px; padding: 0 2px 0 2px\", \"font-weight: normal\");\nc.debug(\"%c%cbar%c\", \"font-weight: normal\", \"background-color: green; color: white; border-radius: 3px; padding: 0 2px 0 2px\", \"font-weight: normal\");\nc.debug(\"%c%cfoo%c\", \"font-weight: normal\", \"background-color: blue; color: white; border-radius: 3px; padding: 0 2px 0 2px\", \"font-weight: normal\");\n}})(console);\nEOF;\n\n        $this->assertEquals($expected, $this->generateScript());\n    }\n\n    public function testContext()\n    {\n        $handler = new BrowserConsoleHandler();\n        $handler->setFormatter($this->getIdentityFormatter());\n\n        $handler->handle($this->getRecord(Level::Debug, 'test', ['foo' => 'bar', 0 => 'oop']));\n\n        $expected = <<<EOF\n(function (c) {if (c && c.groupCollapsed) {\nc.groupCollapsed(\"%ctest\", \"font-weight: normal\");\nc.log(\"%c%s\", \"font-weight: bold\", \"Context\");\nc.log(\"%s: %o\", \"foo\", \"bar\");\nc.log(\"%s: %o\", \"0\", \"oop\");\nc.groupEnd();\n}})(console);\nEOF;\n\n        $this->assertEquals($expected, $this->generateScript());\n    }\n\n    public function testConcurrentHandlers()\n    {\n        $handler1 = new BrowserConsoleHandler();\n        $handler1->setFormatter($this->getIdentityFormatter());\n\n        $handler2 = new BrowserConsoleHandler();\n        $handler2->setFormatter($this->getIdentityFormatter());\n\n        $handler1->handle($this->getRecord(Level::Debug, 'test1'));\n        $handler2->handle($this->getRecord(Level::Debug, 'test2'));\n        $handler1->handle($this->getRecord(Level::Debug, 'test3'));\n        $handler2->handle($this->getRecord(Level::Debug, 'test4'));\n\n        $expected = <<<EOF\n(function (c) {if (c && c.groupCollapsed) {\nc.debug(\"%ctest1\", \"font-weight: normal\");\nc.debug(\"%ctest2\", \"font-weight: normal\");\nc.debug(\"%ctest3\", \"font-weight: normal\");\nc.debug(\"%ctest4\", \"font-weight: normal\");\n}})(console);\nEOF;\n\n        $this->assertEquals($expected, $this->generateScript());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/BufferHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\nclass BufferHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    private TestHandler $shutdownCheckHandler;\n\n    /**\n     * @covers Monolog\\Handler\\BufferHandler::__construct\n     * @covers Monolog\\Handler\\BufferHandler::handle\n     * @covers Monolog\\Handler\\BufferHandler::close\n     */\n    public function testHandleBuffers()\n    {\n        $test = new TestHandler();\n        $handler = new BufferHandler($test);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        $this->assertFalse($test->hasDebugRecords());\n        $this->assertFalse($test->hasInfoRecords());\n        $handler->close();\n        $this->assertTrue($test->hasInfoRecords());\n        $this->assertCount(2, $test->getRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\BufferHandler::close\n     * @covers Monolog\\Handler\\BufferHandler::flush\n     */\n    public function testPropagatesRecordsAtEndOfRequest()\n    {\n        $test = new TestHandler();\n        $handler = new BufferHandler($test);\n        $handler->handle($this->getRecord(Level::Warning));\n        $handler->handle($this->getRecord(Level::Debug));\n        $this->shutdownCheckHandler = $test;\n        register_shutdown_function([$this, 'checkPropagation']);\n    }\n\n    public function checkPropagation()\n    {\n        if (!$this->shutdownCheckHandler->hasWarningRecords() || !$this->shutdownCheckHandler->hasDebugRecords()) {\n            echo '!!! BufferHandlerTest::testPropagatesRecordsAtEndOfRequest failed to verify that the messages have been propagated' . PHP_EOL;\n            exit(1);\n        }\n    }\n\n    /**\n     * @covers Monolog\\Handler\\BufferHandler::handle\n     */\n    public function testHandleBufferLimit()\n    {\n        $test = new TestHandler();\n        $handler = new BufferHandler($test, 2);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        $handler->handle($this->getRecord(Level::Warning));\n        $handler->close();\n        $this->assertTrue($test->hasWarningRecords());\n        $this->assertTrue($test->hasInfoRecords());\n        $this->assertFalse($test->hasDebugRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\BufferHandler::handle\n     */\n    public function testHandleBufferLimitWithFlushOnOverflow()\n    {\n        $test = new TestHandler();\n        $handler = new BufferHandler($test, 3, Level::Debug, true, true);\n\n        // send two records\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Debug));\n        $this->assertFalse($test->hasDebugRecords());\n        $this->assertCount(0, $test->getRecords());\n\n        // overflow\n        $handler->handle($this->getRecord(Level::Info));\n        $this->assertTrue($test->hasDebugRecords());\n        $this->assertCount(3, $test->getRecords());\n\n        // should buffer again\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertCount(3, $test->getRecords());\n\n        $handler->close();\n        $this->assertCount(5, $test->getRecords());\n        $this->assertTrue($test->hasWarningRecords());\n        $this->assertTrue($test->hasInfoRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\BufferHandler::handle\n     */\n    public function testHandleLevel()\n    {\n        $test = new TestHandler();\n        $handler = new BufferHandler($test, 0, Level::Info);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        $handler->handle($this->getRecord(Level::Warning));\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->close();\n        $this->assertTrue($test->hasWarningRecords());\n        $this->assertTrue($test->hasInfoRecords());\n        $this->assertFalse($test->hasDebugRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\BufferHandler::flush\n     */\n    public function testFlush()\n    {\n        $test = new TestHandler();\n        $handler = new BufferHandler($test, 0);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        $handler->flush();\n        $this->assertTrue($test->hasInfoRecords());\n        $this->assertTrue($test->hasDebugRecords());\n        $this->assertFalse($test->hasWarningRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\BufferHandler::handle\n     */\n    public function testHandleUsesProcessors()\n    {\n        $test = new TestHandler();\n        $handler = new BufferHandler($test);\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo'] = true;\n\n            return $record;\n        });\n        $handler->handle($this->getRecord(Level::Warning));\n        $handler->flush();\n        $this->assertTrue($test->hasWarningRecords());\n        $records = $test->getRecords();\n        $this->assertTrue($records[0]['extra']['foo']);\n    }\n\n    public function testSetHandler()\n    {\n        $testOriginal = new TestHandler();\n        $handler = new BufferHandler($testOriginal);\n        $handler->handle($this->getRecord(Level::Info));\n\n        $testNew = new TestHandler();\n        $handler->setHandler($testNew);\n\n        $handler->handle($this->getRecord(Level::Debug));\n\n        $handler->close();\n\n        $this->assertFalse($testOriginal->hasInfoRecords());\n        $this->assertFalse($testOriginal->hasDebugRecords());\n        $this->assertTrue($testNew->hasInfoRecords());\n        $this->assertTrue($testNew->hasDebugRecords());\n        $this->assertCount(2, $testNew->getRecords());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/ChromePHPHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\n/**\n * @covers Monolog\\Handler\\ChromePHPHandler\n */\nclass ChromePHPHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    protected function setUp(): void\n    {\n        TestChromePHPHandler::resetStatic();\n        $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; Chrome/1.0';\n    }\n\n    #[DataProvider('agentsProvider')]\n    public function testHeaders($agent)\n    {\n        $_SERVER['HTTP_USER_AGENT'] = $agent;\n\n        $handler = new TestChromePHPHandler();\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Warning));\n\n        $expected = [\n            'X-ChromeLogger-Data'   => base64_encode(json_encode([\n                'version' => '4.0',\n                'columns' => ['label', 'log', 'backtrace', 'type'],\n                'rows' => [\n                    'test',\n                    'test',\n                ],\n                'request_uri' => '',\n            ])),\n        ];\n\n        $this->assertEquals($expected, $handler->getHeaders());\n    }\n\n    public static function agentsProvider()\n    {\n        return [\n            ['Monolog Test; Chrome/1.0'],\n            ['Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0'],\n            ['Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/56.0.2924.76 Chrome/56.0.2924.76 Safari/537.36'],\n            ['Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome Safari/537.36'],\n        ];\n    }\n\n    public function testHeadersOverflow()\n    {\n        $handler = new TestChromePHPHandler();\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Warning, str_repeat('a', 2 * 1024)));\n\n        // overflow chrome headers limit\n        $handler->handle($this->getRecord(Level::Warning, str_repeat('b', 2 * 1024)));\n\n        $expected = [\n            'X-ChromeLogger-Data'   => base64_encode(json_encode([\n                'version' => '4.0',\n                'columns' => ['label', 'log', 'backtrace', 'type'],\n                'rows' => [\n                    [\n                        'test',\n                        'test',\n                        'unknown',\n                        'log',\n                    ],\n                    [\n                        'test',\n                        str_repeat('a', 2 * 1024),\n                        'unknown',\n                        'warn',\n                    ],\n                    [\n                        'monolog',\n                        'Incomplete logs, chrome header size limit reached',\n                        'unknown',\n                        'warn',\n                    ],\n                ],\n                'request_uri' => '',\n            ])),\n        ];\n\n        $this->assertEquals($expected, $handler->getHeaders());\n    }\n\n    public function testConcurrentHandlers()\n    {\n        $handler = new TestChromePHPHandler();\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Warning));\n\n        $handler2 = new TestChromePHPHandler();\n        $handler2->setFormatter($this->getIdentityFormatter());\n        $handler2->handle($this->getRecord(Level::Debug));\n        $handler2->handle($this->getRecord(Level::Warning));\n\n        $expected = [\n            'X-ChromeLogger-Data'   => base64_encode(json_encode([\n                'version' => '4.0',\n                'columns' => ['label', 'log', 'backtrace', 'type'],\n                'rows' => [\n                    'test',\n                    'test',\n                    'test',\n                    'test',\n                ],\n                'request_uri' => '',\n            ])),\n        ];\n\n        $this->assertEquals($expected, $handler2->getHeaders());\n    }\n}\n\nclass TestChromePHPHandler extends ChromePHPHandler\n{\n    protected array $headers = [];\n\n    public static function resetStatic(): void\n    {\n        self::$initialized = false;\n        self::$overflowed = false;\n        self::$sendHeaders = true;\n        self::$json['rows'] = [];\n    }\n\n    protected function sendHeader(string $header, string $content): void\n    {\n        $this->headers[$header] = $content;\n    }\n\n    public function getHeaders(): array\n    {\n        return $this->headers;\n    }\n\n    protected function isWebRequest(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/CouchDBHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\nclass CouchDBHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testHandle()\n    {\n        $record = $this->getRecord(Level::Warning, 'test', ['data' => new \\stdClass, 'foo' => 34]);\n\n        $handler = new CouchDBHandler();\n\n        try {\n            $handler->handle($record);\n        } catch (\\RuntimeException $e) {\n            $this->markTestSkipped('Could not connect to couchdb server on http://localhost:5984');\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/DeduplicationHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\nclass DeduplicationHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Handler\\DeduplicationHandler::flush\n     */\n    public function testFlushPassthruIfAllRecordsUnderTrigger()\n    {\n        $test = new TestHandler();\n        @unlink(sys_get_temp_dir().'/monolog_dedup.log');\n        $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', Level::Debug);\n\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n\n        $handler->flush();\n\n        $this->assertTrue($test->hasInfoRecords());\n        $this->assertTrue($test->hasDebugRecords());\n        $this->assertFalse($test->hasWarningRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\DeduplicationHandler::flush\n     * @covers Monolog\\Handler\\DeduplicationHandler::appendRecord\n     */\n    public function testFlushPassthruIfEmptyLog()\n    {\n        $test = new TestHandler();\n        @unlink(sys_get_temp_dir().'/monolog_dedup.log');\n        $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', Level::Debug);\n\n        $handler->handle($this->getRecord(Level::Error, 'Foo:bar'));\n        $handler->handle($this->getRecord(Level::Critical, \"Foo\\nbar\"));\n\n        $handler->flush();\n\n        $this->assertTrue($test->hasErrorRecords());\n        $this->assertTrue($test->hasCriticalRecords());\n        $this->assertFalse($test->hasWarningRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\DeduplicationHandler::flush\n     * @covers Monolog\\Handler\\DeduplicationHandler::appendRecord\n     * @covers Monolog\\Handler\\DeduplicationHandler::isDuplicate\n     * @depends testFlushPassthruIfEmptyLog\n     */\n    public function testFlushSkipsIfLogExists()\n    {\n        $test = new TestHandler();\n        $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', Level::Debug);\n\n        $handler->handle($this->getRecord(Level::Error, 'Foo:bar'));\n        $handler->handle($this->getRecord(Level::Critical, \"Foo\\nbar\"));\n\n        $handler->flush();\n\n        $this->assertFalse($test->hasErrorRecords());\n        $this->assertFalse($test->hasCriticalRecords());\n        $this->assertFalse($test->hasWarningRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\DeduplicationHandler::flush\n     * @covers Monolog\\Handler\\DeduplicationHandler::appendRecord\n     * @covers Monolog\\Handler\\DeduplicationHandler::isDuplicate\n     * @depends testFlushPassthruIfEmptyLog\n     */\n    public function testFlushPassthruIfLogTooOld()\n    {\n        $test = new TestHandler();\n        $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', Level::Debug);\n\n        $record = $this->getRecord(Level::Error, datetime: new \\DateTimeImmutable('+62seconds'));\n        $handler->handle($record);\n        $record = $this->getRecord(Level::Critical, datetime: new \\DateTimeImmutable('+62seconds'));\n        $handler->handle($record);\n\n        $handler->flush();\n\n        $this->assertTrue($test->hasErrorRecords());\n        $this->assertTrue($test->hasCriticalRecords());\n        $this->assertFalse($test->hasWarningRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\DeduplicationHandler::flush\n     * @covers Monolog\\Handler\\DeduplicationHandler::appendRecord\n     * @covers Monolog\\Handler\\DeduplicationHandler::isDuplicate\n     * @covers Monolog\\Handler\\DeduplicationHandler::collectLogs\n     */\n    public function testGcOldLogs()\n    {\n        $test = new TestHandler();\n        @unlink(sys_get_temp_dir().'/monolog_dedup.log');\n        $handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', Level::Debug);\n\n        // handle two records from yesterday, and one recent\n        $record = $this->getRecord(Level::Error, datetime: new \\DateTimeImmutable('-1day -10seconds'));\n        $handler->handle($record);\n        $record2 = $this->getRecord(Level::Critical, datetime: new \\DateTimeImmutable('-1day -10seconds'));\n        $handler->handle($record2);\n        $record3 = $this->getRecord(Level::Critical, datetime: new \\DateTimeImmutable('-30seconds'));\n        $handler->handle($record3);\n\n        // log is written as none of them are duplicate\n        $handler->flush();\n        $this->assertSame(\n            $record->datetime->getTimestamp() . \":ERROR:test\\n\" .\n            $record2['datetime']->getTimestamp() . \":CRITICAL:test\\n\" .\n            $record3['datetime']->getTimestamp() . \":CRITICAL:test\\n\",\n            file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log')\n        );\n        $this->assertTrue($test->hasErrorRecords());\n        $this->assertTrue($test->hasCriticalRecords());\n        $this->assertFalse($test->hasWarningRecords());\n\n        // clear test handler\n        $test->clear();\n        $this->assertFalse($test->hasErrorRecords());\n        $this->assertFalse($test->hasCriticalRecords());\n\n        // log new records, duplicate log gets GC'd at the end of this flush call\n        $handler->handle($record = $this->getRecord(Level::Error));\n        $handler->handle($record2 = $this->getRecord(Level::Critical));\n        $handler->flush();\n\n        // log should now contain the new errors and the previous one that was recent enough\n        $this->assertSame(\n            $record3['datetime']->getTimestamp() . \":CRITICAL:test\\n\" .\n            $record->datetime->getTimestamp() . \":ERROR:test\\n\" .\n            $record2['datetime']->getTimestamp() . \":CRITICAL:test\\n\",\n            file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log')\n        );\n        $this->assertTrue($test->hasErrorRecords());\n        $this->assertTrue($test->hasCriticalRecords());\n        $this->assertFalse($test->hasWarningRecords());\n    }\n\n    public static function tearDownAfterClass(): void\n    {\n        @unlink(sys_get_temp_dir().'/monolog_dedup.log');\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\nclass DoctrineCouchDBHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    protected function setUp(): void\n    {\n        if (!class_exists('Doctrine\\CouchDB\\CouchDBClient')) {\n            $this->markTestSkipped('The \"doctrine/couchdb\" package is not installed');\n        }\n    }\n\n    public function testHandle()\n    {\n        $client = $this->getMockBuilder('Doctrine\\\\CouchDB\\\\CouchDBClient')\n            ->onlyMethods(['postDocument'])\n            ->disableOriginalConstructor()\n            ->getMock();\n\n        $record = $this->getRecord(Level::Warning, 'test', ['data' => new \\stdClass, 'foo' => 34]);\n\n        $expected = [\n            'message' => 'test',\n            'context' => ['data' => ['stdClass' => []], 'foo' => 34],\n            'level' => Level::Warning->value,\n            'level_name' => 'WARNING',\n            'channel' => 'test',\n            'datetime' => (string) $record->datetime,\n            'extra' => [],\n        ];\n\n        $client->expects($this->once())\n            ->method('postDocument')\n            ->with($expected);\n\n        $handler = new DoctrineCouchDBHandler($client);\n        $handler->handle($record);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/DynamoDbHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Aws\\DynamoDb\\DynamoDbClient;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\n\nclass DynamoDbHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    private DynamoDbClient&MockObject $client;\n\n    private bool $isV3;\n\n    public function setUp(): void\n    {\n        if (!class_exists(DynamoDbClient::class)) {\n            $this->markTestSkipped('aws/aws-sdk-php not installed');\n        }\n\n        $this->isV3 = \\defined('Aws\\Sdk::VERSION') && version_compare(\\Aws\\Sdk::VERSION, '3.0', '>=');\n\n        $implementedMethods = ['__call'];\n\n        $clientMockBuilder = $this->getMockBuilder(DynamoDbClient::class)\n            ->onlyMethods($implementedMethods)\n            ->disableOriginalConstructor();\n\n        $this->client = $clientMockBuilder->getMock();\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->client);\n    }\n\n    public function testGetFormatter()\n    {\n        $handler = new DynamoDbHandler($this->client, 'foo');\n        $this->assertInstanceOf('Monolog\\Formatter\\ScalarFormatter', $handler->getFormatter());\n    }\n\n    public function testHandle()\n    {\n        $record = $this->getRecord();\n        $formatter = $this->createMock('Monolog\\Formatter\\FormatterInterface');\n        $formatted = ['foo' => 1, 'bar' => 2];\n        $handler = new DynamoDbHandler($this->client, 'foo');\n        $handler->setFormatter($formatter);\n\n        if ($this->isV3) {\n            $expFormatted = ['foo' => ['N' => 1], 'bar' => ['N' => 2]];\n        } else {\n            $expFormatted = $formatted;\n        }\n\n        $formatter\n             ->expects($this->once())\n             ->method('format')\n             ->with($record)\n             ->willReturn($formatted);\n        $this->client\n             ->expects($this->once())\n             ->method('__call')\n             ->with('putItem', [[\n                 'TableName' => 'foo',\n                 'Item' => $expFormatted,\n             ]]);\n\n        $handler->handle($record);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/ElasticaHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\ElasticaFormatter;\nuse Monolog\\Formatter\\NormalizerFormatter;\nuse Monolog\\Level;\nuse Elastica\\Client;\nuse Elastica\\Request;\nuse Elastica\\Response;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n#[Group('Elastica')]\nclass ElasticaHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @var Client mock\n     */\n    protected Client $client;\n\n    /**\n     * @var array Default handler options\n     */\n    protected array $options = [\n        'index' => 'my_index',\n        'type'  => 'doc_type',\n    ];\n\n    public function setUp(): void\n    {\n        // Elastica lib required\n        if (!class_exists(\"Elastica\\Client\")) {\n            $this->markTestSkipped(\"ruflin/elastica not installed\");\n        }\n\n        // base mock Elastica Client object\n        $this->client = $this->getMockBuilder('Elastica\\Client')\n            ->onlyMethods(['addDocuments'])\n            ->disableOriginalConstructor()\n            ->getMock();\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->client);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ElasticaHandler::write\n     * @covers Monolog\\Handler\\ElasticaHandler::handleBatch\n     * @covers Monolog\\Handler\\ElasticaHandler::bulkSend\n     * @covers Monolog\\Handler\\ElasticaHandler::getDefaultFormatter\n     */\n    public function testHandle()\n    {\n        // log message\n        $msg = $this->getRecord(Level::Error, 'log', context: ['foo' => 7, 'bar', 'class' => new \\stdClass], datetime: new \\DateTimeImmutable(\"@0\"));\n\n        // format expected result\n        $formatter = new ElasticaFormatter($this->options['index'], $this->options['type']);\n        $expected = [$formatter->format($msg)];\n\n        // setup ES client mock\n        $this->client->expects($this->any())\n            ->method('addDocuments')\n            ->with($expected);\n\n        // perform tests\n        $handler = new ElasticaHandler($this->client, $this->options);\n        $handler->handle($msg);\n        $handler->handleBatch([$msg]);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ElasticaHandler::setFormatter\n     */\n    public function testSetFormatter()\n    {\n        $handler = new ElasticaHandler($this->client);\n        $formatter = new ElasticaFormatter('index_new', 'type_new');\n        $handler->setFormatter($formatter);\n        $this->assertInstanceOf('Monolog\\Formatter\\ElasticaFormatter', $handler->getFormatter());\n        $this->assertEquals('index_new', $handler->getFormatter()->getIndex());\n        $this->assertEquals('type_new', $handler->getFormatter()->getType());\n    }\n\n    /**\n     * @covers                   Monolog\\Handler\\ElasticaHandler::setFormatter\n     */\n    public function testSetFormatterInvalid()\n    {\n        $handler = new ElasticaHandler($this->client);\n        $formatter = new NormalizerFormatter();\n\n        $this->expectException(\\InvalidArgumentException::class);\n        $this->expectExceptionMessage('ElasticaHandler is only compatible with ElasticaFormatter');\n\n        $handler->setFormatter($formatter);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ElasticaHandler::__construct\n     * @covers Monolog\\Handler\\ElasticaHandler::getOptions\n     */\n    public function testOptions()\n    {\n        $expected = [\n            'index' => $this->options['index'],\n            'type' => $this->options['type'],\n            'ignore_error' => false,\n        ];\n        $handler = new ElasticaHandler($this->client, $this->options);\n        $this->assertEquals($expected, $handler->getOptions());\n    }\n\n    /**\n     * @covers       Monolog\\Handler\\ElasticaHandler::bulkSend\n     */\n    #[DataProvider('providerTestConnectionErrors')]\n    public function testConnectionErrors($ignore, $expectedError)\n    {\n        $clientOpts = ['host' => '127.0.0.1', 'port' => 1];\n        $client = new Client($clientOpts);\n        $handlerOpts = ['ignore_error' => $ignore];\n        $handler = new ElasticaHandler($client, $handlerOpts);\n\n        if ($expectedError) {\n            $this->expectException($expectedError[0]);\n            $this->expectExceptionMessage($expectedError[1]);\n            $handler->handle($this->getRecord());\n        } else {\n            $this->assertFalse($handler->handle($this->getRecord()));\n        }\n    }\n\n    public static function providerTestConnectionErrors(): array\n    {\n        return [\n            [false, ['RuntimeException', 'Error sending messages to Elasticsearch']],\n            [true, false],\n        ];\n    }\n\n    /**\n     * Integration test using localhost Elastic Search server version 7+\n     *\n     * @covers Monolog\\Handler\\ElasticaHandler::__construct\n     * @covers Monolog\\Handler\\ElasticaHandler::handleBatch\n     * @covers Monolog\\Handler\\ElasticaHandler::bulkSend\n     * @covers Monolog\\Handler\\ElasticaHandler::getDefaultFormatter\n     */\n    public function testHandleIntegrationNewESVersion()\n    {\n        $msg = $this->getRecord(Level::Error, 'log', context: ['foo' => 7, 'bar', 'class' => new \\stdClass], datetime: new \\DateTimeImmutable(\"@0\"));\n\n        $expected = (array) $msg;\n        $expected['datetime'] = $msg['datetime']->format(\\DateTime::ATOM);\n        $expected['context'] = [\n            'class' => '[object] (stdClass: {})',\n            'foo' => 7,\n            0 => 'bar',\n        ];\n\n        $clientOpts = ['url' => 'http://elastic:changeme@127.0.0.1:9200'];\n        $client = new Client($clientOpts);\n\n        $handler = new ElasticaHandler($client, $this->options);\n\n        try {\n            $handler->handleBatch([$msg]);\n        } catch (\\RuntimeException $e) {\n            $this->markTestSkipped(\"Cannot connect to Elastic Search server on localhost\");\n        }\n\n        // check document id from ES server response\n        $documentId = $this->getCreatedDocId($client->getLastResponse());\n        $this->assertNotEmpty($documentId, 'No elastic document id received');\n\n        // retrieve document source from ES and validate\n        $document = $this->getDocSourceFromElastic(\n            $client,\n            $this->options['index'],\n            null,\n            $documentId\n        );\n        $this->assertEquals($expected, $document);\n\n        // remove test index from ES\n        $client->request(\"/{$this->options['index']}\", Request::DELETE);\n    }\n\n    /**\n     * Return last created document id from ES response\n     * @param Response $response Elastica Response object\n     */\n    protected function getCreatedDocId(Response $response): ?string\n    {\n        $data = $response->getData();\n\n        if (!empty($data['items'][0]['index']['_id'])) {\n            return $data['items'][0]['index']['_id'];\n        }\n\n        var_dump('Unexpected response: ', $data);\n\n        return null;\n    }\n\n    /**\n     * Retrieve document by id from Elasticsearch\n     * @param Client  $client Elastica client\n     * @param ?string $type\n     */\n    protected function getDocSourceFromElastic(Client $client, string $index, $type, string $documentId): array\n    {\n        if ($type === null) {\n            $path  = \"/{$index}/_doc/{$documentId}\";\n        } else {\n            $path  = \"/{$index}/{$type}/{$documentId}\";\n        }\n        $resp = $client->request($path, Request::GET);\n        $data = $resp->getData();\n        if (!empty($data['_source'])) {\n            return $data['_source'];\n        }\n\n        return [];\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/ElasticsearchHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\ElasticsearchFormatter;\nuse Monolog\\Formatter\\NormalizerFormatter;\nuse Monolog\\Level;\nuse Elasticsearch\\Client;\nuse Elastic\\Elasticsearch\\Client as Client8;\nuse Elasticsearch\\ClientBuilder;\nuse Elastic\\Elasticsearch\\ClientBuilder as ClientBuilder8;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\Group;\n\n#[Group('Elasticsearch')]\n#[CoversClass(ElasticsearchHandler::class)]\nclass ElasticsearchHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    protected Client|Client8 $client;\n\n    /**\n     * @var array Default handler options\n     */\n    protected array $options = [\n        'index' => 'my_index',\n        'type'  => 'doc_type',\n        'op_type' => 'index',\n    ];\n\n    public function setUp(): void\n    {\n        $hosts = ['http://elastic:changeme@127.0.0.1:9200'];\n        $this->client = $this->getClientBuilder()\n            ->setHosts($hosts)\n            ->build();\n\n        try {\n            $this->client->info();\n        } catch (\\Throwable $e) {\n            $this->markTestSkipped('Could not connect to Elasticsearch on 127.0.0.1:9200');\n        }\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->client);\n    }\n\n    public function testSetFormatter()\n    {\n        $handler = new ElasticsearchHandler($this->client);\n        $formatter = new ElasticsearchFormatter('index_new', 'type_new');\n        $handler->setFormatter($formatter);\n        $this->assertInstanceOf('Monolog\\Formatter\\ElasticsearchFormatter', $handler->getFormatter());\n        $this->assertEquals('index_new', $handler->getFormatter()->getIndex());\n        $this->assertEquals('type_new', $handler->getFormatter()->getType());\n    }\n\n    public function testSetFormatterInvalid()\n    {\n        $handler = new ElasticsearchHandler($this->client);\n        $formatter = new NormalizerFormatter();\n\n        $this->expectException(\\InvalidArgumentException::class);\n        $this->expectExceptionMessage('ElasticsearchHandler is only compatible with ElasticsearchFormatter');\n\n        $handler->setFormatter($formatter);\n    }\n\n    public function testOptions()\n    {\n        $expected = [\n            'index' => $this->options['index'],\n            'type' => $this->options['type'],\n            'ignore_error' => false,\n            'op_type' => $this->options['op_type'],\n        ];\n\n        if ($this->client instanceof Client8 || $this->client::VERSION[0] === '7') {\n            $expected['type'] = '_doc';\n        }\n\n        $handler = new ElasticsearchHandler($this->client, $this->options);\n        $this->assertEquals($expected, $handler->getOptions());\n    }\n\n    #[DataProvider('providerTestConnectionErrors')]\n    public function testConnectionErrors($ignore, $expectedError)\n    {\n        $hosts = ['http://127.0.0.1:1'];\n        $client = $this->getClientBuilder()\n            ->setHosts($hosts)\n            ->build();\n\n        $handlerOpts = ['ignore_error' => $ignore];\n        $handler = new ElasticsearchHandler($client, $handlerOpts);\n\n        if ($expectedError) {\n            $this->expectException($expectedError[0]);\n            $this->expectExceptionMessage($expectedError[1]);\n            $handler->handle($this->getRecord());\n        } else {\n            $this->assertFalse($handler->handle($this->getRecord()));\n        }\n    }\n\n    public static function providerTestConnectionErrors(): array\n    {\n        return [\n            [false, ['RuntimeException', 'Error sending messages to Elasticsearch']],\n            [true, false],\n        ];\n    }\n\n    /**\n     * Integration test using localhost Elasticsearch server\n     *\n     * @covers Monolog\\Handler\\ElasticsearchHandler::__construct\n     * @covers Monolog\\Handler\\ElasticsearchHandler::handleBatch\n     * @covers Monolog\\Handler\\ElasticsearchHandler::bulkSend\n     * @covers Monolog\\Handler\\ElasticsearchHandler::getDefaultFormatter\n     */\n    public function testHandleBatchIntegration()\n    {\n        $msg = $this->getRecord(Level::Error, 'log', context: ['foo' => 7, 'bar', 'class' => new \\stdClass], datetime: new \\DateTimeImmutable(\"@0\"));\n\n        $expected = $msg->toArray();\n        $expected['datetime'] = $msg['datetime']->format(\\DateTime::ATOM);\n        $expected['context'] = [\n            'class' => [\"stdClass\" => []],\n            'foo' => 7,\n            0 => 'bar',\n        ];\n\n        $hosts = ['http://elastic:changeme@127.0.0.1:9200'];\n        $client = $this->getClientBuilder()\n            ->setHosts($hosts)\n            ->build();\n        $handler = new ElasticsearchHandler($client, $this->options);\n        $handler->handleBatch([$msg]);\n\n        // check document id from ES server response\n        if ($client instanceof Client8) {\n            $messageBody = $client->getTransport()->getLastResponse()->getBody();\n\n            $info = json_decode((string) $messageBody, true);\n            $this->assertNotNull($info, 'Decoding failed');\n\n            $documentId = $this->getCreatedDocIdV8($info);\n            $this->assertNotEmpty($documentId, 'No elastic document id received');\n        } else {\n            $documentId = $this->getCreatedDocId($client->transport->getLastConnection()->getLastRequestInfo());\n            $this->assertNotEmpty($documentId, 'No elastic document id received');\n        }\n\n        // retrieve document source from ES and validate\n        $document = $this->getDocSourceFromElastic(\n            $client,\n            $this->options['index'],\n            $this->options['type'],\n            $documentId\n        );\n\n        $this->assertEquals($expected, $document);\n\n        // remove test index from ES\n        $client->indices()->delete(['index' => $this->options['index']]);\n    }\n\n    /**\n     * Return last created document id from ES response\n     *\n     * @param array $info Elasticsearch last request info\n     */\n    protected function getCreatedDocId(array $info): ?string\n    {\n        $data = json_decode($info['response']['body'], true);\n\n        if (!empty($data['items'][0]['index']['_id'])) {\n            return $data['items'][0]['index']['_id'];\n        }\n\n        return null;\n    }\n\n    /**\n     * Return last created document id from ES response\n     *\n     * @param  array       $data Elasticsearch last request info\n     * @return string|null\n     */\n    protected function getCreatedDocIdV8(array $data)\n    {\n        if (!empty($data['items'][0]['index']['_id'])) {\n            return $data['items'][0]['index']['_id'];\n        }\n\n        return null;\n    }\n\n    /**\n     * Retrieve document by id from Elasticsearch\n     *\n     * @return array<mixed>\n     */\n    protected function getDocSourceFromElastic(Client|Client8 $client, string $index, string $type, string $documentId): array\n    {\n        $params = [\n            'index' => $index,\n            'id' => $documentId,\n        ];\n\n        if (!$client instanceof Client8 && $client::VERSION[0] !== '7') {\n            $params['type'] = $type;\n        }\n\n        $data = $client->get($params);\n\n        if (!empty($data['_source'])) {\n            return $data['_source'];\n        }\n\n        return [];\n    }\n\n    /**\n     * @return ClientBuilder|ClientBuilder8\n     */\n    private function getClientBuilder()\n    {\n        if (class_exists(ClientBuilder8::class)) {\n            return ClientBuilder8::create();\n        }\n\n        return ClientBuilder::create();\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/ErrorLogHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\LineFormatter;\n\nfunction error_log()\n{\n    $GLOBALS['error_log'][] = \\func_get_args();\n}\n\nclass ErrorLogHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    protected function setUp(): void\n    {\n        $GLOBALS['error_log'] = [];\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ErrorLogHandler::__construct\n     */\n    public function testShouldNotAcceptAnInvalidTypeOnConstructor()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n        $this->expectExceptionMessage('The given message type \"42\" is not supported');\n\n        new ErrorLogHandler(42);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ErrorLogHandler::write\n     */\n    public function testShouldLogMessagesUsingErrorLogFunction()\n    {\n        $type = ErrorLogHandler::OPERATING_SYSTEM;\n        $handler = new ErrorLogHandler($type);\n        $handler->setFormatter(new LineFormatter('%channel%.%level_name%: %message% %context% %extra%', null, true));\n        $handler->handle($this->getRecord(Level::Error, \"Foo\\nBar\\r\\n\\r\\nBaz\"));\n\n        $this->assertSame(\"test.ERROR: Foo\\nBar\\r\\n\\r\\nBaz [] []\", $GLOBALS['error_log'][0][0]);\n        $this->assertSame($GLOBALS['error_log'][0][1], $type);\n\n        $handler = new ErrorLogHandler($type, Level::Debug, true, true);\n        $handler->setFormatter(new LineFormatter(null, null, true));\n        $handler->handle($this->getRecord(Level::Error, \"Foo\\nBar\\r\\n\\r\\nBaz\"));\n\n        $this->assertStringMatchesFormat('[%s] test.ERROR: Foo', $GLOBALS['error_log'][1][0]);\n        $this->assertSame($GLOBALS['error_log'][1][1], $type);\n\n        $this->assertStringMatchesFormat('Bar', $GLOBALS['error_log'][2][0]);\n        $this->assertSame($GLOBALS['error_log'][2][1], $type);\n\n        $this->assertStringMatchesFormat('Baz [] []', $GLOBALS['error_log'][3][0]);\n        $this->assertSame($GLOBALS['error_log'][3][1], $type);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/ExceptionTestHandler.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Exception;\nuse Monolog\\LogRecord;\n\nclass ExceptionTestHandler extends TestHandler\n{\n    /**\n     * @inheritDoc\n     */\n    protected function write(LogRecord $record): void\n    {\n        throw new Exception(\"ExceptionTestHandler::handle\");\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/FallbackGroupHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\LogRecord;\n\nclass FallbackGroupHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Handler\\FallbackGroupHandler::__construct\n     * @covers Monolog\\Handler\\FallbackGroupHandler::handle\n     */\n    public function testHandle()\n    {\n        $testHandlerOne = new TestHandler();\n        $testHandlerTwo = new TestHandler();\n        $testHandlers = [$testHandlerOne, $testHandlerTwo];\n        $handler = new FallbackGroupHandler($testHandlers);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n\n        $this->assertCount(2, $testHandlerOne->getRecords());\n        $this->assertCount(0, $testHandlerTwo->getRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FallbackGroupHandler::__construct\n     * @covers Monolog\\Handler\\FallbackGroupHandler::handle\n     */\n    public function testHandleExceptionThrown()\n    {\n        $testHandlerOne = new ExceptionTestHandler();\n        $testHandlerTwo = new TestHandler();\n        $testHandlers = [$testHandlerOne, $testHandlerTwo];\n        $handler = new FallbackGroupHandler($testHandlers);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n\n        $this->assertCount(0, $testHandlerOne->getRecords());\n        $this->assertCount(2, $testHandlerTwo->getRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FallbackGroupHandler::handleBatch\n     */\n    public function testHandleBatch()\n    {\n        $testHandlerOne = new TestHandler();\n        $testHandlerTwo = new TestHandler();\n        $testHandlers = [$testHandlerOne, $testHandlerTwo];\n        $handler = new FallbackGroupHandler($testHandlers);\n        $handler->handleBatch([$this->getRecord(Level::Debug), $this->getRecord(Level::Info)]);\n        $this->assertCount(2, $testHandlerOne->getRecords());\n        $this->assertCount(0, $testHandlerTwo->getRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FallbackGroupHandler::handleBatch\n     */\n    public function testHandleBatchExceptionThrown()\n    {\n        $testHandlerOne = new ExceptionTestHandler();\n        $testHandlerTwo = new TestHandler();\n        $testHandlers = [$testHandlerOne, $testHandlerTwo];\n        $handler = new FallbackGroupHandler($testHandlers);\n        $handler->handleBatch([$this->getRecord(Level::Debug), $this->getRecord(Level::Info)]);\n        $this->assertCount(0, $testHandlerOne->getRecords());\n        $this->assertCount(2, $testHandlerTwo->getRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FallbackGroupHandler::isHandling\n     */\n    public function testIsHandling()\n    {\n        $testHandlers = [new TestHandler(Level::Error), new TestHandler(Level::Warning)];\n        $handler = new FallbackGroupHandler($testHandlers);\n        $this->assertTrue($handler->isHandling($this->getRecord(Level::Error)));\n        $this->assertTrue($handler->isHandling($this->getRecord(Level::Warning)));\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Debug)));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FallbackGroupHandler::handle\n     */\n    public function testHandleUsesProcessors()\n    {\n        $test = new TestHandler();\n        $handler = new FallbackGroupHandler([$test]);\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo'] = true;\n\n            return $record;\n        });\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertTrue($test->hasWarningRecords());\n        $records = $test->getRecords();\n        $this->assertTrue($records[0]['extra']['foo']);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FallbackGroupHandler::handleBatch\n     */\n    public function testHandleBatchUsesProcessors()\n    {\n        $testHandlerOne = new ExceptionTestHandler();\n        $testHandlerTwo = new TestHandler();\n        $testHandlers = [$testHandlerOne, $testHandlerTwo];\n        $handler = new FallbackGroupHandler($testHandlers);\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo'] = true;\n\n            return $record;\n        });\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo2'] = true;\n\n            return $record;\n        });\n        $handler->handleBatch([$this->getRecord(Level::Debug), $this->getRecord(Level::Info)]);\n        $this->assertEmpty($testHandlerOne->getRecords());\n        $this->assertTrue($testHandlerTwo->hasDebugRecords());\n        $this->assertTrue($testHandlerTwo->hasInfoRecords());\n        $this->assertCount(2, $testHandlerTwo->getRecords());\n        $records = $testHandlerTwo->getRecords();\n        $this->assertTrue($records[0]['extra']['foo']);\n        $this->assertTrue($records[1]['extra']['foo']);\n        $this->assertTrue($records[0]['extra']['foo2']);\n        $this->assertTrue($records[1]['extra']['foo2']);\n    }\n\n    public function testProcessorsDoNotInterfereBetweenHandlers()\n    {\n        $t1 = new ExceptionTestHandler();\n        $t2 = new TestHandler();\n        $handler = new FallbackGroupHandler([$t1, $t2]);\n\n        $t1->pushProcessor(function (LogRecord $record) {\n            $record->extra['foo'] = 'bar';\n\n            return $record;\n        });\n        $handler->handle($this->getRecord());\n\n        self::assertSame([], $t2->getRecords()[0]->extra);\n    }\n\n    public function testProcessorsDoNotInterfereBetweenHandlersWithBatch()\n    {\n        $t1 = new ExceptionTestHandler();\n        $t2 = new TestHandler();\n        $handler = new FallbackGroupHandler([$t1, $t2]);\n\n        $t1->pushProcessor(function (LogRecord $record) {\n            $record->extra['foo'] = 'bar';\n\n            return $record;\n        });\n\n        $handler->handleBatch([$this->getRecord()]);\n\n        self::assertSame([], $t2->getRecords()[0]->extra);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/FilterHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\nclass FilterHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Handler\\FilterHandler::isHandling\n     */\n    public function testIsHandling()\n    {\n        $test    = new TestHandler();\n        $handler = new FilterHandler($test, Level::Info, Level::Notice);\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Debug)));\n        $this->assertTrue($handler->isHandling($this->getRecord(Level::Info)));\n        $this->assertTrue($handler->isHandling($this->getRecord(Level::Notice)));\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Warning)));\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Error)));\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Critical)));\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Alert)));\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Emergency)));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FilterHandler::handle\n     * @covers Monolog\\Handler\\FilterHandler::setAcceptedLevels\n     * @covers Monolog\\Handler\\FilterHandler::isHandling\n     */\n    public function testHandleProcessOnlyNeededLevels()\n    {\n        $test    = new TestHandler();\n        $handler = new FilterHandler($test, Level::Info, Level::Notice);\n\n        $handler->handle($this->getRecord(Level::Debug));\n        $this->assertFalse($test->hasDebugRecords());\n\n        $handler->handle($this->getRecord(Level::Info));\n        $this->assertTrue($test->hasInfoRecords());\n        $handler->handle($this->getRecord(Level::Notice));\n        $this->assertTrue($test->hasNoticeRecords());\n\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertFalse($test->hasWarningRecords());\n        $handler->handle($this->getRecord(Level::Error));\n        $this->assertFalse($test->hasErrorRecords());\n        $handler->handle($this->getRecord(Level::Critical));\n        $this->assertFalse($test->hasCriticalRecords());\n        $handler->handle($this->getRecord(Level::Alert));\n        $this->assertFalse($test->hasAlertRecords());\n        $handler->handle($this->getRecord(Level::Emergency));\n        $this->assertFalse($test->hasEmergencyRecords());\n\n        $test    = new TestHandler();\n        $handler = new FilterHandler($test, [Level::Info, Level::Error]);\n\n        $handler->handle($this->getRecord(Level::Debug));\n        $this->assertFalse($test->hasDebugRecords());\n        $handler->handle($this->getRecord(Level::Info));\n        $this->assertTrue($test->hasInfoRecords());\n        $handler->handle($this->getRecord(Level::Notice));\n        $this->assertFalse($test->hasNoticeRecords());\n        $handler->handle($this->getRecord(Level::Error));\n        $this->assertTrue($test->hasErrorRecords());\n        $handler->handle($this->getRecord(Level::Critical));\n        $this->assertFalse($test->hasCriticalRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FilterHandler::setAcceptedLevels\n     * @covers Monolog\\Handler\\FilterHandler::getAcceptedLevels\n     */\n    public function testAcceptedLevelApi()\n    {\n        $test    = new TestHandler();\n        $handler = new FilterHandler($test);\n\n        $levels = [Level::Info, Level::Error];\n        $levelsExpect = [Level::Info, Level::Error];\n        $handler->setAcceptedLevels($levels);\n        $this->assertSame($levelsExpect, $handler->getAcceptedLevels());\n\n        $handler->setAcceptedLevels(['info', 'error']);\n        $this->assertSame($levelsExpect, $handler->getAcceptedLevels());\n\n        $levels = [Level::Critical, Level::Alert, Level::Emergency];\n        $handler->setAcceptedLevels(Level::Critical, Level::Emergency);\n        $this->assertSame($levels, $handler->getAcceptedLevels());\n\n        $handler->setAcceptedLevels('critical', 'emergency');\n        $this->assertSame($levels, $handler->getAcceptedLevels());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FilterHandler::handle\n     */\n    public function testHandleUsesProcessors()\n    {\n        $test    = new TestHandler();\n        $handler = new FilterHandler($test, Level::Debug, Level::Emergency);\n        $handler->pushProcessor(\n            function ($record) {\n                $record->extra['foo'] = true;\n\n                return $record;\n            }\n        );\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertTrue($test->hasWarningRecords());\n        $records = $test->getRecords();\n        $this->assertTrue($records[0]['extra']['foo']);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FilterHandler::handle\n     */\n    public function testHandleRespectsBubble()\n    {\n        $test = new TestHandler();\n\n        $handler = new FilterHandler($test, Level::Info, Level::Notice, false);\n        $this->assertTrue($handler->handle($this->getRecord(Level::Info)));\n        $this->assertFalse($handler->handle($this->getRecord(Level::Warning)));\n\n        $handler = new FilterHandler($test, Level::Info, Level::Notice, true);\n        $this->assertFalse($handler->handle($this->getRecord(Level::Info)));\n        $this->assertFalse($handler->handle($this->getRecord(Level::Warning)));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FilterHandler::handle\n     */\n    public function testHandleWithCallback()\n    {\n        $test    = new TestHandler();\n        $handler = new FilterHandler(\n            function ($record, $handler) use ($test) {\n                return $test;\n            },\n            Level::Info,\n            Level::Notice,\n            false\n        );\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        $this->assertFalse($test->hasDebugRecords());\n        $this->assertTrue($test->hasInfoRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FilterHandler::handle\n     */\n    public function testHandleWithBadCallbackThrowsException()\n    {\n        $handler = new FilterHandler(\n            function ($record, $handler) {\n                return 'foo';\n            }\n        );\n\n        $this->expectException(\\RuntimeException::class);\n\n        $handler->handle($this->getRecord(Level::Warning));\n    }\n\n    public function testHandleEmptyBatch()\n    {\n        $test = new TestHandler();\n        $handler = new FilterHandler($test);\n        $handler->handleBatch([]);\n        $this->assertSame([], $test->getRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FilterHandler::handle\n     * @covers Monolog\\Handler\\FilterHandler::reset\n     */\n    public function testResetTestHandler()\n    {\n        $test = new TestHandler();\n        $handler = new FilterHandler($test, [Level::Info, Level::Error]);\n\n        $handler->handle($this->getRecord(Level::Info));\n        $this->assertTrue($test->hasInfoRecords());\n\n        $handler->handle($this->getRecord(Level::Error));\n        $this->assertTrue($test->hasErrorRecords());\n\n        $handler->reset();\n\n        $this->assertFalse($test->hasInfoRecords());\n        $this->assertFalse($test->hasInfoRecords());\n\n        $this->assertSame([], $test->getRecords());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/FingersCrossedHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy;\nuse Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy;\nuse Psr\\Log\\LogLevel;\n\nclass FingersCrossedHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::__construct\n     * @covers Monolog\\Handler\\FingersCrossedHandler::handle\n     * @covers Monolog\\Handler\\FingersCrossedHandler::activate\n     */\n    public function testHandleBuffers()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        $this->assertFalse($test->hasDebugRecords());\n        $this->assertFalse($test->hasInfoRecords());\n        $handler->handle($this->getRecord(Level::Warning));\n        $handler->close();\n        $this->assertTrue($test->hasInfoRecords());\n        $this->assertCount(3, $test->getRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::handle\n     * @covers Monolog\\Handler\\FingersCrossedHandler::activate\n     */\n    public function testHandleStopsBufferingAfterTrigger()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test);\n        $handler->handle($this->getRecord(Level::Warning));\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->close();\n        $this->assertTrue($test->hasWarningRecords());\n        $this->assertTrue($test->hasDebugRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::handle\n     * @covers Monolog\\Handler\\FingersCrossedHandler::activate\n     * @covers Monolog\\Handler\\FingersCrossedHandler::reset\n     */\n    public function testHandleResetBufferingAfterReset()\n    {\n        $test = new TestHandler();\n        $test->setSkipReset(true);\n        $handler = new FingersCrossedHandler($test);\n        $handler->handle($this->getRecord(Level::Warning));\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->reset();\n        $handler->handle($this->getRecord(Level::Info));\n        $handler->close();\n        $this->assertTrue($test->hasWarningRecords());\n        $this->assertTrue($test->hasDebugRecords());\n        $this->assertFalse($test->hasInfoRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::handle\n     * @covers Monolog\\Handler\\FingersCrossedHandler::activate\n     */\n    public function testHandleResetBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, Level::Warning, 0, false, false);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Warning));\n        $handler->handle($this->getRecord(Level::Info));\n        $handler->close();\n        $this->assertTrue($test->hasWarningRecords());\n        $this->assertTrue($test->hasDebugRecords());\n        $this->assertFalse($test->hasInfoRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::handle\n     * @covers Monolog\\Handler\\FingersCrossedHandler::activate\n     */\n    public function testHandleBufferLimit()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, Level::Warning, 2);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertTrue($test->hasWarningRecords());\n        $this->assertTrue($test->hasInfoRecords());\n        $this->assertFalse($test->hasDebugRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::handle\n     * @covers Monolog\\Handler\\FingersCrossedHandler::activate\n     */\n    public function testHandleWithCallback()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler(function ($record, $handler) use ($test) {\n            return $test;\n        });\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        $this->assertFalse($test->hasDebugRecords());\n        $this->assertFalse($test->hasInfoRecords());\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertTrue($test->hasInfoRecords());\n        $this->assertCount(3, $test->getRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::handle\n     * @covers Monolog\\Handler\\FingersCrossedHandler::activate\n     */\n    public function testHandleWithBadCallbackThrowsException()\n    {\n        $handler = new FingersCrossedHandler(function ($record, $handler) {\n            return 'foo';\n        });\n\n        $this->expectException(\\RuntimeException::class);\n\n        $handler->handle($this->getRecord(Level::Warning));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::isHandling\n     */\n    public function testIsHandlingAlways()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, Level::Error);\n        $this->assertTrue($handler->isHandling($this->getRecord(Level::Debug)));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::__construct\n     * @covers Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy::__construct\n     * @covers Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy::isHandlerActivated\n     */\n    public function testErrorLevelActivationStrategy()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Level::Warning));\n        $handler->handle($this->getRecord(Level::Debug));\n        $this->assertFalse($test->hasDebugRecords());\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertTrue($test->hasDebugRecords());\n        $this->assertTrue($test->hasWarningRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::__construct\n     * @covers Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy::__construct\n     * @covers Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy::isHandlerActivated\n     */\n    public function testErrorLevelActivationStrategyWithPsrLevel()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning'));\n        $handler->handle($this->getRecord(Level::Debug));\n        $this->assertFalse($test->hasDebugRecords());\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertTrue($test->hasDebugRecords());\n        $this->assertTrue($test->hasWarningRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::__construct\n     * @covers Monolog\\Handler\\FingersCrossedHandler::activate\n     */\n    public function testOverrideActivationStrategy()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy('warning'));\n        $handler->handle($this->getRecord(Level::Debug));\n        $this->assertFalse($test->hasDebugRecords());\n        $handler->activate();\n        $this->assertTrue($test->hasDebugRecords());\n        $handler->handle($this->getRecord(Level::Info));\n        $this->assertTrue($test->hasInfoRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy::__construct\n     * @covers Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy::isHandlerActivated\n     */\n    public function testChannelLevelActivationStrategy()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy(Level::Error, ['othertest' => Level::Debug]));\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertFalse($test->hasWarningRecords());\n        $record = $this->getRecord(Level::Debug, channel: 'othertest');\n        $handler->handle($record);\n        $this->assertTrue($test->hasDebugRecords());\n        $this->assertTrue($test->hasWarningRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy::__construct\n     * @covers Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy::isHandlerActivated\n     */\n    public function testChannelLevelActivationStrategyWithPsrLevels()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy('error', ['othertest' => 'debug']));\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertFalse($test->hasWarningRecords());\n        $record = $this->getRecord(Level::Debug, channel: 'othertest');\n        $handler->handle($record);\n        $this->assertTrue($test->hasDebugRecords());\n        $this->assertTrue($test->hasWarningRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::handle\n     * @covers Monolog\\Handler\\FingersCrossedHandler::activate\n     */\n    public function testHandleUsesProcessors()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, Level::Info);\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo'] = true;\n\n            return $record;\n        });\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertTrue($test->hasWarningRecords());\n        $records = $test->getRecords();\n        $this->assertTrue($records[0]['extra']['foo']);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::close\n     */\n    public function testPassthruOnClose()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Level::Warning), 0, true, true, Level::Info);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        $handler->handle($this->getRecord(Level::Notice));\n        $handler->close();\n        $this->assertFalse($test->hasDebugRecords());\n        $this->assertTrue($test->hasInfoRecords());\n        $this->assertTrue($test->hasNoticeRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\FingersCrossedHandler::close\n     */\n    public function testPsrLevelPassthruOnClose()\n    {\n        $test = new TestHandler();\n        $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Level::Warning), 0, true, true, LogLevel::INFO);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        $handler->handle($this->getRecord(Level::Notice));\n        $handler->close();\n        $this->assertFalse($test->hasDebugRecords());\n        $this->assertTrue($test->hasInfoRecords());\n        $this->assertTrue($test->hasNoticeRecords());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/FirePHPHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\n/**\n * @covers Monolog\\Handler\\FirePHPHandler\n */\nclass FirePHPHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function setUp(): void\n    {\n        TestFirePHPHandler::resetStatic();\n        $_SERVER['HTTP_USER_AGENT'] = 'Monolog Test; FirePHP/1.0';\n    }\n\n    public function testHeaders()\n    {\n        $handler = new TestFirePHPHandler;\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Warning));\n\n        $expected = [\n            'X-Wf-Protocol-1'    => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2',\n            'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1',\n            'X-Wf-1-Plugin-1'    => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3',\n            'X-Wf-1-1-1-1'       => 'test',\n            'X-Wf-1-1-1-2'       => 'test',\n        ];\n\n        $this->assertEquals($expected, $handler->getHeaders());\n    }\n\n    public function testConcurrentHandlers()\n    {\n        $handler = new TestFirePHPHandler;\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Warning));\n\n        $handler2 = new TestFirePHPHandler;\n        $handler2->setFormatter($this->getIdentityFormatter());\n        $handler2->handle($this->getRecord(Level::Debug));\n        $handler2->handle($this->getRecord(Level::Warning));\n\n        $expected = [\n            'X-Wf-Protocol-1'    => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2',\n            'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1',\n            'X-Wf-1-Plugin-1'    => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3',\n            'X-Wf-1-1-1-1'       => 'test',\n            'X-Wf-1-1-1-2'       => 'test',\n        ];\n\n        $expected2 = [\n            'X-Wf-1-1-1-3'       => 'test',\n            'X-Wf-1-1-1-4'       => 'test',\n        ];\n\n        $this->assertEquals($expected, $handler->getHeaders());\n        $this->assertEquals($expected2, $handler2->getHeaders());\n    }\n}\n\nclass TestFirePHPHandler extends FirePHPHandler\n{\n    protected array $headers = [];\n\n    public static function resetStatic(): void\n    {\n        self::$initialized = false;\n        self::$sendHeaders = true;\n        self::$messageIndex = 1;\n    }\n\n    protected function sendHeader(string $header, string $content): void\n    {\n        $this->headers[$header] = $content;\n    }\n\n    public function getHeaders(): array\n    {\n        return $this->headers;\n    }\n\n    protected function isWebRequest(): bool\n    {\n        return true;\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/Fixtures/.gitkeep",
    "content": ""
  },
  {
    "path": "tests/Monolog/Handler/FleepHookHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Level;\n\n/**\n * @coversDefaultClass \\Monolog\\Handler\\FleepHookHandler\n */\nclass FleepHookHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * Default token to use in tests\n     */\n    const TOKEN = '123abc';\n\n    private FleepHookHandler $handler;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        if (!\\extension_loaded('openssl')) {\n            $this->markTestSkipped('This test requires openssl extension to run');\n        }\n\n        // Create instances of the handler and logger for convenience\n        $this->handler = new FleepHookHandler(self::TOKEN);\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->handler);\n    }\n\n    /**\n     * @covers ::__construct\n     */\n    public function testConstructorSetsExpectedDefaults()\n    {\n        $this->assertEquals(Level::Debug, $this->handler->getLevel());\n        $this->assertEquals(true, $this->handler->getBubble());\n    }\n\n    /**\n     * @covers ::getDefaultFormatter\n     */\n    public function testHandlerUsesLineFormatterWhichIgnoresEmptyArrays()\n    {\n        $record = $this->getRecord(Level::Debug, 'msg');\n\n        $expectedFormatter = new LineFormatter(null, null, true, true);\n        $expected = $expectedFormatter->format($record);\n\n        $handlerFormatter = $this->handler->getFormatter();\n        $actual = $handlerFormatter->format($record);\n\n        $this->assertEquals($expected, $actual, 'Empty context and extra arrays should not be rendered');\n    }\n\n    /**\n     * @covers ::__construct\n     */\n    public function testConnectionStringisConstructedCorrectly()\n    {\n        $this->assertEquals('ssl://fleep.io:443', $this->handler->getConnectionString());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/FlowdockHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\FlowdockFormatter;\nuse Monolog\\Level;\n\n/**\n * @author Dominik Liebler <liebler.dominik@gmail.com>\n * @see    https://www.hipchat.com/docs/api\n */\nclass FlowdockHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @var resource\n     */\n    private $res;\n\n    private FlowdockHandler $handler;\n\n    public function setUp(): void\n    {\n        if (!\\extension_loaded('openssl')) {\n            $this->markTestSkipped('This test requires openssl to run');\n        }\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->res);\n        unset($this->handler);\n    }\n\n    public function testWriteHeader()\n    {\n        $this->createHandler();\n        $this->handler->handle($this->getRecord(Level::Critical, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/POST \\/v1\\/messages\\/team_inbox\\/.* HTTP\\/1.1\\\\r\\\\nHost: api.flowdock.com\\\\r\\\\nContent-Type: application\\/json\\\\r\\\\nContent-Length: \\d{2,4}\\\\r\\\\n\\\\r\\\\n/', $content);\n\n        return $content;\n    }\n\n    /**\n     * @depends testWriteHeader\n     */\n    public function testWriteContent($content)\n    {\n        $this->assertMatchesRegularExpression('/\"source\":\"test_source\"/', $content);\n        $this->assertMatchesRegularExpression('/\"from_address\":\"source@test\\.com\"/', $content);\n    }\n\n    private function createHandler($token = 'myToken')\n    {\n        $constructorArgs = [$token, Level::Debug];\n        $this->res = fopen('php://memory', 'a');\n        $this->handler = $this->getMockBuilder('Monolog\\Handler\\FlowdockHandler')\n            ->setConstructorArgs($constructorArgs)\n            ->onlyMethods(['fsockopen', 'streamSetTimeout', 'closeSocket'])\n            ->getMock();\n\n        $reflectionProperty = new \\ReflectionProperty('Monolog\\Handler\\SocketHandler', 'connectionString');\n        $reflectionProperty->setValue($this->handler, 'localhost:1234');\n\n        $this->handler->expects($this->any())\n            ->method('fsockopen')\n            ->willReturn($this->res);\n        $this->handler->expects($this->any())\n            ->method('streamSetTimeout')\n            ->willReturn(true);\n        $this->handler->expects($this->any())\n            ->method('closeSocket');\n\n        $this->handler->setFormatter(new FlowdockFormatter('test_source', 'source@test.com'));\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/GelfHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Gelf\\Message;\nuse Monolog\\Level;\nuse Monolog\\Formatter\\GelfMessageFormatter;\n\nclass GelfHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function setUp(): void\n    {\n        if (!class_exists('Gelf\\Publisher') || !class_exists('Gelf\\Message')) {\n            $this->markTestSkipped(\"graylog2/gelf-php not installed\");\n        }\n    }\n\n    /**\n     * @covers Monolog\\Handler\\GelfHandler::__construct\n     */\n    public function testConstruct()\n    {\n        $handler = new GelfHandler($this->getMessagePublisher());\n        $this->assertInstanceOf('Monolog\\Handler\\GelfHandler', $handler);\n    }\n\n    protected function getHandler($messagePublisher)\n    {\n        $handler = new GelfHandler($messagePublisher);\n\n        return $handler;\n    }\n\n    protected function getMessagePublisher()\n    {\n        return $this->getMockBuilder('Gelf\\Publisher')\n            ->onlyMethods(['publish'])\n            ->disableOriginalConstructor()\n            ->getMock();\n    }\n\n    public function testDebug()\n    {\n        $record = $this->getRecord(Level::Debug, \"A test debug message\");\n        $expectedMessage = new Message();\n        $expectedMessage\n            ->setLevel(7)\n            ->setAdditional('facility', 'test')\n            ->setShortMessage($record->message)\n            ->setTimestamp($record->datetime)\n        ;\n\n        $messagePublisher = $this->getMessagePublisher();\n        $messagePublisher->expects($this->once())\n            ->method('publish')\n            ->with($expectedMessage);\n\n        $handler = $this->getHandler($messagePublisher);\n\n        $handler->handle($record);\n    }\n\n    public function testWarning()\n    {\n        $record = $this->getRecord(Level::Warning, \"A test warning message\");\n        $expectedMessage = new Message();\n        $expectedMessage\n            ->setLevel(4)\n            ->setAdditional('facility', 'test')\n            ->setShortMessage($record->message)\n            ->setTimestamp($record->datetime)\n        ;\n\n        $messagePublisher = $this->getMessagePublisher();\n        $messagePublisher->expects($this->once())\n            ->method('publish')\n            ->with($expectedMessage);\n\n        $handler = $this->getHandler($messagePublisher);\n\n        $handler->handle($record);\n    }\n\n    public function testInjectedGelfMessageFormatter()\n    {\n        $record = $this->getRecord(\n            Level::Warning,\n            \"A test warning message\",\n            extra: ['blarg' => 'yep'],\n            context: ['from' => 'logger'],\n        );\n\n        $expectedMessage = new Message();\n        $expectedMessage\n            ->setLevel(4)\n            ->setAdditional('facility', 'test')\n            ->setHost(\"mysystem\")\n            ->setShortMessage($record->message)\n            ->setTimestamp($record->datetime)\n            ->setAdditional(\"EXTblarg\", 'yep')\n            ->setAdditional(\"CTXfrom\", 'logger')\n        ;\n\n        $messagePublisher = $this->getMessagePublisher();\n        $messagePublisher->expects($this->once())\n            ->method('publish')\n            ->with($expectedMessage);\n\n        $handler = $this->getHandler($messagePublisher);\n        $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX'));\n        $handler->handle($record);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/GroupHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\LogRecord;\nuse Monolog\\Level;\n\nclass GroupHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Handler\\GroupHandler::__construct\n     */\n    public function testConstructorOnlyTakesHandler()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        new GroupHandler([new TestHandler(), \"foo\"]);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\GroupHandler::__construct\n     * @covers Monolog\\Handler\\GroupHandler::handle\n     */\n    public function testHandle()\n    {\n        $testHandlers = [new TestHandler(), new TestHandler()];\n        $handler = new GroupHandler($testHandlers);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        foreach ($testHandlers as $test) {\n            $this->assertTrue($test->hasDebugRecords());\n            $this->assertTrue($test->hasInfoRecords());\n            $this->assertCount(2, $test->getRecords());\n        }\n    }\n\n    /**\n     * @covers Monolog\\Handler\\GroupHandler::handleBatch\n     */\n    public function testHandleBatch()\n    {\n        $testHandlers = [new TestHandler(), new TestHandler()];\n        $handler = new GroupHandler($testHandlers);\n        $handler->handleBatch([$this->getRecord(Level::Debug), $this->getRecord(Level::Info)]);\n        foreach ($testHandlers as $test) {\n            $this->assertTrue($test->hasDebugRecords());\n            $this->assertTrue($test->hasInfoRecords());\n            $this->assertCount(2, $test->getRecords());\n        }\n    }\n\n    /**\n     * @covers Monolog\\Handler\\GroupHandler::isHandling\n     */\n    public function testIsHandling()\n    {\n        $testHandlers = [new TestHandler(Level::Error), new TestHandler(Level::Warning)];\n        $handler = new GroupHandler($testHandlers);\n        $this->assertTrue($handler->isHandling($this->getRecord(Level::Error)));\n        $this->assertTrue($handler->isHandling($this->getRecord(Level::Warning)));\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Debug)));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\GroupHandler::handle\n     */\n    public function testHandleUsesProcessors()\n    {\n        $test = new TestHandler();\n        $handler = new GroupHandler([$test]);\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo'] = true;\n\n            return $record;\n        });\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertTrue($test->hasWarningRecords());\n        $records = $test->getRecords();\n        $this->assertTrue($records[0]['extra']['foo']);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\GroupHandler::handle\n     */\n    public function testHandleBatchUsesProcessors()\n    {\n        $testHandlers = [new TestHandler(), new TestHandler()];\n        $handler = new GroupHandler($testHandlers);\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo'] = true;\n\n            return $record;\n        });\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo2'] = true;\n\n            return $record;\n        });\n        $handler->handleBatch([$this->getRecord(Level::Debug), $this->getRecord(Level::Info)]);\n        foreach ($testHandlers as $test) {\n            $this->assertTrue($test->hasDebugRecords());\n            $this->assertTrue($test->hasInfoRecords());\n            $this->assertCount(2, $test->getRecords());\n            $records = $test->getRecords();\n            $this->assertTrue($records[0]['extra']['foo']);\n            $this->assertTrue($records[1]['extra']['foo']);\n            $this->assertTrue($records[0]['extra']['foo2']);\n            $this->assertTrue($records[1]['extra']['foo2']);\n        }\n    }\n\n    public function testProcessorsDoNotInterfereBetweenHandlers()\n    {\n        $t1 = new TestHandler();\n        $t2 = new TestHandler();\n        $handler = new GroupHandler([$t1, $t2]);\n\n        $t1->pushProcessor(function (LogRecord $record) {\n            $record->extra['foo'] = 'bar';\n\n            return $record;\n        });\n        $handler->handle($this->getRecord());\n\n        self::assertSame([], $t2->getRecords()[0]->extra);\n    }\n\n    public function testProcessorsDoNotInterfereBetweenHandlersWithBatch()\n    {\n        $t1 = new TestHandler();\n        $t2 = new TestHandler();\n        $handler = new GroupHandler([$t1, $t2]);\n\n        $t1->pushProcessor(function (LogRecord $record) {\n            $record->extra['foo'] = 'bar';\n\n            return $record;\n        });\n\n        $handler->handleBatch([$this->getRecord()]);\n\n        self::assertSame([], $t2->getRecords()[0]->extra);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/HandlerWrapperTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\n\n/**\n * @author Alexey Karapetov <alexey@karapetov.com>\n */\nclass HandlerWrapperTest extends \\Monolog\\Test\\MonologTestCase\n{\n    private HandlerWrapper $wrapper;\n\n    private HandlerInterface&MockObject $handler;\n\n    public function setUp(): void\n    {\n        parent::setUp();\n        $this->handler = $this->createMock(HandlerInterface::class);\n        $this->wrapper = new HandlerWrapper($this->handler);\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->wrapper);\n        unset($this->handler);\n    }\n\n    public static function trueFalseDataProvider(): array\n    {\n        return [\n            [true],\n            [false],\n        ];\n    }\n\n    #[DataProvider('trueFalseDataProvider')]\n    public function testIsHandling(bool $result)\n    {\n        $record = $this->getRecord();\n        $this->handler->expects($this->once())\n            ->method('isHandling')\n            ->with($record)\n            ->willReturn($result);\n\n        $this->assertEquals($result, $this->wrapper->isHandling($record));\n    }\n\n    #[DataProvider('trueFalseDataProvider')]\n    public function testHandle(bool $result)\n    {\n        $record = $this->getRecord();\n        $this->handler->expects($this->once())\n            ->method('handle')\n            ->with($record)\n            ->willReturn($result);\n\n        $this->assertEquals($result, $this->wrapper->handle($record));\n    }\n\n    #[DataProvider('trueFalseDataProvider')]\n    public function testHandleBatch(bool $result)\n    {\n        $records = $this->getMultipleRecords();\n        $this->handler->expects($this->once())\n            ->method('handleBatch')\n            ->with($records);\n\n        $this->wrapper->handleBatch($records);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/InsightOpsHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\n\n/**\n * @author Robert Kaufmann III <rok3@rok3.me>\n * @author Gabriel Machado <gabriel.ms1@hotmail.com>\n */\nclass InsightOpsHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @var resource\n     */\n    private $resource;\n\n    private InsightOpsHandler&MockObject $handler;\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->resource);\n        unset($this->handler);\n    }\n\n    public function testWriteContent()\n    {\n        $this->createHandler();\n        $this->handler->handle($this->getRecord(Level::Critical, 'Critical write test'));\n\n        fseek($this->resource, 0);\n        $content = fread($this->resource, 1024);\n\n        $this->assertMatchesRegularExpression('/testToken \\[\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{6}\\+00:00\\] test.CRITICAL: Critical write test/', $content);\n    }\n\n    public function testWriteBatchContent()\n    {\n        $this->createHandler();\n        $this->handler->handleBatch($this->getMultipleRecords());\n\n        fseek($this->resource, 0);\n        $content = fread($this->resource, 1024);\n\n        $this->assertMatchesRegularExpression('/(testToken \\[\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{6}\\+00:00\\] .* \\[\\] \\[\\]\\n){3}/', $content);\n    }\n\n    private function createHandler()\n    {\n        $useSSL = \\extension_loaded('openssl');\n        $args = ['testToken', 'us', $useSSL, Level::Debug, true];\n        $this->resource = fopen('php://memory', 'a');\n        $this->handler = $this->getMockBuilder(InsightOpsHandler::class)\n            ->onlyMethods(['fsockopen', 'streamSetTimeout', 'closeSocket'])\n            ->setConstructorArgs($args)\n            ->getMock();\n\n        $reflectionProperty = new \\ReflectionProperty('\\Monolog\\Handler\\SocketHandler', 'connectionString');\n        $reflectionProperty->setValue($this->handler, 'localhost:1234');\n\n        $this->handler->expects($this->any())\n            ->method('fsockopen')\n            ->willReturn($this->resource);\n        $this->handler->expects($this->any())\n            ->method('streamSetTimeout')\n            ->willReturn(true);\n        $this->handler->expects($this->any())\n            ->method('closeSocket');\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/LogEntriesHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\n\n/**\n * @author Robert Kaufmann III <rok3@rok3.me>\n */\nclass LogEntriesHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @var resource\n     */\n    private $res;\n\n    private LogEntriesHandler&MockObject $handler;\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->res);\n        unset($this->handler);\n    }\n\n    public function testWriteContent()\n    {\n        $this->createHandler();\n        $this->handler->handle($this->getRecord(Level::Critical, 'Critical write test'));\n\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/testToken \\[\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{6}\\+00:00\\] test.CRITICAL: Critical write test/', $content);\n    }\n\n    public function testWriteBatchContent()\n    {\n        $records = [\n            $this->getRecord(),\n            $this->getRecord(),\n            $this->getRecord(),\n        ];\n        $this->createHandler();\n        $this->handler->handleBatch($records);\n\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/(testToken \\[\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{6}\\+00:00\\] .* \\[\\] \\[\\]\\n){3}/', $content);\n    }\n\n    private function createHandler()\n    {\n        $useSSL = \\extension_loaded('openssl');\n        $args = ['testToken', $useSSL, Level::Debug, true];\n        $this->res = fopen('php://memory', 'a');\n        $this->handler = $this->getMockBuilder('Monolog\\Handler\\LogEntriesHandler')\n            ->setConstructorArgs($args)\n            ->onlyMethods(['fsockopen', 'streamSetTimeout', 'closeSocket'])\n            ->getMock();\n\n        $reflectionProperty = new \\ReflectionProperty('Monolog\\Handler\\SocketHandler', 'connectionString');\n        $reflectionProperty->setValue($this->handler, 'localhost:1234');\n\n        $this->handler->expects($this->any())\n            ->method('fsockopen')\n            ->willReturn($this->res);\n        $this->handler->expects($this->any())\n            ->method('streamSetTimeout')\n            ->willReturn(true);\n        $this->handler->expects($this->any())\n            ->method('closeSocket');\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/LogmaticHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\n\n/**\n * @author Julien Breux <julien.breux@gmail.com>\n */\nclass LogmaticHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @var resource\n     */\n    private $res;\n\n    private LogmaticHandler&MockObject $handler;\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->res);\n        unset($this->handler);\n    }\n\n    public function testWriteContent()\n    {\n        $this->createHandler();\n        $this->handler->handle($this->getRecord(Level::Critical, 'Critical write test'));\n\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/testToken {\"message\":\"Critical write test\",\"context\":{},\"level\":500,\"level_name\":\"CRITICAL\",\"channel\":\"test\",\"datetime\":\"(.*)\",\"extra\":{},\"hostname\":\"testHostname\",\"appname\":\"testAppname\",\"@marker\":\\[\"sourcecode\",\"php\"\\]}/', $content);\n    }\n\n    public function testWriteBatchContent()\n    {\n        $records = [\n            $this->getRecord(),\n            $this->getRecord(),\n            $this->getRecord(),\n        ];\n        $this->createHandler();\n        $this->handler->handleBatch($records);\n\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/testToken {\"message\":\"test\",\"context\":{},\"level\":300,\"level_name\":\"WARNING\",\"channel\":\"test\",\"datetime\":\"(.*)\",\"extra\":{},\"hostname\":\"testHostname\",\"appname\":\"testAppname\",\"@marker\":\\[\"sourcecode\",\"php\"\\]}/', $content);\n    }\n\n    private function createHandler()\n    {\n        $useSSL = \\extension_loaded('openssl');\n        $args = ['testToken', 'testHostname', 'testAppname', $useSSL, Level::Debug, true];\n        $this->res = fopen('php://memory', 'a');\n        $this->handler = $this->getMockBuilder('Monolog\\Handler\\LogmaticHandler')\n            ->setConstructorArgs($args)\n            ->onlyMethods(['fsockopen', 'streamSetTimeout', 'closeSocket'])\n            ->getMock();\n\n        $reflectionProperty = new \\ReflectionProperty('Monolog\\Handler\\SocketHandler', 'connectionString');\n        $reflectionProperty->setValue($this->handler, 'localhost:1234');\n\n        $this->handler->expects($this->any())\n            ->method('fsockopen')\n            ->willReturn($this->res);\n        $this->handler->expects($this->any())\n            ->method('streamSetTimeout')\n            ->willReturn(true);\n        $this->handler->expects($this->any())\n            ->method('closeSocket');\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/MailHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\nclass MailHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Handler\\MailHandler::handleBatch\n     */\n    public function testHandleBatch()\n    {\n        $formatter = $this->createMock('Monolog\\Formatter\\FormatterInterface');\n        $formatter->expects($this->once())\n            ->method('formatBatch'); // Each record is formatted\n\n        $handler = $this->createPartialMock('Monolog\\Handler\\MailHandler', ['send', 'write']);\n        $handler->expects($this->once())\n            ->method('send');\n        $handler->expects($this->never())\n            ->method('write'); // write is for individual records\n\n        $handler->setFormatter($formatter);\n\n        $handler->handleBatch($this->getMultipleRecords());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\MailHandler::handleBatch\n     */\n    public function testHandleBatchNotSendsMailIfMessagesAreBelowLevel()\n    {\n        $records = [\n            $this->getRecord(Level::Debug, 'debug message 1'),\n            $this->getRecord(Level::Debug, 'debug message 2'),\n            $this->getRecord(Level::Info, 'information'),\n        ];\n\n        $handler = $this->createPartialMock('Monolog\\Handler\\MailHandler', ['send']);\n        $handler->expects($this->never())\n            ->method('send');\n        $handler->setLevel(Level::Error);\n\n        $handler->handleBatch($records);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\MailHandler::write\n     */\n    public function testHandle()\n    {\n        $handler = $this->createPartialMock('Monolog\\Handler\\MailHandler', ['send']);\n        $handler->setFormatter(new \\Monolog\\Formatter\\LineFormatter);\n\n        $record = $this->getRecord(message: 'test handle');\n        $record->formatted = '['.$record->datetime.'] test.WARNING: test handle [] []'.\"\\n\";\n\n        $handler->expects($this->once())\n            ->method('send')\n            ->with($record->formatted, [$record]);\n\n        $handler->handle($record);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/MongoDBHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse MongoDB\\BSON\\UTCDateTime;\nuse MongoDB\\Client;\nuse MongoDB\\Collection;\nuse MongoDB\\Driver\\Manager;\nuse PHPUnit\\Framework\\Attributes\\RequiresPhpExtension;\n\n#[RequiresPhpExtension('mongodb')]\nclass MongoDBHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testConstructorShouldThrowExceptionForInvalidMongo()\n    {\n        $this->expectException(\\TypeError::class);\n\n        new MongoDBHandler(new \\stdClass, 'db', 'collection');\n    }\n\n    public function testHandleWithLibraryClient()\n    {\n        if (!class_exists(Client::class)) {\n            $this->markTestSkipped('mongodb/mongodb not installed');\n        }\n\n        $client = $this->getMockBuilder(Client::class)\n            ->disableOriginalConstructor()\n            ->getMock();\n\n        $collection = $this->getMockBuilder(Collection::class)\n            ->disableOriginalConstructor()\n            ->getMock();\n\n        $client->expects($this->once())\n            ->method('getCollection')\n            ->with('db', 'collection')\n            ->willReturn($collection);\n\n        $record = $this->getRecord();\n        $expected = $record->toArray();\n        $expected['datetime'] = new UTCDateTime((int) floor(((float) $record->datetime->format('U.u')) * 1000));\n\n        $collection->expects($this->once())\n            ->method('insertOne')\n            ->with($expected);\n\n        $handler = new MongoDBHandler($client, 'db', 'collection');\n        $handler->handle($record);\n    }\n\n    public function testHandleWithDriverManager()\n    {\n        $manager = new Manager('mongodb://localhost:27017');\n        $handler = new MongoDBHandler($manager, 'test', 'monolog');\n        $record = $this->getRecord();\n\n        try {\n            $handler->handle($record);\n        } catch (\\RuntimeException $e) {\n            $this->markTestSkipped('Could not connect to MongoDB server on mongodb://localhost:27017');\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/NativeMailerHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\nfunction mail($to, $subject, $message, $additional_headers = null, $additional_parameters = null)\n{\n    $GLOBALS['mail'][] = \\func_get_args();\n}\n\nclass NativeMailerHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    protected function setUp(): void\n    {\n        $GLOBALS['mail'] = [];\n    }\n\n    protected function newNativeMailerHandler(... $args) : NativeMailerHandler\n    {\n        return new class(... $args) extends NativeMailerHandler {\n            public $mail = [];\n\n            protected function mail(\n                string $to,\n                string $subject,\n                string $content,\n                string $headers,\n                string $parameters\n            ) : void {\n                $this->mail[] = \\func_get_args();\n            }\n        };\n    }\n\n    public function testConstructorHeaderInjection()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', \"receiver@example.org\\r\\nFrom: faked@attacker.org\");\n    }\n\n    public function testSetterHeaderInjection()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');\n        $mailer->addHeader(\"Content-Type: text/html\\r\\nFrom: faked@attacker.org\");\n    }\n\n    public function testSetterArrayHeaderInjection()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');\n        $mailer->addHeader([\"Content-Type: text/html\\r\\nFrom: faked@attacker.org\"]);\n    }\n\n    public function testSetterContentTypeInjection()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');\n        $mailer->setContentType(\"text/html\\r\\nFrom: faked@attacker.org\");\n    }\n\n    public function testSetterEncodingInjection()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');\n        $mailer->setEncoding(\"utf-8\\r\\nFrom: faked@attacker.org\");\n    }\n\n    public function testSend()\n    {\n        $to = 'spammer@example.org';\n        $subject = 'dear victim';\n        $from = 'receiver@example.org';\n\n        $mailer = $this->newNativeMailerHandler($to, $subject, $from);\n        $mailer->setFormatter(new \\Monolog\\Formatter\\LineFormatter);\n        $mailer->handleBatch([]);\n\n        // batch is empty, nothing sent\n        $this->assertEmpty($mailer->mail);\n\n        // non-empty batch\n        $mailer->handle($this->getRecord(Level::Error, \"Foo\\nBar\\r\\n\\r\\nBaz\"));\n        $this->assertNotEmpty($mailer->mail);\n        $this->assertIsArray($mailer->mail);\n        $this->assertArrayHasKey('0', $mailer->mail);\n        $params = $mailer->mail[0];\n        $this->assertCount(5, $params);\n        $this->assertSame($to, $params[0]);\n        $this->assertSame($subject, $params[1]);\n        $this->assertStringEndsWith(\" test.ERROR: Foo Bar  Baz [] []\\n\", $params[2]);\n        $this->assertSame(\"From: $from\\r\\nContent-type: text/plain; charset=utf-8\\r\\n\", $params[3]);\n        $this->assertSame('', $params[4]);\n    }\n\n    public function testMessageSubjectFormatting()\n    {\n        $mailer = $this->newNativeMailerHandler('to@example.org', 'Alert: %level_name% %message%', 'from@example.org');\n        $mailer->handle($this->getRecord(Level::Error, \"Foo\\nBar\\r\\n\\r\\nBaz\"));\n        $this->assertNotEmpty($mailer->mail);\n        $this->assertIsArray($mailer->mail);\n        $this->assertArrayHasKey('0', $mailer->mail);\n        $params = $mailer->mail[0];\n        $this->assertCount(5, $params);\n        $this->assertSame('Alert: ERROR Foo Bar  Baz', $params[1]);\n    }\n\n    public function testMail()\n    {\n        $mailer = new NativeMailerHandler('to@example.org', 'subject', 'from@example.org');\n        $mailer->addParameter('foo');\n        $mailer->handle($this->getRecord(Level::Error, \"FooBarBaz\"));\n        $this->assertNotEmpty($GLOBALS['mail']);\n        $this->assertIsArray($GLOBALS['mail']);\n        $this->assertArrayHasKey('0', $GLOBALS['mail']);\n        $params = $GLOBALS['mail'][0];\n        $this->assertCount(5, $params);\n        $this->assertSame('to@example.org', $params[0]);\n        $this->assertSame('subject', $params[1]);\n        $this->assertStringContainsString(\"FooBarBaz\", $params[2]);\n        $this->assertStringContainsString('From: from@example.org', $params[3]);\n        $this->assertSame('foo', $params[4]);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/NewRelicHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Level;\n\nclass NewRelicHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public static $appname;\n    public static $customParameters;\n    public static $transactionName;\n\n    public function setUp(): void\n    {\n        self::$appname = null;\n        self::$customParameters = [];\n        self::$transactionName = null;\n    }\n\n    public function testThehandlerThrowsAnExceptionIfTheNRExtensionIsNotLoaded()\n    {\n        $handler = new StubNewRelicHandlerWithoutExtension();\n\n        $this->expectException(MissingExtensionException::class);\n\n        $handler->handle($this->getRecord(Level::Error));\n    }\n\n    public function testThehandlerCanHandleTheRecord()\n    {\n        $handler = new StubNewRelicHandler();\n        $handler->handle($this->getRecord(Level::Error));\n    }\n\n    public function testThehandlerCanAddContextParamsToTheNewRelicTrace()\n    {\n        $handler = new StubNewRelicHandler();\n        $handler->handle($this->getRecord(Level::Error, 'log message', ['a' => 'b']));\n        $this->assertEquals(['context_a' => 'b'], self::$customParameters);\n    }\n\n    public function testThehandlerCanAddExplodedContextParamsToTheNewRelicTrace()\n    {\n        $handler = new StubNewRelicHandler(Level::Error, true, self::$appname, true);\n        $handler->handle($this->getRecord(\n            Level::Error,\n            'log message',\n            ['a' => ['key1' => 'value1', 'key2' => 'value2']]\n        ));\n        $this->assertEquals(\n            ['context_a_key1' => 'value1', 'context_a_key2' => 'value2'],\n            self::$customParameters\n        );\n    }\n\n    public function testThehandlerCanAddExtraParamsToTheNewRelicTrace()\n    {\n        $record = $this->getRecord(Level::Error, 'log message');\n        $record->extra = ['c' => 'd'];\n\n        $handler = new StubNewRelicHandler();\n        $handler->handle($record);\n\n        $this->assertEquals(['extra_c' => 'd'], self::$customParameters);\n    }\n\n    public function testThehandlerCanAddExplodedExtraParamsToTheNewRelicTrace()\n    {\n        $record = $this->getRecord(Level::Error, 'log message');\n        $record->extra = ['c' => ['key1' => 'value1', 'key2' => 'value2']];\n\n        $handler = new StubNewRelicHandler(Level::Error, true, self::$appname, true);\n        $handler->handle($record);\n\n        $this->assertEquals(\n            ['extra_c_key1' => 'value1', 'extra_c_key2' => 'value2'],\n            self::$customParameters\n        );\n    }\n\n    public function testThehandlerCanAddExtraContextAndParamsToTheNewRelicTrace()\n    {\n        $record = $this->getRecord(Level::Error, 'log message', ['a' => 'b']);\n        $record->extra = ['c' => 'd'];\n\n        $handler = new StubNewRelicHandler();\n        $handler->handle($record);\n\n        $expected = [\n            'context_a' => 'b',\n            'extra_c' => 'd',\n        ];\n\n        $this->assertEquals($expected, self::$customParameters);\n    }\n\n    public function testThehandlerCanHandleTheRecordsFormattedUsingTheLineFormatter()\n    {\n        $handler = new StubNewRelicHandler();\n        $handler->setFormatter(new LineFormatter());\n        $handler->handle($this->getRecord(Level::Error));\n    }\n\n    public function testTheAppNameIsNullByDefault()\n    {\n        $handler = new StubNewRelicHandler();\n        $handler->handle($this->getRecord(Level::Error, 'log message'));\n\n        $this->assertEquals(null, self::$appname);\n    }\n\n    public function testTheAppNameCanBeInjectedFromtheConstructor()\n    {\n        $handler = new StubNewRelicHandler(Level::Debug, false, 'myAppName');\n        $handler->handle($this->getRecord(Level::Error, 'log message'));\n\n        $this->assertEquals('myAppName', self::$appname);\n    }\n\n    public function testTheAppNameCanBeOverriddenFromEachLog()\n    {\n        $handler = new StubNewRelicHandler(Level::Debug, false, 'myAppName');\n        $handler->handle($this->getRecord(Level::Error, 'log message', ['appname' => 'logAppName']));\n\n        $this->assertEquals('logAppName', self::$appname);\n    }\n\n    public function testTheTransactionNameIsNullByDefault()\n    {\n        $handler = new StubNewRelicHandler();\n        $handler->handle($this->getRecord(Level::Error, 'log message'));\n\n        $this->assertEquals(null, self::$transactionName);\n    }\n\n    public function testTheTransactionNameCanBeInjectedFromTheConstructor()\n    {\n        $handler = new StubNewRelicHandler(Level::Debug, false, null, false, 'myTransaction');\n        $handler->handle($this->getRecord(Level::Error, 'log message'));\n\n        $this->assertEquals('myTransaction', self::$transactionName);\n    }\n\n    public function testTheTransactionNameCanBeOverriddenFromEachLog()\n    {\n        $handler = new StubNewRelicHandler(Level::Debug, false, null, false, 'myTransaction');\n        $handler->handle($this->getRecord(Level::Error, 'log message', ['transaction_name' => 'logTransactName']));\n\n        $this->assertEquals('logTransactName', self::$transactionName);\n    }\n}\n\nclass StubNewRelicHandlerWithoutExtension extends NewRelicHandler\n{\n    protected function isNewRelicEnabled(): bool\n    {\n        return false;\n    }\n}\n\nclass StubNewRelicHandler extends NewRelicHandler\n{\n    protected function isNewRelicEnabled(): bool\n    {\n        return true;\n    }\n}\n\nfunction newrelic_notice_error()\n{\n    return true;\n}\n\nfunction newrelic_set_appname($appname)\n{\n    return NewRelicHandlerTest::$appname = $appname;\n}\n\nfunction newrelic_name_transaction($transactionName)\n{\n    return NewRelicHandlerTest::$transactionName = $transactionName;\n}\n\nfunction newrelic_add_custom_parameter($key, $value)\n{\n    NewRelicHandlerTest::$customParameters[$key] = $value;\n\n    return true;\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/NoopHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\n/**\n * @covers Monolog\\Handler\\NoopHandler::handle\n */\nclass NoopHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    #[DataProvider('logLevelsProvider')]\n    public function testIsHandling(Level $level)\n    {\n        $handler = new NoopHandler();\n        $this->assertTrue($handler->isHandling($this->getRecord($level)));\n    }\n\n    #[DataProvider('logLevelsProvider')]\n    public function testHandle(Level $level)\n    {\n        $handler = new NoopHandler();\n        $this->assertFalse($handler->handle($this->getRecord($level)));\n    }\n\n    public static function logLevelsProvider()\n    {\n        return array_map(\n            fn ($level) => [$level],\n            Level::cases()\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/NullHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\n/**\n * @covers Monolog\\Handler\\NullHandler::handle\n */\nclass NullHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testHandle()\n    {\n        $handler = new NullHandler();\n        $this->assertTrue($handler->handle($this->getRecord()));\n    }\n\n    public function testHandleLowerLevelRecord()\n    {\n        $handler = new NullHandler(Level::Warning);\n        $this->assertFalse($handler->handle($this->getRecord(Level::Debug)));\n    }\n\n    public function testSerializeRestorePrivate()\n    {\n        $handler = new NullHandler(Level::Warning);\n        self::assertFalse($handler->handle($this->getRecord(Level::Debug)));\n        self::assertTrue($handler->handle($this->getRecord(Level::Warning)));\n\n        $handler = unserialize(serialize($handler));\n        self::assertFalse($handler->handle($this->getRecord(Level::Debug)));\n        self::assertTrue($handler->handle($this->getRecord(Level::Warning)));\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/OverflowHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\n/**\n * @author Kris Buist <krisbuist@gmail.com>\n * @covers \\Monolog\\Handler\\OverflowHandler\n */\nclass OverflowHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testNotPassingRecordsBeneathLogLevel()\n    {\n        $testHandler = new TestHandler();\n        $handler = new OverflowHandler($testHandler, [], Level::Info);\n        $handler->handle($this->getRecord(Level::Debug));\n        $this->assertFalse($testHandler->hasDebugRecords());\n    }\n\n    public function testPassThroughWithoutThreshold()\n    {\n        $testHandler = new TestHandler();\n        $handler = new OverflowHandler($testHandler, [], Level::Info);\n\n        $handler->handle($this->getRecord(Level::Info, 'Info 1'));\n        $handler->handle($this->getRecord(Level::Info, 'Info 2'));\n        $handler->handle($this->getRecord(Level::Warning, 'Warning 1'));\n\n        $this->assertTrue($testHandler->hasInfoThatContains('Info 1'));\n        $this->assertTrue($testHandler->hasInfoThatContains('Info 2'));\n        $this->assertTrue($testHandler->hasWarningThatContains('Warning 1'));\n    }\n\n    /**\n     * @test\n     */\n    public function testHoldingMessagesBeneathThreshold()\n    {\n        $testHandler = new TestHandler();\n        $handler = new OverflowHandler($testHandler, [Level::Info->value => 3]);\n\n        $handler->handle($this->getRecord(Level::Debug, 'debug 1'));\n        $handler->handle($this->getRecord(Level::Debug, 'debug 2'));\n\n        foreach (range(1, 3) as $i) {\n            $handler->handle($this->getRecord(Level::Info, 'info ' . $i));\n        }\n\n        $this->assertTrue($testHandler->hasDebugThatContains('debug 1'));\n        $this->assertTrue($testHandler->hasDebugThatContains('debug 2'));\n        $this->assertFalse($testHandler->hasInfoRecords());\n\n        $handler->handle($this->getRecord(Level::Info, 'info 4'));\n\n        foreach (range(1, 4) as $i) {\n            $this->assertTrue($testHandler->hasInfoThatContains('info ' . $i));\n        }\n    }\n\n    /**\n     * @test\n     */\n    public function testCombinedThresholds()\n    {\n        $testHandler = new TestHandler();\n        $handler = new OverflowHandler($testHandler, [Level::Info->value => 5, Level::Warning->value => 10]);\n\n        $handler->handle($this->getRecord(Level::Debug));\n\n        foreach (range(1, 5) as $i) {\n            $handler->handle($this->getRecord(Level::Info, 'info ' . $i));\n        }\n\n        foreach (range(1, 10) as $i) {\n            $handler->handle($this->getRecord(Level::Warning, 'warning ' . $i));\n        }\n\n        // Only 1 DEBUG records\n        $this->assertCount(1, $testHandler->getRecords());\n\n        $handler->handle($this->getRecord(Level::Info, 'info final'));\n\n        // 1 DEBUG + 5 buffered INFO + 1 new INFO\n        $this->assertCount(7, $testHandler->getRecords());\n\n        $handler->handle($this->getRecord(Level::Warning, 'warning final'));\n\n        // 1 DEBUG + 6 INFO + 10 buffered WARNING + 1 new WARNING\n        $this->assertCount(18, $testHandler->getRecords());\n\n        $handler->handle($this->getRecord(Level::Info, 'Another info'));\n        $handler->handle($this->getRecord(Level::Warning, 'Anther warning'));\n\n        // 1 DEBUG + 6 INFO + 11 WARNING + 1 new INFO + 1 new WARNING\n        $this->assertCount(20, $testHandler->getRecords());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/PHPConsoleHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Exception;\nuse Monolog\\ErrorHandler;\nuse Monolog\\Level;\nuse Monolog\\Logger;\nuse PhpConsole\\Connector;\nuse PhpConsole\\Dispatcher\\Debug as DebugDispatcher;\nuse PhpConsole\\Dispatcher\\Errors as ErrorDispatcher;\nuse PhpConsole\\Handler as VendorPhpConsoleHandler;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\Attributes\\WithoutErrorHandler;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\n\n/**\n * @covers Monolog\\Handler\\PHPConsoleHandler\n * @author Sergey Barbushin https://www.linkedin.com/in/barbushin\n */\nclass PHPConsoleHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    protected Connector&MockObject $connector;\n    protected DebugDispatcher&MockObject $debugDispatcher;\n    protected ErrorDispatcher&MockObject $errorDispatcher;\n\n    protected function setUp(): void\n    {\n        // suppress warnings until https://github.com/barbushin/php-console/pull/173 is merged\n        $previous = error_reporting(0);\n        if (!class_exists('PhpConsole\\Connector')) {\n            error_reporting($previous);\n            $this->markTestSkipped('PHP Console library not found. See https://github.com/barbushin/php-console#installation');\n        }\n        if (!class_exists('PhpConsole\\Handler')) {\n            error_reporting($previous);\n            $this->markTestSkipped('PHP Console library not found. See https://github.com/barbushin/php-console#installation');\n        }\n        error_reporting($previous);\n        $this->connector = $this->initConnectorMock();\n\n        $this->debugDispatcher = $this->initDebugDispatcherMock($this->connector);\n        $this->connector->setDebugDispatcher($this->debugDispatcher);\n\n        $this->errorDispatcher = $this->initErrorDispatcherMock($this->connector);\n        $this->connector->setErrorsDispatcher($this->errorDispatcher);\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->connector, $this->debugDispatcher, $this->errorDispatcher);\n    }\n\n    protected function initDebugDispatcherMock(Connector $connector)\n    {\n        return $this->getMockBuilder('PhpConsole\\Dispatcher\\Debug')\n            ->disableOriginalConstructor()\n            ->onlyMethods(['dispatchDebug'])\n            ->setConstructorArgs([$connector, $connector->getDumper()])\n            ->getMock();\n    }\n\n    protected function initErrorDispatcherMock(Connector $connector)\n    {\n        return $this->getMockBuilder('PhpConsole\\Dispatcher\\Errors')\n            ->disableOriginalConstructor()\n            ->onlyMethods(['dispatchError', 'dispatchException'])\n            ->setConstructorArgs([$connector, $connector->getDumper()])\n            ->getMock();\n    }\n\n    protected function initConnectorMock()\n    {\n        $connector = $this->getMockBuilder('PhpConsole\\Connector')\n            ->disableOriginalConstructor()\n            ->onlyMethods([\n                'sendMessage',\n                'onShutDown',\n                'isActiveClient',\n                'setSourcesBasePath',\n                'setServerEncoding',\n                'setPassword',\n                'enableSslOnlyMode',\n                'setAllowedIpMasks',\n                'setHeadersLimit',\n                'startEvalRequestsListener',\n            ])\n            ->getMock();\n\n        $connector->expects($this->any())\n            ->method('isActiveClient')\n            ->willReturn(true);\n\n        return $connector;\n    }\n\n    protected function getHandlerDefaultOption($name)\n    {\n        $handler = new PHPConsoleHandler([], $this->connector);\n        $options = $handler->getOptions();\n\n        return $options[$name];\n    }\n\n    protected function initLogger($handlerOptions = [], $level = Level::Debug)\n    {\n        return new Logger('test', [\n            new PHPConsoleHandler($handlerOptions, $this->connector, $level),\n        ]);\n    }\n\n    public function testInitWithDefaultConnector()\n    {\n        $handler = new PHPConsoleHandler();\n        $this->assertEquals(spl_object_hash(Connector::getInstance()), spl_object_hash($handler->getConnector()));\n    }\n\n    public function testInitWithCustomConnector()\n    {\n        $handler = new PHPConsoleHandler([], $this->connector);\n        $this->assertEquals(spl_object_hash($this->connector), spl_object_hash($handler->getConnector()));\n    }\n\n    public function testDebug()\n    {\n        $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with($this->equalTo('test'));\n        $this->initLogger()->debug('test');\n    }\n\n    public function testDebugContextInMessage()\n    {\n        $message = 'test';\n        $tag = 'tag';\n        $context = [$tag, 'custom' => mt_rand()];\n        $expectedMessage = $message . ' ' . json_encode(\\array_slice($context, 1));\n        $this->debugDispatcher->expects($this->once())->method('dispatchDebug')->with(\n            $this->equalTo($expectedMessage),\n            $this->equalTo($tag)\n        );\n        $this->initLogger()->debug($message, $context);\n    }\n\n    public function testDebugTags($tagsContextKeys = null)\n    {\n        $expectedTags = mt_rand();\n        $logger = $this->initLogger($tagsContextKeys ? ['debugTagsKeysInContext' => $tagsContextKeys] : []);\n        if (!$tagsContextKeys) {\n            $tagsContextKeys = $this->getHandlerDefaultOption('debugTagsKeysInContext');\n        }\n        foreach ($tagsContextKeys as $key) {\n            $debugDispatcher = $this->initDebugDispatcherMock($this->connector);\n            $debugDispatcher->expects($this->once())->method('dispatchDebug')->with(\n                $this->anything(),\n                $this->equalTo($expectedTags)\n            );\n            $this->connector->setDebugDispatcher($debugDispatcher);\n            $logger->debug('test', [$key => $expectedTags]);\n        }\n    }\n\n    #[WithoutErrorHandler]\n    public function testError($classesPartialsTraceIgnore = null)\n    {\n        $code = E_USER_NOTICE;\n        $message = 'message';\n        $file = __FILE__;\n        $line = __LINE__;\n        $this->errorDispatcher->expects($this->once())->method('dispatchError')->with(\n            $this->equalTo($code),\n            $this->equalTo($message),\n            $this->equalTo($file),\n            $this->equalTo($line),\n            $classesPartialsTraceIgnore ?: $this->equalTo($this->getHandlerDefaultOption('classesPartialsTraceIgnore'))\n        );\n        $errorHandler = ErrorHandler::register($this->initLogger($classesPartialsTraceIgnore ? ['classesPartialsTraceIgnore' => $classesPartialsTraceIgnore] : []), false, false);\n        $errorHandler->registerErrorHandler([], false, E_USER_WARNING);\n        $reflMethod = new \\ReflectionMethod($errorHandler, 'handleError');\n        $reflMethod->invoke($errorHandler, $code, $message, $file, $line);\n\n        restore_error_handler();\n    }\n\n    public function testException()\n    {\n        $e = new Exception();\n        $this->errorDispatcher->expects($this->once())->method('dispatchException')->with(\n            $this->equalTo($e)\n        );\n        $handler = $this->initLogger();\n        $handler->log(\n            \\Psr\\Log\\LogLevel::ERROR,\n            \\sprintf('Uncaught Exception %s: \"%s\" at %s line %s', \\get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()),\n            ['exception' => $e]\n        );\n    }\n\n    public function testWrongOptionsThrowsException()\n    {\n        $this->expectException(\\Exception::class);\n\n        new PHPConsoleHandler(['xxx' => 1]);\n    }\n\n    public function testOptionEnabled()\n    {\n        $this->debugDispatcher->expects($this->never())->method('dispatchDebug');\n        $this->initLogger(['enabled' => false])->debug('test');\n    }\n\n    public function testOptionDebugTagsKeysInContext()\n    {\n        $this->testDebugTags(['key1', 'key2']);\n    }\n\n    public function testOptionUseOwnErrorsAndExceptionsHandler()\n    {\n        $this->initLogger(['useOwnErrorsHandler' => true, 'useOwnExceptionsHandler' => true]);\n        $this->assertEquals([VendorPhpConsoleHandler::getInstance(), 'handleError'], set_error_handler(function () {\n        }));\n        $this->assertEquals([VendorPhpConsoleHandler::getInstance(), 'handleException'], set_exception_handler(function () {\n        }));\n\n        restore_exception_handler();\n        restore_error_handler();\n        restore_exception_handler();\n        restore_error_handler();\n    }\n\n    public static function provideConnectorMethodsOptionsSets()\n    {\n        return [\n            ['sourcesBasePath', 'setSourcesBasePath', __DIR__],\n            ['serverEncoding', 'setServerEncoding', 'cp1251'],\n            ['password', 'setPassword', '******'],\n            ['enableSslOnlyMode', 'enableSslOnlyMode', true, false],\n            ['ipMasks', 'setAllowedIpMasks', ['127.0.0.*']],\n            ['headersLimit', 'setHeadersLimit', 2500],\n            ['enableEvalListener', 'startEvalRequestsListener', true, false],\n        ];\n    }\n\n    #[DataProvider('provideConnectorMethodsOptionsSets')]\n    public function testOptionCallsConnectorMethod($option, $method, $value, $isArgument = true)\n    {\n        $expectCall = $this->connector->expects($this->once())->method($method);\n        if ($isArgument) {\n            $expectCall->with($value);\n        }\n        new PHPConsoleHandler([$option => $value], $this->connector);\n    }\n\n    public function testOptionDetectDumpTraceAndSource()\n    {\n        new PHPConsoleHandler(['detectDumpTraceAndSource' => true], $this->connector);\n        $this->assertTrue($this->connector->getDebugDispatcher()->detectTraceAndSource);\n    }\n\n    public static function provideDumperOptionsValues()\n    {\n        return [\n            ['dumperLevelLimit', 'levelLimit', 1001],\n            ['dumperItemsCountLimit', 'itemsCountLimit', 1002],\n            ['dumperItemSizeLimit', 'itemSizeLimit', 1003],\n            ['dumperDumpSizeLimit', 'dumpSizeLimit', 1004],\n            ['dumperDetectCallbacks', 'detectCallbacks', true],\n        ];\n    }\n\n    #[DataProvider('provideDumperOptionsValues')]\n    public function testDumperOptions($option, $dumperProperty, $value)\n    {\n        new PHPConsoleHandler([$option => $value], $this->connector);\n        $this->assertEquals($value, $this->connector->getDumper()->$dumperProperty);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/ProcessHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass ProcessHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * Dummy command to be used by tests that should not fail due to the command.\n     *\n     * @var string\n     */\n    const DUMMY_COMMAND = 'php -r \"echo fgets(STDIN);\"';\n\n    /**\n     * @covers Monolog\\Handler\\ProcessHandler::__construct\n     * @covers Monolog\\Handler\\ProcessHandler::guardAgainstInvalidCommand\n     * @covers Monolog\\Handler\\ProcessHandler::guardAgainstInvalidCwd\n     * @covers Monolog\\Handler\\ProcessHandler::write\n     * @covers Monolog\\Handler\\ProcessHandler::ensureProcessIsStarted\n     * @covers Monolog\\Handler\\ProcessHandler::startProcess\n     * @covers Monolog\\Handler\\ProcessHandler::handleStartupErrors\n     */\n    public function testWriteOpensProcessAndWritesToStdInOfProcess()\n    {\n        $fixtures = [\n            'chuck norris',\n            'foobar1337',\n        ];\n\n        $mockBuilder = $this->getMockBuilder('Monolog\\Handler\\ProcessHandler');\n        $mockBuilder->onlyMethods(['writeProcessInput']);\n        $mockBuilder->setConstructorArgs([self::DUMMY_COMMAND]);\n\n        $handler = $mockBuilder->getMock();\n\n        $matcher = $this->exactly(2);\n        $handler->expects($matcher)\n            ->method('writeProcessInput')\n            ->willReturnCallback(function () use ($matcher, $fixtures) {\n                match ($matcher->numberOfInvocations()) {\n                    1 =>  $this->stringContains($fixtures[0]),\n                    2 =>  $this->stringContains($fixtures[1]),\n                };\n            })\n        ;\n\n        /** @var ProcessHandler $handler */\n        $handler->handle($this->getRecord(Level::Warning, $fixtures[0]));\n        $handler->handle($this->getRecord(Level::Error, $fixtures[1]));\n    }\n\n    /**\n     * Data provider for invalid commands.\n     */\n    public static function invalidCommandProvider(): array\n    {\n        return [\n            [1337, 'TypeError'],\n            ['', 'InvalidArgumentException'],\n            [null, 'TypeError'],\n            [fopen('php://input', 'r'), 'TypeError'],\n        ];\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ProcessHandler::guardAgainstInvalidCommand\n     */\n    #[DataProvider('invalidCommandProvider')]\n    public function testConstructWithInvalidCommandThrowsInvalidArgumentException(mixed $invalidCommand, string $expectedExcep)\n    {\n        $this->expectException($expectedExcep);\n        new ProcessHandler($invalidCommand, Level::Debug);\n    }\n\n    /**\n     * Data provider for invalid CWDs.\n     */\n    public static function invalidCwdProvider(): array\n    {\n        return [\n            [1337, 'TypeError'],\n            ['', 'InvalidArgumentException'],\n            [fopen('php://input', 'r'), 'TypeError'],\n        ];\n    }\n\n    /**\n     * @param mixed $invalidCwd\n     * @covers Monolog\\Handler\\ProcessHandler::guardAgainstInvalidCwd\n     */\n    #[DataProvider('invalidCwdProvider')]\n    public function testConstructWithInvalidCwdThrowsInvalidArgumentException($invalidCwd, $expectedExcep)\n    {\n        $this->expectException($expectedExcep);\n        new ProcessHandler(self::DUMMY_COMMAND, Level::Debug, true, $invalidCwd);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ProcessHandler::__construct\n     * @covers Monolog\\Handler\\ProcessHandler::guardAgainstInvalidCwd\n     */\n    public function testConstructWithValidCwdWorks()\n    {\n        $handler = new ProcessHandler(self::DUMMY_COMMAND, Level::Debug, true, sys_get_temp_dir());\n        $this->assertInstanceOf(\n            'Monolog\\Handler\\ProcessHandler',\n            $handler,\n            'Constructed handler is not a ProcessHandler.'\n        );\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ProcessHandler::handleStartupErrors\n     */\n    public function testStartupWithFailingToSelectErrorStreamThrowsUnexpectedValueException()\n    {\n        $mockBuilder = $this->getMockBuilder('Monolog\\Handler\\ProcessHandler');\n        $mockBuilder->onlyMethods(['selectErrorStream']);\n        $mockBuilder->setConstructorArgs([self::DUMMY_COMMAND]);\n\n        $handler = $mockBuilder->getMock();\n\n        $handler->expects($this->once())\n            ->method('selectErrorStream')\n            ->willReturn(false);\n\n        $this->expectException(\\UnexpectedValueException::class);\n        /** @var ProcessHandler $handler */\n        $handler->handle($this->getRecord(Level::Warning, 'stream failing, whoops'));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ProcessHandler::handleStartupErrors\n     * @covers Monolog\\Handler\\ProcessHandler::selectErrorStream\n     */\n    public function testStartupWithErrorsThrowsUnexpectedValueException()\n    {\n        $handler = new ProcessHandler('>&2 echo \"some fake error message\"');\n\n        $this->expectException(\\UnexpectedValueException::class);\n\n        $handler->handle($this->getRecord(Level::Warning, 'some warning in the house'));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ProcessHandler::write\n     */\n    public function testWritingWithErrorsOnStdOutOfProcessThrowsInvalidArgumentException()\n    {\n        $mockBuilder = $this->getMockBuilder('Monolog\\Handler\\ProcessHandler');\n        $mockBuilder->onlyMethods(['readProcessErrors']);\n        $mockBuilder->setConstructorArgs([self::DUMMY_COMMAND]);\n\n        $handler = $mockBuilder->getMock();\n\n        $handler->expects($this->exactly(2))\n            ->method('readProcessErrors')\n            ->willReturnOnConsecutiveCalls('', 'some fake error message here');\n\n        $this->expectException(\\UnexpectedValueException::class);\n        /** @var ProcessHandler $handler */\n        $handler->handle($this->getRecord(Level::Warning, 'some test stuff'));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ProcessHandler::close\n     */\n    public function testCloseClosesProcess()\n    {\n        $class = new \\ReflectionClass('Monolog\\Handler\\ProcessHandler');\n        $property = $class->getProperty('process');\n\n        $handler = new ProcessHandler(self::DUMMY_COMMAND);\n        $handler->handle($this->getRecord(Level::Warning, '21 is only the half truth'));\n\n        $process = $property->getValue($handler);\n        $this->assertTrue(\\is_resource($process), 'Process is not running although it should.');\n\n        $handler->close();\n\n        $process = $property->getValue($handler);\n        $this->assertFalse(\\is_resource($process), 'Process is still running although it should not.');\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/PsrHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\LineFormatter;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\n/**\n * @covers Monolog\\Handler\\PsrHandler::handle\n */\nclass PsrHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public static function logLevelProvider()\n    {\n        return array_map(\n            fn (Level $level) => [$level->toPsrLogLevel(), $level],\n            Level::cases()\n        );\n    }\n\n    #[DataProvider('logLevelProvider')]\n    public function testHandlesAllLevels(string $levelName, Level $level)\n    {\n        $message = 'Hello, world! ' . $level->value;\n        $context = ['foo' => 'bar', 'level' => $level->value];\n\n        $psrLogger = $this->createMock('Psr\\Log\\NullLogger');\n        $psrLogger->expects($this->once())\n            ->method('log')\n            ->with($levelName, $message, $context);\n\n        $handler = new PsrHandler($psrLogger);\n        $handler->handle($this->getRecord($level, $message, context: $context));\n    }\n\n    public function testFormatter()\n    {\n        $message = 'Hello, world!';\n        $context = ['foo' => 'bar'];\n        $level = Level::Error;\n\n        $psrLogger = $this->createMock('Psr\\Log\\NullLogger');\n        $psrLogger->expects($this->once())\n            ->method('log')\n            ->with($level->toPsrLogLevel(), 'dummy', $context);\n\n        $handler = new PsrHandler($psrLogger);\n        $handler->setFormatter(new LineFormatter('dummy'));\n        $handler->handle($this->getRecord($level, $message, context: $context, datetime: new \\DateTimeImmutable()));\n    }\n\n    public function testIncludeExtra()\n    {\n        $message = 'Hello, world!';\n        $context = ['foo' => 'bar'];\n        $extra = ['baz' => 'boo'];\n        $level = Level::Error;\n\n        $psrLogger = $this->createMock('Psr\\Log\\NullLogger');\n        $psrLogger->expects($this->once())\n            ->method('log')\n            ->with($level->toPsrLogLevel(), $message, ['baz' => 'boo', 'foo' => 'bar']);\n\n        $handler = new PsrHandler($psrLogger, includeExtra: true);\n        $handler->handle($this->getRecord($level, $message, context: $context, datetime: new \\DateTimeImmutable(), extra: $extra));\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/PushoverHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\n\n/**\n * Almost all examples (expected header, titles, messages) taken from\n * https://www.pushover.net/api\n * @author Sebastian Göttschkes <sebastian.goettschkes@googlemail.com>\n * @see https://www.pushover.net/api\n */\nclass PushoverHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /** @var resource */\n    private $res;\n    private PushoverHandler&MockObject $handler;\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->res);\n        unset($this->handler);\n    }\n\n    public function testWriteHeader()\n    {\n        $this->createHandler();\n        $this->handler->setHighPriorityLevel(Level::Emergency); // skip priority notifications\n        $this->handler->handle($this->getRecord(Level::Critical, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/POST \\/1\\/messages.json HTTP\\/1.1\\\\r\\\\nHost: api.pushover.net\\\\r\\\\nContent-Type: application\\/x-www-form-urlencoded\\\\r\\\\nContent-Length: \\d{2,4}\\\\r\\\\n\\\\r\\\\n/', $content);\n\n        return $content;\n    }\n\n    /**\n     * @depends testWriteHeader\n     */\n    public function testWriteContent($content)\n    {\n        $this->assertMatchesRegularExpression('/token=myToken&user=myUser&message=test1&title=Monolog&timestamp=\\d{10}$/', $content);\n    }\n\n    public function testWriteWithComplexTitle()\n    {\n        $this->createHandler('myToken', 'myUser', 'Backup finished - SQL1');\n        $this->handler->handle($this->getRecord(Level::Critical, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/title=Backup\\+finished\\+-\\+SQL1/', $content);\n    }\n\n    public function testWriteWithComplexMessage()\n    {\n        $this->createHandler();\n        $this->handler->setHighPriorityLevel(Level::Emergency); // skip priority notifications\n        $this->handler->handle($this->getRecord(Level::Critical, 'Backup of database \"example\" finished in 16 minutes.'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/message=Backup\\+of\\+database\\+%22example%22\\+finished\\+in\\+16\\+minutes\\./', $content);\n    }\n\n    public function testWriteWithTooLongMessage()\n    {\n        $message = str_pad('test', 520, 'a');\n        $this->createHandler();\n        $this->handler->setHighPriorityLevel(Level::Emergency); // skip priority notifications\n        $this->handler->handle($this->getRecord(Level::Critical, $message));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $expectedMessage = substr($message, 0, 505);\n\n        $this->assertMatchesRegularExpression('/message=' . $expectedMessage . '&title/', $content);\n    }\n\n    public function testWriteWithHighPriority()\n    {\n        $this->createHandler();\n        $this->handler->handle($this->getRecord(Level::Critical, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/token=myToken&user=myUser&message=test1&title=Monolog&timestamp=\\d{10}&priority=1$/', $content);\n    }\n\n    public function testWriteWithEmergencyPriority()\n    {\n        $this->createHandler();\n        $this->handler->handle($this->getRecord(Level::Emergency, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/token=myToken&user=myUser&message=test1&title=Monolog&timestamp=\\d{10}&priority=2&retry=30&expire=25200$/', $content);\n    }\n\n    public function testWriteToMultipleUsers()\n    {\n        $this->createHandler('myToken', ['userA', 'userB']);\n        $this->handler->handle($this->getRecord(Level::Emergency, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/token=myToken&user=userA&message=test1&title=Monolog&timestamp=\\d{10}&priority=2&retry=30&expire=25200POST/', $content);\n        $this->assertMatchesRegularExpression('/token=myToken&user=userB&message=test1&title=Monolog&timestamp=\\d{10}&priority=2&retry=30&expire=25200$/', $content);\n    }\n\n    private function createHandler($token = 'myToken', $user = 'myUser', $title = 'Monolog')\n    {\n        $constructorArgs = [$token, $user, $title];\n        $this->res = fopen('php://memory', 'a');\n        $this->handler = $this->getMockBuilder(PushoverHandler::class)\n            ->setConstructorArgs($constructorArgs)\n            ->onlyMethods(['fsockopen', 'streamSetTimeout', 'closeSocket'])\n            ->getMock();\n\n        $reflectionProperty = new \\ReflectionProperty('Monolog\\Handler\\SocketHandler', 'connectionString');\n        $reflectionProperty->setValue($this->handler, 'localhost:1234');\n\n        $this->handler->expects($this->any())\n            ->method('fsockopen')\n            ->willReturn($this->res);\n        $this->handler->expects($this->any())\n            ->method('streamSetTimeout')\n            ->willReturn(true);\n        $this->handler->expects($this->any())\n            ->method('closeSocket');\n\n        $this->handler->setFormatter($this->getIdentityFormatter());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/RedisHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\LineFormatter;\n\nclass RedisHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testConstructorShouldWorkWithPredis()\n    {\n        $redis = $this->createMock('Predis\\Client');\n        $this->assertInstanceof('Monolog\\Handler\\RedisHandler', new RedisHandler($redis, 'key'));\n    }\n\n    public function testConstructorShouldWorkWithRedis()\n    {\n        if (!class_exists('Redis')) {\n            $this->markTestSkipped('The redis ext is required to run this test');\n        }\n\n        $redis = $this->createMock('Redis');\n        $this->assertInstanceof('Monolog\\Handler\\RedisHandler', new RedisHandler($redis, 'key'));\n    }\n\n    public function testPredisHandle()\n    {\n        $redis = $this->getMockBuilder('Predis\\Client')->getMock();\n        $redis->expects($this->atLeastOnce())\n            ->method('__call')\n            ->with(self::equalTo('rpush'), self::equalTo(['key', 'test']));\n\n        $record = $this->getRecord(Level::Warning, 'test', ['data' => new \\stdClass, 'foo' => 34]);\n\n        $handler = new RedisHandler($redis, 'key');\n        $handler->setFormatter(new LineFormatter(\"%message%\"));\n        $handler->handle($record);\n    }\n\n    public function testRedisHandle()\n    {\n        if (!class_exists('Redis')) {\n            $this->markTestSkipped('The redis ext is required to run this test');\n        }\n\n        $redis = $this->createPartialMock('Redis', ['rPush']);\n\n        // Redis uses rPush\n        $redis->expects($this->once())\n            ->method('rPush')\n            ->with('key', 'test');\n\n        $record = $this->getRecord(Level::Warning, 'test', ['data' => new \\stdClass, 'foo' => 34]);\n\n        $handler = new RedisHandler($redis, 'key');\n        $handler->setFormatter(new LineFormatter(\"%message%\"));\n        $handler->handle($record);\n    }\n\n    public function testRedisHandleCapped()\n    {\n        if (!class_exists('Redis')) {\n            $this->markTestSkipped('The redis ext is required to run this test');\n        }\n\n        $redis = $this->createPartialMock('Redis', ['multi', 'rPush', 'lTrim', 'exec']);\n\n        // Redis uses multi\n        $redis->expects($this->once())\n            ->method('multi')\n            ->willReturnSelf();\n\n        $redis->expects($this->once())\n            ->method('rPush')\n            ->willReturnSelf();\n\n        $redis->expects($this->once())\n            ->method('lTrim')\n            ->willReturnSelf();\n\n        $redis->expects($this->once())\n            ->method('exec')\n            ->willReturnSelf();\n\n        $record = $this->getRecord(Level::Warning, 'test', ['data' => new \\stdClass, 'foo' => 34]);\n\n        $handler = new RedisHandler($redis, 'key', Level::Debug, true, 10);\n        $handler->setFormatter(new LineFormatter(\"%message%\"));\n        $handler->handle($record);\n    }\n\n    public function testPredisHandleCapped()\n    {\n        $redis = new class extends \\Predis\\Client {\n            public array $testResults = [];\n\n            public function rpush(...$args)\n            {\n                $this->testResults[] = ['rpush', ...$args];\n\n                return $this;\n            }\n\n            public function ltrim(...$args)\n            {\n                $this->testResults[] = ['ltrim', ...$args];\n\n                return $this;\n            }\n\n            public function transaction(...$args)\n            {\n                $this->testResults[] = ['transaction start'];\n\n                return ($args[0])($this);\n            }\n        };\n\n        $record = $this->getRecord(Level::Warning, 'test', ['data' => new \\stdClass, 'foo' => 34]);\n\n        $handler = new RedisHandler($redis, 'key', Level::Debug, true, 10);\n        $handler->setFormatter(new LineFormatter(\"%message%\"));\n        $handler->handle($record);\n\n        self::assertsame([\n            ['transaction start'],\n            ['rpush', 'key', 'test'],\n            ['ltrim', 'key', -10, -1],\n        ], $redis->testResults);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/RedisPubSubHandlerTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\LineFormatter;\n\nclass RedisPubSubHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testConstructorShouldWorkWithPredis()\n    {\n        $redis = $this->createMock('Predis\\Client');\n        $this->assertInstanceof('Monolog\\Handler\\RedisPubSubHandler', new RedisPubSubHandler($redis, 'key'));\n    }\n\n    public function testConstructorShouldWorkWithRedis()\n    {\n        if (!class_exists('Redis')) {\n            $this->markTestSkipped('The redis ext is required to run this test');\n        }\n\n        $redis = $this->createMock('Redis');\n        $this->assertInstanceof('Monolog\\Handler\\RedisPubSubHandler', new RedisPubSubHandler($redis, 'key'));\n    }\n\n    public function testPredisHandle()\n    {\n        $redis = $this->getMockBuilder('Predis\\Client')->getMock();\n        $redis->expects($this->atLeastOnce())\n            ->method('__call')\n            ->with(self::equalTo('publish'), self::equalTo(['key', 'test']));\n\n        $record = $this->getRecord(Level::Warning, 'test', ['data' => new \\stdClass(), 'foo' => 34]);\n\n        $handler = new RedisPubSubHandler($redis, 'key');\n        $handler->setFormatter(new LineFormatter(\"%message%\"));\n        $handler->handle($record);\n    }\n\n    public function testRedisHandle()\n    {\n        if (!class_exists('Redis')) {\n            $this->markTestSkipped('The redis ext is required to run this test');\n        }\n\n        $redis = $this->createPartialMock('Redis', ['publish']);\n\n        $redis->expects($this->once())\n            ->method('publish')\n            ->with('key', 'test');\n\n        $record = $this->getRecord(Level::Warning, 'test', ['data' => new \\stdClass(), 'foo' => 34]);\n\n        $handler = new RedisPubSubHandler($redis, 'key');\n        $handler->setFormatter(new LineFormatter(\"%message%\"));\n        $handler->handle($record);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/RollbarHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Exception;\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Rollbar\\RollbarLogger;\n\n/**\n * @author Erik Johansson <erik.pm.johansson@gmail.com>\n * @see    https://rollbar.com/docs/notifier/rollbar-php/\n *\n * @coversDefaultClass Monolog\\Handler\\RollbarHandler\n *\n * @requires function \\Rollbar\\RollbarLogger::__construct\n */\nclass RollbarHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    private RollbarLogger&MockObject $rollbarLogger;\n\n    private array $reportedExceptionArguments;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        $this->setupRollbarLoggerMock();\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->rollbarLogger, $this->reportedExceptionArguments);\n    }\n\n    /**\n     * When reporting exceptions to Rollbar the\n     * level has to be set in the payload data\n     */\n    public function testExceptionLogLevel()\n    {\n        $handler = $this->createHandler();\n\n        $handler->handle($this->getRecord(Level::Debug, context: ['exception' => $e = new Exception()]));\n\n        $this->assertEquals('debug', $this->reportedExceptionArguments['payload']['level']);\n        $this->assertSame($e, $this->reportedExceptionArguments['context']);\n    }\n\n    private function setupRollbarLoggerMock()\n    {\n        $config = [\n            'access_token' => 'ad865e76e7fb496fab096ac07b1dbabb',\n            'environment' => 'test',\n        ];\n\n        $this->rollbarLogger = $this->getMockBuilder(RollbarLogger::class)\n            ->setConstructorArgs([$config])\n            ->onlyMethods(['log'])\n            ->getMock();\n\n        $this->rollbarLogger\n            ->expects($this->any())\n            ->method('log')\n            ->willReturnCallback(function ($exception, $context, $payload) {\n                $this->reportedExceptionArguments = compact('exception', 'context', 'payload');\n            });\n    }\n\n    private function createHandler(): RollbarHandler\n    {\n        return new RollbarHandler($this->rollbarLogger, Level::Debug);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/RotatingFileHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\n/**\n * @covers Monolog\\Handler\\RotatingFileHandler\n */\nclass RotatingFileHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    private array|null $lastError = null;\n\n    public function setUp(): void\n    {\n        $dir = __DIR__.'/Fixtures';\n        chmod($dir, 0777);\n        if (!is_writable($dir)) {\n            $this->markTestSkipped($dir.' must be writable to test the RotatingFileHandler.');\n        }\n        $this->lastError = null;\n        set_error_handler(function ($code, $message) {\n            $this->lastError = [\n                'code' => $code,\n                'message' => $message,\n            ];\n\n            return true;\n        });\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        foreach (glob(__DIR__.'/Fixtures/*.rot') as $file) {\n            unlink($file);\n        }\n\n        if ('testRotationWithFolderByDate' === $this->name()) {\n            foreach (glob(__DIR__.'/Fixtures/[0-9]*') as $folder) {\n                $this->rrmdir($folder);\n            }\n        }\n\n        restore_error_handler();\n\n        unset($this->lastError);\n    }\n\n    private function rrmdir($directory)\n    {\n        if (! is_dir($directory)) {\n            throw new InvalidArgumentException(\"$directory must be a directory\");\n        }\n\n        if (substr($directory, \\strlen($directory) - 1, 1) !== '/') {\n            $directory .= '/';\n        }\n\n        foreach (glob($directory . '*', GLOB_MARK) as $path) {\n            if (is_dir($path)) {\n                $this->rrmdir($path);\n            } else {\n                unlink($path);\n            }\n        }\n\n        return rmdir($directory);\n    }\n\n    private function assertErrorWasTriggered($code, $message)\n    {\n        if (empty($this->lastError)) {\n            $this->fail(\n                \\sprintf(\n                    'Failed asserting that error with code `%d` and message `%s` was triggered',\n                    $code,\n                    $message\n                )\n            );\n        }\n        $this->assertEquals($code, $this->lastError['code'], \\sprintf('Expected an error with code %d to be triggered, got `%s` instead', $code, $this->lastError['code']));\n        $this->assertEquals($message, $this->lastError['message'], \\sprintf('Expected an error with message `%d` to be triggered, got `%s` instead', $message, $this->lastError['message']));\n    }\n\n    public function testRotationCreatesNewFile()\n    {\n        touch(__DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot');\n\n        $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot');\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->handle($this->getRecord());\n\n        $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot';\n        $this->assertTrue(file_exists($log));\n        $this->assertEquals('test', file_get_contents($log));\n    }\n\n    #[DataProvider('rotationTests')]\n    public function testRotation($createFile, $dateFormat, $timeCallback)\n    {\n        touch($old1 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-1)).'.rot');\n        touch($old2 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-2)).'.rot');\n        touch($old3 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-3)).'.rot');\n        touch($old4 = __DIR__.'/Fixtures/foo-'.date($dateFormat, $timeCallback(-4)).'.rot');\n\n        $log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot';\n\n        if ($createFile) {\n            touch($log);\n        }\n\n        $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->setFilenameFormat('{filename}-{date}', $dateFormat);\n        $handler->handle($this->getRecord());\n\n        $handler->close();\n\n        $this->assertTrue(file_exists($log));\n        $this->assertTrue(file_exists($old1));\n        $this->assertEquals($createFile, file_exists($old2));\n        $this->assertEquals($createFile, file_exists($old3));\n        $this->assertEquals($createFile, file_exists($old4));\n        $this->assertEquals('test', file_get_contents($log));\n    }\n\n    public static function rotationTests()\n    {\n        $now = time();\n        $dayCallback = function ($ago) use ($now) {\n            return $now + 86400 * $ago;\n        };\n        $monthCallback = function ($ago) {\n            return gmmktime(0, 0, 0, (int) (date('n') + $ago), 1, (int) date('Y'));\n        };\n        $yearCallback = function ($ago) {\n            return gmmktime(0, 0, 0, 1, 1, (int) (date('Y') + $ago));\n        };\n\n        return [\n            'Rotation is triggered when the file of the current day is not present'\n                => [true, RotatingFileHandler::FILE_PER_DAY, $dayCallback],\n            'Rotation is not triggered when the file of the current day is already present'\n                => [false, RotatingFileHandler::FILE_PER_DAY, $dayCallback],\n\n            'Rotation is triggered when the file of the current month is not present'\n                => [true, RotatingFileHandler::FILE_PER_MONTH, $monthCallback],\n            'Rotation is not triggered when the file of the current month is already present'\n                => [false, RotatingFileHandler::FILE_PER_MONTH, $monthCallback],\n\n            'Rotation is triggered when the file of the current year is not present'\n                => [true, RotatingFileHandler::FILE_PER_YEAR, $yearCallback],\n            'Rotation is not triggered when the file of the current year is already present'\n                => [false, RotatingFileHandler::FILE_PER_YEAR, $yearCallback],\n        ];\n    }\n\n    private function createDeep($file)\n    {\n        mkdir(\\dirname($file), 0777, true);\n        touch($file);\n\n        return $file;\n    }\n\n    #[DataProvider('rotationWithFolderByDateTests')]\n    public function testRotationWithFolderByDate($createFile, $dateFormat, $timeCallback)\n    {\n        $old1 = $this->createDeep(__DIR__.'/Fixtures/'.date($dateFormat, $timeCallback(-1)).'/foo.rot');\n        $old2 = $this->createDeep(__DIR__.'/Fixtures/'.date($dateFormat, $timeCallback(-2)).'/foo.rot');\n        $old3 = $this->createDeep(__DIR__.'/Fixtures/'.date($dateFormat, $timeCallback(-3)).'/foo.rot');\n        $old4 = $this->createDeep(__DIR__.'/Fixtures/'.date($dateFormat, $timeCallback(-4)).'/foo.rot');\n\n        $log = __DIR__.'/Fixtures/'.date($dateFormat).'/foo.rot';\n\n        if ($createFile) {\n            $this->createDeep($log);\n        }\n\n        $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->setFilenameFormat('{date}/{filename}', $dateFormat);\n        $handler->handle($this->getRecord());\n\n        $handler->close();\n\n        $this->assertTrue(file_exists($log));\n        $this->assertTrue(file_exists($old1));\n        $this->assertEquals($createFile, file_exists($old2));\n        $this->assertEquals($createFile, file_exists($old3));\n        $this->assertEquals($createFile, file_exists($old4));\n        $this->assertEquals('test', file_get_contents($log));\n    }\n\n    public static function rotationWithFolderByDateTests()\n    {\n        $now = time();\n        $dayCallback = function ($ago) use ($now) {\n            return $now + 86400 * $ago;\n        };\n        $monthCallback = function ($ago) {\n            return gmmktime(0, 0, 0, (int) (date('n') + $ago), 1, (int) date('Y'));\n        };\n        $yearCallback = function ($ago) {\n            return gmmktime(0, 0, 0, 1, 1, (int) (date('Y') + $ago));\n        };\n\n        return [\n            'Rotation is triggered when the file of the current day is not present'\n                => [true, 'Y/m/d', $dayCallback],\n            'Rotation is not triggered when the file of the current day is already present'\n                => [false, 'Y/m/d', $dayCallback],\n\n            'Rotation is triggered when the file of the current month is not present'\n                => [true, 'Y/m', $monthCallback],\n            'Rotation is not triggered when the file of the current month is already present'\n                => [false, 'Y/m', $monthCallback],\n\n            'Rotation is triggered when the file of the current year is not present'\n                => [true, 'Y', $yearCallback],\n            'Rotation is not triggered when the file of the current year is already present'\n                => [false, 'Y', $yearCallback],\n        ];\n    }\n\n    #[DataProvider('dateFormatProvider')]\n    public function testAllowOnlyFixedDefinedDateFormats($dateFormat, $valid)\n    {\n        $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);\n        if (!$valid) {\n            $this->expectException(InvalidArgumentException::class);\n            $this->expectExceptionMessageMatches('~^Invalid date format~');\n        }\n        $handler->setFilenameFormat('{filename}-{date}', $dateFormat);\n        $this->assertTrue(true);\n    }\n\n    public static function dateFormatProvider()\n    {\n        return [\n            [RotatingFileHandler::FILE_PER_DAY, true],\n            [RotatingFileHandler::FILE_PER_MONTH, true],\n            [RotatingFileHandler::FILE_PER_YEAR, true],\n            ['Y/m/d', true],\n            ['Y.m.d', true],\n            ['Y_m_d', true],\n            ['Ymd', true],\n            ['Ym/d', true],\n            ['Y/m', true],\n            ['Ym', true],\n            ['Y.m', true],\n            ['Y_m', true],\n            ['Y/md', true],\n            ['', false],\n            ['m-d-Y', false],\n            ['Y-m-d-h-i', false],\n            ['Y-', false],\n            ['Y-m-', false],\n            ['Y--', false],\n            ['m-d', false],\n            ['Y-d', false],\n        ];\n    }\n\n    #[DataProvider('filenameFormatProvider')]\n    public function testDisallowFilenameFormatsWithoutDate($filenameFormat, $valid)\n    {\n        $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);\n        if (!$valid) {\n            $this->expectException(InvalidArgumentException::class);\n            $this->expectExceptionMessageMatches('~^Invalid filename format~');\n        }\n\n        $handler->setFilenameFormat($filenameFormat, RotatingFileHandler::FILE_PER_DAY);\n    }\n\n    public static function filenameFormatProvider()\n    {\n        return [\n            ['{filename}', false],\n            ['{filename}-{date}', true],\n            ['{date}', true],\n            ['foobar-{date}', true],\n            ['foo-{date}-bar', true],\n            ['{date}-foobar', true],\n            ['{date}/{filename}', true],\n            ['foobar', false],\n        ];\n    }\n\n    #[DataProvider('rotationWhenSimilarFilesExistTests')]\n    public function testRotationWhenSimilarFileNamesExist($dateFormat)\n    {\n        touch($old1 = __DIR__.'/Fixtures/foo-foo-'.date($dateFormat).'.rot');\n        touch($old2 = __DIR__.'/Fixtures/foo-bar-'.date($dateFormat).'.rot');\n\n        $log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot';\n\n        $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->setFilenameFormat('{filename}-{date}', $dateFormat);\n        $handler->handle($this->getRecord());\n        $handler->close();\n\n        $this->assertTrue(file_exists($log));\n    }\n\n    public static function rotationWhenSimilarFilesExistTests()\n    {\n        return [\n            'Rotation is triggered when the file of the current day is not present but similar exists'\n                => [RotatingFileHandler::FILE_PER_DAY],\n\n            'Rotation is triggered when the file of the current month is not present but similar exists'\n                => [RotatingFileHandler::FILE_PER_MONTH],\n\n            'Rotation is triggered when the file of the current year is not present but similar exists'\n                => [RotatingFileHandler::FILE_PER_YEAR],\n        ];\n    }\n\n    public function testReuseCurrentFile()\n    {\n        $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot';\n        file_put_contents($log, \"foo\");\n        $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot');\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->handle($this->getRecord());\n        $this->assertEquals('footest', file_get_contents($log));\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/SamplingHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\n/**\n * @covers Monolog\\Handler\\SamplingHandler::handle\n */\nclass SamplingHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testHandle()\n    {\n        $testHandler = new TestHandler();\n        $handler = new SamplingHandler($testHandler, 2);\n        for ($i = 0; $i < 10000; $i++) {\n            $handler->handle($this->getRecord());\n        }\n        $count = \\count($testHandler->getRecords());\n        // $count should be half of 10k, so between 4k and 6k\n        $this->assertLessThan(6000, $count);\n        $this->assertGreaterThan(4000, $count);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/Slack/SlackRecordTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler\\Slack;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\n#[CoversClass(SlackRecord::class)]\nclass SlackRecordTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public static function dataGetAttachmentColor()\n    {\n        return [\n            [Level::Debug, SlackRecord::COLOR_DEFAULT],\n            [Level::Info, SlackRecord::COLOR_GOOD],\n            [Level::Notice, SlackRecord::COLOR_GOOD],\n            [Level::Warning, SlackRecord::COLOR_WARNING],\n            [Level::Error, SlackRecord::COLOR_DANGER],\n            [Level::Critical, SlackRecord::COLOR_DANGER],\n            [Level::Alert, SlackRecord::COLOR_DANGER],\n            [Level::Emergency, SlackRecord::COLOR_DANGER],\n        ];\n    }\n\n    #[DataProvider('dataGetAttachmentColor')]\n    public function testGetAttachmentColor(Level $logLevel, string $expectedColour)\n    {\n        $slackRecord = new SlackRecord();\n        $this->assertSame(\n            $expectedColour,\n            $slackRecord->getAttachmentColor($logLevel)\n        );\n    }\n\n    public function testAddsChannel()\n    {\n        $channel = '#test';\n        $record = new SlackRecord($channel);\n        $data = $record->getSlackData($this->getRecord());\n\n        $this->assertArrayHasKey('channel', $data);\n        $this->assertSame($channel, $data['channel']);\n    }\n\n    public function testNoUsernameByDefault()\n    {\n        $record = new SlackRecord();\n        $data = $record->getSlackData($this->getRecord());\n\n        $this->assertArrayNotHasKey('username', $data);\n    }\n\n    public static function dataStringify(): array\n    {\n        $multipleDimensions = [[1, 2]];\n        $numericKeys = ['library' => 'monolog'];\n        $singleDimension = [1, 'Hello', 'Jordi'];\n\n        return [\n            [[], '[]'],\n            [$multipleDimensions, json_encode($multipleDimensions, JSON_PRETTY_PRINT)],\n            [$numericKeys, json_encode($numericKeys, JSON_PRETTY_PRINT)],\n            [$singleDimension, json_encode($singleDimension)],\n        ];\n    }\n\n    #[DataProvider('dataStringify')]\n    public function testStringify($fields, $expectedResult)\n    {\n        $slackRecord = new SlackRecord(\n            '#test',\n            'test',\n            true,\n            null,\n            true,\n            true\n        );\n\n        $this->assertSame($expectedResult, $slackRecord->stringify($fields));\n    }\n\n    public function testAddsCustomUsername()\n    {\n        $username = 'Monolog bot';\n        $record = new SlackRecord(null, $username);\n        $data = $record->getSlackData($this->getRecord());\n\n        $this->assertArrayHasKey('username', $data);\n        $this->assertSame($username, $data['username']);\n    }\n\n    public function testNoIcon()\n    {\n        $record = new SlackRecord();\n        $data = $record->getSlackData($this->getRecord());\n\n        $this->assertArrayNotHasKey('icon_emoji', $data);\n    }\n\n    public function testAddsIcon()\n    {\n        $record = $this->getRecord();\n        $slackRecord = new SlackRecord(null, null, false, 'ghost');\n        $data = $slackRecord->getSlackData($record);\n\n        $slackRecord2 = new SlackRecord(null, null, false, 'http://github.com/Seldaek/monolog');\n        $data2 = $slackRecord2->getSlackData($record);\n\n        $this->assertArrayHasKey('icon_emoji', $data);\n        $this->assertSame(':ghost:', $data['icon_emoji']);\n        $this->assertArrayHasKey('icon_url', $data2);\n        $this->assertSame('http://github.com/Seldaek/monolog', $data2['icon_url']);\n    }\n\n    public function testAttachmentsNotPresentIfNoAttachment()\n    {\n        $record = new SlackRecord(null, null, false);\n        $data = $record->getSlackData($this->getRecord());\n\n        $this->assertArrayNotHasKey('attachments', $data);\n    }\n\n    public function testAddsOneAttachment()\n    {\n        $record = new SlackRecord();\n        $data = $record->getSlackData($this->getRecord());\n\n        $this->assertArrayHasKey('attachments', $data);\n        $this->assertArrayHasKey(0, $data['attachments']);\n        $this->assertIsArray($data['attachments'][0]);\n    }\n\n    public function testTextEqualsMessageIfNoAttachment()\n    {\n        $message = 'Test message';\n        $record = new SlackRecord(null, null, false);\n        $data = $record->getSlackData($this->getRecord(Level::Warning, $message));\n\n        $this->assertArrayHasKey('text', $data);\n        $this->assertSame($message, $data['text']);\n    }\n\n    public function testTextEqualsFormatterOutput()\n    {\n        $formatter = $this->createMock('Monolog\\\\Formatter\\\\FormatterInterface');\n        $formatter\n            ->expects($this->any())\n            ->method('format')\n            ->willReturnCallback(function ($record) {\n                return $record->message . 'test';\n            });\n\n        $formatter2 = $this->createMock('Monolog\\\\Formatter\\\\FormatterInterface');\n        $formatter2\n            ->expects($this->any())\n            ->method('format')\n            ->willReturnCallback(function ($record) {\n                return $record->message . 'test1';\n            });\n\n        $message = 'Test message';\n        $record = new SlackRecord(null, null, false, null, false, false, [], $formatter);\n        $data = $record->getSlackData($this->getRecord(Level::Warning, $message));\n\n        $this->assertArrayHasKey('text', $data);\n        $this->assertSame($message . 'test', $data['text']);\n\n        $record->setFormatter($formatter2);\n        $data = $record->getSlackData($this->getRecord(Level::Warning, $message));\n\n        $this->assertArrayHasKey('text', $data);\n        $this->assertSame($message . 'test1', $data['text']);\n    }\n\n    public function testAddsFallbackAndTextToAttachment()\n    {\n        $message = 'Test message';\n        $record = new SlackRecord(null);\n        $data = $record->getSlackData($this->getRecord(Level::Warning, $message));\n\n        $this->assertSame($message, $data['attachments'][0]['text']);\n        $this->assertSame($message, $data['attachments'][0]['fallback']);\n    }\n\n    public function testMapsLevelToColorAttachmentColor()\n    {\n        $record = new SlackRecord(null);\n        $errorLoggerRecord = $this->getRecord(Level::Error);\n        $emergencyLoggerRecord = $this->getRecord(Level::Emergency);\n        $warningLoggerRecord = $this->getRecord(Level::Warning);\n        $infoLoggerRecord = $this->getRecord(Level::Info);\n        $debugLoggerRecord = $this->getRecord(Level::Debug);\n\n        $data = $record->getSlackData($errorLoggerRecord);\n        $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']);\n\n        $data = $record->getSlackData($emergencyLoggerRecord);\n        $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']);\n\n        $data = $record->getSlackData($warningLoggerRecord);\n        $this->assertSame(SlackRecord::COLOR_WARNING, $data['attachments'][0]['color']);\n\n        $data = $record->getSlackData($infoLoggerRecord);\n        $this->assertSame(SlackRecord::COLOR_GOOD, $data['attachments'][0]['color']);\n\n        $data = $record->getSlackData($debugLoggerRecord);\n        $this->assertSame(SlackRecord::COLOR_DEFAULT, $data['attachments'][0]['color']);\n    }\n\n    public function testAddsShortAttachmentWithoutContextAndExtra()\n    {\n        $level = Level::Error;\n        $levelName = $level->getName();\n        $record = new SlackRecord(null, null, true, null, true);\n        $data = $record->getSlackData($this->getRecord($level, 'test', ['test' => 1]));\n\n        $attachment = $data['attachments'][0];\n        $this->assertArrayHasKey('title', $attachment);\n        $this->assertArrayHasKey('fields', $attachment);\n        $this->assertSame($levelName, $attachment['title']);\n        $this->assertSame([], $attachment['fields']);\n    }\n\n    public function testAddsShortAttachmentWithContextAndExtra()\n    {\n        $level = Level::Error;\n        $levelName = $level->getName();\n        $context = ['test' => 1];\n        $extra = ['tags' => ['web']];\n        $record = new SlackRecord(null, null, true, null, true, true);\n        $loggerRecord = $this->getRecord($level, 'test', $context);\n        $loggerRecord['extra'] = $extra;\n        $data = $record->getSlackData($loggerRecord);\n\n        $attachment = $data['attachments'][0];\n        $this->assertArrayHasKey('title', $attachment);\n        $this->assertArrayHasKey('fields', $attachment);\n        $this->assertCount(2, $attachment['fields']);\n        $this->assertSame($levelName, $attachment['title']);\n        $this->assertSame(\n            [\n                [\n                    'title' => 'Extra',\n                    'value' => \\sprintf('```%s```', json_encode($extra, JSON_PRETTY_PRINT)),\n                    'short' => false,\n                ],\n                [\n                    'title' => 'Context',\n                    'value' => \\sprintf('```%s```', json_encode($context, JSON_PRETTY_PRINT)),\n                    'short' => false,\n                ],\n            ],\n            $attachment['fields']\n        );\n    }\n\n    public function testAddsLongAttachmentWithoutContextAndExtra()\n    {\n        $level = Level::Error;\n        $levelName = $level->getName();\n        $record = new SlackRecord(null, null, true, null);\n        $data = $record->getSlackData($this->getRecord($level, 'test', ['test' => 1]));\n\n        $attachment = $data['attachments'][0];\n        $this->assertArrayHasKey('title', $attachment);\n        $this->assertArrayHasKey('fields', $attachment);\n        $this->assertCount(1, $attachment['fields']);\n        $this->assertSame('Message', $attachment['title']);\n        $this->assertSame(\n            [[\n                'title' => 'Level',\n                'value' => $levelName,\n                'short' => false,\n            ]],\n            $attachment['fields']\n        );\n    }\n\n    public function testAddsLongAttachmentWithContextAndExtra()\n    {\n        $level = Level::Error;\n        $levelName = $level->getName();\n        $context = ['test' => 1];\n        $extra = ['tags' => ['web']];\n        $record = new SlackRecord(null, null, true, null, false, true);\n        $loggerRecord = $this->getRecord($level, 'test', $context);\n        $loggerRecord['extra'] = $extra;\n        $data = $record->getSlackData($loggerRecord);\n\n        $expectedFields = [\n            [\n                'title' => 'Level',\n                'value' => $levelName,\n                'short' => false,\n            ],\n            [\n                'title' => 'Tags',\n                'value' => \\sprintf('```%s```', json_encode($extra['tags'])),\n                'short' => false,\n            ],\n            [\n                'title' => 'Test',\n                'value' => $context['test'],\n                'short' => false,\n            ],\n        ];\n\n        $attachment = $data['attachments'][0];\n        $this->assertArrayHasKey('title', $attachment);\n        $this->assertArrayHasKey('fields', $attachment);\n        $this->assertCount(3, $attachment['fields']);\n        $this->assertSame('Message', $attachment['title']);\n        $this->assertSame(\n            $expectedFields,\n            $attachment['fields']\n        );\n    }\n\n    public function testAddsTimestampToAttachment()\n    {\n        $record = $this->getRecord();\n        $slackRecord = new SlackRecord();\n        $data = $slackRecord->getSlackData($this->getRecord());\n\n        $attachment = $data['attachments'][0];\n        $this->assertArrayHasKey('ts', $attachment);\n        $this->assertSame($record->datetime->getTimestamp(), $attachment['ts']);\n    }\n\n    public function testContextHasException()\n    {\n        $record = $this->getRecord(Level::Critical, 'This is a critical message.', ['exception' => new \\Exception()]);\n        $slackRecord = new SlackRecord(null, null, true, null, false, true);\n        $data = $slackRecord->getSlackData($record);\n        $this->assertIsString($data['attachments'][0]['fields'][1]['value']);\n    }\n\n    public function testExcludeExtraAndContextFields()\n    {\n        $record = $this->getRecord(\n            Level::Warning,\n            'test',\n            context: ['info' => ['library' => 'monolog', 'author' => 'Jordi']],\n            extra: ['tags' => ['web', 'cli']],\n        );\n\n        $slackRecord = new SlackRecord(null, null, true, null, false, true, ['context.info.library', 'extra.tags.1']);\n        $data = $slackRecord->getSlackData($record);\n        $attachment = $data['attachments'][0];\n\n        $expected = [\n            [\n                'title' => 'Info',\n                'value' => \\sprintf('```%s```', json_encode(['author' => 'Jordi'], JSON_PRETTY_PRINT)),\n                'short' => false,\n            ],\n            [\n                'title' => 'Tags',\n                'value' => \\sprintf('```%s```', json_encode(['web'])),\n                'short' => false,\n            ],\n        ];\n\n        foreach ($expected as $field) {\n            $this->assertNotFalse(array_search($field, $attachment['fields']));\n            break;\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/SlackHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Handler\\Slack\\SlackRecord;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\n/**\n * @author Greg Kedzierski <greg@gregkedzierski.com>\n * @see    https://api.slack.com/\n */\nclass SlackHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @var resource\n     */\n    private $res;\n\n    private SlackHandler $handler;\n\n    public function setUp(): void\n    {\n        if (!\\extension_loaded('openssl')) {\n            $this->markTestSkipped('This test requires openssl to run');\n        }\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->res);\n        unset($this->handler);\n    }\n\n    public function testWriteHeader()\n    {\n        $this->createHandler();\n        $this->handler->handle($this->getRecord(Level::Critical, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('{POST /api/chat.postMessage HTTP/1.1\\\\r\\\\nHost: slack.com\\\\r\\\\nContent-Type: application/x-www-form-urlencoded\\\\r\\\\nContent-Length: \\d{2,4}\\\\r\\\\n\\\\r\\\\n}', $content);\n    }\n\n    public function testWriteContent()\n    {\n        $this->createHandler();\n        $this->handler->handle($this->getRecord(Level::Critical, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/username=Monolog/', $content);\n        $this->assertMatchesRegularExpression('/channel=channel1/', $content);\n        $this->assertMatchesRegularExpression('/token=myToken/', $content);\n        $this->assertMatchesRegularExpression('/attachments/', $content);\n    }\n\n    public function testWriteContentUsesFormatterIfProvided()\n    {\n        $this->createHandler('myToken', 'channel1', 'Monolog', false);\n        $this->handler->handle($this->getRecord(Level::Critical, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->createHandler('myToken', 'channel1', 'Monolog', false);\n        $this->handler->setFormatter(new LineFormatter('foo--%message%'));\n        $this->handler->handle($this->getRecord(Level::Critical, 'test2'));\n        fseek($this->res, 0);\n        $content2 = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/text=test1/', $content);\n        $this->assertMatchesRegularExpression('/text=foo--test2/', $content2);\n    }\n\n    public function testWriteContentWithEmoji()\n    {\n        $this->createHandler('myToken', 'channel1', 'Monolog', true, 'alien');\n        $this->handler->handle($this->getRecord(Level::Critical, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/icon_emoji=%3Aalien%3A/', $content);\n    }\n\n    #[DataProvider('provideLevelColors')]\n    public function testWriteContentWithColors($level, $expectedColor)\n    {\n        $this->createHandler();\n        $this->handler->handle($this->getRecord($level, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/%22color%22%3A%22'.$expectedColor.'/', $content);\n    }\n\n    public function testWriteContentWithPlainTextMessage()\n    {\n        $this->createHandler('myToken', 'channel1', 'Monolog', false);\n        $this->handler->handle($this->getRecord(Level::Critical, 'test1'));\n        fseek($this->res, 0);\n        $content = fread($this->res, 1024);\n\n        $this->assertMatchesRegularExpression('/text=test1/', $content);\n    }\n\n    public static function provideLevelColors()\n    {\n        return [\n            [Level::Debug,    urlencode(SlackRecord::COLOR_DEFAULT)],\n            [Level::Info,     SlackRecord::COLOR_GOOD],\n            [Level::Notice,   SlackRecord::COLOR_GOOD],\n            [Level::Warning,  SlackRecord::COLOR_WARNING],\n            [Level::Error,    SlackRecord::COLOR_DANGER],\n            [Level::Critical, SlackRecord::COLOR_DANGER],\n            [Level::Alert,    SlackRecord::COLOR_DANGER],\n            [Level::Emergency,SlackRecord::COLOR_DANGER],\n        ];\n    }\n\n    private function createHandler($token = 'myToken', $channel = 'channel1', $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeExtra = false)\n    {\n        $constructorArgs = [$token, $channel, $username, $useAttachment, $iconEmoji, Level::Debug, true, $useShortAttachment, $includeExtra];\n        $this->res = fopen('php://memory', 'a');\n        $this->handler = $this->getMockBuilder('Monolog\\Handler\\SlackHandler')\n            ->setConstructorArgs($constructorArgs)\n            ->onlyMethods(['fsockopen', 'streamSetTimeout', 'closeSocket'])\n            ->getMock();\n\n        $reflectionProperty = new \\ReflectionProperty('Monolog\\Handler\\SocketHandler', 'connectionString');\n        $reflectionProperty->setValue($this->handler, 'localhost:1234');\n\n        $this->handler->expects($this->any())\n            ->method('fsockopen')\n            ->willReturn($this->res);\n        $this->handler->expects($this->any())\n            ->method('streamSetTimeout')\n            ->willReturn(true);\n        $this->handler->expects($this->any())\n            ->method('closeSocket');\n\n        $this->handler->setFormatter($this->getIdentityFormatter());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/SlackWebhookHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Handler\\Slack\\SlackRecord;\n\n/**\n * @author Haralan Dobrev <hkdobrev@gmail.com>\n * @see    https://api.slack.com/incoming-webhooks\n * @coversDefaultClass Monolog\\Handler\\SlackWebhookHandler\n */\nclass SlackWebhookHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    const WEBHOOK_URL = 'https://hooks.slack.com/services/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E';\n\n    /**\n     * @covers ::__construct\n     * @covers ::getSlackRecord\n     */\n    public function testConstructorMinimal()\n    {\n        $handler = new SlackWebhookHandler(self::WEBHOOK_URL);\n        $record = $this->getRecord();\n        $slackRecord = $handler->getSlackRecord();\n        $this->assertInstanceOf('Monolog\\Handler\\Slack\\SlackRecord', $slackRecord);\n        $this->assertEquals([\n            'attachments' => [\n                [\n                    'fallback' => 'test',\n                    'text' => 'test',\n                    'color' => SlackRecord::COLOR_WARNING,\n                    'fields' => [\n                        [\n                            'title' => 'Level',\n                            'value' => 'WARNING',\n                            'short' => false,\n                        ],\n                    ],\n                    'title' => 'Message',\n                    'mrkdwn_in' => ['fields'],\n                    'ts' => $record->datetime->getTimestamp(),\n                    'footer' => null,\n                    'footer_icon' => null,\n                ],\n            ],\n        ], $slackRecord->getSlackData($record));\n    }\n\n    /**\n     * @covers ::__construct\n     * @covers ::getSlackRecord\n     */\n    public function testConstructorFull()\n    {\n        $handler = new SlackWebhookHandler(\n            self::WEBHOOK_URL,\n            'test-channel',\n            'test-username',\n            false,\n            ':ghost:',\n            false,\n            false,\n            Level::Debug,\n            false\n        );\n\n        $slackRecord = $handler->getSlackRecord();\n        $this->assertInstanceOf('Monolog\\Handler\\Slack\\SlackRecord', $slackRecord);\n        $this->assertEquals([\n            'username' => 'test-username',\n            'text' => 'test',\n            'channel' => 'test-channel',\n            'icon_emoji' => ':ghost:',\n        ], $slackRecord->getSlackData($this->getRecord()));\n    }\n\n    /**\n     * @covers ::__construct\n     * @covers ::getSlackRecord\n     */\n    public function testConstructorFullWithAttachment()\n    {\n        $handler = new SlackWebhookHandler(\n            self::WEBHOOK_URL,\n            'test-channel-with-attachment',\n            'test-username-with-attachment',\n            true,\n            'https://www.example.com/example.png',\n            false,\n            false,\n            Level::Debug,\n            false\n        );\n\n        $record = $this->getRecord();\n        $slackRecord = $handler->getSlackRecord();\n        $this->assertInstanceOf('Monolog\\Handler\\Slack\\SlackRecord', $slackRecord);\n        $this->assertEquals([\n            'username' => 'test-username-with-attachment',\n            'channel' => 'test-channel-with-attachment',\n            'attachments' => [\n                [\n                    'fallback' => 'test',\n                    'text' => 'test',\n                    'color' => SlackRecord::COLOR_WARNING,\n                    'fields' => [\n                        [\n                            'title' => 'Level',\n                            'value' => Level::Warning->getName(),\n                            'short' => false,\n                        ],\n                    ],\n                    'mrkdwn_in' => ['fields'],\n                    'ts' => $record['datetime']->getTimestamp(),\n                    'footer' => 'test-username-with-attachment',\n                    'footer_icon' => 'https://www.example.com/example.png',\n                    'title' => 'Message',\n                ],\n            ],\n            'icon_url' => 'https://www.example.com/example.png',\n        ], $slackRecord->getSlackData($record));\n    }\n\n    /**\n     * @covers ::getFormatter\n     */\n    public function testGetFormatter()\n    {\n        $handler = new SlackWebhookHandler(self::WEBHOOK_URL);\n        $formatter = $handler->getFormatter();\n        $this->assertInstanceOf('Monolog\\Formatter\\FormatterInterface', $formatter);\n    }\n\n    /**\n     * @covers ::setFormatter\n     */\n    public function testSetFormatter()\n    {\n        $handler = new SlackWebhookHandler(self::WEBHOOK_URL);\n        $formatter = new LineFormatter();\n        $handler->setFormatter($formatter);\n        $this->assertSame($formatter, $handler->getFormatter());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/SocketHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\n\n/**\n * @author Pablo de Leon Belloc <pablolb@gmail.com>\n */\nclass SocketHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    private SocketHandler&MockObject $handler;\n\n    /**\n     * @var resource\n     */\n    private $res;\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->res);\n        unset($this->handler);\n    }\n\n    public function testInvalidHostname()\n    {\n        $this->expectException(\\UnexpectedValueException::class);\n\n        $handler = $this->createHandler('garbage://here');\n        $handler->handle($this->getRecord(Level::Warning, 'data'));\n    }\n\n    public function testBadConnectionTimeout()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        $handler = $this->createHandler('localhost:1234');\n        $handler->setConnectionTimeout(-1);\n    }\n\n    public function testSetConnectionTimeout()\n    {\n        $handler = $this->createHandler('localhost:1234');\n        $handler->setConnectionTimeout(10.1);\n        $this->assertEquals(10.1, $handler->getConnectionTimeout());\n    }\n\n    public function testBadTimeout()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        $handler = $this->createHandler('localhost:1234');\n        $handler->setTimeout(-1);\n    }\n\n    public function testSetTimeout()\n    {\n        $handler = $this->createHandler('localhost:1234');\n        $handler->setTimeout(10.25);\n        $this->assertEquals(10.25, $handler->getTimeout());\n    }\n\n    public function testSetWritingTimeout()\n    {\n        $handler = $this->createHandler('localhost:1234');\n        $handler->setWritingTimeout(10.25);\n        $this->assertEquals(10.25, $handler->getWritingTimeout());\n    }\n\n    public function testSetChunkSize()\n    {\n        $handler = $this->createHandler('localhost:1234');\n        $handler->setChunkSize(1025);\n        $this->assertEquals(1025, $handler->getChunkSize());\n    }\n\n    public function testSetConnectionString()\n    {\n        $handler = $this->createHandler('tcp://localhost:9090');\n        $this->assertEquals('tcp://localhost:9090', $handler->getConnectionString());\n    }\n\n    public function testExceptionIsThrownOnFsockopenError()\n    {\n        $this->setMockHandler(['fsockopen']);\n        $this->handler->expects($this->once())\n            ->method('fsockopen')\n            ->willReturn(false);\n\n        $this->expectException(\\UnexpectedValueException::class);\n\n        $this->writeRecord('Hello world');\n    }\n\n    public function testExceptionIsThrownOnPfsockopenError()\n    {\n        $this->setMockHandler(['pfsockopen']);\n        $this->handler->expects($this->once())\n            ->method('pfsockopen')\n            ->willReturn(false);\n\n        $this->handler->setPersistent(true);\n\n        $this->expectException(\\UnexpectedValueException::class);\n\n        $this->writeRecord('Hello world');\n    }\n\n    public function testExceptionIsThrownIfCannotSetTimeout()\n    {\n        $this->setMockHandler(['streamSetTimeout']);\n        $this->handler->expects($this->once())\n            ->method('streamSetTimeout')\n            ->willReturn(false);\n\n        $this->expectException(\\UnexpectedValueException::class);\n\n        $this->writeRecord('Hello world');\n    }\n\n    public function testExceptionIsThrownIfCannotSetChunkSize()\n    {\n        $this->setMockHandler(['streamSetChunkSize']);\n        $this->handler->setChunkSize(8192);\n        $this->handler->expects($this->once())\n            ->method('streamSetChunkSize')\n            ->willReturn(false);\n\n        $this->expectException(\\UnexpectedValueException::class);\n\n        $this->writeRecord('Hello world');\n    }\n\n    public function testWriteFailsOnIfFwriteReturnsFalse()\n    {\n        $this->setMockHandler(['fwrite']);\n\n        $callback = function ($arg) {\n            $map = [\n                'Hello world' => 6,\n                'world' => false,\n            ];\n\n            return $map[$arg];\n        };\n\n        $this->handler->expects($this->exactly(2))\n            ->method('fwrite')\n            ->willReturnCallback($callback);\n\n        $this->expectException(\\RuntimeException::class);\n\n        $this->writeRecord('Hello world');\n    }\n\n    public function testWriteFailsIfStreamTimesOut()\n    {\n        $this->setMockHandler(['fwrite', 'streamGetMetadata']);\n\n        $callback = function ($arg) {\n            $map = [\n                'Hello world' => 6,\n                'world' => 5,\n            ];\n\n            return $map[$arg];\n        };\n\n        $this->handler->expects($this->exactly(1))\n            ->method('fwrite')\n            ->willReturnCallback($callback);\n        $this->handler->expects($this->exactly(1))\n            ->method('streamGetMetadata')\n            ->willReturn(['timed_out' => true]);\n\n        $this->expectException(\\RuntimeException::class);\n\n        $this->writeRecord('Hello world');\n    }\n\n    public function testWriteFailsOnIncompleteWrite()\n    {\n        $this->setMockHandler(['fwrite', 'streamGetMetadata']);\n\n        $res = $this->res;\n        $callback = function ($string) use ($res) {\n            fclose($res);\n\n            return \\strlen('Hello');\n        };\n\n        $this->handler->expects($this->exactly(1))\n            ->method('fwrite')\n            ->willReturnCallback($callback);\n        $this->handler->expects($this->exactly(1))\n            ->method('streamGetMetadata')\n            ->willReturn(['timed_out' => false]);\n\n        $this->expectException(\\RuntimeException::class);\n\n        $this->writeRecord('Hello world');\n    }\n\n    public function testWriteWithMemoryFile()\n    {\n        $this->setMockHandler();\n        $this->writeRecord('test1');\n        $this->writeRecord('test2');\n        $this->writeRecord('test3');\n        fseek($this->res, 0);\n        $this->assertEquals('test1test2test3', fread($this->res, 1024));\n    }\n\n    public function testWriteWithMock()\n    {\n        $this->setMockHandler(['fwrite']);\n\n        $callback = function ($arg) {\n            $map = [\n                'Hello world' => 6,\n                'world' => 5,\n            ];\n\n            return $map[$arg];\n        };\n\n        $this->handler->expects($this->exactly(2))\n            ->method('fwrite')\n            ->willReturnCallback($callback);\n\n        $this->writeRecord('Hello world');\n    }\n\n    public function testClose()\n    {\n        $this->setMockHandler();\n        $this->writeRecord('Hello world');\n        $this->assertIsResource($this->res);\n        $this->handler->close();\n        $this->assertFalse(\\is_resource($this->res), \"Expected resource to be closed after closing handler\");\n    }\n\n    public function testCloseDoesNotClosePersistentSocket()\n    {\n        $this->setMockHandler();\n        $this->handler->setPersistent(true);\n        $this->writeRecord('Hello world');\n        $this->assertTrue(\\is_resource($this->res));\n        $this->handler->close();\n        $this->assertTrue(\\is_resource($this->res));\n    }\n\n    public function testAvoidInfiniteLoopWhenNoDataIsWrittenForAWritingTimeoutSeconds()\n    {\n        $this->setMockHandler(['fwrite', 'streamGetMetadata']);\n\n        $this->handler->expects($this->any())\n            ->method('fwrite')\n            ->willReturn(0);\n\n        $this->handler->expects($this->any())\n            ->method('streamGetMetadata')\n            ->willReturn(['timed_out' => false]);\n\n        $this->handler->setWritingTimeout(1);\n\n        $this->expectException(\\RuntimeException::class);\n\n        $this->writeRecord('Hello world');\n    }\n\n    private function createHandler(string $connectionString): SocketHandler\n    {\n        $handler = new SocketHandler($connectionString);\n        $handler->setFormatter($this->getIdentityFormatter());\n\n        return $handler;\n    }\n\n    private function writeRecord($string)\n    {\n        $this->handler->handle($this->getRecord(Level::Warning, $string));\n    }\n\n    private function setMockHandler(array $methods = [])\n    {\n        $this->res = fopen('php://memory', 'a');\n\n        $defaultMethods = ['fsockopen', 'pfsockopen', 'streamSetTimeout', 'streamSetChunkSize'];\n        $newMethods = array_diff($methods, $defaultMethods);\n\n        $finalMethods = array_merge($defaultMethods, $newMethods);\n\n        $this->handler = $this->getMockBuilder('Monolog\\Handler\\SocketHandler')\n            ->onlyMethods($finalMethods)\n            ->setConstructorArgs(['localhost:1234'])\n            ->getMock();\n\n        if (!\\in_array('fsockopen', $methods)) {\n            $this->handler->expects($this->any())\n                ->method('fsockopen')\n                ->willReturn($this->res);\n        }\n\n        if (!\\in_array('pfsockopen', $methods)) {\n            $this->handler->expects($this->any())\n                ->method('pfsockopen')\n                ->willReturn($this->res);\n        }\n\n        if (!\\in_array('streamSetTimeout', $methods)) {\n            $this->handler->expects($this->any())\n                ->method('streamSetTimeout')\n                ->willReturn(true);\n        }\n\n        if (!\\in_array('streamSetChunkSize', $methods)) {\n            $this->handler->expects($this->any())\n                ->method('streamSetChunkSize')\n                ->willReturn(8192);\n        }\n\n        $this->handler->setFormatter($this->getIdentityFormatter());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/StreamHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass StreamHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        @unlink(__DIR__.'/test.log');\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::__construct\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    public function testWrite()\n    {\n        $handle = fopen('php://memory', 'a+');\n        $handler = new StreamHandler($handle);\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->handle($this->getRecord(Level::Warning, 'test'));\n        $handler->handle($this->getRecord(Level::Warning, 'test2'));\n        $handler->handle($this->getRecord(Level::Warning, 'test3'));\n        fseek($handle, 0);\n        $this->assertEquals('testtest2test3', fread($handle, 100));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::close\n     */\n    public function testCloseKeepsExternalHandlersOpen()\n    {\n        $handle = fopen('php://memory', 'a+');\n        $handler = new StreamHandler($handle);\n        $this->assertTrue(\\is_resource($handle));\n        $handler->close();\n        $this->assertTrue(\\is_resource($handle));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::close\n     */\n    public function testClose()\n    {\n        $handler = new StreamHandler('php://memory');\n        $handler->handle($this->getRecord(Level::Warning, 'test'));\n        $stream = $handler->getStream();\n\n        $this->assertTrue(\\is_resource($stream));\n        $handler->close();\n        $this->assertFalse(\\is_resource($stream));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::close\n     * @covers Monolog\\Handler\\Handler::__serialize\n     */\n    public function testSerialization()\n    {\n        $handler = new StreamHandler('php://memory');\n        $handler->handle($this->getRecord(Level::Warning, 'testfoo'));\n        $stream = $handler->getStream();\n\n        $this->assertTrue(\\is_resource($stream));\n        fseek($stream, 0);\n        $this->assertStringContainsString('testfoo', stream_get_contents($stream));\n        $serialized = serialize($handler);\n        $this->assertFalse(\\is_resource($stream));\n\n        $handler = unserialize($serialized);\n        $handler->handle($this->getRecord(Level::Warning, 'testbar'));\n        $stream = $handler->getStream();\n\n        $this->assertTrue(\\is_resource($stream));\n        fseek($stream, 0);\n        $contents = stream_get_contents($stream);\n        $this->assertStringNotContainsString('testfoo', $contents);\n        $this->assertStringContainsString('testbar', $contents);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    public function testWriteCreatesTheStreamResource()\n    {\n        $handler = new StreamHandler('php://memory');\n        $handler->handle($this->getRecord());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::__construct\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    public function testWriteLocking()\n    {\n        $temp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'monolog_locked_log';\n        $handler = new StreamHandler($temp, Level::Debug, true, null, true);\n        $handler->handle($this->getRecord());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::__construct\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    public function testWriteMissingResource()\n    {\n        $this->expectException(\\LogicException::class);\n\n        $handler = new StreamHandler(null);\n        $handler->handle($this->getRecord());\n    }\n\n    public static function invalidArgumentProvider()\n    {\n        return [\n            [1],\n            [[]],\n            [['bogus://url']],\n        ];\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::__construct\n     */\n    #[DataProvider('invalidArgumentProvider')]\n    public function testWriteInvalidArgument($invalidArgument)\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        $handler = new StreamHandler($invalidArgument);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::__construct\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    public function testWriteInvalidResource()\n    {\n        $this->expectException(\\UnexpectedValueException::class);\n        $php7xMessage = <<<STRING\nThe stream or file \"bogus://url\" could not be opened in append mode: failed to open stream: No such file or directory\nThe exception occurred while attempting to log: test\nContext: {\"foo\":\"bar\"}\nExtra: [1,2,3]\nSTRING;\n\n        $php8xMessage = <<<STRING\nThe stream or file \"bogus://url\" could not be opened in append mode: Failed to open stream: No such file or directory\nThe exception occurred while attempting to log: test\nContext: {\"foo\":\"bar\"}\nExtra: [1,2,3]\nSTRING;\n\n        $phpVersionString = phpversion();\n        $phpVersionComponents = explode('.', $phpVersionString);\n        $majorVersion = (int) $phpVersionComponents[0];\n\n        $this->expectExceptionMessage(($majorVersion >= 8) ? $php8xMessage : $php7xMessage);\n\n        $handler = new StreamHandler('bogus://url');\n        $record = $this->getRecord(\n            context: ['foo' => 'bar'],\n            extra: [1, 2, 3],\n        );\n        $handler->handle($record);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::__construct\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    public function testWriteNonExistingResource()\n    {\n        $this->expectException(\\UnexpectedValueException::class);\n\n        $handler = new StreamHandler('ftp://foo/bar/baz/'.rand(0, 10000));\n        $handler->handle($this->getRecord());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::__construct\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    public function testWriteNonExistingPath()\n    {\n        $handler = new StreamHandler(sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000));\n        $handler->handle($this->getRecord());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::__construct\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    public function testWriteNonExistingFileResource()\n    {\n        $handler = new StreamHandler('file://'.sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000));\n        $handler->handle($this->getRecord());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    public function testWriteErrorDuringWriteRetriesWithClose()\n    {\n        $handler = $this->getMockBuilder(StreamHandler::class)\n            ->onlyMethods(['streamWrite'])\n            ->setConstructorArgs(['file://'.sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)])\n            ->getMock();\n\n        $refs = [];\n        $handler->expects($this->exactly(2))\n            ->method('streamWrite')\n            ->willReturnCallback(function ($stream) use (&$refs) {\n                $refs[] = $stream;\n                if (\\count($refs) === 2) {\n                    self::assertNotSame($stream, $refs[0]);\n                }\n                if (\\count($refs) === 1) {\n                    trigger_error('fwrite(): Write of 378 bytes failed with errno=32 Broken pipe', E_USER_ERROR);\n                }\n            });\n\n        $handler->handle($this->getRecord());\n        if (method_exists($this, 'assertIsClosedResource')) {\n            self::assertIsClosedResource($refs[0]);\n            self::assertIsResource($refs[1]);\n        }\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    public function testWriteErrorDuringWriteRetriesButThrowsIfStillFails()\n    {\n        $handler = $this->getMockBuilder(StreamHandler::class)\n            ->onlyMethods(['streamWrite'])\n            ->setConstructorArgs(['file://'.sys_get_temp_dir().'/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000)])\n            ->getMock();\n\n        $refs = [];\n        $handler->expects($this->exactly(2))\n            ->method('streamWrite')\n            ->willReturnCallback(function ($stream) use (&$refs) {\n                $refs[] = $stream;\n                if (\\count($refs) === 2) {\n                    self::assertNotSame($stream, $refs[0]);\n                }\n                trigger_error('fwrite(): Write of 378 bytes failed with errno=32 Broken pipe', E_USER_ERROR);\n            });\n\n        self::expectException(\\UnexpectedValueException::class);\n        self::expectExceptionMessage('Writing to the log file failed: Write of 378 bytes failed with errno=32 Broken pipe\nThe exception occurred while attempting to log: test');\n        $handler->handle($this->getRecord());\n    }\n\n    /**\n     * @covers Monolog\\Handler\\StreamHandler::__construct\n     * @covers Monolog\\Handler\\StreamHandler::write\n     */\n    #[DataProvider('provideNonExistingAndNotCreatablePath')]\n    public function testWriteNonExistingAndNotCreatablePath($nonExistingAndNotCreatablePath)\n    {\n        if (\\defined('PHP_WINDOWS_VERSION_BUILD')) {\n            $this->markTestSkipped('Permissions checks can not run on windows');\n        }\n\n        $handler = null;\n\n        try {\n            $handler = new StreamHandler($nonExistingAndNotCreatablePath);\n        } catch (\\Exception $fail) {\n            $this->fail(\n                'A non-existing and not creatable path should throw an Exception earliest on first write.\n                 Not during instantiation.'\n            );\n        }\n\n        $this->expectException(\\UnexpectedValueException::class);\n        $this->expectExceptionMessage('There is no existing directory at');\n\n        $handler->handle($this->getRecord());\n    }\n\n    public static function provideNonExistingAndNotCreatablePath()\n    {\n        return [\n            '/foo/bar/…' => [\n                '/foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000),\n            ],\n            'file:///foo/bar/…' => [\n                'file:///foo/bar/'.rand(0, 10000).DIRECTORY_SEPARATOR.rand(0, 10000),\n            ],\n        ];\n    }\n\n    public static function provideMemoryValues()\n    {\n        return [\n            ['1M', (int) (1024*1024/10)],\n            ['10M', (int) (1024*1024)],\n            ['1024M', (int) (1024*1024*1024/10)],\n            ['1G', (int) (1024*1024*1024/10)],\n            ['2000M', (int) (2000*1024*1024/10)],\n            ['2050M', (int) (2050*1024*1024/10)],\n            ['2048M', (int) (2048*1024*1024/10)],\n            ['3G', (int) (3*1024*1024*1024/10)],\n            ['2560M', (int) (2560*1024*1024/10)],\n        ];\n    }\n\n    #[DataProvider('provideMemoryValues')]\n    public function testPreventOOMError($phpMemory, $expectedChunkSize): void\n    {\n        $previousValue = @ini_set('memory_limit', $phpMemory);\n\n        if ($previousValue === false) {\n            $this->markTestSkipped('We could not set a memory limit that would trigger the error.');\n        }\n\n        try {\n            $stream = tmpfile();\n\n            if ($stream === false) {\n                $this->markTestSkipped('We could not create a temp file to be use as a stream.');\n            }\n\n            $handler = new StreamHandler($stream);\n            stream_get_contents($stream, 1024);\n\n            $this->assertEquals($expectedChunkSize, $handler->getStreamChunkSize());\n        } finally {\n            ini_set('memory_limit', $previousValue);\n        }\n    }\n\n    public function testSimpleOOMPrevention(): void\n    {\n        $previousValue = ini_set('memory_limit', '2048M');\n\n        if ($previousValue === false) {\n            $this->markTestSkipped('We could not set a memory limit that would trigger the error.');\n        }\n\n        try {\n            $stream = tmpfile();\n            new StreamHandler($stream);\n            stream_get_contents($stream);\n            $this->assertTrue(true);\n        } finally {\n            ini_set('memory_limit', $previousValue);\n        }\n    }\n\n    public function testReopensFileIfInodeChanges()\n    {\n        $filename = __DIR__ . '/test.log';\n        $handler = new StreamHandler($filename);\n        $handler->setFormatter($this->getIdentityFormatter());\n        $handler->handle($this->getRecord(Level::Warning, 'test1'));\n        @unlink($filename);\n        $handler->handle($this->getRecord(Level::Warning, 'test2'));\n        $data = @file_get_contents($filename);\n        $this->assertEquals('test2', $data);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/SymfonyMailerHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Logger;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Symfony\\Component\\Mailer\\MailerInterface;\nuse Symfony\\Component\\Mime\\Email;\n\nclass SymfonyMailerHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /** @var MailerInterface&MockObject */\n    private $mailer;\n\n    public function setUp(): void\n    {\n        $this->mailer = $this\n            ->getMockBuilder(MailerInterface::class)\n            ->disableOriginalConstructor()\n            ->getMock();\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->mailer);\n    }\n\n    public function testMessageCreationIsLazyWhenUsingCallback()\n    {\n        $this->mailer->expects($this->never())\n            ->method('send');\n\n        $callback = function () {\n            throw new \\RuntimeException('Email creation callback should not have been called in this test');\n        };\n        $handler = new SymfonyMailerHandler($this->mailer, $callback);\n\n        $records = [\n            $this->getRecord(Logger::DEBUG),\n            $this->getRecord(Logger::INFO),\n        ];\n        $handler->handleBatch($records);\n    }\n\n    public function testMessageCanBeCustomizedGivenLoggedData()\n    {\n        // Wire Mailer to expect a specific Email with a customized Subject\n        $expectedMessage = new Email();\n        $this->mailer->expects($this->once())\n            ->method('send')\n            ->with($this->callback(function ($value) use ($expectedMessage) {\n                return $value instanceof Email\n                    && $value->getSubject() === 'Emergency'\n                    && $value === $expectedMessage;\n            }));\n\n        // Callback dynamically changes subject based on number of logged records\n        $callback = function ($content, array $records) use ($expectedMessage) {\n            $subject = \\count($records) > 0 ? 'Emergency' : 'Normal';\n\n            return $expectedMessage->subject($subject);\n        };\n        $handler = new SymfonyMailerHandler($this->mailer, $callback);\n\n        // Logging 1 record makes this an Emergency\n        $records = [\n            $this->getRecord(Logger::EMERGENCY),\n        ];\n        $handler->handleBatch($records);\n    }\n\n    public function testMessageSubjectFormatting()\n    {\n        // Wire Mailer to expect a specific Email with a customized Subject\n        $messageTemplate = new Email();\n        $messageTemplate->subject('Alert: %level_name% %message%');\n        $receivedMessage = null;\n\n        $this->mailer->expects($this->once())\n            ->method('send')\n            ->with($this->callback(function ($value) use (&$receivedMessage) {\n                $receivedMessage = $value;\n\n                return true;\n            }));\n\n        $handler = new SymfonyMailerHandler($this->mailer, $messageTemplate);\n\n        $records = [\n            $this->getRecord(Logger::EMERGENCY),\n        ];\n        $handler->handleBatch($records);\n\n        $this->assertEquals('Alert: EMERGENCY test', $receivedMessage->getSubject());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/SyslogHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\nclass SyslogHandlerTest extends \\PHPUnit\\Framework\\TestCase\n{\n    /**\n     * @covers Monolog\\Handler\\SyslogHandler::__construct\n     */\n    public function testConstruct()\n    {\n        $handler = new SyslogHandler('test');\n        $this->assertInstanceOf('Monolog\\Handler\\SyslogHandler', $handler);\n\n        $handler = new SyslogHandler('test', LOG_USER);\n        $this->assertInstanceOf('Monolog\\Handler\\SyslogHandler', $handler);\n\n        $handler = new SyslogHandler('test', 'user');\n        $this->assertInstanceOf('Monolog\\Handler\\SyslogHandler', $handler);\n\n        $handler = new SyslogHandler('test', LOG_USER, Level::Debug, true, LOG_PERROR);\n        $this->assertInstanceOf('Monolog\\Handler\\SyslogHandler', $handler);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\SyslogHandler::__construct\n     */\n    public function testConstructInvalidFacility()\n    {\n        $this->expectException(\\UnexpectedValueException::class);\n        $handler = new SyslogHandler('test', 'unknown');\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/SyslogUdpHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\n\n/**\n * @requires extension sockets\n */\nclass SyslogUdpHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testWeValidateFacilities()\n    {\n        $this->expectException(\\UnexpectedValueException::class);\n\n        $handler = new SyslogUdpHandler(\"ip\", 514, \"invalidFacility\");\n    }\n\n    public function testWeSplitIntoLines()\n    {\n        $pid = getmypid();\n        $host = gethostname();\n\n        $handler = new \\Monolog\\Handler\\SyslogUdpHandler(\"127.0.0.1\", 514, \"authpriv\");\n        $handler->setFormatter(new \\Monolog\\Formatter\\ChromePHPFormatter());\n\n        $time = '2014-01-07T12:34:56+00:00';\n        $socket = $this->getMockBuilder('Monolog\\Handler\\SyslogUdp\\UdpSocket')\n            ->onlyMethods(['write'])\n            ->setConstructorArgs(['lol'])\n            ->getMock();\n\n        $matcher = $this->atLeast(2);\n\n        $socket->expects($matcher)\n            ->method('write')\n            ->willReturnCallback(function () use ($matcher, $time, $host, $pid) {\n                match ($matcher->numberOfInvocations()) {\n                    1 => $this->equalTo(\"lol\") && $this->equalTo(\"<\".(LOG_AUTHPRIV + LOG_WARNING).\">1 $time $host php $pid - - \"),\n                    2 => $this->equalTo(\"hej\") && $this->equalTo(\"<\".(LOG_AUTHPRIV + LOG_WARNING).\">1 $time $host php $pid - - \"),\n                    default => $this->assertTrue(true)\n                };\n            });\n\n        $handler->setSocket($socket);\n\n        $handler->handle($this->getRecordWithMessage(\"hej\\nlol\"));\n    }\n\n    public function testSplitWorksOnEmptyMsg()\n    {\n        $handler = new SyslogUdpHandler(\"127.0.0.1\", 514, \"authpriv\");\n        $handler->setFormatter($this->getIdentityFormatter());\n\n        $socket = $this->getMockBuilder('Monolog\\Handler\\SyslogUdp\\UdpSocket')\n            ->onlyMethods(['write'])\n            ->setConstructorArgs(['lol'])\n            ->getMock();\n        $socket->expects($this->never())\n            ->method('write');\n\n        $handler->setSocket($socket);\n\n        $handler->handle($this->getRecordWithMessage(''));\n    }\n\n    public function testRfc()\n    {\n        $time = 'Jan 07 12:34:56';\n        $pid = getmypid();\n        $host = gethostname();\n\n        $handler = $this->getMockBuilder('\\Monolog\\Handler\\SyslogUdpHandler')\n            ->setConstructorArgs([\"127.0.0.1\", 514, \"authpriv\", 'debug', true, \"php\", \\Monolog\\Handler\\SyslogUdpHandler::RFC3164])\n            ->onlyMethods([])\n            ->getMock();\n\n        $handler->setFormatter(new \\Monolog\\Formatter\\ChromePHPFormatter());\n\n        $socket = $this->getMockBuilder('\\Monolog\\Handler\\SyslogUdp\\UdpSocket')\n            ->setConstructorArgs(['lol', 999])\n            ->onlyMethods(['write'])\n            ->getMock();\n\n        $matcher = $this->atLeast(2);\n\n        $socket->expects($matcher)\n            ->method('write')\n            ->willReturnCallback(function () use ($matcher, $time, $host, $pid) {\n                match ($matcher->numberOfInvocations()) {\n                    1 => $this->equalTo(\"lol\") && $this->equalTo(\"<\".(LOG_AUTHPRIV + LOG_WARNING).\">$time $host php[$pid]: \"),\n                    2 => $this->equalTo(\"hej\") && $this->equalTo(\"<\".(LOG_AUTHPRIV + LOG_WARNING).\">$time $host php[$pid]: \"),\n                    default => $this->assertTrue(true)\n                };\n            });\n\n        $handler->setSocket($socket);\n\n        $handler->handle($this->getRecordWithMessage(\"hej\\nlol\"));\n    }\n\n    protected function getRecordWithMessage($msg)\n    {\n        return $this->getRecord(message: $msg, level: Level::Warning, channel: 'lol', datetime: new \\DateTimeImmutable('2014-01-07 12:34:56'));\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/TelegramBotHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\n\n/**\n * @author Mazur Alexandr <alexandrmazur96@gmail.com>\n * @link https://core.telegram.org/bots/api\n */\nclass TelegramBotHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    private TelegramBotHandler&MockObject $handler;\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->handler);\n    }\n\n    public function testSendTelegramRequest(): void\n    {\n        $this->createHandler();\n        $this->handler->handle($this->getRecord());\n    }\n\n    private function createHandler(\n        string $apiKey = 'testKey',\n        string $channel = 'testChannel',\n        string $parseMode = 'Markdown',\n        bool $disableWebPagePreview = false,\n        bool $disableNotification = true,\n        int $topic = 1\n    ): void {\n        $constructorArgs = [$apiKey, $channel, Level::Debug, true, $parseMode, $disableWebPagePreview, $disableNotification, $topic];\n\n        $this->handler = $this->getMockBuilder(TelegramBotHandler::class)\n            ->setConstructorArgs($constructorArgs)\n            ->onlyMethods(['send'])\n            ->getMock();\n\n        $this->handler->expects($this->atLeast(1))\n            ->method('send');\n    }\n\n    public function testSetInvalidParseMode(): void\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        $handler = new TelegramBotHandler('testKey', 'testChannel');\n        $handler->setParseMode('invalid parse mode');\n    }\n\n    public function testSetParseMode(): void\n    {\n        $handler = new TelegramBotHandler('testKey', 'testChannel');\n        $handler->setParseMode('HTML');\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/TestHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\n/**\n * @covers Monolog\\Handler\\TestHandler\n */\nclass TestHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    #[DataProvider('methodProvider')]\n    public function testHandler($method, Level $level)\n    {\n        $handler = new TestHandler;\n        $record = $this->getRecord($level, 'test'.$method);\n        $this->assertFalse($handler->hasRecords($level));\n        $this->assertFalse($handler->hasRecord($record->message, $level));\n        $this->assertFalse($handler->{'has'.$method}($record->message), 'has'.$method);\n        $this->assertFalse($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains');\n        $this->assertFalse($handler->{'has'.$method.'ThatPasses'}(function ($rec) {\n            return true;\n        }), 'has'.$method.'ThatPasses');\n        $this->assertFalse($handler->{'has'.$method.'ThatMatches'}('/test\\w+/'));\n        $this->assertFalse($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records');\n        $handler->handle($record);\n\n        $this->assertFalse($handler->{'has'.$method}('bar'), 'has'.$method);\n        $this->assertTrue($handler->hasRecords($level));\n        $this->assertTrue($handler->hasRecord($record->message, $level));\n        $this->assertTrue($handler->{'has'.$method}($record->message), 'has'.$method);\n        $this->assertTrue($handler->{'has'.$method}('test'.$method), 'has'.$method);\n        $this->assertTrue($handler->{'has'.$method.'ThatContains'}('test'), 'has'.$method.'ThatContains');\n        $this->assertTrue($handler->{'has'.$method.'ThatPasses'}(function ($rec) {\n            return true;\n        }), 'has'.$method.'ThatPasses');\n        $this->assertTrue($handler->{'has'.$method.'ThatMatches'}('/test\\w+/'));\n        $this->assertTrue($handler->{'has'.$method.'Records'}(), 'has'.$method.'Records');\n\n        $records = $handler->getRecords();\n        $records[0]->formatted = null;\n        $this->assertEquals([$record], $records);\n    }\n\n    public function testHandlerAssertEmptyContext()\n    {\n        $handler = new TestHandler;\n        $record  = $this->getRecord(Level::Warning, 'test', []);\n        $this->assertFalse($handler->hasWarning([\n            'message' => 'test',\n            'context' => [],\n        ]));\n\n        $handler->handle($record);\n\n        $this->assertTrue($handler->hasWarning([\n            'message' => 'test',\n            'context' => [],\n        ]));\n        $this->assertFalse($handler->hasWarning([\n            'message' => 'test',\n            'context' => [\n                'foo' => 'bar',\n            ],\n        ]));\n    }\n\n    public function testHandlerAssertNonEmptyContext()\n    {\n        $handler = new TestHandler;\n        $record  = $this->getRecord(Level::Warning, 'test', ['foo' => 'bar']);\n        $this->assertFalse($handler->hasWarning([\n            'message' => 'test',\n            'context' => [\n                'foo' => 'bar',\n            ],\n        ]));\n\n        $handler->handle($record);\n\n        $this->assertTrue($handler->hasWarning([\n            'message' => 'test',\n            'context' => [\n                'foo' => 'bar',\n            ],\n        ]));\n        $this->assertFalse($handler->hasWarning([\n            'message' => 'test',\n            'context' => [],\n        ]));\n    }\n\n    public static function methodProvider()\n    {\n        return [\n            ['Emergency', Level::Emergency],\n            ['Alert'    , Level::Alert],\n            ['Critical' , Level::Critical],\n            ['Error'    , Level::Error],\n            ['Warning'  , Level::Warning],\n            ['Info'     , Level::Info],\n            ['Notice'   , Level::Notice],\n            ['Debug'    , Level::Debug],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/UdpSocketTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\Handler\\SyslogUdp\\UdpSocket;\n\n/**\n * @requires extension sockets\n */\nclass UdpSocketTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testWeDoNotTruncateShortMessages()\n    {\n        $socket = $this->getMockBuilder('Monolog\\Handler\\SyslogUdp\\UdpSocket')\n            ->onlyMethods(['send'])\n            ->setConstructorArgs(['lol'])\n            ->getMock();\n\n        $socket\n            ->method('send')\n            ->with(\"HEADER: The quick brown fox jumps over the lazy dog\");\n\n        $socket->write(\"The quick brown fox jumps over the lazy dog\", \"HEADER: \");\n    }\n\n    public function testLongMessagesAreTruncated()\n    {\n        $socket = $this->getMockBuilder('Monolog\\Handler\\SyslogUdp\\UdpSocket')\n            ->onlyMethods(['send'])\n            ->setConstructorArgs(['lol'])\n            ->getMock();\n\n        $truncatedString = str_repeat(\"derp\", 16254).'d';\n\n        $socket->expects($this->exactly(1))\n            ->method('send')\n            ->with(\"HEADER\" . $truncatedString);\n\n        $longString = str_repeat(\"derp\", 20000);\n\n        $socket->write($longString, \"HEADER\");\n    }\n\n    public function testDoubleCloseDoesNotError()\n    {\n        $socket = new UdpSocket('127.0.0.1', 514);\n        $socket->close();\n        $socket->close();\n    }\n\n    public function testWriteAfterCloseReopened()\n    {\n        $socket = new UdpSocket('127.0.0.1', 514);\n        $socket->close();\n        $socket->write('foo', \"HEADER\");\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/WhatFailureGroupHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nuse Monolog\\LogRecord;\nuse Monolog\\Level;\n\nclass WhatFailureGroupHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Handler\\WhatFailureGroupHandler::__construct\n     */\n    public function testConstructorOnlyTakesHandler()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n\n        new WhatFailureGroupHandler([new TestHandler(), \"foo\"]);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\WhatFailureGroupHandler::__construct\n     * @covers Monolog\\Handler\\WhatFailureGroupHandler::handle\n     */\n    public function testHandle()\n    {\n        $testHandlers = [new TestHandler(), new TestHandler()];\n        $handler = new WhatFailureGroupHandler($testHandlers);\n        $handler->handle($this->getRecord(Level::Debug));\n        $handler->handle($this->getRecord(Level::Info));\n        foreach ($testHandlers as $test) {\n            $this->assertTrue($test->hasDebugRecords());\n            $this->assertTrue($test->hasInfoRecords());\n            $this->assertCount(2, $test->getRecords());\n        }\n    }\n\n    /**\n     * @covers Monolog\\Handler\\WhatFailureGroupHandler::handleBatch\n     */\n    public function testHandleBatch()\n    {\n        $testHandlers = [new TestHandler(), new TestHandler()];\n        $handler = new WhatFailureGroupHandler($testHandlers);\n        $handler->handleBatch([$this->getRecord(Level::Debug), $this->getRecord(Level::Info)]);\n        foreach ($testHandlers as $test) {\n            $this->assertTrue($test->hasDebugRecords());\n            $this->assertTrue($test->hasInfoRecords());\n            $this->assertCount(2, $test->getRecords());\n        }\n    }\n\n    /**\n     * @covers Monolog\\Handler\\WhatFailureGroupHandler::isHandling\n     */\n    public function testIsHandling()\n    {\n        $testHandlers = [new TestHandler(Level::Error), new TestHandler(Level::Warning)];\n        $handler = new WhatFailureGroupHandler($testHandlers);\n        $this->assertTrue($handler->isHandling($this->getRecord(Level::Error)));\n        $this->assertTrue($handler->isHandling($this->getRecord(Level::Warning)));\n        $this->assertFalse($handler->isHandling($this->getRecord(Level::Debug)));\n    }\n\n    /**\n     * @covers Monolog\\Handler\\WhatFailureGroupHandler::handle\n     */\n    public function testHandleUsesProcessors()\n    {\n        $test = new TestHandler();\n        $handler = new WhatFailureGroupHandler([$test]);\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo'] = true;\n\n            return $record;\n        });\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertTrue($test->hasWarningRecords());\n        $records = $test->getRecords();\n        $this->assertTrue($records[0]['extra']['foo']);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\WhatFailureGroupHandler::handleBatch\n     */\n    public function testHandleBatchUsesProcessors()\n    {\n        $testHandlers = [new TestHandler(), new TestHandler()];\n        $handler = new WhatFailureGroupHandler($testHandlers);\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo'] = true;\n\n            return $record;\n        });\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo2'] = true;\n\n            return $record;\n        });\n        $handler->handleBatch([$this->getRecord(Level::Debug), $this->getRecord(Level::Info)]);\n        foreach ($testHandlers as $test) {\n            $this->assertTrue($test->hasDebugRecords());\n            $this->assertTrue($test->hasInfoRecords());\n            $this->assertCount(2, $test->getRecords());\n            $records = $test->getRecords();\n            $this->assertTrue($records[0]['extra']['foo']);\n            $this->assertTrue($records[1]['extra']['foo']);\n            $this->assertTrue($records[0]['extra']['foo2']);\n            $this->assertTrue($records[1]['extra']['foo2']);\n        }\n    }\n\n    /**\n     * @covers Monolog\\Handler\\WhatFailureGroupHandler::handle\n     */\n    public function testHandleException()\n    {\n        $test = new TestHandler();\n        $exception = new ExceptionTestHandler();\n        $handler = new WhatFailureGroupHandler([$exception, $test, $exception]);\n        $handler->pushProcessor(function ($record) {\n            $record->extra['foo'] = true;\n\n            return $record;\n        });\n        $handler->handle($this->getRecord(Level::Warning));\n        $this->assertTrue($test->hasWarningRecords());\n        $records = $test->getRecords();\n        $this->assertTrue($records[0]['extra']['foo']);\n    }\n\n    public function testProcessorsDoNotInterfereBetweenHandlers()\n    {\n        $t1 = new TestHandler();\n        $t2 = new TestHandler();\n        $handler = new WhatFailureGroupHandler([$t1, $t2]);\n\n        $t1->pushProcessor(function (LogRecord $record) {\n            $record->extra['foo'] = 'bar';\n\n            return $record;\n        });\n        $handler->handle($this->getRecord());\n\n        self::assertSame([], $t2->getRecords()[0]->extra);\n    }\n\n    public function testProcessorsDoNotInterfereBetweenHandlersWithBatch()\n    {\n        $t1 = new TestHandler();\n        $t2 = new TestHandler();\n        $handler = new WhatFailureGroupHandler([$t1, $t2]);\n\n        $t1->pushProcessor(function (LogRecord $record) {\n            $record->extra['foo'] = 'bar';\n\n            return $record;\n        });\n\n        $handler->handleBatch([$this->getRecord()]);\n\n        self::assertSame([], $t2->getRecords()[0]->extra);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Handler/ZendMonitorHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Handler;\n\nclass ZendMonitorHandlerTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function setUp(): void\n    {\n        if (!\\function_exists('zend_monitor_custom_event')) {\n            $this->markTestSkipped('ZendServer is not installed');\n        }\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->zendMonitorHandler);\n    }\n\n    /**\n     * @covers  Monolog\\Handler\\ZendMonitorHandler::write\n     */\n    public function testWrite()\n    {\n        $record = $this->getRecord();\n        $formatterResult = [\n            'message' => $record->message,\n        ];\n\n        $zendMonitor = $this->getMockBuilder('Monolog\\Handler\\ZendMonitorHandler')\n            ->onlyMethods(['writeZendMonitorCustomEvent', 'getDefaultFormatter'])\n            ->getMock();\n\n        $formatterMock = $this->getMockBuilder('Monolog\\Formatter\\NormalizerFormatter')\n            ->disableOriginalConstructor()\n            ->getMock();\n\n        $formatterMock->expects($this->once())\n            ->method('format')\n            ->willReturn($formatterResult);\n\n        $zendMonitor->expects($this->once())\n            ->method('getDefaultFormatter')\n            ->willReturn($formatterMock);\n\n        $zendMonitor->expects($this->once())\n            ->method('writeZendMonitorCustomEvent')\n            ->with(\n                $record->level->getName(),\n                $record->message,\n                $formatterResult,\n                \\ZEND_MONITOR_EVENT_SEVERITY_WARNING\n            );\n\n        $zendMonitor->handle($record);\n    }\n\n    /**\n     * @covers Monolog\\Handler\\ZendMonitorHandler::getDefaultFormatter\n     */\n    public function testGetDefaultFormatterReturnsNormalizerFormatter()\n    {\n        $zendMonitor = new ZendMonitorHandler();\n        $this->assertInstanceOf('Monolog\\Formatter\\NormalizerFormatter', $zendMonitor->getDefaultFormatter());\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/LoggerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse Monolog\\Handler\\HandlerInterface;\nuse Monolog\\Processor\\WebProcessor;\nuse Monolog\\Handler\\TestHandler;\nuse Monolog\\Test\\MonologTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass LoggerTest extends MonologTestCase\n{\n    /**\n     * @covers Logger::getName\n     */\n    public function testGetName()\n    {\n        $logger = new Logger('foo');\n        $this->assertEquals('foo', $logger->getName());\n    }\n\n    /**\n     * @covers Logger::withName\n     */\n    public function testWithName()\n    {\n        $first = new Logger('first', [$handler = new TestHandler()]);\n        $second = $first->withName('second');\n\n        $this->assertSame('first', $first->getName());\n        $this->assertSame('second', $second->getName());\n        $this->assertSame($handler, $second->popHandler());\n    }\n\n    /**\n     * @covers Logger::toMonologLevel\n     */\n    public function testConvertPSR3ToMonologLevel()\n    {\n        $this->assertEquals(Logger::toMonologLevel('debug'), Level::Debug);\n        $this->assertEquals(Logger::toMonologLevel('info'), Level::Info);\n        $this->assertEquals(Logger::toMonologLevel('notice'), Level::Notice);\n        $this->assertEquals(Logger::toMonologLevel('warning'), Level::Warning);\n        $this->assertEquals(Logger::toMonologLevel('error'), Level::Error);\n        $this->assertEquals(Logger::toMonologLevel('critical'), Level::Critical);\n        $this->assertEquals(Logger::toMonologLevel('alert'), Level::Alert);\n        $this->assertEquals(Logger::toMonologLevel('emergency'), Level::Emergency);\n    }\n\n    /**\n     * @covers Monolog\\Logger::addRecord\n     * @covers Monolog\\Logger::log\n     */\n    public function testConvertRFC5424ToMonologLevelInAddRecordAndLog()\n    {\n        $logger = new Logger('test');\n        $handler = new TestHandler;\n        $logger->pushHandler($handler);\n\n        foreach ([\n            7 => 100,\n            6 => 200,\n            5 => 250,\n            4 => 300,\n            3 => 400,\n            2 => 500,\n            1 => 550,\n            0 => 600,\n        ] as $rfc5424Level => $monologLevel) {\n            $handler->reset();\n            $logger->addRecord($rfc5424Level, 'test');\n            $logger->log($rfc5424Level, 'test');\n            $records = $handler->getRecords();\n\n            self::assertCount(2, $records);\n            self::assertSame($monologLevel, $records[0]['level']);\n            self::assertSame($monologLevel, $records[1]['level']);\n        }\n    }\n\n    /**\n     * @covers Logger::__construct\n     */\n    public function testChannel()\n    {\n        $logger = new Logger('foo');\n        $handler = new TestHandler;\n        $logger->pushHandler($handler);\n        $logger->warning('test');\n        list($record) = $handler->getRecords();\n        $this->assertEquals('foo', $record->channel);\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testLogPreventsCircularLogging()\n    {\n        $logger = new Logger(__METHOD__);\n\n        $loggingHandler = new LoggingHandler($logger);\n        $testHandler = new TestHandler();\n\n        $logger->pushHandler($loggingHandler);\n        $logger->pushHandler($testHandler);\n\n        $logger->addRecord(Level::Alert, 'test');\n\n        $records = $testHandler->getRecords();\n        $this->assertCount(3, $records);\n        $this->assertSame('ALERT', $records[0]->level->getName());\n        $this->assertSame('DEBUG', $records[1]->level->getName());\n        $this->assertSame('WARNING', $records[2]->level->getName());\n    }\n\n    /**\n     * @covers Monolog\\Logger::addRecord\n     */\n    public function testLog()\n    {\n        $logger = new Logger(__METHOD__);\n\n        $handler = $this->getMockBuilder('Monolog\\Handler\\HandlerInterface')->getMock();\n        $handler->expects($this->never())->method('isHandling');\n        $handler->expects($this->once())->method('handle');\n\n        $logger->pushHandler($handler);\n\n        $this->assertTrue($logger->addRecord(Level::Warning, 'test'));\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testLogAlwaysHandledIfNoProcessorsArePresent()\n    {\n        $logger = new Logger(__METHOD__);\n\n        $handler = $this->getMockBuilder('Monolog\\Handler\\HandlerInterface')->getMock();\n        $handler->expects($this->never())->method('isHandling');\n        $handler->expects($this->once())->method('handle');\n\n        $logger->pushHandler($handler);\n\n        $this->assertTrue($logger->addRecord(Level::Warning, 'test'));\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testLogNotHandledIfProcessorsArePresent()\n    {\n        $logger = new Logger(__METHOD__);\n\n        $handler = $this->getMockBuilder('Monolog\\Handler\\HandlerInterface')->getMock();\n        $handler->expects($this->once())->method('isHandling')->willReturn(false);\n        $handler->expects($this->never())->method('handle');\n\n        $logger->pushProcessor(fn (LogRecord $record) => $record);\n        $logger->pushHandler($handler);\n\n        $this->assertFalse($logger->addRecord(Level::Warning, 'test'));\n    }\n\n    public function testHandlersInCtor()\n    {\n        $handler1 = new TestHandler;\n        $handler2 = new TestHandler;\n        $logger = new Logger(__METHOD__, [$handler1, $handler2]);\n\n        $this->assertEquals($handler1, $logger->popHandler());\n        $this->assertEquals($handler2, $logger->popHandler());\n    }\n\n    public function testProcessorsInCtor()\n    {\n        $processor1 = new WebProcessor;\n        $processor2 = new WebProcessor;\n        $logger = new Logger(__METHOD__, [], [$processor1, $processor2]);\n\n        $this->assertEquals($processor1, $logger->popProcessor());\n        $this->assertEquals($processor2, $logger->popProcessor());\n    }\n\n    /**\n     * @covers Logger::pushHandler\n     * @covers Logger::popHandler\n     */\n    public function testPushPopHandler()\n    {\n        $logger = new Logger(__METHOD__);\n        $handler1 = new TestHandler;\n        $handler2 = new TestHandler;\n\n        $logger->pushHandler($handler1);\n        $logger->pushHandler($handler2);\n\n        $this->assertEquals($handler2, $logger->popHandler());\n        $this->assertEquals($handler1, $logger->popHandler());\n\n        $this->expectException(\\LogicException::class);\n\n        $logger->popHandler();\n    }\n\n    /**\n     * @covers Logger::setHandlers\n     */\n    public function testSetHandlers()\n    {\n        $logger = new Logger(__METHOD__);\n        $handler1 = new TestHandler;\n        $handler2 = new TestHandler;\n\n        $logger->pushHandler($handler1);\n        $logger->setHandlers([$handler2]);\n\n        // handler1 has been removed\n        $this->assertEquals([$handler2], $logger->getHandlers());\n\n        $logger->setHandlers([\n            \"AMapKey\" => $handler1,\n            \"Woop\" => $handler2,\n        ]);\n\n        // Keys have been scrubbed\n        $this->assertEquals([$handler1, $handler2], $logger->getHandlers());\n    }\n\n    /**\n     * @covers Logger::pushProcessor\n     * @covers Logger::popProcessor\n     */\n    public function testPushPopProcessor()\n    {\n        $logger = new Logger(__METHOD__);\n        $processor1 = new WebProcessor;\n        $processor2 = new WebProcessor;\n\n        $logger->pushProcessor($processor1);\n        $logger->pushProcessor($processor2);\n\n        $this->assertEquals($processor2, $logger->popProcessor());\n        $this->assertEquals($processor1, $logger->popProcessor());\n\n        $this->expectException(\\LogicException::class);\n\n        $logger->popProcessor();\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testProcessorsAreExecuted()\n    {\n        $logger = new Logger(__METHOD__);\n        $handler = new TestHandler;\n        $logger->pushHandler($handler);\n        $logger->pushProcessor(function ($record) {\n            $record->extra['win'] = true;\n\n            return $record;\n        });\n        $logger->error('test');\n        list($record) = $handler->getRecords();\n        $this->assertTrue($record->extra['win']);\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testProcessorsAreCalledOnlyOnce()\n    {\n        $logger = new Logger(__METHOD__);\n        $handler = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler->expects($this->any())\n            ->method('isHandling')\n            ->willReturn(true);\n\n        $handler->expects($this->any())\n            ->method('handle')\n            ->willReturn(true);\n\n        $logger->pushHandler($handler);\n\n        $processor = $this->getMockBuilder('Monolog\\Processor\\WebProcessor')\n            ->disableOriginalConstructor()\n            ->onlyMethods(['__invoke'])\n            ->getMock()\n        ;\n        $processor->expects($this->once())\n            ->method('__invoke')\n            ->willReturnArgument(0)\n        ;\n        $logger->pushProcessor($processor);\n\n        $logger->error('test');\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testProcessorsNotCalledWhenNotHandled()\n    {\n        $logger = new Logger(__METHOD__);\n        $handler = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler->expects($this->once())\n            ->method('isHandling')\n            ->willReturn(false);\n\n        $logger->pushHandler($handler);\n        $that = $this;\n        $logger->pushProcessor(function ($record) use ($that) {\n            $that->fail('The processor should not be called');\n        });\n        $logger->alert('test');\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testHandlersNotCalledBeforeFirstHandlingWhenProcessorsPresent()\n    {\n        $logger = new Logger(__METHOD__);\n        $logger->pushProcessor(fn ($record) => $record);\n\n        $handler1 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler1->expects($this->never())\n            ->method('isHandling')\n            ->willReturn(false);\n\n        $handler1->expects($this->once())\n            ->method('handle')\n            ->willReturn(false);\n\n        $logger->pushHandler($handler1);\n\n        $handler2 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler2->expects($this->once())\n            ->method('isHandling')\n            ->willReturn(true);\n\n        $handler2->expects($this->once())\n            ->method('handle')\n            ->willReturn(false);\n\n        $logger->pushHandler($handler2);\n\n        $handler3 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler3->expects($this->once())\n            ->method('isHandling')\n            ->willReturn(false);\n\n        $handler3->expects($this->never())\n            ->method('handle')\n        ;\n        $logger->pushHandler($handler3);\n\n        $logger->debug('test');\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testHandlersNotCalledBeforeFirstHandlingWhenProcessorsPresentWithAssocArray()\n    {\n        $handler1 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler1->expects($this->never())\n            ->method('isHandling')\n            ->willReturn(false);\n\n        $handler1->expects($this->once())\n            ->method('handle')\n            ->willReturn(false);\n\n        $handler2 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler2->expects($this->once())\n            ->method('isHandling')\n            ->willReturn(true);\n\n        $handler2->expects($this->once())\n            ->method('handle')\n            ->willReturn(false);\n\n        $handler3 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler3->expects($this->once())\n            ->method('isHandling')\n            ->willReturn(false);\n\n        $handler3->expects($this->never())\n            ->method('handle')\n        ;\n\n        $logger = new Logger(__METHOD__, ['last' => $handler3, 'second' => $handler2, 'first' => $handler1]);\n        $logger->pushProcessor(fn ($record) => $record);\n\n        $logger->debug('test');\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testBubblingWhenTheHandlerReturnsFalse()\n    {\n        $logger = new Logger(__METHOD__);\n\n        $handler1 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler1->expects($this->any())\n            ->method('isHandling')\n            ->willReturn(true);\n\n        $handler1->expects($this->once())\n            ->method('handle')\n            ->willReturn(false);\n\n        $logger->pushHandler($handler1);\n\n        $handler2 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler2->expects($this->any())\n            ->method('isHandling')\n            ->willReturn(true);\n\n        $handler2->expects($this->once())\n            ->method('handle')\n            ->willReturn(false);\n\n        $logger->pushHandler($handler2);\n\n        $logger->debug('test');\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testNotBubblingWhenTheHandlerReturnsTrue()\n    {\n        $logger = new Logger(__METHOD__);\n\n        $handler1 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler1->expects($this->any())\n            ->method('isHandling')\n            ->willReturn(true);\n\n        $handler1->expects($this->never())\n            ->method('handle')\n        ;\n        $logger->pushHandler($handler1);\n\n        $handler2 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler2->expects($this->any())\n            ->method('isHandling')\n            ->willReturn(true);\n\n        $handler2->expects($this->once())\n            ->method('handle')\n            ->willReturn(true);\n\n        $logger->pushHandler($handler2);\n\n        $logger->debug('test');\n    }\n\n    /**\n     * @covers Logger::isHandling\n     */\n    public function testIsHandling()\n    {\n        $logger = new Logger(__METHOD__);\n\n        $handler1 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler1->expects($this->any())\n            ->method('isHandling')\n            ->willReturn(false);\n\n        $logger->pushHandler($handler1);\n        $this->assertFalse($logger->isHandling(Level::Debug));\n\n        $handler2 = $this->createMock('Monolog\\Handler\\HandlerInterface');\n        $handler2->expects($this->any())\n            ->method('isHandling')\n            ->willReturn(true);\n\n        $logger->pushHandler($handler2);\n        $this->assertTrue($logger->isHandling(Level::Debug));\n    }\n\n    /**\n     * @covers Level::Debug\n     * @covers Level::Info\n     * @covers Level::Notice\n     * @covers Level::Warning\n     * @covers Level::Error\n     * @covers Level::Critical\n     * @covers Level::Alert\n     * @covers Level::Emergency\n     */\n    #[DataProvider('logMethodProvider')]\n    public function testLogMethods(string $method, Level $expectedLevel)\n    {\n        $logger = new Logger('foo');\n        $handler = new TestHandler;\n        $logger->pushHandler($handler);\n        $logger->{$method}('test');\n        list($record) = $handler->getRecords();\n        $this->assertEquals($expectedLevel, $record->level);\n    }\n\n    public static function logMethodProvider()\n    {\n        return [\n            // PSR-3 methods\n            ['debug',  Level::Debug],\n            ['info',   Level::Info],\n            ['notice', Level::Notice],\n            ['warning',   Level::Warning],\n            ['error',    Level::Error],\n            ['critical',   Level::Critical],\n            ['alert',  Level::Alert],\n            ['emergency',  Level::Emergency],\n        ];\n    }\n\n    /**\n     * @covers Logger::setTimezone\n     */\n    #[DataProvider('setTimezoneProvider')]\n    public function testSetTimezone($tz)\n    {\n        $logger = new Logger('foo');\n        $logger->setTimezone($tz);\n        $handler = new TestHandler;\n        $logger->pushHandler($handler);\n        $logger->info('test');\n        list($record) = $handler->getRecords();\n        $this->assertEquals($tz, $record->datetime->getTimezone());\n    }\n\n    public static function setTimezoneProvider()\n    {\n        return array_map(\n            function ($tz) {\n                return [new \\DateTimeZone($tz)];\n            },\n            \\DateTimeZone::listIdentifiers()\n        );\n    }\n\n    /**\n     * @covers Logger::setTimezone\n     * @covers JsonSerializableDateTimeImmutable::__construct\n     */\n    public function testTimezoneIsRespectedInUTC()\n    {\n        foreach ([true, false] as $microseconds) {\n            $logger = new Logger('foo');\n            $logger->useMicrosecondTimestamps($microseconds);\n            $tz = new \\DateTimeZone('America/New_York');\n            $logger->setTimezone($tz);\n            $handler = new TestHandler;\n            $logger->pushHandler($handler);\n            $dt = new \\DateTime('now', $tz);\n            $logger->info('test');\n            list($record) = $handler->getRecords();\n\n            $this->assertEquals($tz, $record->datetime->getTimezone());\n            $this->assertEquals($dt->format('Y/m/d H:i'), $record->datetime->format('Y/m/d H:i'), 'Time should match timezone with microseconds set to: '.var_export($microseconds, true));\n        }\n    }\n\n    /**\n     * @covers Logger::setTimezone\n     * @covers JsonSerializableDateTimeImmutable::__construct\n     */\n    public function testTimezoneIsRespectedInOtherTimezone()\n    {\n        date_default_timezone_set('CET');\n        foreach ([true, false] as $microseconds) {\n            $logger = new Logger('foo');\n            $logger->useMicrosecondTimestamps($microseconds);\n            $tz = new \\DateTimeZone('America/New_York');\n            $logger->setTimezone($tz);\n            $handler = new TestHandler;\n            $logger->pushHandler($handler);\n            $dt = new \\DateTime('now', $tz);\n            $logger->info('test');\n            list($record) = $handler->getRecords();\n\n            $this->assertEquals($tz, $record->datetime->getTimezone());\n            $this->assertEquals($dt->format('Y/m/d H:i'), $record->datetime->format('Y/m/d H:i'), 'Time should match timezone with microseconds set to: '.var_export($microseconds, true));\n        }\n    }\n\n    public function tearDown(): void\n    {\n        date_default_timezone_set('UTC');\n    }\n\n    /**\n     * @covers Logger::useMicrosecondTimestamps\n     * @covers Logger::addRecord\n     */\n    #[DataProvider('useMicrosecondTimestampsProvider')]\n    public function testUseMicrosecondTimestamps($micro, $assert, $assertFormat)\n    {\n        if (PHP_VERSION_ID === 70103) {\n            $this->markTestSkipped();\n        }\n\n        $logger = new Logger('foo');\n        $logger->useMicrosecondTimestamps($micro);\n        $handler = new TestHandler;\n        $logger->pushHandler($handler);\n        $logger->info('test');\n        list($record) = $handler->getRecords();\n        $this->{$assert}('000000', $record->datetime->format('u'));\n        $this->assertSame($record->datetime->format($assertFormat), (string) $record->datetime);\n    }\n\n    public static function useMicrosecondTimestampsProvider()\n    {\n        return [\n            // this has a very small chance of a false negative (1/10^6)\n            'with microseconds' => [true, 'assertNotSame', 'Y-m-d\\TH:i:s.uP'],\n            // php 7.1 always includes microseconds, so we keep them in, but we format the datetime without\n            'without microseconds' => [false, 'assertNotSame', 'Y-m-d\\TH:i:sP'],\n        ];\n    }\n\n    public function testProcessorsDoNotInterfereBetweenHandlers()\n    {\n        $logger = new Logger('foo');\n        $logger->pushHandler($t1 = new TestHandler());\n        $logger->pushHandler($t2 = new TestHandler());\n        $t1->pushProcessor(function (LogRecord $record) {\n            $record->extra['foo'] = 'bar';\n\n            return $record;\n        });\n        $logger->error('Foo');\n\n        self::assertSame([], $t2->getRecords()[0]->extra);\n    }\n\n    /**\n     * @covers Logger::setExceptionHandler\n     */\n    public function testSetExceptionHandler()\n    {\n        $logger = new Logger(__METHOD__);\n        $this->assertNull($logger->getExceptionHandler());\n        $callback = function ($ex) {\n        };\n        $logger->setExceptionHandler($callback);\n        $this->assertEquals($callback, $logger->getExceptionHandler());\n    }\n\n    /**\n     * @covers Logger::handleException\n     */\n    public function testDefaultHandleException()\n    {\n        $logger = new Logger(__METHOD__);\n        $handler = $this->getMockBuilder('Monolog\\Handler\\HandlerInterface')->getMock();\n        $handler->expects($this->any())\n            ->method('isHandling')\n            ->willReturn(true);\n\n        $handler->expects($this->any())\n            ->method('handle')\n            ->will($this->throwException(new \\Exception('Some handler exception')))\n        ;\n\n        $this->expectException(\\Exception::class);\n\n        $logger->pushHandler($handler);\n        $logger->info('test');\n    }\n\n    /**\n     * @covers Logger::handleException\n     * @covers Logger::addRecord\n     */\n    public function testCustomHandleException()\n    {\n        $logger = new Logger(__METHOD__);\n        $that = $this;\n        $logger->setExceptionHandler(function ($e, $record) use ($that) {\n            $that->assertEquals($e->getMessage(), 'Some handler exception');\n            $that->assertInstanceOf(LogRecord::class, $record);\n            $that->assertEquals($record->message, 'test');\n        });\n        $handler = $this->getMockBuilder('Monolog\\Handler\\HandlerInterface')->getMock();\n        $handler->expects($this->any())\n            ->method('isHandling')\n            ->willReturn(true);\n\n        $handler->expects($this->any())\n            ->method('handle')\n            ->will($this->throwException(new \\Exception('Some handler exception')))\n        ;\n        $logger->pushHandler($handler);\n        $logger->info('test');\n    }\n\n    public function testSerializable()\n    {\n        $logger = new Logger(__METHOD__);\n        $copy = unserialize(serialize($logger));\n        self::assertInstanceOf(Logger::class, $copy);\n        self::assertSame($logger->getName(), $copy->getName());\n        self::assertSame($logger->getTimezone()->getName(), $copy->getTimezone()->getName());\n        self::assertSame($logger->getHandlers(), $copy->getHandlers());\n    }\n\n    public function testReset()\n    {\n        $logger = new Logger('app');\n\n        $testHandler = new Handler\\TestHandler();\n        $testHandler->setSkipReset(true);\n        $bufferHandler = new Handler\\BufferHandler($testHandler);\n        $groupHandler = new Handler\\GroupHandler([$bufferHandler]);\n        $fingersCrossedHandler = new Handler\\FingersCrossedHandler($groupHandler);\n\n        $logger->pushHandler($fingersCrossedHandler);\n\n        $processorUid1 = new Processor\\UidProcessor(10);\n        $uid1 = $processorUid1->getUid();\n        $groupHandler->pushProcessor($processorUid1);\n\n        $processorUid2 = new Processor\\UidProcessor(5);\n        $uid2 = $processorUid2->getUid();\n        $logger->pushProcessor($processorUid2);\n\n        $getProperty = function ($object, $property) {\n            $reflectionProperty = new \\ReflectionProperty(\\get_class($object), $property);\n\n            return $reflectionProperty->getValue($object);\n        };\n        $assertBufferOfBufferHandlerEmpty = function () use ($getProperty, $bufferHandler) {\n            self::assertEmpty($getProperty($bufferHandler, 'buffer'));\n        };\n        $assertBuffersEmpty = function () use ($assertBufferOfBufferHandlerEmpty, $getProperty, $fingersCrossedHandler) {\n            $assertBufferOfBufferHandlerEmpty();\n            self::assertEmpty($getProperty($fingersCrossedHandler, 'buffer'));\n        };\n\n        $logger->debug('debug1');\n        $logger->reset();\n        $assertBuffersEmpty();\n        $this->assertFalse($testHandler->hasDebugRecords());\n        $this->assertFalse($testHandler->hasErrorRecords());\n        $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());\n        $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());\n\n        $logger->debug('debug2');\n        $logger->error('error2');\n        $logger->reset();\n        $assertBuffersEmpty();\n        $this->assertTrue($testHandler->hasRecordThatContains('debug2', Level::Debug));\n        $this->assertTrue($testHandler->hasRecordThatContains('error2', Level::Error));\n        $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());\n        $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());\n\n        $logger->info('info3');\n        $this->assertNotEmpty($getProperty($fingersCrossedHandler, 'buffer'));\n        $assertBufferOfBufferHandlerEmpty();\n        $this->assertFalse($testHandler->hasInfoRecords());\n\n        $logger->reset();\n        $assertBuffersEmpty();\n        $this->assertFalse($testHandler->hasInfoRecords());\n        $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());\n        $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());\n\n        $logger->notice('notice4');\n        $logger->emergency('emergency4');\n        $logger->reset();\n        $assertBuffersEmpty();\n        $this->assertFalse($testHandler->hasInfoRecords());\n        $this->assertTrue($testHandler->hasRecordThatContains('notice4', Level::Notice));\n        $this->assertTrue($testHandler->hasRecordThatContains('emergency4', Level::Emergency));\n        $this->assertNotSame($uid1, $processorUid1->getUid());\n        $this->assertNotSame($uid2, $processorUid2->getUid());\n    }\n\n    /**\n     * @covers Logger::addRecord\n     */\n    public function testLogWithDateTime()\n    {\n        foreach ([true, false] as $microseconds) {\n            $logger = new Logger(__METHOD__);\n\n            $loggingHandler = new LoggingHandler($logger);\n            $testHandler = new TestHandler();\n\n            $logger->pushHandler($loggingHandler);\n            $logger->pushHandler($testHandler);\n\n            $datetime = (new JsonSerializableDateTimeImmutable($microseconds))->modify('2022-03-04 05:06:07');\n            $logger->addRecord(Level::Debug, 'test', [], $datetime);\n\n            list($record) = $testHandler->getRecords();\n            $this->assertEquals($datetime->format('Y-m-d H:i:s'), $record->datetime->format('Y-m-d H:i:s'));\n        }\n    }\n\n    public function testLogCycleDetectionWithFibersWithoutCycle()\n    {\n        $logger = new Logger(__METHOD__);\n\n        $fiberSuspendHandler = new FiberSuspendHandler();\n        $testHandler = new TestHandler();\n\n        $logger->pushHandler($fiberSuspendHandler);\n        $logger->pushHandler($testHandler);\n\n        $fibers = [];\n        for ($i = 0; $i < 10; $i++) {\n            $fiber = new \\Fiber(static function () use ($logger) {\n                $logger->info('test');\n            });\n\n            $fiber->start();\n\n            // We need to keep a reference here, because otherwise the fiber gets automatically cleaned up\n            $fibers[] = $fiber;\n        }\n\n        self::assertCount(10, $testHandler->getRecords());\n    }\n\n    public function testLogCycleDetectionWithFibersWithCycle()\n    {\n        $logger = new Logger(__METHOD__);\n\n        $fiberSuspendHandler = new FiberSuspendHandler();\n        $loggingHandler = new LoggingHandler($logger);\n        $testHandler = new TestHandler();\n\n        $logger->pushHandler($fiberSuspendHandler);\n        $logger->pushHandler($loggingHandler);\n        $logger->pushHandler($testHandler);\n\n        $fiber = new \\Fiber(static function () use ($logger) {\n            $logger->info('test');\n        });\n\n        $fiber->start();\n\n        self::assertCount(3, $testHandler->getRecords());\n    }\n}\n\nclass LoggingHandler implements HandlerInterface\n{\n    /**\n     * @var Logger\n     */\n    private $logger;\n\n    public function __construct(Logger $logger)\n    {\n        $this->logger = $logger;\n    }\n\n    public function isHandling(LogRecord $record): bool\n    {\n        return true;\n    }\n\n    public function handle(LogRecord $record): bool\n    {\n        $this->logger->debug('Log triggered while logging');\n\n        return false;\n    }\n\n    public function handleBatch(array $records): void\n    {\n    }\n\n    public function close(): void\n    {\n    }\n}\n\nclass FiberSuspendHandler implements HandlerInterface\n{\n    public function isHandling(LogRecord $record): bool\n    {\n        return true;\n    }\n\n    public function handle(LogRecord $record): bool\n    {\n        \\Fiber::suspend();\n\n        return true;\n    }\n\n    public function handleBatch(array $records): void\n    {\n    }\n\n    public function close(): void\n    {\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/ClosureContextProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass ClosureContextProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testReplace()\n    {\n        $context = ['obj' => new \\stdClass()];\n        $processor = new ClosureContextProcessor();\n\n        $record = $processor($this->getRecord(context: [fn () => $context]));\n        $this->assertSame($context, $record->context);\n    }\n\n    #[DataProvider('getContexts')]\n    public function testSkip(array $context)\n    {\n        $processor = new ClosureContextProcessor();\n\n        $record = $processor($this->getRecord(context: $context));\n        $this->assertSame($context, $record->context);\n    }\n\n    public function testClosureReturnsNotArray()\n    {\n        $object = new \\stdClass();\n        $processor = new ClosureContextProcessor();\n\n        $record = $processor($this->getRecord(context: [fn () => $object]));\n        $this->assertSame([$object], $record->context);\n    }\n\n    public function testClosureThrows()\n    {\n        $exception = new \\Exception('For test.');\n        $expected = [\n            'error_on_context_generation' => 'For test.',\n            'exception' => $exception,\n        ];\n        $processor = new ClosureContextProcessor();\n\n        $record = $processor($this->getRecord(context: [fn () => throw $exception]));\n        $this->assertSame($expected, $record->context);\n    }\n\n    public static function getContexts(): iterable\n    {\n        yield [['foo']];\n        yield [['foo' => 'bar']];\n        yield [['foo', 'bar']];\n        yield [['foo', fn () => 'bar']];\n        yield [[fn () => 'foo', 'bar']];\n        yield [['foo' => fn () => 'bar']];\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/GitProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\Level;\n\nclass GitProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Processor\\GitProcessor::__invoke\n     */\n    public function testProcessor()\n    {\n        $processor = new GitProcessor();\n        $record = $processor($this->getRecord());\n\n        $this->assertArrayHasKey('git', $record->extra);\n        $this->assertTrue(!\\is_array($record->extra['git']['branch']));\n    }\n\n    /**\n     * @covers Monolog\\Processor\\GitProcessor::__invoke\n     */\n    public function testProcessorWithLevel()\n    {\n        $processor = new GitProcessor(Level::Error);\n        $record = $processor($this->getRecord());\n\n        $this->assertArrayNotHasKey('git', $record->extra);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/HostnameProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nclass HostnameProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Processor\\HostnameProcessor::__invoke\n     */\n    public function testProcessor()\n    {\n        $processor = new HostnameProcessor();\n        $record = $processor($this->getRecord());\n        $this->assertArrayHasKey('hostname', $record->extra);\n        $this->assertIsString($record->extra['hostname']);\n        $this->assertNotEmpty($record->extra['hostname']);\n        $this->assertEquals(gethostname(), $record->extra['hostname']);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/IntrospectionProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Acme;\n\nclass Tester\n{\n    public function test($handler, $record)\n    {\n        $handler->handle($record);\n    }\n}\n\nfunction tester($handler, $record)\n{\n    $handler->handle($record);\n}\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\Level;\nuse Monolog\\Handler\\TestHandler;\n\nclass IntrospectionProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function getHandler()\n    {\n        $processor = new IntrospectionProcessor();\n        $handler = new TestHandler();\n        $handler->pushProcessor($processor);\n\n        return $handler;\n    }\n\n    public function testProcessorFromClass()\n    {\n        $handler = $this->getHandler();\n        $tester = new \\Acme\\Tester;\n        $tester->test($handler, $this->getRecord());\n        list($record) = $handler->getRecords();\n        $this->assertEquals(__FILE__, $record->extra['file']);\n        $this->assertEquals(18, $record->extra['line']);\n        $this->assertEquals('Acme\\Tester', $record->extra['class']);\n        $this->assertEquals('test', $record->extra['function']);\n    }\n\n    public function testProcessorFromFunc()\n    {\n        $handler = $this->getHandler();\n        \\Acme\\tester($handler, $this->getRecord());\n        list($record) = $handler->getRecords();\n        $this->assertEquals(__FILE__, $record->extra['file']);\n        $this->assertEquals(24, $record->extra['line']);\n        $this->assertEquals(null, $record->extra['class']);\n        $this->assertEquals('Acme\\tester', $record->extra['function']);\n    }\n\n    public function testLevelTooLow()\n    {\n        $input = $this->getRecord(Level::Debug);\n\n        $expected = clone $input;\n\n        $processor = new IntrospectionProcessor(Level::Critical);\n        $actual = $processor($input);\n\n        $this->assertEquals($expected, $actual);\n    }\n\n    public function testLevelEqual()\n    {\n        $input = $this->getRecord(Level::Critical);\n\n        $expected = clone $input;\n        $expected['extra'] = [\n            'file' => null,\n            'line' => null,\n            'class' => 'PHPUnit\\Framework\\TestCase',\n            'function' => 'runTest',\n            'callType' => '->',\n        ];\n\n        $processor = new IntrospectionProcessor(Level::Critical);\n        $actual = $processor($input);\n\n        $this->assertEquals($expected, $actual);\n    }\n\n    public function testLevelHigher()\n    {\n        $input = $this->getRecord(Level::Emergency);\n\n        $expected = clone $input;\n        $expected['extra'] = [\n            'file' => null,\n            'line' => null,\n            'class' => 'PHPUnit\\Framework\\TestCase',\n            'function' => 'runTest',\n            'callType' => '->',\n        ];\n\n        $processor = new IntrospectionProcessor(Level::Critical);\n        $actual = $processor($input);\n\n        $this->assertEquals($expected, $actual);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/LoadAverageProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nclass LoadAverageProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Processor\\LoadAverageProcessor::__invoke\n     */\n    public function testProcessor()\n    {\n        $processor = new LoadAverageProcessor();\n        $record = $processor($this->getRecord());\n        $this->assertArrayHasKey('load_average', $record->extra);\n        $this->assertIsFloat($record->extra['load_average']);\n    }\n\n    /**\n     * @covers Monolog\\Processor\\LoadAverageProcessor::__invoke\n     */\n    public function testProcessorWithInvalidAvgSystemLoad()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n        $this->expectExceptionMessage('Invalid average system load: `3`');\n        new LoadAverageProcessor(3);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nclass MemoryPeakUsageProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Processor\\MemoryPeakUsageProcessor::__invoke\n     * @covers Monolog\\Processor\\MemoryProcessor::formatBytes\n     */\n    public function testProcessor()\n    {\n        $processor = new MemoryPeakUsageProcessor();\n        $record = $processor($this->getRecord());\n        $this->assertArrayHasKey('memory_peak_usage', $record->extra);\n        $this->assertMatchesRegularExpression('#[0-9.]+ (M|K)?B$#', $record->extra['memory_peak_usage']);\n    }\n\n    /**\n     * @covers Monolog\\Processor\\MemoryPeakUsageProcessor::__invoke\n     * @covers Monolog\\Processor\\MemoryProcessor::formatBytes\n     */\n    public function testProcessorWithoutFormatting()\n    {\n        $processor = new MemoryPeakUsageProcessor(true, false);\n        $record = $processor($this->getRecord());\n        $this->assertArrayHasKey('memory_peak_usage', $record->extra);\n        $this->assertIsInt($record->extra['memory_peak_usage']);\n        $this->assertGreaterThan(0, $record->extra['memory_peak_usage']);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/MemoryUsageProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nclass MemoryUsageProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Processor\\MemoryUsageProcessor::__invoke\n     * @covers Monolog\\Processor\\MemoryProcessor::formatBytes\n     */\n    public function testProcessor()\n    {\n        $processor = new MemoryUsageProcessor();\n        $record = $processor($this->getRecord());\n        $this->assertArrayHasKey('memory_usage', $record->extra);\n        $this->assertMatchesRegularExpression('#[0-9.]+ (M|K)?B$#', $record->extra['memory_usage']);\n    }\n\n    /**\n     * @covers Monolog\\Processor\\MemoryUsageProcessor::__invoke\n     * @covers Monolog\\Processor\\MemoryProcessor::formatBytes\n     */\n    public function testProcessorWithoutFormatting()\n    {\n        $processor = new MemoryUsageProcessor(true, false);\n        $record = $processor($this->getRecord());\n        $this->assertArrayHasKey('memory_usage', $record->extra);\n        $this->assertIsInt($record->extra['memory_usage']);\n        $this->assertGreaterThan(0, $record->extra['memory_usage']);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/MercurialProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nclass MercurialProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    private string $oldCwd;\n    private string $testDir;\n\n    protected function setUp(): void\n    {\n        parent::setUp();\n\n        $this->oldCwd = getcwd();\n        $this->testDir = sys_get_temp_dir().'/monolog-processor-mercurial-test';\n\n        mkdir($this->testDir, recursive: true);\n        chdir($this->testDir);\n    }\n\n    protected function tearDown(): void\n    {\n        parent::tearDown();\n\n        chdir($this->oldCwd);\n\n        if (!file_exists($this->testDir)) {\n            return;\n        }\n        $items = new \\RecursiveIteratorIterator(\n            new \\RecursiveDirectoryIterator($this->testDir, \\RecursiveDirectoryIterator::SKIP_DOTS),\n            \\RecursiveIteratorIterator::CHILD_FIRST\n        );\n\n        foreach ($items as $item) {\n            $item->isDir() ? rmdir((string) $item) : unlink((string) $item);\n        }\n\n        rmdir($this->testDir);\n    }\n\n    /**\n     * @covers Monolog\\Processor\\MercurialProcessor::__invoke\n     */\n    public function testProcessor()\n    {\n        if (\\defined('PHP_WINDOWS_VERSION_BUILD')) {\n            exec(\"where hg 2>NUL\", $output, $result);\n        } else {\n            exec(\"which hg 2>/dev/null >/dev/null\", $output, $result);\n        }\n        if ($result != 0) {\n            $this->markTestSkipped('hg is missing');\n\n            return;\n        }\n\n        exec('hg init');\n        exec('hg branch default');\n        touch('test.txt');\n        exec('hg add test.txt');\n        exec('hg commit -u foo -m \"initial commit\"');\n\n        $processor = new MercurialProcessor();\n        $record = $processor($this->getRecord());\n\n        $this->assertArrayHasKey('hg', $record->extra);\n        $this->assertSame('default', $record->extra['hg']['branch']);\n        $this->assertSame('0', $record->extra['hg']['revision']);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/ProcessIdProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nclass ProcessIdProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Processor\\ProcessIdProcessor::__invoke\n     */\n    public function testProcessor()\n    {\n        $processor = new ProcessIdProcessor();\n        $record = $processor($this->getRecord());\n        $this->assertArrayHasKey('process_id', $record->extra);\n        $this->assertIsInt($record->extra['process_id']);\n        $this->assertGreaterThan(0, $record->extra['process_id']);\n        $this->assertEquals(getmypid(), $record->extra['process_id']);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/PsrLogMessageProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nuse Monolog\\Level;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass PsrLogMessageProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    #[DataProvider('getPairs')]\n    public function testReplacement($val, $expected)\n    {\n        $proc = new PsrLogMessageProcessor;\n\n        $message = $proc($this->getRecord(message: '{foo}', context: ['foo' => $val]));\n        $this->assertEquals($expected, $message['message']);\n        $this->assertSame(['foo' => $val], $message['context']);\n    }\n\n    public function testReplacementWithContextRemoval()\n    {\n        $proc = new PsrLogMessageProcessor($dateFormat = null, $removeUsedContextFields = true);\n\n        $message = $proc($this->getRecord(message: '{foo}', context: ['foo' => 'bar', 'lorem' => 'ipsum']));\n        $this->assertSame('bar', $message['message']);\n        $this->assertSame(['lorem' => 'ipsum'], $message['context']);\n    }\n\n    public function testCustomDateFormat()\n    {\n        $format = \"Y-m-d\";\n        $date = new \\DateTime();\n\n        $proc = new PsrLogMessageProcessor($format);\n\n        $message = $proc($this->getRecord(message: '{foo}', context: ['foo' => $date]));\n        $this->assertEquals($date->format($format), $message['message']);\n        $this->assertSame(['foo' => $date], $message['context']);\n    }\n\n    public static function getPairs()\n    {\n        $date = new \\DateTime();\n\n        return [\n            ['foo',    'foo'],\n            ['3',      '3'],\n            [3,        '3'],\n            [null,     ''],\n            [true,     '1'],\n            [false,    ''],\n            [$date, $date->format(PsrLogMessageProcessor::SIMPLE_DATE)],\n            [new \\stdClass, '[object stdClass]'],\n            [[], 'array[]'],\n            [[], 'array[]'],\n            [[1, 2, 3], 'array[1,2,3]'],\n            [['foo' => 'bar'], 'array{\"foo\":\"bar\"}'],\n            [stream_context_create(), '[resource]'],\n            [Level::Info, Level::Info->value],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/TagProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nclass TagProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Processor\\TagProcessor::__invoke\n     */\n    public function testProcessor()\n    {\n        $tags = [1, 2, 3];\n        $processor = new TagProcessor($tags);\n        $record = $processor($this->getRecord());\n\n        $this->assertEquals($tags, $record->extra['tags']);\n    }\n\n    /**\n     * @covers Monolog\\Processor\\TagProcessor::__invoke\n     */\n    public function testProcessorTagModification()\n    {\n        $tags = [1, 2, 3];\n        $processor = new TagProcessor($tags);\n\n        $record = $processor($this->getRecord());\n        $this->assertEquals($tags, $record->extra['tags']);\n\n        $processor->setTags(['a', 'b']);\n        $record = $processor($this->getRecord());\n        $this->assertEquals(['a', 'b'], $record->extra['tags']);\n\n        $processor->addTags(['a', 'c', 'foo' => 'bar']);\n        $record = $processor($this->getRecord());\n        $this->assertEquals(['a', 'b', 'a', 'c', 'foo' => 'bar'], $record->extra['tags']);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/UidProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nclass UidProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    /**\n     * @covers Monolog\\Processor\\UidProcessor::__invoke\n     */\n    public function testProcessor()\n    {\n        $processor = new UidProcessor();\n        $record = $processor($this->getRecord());\n        $this->assertArrayHasKey('uid', $record->extra);\n    }\n\n    public function testGetUid()\n    {\n        $processor = new UidProcessor(10);\n        $this->assertEquals(10, \\strlen($processor->getUid()));\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/Processor/WebProcessorTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog\\Processor;\n\nclass WebProcessorTest extends \\Monolog\\Test\\MonologTestCase\n{\n    public function testProcessor()\n    {\n        $server = [\n            'REQUEST_URI'    => 'A',\n            'REMOTE_ADDR'    => 'B',\n            'REQUEST_METHOD' => 'C',\n            'HTTP_REFERER'   => 'D',\n            'SERVER_NAME'    => 'F',\n            'UNIQUE_ID'      => 'G',\n        ];\n\n        $processor = new WebProcessor($server);\n        $record = $processor($this->getRecord());\n        $this->assertEquals($server['REQUEST_URI'], $record->extra['url']);\n        $this->assertEquals($server['REMOTE_ADDR'], $record->extra['ip']);\n        $this->assertEquals($server['REQUEST_METHOD'], $record->extra['http_method']);\n        $this->assertEquals($server['HTTP_REFERER'], $record->extra['referrer']);\n        $this->assertEquals($server['SERVER_NAME'], $record->extra['server']);\n        $this->assertEquals($server['UNIQUE_ID'], $record->extra['unique_id']);\n    }\n\n    public function testProcessorDoNothingIfNoRequestUri()\n    {\n        $server = [\n            'REMOTE_ADDR'    => 'B',\n            'REQUEST_METHOD' => 'C',\n        ];\n        $processor = new WebProcessor($server);\n        $record = $processor($this->getRecord());\n        $this->assertEmpty($record->extra);\n    }\n\n    public function testProcessorReturnNullIfNoHttpReferer()\n    {\n        $server = [\n            'REQUEST_URI'    => 'A',\n            'REMOTE_ADDR'    => 'B',\n            'REQUEST_METHOD' => 'C',\n            'SERVER_NAME'    => 'F',\n        ];\n        $processor = new WebProcessor($server);\n        $record = $processor($this->getRecord());\n        $this->assertNull($record->extra['referrer']);\n    }\n\n    public function testProcessorDoesNotAddUniqueIdIfNotPresent()\n    {\n        $server = [\n            'REQUEST_URI'    => 'A',\n            'REMOTE_ADDR'    => 'B',\n            'REQUEST_METHOD' => 'C',\n            'SERVER_NAME'    => 'F',\n        ];\n        $processor = new WebProcessor($server);\n        $record = $processor($this->getRecord());\n        $this->assertFalse(isset($record->extra['unique_id']));\n    }\n\n    public function testProcessorAddsOnlyRequestedExtraFields()\n    {\n        $server = [\n            'REQUEST_URI'    => 'A',\n            'REMOTE_ADDR'    => 'B',\n            'REQUEST_METHOD' => 'C',\n            'SERVER_NAME'    => 'F',\n        ];\n\n        $processor = new WebProcessor($server, ['url', 'http_method']);\n        $record = $processor($this->getRecord());\n\n        $this->assertSame(['url' => 'A', 'http_method' => 'C'], $record->extra);\n    }\n\n    public function testProcessorAddsOnlyRequestedExtraFieldsIncludingOptionalFields()\n    {\n        $server = [\n            'REQUEST_URI'  => 'A',\n            'UNIQUE_ID'    => 'X',\n        ];\n\n        $processor = new WebProcessor($server, ['url']);\n        $record = $processor($this->getRecord());\n\n        $this->assertSame(['url' => 'A'], $record->extra);\n    }\n\n    public function testProcessorConfiguringOfExtraFields()\n    {\n        $server = [\n            'REQUEST_URI'    => 'A',\n            'REMOTE_ADDR'    => 'B',\n            'REQUEST_METHOD' => 'C',\n            'SERVER_NAME'    => 'F',\n        ];\n\n        $processor = new WebProcessor($server, ['url' => 'REMOTE_ADDR']);\n        $record = $processor($this->getRecord());\n\n        $this->assertSame(['url' => 'B'], $record->extra);\n    }\n\n    public function testInvalidData()\n    {\n        $this->expectException(\\TypeError::class);\n\n        new WebProcessor(new \\stdClass);\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/PsrLogCompatTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse DateTimeZone;\nuse Monolog\\Handler\\TestHandler;\nuse Monolog\\Formatter\\LineFormatter;\nuse Monolog\\Processor\\PsrLogMessageProcessor;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Psr\\Log\\InvalidArgumentException;\nuse Psr\\Log\\LoggerInterface;\nuse Psr\\Log\\LogLevel;\nuse Stringable;\n\nclass PsrLogCompatTest extends \\Monolog\\Test\\MonologTestCase\n{\n    private TestHandler $handler;\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        unset($this->handler);\n    }\n\n    public function getLogger(): LoggerInterface\n    {\n        $logger = new Logger('foo');\n        $logger->pushHandler($handler = new TestHandler);\n        $logger->pushProcessor(new PsrLogMessageProcessor);\n        $handler->setFormatter(new LineFormatter('%level_name% %message%'));\n\n        $this->handler = $handler;\n\n        return $logger;\n    }\n\n    public function getLogs(): array\n    {\n        $convert = function ($record) {\n            $lower = function ($match) {\n                return strtolower($match[0]);\n            };\n\n            return preg_replace_callback('{^[A-Z]+}', $lower, $record->formatted);\n        };\n\n        return array_map($convert, $this->handler->getRecords());\n    }\n\n    public function testImplements()\n    {\n        $this->assertInstanceOf(LoggerInterface::class, $this->getLogger());\n    }\n\n    #[DataProvider('provideLevelsAndMessages')]\n    public function testLogsAtAllLevels($level, $message)\n    {\n        $logger = $this->getLogger();\n        $logger->{$level}($message, ['user' => 'Bob']);\n        $logger->log($level, $message, ['user' => 'Bob']);\n\n        $expected = [\n            \"$level message of level $level with context: Bob\",\n            \"$level message of level $level with context: Bob\",\n        ];\n        $this->assertEquals($expected, $this->getLogs());\n    }\n\n    public static function provideLevelsAndMessages()\n    {\n        return [\n            LogLevel::EMERGENCY => [LogLevel::EMERGENCY, 'message of level emergency with context: {user}'],\n            LogLevel::ALERT => [LogLevel::ALERT, 'message of level alert with context: {user}'],\n            LogLevel::CRITICAL => [LogLevel::CRITICAL, 'message of level critical with context: {user}'],\n            LogLevel::ERROR => [LogLevel::ERROR, 'message of level error with context: {user}'],\n            LogLevel::WARNING => [LogLevel::WARNING, 'message of level warning with context: {user}'],\n            LogLevel::NOTICE => [LogLevel::NOTICE, 'message of level notice with context: {user}'],\n            LogLevel::INFO => [LogLevel::INFO, 'message of level info with context: {user}'],\n            LogLevel::DEBUG => [LogLevel::DEBUG, 'message of level debug with context: {user}'],\n        ];\n    }\n\n    public function testThrowsOnInvalidLevel()\n    {\n        $logger = $this->getLogger();\n\n        $this->expectException(InvalidArgumentException::class);\n        $logger->log('invalid level', 'Foo');\n    }\n\n    public function testContextReplacement()\n    {\n        $logger = $this->getLogger();\n        $logger->info('{Message {nothing} {user} {foo.bar} a}', ['user' => 'Bob', 'foo.bar' => 'Bar']);\n\n        $expected = ['info {Message {nothing} Bob Bar a}'];\n        $this->assertEquals($expected, $this->getLogs());\n    }\n\n    public function testObjectCastToString()\n    {\n        $string = uniqid('DUMMY');\n        $dummy = $this->createStringable($string);\n        $dummy->expects($this->once())\n            ->method('__toString');\n\n        $this->getLogger()->warning($dummy);\n\n        $expected = [\"warning $string\"];\n        $this->assertEquals($expected, $this->getLogs());\n    }\n\n    public function testContextCanContainAnything()\n    {\n        $closed = fopen('php://memory', 'r');\n        fclose($closed);\n\n        $context = [\n            'bool' => true,\n            'null' => null,\n            'string' => 'Foo',\n            'int' => 0,\n            'float' => 0.5,\n            'nested' => ['with object' => $this->createStringable()],\n            'object' => new \\DateTime('now', new DateTimeZone('Europe/London')),\n            'resource' => fopen('php://memory', 'r'),\n            'closed' => $closed,\n        ];\n\n        $this->getLogger()->warning('Crazy context data', $context);\n\n        $expected = ['warning Crazy context data'];\n        $this->assertEquals($expected, $this->getLogs());\n    }\n\n    public function testContextExceptionKeyCanBeExceptionOrOtherValues()\n    {\n        $logger = $this->getLogger();\n        $logger->warning('Random message', ['exception' => 'oops']);\n        $logger->critical('Uncaught Exception!', ['exception' => new \\LogicException('Fail')]);\n\n        $expected = [\n            'warning Random message',\n            'critical Uncaught Exception!',\n        ];\n        $this->assertEquals($expected, $this->getLogs());\n    }\n\n    /**\n     * Creates a mock of a `Stringable`.\n     *\n     * @param string $string The string that must be represented by the stringable.\n     */\n    protected function createStringable(string $string = ''): MockObject&Stringable\n    {\n        $mock = $this->getMockBuilder(Stringable::class)\n            ->getMock();\n\n        $mock->method('__toString')\n            ->willReturn($string);\n\n        return $mock;\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/RegistryTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass RegistryTest extends \\PHPUnit\\Framework\\TestCase\n{\n    protected function setUp(): void\n    {\n        Registry::clear();\n    }\n\n    /**\n     * @covers Monolog\\Registry::hasLogger\n     */\n    #[DataProvider('hasLoggerProvider')]\n    public function testHasLogger(array $loggersToAdd, array $loggersToCheck, array $expectedResult)\n    {\n        foreach ($loggersToAdd as $loggerToAdd) {\n            Registry::addLogger($loggerToAdd);\n        }\n        foreach ($loggersToCheck as $index => $loggerToCheck) {\n            $this->assertSame($expectedResult[$index], Registry::hasLogger($loggerToCheck));\n        }\n    }\n\n    public static function hasLoggerProvider()\n    {\n        $logger1 = new Logger('test1');\n        $logger2 = new Logger('test2');\n        $logger3 = new Logger('test3');\n\n        return [\n            // only instances\n            [\n                [$logger1],\n                [$logger1, $logger2],\n                [true, false],\n            ],\n            // only names\n            [\n                [$logger1],\n                ['test1', 'test2'],\n                [true, false],\n            ],\n            // mixed case\n            [\n                [$logger1, $logger2],\n                ['test1', $logger2, 'test3', $logger3],\n                [true, true, false, false],\n            ],\n        ];\n    }\n\n    /**\n     * @covers Monolog\\Registry::clear\n     */\n    public function testClearClears()\n    {\n        Registry::addLogger(new Logger('test1'), 'log');\n        Registry::clear();\n\n        $this->expectException('\\InvalidArgumentException');\n        Registry::getInstance('log');\n    }\n\n    /**\n     * @covers Monolog\\Registry::addLogger\n     * @covers Monolog\\Registry::removeLogger\n     */\n    #[DataProvider('removedLoggerProvider')]\n    public function testRemovesLogger($loggerToAdd, $remove)\n    {\n        Registry::addLogger($loggerToAdd);\n        Registry::removeLogger($remove);\n\n        $this->expectException('\\InvalidArgumentException');\n        Registry::getInstance($loggerToAdd->getName());\n    }\n\n    public static function removedLoggerProvider()\n    {\n        $logger1 = new Logger('test1');\n\n        return [\n            [$logger1, $logger1],\n            [$logger1, 'test1'],\n        ];\n    }\n\n    /**\n     * @covers Monolog\\Registry::addLogger\n     * @covers Monolog\\Registry::getInstance\n     * @covers Monolog\\Registry::__callStatic\n     */\n    public function testGetsSameLogger()\n    {\n        $logger1 = new Logger('test1');\n        $logger2 = new Logger('test2');\n\n        Registry::addLogger($logger1, 'test1');\n        Registry::addLogger($logger2);\n\n        $this->assertSame($logger1, Registry::getInstance('test1'));\n        $this->assertSame($logger2, Registry::test2());\n    }\n\n    /**\n     * @covers Monolog\\Registry::getInstance\n     */\n    public function testFailsOnNonExistentLogger()\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n        Registry::getInstance('test1');\n    }\n\n    /**\n     * @covers Monolog\\Registry::addLogger\n     */\n    public function testReplacesLogger()\n    {\n        $log1 = new Logger('test1');\n        $log2 = new Logger('test2');\n\n        Registry::addLogger($log1, 'log');\n\n        Registry::addLogger($log2, 'log', true);\n\n        $this->assertSame($log2, Registry::getInstance('log'));\n    }\n\n    /**\n     * @covers Monolog\\Registry::addLogger\n     */\n    public function testFailsOnUnspecifiedReplacement()\n    {\n        $log1 = new Logger('test1');\n        $log2 = new Logger('test2');\n\n        Registry::addLogger($log1, 'log');\n\n        $this->expectException(\\InvalidArgumentException::class);\n\n        Registry::addLogger($log2, 'log');\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/SignalHandlerTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse Monolog\\Handler\\StreamHandler;\nuse Monolog\\Handler\\TestHandler;\nuse Monolog\\Test\\MonologTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Psr\\Log\\LogLevel;\n\n/**\n * @author Robert Gust-Bardon <robert@gust-bardon.org>\n * @covers Monolog\\SignalHandler\n */\nclass SignalHandlerTest extends MonologTestCase\n{\n    private bool $asyncSignalHandling;\n    private array $blockedSignals = [];\n    private array $signalHandlers = [];\n\n    protected function setUp(): void\n    {\n        $this->signalHandlers = [];\n        if (\\extension_loaded('pcntl')) {\n            if (\\function_exists('pcntl_async_signals')) {\n                $this->asyncSignalHandling = pcntl_async_signals();\n            }\n            if (\\function_exists('pcntl_sigprocmask')) {\n                pcntl_sigprocmask(SIG_SETMASK, [], $this->blockedSignals);\n            }\n        }\n    }\n\n    public function tearDown(): void\n    {\n        parent::tearDown();\n\n        if ($this->asyncSignalHandling !== null) {\n            pcntl_async_signals($this->asyncSignalHandling);\n        }\n        if ($this->blockedSignals !== null) {\n            pcntl_sigprocmask(SIG_SETMASK, $this->blockedSignals);\n        }\n        if ($this->signalHandlers) {\n            pcntl_signal_dispatch();\n            foreach ($this->signalHandlers as $signo => $handler) {\n                pcntl_signal($signo, $handler);\n            }\n        }\n\n        unset($this->signalHandlers, $this->blockedSignals, $this->asyncSignalHandling);\n    }\n\n    private function setSignalHandler($signo, $handler = SIG_DFL)\n    {\n        if (\\function_exists('pcntl_signal_get_handler')) {\n            $this->signalHandlers[$signo] = pcntl_signal_get_handler($signo);\n        } else {\n            $this->signalHandlers[$signo] = SIG_DFL;\n        }\n        $this->assertTrue(pcntl_signal($signo, $handler));\n    }\n\n    public function testHandleSignal()\n    {\n        $logger = new Logger('test', [$handler = new TestHandler]);\n        $errHandler = new SignalHandler($logger);\n        $signo = 2;  // SIGINT.\n        $siginfo = ['signo' => $signo, 'errno' => 0, 'code' => 0];\n        $errHandler->handleSignal($signo, $siginfo);\n        $this->assertCount(1, $handler->getRecords());\n        $this->assertTrue($handler->hasCriticalRecords());\n        $records = $handler->getRecords();\n        $this->assertSame($siginfo, $records[0]['context']);\n    }\n\n    /**\n     * @depends testHandleSignal\n     * @requires extension pcntl\n     * @requires extension posix\n     * @requires function pcntl_signal\n     * @requires function pcntl_signal_dispatch\n     * @requires function posix_getpid\n     * @requires function posix_kill\n     */\n    public function testRegisterSignalHandler()\n    {\n        // SIGCONT and SIGURG should be ignored by default.\n        if (!\\defined('SIGCONT') || !\\defined('SIGURG')) {\n            $this->markTestSkipped('This test requires the SIGCONT and SIGURG pcntl constants.');\n        }\n\n        $this->setSignalHandler(SIGCONT, SIG_IGN);\n        $this->setSignalHandler(SIGURG, SIG_IGN);\n\n        $logger = new Logger('test', [$handler = new TestHandler]);\n        $errHandler = new SignalHandler($logger);\n        $pid = posix_getpid();\n\n        $this->assertTrue(posix_kill($pid, SIGURG));\n        $this->assertTrue(pcntl_signal_dispatch());\n        $this->assertCount(0, $handler->getRecords());\n\n        $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, false);\n\n        $this->assertTrue(posix_kill($pid, SIGCONT));\n        $this->assertTrue(pcntl_signal_dispatch());\n        $this->assertCount(0, $handler->getRecords());\n\n        $this->assertTrue(posix_kill($pid, SIGURG));\n        $this->assertTrue(pcntl_signal_dispatch());\n        $this->assertCount(1, $handler->getRecords());\n        $this->assertTrue($handler->hasInfoThatContains('SIGURG'));\n    }\n\n    /**\n     * @depends testRegisterSignalHandler\n     * @requires function pcntl_fork\n     * @requires function pcntl_sigprocmask\n     * @requires function pcntl_waitpid\n     */\n    #[DataProvider('defaultPreviousProvider')]\n    public function testRegisterDefaultPreviousSignalHandler($signo, $callPrevious, $expected)\n    {\n        $this->setSignalHandler($signo, SIG_DFL);\n\n        $path = tempnam(sys_get_temp_dir(), 'monolog-');\n        $this->assertNotFalse($path);\n\n        $pid = pcntl_fork();\n        if ($pid === 0) {  // Child.\n            $streamHandler = new StreamHandler($path);\n            $streamHandler->setFormatter($this->getIdentityFormatter());\n            $logger = new Logger('test', [$streamHandler]);\n            $errHandler = new SignalHandler($logger);\n            $errHandler->registerSignalHandler($signo, LogLevel::INFO, $callPrevious, false, false);\n            pcntl_sigprocmask(SIG_SETMASK, [SIGCONT]);\n            posix_kill(posix_getpid(), $signo);\n            pcntl_signal_dispatch();\n            // If $callPrevious is true, SIGINT should terminate by this line.\n            pcntl_sigprocmask(SIG_SETMASK, [], $oldset);\n            file_put_contents($path, implode(' ', $oldset), FILE_APPEND);\n            posix_kill(posix_getpid(), $signo);\n            pcntl_signal_dispatch();\n            exit();\n        }\n\n        $this->assertNotSame(-1, $pid);\n        $this->assertNotSame(-1, pcntl_waitpid($pid, $status));\n        $this->assertNotSame(-1, $status);\n        $this->assertSame($expected, file_get_contents($path));\n    }\n\n    public static function defaultPreviousProvider()\n    {\n        if (!\\defined('SIGCONT') || !\\defined('SIGINT') || !\\defined('SIGURG')) {\n            return [];\n        }\n\n        return [\n            [SIGINT, false, 'Program received signal SIGINT'.SIGCONT.'Program received signal SIGINT'],\n            [SIGINT, true, 'Program received signal SIGINT'],\n            [SIGURG, false, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'],\n            [SIGURG, true, 'Program received signal SIGURG'.SIGCONT.'Program received signal SIGURG'],\n        ];\n    }\n\n    /**\n     * @depends testRegisterSignalHandler\n     * @requires function pcntl_signal_get_handler\n     */\n    #[DataProvider('callablePreviousProvider')]\n    public function testRegisterCallablePreviousSignalHandler($callPrevious)\n    {\n        $this->setSignalHandler(SIGURG, SIG_IGN);\n\n        $logger = new Logger('test', [$handler = new TestHandler]);\n        $errHandler = new SignalHandler($logger);\n        $previousCalled = 0;\n        pcntl_signal(SIGURG, function ($signo, ?array $siginfo = null) use (&$previousCalled) {\n            ++$previousCalled;\n        });\n        $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, $callPrevious, false, false);\n        $this->assertTrue(posix_kill(posix_getpid(), SIGURG));\n        $this->assertTrue(pcntl_signal_dispatch());\n        $this->assertCount(1, $handler->getRecords());\n        $this->assertTrue($handler->hasInfoThatContains('SIGURG'));\n        $this->assertSame($callPrevious ? 1 : 0, $previousCalled);\n    }\n\n    public static function callablePreviousProvider()\n    {\n        return [\n            [false],\n            [true],\n        ];\n    }\n\n    /**\n     * @depends testRegisterDefaultPreviousSignalHandler\n     * @requires function pcntl_fork\n     * @requires function pcntl_waitpid\n     */\n    #[DataProvider('restartSyscallsProvider')]\n    public function testRegisterSyscallRestartingSignalHandler($restartSyscalls)\n    {\n        $this->setSignalHandler(SIGURG, SIG_IGN);\n\n        $parentPid = posix_getpid();\n        $microtime = microtime(true);\n\n        $pid = pcntl_fork();\n        if ($pid === 0) {  // Child.\n            usleep(100000);\n            posix_kill($parentPid, SIGURG);\n            usleep(100000);\n            exit();\n        }\n\n        $this->assertNotSame(-1, $pid);\n        $logger = new Logger('test', [$handler = new TestHandler]);\n        $errHandler = new SignalHandler($logger);\n        $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, $restartSyscalls, false);\n        if ($restartSyscalls) {\n            // pcntl_wait is expected to be restarted after the signal handler.\n            $this->assertNotSame(-1, pcntl_waitpid($pid, $status));\n        } else {\n            // pcntl_wait is expected to be interrupted when the signal handler is invoked.\n            $this->assertSame(-1, pcntl_waitpid($pid, $status));\n        }\n        $this->assertSame($restartSyscalls, microtime(true) - $microtime > 0.15);\n        $this->assertTrue(pcntl_signal_dispatch());\n        $this->assertCount(1, $handler->getRecords());\n        if ($restartSyscalls) {\n            // The child has already exited.\n            $this->assertSame(-1, pcntl_waitpid($pid, $status));\n        } else {\n            // The child has not exited yet.\n            $this->assertNotSame(-1, pcntl_waitpid($pid, $status));\n        }\n    }\n\n    public static function restartSyscallsProvider()\n    {\n        return [\n            [false],\n            [true],\n            [false],\n            [true],\n        ];\n    }\n\n    /**\n     * @depends testRegisterDefaultPreviousSignalHandler\n     * @requires function pcntl_async_signals\n     */\n    #[DataProvider('asyncProvider')]\n    public function testRegisterAsyncSignalHandler($initialAsync, $desiredAsync, $expectedBefore, $expectedAfter)\n    {\n        $this->setSignalHandler(SIGURG, SIG_IGN);\n        pcntl_async_signals($initialAsync);\n\n        $logger = new Logger('test', [$handler = new TestHandler]);\n        $errHandler = new SignalHandler($logger);\n        $errHandler->registerSignalHandler(SIGURG, LogLevel::INFO, false, false, $desiredAsync);\n        $this->assertTrue(posix_kill(posix_getpid(), SIGURG));\n        $this->assertCount($expectedBefore, $handler->getRecords());\n        $this->assertTrue(pcntl_signal_dispatch());\n        $this->assertCount($expectedAfter, $handler->getRecords());\n    }\n\n    public static function asyncProvider()\n    {\n        return [\n            [false, false, 0, 1],\n            [false, null, 0, 1],\n            [false, true, 1, 1],\n            [true, false, 0, 1],\n            [true, null, 1, 1],\n            [true, true, 1, 1],\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/Monolog/UtilsTest.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Monolog;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass UtilsTest extends \\PHPUnit_Framework_TestCase\n{\n    #[DataProvider('provideObjects')]\n    public function testGetClass(string $expected, object $object)\n    {\n        $this->assertSame($expected, Utils::getClass($object));\n    }\n\n    public static function provideObjects()\n    {\n        return [\n            ['stdClass', new \\stdClass()],\n            ['class@anonymous', new class {\n            }],\n            ['stdClass@anonymous', new class extends \\stdClass {\n            }],\n        ];\n    }\n\n    #[DataProvider('providePathsToCanonicalize')]\n    public function testCanonicalizePath(string $expected, string $input)\n    {\n        $this->assertSame($expected, Utils::canonicalizePath($input));\n    }\n\n    public static function providePathsToCanonicalize()\n    {\n        return [\n            ['/foo/bar', '/foo/bar'],\n            ['file://'.getcwd().'/bla', 'file://bla'],\n            [getcwd().'/bla', 'bla'],\n            [getcwd().'/./bla', './bla'],\n            ['file:///foo/bar', 'file:///foo/bar'],\n            ['any://foo', 'any://foo'],\n            ['\\\\\\\\network\\path', '\\\\\\\\network\\path'],\n        ];\n    }\n\n    #[DataProvider('providesHandleJsonErrorFailure')]\n    public function testHandleJsonErrorFailure(int $code, string $msg)\n    {\n        $this->expectException('RuntimeException', $msg);\n        Utils::handleJsonError($code, 'faked');\n    }\n\n    public static function providesHandleJsonErrorFailure()\n    {\n        return [\n            'depth' => [JSON_ERROR_DEPTH, 'Maximum stack depth exceeded'],\n            'state' => [JSON_ERROR_STATE_MISMATCH, 'Underflow or the modes mismatch'],\n            'ctrl' => [JSON_ERROR_CTRL_CHAR, 'Unexpected control character found'],\n            'default' => [-1, 'Unknown error'],\n        ];\n    }\n\n    /**\n     * @param mixed $in     Input\n     * @param mixed $expect Expected output\n     * @covers Monolog\\Formatter\\NormalizerFormatter::detectAndCleanUtf8\n     */\n    #[DataProvider('providesDetectAndCleanUtf8')]\n    public function testDetectAndCleanUtf8($in, $expect)\n    {\n        $reflMethod = new \\ReflectionMethod(Utils::class, 'detectAndCleanUtf8');\n        $args = [&$in];\n        $reflMethod->invokeArgs(null, $args);\n        $this->assertSame($expect, $in);\n    }\n\n    public static function providesDetectAndCleanUtf8()\n    {\n        $obj = new \\stdClass;\n\n        return [\n            'null' => [null, null],\n            'int' => [123, 123],\n            'float' => [123.45, 123.45],\n            'bool false' => [false, false],\n            'bool true' => [true, true],\n            'ascii string' => ['abcdef', 'abcdef'],\n            'latin9 string' => [\"\\xB1\\x31\\xA4\\xA6\\xA8\\xB4\\xB8\\xBC\\xBD\\xBE\\xFF\", '±1€ŠšŽžŒœŸÿ'],\n            'unicode string' => ['¤¦¨´¸¼½¾€ŠšŽžŒœŸ', '¤¦¨´¸¼½¾€ŠšŽžŒœŸ'],\n            'empty array' => [[], []],\n            'array' => [['abcdef'], ['abcdef']],\n            'object' => [$obj, $obj],\n        ];\n    }\n\n    public static function provideIniValuesToConvertToBytes()\n    {\n        return [\n            ['1', 1],\n            ['2', 2],\n            ['2.5', 2],\n            ['2.9', 2],\n            ['1B', false],\n            ['1X', false],\n            ['1K', 1024],\n            ['1 K', 1024],\n            ['   5 M  ', 5*1024*1024],\n            ['1G', 1073741824],\n            ['', false],\n            [null, false],\n            ['A', false],\n            ['AA', false],\n            ['B', false],\n            ['BB', false],\n            ['G', false],\n            ['GG', false],\n            ['-1', -1],\n            ['-123', -123],\n            ['-1A', -1],\n            ['-1B', -1],\n            ['-123G', -123],\n            ['-B', false],\n            ['-A', false],\n            ['-', false],\n            [true, false],\n            [false, false],\n        ];\n    }\n\n    #[DataProvider('provideIniValuesToConvertToBytes')]\n    public function testExpandIniShorthandBytes(string|null|bool $input, int|false $expected)\n    {\n        $result = Utils::expandIniShorthandBytes($input);\n        $this->assertEquals($expected, $result);\n    }\n}\n"
  },
  {
    "path": "tests/bootstrap.php",
    "content": "<?php declare(strict_types=1);\n\n/*\n * This file is part of the Monolog package.\n *\n * (c) Jordi Boggiano <j.boggiano@seld.be>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\ndate_default_timezone_set('UTC');\n\nrequire __DIR__.'/../vendor/autoload.php';\n\n// B.C. for PSR Log's old inheritance\n// see https://github.com/php-fig/log/pull/52\nif (!class_exists('\\\\PHPUnit_Framework_TestCase', true)) {\n    class_alias('\\\\PHPUnit\\\\Framework\\\\TestCase', '\\\\PHPUnit_Framework_TestCase');\n}\n"
  }
]