Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Seldaek/monolog
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2.9.2
Choose a base ref
...
head repository: Seldaek/monolog
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 3.0.0
Choose a head ref

Commits on Mar 21, 2022

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    22c8b19 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    400effd View commit details
  3. Remove SwiftMailerHandler

    Seldaek committed Mar 21, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b586dbe View commit details
  4. Code cleanups

    Seldaek committed Mar 21, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5eb9b8e View commit details
  5. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    657ff7f View commit details
  6. More cleanups

    Seldaek committed Mar 21, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6b5bd6a View commit details
  7. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0aef68c View commit details
  8. Cleanups

    Seldaek committed Mar 21, 2022

    Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    a3ba644 View commit details
  9. Remove at matcher

    Seldaek committed Mar 21, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    248673e View commit details

Commits on Apr 19, 2022

  1. Copy the full SHA
    2d006a8 View commit details
  2. Use never return typehint (#1654)

    The `never` return typehint was added in PHP 8.1 and can be used
    to indicate that a function will never return.
    
    RFC: https://wiki.php.net/rfc/noreturn_type
    hemberger authored Apr 19, 2022
    Copy the full SHA
    14f39fe View commit details
  3. Replace switch with match (#1655)

    The `match` expression is available as of PHP 8.0 and provides a more
    intuitive interface than `switch` when a single value is returned.
    
    Note that it performs an identity check (===) between the value and
    case instead of a weak equality check (==).
    
    https://www.php.net/manual/en/control-structures.match.php
    hemberger authored Apr 19, 2022
    Copy the full SHA
    e4bb5c5 View commit details
  4. CI fixes

    Seldaek committed Apr 19, 2022
    Copy the full SHA
    2695fa8 View commit details

Commits on Apr 20, 2022

  1. Move phpdoc to native types

    Seldaek committed Apr 20, 2022
    Copy the full SHA
    7952a83 View commit details
  2. Copy the full SHA
    6634bd9 View commit details

Commits on Apr 21, 2022

  1. Copy the full SHA
    0dac879 View commit details

Commits on Apr 22, 2022

  1. Copy the full SHA
    a7de8dd View commit details
  2. Copy the full SHA
    1c80bce View commit details

Commits on Apr 23, 2022

  1. Remove dead code

    Seldaek committed Apr 23, 2022
    Copy the full SHA
    733d6f1 View commit details
  2. Update upgrade notes

    Seldaek committed Apr 23, 2022
    Copy the full SHA
    ad732b3 View commit details

Commits on Apr 24, 2022

  1. Copy the full SHA
    6627c09 View commit details
  2. Copy the full SHA
    bd5968a View commit details
  3. Copy the full SHA
    c312cfd View commit details
  4. Fix phpdoc

    Seldaek committed Apr 24, 2022
    Copy the full SHA
    a6a66b1 View commit details

Commits on May 8, 2022

  1. Merge branch '2.x' into main

    Seldaek committed May 8, 2022
    Copy the full SHA
    1181473 View commit details
  2. Fix ES8 build

    Seldaek committed May 8, 2022
    Copy the full SHA
    8a35649 View commit details
  3. Drop prophecy usage

    Seldaek committed May 8, 2022
    Copy the full SHA
    acc142c View commit details
  4. replace deprecated method calls in GelfMessageFormatter (#1664)

    - the getter/setter methods for file, level and facility are deprecated in gelf v1.1
    - add those fields as additional instead, as suggested in the gelf spec (https://docs.graylog.org/v1/docs/gelf#gelf-payload-specification)
    - update tests to reflect changes
    jortgies authored May 8, 2022
    Copy the full SHA
    709cb93 View commit details
  5. Update upgrade notes

    Seldaek committed May 8, 2022
    Copy the full SHA
    de11fc3 View commit details
  6. Merge branch '2.x' into main

    Seldaek committed May 8, 2022
    Copy the full SHA
    5eca082 View commit details
  7. Update changelog

    Seldaek committed May 8, 2022
    Copy the full SHA
    a71c4e0 View commit details
  8. Update docs

    Seldaek committed May 8, 2022
    Copy the full SHA
    38fd8ef View commit details
  9. Merge branch '2.x' into main

    Seldaek committed May 8, 2022
    Copy the full SHA
    d381140 View commit details

Commits on May 10, 2022

  1. Copy the full SHA
    1dacc79 View commit details
  2. Merge branch '2.x' into main

    Seldaek committed May 10, 2022
    Copy the full SHA
    b3451b0 View commit details
  3. Update changelog

    Seldaek committed May 10, 2022
    Copy the full SHA
    60ad518 View commit details
Showing with 4,151 additions and 4,667 deletions.
  1. +1 −1 .gitattributes
  2. +0 −24 .github/workflows/continuous-integration.yml
  3. +1 −2 .github/workflows/lint.yml
  4. +2 −3 .github/workflows/phpstan.yml
  5. +1 −1 .gitignore
  6. +12 −11 .php_cs → .php-cs-fixer.php
  7. +43 −0 CHANGELOG.md
  8. +7 −4 README.md
  9. +93 −0 UPGRADE.md
  10. +10 −14 composer.json
  11. +17 −12 doc/01-usage.md
  12. +11 −10 doc/04-extending.md
  13. +13 −7 doc/message-structure.md
  14. +1 −2 doc/sockets.md
  15. +112 −0 phpstan-baseline.neon
  16. +8 −17 phpstan.neon.dist
  17. +9 −19 src/Monolog/Attribute/AsMonologProcessor.php
  18. +1 −4 src/Monolog/DateTimeImmutable.php
  19. +58 −86 src/Monolog/ErrorHandler.php
  20. +29 −25 src/Monolog/Formatter/ChromePHPFormatter.php
  21. +7 −9 src/Monolog/Formatter/ElasticaFormatter.php
  22. +5 −8 src/Monolog/Formatter/ElasticsearchFormatter.php
  23. +14 −20 src/Monolog/Formatter/FlowdockFormatter.php
  24. +11 −10 src/Monolog/Formatter/FluentdFormatter.php
  25. +7 −11 src/Monolog/Formatter/FormatterInterface.php
  26. +38 −45 src/Monolog/Formatter/GelfMessageFormatter.php
  27. +27 −27 src/Monolog/Formatter/HtmlFormatter.php
  28. +22 −38 src/Monolog/Formatter/JsonFormatter.php
  29. +19 −23 src/Monolog/Formatter/LineFormatter.php
  30. +8 −6 src/Monolog/Formatter/LogglyFormatter.php
  31. +13 −15 src/Monolog/Formatter/LogmaticFormatter.php
  32. +24 −25 src/Monolog/Formatter/LogstashFormatter.php
  33. +9 −11 src/Monolog/Formatter/MongoDBFormatter.php
  34. +41 −24 src/Monolog/Formatter/NormalizerFormatter.php
  35. +8 −10 src/Monolog/Formatter/ScalarFormatter.php
  36. +42 −44 src/Monolog/Formatter/WildfireFormatter.php
  37. +20 −30 src/Monolog/Handler/AbstractHandler.php
  38. +9 −18 src/Monolog/Handler/AbstractProcessingHandler.php
  39. +37 −39 src/Monolog/Handler/AbstractSyslogHandler.php
  40. +13 −26 src/Monolog/Handler/AmqpHandler.php
  41. +28 −31 src/Monolog/Handler/BrowserConsoleHandler.php
  42. +23 −25 src/Monolog/Handler/BufferHandler.php
  43. +22 −29 src/Monolog/Handler/ChromePHPHandler.php
  44. +29 −9 src/Monolog/Handler/CouchDBHandler.php
  45. +27 −27 src/Monolog/Handler/CubeHandler.php
  46. +5 −7 src/Monolog/Handler/Curl/Util.php
  47. +24 −44 src/Monolog/Handler/DeduplicationHandler.php
  48. +7 −7 src/Monolog/Handler/DoctrineCouchDBHandler.php
  49. +13 −37 src/Monolog/Handler/DynamoDbHandler.php
  50. +26 −13 src/Monolog/Handler/ElasticaHandler.php
  51. +27 −18 src/Monolog/Handler/ElasticsearchHandler.php
  52. +11 −11 src/Monolog/Handler/ErrorLogHandler.php
  53. +6 −9 src/Monolog/Handler/FallbackGroupHandler.php
  54. +47 −58 src/Monolog/Handler/FilterHandler.php
  55. +3 −5 src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php
  56. +17 −25 src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php
  57. +8 −12 src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php
  58. +49 −59 src/Monolog/Handler/FingersCrossedHandler.php
  59. +9 −15 src/Monolog/Handler/FirePHPHandler.php
  60. +10 −13 src/Monolog/Handler/FleepHookHandler.php
  61. +11 −17 src/Monolog/Handler/FlowdockHandler.php
  62. +1 −4 src/Monolog/Handler/FormattableHandlerInterface.php
  63. +4 −7 src/Monolog/Handler/FormattableHandlerTrait.php
  64. +8 −7 src/Monolog/Handler/GelfHandler.php
  65. +12 −16 src/Monolog/Handler/GroupHandler.php
  66. +2 −2 src/Monolog/Handler/Handler.php
  67. +9 −18 src/Monolog/Handler/HandlerInterface.php
  68. +14 −16 src/Monolog/Handler/HandlerWrapper.php
  69. +9 −10 src/Monolog/Handler/IFTTTHandler.php
  70. +10 −12 src/Monolog/Handler/InsightOpsHandler.php
  71. +10 −12 src/Monolog/Handler/LogEntriesHandler.php
  72. +22 −27 src/Monolog/Handler/LogglyHandler.php
  73. +19 −27 src/Monolog/Handler/LogmaticHandler.php
  74. +12 −16 src/Monolog/Handler/MailHandler.php
  75. +7 −9 src/Monolog/Handler/MandrillHandler.php
  76. +12 −16 src/Monolog/Handler/MongoDBHandler.php
  77. +13 −20 src/Monolog/Handler/NativeMailerHandler.php
  78. +36 −55 src/Monolog/Handler/NewRelicHandler.php
  79. +6 −4 src/Monolog/Handler/NoopHandler.php
  80. +13 −17 src/Monolog/Handler/NullHandler.php
  81. +16 −26 src/Monolog/Handler/OverflowHandler.php
  82. +91 −52 src/Monolog/Handler/PHPConsoleHandler.php
  83. +11 −16 src/Monolog/Handler/ProcessHandler.php
  84. +3 −4 src/Monolog/Handler/ProcessableHandlerInterface.php
  85. +7 −14 src/Monolog/Handler/ProcessableHandlerTrait.php
  86. +11 −19 src/Monolog/Handler/PsrHandler.php
  87. +62 −66 src/Monolog/Handler/PushoverHandler.php
  88. +22 −29 src/Monolog/Handler/RedisHandler.php
  89. +14 −16 src/Monolog/Handler/RedisPubSubHandler.php
  90. +38 −36 src/Monolog/Handler/RollbarHandler.php
  91. +19 −25 src/Monolog/Handler/RotatingFileHandler.php
  92. +22 −33 src/Monolog/Handler/SamplingHandler.php
  93. +8 −12 src/Monolog/Handler/SendGridHandler.php
  94. +60 −80 src/Monolog/Handler/Slack/SlackRecord.php
  95. +13 −19 src/Monolog/Handler/SlackHandler.php
  96. +11 −12 src/Monolog/Handler/SlackWebhookHandler.php
  97. +27 −46 src/Monolog/Handler/SocketHandler.php
  98. +9 −10 src/Monolog/Handler/SqsHandler.php
  99. +17 −36 src/Monolog/Handler/StreamHandler.php
  100. +0 −115 src/Monolog/Handler/SwiftMailerHandler.php
  101. +13 −15 src/Monolog/Handler/SymfonyMailerHandler.php
  102. +9 −11 src/Monolog/Handler/SyslogHandler.php
  103. +10 −21 src/Monolog/Handler/SyslogUdp/UdpSocket.php
  104. +22 −20 src/Monolog/Handler/SyslogUdpHandler.php
  105. +22 −37 src/Monolog/Handler/TelegramBotHandler.php
  106. +39 −75 src/Monolog/Handler/TestHandler.php
  107. +0 −1 src/Monolog/Handler/WebRequestRecognizerTrait.php
  108. +11 −12 src/Monolog/Handler/WhatFailureGroupHandler.php
  109. +32 −43 src/Monolog/Handler/ZendMonitorHandler.php
  110. +187 −0 src/Monolog/Level.php
  111. +101 −11 src/Monolog/LogRecord.php
  112. +129 −147 src/Monolog/Logger.php
  113. +13 −15 src/Monolog/Processor/GitProcessor.php
  114. +6 −5 src/Monolog/Processor/HostnameProcessor.php
  115. +21 −22 src/Monolog/Processor/IntrospectionProcessor.php
  116. +5 −3 src/Monolog/Processor/MemoryPeakUsageProcessor.php
  117. +2 −3 src/Monolog/Processor/MemoryProcessor.php
  118. +5 −3 src/Monolog/Processor/MemoryUsageProcessor.php
  119. +12 −14 src/Monolog/Processor/MercurialProcessor.php
  120. +5 −3 src/Monolog/Processor/ProcessIdProcessor.php
  121. +4 −7 src/Monolog/Processor/ProcessorInterface.php
  122. +14 −15 src/Monolog/Processor/PsrLogMessageProcessor.php
  123. +6 −4 src/Monolog/Processor/TagProcessor.php
  124. +14 −6 src/Monolog/Processor/UidProcessor.php
  125. +14 −13 src/Monolog/Processor/WebProcessor.php
  126. +5 −6 src/Monolog/Registry.php
  127. +1 −4 src/Monolog/ResettableInterface.php
  128. +10 −18 src/Monolog/SignalHandler.php
  129. +24 −27 src/Monolog/Test/TestCase.php
  130. +23 −33 src/Monolog/Utils.php
  131. +1 −1 tests/Monolog/Attribute/AsMonologProcessorTest.php
  132. +1 −2 tests/Monolog/ErrorHandlerTest.php
  133. +37 −47 tests/Monolog/Formatter/ChromePHPFormatterTest.php
  134. +11 −12 tests/Monolog/Formatter/ElasticaFormatterTest.php
  135. +11 −12 tests/Monolog/Formatter/ElasticsearchFormatterTest.php
  136. +6 −6 tests/Monolog/Formatter/FlowdockFormatterTest.php
  137. +3 −5 tests/Monolog/Formatter/FluentdFormatterTest.php
  138. +70 −97 tests/Monolog/Formatter/GelfMessageFormatterTest.php
  139. +47 −72 tests/Monolog/Formatter/JsonFormatterTest.php
  140. +102 −161 tests/Monolog/Formatter/LineFormatterTest.php
  141. +1 −1 tests/Monolog/Formatter/LogglyFormatterTest.php
  142. +1 −1 tests/Monolog/Formatter/LogmaticFormatterTest.php
  143. +49 −58 tests/Monolog/Formatter/LogstashFormatterTest.php
  144. +53 −68 tests/Monolog/Formatter/MongoDBFormatterTest.php
  145. +56 −69 tests/Monolog/Formatter/NormalizerFormatterTest.php
  146. +27 −32 tests/Monolog/Formatter/ScalarFormatterTest.php
  147. +33 −47 tests/Monolog/Formatter/WildfireFormatterTest.php
  148. +9 −9 tests/Monolog/Handler/AbstractHandlerTest.php
  149. +8 −8 tests/Monolog/Handler/AbstractProcessingHandlerTest.php
  150. +3 −3 tests/Monolog/Handler/AmqpHandlerTest.php
  151. +12 −12 tests/Monolog/Handler/BrowserConsoleHandlerTest.php
  152. +25 −25 tests/Monolog/Handler/BufferHandlerTest.php
  153. +17 −17 tests/Monolog/Handler/ChromePHPHandlerTest.php
  154. +2 −2 tests/Monolog/Handler/CouchDBHandlerTest.php
  155. +21 −26 tests/Monolog/Handler/DeduplicationHandlerTest.php
  156. +4 −4 tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php
  157. +8 −16 tests/Monolog/Handler/DynamoDbHandlerTest.php
  158. +12 −35 tests/Monolog/Handler/ElasticaHandlerTest.php
  159. +10 −29 tests/Monolog/Handler/ElasticsearchHandlerTest.php
  160. +4 −4 tests/Monolog/Handler/ErrorLogHandlerTest.php
  161. +3 −2 tests/Monolog/Handler/ExceptionTestHandler.php
  162. +16 −16 tests/Monolog/Handler/FallbackGroupHandlerTest.php
  163. +51 −50 tests/Monolog/Handler/FilterHandlerTest.php
  164. +45 −47 tests/Monolog/Handler/FingersCrossedHandlerTest.php
  165. +8 −8 tests/Monolog/Handler/FirePHPHandlerTest.php
  166. +4 −15 tests/Monolog/Handler/FleepHookHandlerTest.php
  167. +7 −10 tests/Monolog/Handler/FlowdockHandlerTest.php
  168. +18 −15 tests/Monolog/Handler/GelfHandlerTest.php
  169. +13 −13 tests/Monolog/Handler/GroupHandlerTest.php
  170. +5 −10 tests/Monolog/Handler/HandlerWrapperTest.php
  171. +8 −10 tests/Monolog/Handler/InsightOpsHandlerTest.php
  172. +7 −9 tests/Monolog/Handler/LogEntriesHandlerTest.php
  173. +7 −9 tests/Monolog/Handler/LogmaticHandlerTest.php
  174. +6 −6 tests/Monolog/Handler/MailHandlerTest.php
  175. +3 −3 tests/Monolog/Handler/MongoDBHandlerTest.php
  176. +3 −3 tests/Monolog/Handler/NativeMailerHandlerTest.php
  177. +24 −24 tests/Monolog/Handler/NewRelicHandlerTest.php
  178. +5 −7 tests/Monolog/Handler/NoopHandlerTest.php
  179. +3 −3 tests/Monolog/Handler/NullHandlerTest.php
  180. +20 −20 tests/Monolog/Handler/OverflowHandlerTest.php
  181. +7 −8 tests/Monolog/Handler/PHPConsoleHandlerTest.php
  182. +12 −16 tests/Monolog/Handler/ProcessHandlerTest.php
  183. +13 −18 tests/Monolog/Handler/PsrHandlerTest.php
  184. +24 −22 tests/Monolog/Handler/PushoverHandlerTest.php
  185. +11 −17 tests/Monolog/Handler/RedisHandlerTest.php
  186. +7 −13 tests/Monolog/Handler/RedisPubSubHandlerTest.php
  187. +10 −16 tests/Monolog/Handler/RollbarHandlerTest.php
  188. +6 −6 tests/Monolog/Handler/RotatingFileHandlerTest.php
  189. +80 −85 tests/Monolog/Handler/Slack/SlackRecordTest.php
  190. +29 −32 tests/Monolog/Handler/SlackHandlerTest.php
  191. +29 −29 tests/Monolog/Handler/SlackWebhookHandlerTest.php
  192. +29 −30 tests/Monolog/Handler/SocketHandlerTest.php
  193. +14 −18 tests/Monolog/Handler/StreamHandlerTest.php
  194. +0 −122 tests/Monolog/Handler/SwiftMailerHandlerTest.php
  195. +2 −2 tests/Monolog/Handler/SyslogHandlerTest.php
  196. +16 −15 tests/Monolog/Handler/SyslogUdpHandlerTest.php
  197. +4 −10 tests/Monolog/Handler/TelegramBotHandlerTest.php
  198. +17 −17 tests/Monolog/Handler/TestHandlerTest.php
  199. +1 −1 tests/Monolog/Handler/UdpSocketTest.php
  200. +16 −16 tests/Monolog/Handler/WhatFailureGroupHandlerTest.php
  201. +4 −9 tests/Monolog/Handler/ZendMonitorHandlerTest.php
  202. +119 −118 tests/Monolog/LoggerTest.php
  203. +2 −2 tests/Monolog/Processor/GitProcessorTest.php
  204. +4 −4 tests/Monolog/Processor/HostnameProcessorTest.php
  205. +15 −24 tests/Monolog/Processor/IntrospectionProcessorTest.php
  206. +5 −5 tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php
  207. +5 −5 tests/Monolog/Processor/MemoryUsageProcessorTest.php
  208. +3 −3 tests/Monolog/Processor/MercurialProcessorTest.php
  209. +4 −4 tests/Monolog/Processor/ProcessIdProcessorTest.php
  210. +6 −13 tests/Monolog/Processor/PsrLogMessageProcessorTest.php
  211. +4 −4 tests/Monolog/Processor/TagProcessorTest.php
  212. +1 −1 tests/Monolog/Processor/UidProcessorTest.php
  213. +16 −16 tests/Monolog/Processor/WebProcessorTest.php
  214. +33 −34 tests/Monolog/PsrLogCompatTest.php
  215. +39 −39 tests/Monolog/SignalHandlerTest.php
  216. +20 −25 tests/Monolog/UtilsTest.php
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -4,4 +4,4 @@
/phpstan.neon.dist export-ignore
/phpunit.xml.dist export-ignore
/_config.yml export-ignore
/UPGRADE.md
/UPGRADE.md export-ignore
24 changes: 0 additions & 24 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
@@ -15,10 +15,6 @@ jobs:

matrix:
php-version:
- "7.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"

dependencies: [highest]
@@ -27,9 +23,6 @@ jobs:
- "ubuntu-latest"

include:
- php-version: "7.2"
dependencies: lowest
operating-system: ubuntu-latest
- php-version: "8.1"
dependencies: lowest
operating-system: ubuntu-latest
@@ -98,10 +91,6 @@ jobs:
- "ubuntu-latest"

php-version:
- "7.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"

dependencies:
@@ -112,16 +101,6 @@ jobs:
- "7.0.0"
- "7.17.0"

exclude:
# php 7.3 is required
- php-version: "7.2"
es-version: "7.17.0"
# tests failing due an error in deprecated guzzlehttp/ringphp
- php-version: "7.3"
es-version: "7.0.0"
- php-version: "7.4"
es-version: "7.0.0"

steps:
- name: "Checkout"
uses: "actions/checkout@v2"
@@ -184,9 +163,6 @@ jobs:
- "ubuntu-latest"

php-version:
# ES 8 requires PHP 7.4+
- "7.4"
- "8.0"
- "8.1"

dependencies:
3 changes: 1 addition & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -13,8 +13,7 @@ jobs:
strategy:
matrix:
php-version:
- "7.2"
- "8.0"
- "8.1"

steps:
- name: "Checkout"
5 changes: 2 additions & 3 deletions .github/workflows/phpstan.yml
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
php-version:
- "8.0"
- "8.1"

steps:
- name: "Checkout"
@@ -44,8 +44,7 @@ jobs:
run: 'composer require ${{ env.COMPOSER_FLAGS }} mongodb/mongodb --dev --no-update'

- name: "Install latest dependencies"
# --ignore-platform-req=php here needed as long as elasticsearch/elasticsearch does not support php 8
run: "composer update ${{ env.COMPOSER_FLAGS }} --ignore-platform-req=php"
run: "composer update ${{ env.COMPOSER_FLAGS }}"

- name: Run PHPStan
run: composer phpstan
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -3,6 +3,6 @@ composer.phar
phpunit.xml
composer.lock
.DS_Store
.php_cs.cache
.php-cs-fixer.cache
.hg
.phpunit.result.cache
23 changes: 12 additions & 11 deletions .php_cs → .php-cs-fixer.php
Original file line number Diff line number Diff line change
@@ -17,26 +17,26 @@
->in(__DIR__.'/tests')
;

return PhpCsFixer\Config::create()
->setUsingCache(true)
->setRiskyAllowed(true)
->setRules(array(
$config = new PhpCsFixer\Config();
return $config->setRules(array(
'@PSR2' => true,
// some rules disabled as long as 1.x branch is maintained
'binary_operator_spaces' => array(
'array_syntax' => ['syntax' => 'short'],
'binary_operator_spaces' => [
'default' => null,
),
],
'blank_line_before_statement' => ['statements' => ['continue', 'declare', 'return', 'throw', 'try']],
'cast_spaces' => ['space' => 'single'],
'header_comment' => ['header' => $header],
'include' => true,
'class_attributes_separation' => ['elements' => ['method']],
'class_attributes_separation' => array('elements' => array('method' => 'one', 'trait_import' => 'none')),
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_consecutive_blank_lines' => true,
'no_extra_blank_lines' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],
'no_trailing_comma_in_singleline_array' => true,
'no_unused_imports' => true,
'no_whitespace_in_blank_line' => true,
@@ -49,13 +49,14 @@
//'phpdoc_scalar' => true,
'phpdoc_trim' => true,
//'phpdoc_types' => true,
'psr0' => true,
//'array_syntax' => array('syntax' => 'short'),
'psr_autoloading' => ['dir' => 'src'],
'declare_strict_types' => true,
'single_blank_line_before_namespace' => true,
'standardize_not_equals' => true,
'ternary_operator_spaces' => true,
'trailing_comma_in_multiline_array' => true,
'trailing_comma_in_multiline' => true,
))
->setUsingCache(true)
->setRiskyAllowed(true)
->setFinder($finder)
;
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
### 3.0.0 (2022-05-10)

Changes from RC1

- The `Monolog\LevelName` enum does not exist anymore, use `Monolog\Level->getName()` instead.

### 3.0.0-RC1 (2022-05-08)

This is mostly a cleanup release offering stronger type guarantees for integrators with the
array->object/enum changes, but there is no big new feature for end users.

See [UPGRADE notes](UPGRADE.md#300) for details on all breaking changes especially if you are extending/implementing Monolog classes/interfaces.

Noteworthy BC Breaks:

- The minimum supported PHP version is now `8.1.0`.
- Log records have been converted from an array to a [`Monolog\LogRecord` object](src/Monolog/LogRecord.php)
with public (and mostly readonly) properties. e.g. instead of doing
`$record['context']` use `$record->context`.
In formatters or handlers if you rather need an array to work with you can use `$record->toArray()`
to get back a Monolog 1/2 style record array. This will contain the enum values instead of enum cases
in the `level` and `level_name` keys to be more backwards compatible and use simpler data types.
- `FormatterInterface`, `HandlerInterface`, `ProcessorInterface`, etc. changed to contain `LogRecord $record`
instead of `array $record` parameter types. If you want to support multiple Monolog versions this should
be possible by type-hinting nothing, or `array|LogRecord` if you support PHP 8.0+. You can then code
against the $record using Monolog 2 style as LogRecord implements ArrayAccess for BC.
The interfaces do not require a `LogRecord` return type even where it would be applicable, but if you only
support Monolog 3 in integration code I would recommend you use `LogRecord` return types wherever fitting
to ensure forward compatibility as it may be added in Monolog 4.
- Log levels are now enums [`Monolog\Level`](src/Monolog/Level.php) and [`Monolog\LevelName`](src/Monolog/LevelName.php)
- Removed deprecated SwiftMailerHandler, migrate to SymfonyMailerHandler instead.
- `ResettableInterface::reset()` now requires a void return type.
- All properties have had types added, which may require you to do so as well if you extended
a Monolog class and declared the same property.

New deprecations:

- `Logger::DEBUG`, `Logger::ERROR`, etc. are now deprecated in favor of the `Monolog\Level` enum.
e.g. instead of `Logger::WARNING` use `Level::Warning` if you need to pass the enum case
to Monolog or one of its handlers, or `Level::Warning->value` if you need the integer
value equal to what `Logger::WARNING` was giving you.
- `Logger::getLevelName()` is now deprecated.

### 2.6.0 (2022-05-10)

* Deprecated `SwiftMailerHandler`, use `SymfonyMailerHandler` instead
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -28,12 +28,13 @@ $ composer require monolog/monolog
```php
<?php

use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
$log->pushHandler(new StreamHandler('path/to/your.log', Level::Warning));

// add records to the log
$log->warning('Foo');
@@ -50,7 +51,7 @@ $log->error('Bar');

## Support Monolog Financially

Get 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).
Get 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).

Tidelift 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.

@@ -64,11 +65,13 @@ can also add your own there if you publish one.

### Requirements

- Monolog `^2.0` works with PHP 7.2 or above, use Monolog `^1.25` for PHP 5.3+ support.
- Monolog `^3.0` works with PHP 8.1 or above.
- Monolog `^2.5` works with PHP 7.2 or above.
- 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.

### Support

Monolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 where possible to benefit from all the latest features and fixes.
Monolog 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.

### Submitting bugs and feature requests

93 changes: 93 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,96 @@
### 3.0.0

Overall / notable changes:

- The minimum supported PHP version is now `8.1.0`.
- `Monolog\Logger::API` can be used to distinguish between a Monolog `3`, `2` or `1`
install when writing integration code.
- Log records have been converted from an array to a [`Monolog\LogRecord` object](src/Monolog/LogRecord.php)
with public (and mostly readonly) properties. e.g. instead of doing
`$record['context']` use `$record->context`.
In formatters or handlers if you rather need an array to work with you can use `$record->toArray()`
to get back a Monolog 1/2 style record array. This will contain the enum values instead of enum cases
in the `level` and `level_name` keys to be more backwards compatible and use simpler data types.
- `FormatterInterface`, `HandlerInterface`, `ProcessorInterface`, etc. changed to contain `LogRecord $record`
instead of `array $record` parameter types. If you want to support multiple Monolog versions this should
be possible by type-hinting nothing, or `array|LogRecord` if you support PHP 8.0+. You can then code
against the $record using Monolog 2 style as LogRecord implements ArrayAccess for BC.
The interfaces do not require a `LogRecord` return type even where it would be applicable, but if you only
support Monolog 3 in integration code I would recommend you use `LogRecord` return types wherever fitting
to ensure forward compatibility as it may be added in Monolog 4.
- Log levels are now stored as an enum [`Monolog\Level`](src/Monolog/Level.php)
- All properties have had types added, which may require you to do so as well if you extended
a Monolog class and declared the same property.

#### Logger

- `Logger::DEBUG`, `Logger::ERROR`, etc. are now deprecated in favor of the `Level` enum.
e.g. instead of `Logger::WARNING` use `Level::Warning` if you need to pass the enum case
to Monolog or one of its handlers, or `Level::Warning->value` if you need the integer
value equal to what `Logger::WARNING` was giving you.
- `Logger::$levels` has been removed.
- `Logger::getLevels` has been removed in favor of `Monolog\Level::VALUES` or `Monolog\Level::cases()`.
- `setExceptionHandler` now requires a `Closure` instance and not just any `callable`.

#### HtmlFormatter

- If you redefined colors in the `$logLevels` property you must now override the
`getLevelColor` method instead.

#### NormalizerFormatter

- A new `normalizeRecord` method is available as an extension point which is called
only when converting the LogRecord to an array. You may need this if you overrode
`format` previously as `parent::format` now needs to receive a LogRecord still
so you cannot modify it before.

#### AbstractSyslogHandler

- If you redefined syslog levels in the `$logLevels` property you must now override the
`toSyslogPriority` method instead.

#### DynamoDbHandler

- Dropped support for AWS SDK v2

#### FilterHandler

- The factory callable to lazy load the nested handler must now be a `Closure` instance
and not just a `callable`.

#### FingersCrossedHandler

- The factory callable to lazy load the nested handler must now be a `Closure` instance
and not just a `callable`.

#### GelfHandler

- Dropped support for Gelf <1.1 and added support for graylog2/gelf-php v2.x. File, level
and facility are now passed in as additional fields (#1664)[https://github.com/Seldaek/monolog/pull/1664].

#### RollbarHandler

- If you redefined rollbar levels in the `$logLevels` property you must now override the
`toRollbarLevel` method instead.

#### SamplingHandler

- The factory callable to lazy load the nested handler must now be a `Closure` instance
and not just a `callable`.

#### SwiftMailerHandler

- Removed deprecated SwiftMailer handler, migrate to SymfonyMailerHandler instead.

#### ZendMonitorHandler

- If you redefined zend monitor levels in the `$levelMap` property you must now override the
`toZendMonitorLevel` method instead.

#### ResettableInterface

- `reset()` now requires a void return type.

### 2.0.0

- `Monolog\Logger::API` can be used to distinguish between a Monolog `1` and `2`
24 changes: 10 additions & 14 deletions composer.json
Original file line number Diff line number Diff line change
@@ -13,12 +13,12 @@
}
],
"require": {
"php": ">=7.2",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
"php": ">=8.1",
"psr/log": "^2.0 || ^3.0"
},
"require-dev": {
"ext-json": "*",
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"aws/aws-sdk-php": "^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7 || ^8",
"graylog2/gelf-php": "^1.4.2",
@@ -27,13 +27,12 @@
"mongodb/mongodb": "^1.8",
"php-amqplib/php-amqplib": "~2.4 || ^3",
"php-console/php-console": "^3.1.3",
"phpspec/prophecy": "^1.15",
"phpstan/phpstan": "^0.12.91",
"phpunit/phpunit": "^8.5.14",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-strict-rules": "^1.1",
"phpunit/phpunit": "^9.5.16",
"predis/predis": "^1.1",
"rollbar/rollbar": "^1.3 || ^2 || ^3",
"ruflin/elastica": "^7",
"swiftmailer/swiftmailer": "^5.3|^6.0",
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
@@ -61,11 +60,11 @@
"psr-4": {"Monolog\\": "tests/Monolog"}
},
"provide": {
"psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
"psr/log-implementation": "3.0.0"
},
"extra": {
"branch-alias": {
"dev-main": "2.x-dev"
"dev-main": "3.x-dev"
}
},
"scripts": {
@@ -75,9 +74,6 @@
"config": {
"lock": false,
"sort-packages": true,
"platform-check": false,
"allow-plugins": {
"composer/package-versions-deprecated": true
}
"platform-check": false
}
}
29 changes: 17 additions & 12 deletions doc/01-usage.md
Original file line number Diff line number Diff line change
@@ -83,14 +83,15 @@ Here is a basic setup to log to a file and to firephp on the DEBUG level:
```php
<?php

use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;

// Create the logger
$logger = new Logger('my_logger');
// Now add some handlers
$logger->pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG));
$logger->pushHandler(new StreamHandler(__DIR__.'/my_app.log', Level::Debug));
$logger->pushHandler(new FirePHPHandler());

// You can now use your logger
@@ -140,14 +141,14 @@ write a processor adding some dummy data in the record:
<?php

$logger->pushProcessor(function ($record) {
$record['extra']['dummy'] = 'Hello world!';
$record->extra['dummy'] = 'Hello world!';

return $record;
});
```

Monolog provides some built-in processors that can be used in your project.
Look at the [dedicated chapter](https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md#processors) for the list.
Look at the [dedicated chapter](https://github.com/Seldaek/monolog/blob/main/doc/02-handlers-formatters-processors.md#processors) for the list.

> Tip: processors can also be registered on a specific handler instead of
the logger to apply only for this handler.
@@ -165,12 +166,13 @@ You can easily grep through the log files filtering this or that channel.
```php
<?php

use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;

// Create some handlers
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$stream = new StreamHandler(__DIR__.'/my_app.log', Level::Debug);
$firephp = new FirePHPHandler();

// Create the main logger of the app
@@ -190,23 +192,26 @@ $securityLogger = $logger->withName('security');
## Customizing the log format

In Monolog it's easy to customize the format of the logs written into files,
sockets, mails, databases and other handlers; by the use of "Formatters".
sockets, mails, databases and other handlers; by the use of "Formatters".

As mentioned before, a *Formatter* is attached to a *Handler*, and as a general convention, most of the handlers use the
```php
$record['formatted']
$record->formatted
```
field in the log record to store its formatted value. Again, this field depends on the implementation of the *Handler* but is a good idea to **stick into the good practices and conventions of the project**.
property in the log record to store its formatted value.

You can choose between predefined formatter classes or write your own (e.g. a multiline text file for human-readable output).

> Note:
>
>
> A very useful formatter to look at, is the `LineFormatter`.
>
>
> This formatter, as its name might indicate, is able to return a lineal string representation of the log record provided.
>
> It is also capable to interpolate values from the log record, into the output format template used by the formatter to generate 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 string using the form %value%, e.g: "'%context.foo% => %extra.foo%'", in this example the values $record["context"]["foo"] and $record["extra"]["foo"] will be rendered as part of the final result.
>
> It is also capable to interpolate values from the log record, into the output format template used by the formatter to generate
> 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
> string using the form %value%, e.g: "'%context.foo% => %extra.foo%'" , in this example the values `$record->context["foo"]`
> and `$record->extra["foo"]` will be rendered as part of the final result.
In the following example, we demonstrate how to:
1. Create a `LineFormatter` instance and set a custom output format template.
@@ -229,7 +234,7 @@ $output = "%datetime% > %level_name% > %message% %context% %extra%\n";
$formatter = new LineFormatter($output, $dateFormat);

// Create a handler
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$stream = new StreamHandler(__DIR__.'/my_app.log', Level::Debug);
$stream->setFormatter($formatter);

// bind it to a logger object
21 changes: 11 additions & 10 deletions doc/04-extending.md
Original file line number Diff line number Diff line change
@@ -21,32 +21,33 @@ abstract class provided by Monolog to keep things DRY.
```php
<?php

use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;

class PDOHandler extends AbstractProcessingHandler
{
private $initialized = false;
private $pdo;
private $statement;
private bool $initialized = false;
private PDO $pdo;
private PDOStatement $statement;

public function __construct(PDO $pdo, $level = Logger::DEBUG, bool $bubble = true)
public function __construct(PDO $pdo, int|string|Level $level = Level::Debug, bool $bubble = true)
{
$this->pdo = $pdo;
parent::__construct($level, $bubble);
}

protected function write(array $record): void
protected function write(LogRecord $record): void
{
if (!$this->initialized) {
$this->initialize();
}

$this->statement->execute(array(
'channel' => $record['channel'],
'level' => $record['level'],
'message' => $record['formatted'],
'time' => $record['datetime']->format('U'),
'channel' => $record->channel,
'level' => $record->level,
'message' => $record->formatted,
'time' => $record->datetime->format('U'),
));
}

@@ -78,6 +79,6 @@ $logger->info('My logger is now ready');

The `Monolog\Handler\AbstractProcessingHandler` class provides most of the
logic needed for the handler, including the use of processors and the formatting
of the record (which is why we use ``$record['formatted']`` instead of ``$record['message']``).
of the record (which is why we use ``$record->formatted`` instead of ``$record->message``).

&larr; [Utility classes](03-utilities.md)
20 changes: 13 additions & 7 deletions doc/message-structure.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
# Log message structure

Within monolog log messages are passed around as arrays, for example to processors or handlers.
The table below describes which keys are always available for every log message.
Within monolog log messages are passed around as [Monolog\LogRecord](../src/Monolog/LogRecord.php) objects,
for example to processors or handlers.

key | type | description
The table below describes the properties available.

property | type | description
-----------|---------------------------|-------------------------------------------------------------------------------
message | 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".
level | int | Severity of the log message. See log levels described in [01-usage.md](01-usage.md#log-levels).
level_name | string | String representation of log level.
level | Monolog\Level case | Severity of the log message. See log levels described in [01-usage.md](01-usage.md#log-levels).
context | array | Arbitrary data passed with the construction of the message. For example the username of the current user or their IP address.
channel | string | The channel this message was logged to. This is the name that was passed when the logger was created with `new Logger($channel)`.
datetime | Monolog\DateTimeImmutable | Date and time when the message was logged. Class extends `\DateTimeImmutable`.
extra | array | A placeholder array where processors can put additional data. Always available, but empty if there are no processors registered.

At 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.
The main difference is that `context` can be supplied in user land (it is the 3rd parameter to `Logger::addRecord()`) whereas `extra` is internal only and 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`.
The 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
and 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`.

All properties except `extra` are read-only.

> Note: For BC reasons with Monolog 1 and 2 which used arrays, `LogRecord` implements `ArrayAccess` so you can access the above properties
> using `$record['message']` for example, with the notable exception of `level->getName()` which must be referred to as `level_name` for BC.
3 changes: 1 addition & 2 deletions doc/sockets.md
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ $handler = new SocketHandler('unix:///var/log/httpd_app_log.socket');
$handler->setPersistent(true);

// Now add the handler
$logger->pushHandler($handler, Logger::DEBUG);
$logger->pushHandler($handler, Level::Debug);

// You can now use your logger
$logger->info('My logger is now ready');
@@ -36,4 +36,3 @@ $logger->info('My logger is now ready');
In this example, using syslog-ng, you should see the log on the log server:

cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] []

112 changes: 112 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
parameters:
ignoreErrors:
-
message: "#^Property Monolog\\\\ErrorHandler\\:\\:\\$reservedMemory is never read, only written\\.$#"
count: 1
path: src/Monolog/ErrorHandler.php

-
message: "#^Cannot access offset 'table' on array\\<array\\|bool\\|float\\|int\\|string\\|null\\>\\|bool\\|float\\|int\\|object\\|string\\.$#"
count: 1
path: src/Monolog/Formatter/WildfireFormatter.php

-
message: "#^Return type \\(array\\<array\\|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\\|bool\\|float\\|int\\|string\\|null\\>\\|bool\\|float\\|int\\|string\\|null\\) of method Monolog\\\\Formatter\\\\NormalizerFormatter\\:\\:normalize\\(\\)$#"
count: 1
path: src/Monolog/Formatter/WildfireFormatter.php

-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Monolog/Handler/BrowserConsoleHandler.php

-
message: "#^Instanceof between Monolog\\\\Handler\\\\HandlerInterface and Monolog\\\\Handler\\\\HandlerInterface will always evaluate to true\\.$#"
count: 1
path: src/Monolog/Handler/FilterHandler.php

-
message: "#^Instanceof between Monolog\\\\Handler\\\\HandlerInterface and Monolog\\\\Handler\\\\HandlerInterface will always evaluate to true\\.$#"
count: 1
path: src/Monolog/Handler/FingersCrossedHandler.php

-
message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
count: 1
path: src/Monolog/Handler/FingersCrossedHandler.php

-
message: "#^Call to method setBody\\(\\) on an unknown class Swift_Message\\.$#"
count: 1
path: src/Monolog/Handler/MandrillHandler.php

-
message: "#^Call to method setDate\\(\\) on an unknown class Swift_Message\\.$#"
count: 1
path: src/Monolog/Handler/MandrillHandler.php

-
message: "#^Class Swift_Message not found\\.$#"
count: 2
path: src/Monolog/Handler/MandrillHandler.php

-
message: "#^Cloning object of an unknown class Swift_Message\\.$#"
count: 1
path: src/Monolog/Handler/MandrillHandler.php

-
message: "#^Instanceof between Swift_Message and Swift_Message will always evaluate to true\\.$#"
count: 1
path: src/Monolog/Handler/MandrillHandler.php

-
message: "#^Parameter \\$message of method Monolog\\\\Handler\\\\MandrillHandler\\:\\:__construct\\(\\) has invalid type Swift_Message\\.$#"
count: 3
path: src/Monolog/Handler/MandrillHandler.php

-
message: "#^Property Monolog\\\\Handler\\\\MandrillHandler\\:\\:\\$message has unknown class Swift_Message as its type\\.$#"
count: 1
path: src/Monolog/Handler/MandrillHandler.php

-
message: "#^Method Monolog\\\\Handler\\\\PHPConsoleHandler\\:\\:initOptions\\(\\) should return array\\{enabled\\: bool, classesPartialsTraceIgnore\\: array\\<string\\>, debugTagsKeysInContext\\: array\\<int\\|string\\>, useOwnErrorsHandler\\: bool, useOwnExceptionsHandler\\: bool, sourcesBasePath\\: string\\|null, registerHelper\\: bool, serverEncoding\\: string\\|null, \\.\\.\\.\\} but returns non\\-empty\\-array\\<'classesPartialsTrac…'\\|'dataStorage'\\|'debugTagsKeysInCont…'\\|'detectDumpTraceAndS…'\\|'dumperDetectCallbac…'\\|'dumperDumpSizeLimit'\\|'dumperItemsCountLim…'\\|'dumperItemSizeLimit'\\|'dumperLevelLimit'\\|'enabled'\\|'enableEvalListener'\\|'enableSslOnlyMode'\\|'headersLimit'\\|'ipMasks'\\|'password'\\|'registerHelper'\\|'serverEncoding'\\|'sourcesBasePath'\\|'useOwnErrorsHandler'\\|'useOwnExceptionsHan…', array\\<int\\|string\\>\\|bool\\|int\\|PhpConsole\\\\Storage\\|string\\|null\\>\\.$#"
count: 1
path: src/Monolog/Handler/PHPConsoleHandler.php

-
message: "#^Instanceof between Monolog\\\\Handler\\\\HandlerInterface and Monolog\\\\Handler\\\\HandlerInterface will always evaluate to true\\.$#"
count: 1
path: src/Monolog/Handler/SamplingHandler.php

-
message: "#^Variable property access on \\$this\\(Monolog\\\\LogRecord\\)\\.$#"
count: 4
path: src/Monolog/LogRecord.php

-
message: "#^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\\(\\)$#"
count: 1
path: src/Monolog/Logger.php

-
message: "#^Comparison operation \"\\<\" between int\\<1, 32\\> and 1 is always false\\.$#"
count: 1
path: src/Monolog/Processor/UidProcessor.php

-
message: "#^Comparison operation \"\\>\" between int\\<1, 32\\> and 32 is always false\\.$#"
count: 1
path: src/Monolog/Processor/UidProcessor.php

-
message: "#^Method Monolog\\\\Processor\\\\UidProcessor\\:\\:generateUid\\(\\) should return non\\-empty\\-string but returns string\\.$#"
count: 1
path: src/Monolog/Processor/UidProcessor.php

-
message: "#^Parameter \\#1 \\$length of function random_bytes expects int\\<1, max\\>, int given\\.$#"
count: 1
path: src/Monolog/Processor/UidProcessor.php

25 changes: 8 additions & 17 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ parameters:
level: 8

treatPhpDocTypesAsCertain: false
reportUnmatchedIgnoredErrors: false
reportUnmatchedIgnoredErrors: true

paths:
- src/
@@ -19,22 +19,13 @@ parameters:
paths:
- src/Monolog/Formatter/LineFormatter.php

# blocked until we only support php8+
- '#Parameter \#1 \$socket of function (socket_close|socket_sendto|socket_send) expects Socket, resource\|Socket(\|null)? given\.#'
- '#Parameter \#1 \$handle of function (curl_exec|curl_close|curl_error|curl_errno|curl_setopt) expects CurlHandle, CurlHandle\|resource(\|null)? given\.#'
- message: '#Method Monolog\\Handler\\LogglyHandler::loadCurlHandle\(\) never returns resource so it can be removed from the return typehint.#'
paths:
- src/Monolog/Handler/LogglyHandler.php

# blocked by https://github.com/phpstan/phpstan/issues/5091
- '#has unknown class Monolog\\Handler\\Record#'
- '#::processRecord\(\) should return array#'
- '#::processRecord\(\) has invalid type#'
- '#::processRecord\(\) return type has no value type#'
- '#::processRecord\(\) has parameter \$record with no value type#'
- '#::popProcessor\(\) should return callable#'
- '#Parameter \#1 \$ of callable \(callable\(Monolog\\Handler\\Record\): Monolog\\Handler\\Record\)#'
- '#is incompatible with native type array.#'
# can be removed when rollbar/rollbar can be added as dev require again (needs to allow monolog 3.x)
- '#Rollbar\\RollbarLogger#'

# legacy elasticsearch namespace failures
- '# Elastic\\Elasticsearch\\#'

includes:
- phpstan-baseline.neon
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/phpstan/phpstan-deprecation-rules/rules.neon
28 changes: 9 additions & 19 deletions src/Monolog/Attribute/AsMonologProcessor.php
Original file line number Diff line number Diff line change
@@ -13,34 +13,24 @@

/**
* A reusable attribute to help configure a class or a method as a processor.
*
*
* Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer.
*
*
* Using it with the Monolog library only has no effect at all: processors should still be turned into a callable if
* needed and manually pushed to the loggers and to the processable handlers.
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AsMonologProcessor
{
/** @var string|null */
public $channel = null;
/** @var string|null */
public $handler = null;
/** @var string|null */
public $method = null;

/**
* @param string|null $channel The logging channel the processor should be pushed to.
* @param string|null $handler The handler the processor should be pushed to.
* @param string|null $method The method that processes the records (if the attribute is used at the class level).
* @param string|null $channel The logging channel the processor should be pushed to.
* @param string|null $handler The handler the processor should be pushed to.
* @param string|null $method The method that processes the records (if the attribute is used at the class level).
*/
public function __construct(
?string $channel = null,
?string $handler = null,
?string $method = null
public readonly ?string $channel = null,
public readonly ?string $handler = null,
public readonly ?string $method = null
) {
$this->channel = $channel;
$this->handler = $handler;
$this->method = $method;
}
}
}
5 changes: 1 addition & 4 deletions src/Monolog/DateTimeImmutable.php
Original file line number Diff line number Diff line change
@@ -21,10 +21,7 @@
*/
class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable
{
/**
* @var bool
*/
private $useMicroseconds;
private bool $useMicroseconds;

public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null)
{
144 changes: 58 additions & 86 deletions src/Monolog/ErrorHandler.php
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@

namespace Monolog;

use Closure;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

@@ -25,43 +26,40 @@
*/
class ErrorHandler
{
/** @var LoggerInterface */
private $logger;
private Closure|null $previousExceptionHandler = null;

/** @var ?callable */
private $previousExceptionHandler = null;
/** @var array<class-string, LogLevel::*> an array of class name to LogLevel::* constant mapping */
private $uncaughtExceptionLevelMap = [];
private array $uncaughtExceptionLevelMap = [];

/** @var Closure|true|null */
private Closure|bool|null $previousErrorHandler = null;

/** @var callable|true|null */
private $previousErrorHandler = null;
/** @var array<int, LogLevel::*> an array of E_* constant to LogLevel::* constant mapping */
private $errorLevelMap = [];
/** @var bool */
private $handleOnlyReportedErrors = true;

/** @var bool */
private $hasFatalErrorHandler = false;
/** @var LogLevel::* */
private $fatalLevel = LogLevel::ALERT;
/** @var ?string */
private $reservedMemory = null;
/** @var ?mixed */
private $lastFatalTrace;
/** @var int[] */
private static $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];

public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
private array $errorLevelMap = [];

private bool $handleOnlyReportedErrors = true;

private bool $hasFatalErrorHandler = false;

private string $fatalLevel = LogLevel::ALERT;

private string|null $reservedMemory = null;

/** @var mixed|null */
private $lastFatalTrace = null;

private const FATAL_ERRORS = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];

public function __construct(
private LoggerInterface $logger
) {
}

/**
* Registers a new ErrorHandler for a given Logger
*
* By default it will handle errors, exceptions and fatal errors
*
* @param LoggerInterface $logger
* @param array<int, LogLevel::*>|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
* @param array<class-string, LogLevel::*>|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling
* @param LogLevel::*|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling
@@ -99,8 +97,8 @@ public function registerExceptionHandler(array $levelMap = [], bool $callPreviou
$this->uncaughtExceptionLevelMap[$class] = $level;
}
}
if ($callPrevious && $prev) {
$this->previousExceptionHandler = $prev;
if ($callPrevious && null !== $prev) {
$this->previousExceptionHandler = $prev(...);
}

return $this;
@@ -112,10 +110,10 @@ public function registerExceptionHandler(array $levelMap = [], bool $callPreviou
*/
public function registerErrorHandler(array $levelMap = [], bool $callPrevious = true, int $errorTypes = -1, bool $handleOnlyReportedErrors = true): self
{
$prev = set_error_handler([$this, 'handleError'], $errorTypes);
$prev = set_error_handler($this->handleError(...), $errorTypes);
$this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
if ($callPrevious) {
$this->previousErrorHandler = $prev ?: true;
$this->previousErrorHandler = $prev !== null ? $prev(...) : true;
} else {
$this->previousErrorHandler = null;
}
@@ -131,7 +129,7 @@ public function registerErrorHandler(array $levelMap = [], bool $callPrevious =
*/
public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self
{
register_shutdown_function([$this, 'handleFatalError']);
register_shutdown_function($this->handleFatalError(...));

$this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
$this->fatalLevel = null === $level ? LogLevel::ALERT : $level;
@@ -175,10 +173,7 @@ protected function defaultErrorLevelMap(): array
];
}

/**
* @phpstan-return never
*/
private function handleException(\Throwable $e): void
private function handleException(\Throwable $e): never
{
$level = LogLevel::ERROR;
foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) {
@@ -194,30 +189,25 @@ private function handleException(\Throwable $e): void
['exception' => $e]
);

if ($this->previousExceptionHandler) {
if (null !== $this->previousExceptionHandler) {
($this->previousExceptionHandler)($e);
}

if (!headers_sent() && !ini_get('display_errors')) {
if (!headers_sent() && !(bool) ini_get('display_errors')) {
http_response_code(500);
}

exit(255);
}

/**
* @private
*
* @param mixed[] $context
*/
public function handleError(int $code, string $message, string $file = '', int $line = 0, ?array $context = []): bool
private function handleError(int $code, string $message, string $file = '', int $line = 0): bool
{
if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) {
if ($this->handleOnlyReportedErrors && 0 === (error_reporting() & $code)) {
return false;
}

// fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
if (!$this->hasFatalErrorHandler || !in_array($code, self::FATAL_ERRORS, true)) {
$level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL;
$this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]);
} else {
@@ -228,8 +218,9 @@ public function handleError(int $code, string $message, string $file = '', int $

if ($this->previousErrorHandler === true) {
return false;
} elseif ($this->previousErrorHandler) {
return (bool) ($this->previousErrorHandler)($code, $message, $file, $line, $context);
}
if ($this->previousErrorHandler instanceof Closure) {
return (bool) ($this->previousErrorHandler)($code, $message, $file, $line);
}

return true;
@@ -243,7 +234,7 @@ public function handleFatalError(): void
$this->reservedMemory = '';

$lastError = error_get_last();
if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) {
if (is_array($lastError) && in_array($lastError['type'], self::FATAL_ERRORS, true)) {
$this->logger->log(
$this->fatalLevel,
'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
@@ -258,44 +249,25 @@ public function handleFatalError(): void
}
}

/**
* @param int $code
*/
private static function codeToString($code): string
private static function codeToString(int $code): string
{
switch ($code) {
case E_ERROR:
return 'E_ERROR';
case E_WARNING:
return 'E_WARNING';
case E_PARSE:
return 'E_PARSE';
case E_NOTICE:
return 'E_NOTICE';
case E_CORE_ERROR:
return 'E_CORE_ERROR';
case E_CORE_WARNING:
return 'E_CORE_WARNING';
case E_COMPILE_ERROR:
return 'E_COMPILE_ERROR';
case E_COMPILE_WARNING:
return 'E_COMPILE_WARNING';
case E_USER_ERROR:
return 'E_USER_ERROR';
case E_USER_WARNING:
return 'E_USER_WARNING';
case E_USER_NOTICE:
return 'E_USER_NOTICE';
case E_STRICT:
return 'E_STRICT';
case E_RECOVERABLE_ERROR:
return 'E_RECOVERABLE_ERROR';
case E_DEPRECATED:
return 'E_DEPRECATED';
case E_USER_DEPRECATED:
return 'E_USER_DEPRECATED';
}

return 'Unknown PHP error';
return match ($code) {
E_ERROR => 'E_ERROR',
E_WARNING => 'E_WARNING',
E_PARSE => 'E_PARSE',
E_NOTICE => 'E_NOTICE',
E_CORE_ERROR => 'E_CORE_ERROR',
E_CORE_WARNING => 'E_CORE_WARNING',
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
E_USER_ERROR => 'E_USER_ERROR',
E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE',
E_STRICT => 'E_STRICT',
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED',
default => 'Unknown PHP error',
};
}
}
54 changes: 29 additions & 25 deletions src/Monolog/Formatter/ChromePHPFormatter.php
Original file line number Diff line number Diff line change
@@ -11,7 +11,8 @@

namespace Monolog\Formatter;

use Monolog\Logger;
use Monolog\Level;
use Monolog\LogRecord;

/**
* Formats a log message according to the ChromePHP array format
@@ -23,52 +24,55 @@ class ChromePHPFormatter implements FormatterInterface
/**
* Translates Monolog log levels to Wildfire levels.
*
* @var array<int, 'log'|'info'|'warn'|'error'>
* @return 'log'|'info'|'warn'|'error'
*/
private $logLevels = [
Logger::DEBUG => 'log',
Logger::INFO => 'info',
Logger::NOTICE => 'info',
Logger::WARNING => 'warn',
Logger::ERROR => 'error',
Logger::CRITICAL => 'error',
Logger::ALERT => 'error',
Logger::EMERGENCY => 'error',
];
private function toWildfireLevel(Level $level): string
{
return match ($level) {
Level::Debug => 'log',
Level::Info => 'info',
Level::Notice => 'info',
Level::Warning => 'warn',
Level::Error => 'error',
Level::Critical => 'error',
Level::Alert => 'error',
Level::Emergency => 'error',
};
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function format(array $record)
public function format(LogRecord $record)
{
// Retrieve the line and file if set and remove them from the formatted extra
$backtrace = 'unknown';
if (isset($record['extra']['file'], $record['extra']['line'])) {
$backtrace = $record['extra']['file'].' : '.$record['extra']['line'];
unset($record['extra']['file'], $record['extra']['line']);
if (isset($record->extra['file'], $record->extra['line'])) {
$backtrace = $record->extra['file'].' : '.$record->extra['line'];
unset($record->extra['file'], $record->extra['line']);
}

$message = ['message' => $record['message']];
if ($record['context']) {
$message['context'] = $record['context'];
$message = ['message' => $record->message];
if (\count($record->context) > 0) {
$message['context'] = $record->context;
}
if ($record['extra']) {
$message['extra'] = $record['extra'];
if (\count($record->extra) > 0) {
$message['extra'] = $record->extra;
}
if (count($message) === 1) {
$message = reset($message);
}

return [
$record['channel'],
$record->channel,
$message,
$backtrace,
$this->logLevels[$record['level']],
$this->toWildfireLevel($record->level),
];
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function formatBatch(array $records)
{
16 changes: 7 additions & 9 deletions src/Monolog/Formatter/ElasticaFormatter.php
Original file line number Diff line number Diff line change
@@ -12,25 +12,24 @@
namespace Monolog\Formatter;

use Elastica\Document;
use Monolog\LogRecord;

/**
* Format a log message into an Elastica Document
*
* @author Jelle Vink <jelle.vink@gmail.com>
*
* @phpstan-import-type Record from \Monolog\Logger
*/
class ElasticaFormatter extends NormalizerFormatter
{
/**
* @var string Elastic search index name
*/
protected $index;
protected string $index;

/**
* @var ?string Elastic search document type
* @var string|null Elastic search document type
*/
protected $type;
protected string|null $type;

/**
* @param string $index Elastic Search index name
@@ -46,9 +45,9 @@ public function __construct(string $index, ?string $type)
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function format(array $record)
public function format(LogRecord $record)
{
$record = parent::format($record);

@@ -72,14 +71,13 @@ public function getType(): string
/**
* Convert a log message into an Elastica Document
*
* @phpstan-param Record $record
* @param mixed[] $record
*/
protected function getDocument(array $record): Document
{
$document = new Document();
$document->setData($record);
if (method_exists($document, 'setType')) {
/** @phpstan-ignore-next-line */
$document->setType($this->type);
}
$document->setIndex($this->index);
13 changes: 5 additions & 8 deletions src/Monolog/Formatter/ElasticsearchFormatter.php
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
namespace Monolog\Formatter;

use DateTimeInterface;
use Monolog\LogRecord;

/**
* Format a log message into an Elasticsearch record
@@ -23,12 +24,12 @@ class ElasticsearchFormatter extends NormalizerFormatter
/**
* @var string Elasticsearch index name
*/
protected $index;
protected string $index;

/**
* @var string Elasticsearch record type
*/
protected $type;
protected string $type;

/**
* @param string $index Elasticsearch index name
@@ -44,9 +45,9 @@ public function __construct(string $index, string $type)
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function format(array $record)
public function format(LogRecord $record)
{
$record = parent::format($record);

@@ -55,8 +56,6 @@ public function format(array $record)

/**
* Getter index
*
* @return string
*/
public function getIndex(): string
{
@@ -65,8 +64,6 @@ public function getIndex(): string

/**
* Getter type
*
* @return string
*/
public function getType(): string
{
34 changes: 14 additions & 20 deletions src/Monolog/Formatter/FlowdockFormatter.php
Original file line number Diff line number Diff line change
@@ -11,22 +11,18 @@

namespace Monolog\Formatter;

use Monolog\LogRecord;

/**
* formats the record to be used in the FlowdockHandler
*
* @author Dominik Liebler <liebler.dominik@gmail.com>
*/
class FlowdockFormatter implements FormatterInterface
{
/**
* @var string
*/
private $source;
private string $source;

/**
* @var string
*/
private $sourceEmail;
private string $sourceEmail;

public function __construct(string $source, string $sourceEmail)
{
@@ -35,43 +31,41 @@ public function __construct(string $source, string $sourceEmail)
}

/**
* {@inheritDoc}
* @inheritDoc
*
* @return mixed[]
*/
public function format(array $record): array
public function format(LogRecord $record): array
{
$tags = [
'#logs',
'#' . strtolower($record['level_name']),
'#' . $record['channel'],
'#' . $record->level->toPsrLogLevel(),
'#' . $record->channel,
];

foreach ($record['extra'] as $value) {
foreach ($record->extra as $value) {
$tags[] = '#' . $value;
}

$subject = sprintf(
'in %s: %s - %s',
$this->source,
$record['level_name'],
$this->getShortMessage($record['message'])
$record->level->getName(),
$this->getShortMessage($record->message)
);

$record['flowdock'] = [
return [
'source' => $this->source,
'from_address' => $this->sourceEmail,
'subject' => $subject,
'content' => $record['message'],
'content' => $record->message,
'tags' => $tags,
'project' => $this->source,
];

return $record;
}

/**
* {@inheritDoc}
* @inheritDoc
*
* @return mixed[][]
*/
21 changes: 11 additions & 10 deletions src/Monolog/Formatter/FluentdFormatter.php
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
namespace Monolog\Formatter;

use Monolog\Utils;
use Monolog\LogRecord;

/**
* Class FluentdFormatter
@@ -39,7 +40,7 @@ class FluentdFormatter implements FormatterInterface
/**
* @var bool $levelTag should message level be a part of the fluentd tag
*/
protected $levelTag = false;
protected bool $levelTag = false;

public function __construct(bool $levelTag = false)
{
@@ -55,25 +56,25 @@ public function isUsingLevelsInTag(): bool
return $this->levelTag;
}

public function format(array $record): string
public function format(LogRecord $record): string
{
$tag = $record['channel'];
$tag = $record->channel;
if ($this->levelTag) {
$tag .= '.' . strtolower($record['level_name']);
$tag .= '.' . $record->level->toPsrLogLevel();
}

$message = [
'message' => $record['message'],
'context' => $record['context'],
'extra' => $record['extra'],
'message' => $record->message,
'context' => $record->context,
'extra' => $record->extra,
];

if (!$this->levelTag) {
$message['level'] = $record['level'];
$message['level_name'] = $record['level_name'];
$message['level'] = $record->level->value;
$message['level_name'] = $record->level->getName();
}

return Utils::jsonEncode([$tag, $record['datetime']->getTimestamp(), $message]);
return Utils::jsonEncode([$tag, $record->datetime->getTimestamp(), $message]);
}

public function formatBatch(array $records): string
18 changes: 7 additions & 11 deletions src/Monolog/Formatter/FormatterInterface.php
Original file line number Diff line number Diff line change
@@ -11,32 +11,28 @@

namespace Monolog\Formatter;

use Monolog\LogRecord;

/**
* Interface for formatters
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*
* @phpstan-import-type Record from \Monolog\Logger
*/
interface FormatterInterface
{
/**
* Formats a log record.
*
* @param array $record A record to format
* @return mixed The formatted record
*
* @phpstan-param Record $record
* @param LogRecord $record A record to format
* @return mixed The formatted record
*/
public function format(array $record);
public function format(LogRecord $record);

/**
* Formats a set of log records.
*
* @param array $records A set of records to format
* @return mixed The formatted set of records
*
* @phpstan-param Record[] $records
* @param array<LogRecord> $records A set of records to format
* @return mixed The formatted set of records
*/
public function formatBatch(array $records);
}
83 changes: 38 additions & 45 deletions src/Monolog/Formatter/GelfMessageFormatter.php
Original file line number Diff line number Diff line change
@@ -11,17 +11,16 @@

namespace Monolog\Formatter;

use Monolog\Logger;
use Monolog\Level;
use Gelf\Message;
use Monolog\Utils;
use Monolog\LogRecord;

/**
* Serializes a log message to GELF
* @see http://docs.graylog.org/en/latest/pages/gelf.html
*
* @author Matt Lehner <mlehner@gmail.com>
*
* @phpstan-import-type Level from \Monolog\Logger
*/
class GelfMessageFormatter extends NormalizerFormatter
{
@@ -30,40 +29,39 @@ class GelfMessageFormatter extends NormalizerFormatter
/**
* @var string the name of the system for the Gelf log message
*/
protected $systemName;
protected string $systemName;

/**
* @var string a prefix for 'extra' fields from the Monolog record (optional)
*/
protected $extraPrefix;
protected string $extraPrefix;

/**
* @var string a prefix for 'context' fields from the Monolog record (optional)
*/
protected $contextPrefix;
protected string $contextPrefix;

/**
* @var int max length per field
*/
protected $maxLength;
protected int $maxLength;

/**
* Translates Monolog log levels to Graylog2 log priorities.
*
* @var array<int, int>
*
* @phpstan-var array<Level, int>
*/
private $logLevels = [
Logger::DEBUG => 7,
Logger::INFO => 6,
Logger::NOTICE => 5,
Logger::WARNING => 4,
Logger::ERROR => 3,
Logger::CRITICAL => 2,
Logger::ALERT => 1,
Logger::EMERGENCY => 0,
];
private function getGraylog2Priority(Level $level): int
{
return match ($level) {
Level::Debug => 7,
Level::Info => 6,
Level::Notice => 5,
Level::Warning => 4,
Level::Error => 3,
Level::Critical => 2,
Level::Alert => 1,
Level::Emergency => 0,
};
}

public function __construct(?string $systemName = null, ?string $extraPrefix = null, string $contextPrefix = 'ctxt_', ?int $maxLength = null)
{
@@ -81,47 +79,43 @@ public function __construct(?string $systemName = null, ?string $extraPrefix = n
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function format(array $record): Message
public function format(LogRecord $record): Message
{
$context = $extra = [];
if (isset($record['context'])) {
if (isset($record->context)) {
/** @var mixed[] $context */
$context = parent::normalize($record['context']);
$context = parent::normalize($record->context);
}
if (isset($record['extra'])) {
if (isset($record->extra)) {
/** @var mixed[] $extra */
$extra = parent::normalize($record['extra']);
}

if (!isset($record['datetime'], $record['message'], $record['level'])) {
throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given');
$extra = parent::normalize($record->extra);
}

$message = new Message();
$message
->setTimestamp($record['datetime'])
->setShortMessage((string) $record['message'])
->setTimestamp($record->datetime)
->setShortMessage($record->message)
->setHost($this->systemName)
->setLevel($this->logLevels[$record['level']]);
->setLevel($this->getGraylog2Priority($record->level));

// message length + system name length + 200 for padding / metadata
$len = 200 + strlen((string) $record['message']) + strlen($this->systemName);
$len = 200 + strlen($record->message) + strlen($this->systemName);

if ($len > $this->maxLength) {
$message->setShortMessage(Utils::substr($record['message'], 0, $this->maxLength));
$message->setShortMessage(Utils::substr($record->message, 0, $this->maxLength));
}

if (isset($record['channel'])) {
$message->setFacility($record['channel']);
if (isset($record->channel)) {
$message->setAdditional('facility', $record->channel);
}
if (isset($extra['line'])) {
$message->setLine($extra['line']);
$message->setAdditional('line', $extra['line']);
unset($extra['line']);
}
if (isset($extra['file'])) {
$message->setFile($extra['file']);
$message->setAdditional('file', $extra['file']);
unset($extra['file']);
}

@@ -147,11 +141,10 @@ public function format(array $record): Message
$message->setAdditional($this->contextPrefix . $key, $val);
}

/** @phpstan-ignore-next-line */
if (null === $message->getFile() && isset($context['exception']['file'])) {
if (preg_match("/^(.+):([0-9]+)$/", $context['exception']['file'], $matches)) {
$message->setFile($matches[1]);
$message->setLine($matches[2]);
if (!$message->hasAdditional('file') && isset($context['exception']['file'])) {
if (1 === preg_match("/^(.+):([0-9]+)$/", $context['exception']['file'], $matches)) {
$message->setAdditional('file', $matches[1]);
$message->setAdditional('line', $matches[2]);
}
}

54 changes: 27 additions & 27 deletions src/Monolog/Formatter/HtmlFormatter.php
Original file line number Diff line number Diff line change
@@ -11,8 +11,9 @@

namespace Monolog\Formatter;

use Monolog\Logger;
use Monolog\Level;
use Monolog\Utils;
use Monolog\LogRecord;

/**
* Formats incoming records into an HTML table
@@ -25,19 +26,20 @@ class HtmlFormatter extends NormalizerFormatter
{
/**
* Translates Monolog log levels to html color priorities.
*
* @var array<int, string>
*/
protected $logLevels = [
Logger::DEBUG => '#CCCCCC',
Logger::INFO => '#28A745',
Logger::NOTICE => '#17A2B8',
Logger::WARNING => '#FFC107',
Logger::ERROR => '#FD7E14',
Logger::CRITICAL => '#DC3545',
Logger::ALERT => '#821722',
Logger::EMERGENCY => '#000000',
];
protected function getLevelColor(Level $level): string
{
return match ($level) {
Level::Debug => '#CCCCCC',
Level::Info => '#28A745',
Level::Notice => '#17A2B8',
Level::Warning => '#FFC107',
Level::Error => '#FD7E14',
Level::Critical => '#DC3545',
Level::Alert => '#821722',
Level::Emergency => '#000000',
};
}

/**
* @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
@@ -67,41 +69,39 @@ protected function addRow(string $th, string $td = ' ', bool $escapeTd = true):
/**
* Create a HTML h1 tag
*
* @param string $title Text to be in the h1
* @param int $level Error level
* @return string
* @param string $title Text to be in the h1
*/
protected function addTitle(string $title, int $level): string
protected function addTitle(string $title, Level $level): string
{
$title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');

return '<h1 style="background: '.$this->logLevels[$level].';color: #ffffff;padding: 5px;" class="monolog-output">'.$title.'</h1>';
return '<h1 style="background: '.$this->getLevelColor($level).';color: #ffffff;padding: 5px;" class="monolog-output">'.$title.'</h1>';
}

/**
* Formats a log record.
*
* @return string The formatted record
*/
public function format(array $record): string
public function format(LogRecord $record): string
{
$output = $this->addTitle($record['level_name'], $record['level']);
$output = $this->addTitle($record->level->getName(), $record->level);
$output .= '<table cellspacing="1" width="100%" class="monolog-output">';

$output .= $this->addRow('Message', (string) $record['message']);
$output .= $this->addRow('Time', $this->formatDate($record['datetime']));
$output .= $this->addRow('Channel', $record['channel']);
if ($record['context']) {
$output .= $this->addRow('Message', $record->message);
$output .= $this->addRow('Time', $this->formatDate($record->datetime));
$output .= $this->addRow('Channel', $record->channel);
if (\count($record->context) > 0) {
$embeddedTable = '<table cellspacing="1" width="100%">';
foreach ($record['context'] as $key => $value) {
foreach ($record->context as $key => $value) {
$embeddedTable .= $this->addRow((string) $key, $this->convertToString($value));
}
$embeddedTable .= '</table>';
$output .= $this->addRow('Context', $embeddedTable, false);
}
if ($record['extra']) {
if (\count($record->extra) > 0) {
$embeddedTable = '<table cellspacing="1" width="100%">';
foreach ($record['extra'] as $key => $value) {
foreach ($record->extra as $key => $value) {
$embeddedTable .= $this->addRow((string) $key, $this->convertToString($value));
}
$embeddedTable .= '</table>';
60 changes: 22 additions & 38 deletions src/Monolog/Formatter/JsonFormatter.php
Original file line number Diff line number Diff line change
@@ -12,29 +12,28 @@
namespace Monolog\Formatter;

use Throwable;
use Monolog\LogRecord;

/**
* Encodes whatever record data is passed to it as json
*
* This can be useful to log to databases or remote APIs
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*
* @phpstan-import-type Record from \Monolog\Logger
*/
class JsonFormatter extends NormalizerFormatter
{
public const BATCH_MODE_JSON = 1;
public const BATCH_MODE_NEWLINES = 2;

/** @var self::BATCH_MODE_* */
protected $batchMode;
/** @var bool */
protected $appendNewline;
/** @var bool */
protected $ignoreEmptyContextAndExtra;
/** @var bool */
protected $includeStacktraces = false;
protected int $batchMode;

protected bool $appendNewline;

protected bool $ignoreEmptyContextAndExtra;

protected bool $includeStacktraces = false;

/**
* @param self::BATCH_MODE_* $batchMode
@@ -70,11 +69,11 @@ public function isAppendingNewlines(): bool
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function format(array $record): string
public function format(LogRecord $record): string
{
$normalized = $this->normalize($record);
$normalized = parent::format($record);

if (isset($normalized['context']) && $normalized['context'] === []) {
if ($this->ignoreEmptyContextAndExtra) {
@@ -95,23 +94,16 @@ public function format(array $record): string
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function formatBatch(array $records): string
{
switch ($this->batchMode) {
case static::BATCH_MODE_NEWLINES:
return $this->formatBatchNewlines($records);

case static::BATCH_MODE_JSON:
default:
return $this->formatBatchJson($records);
}
return match ($this->batchMode) {
static::BATCH_MODE_NEWLINES => $this->formatBatchNewlines($records),
default => $this->formatBatchJson($records),
};
}

/**
* @return self
*/
public function includeStacktraces(bool $include = true): self
{
$this->includeStacktraces = $include;
@@ -122,7 +114,7 @@ public function includeStacktraces(bool $include = true): self
/**
* Return a JSON-encoded array of records.
*
* @phpstan-param Record[] $records
* @phpstan-param LogRecord[] $records
*/
protected function formatBatchJson(array $records): string
{
@@ -133,30 +125,22 @@ protected function formatBatchJson(array $records): string
* Use new lines to separate records instead of a
* JSON-encoded array.
*
* @phpstan-param Record[] $records
* @phpstan-param LogRecord[] $records
*/
protected function formatBatchNewlines(array $records): string
{
$instance = $this;

$oldNewline = $this->appendNewline;
$this->appendNewline = false;
array_walk($records, function (&$value, $key) use ($instance) {
$value = $instance->format($value);
});
$formatted = array_map(fn (LogRecord $record) => $this->format($record), $records);
$this->appendNewline = $oldNewline;

return implode("\n", $records);
return implode("\n", $formatted);
}

/**
* Normalizes given $data.
*
* @param mixed $data
*
* @return mixed
*/
protected function normalize($data, int $depth = 0)
protected function normalize(mixed $data, int $depth = 0): mixed
{
if ($depth > $this->maxNormalizeDepth) {
return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization';
@@ -197,7 +181,7 @@ protected function normalize($data, int $depth = 0)
* Normalizes given exception with or without its own stack trace based on
* `includeStacktraces` property.
*
* {@inheritDoc}
* @inheritDoc
*/
protected function normalizeException(Throwable $e, int $depth = 0): array
{
42 changes: 19 additions & 23 deletions src/Monolog/Formatter/LineFormatter.php
Original file line number Diff line number Diff line change
@@ -11,7 +11,9 @@

namespace Monolog\Formatter;

use Closure;
use Monolog\Utils;
use Monolog\LogRecord;

/**
* Formats incoming records into a one-line string
@@ -25,22 +27,16 @@ class LineFormatter extends NormalizerFormatter
{
public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";

/** @var string */
protected $format;
/** @var bool */
protected $allowInlineLineBreaks;
/** @var bool */
protected $ignoreEmptyContextAndExtra;
/** @var bool */
protected $includeStacktraces;
/** @var ?callable */
protected $stacktracesParser;
protected string $format;
protected bool $allowInlineLineBreaks;
protected bool $ignoreEmptyContextAndExtra;
protected bool $includeStacktraces;
protected Closure|null $stacktracesParser = null;

/**
* @param string|null $format The format of the message
* @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
* @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
* @param bool $ignoreEmptyContextAndExtra
* @param string|null $format The format of the message
* @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
* @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
*/
public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false)
{
@@ -51,7 +47,7 @@ public function __construct(?string $format = null, ?string $dateFormat = null,
parent::__construct($dateFormat);
}

public function includeStacktraces(bool $include = true, ?callable $parser = null): self
public function includeStacktraces(bool $include = true, ?Closure $parser = null): self
{
$this->includeStacktraces = $include;
if ($this->includeStacktraces) {
@@ -77,14 +73,13 @@ public function ignoreEmptyContextAndExtra(bool $ignore = true): self
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function format(array $record): string
public function format(LogRecord $record): string
{
$vars = parent::format($record);

$output = $this->format;

foreach ($vars['extra'] as $var => $val) {
if (false !== strpos($output, '%extra.'.$var.'%')) {
$output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
@@ -100,12 +95,12 @@ public function format(array $record): string
}

if ($this->ignoreEmptyContextAndExtra) {
if (empty($vars['context'])) {
if (\count($vars['context']) === 0) {
unset($vars['context']);
$output = str_replace('%context%', '', $output);
}

if (empty($vars['extra'])) {
if (\count($vars['extra']) === 0) {
unset($vars['extra']);
$output = str_replace('%extra%', '', $output);
}
@@ -122,6 +117,7 @@ public function format(array $record): string
$output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
if (null === $output) {
$pcreErrorCode = preg_last_error();

throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
}
}
@@ -151,7 +147,7 @@ protected function normalizeException(\Throwable $e, int $depth = 0): string
{
$str = $this->formatException($e);

if ($previous = $e->getPrevious()) {
if (($previous = $e->getPrevious()) instanceof \Throwable) {
do {
$str .= "\n[previous exception] " . $this->formatException($previous);
} while ($previous = $previous->getPrevious());
@@ -180,7 +176,7 @@ protected function replaceNewlines(string $str): string
{
if ($this->allowInlineLineBreaks) {
if (0 === strpos($str, '{')) {
return str_replace(array('\r', '\n'), array("\r", "\n"), $str);
return str_replace(['\r', '\n'], ["\r", "\n"], $str);
}

return $str;
@@ -222,7 +218,7 @@ private function stacktracesParser(\Throwable $e): string
{
$trace = $e->getTraceAsString();

if ($this->stacktracesParser) {
if ($this->stacktracesParser !== null) {
$trace = $this->stacktracesParserCustom($trace);
}

14 changes: 8 additions & 6 deletions src/Monolog/Formatter/LogglyFormatter.php
Original file line number Diff line number Diff line change
@@ -11,6 +11,8 @@

namespace Monolog\Formatter;

use Monolog\LogRecord;

/**
* Encodes message information into JSON in a format compatible with Loggly.
*
@@ -33,13 +35,13 @@ public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $ap
* @see https://www.loggly.com/docs/automated-parsing/#json
* @see \Monolog\Formatter\JsonFormatter::format()
*/
public function format(array $record): string
protected function normalizeRecord(LogRecord $record): array
{
if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTimeInterface)) {
$record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO");
unset($record["datetime"]);
}
$recordData = parent::normalizeRecord($record);

$recordData["timestamp"] = $record->datetime->format("Y-m-d\TH:i:s.uO");
unset($recordData["datetime"]);

return parent::format($record);
return $recordData;
}
}
28 changes: 13 additions & 15 deletions src/Monolog/Formatter/LogmaticFormatter.php
Original file line number Diff line number Diff line change
@@ -11,6 +11,8 @@

namespace Monolog\Formatter;

use Monolog\LogRecord;

/**
* Encodes message information into JSON in a format compatible with Logmatic.
*
@@ -20,15 +22,9 @@ class LogmaticFormatter extends JsonFormatter
{
protected const MARKERS = ["sourcecode", "php"];

/**
* @var string
*/
protected $hostname = '';
protected string $hostname = '';

/**
* @var string
*/
protected $appname = '';
protected string $appName = '';

public function setHostname(string $hostname): self
{
@@ -37,9 +33,9 @@ public function setHostname(string $hostname): self
return $this;
}

public function setAppname(string $appname): self
public function setAppName(string $appName): self
{
$this->appname = $appname;
$this->appName = $appName;

return $this;
}
@@ -50,17 +46,19 @@ public function setAppname(string $appname): self
* @see http://doc.logmatic.io/docs/basics-to-send-data
* @see \Monolog\Formatter\JsonFormatter::format()
*/
public function format(array $record): string
public function normalizeRecord(LogRecord $record): array
{
if (!empty($this->hostname)) {
$record = parent::normalizeRecord($record);

if ($this->hostname !== '') {
$record["hostname"] = $this->hostname;
}
if (!empty($this->appname)) {
$record["appname"] = $this->appname;
if ($this->appName !== '') {
$record["appname"] = $this->appName;
}

$record["@marker"] = static::MARKERS;

return parent::format($record);
return $record;
}
}
49 changes: 24 additions & 25 deletions src/Monolog/Formatter/LogstashFormatter.php
Original file line number Diff line number Diff line change
@@ -11,6 +11,8 @@

namespace Monolog\Formatter;

use Monolog\LogRecord;

/**
* Serializes a log message to Logstash Event Format
*
@@ -24,22 +26,22 @@ class LogstashFormatter extends NormalizerFormatter
/**
* @var string the name of the system for the Logstash log message, used to fill the @source field
*/
protected $systemName;
protected string $systemName;

/**
* @var string an application name for the Logstash log message, used to fill the @type field
*/
protected $applicationName;
protected string $applicationName;

/**
* @var string the key for 'extra' fields from the Monolog record
*/
protected $extraKey;
protected string $extraKey;

/**
* @var string the key for 'context' fields from the Monolog record
*/
protected $contextKey;
protected string $contextKey;

/**
* @param string $applicationName The application that sends the data, used as the "type" field of logstash
@@ -59,41 +61,38 @@ public function __construct(string $applicationName, ?string $systemName = null,
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function format(array $record): string
public function format(LogRecord $record): string
{
$record = parent::format($record);
$recordData = parent::format($record);

if (empty($record['datetime'])) {
$record['datetime'] = gmdate('c');
}
$message = [
'@timestamp' => $record['datetime'],
'@timestamp' => $recordData['datetime'],
'@version' => 1,
'host' => $this->systemName,
];
if (isset($record['message'])) {
$message['message'] = $record['message'];
if (isset($recordData['message'])) {
$message['message'] = $recordData['message'];
}
if (isset($record['channel'])) {
$message['type'] = $record['channel'];
$message['channel'] = $record['channel'];
if (isset($recordData['channel'])) {
$message['type'] = $recordData['channel'];
$message['channel'] = $recordData['channel'];
}
if (isset($record['level_name'])) {
$message['level'] = $record['level_name'];
if (isset($recordData['level_name'])) {
$message['level'] = $recordData['level_name'];
}
if (isset($record['level'])) {
$message['monolog_level'] = $record['level'];
if (isset($recordData['level'])) {
$message['monolog_level'] = $recordData['level'];
}
if ($this->applicationName) {
if ('' !== $this->applicationName) {
$message['type'] = $this->applicationName;
}
if (!empty($record['extra'])) {
$message[$this->extraKey] = $record['extra'];
if (\count($recordData['extra']) > 0) {
$message[$this->extraKey] = $recordData['extra'];
}
if (!empty($record['context'])) {
$message[$this->contextKey] = $record['context'];
if (\count($recordData['context']) > 0) {
$message[$this->contextKey] = $recordData['context'];
}

return $this->toJson($message) . "\n";
20 changes: 9 additions & 11 deletions src/Monolog/Formatter/MongoDBFormatter.php
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@
use MongoDB\BSON\Type;
use MongoDB\BSON\UTCDateTime;
use Monolog\Utils;
use Monolog\LogRecord;

/**
* Formats a record for use with the MongoDBHandler.
@@ -22,15 +23,12 @@
*/
class MongoDBFormatter implements FormatterInterface
{
/** @var bool */
private $exceptionTraceAsString;
/** @var int */
private $maxNestingLevel;
/** @var bool */
private $isLegacyMongoExt;
private bool $exceptionTraceAsString;
private int $maxNestingLevel;
private bool $isLegacyMongoExt;

/**
* @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2
* @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record->context is 2
* @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings
*/
public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true)
@@ -42,20 +40,20 @@ public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsStri
}

/**
* {@inheritDoc}
* @inheritDoc
*
* @return mixed[]
*/
public function format(array $record): array
public function format(LogRecord $record): array
{
/** @var mixed[] $res */
$res = $this->formatArray($record);
$res = $this->formatArray($record->toArray());

return $res;
}

/**
* {@inheritDoc}
* @inheritDoc
*
* @return array<mixed[]>
*/
65 changes: 41 additions & 24 deletions src/Monolog/Formatter/NormalizerFormatter.php
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@
use Monolog\DateTimeImmutable;
use Monolog\Utils;
use Throwable;
use Monolog\LogRecord;

/**
* Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
@@ -24,15 +25,11 @@ class NormalizerFormatter implements FormatterInterface
{
public const SIMPLE_DATE = "Y-m-d\TH:i:sP";

/** @var string */
protected $dateFormat;
/** @var int */
protected $maxNormalizeDepth = 9;
/** @var int */
protected $maxNormalizeItemCount = 1000;
protected string $dateFormat;
protected int $maxNormalizeDepth = 9;
protected int $maxNormalizeItemCount = 1000;

/** @var int */
private $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS;
private int $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS;

/**
* @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
@@ -46,17 +43,25 @@ public function __construct(?string $dateFormat = null)
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function format(LogRecord $record)
{
return $this->normalizeRecord($record);
}

/**
* Normalize an arbitrary value to a scalar|array|null
*
* @param mixed[] $record
* @return null|scalar|array<mixed[]|scalar|null>
*/
public function format(array $record)
public function normalizeValue(mixed $data): mixed
{
return $this->normalize($record);
return $this->normalize($data);
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function formatBatch(array $records)
{
@@ -124,10 +129,25 @@ public function setJsonPrettyPrint(bool $enable): self
}

/**
* @param mixed $data
* @return null|scalar|array<array|scalar|null>
* Provided as extension point
*
* Because normalize is called with sub-values of context data etc, normalizeRecord can be
* extended when data needs to be appended on the record array but not to other normalized data.
*
* @return array<mixed[]|scalar|null>
*/
protected function normalize($data, int $depth = 0)
protected function normalizeRecord(LogRecord $record): array
{
/** @var array<mixed> $normalized */
$normalized = $this->normalize($record->toArray());

return $normalized;
}

/**
* @return null|scalar|array<mixed[]|scalar|null>
*/
protected function normalize(mixed $data, int $depth = 0): mixed
{
if ($depth > $this->maxNormalizeDepth) {
return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
@@ -172,14 +192,14 @@ protected function normalize($data, int $depth = 0)
}

if ($data instanceof \JsonSerializable) {
/** @var null|scalar|array<array|scalar|null> $value */
/** @var null|scalar|array<mixed[]|scalar|null> $value */
$value = $data->jsonSerialize();
} elseif (method_exists($data, '__toString')) {
/** @var string $value */
$value = $data->__toString();
} else {
// the rest is normalized by json encoding and decoding it
/** @var null|scalar|array<array|scalar|null> $value */
/** @var null|scalar|array<mixed[]|scalar|null> $value */
$value = json_decode($this->toJson($data, true), true);
}

@@ -229,12 +249,12 @@ protected function normalizeException(Throwable $e, int $depth = 0)

$trace = $e->getTrace();
foreach ($trace as $frame) {
if (isset($frame['file'])) {
if (isset($frame['file'], $frame['line'])) {
$data['trace'][] = $frame['file'].':'.$frame['line'];
}
}

if ($previous = $e->getPrevious()) {
if (($previous = $e->getPrevious()) instanceof \Throwable) {
$data['previous'] = $this->normalizeException($previous, $depth + 1);
}

@@ -253,10 +273,7 @@ protected function toJson($data, bool $ignoreErrors = false): string
return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors);
}

/**
* @return string
*/
protected function formatDate(\DateTimeInterface $date)
protected function formatDate(\DateTimeInterface $date): string
{
// in case the date format isn't custom then we defer to the custom DateTimeImmutable
// formatting logic, which will pick the right format based on whether useMicroseconds is on
18 changes: 8 additions & 10 deletions src/Monolog/Formatter/ScalarFormatter.php
Original file line number Diff line number Diff line change
@@ -11,34 +11,32 @@

namespace Monolog\Formatter;

use Monolog\LogRecord;

/**
* Formats data into an associative array of scalar values.
* Formats data into an associative array of scalar (+ null) values.
* Objects and arrays will be JSON encoded.
*
* @author Andrew Lawson <adlawson@gmail.com>
*/
class ScalarFormatter extends NormalizerFormatter
{
/**
* {@inheritDoc}
* @inheritDoc
*
* @phpstan-return array<string, scalar|null> $record
*/
public function format(array $record): array
public function format(LogRecord $record): array
{
$result = [];
foreach ($record as $key => $value) {
$result[$key] = $this->normalizeValue($value);
foreach ($record->toArray() as $key => $value) {
$result[$key] = $this->toScalar($value);
}

return $result;
}

/**
* @param mixed $value
* @return scalar|null
*/
protected function normalizeValue($value)
protected function toScalar(mixed $value): string|int|float|bool|null
{
$normalized = $this->normalize($value);

86 changes: 42 additions & 44 deletions src/Monolog/Formatter/WildfireFormatter.php
Original file line number Diff line number Diff line change
@@ -11,35 +11,18 @@

namespace Monolog\Formatter;

use Monolog\Logger;
use Monolog\Level;
use Monolog\LogRecord;

/**
* Serializes a log message according to Wildfire's header requirements
*
* @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
* @author Christophe Coevoet <stof@notk.org>
* @author Kirill chEbba Chebunin <iam@chebba.org>
*
* @phpstan-import-type Level from \Monolog\Logger
*/
class WildfireFormatter extends NormalizerFormatter
{
/**
* Translates Monolog log levels to Wildfire levels.
*
* @var array<Level, string>
*/
private $logLevels = [
Logger::DEBUG => 'LOG',
Logger::INFO => 'INFO',
Logger::NOTICE => 'INFO',
Logger::WARNING => 'WARN',
Logger::ERROR => 'ERROR',
Logger::CRITICAL => 'ERROR',
Logger::ALERT => 'ERROR',
Logger::EMERGENCY => 'ERROR',
];

/**
* @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
*/
@@ -52,46 +35,61 @@ public function __construct(?string $dateFormat = null)
}

/**
* {@inheritDoc}
* Translates Monolog log levels to Wildfire levels.
*
* @return string
* @return 'LOG'|'INFO'|'WARN'|'ERROR'
*/
private function toWildfireLevel(Level $level): string
{
return match ($level) {
Level::Debug => 'LOG',
Level::Info => 'INFO',
Level::Notice => 'INFO',
Level::Warning => 'WARN',
Level::Error => 'ERROR',
Level::Critical => 'ERROR',
Level::Alert => 'ERROR',
Level::Emergency => 'ERROR',
};
}

/**
* @inheritDoc
*/
public function format(array $record): string
public function format(LogRecord $record): string
{
// Retrieve the line and file if set and remove them from the formatted extra
$file = $line = '';
if (isset($record['extra']['file'])) {
$file = $record['extra']['file'];
unset($record['extra']['file']);
if (isset($record->extra['file'])) {
$file = $record->extra['file'];
unset($record->extra['file']);
}
if (isset($record['extra']['line'])) {
$line = $record['extra']['line'];
unset($record['extra']['line']);
if (isset($record->extra['line'])) {
$line = $record->extra['line'];
unset($record->extra['line']);
}

/** @var mixed[] $record */
$record = $this->normalize($record);
$message = ['message' => $record['message']];
$message = ['message' => $record->message];
$handleError = false;
if ($record['context']) {
$message['context'] = $record['context'];
if (count($record->context) > 0) {
$message['context'] = $this->normalize($record->context);
$handleError = true;
}
if ($record['extra']) {
$message['extra'] = $record['extra'];
if (count($record->extra) > 0) {
$message['extra'] = $this->normalize($record->extra);
$handleError = true;
}
if (count($message) === 1) {
$message = reset($message);
}

if (isset($record['context']['table'])) {
if (is_array($message) && isset($message['context']['table'])) {
$type = 'TABLE';
$label = $record['channel'] .': '. $record['message'];
$message = $record['context']['table'];
$label = $record->channel .': '. $record->message;
$message = $message['context']['table'];
} else {
$type = $this->logLevels[$record['level']];
$label = $record['channel'];
$type = $this->toWildfireLevel($record->level);
$label = $record->channel;
}

// Create JSON object describing the appearance of the message in the console
@@ -114,7 +112,7 @@ public function format(array $record): string
}

/**
* {@inheritDoc}
* @inheritDoc
*
* @phpstan-return never
*/
@@ -124,11 +122,11 @@ public function formatBatch(array $records)
}

/**
* {@inheritDoc}
* @inheritDoc
*
* @return null|scalar|array<array|scalar|null>|object
* @return null|scalar|array<mixed[]|scalar|null>|object
*/
protected function normalize($data, int $depth = 0)
protected function normalize(mixed $data, int $depth = 0): mixed
{
if (is_object($data) && !$data instanceof \DateTimeInterface) {
return $data;
50 changes: 20 additions & 30 deletions src/Monolog/Handler/AbstractHandler.php
Original file line number Diff line number Diff line change
@@ -11,55 +11,50 @@

namespace Monolog\Handler;

use Monolog\Level;
use Monolog\Logger;
use Monolog\ResettableInterface;
use Psr\Log\LogLevel;
use Monolog\LogRecord;

/**
* Base Handler class providing basic level/bubble support
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*
* @phpstan-import-type Level from \Monolog\Logger
* @phpstan-import-type LevelName from \Monolog\Logger
*/
abstract class AbstractHandler extends Handler implements ResettableInterface
{
/**
* @var int
* @phpstan-var Level
*/
protected $level = Logger::DEBUG;
/** @var bool */
protected $bubble = true;
protected Level $level = Level::Debug;
protected bool $bubble = true;

/**
* @param int|string $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param int|string|Level|LogLevel::* $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*
* @phpstan-param Level|LevelName|LogLevel::* $level
* @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level
*/
public function __construct($level = Logger::DEBUG, bool $bubble = true)
public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true)
{
$this->setLevel($level);
$this->bubble = $bubble;
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function isHandling(array $record): bool
public function isHandling(LogRecord $record): bool
{
return $record['level'] >= $this->level;
return $record->level->value >= $this->level->value;
}

/**
* Sets minimum logging level at which this handler will be triggered.
*
* @param Level|LevelName|LogLevel::* $level Level or level name
* @return self
* @param Level|LogLevel::* $level Level or level name
*
* @phpstan-param value-of<Level::VALUES>|value-of<Level::NAMES>|Level|LogLevel::* $level
*/
public function setLevel($level): self
public function setLevel(int|string|Level $level): self
{
$this->level = Logger::toMonologLevel($level);

@@ -68,22 +63,17 @@ public function setLevel($level): self

/**
* Gets minimum logging level at which this handler will be triggered.
*
* @return int
*
* @phpstan-return Level
*/
public function getLevel(): int
public function getLevel(): Level
{
return $this->level;
}

/**
* Sets the bubbling behavior.
*
* @param bool $bubble true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @return self
* @param bool $bubble true means that this handler allows bubbling.
* false means that bubbling is not permitted.
*/
public function setBubble(bool $bubble): self
{
@@ -104,9 +94,9 @@ public function getBubble(): bool
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function reset()
public function reset(): void
{
}
}
27 changes: 9 additions & 18 deletions src/Monolog/Handler/AbstractProcessingHandler.php
Original file line number Diff line number Diff line change
@@ -11,56 +11,47 @@

namespace Monolog\Handler;

use Monolog\LogRecord;

/**
* Base Handler class providing the Handler structure, including processors and formatters
*
* Classes extending it should (in most cases) only implement write($record)
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*
* @phpstan-import-type LevelName from \Monolog\Logger
* @phpstan-import-type Level from \Monolog\Logger
* @phpstan-import-type Record from \Monolog\Logger
* @phpstan-type FormattedRecord array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[], formatted: mixed}
*/
abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface
{
use ProcessableHandlerTrait;
use FormattableHandlerTrait;

/**
* {@inheritDoc}
* @inheritDoc
*/
public function handle(array $record): bool
public function handle(LogRecord $record): bool
{
if (!$this->isHandling($record)) {
return false;
}

if ($this->processors) {
/** @var Record $record */
if (\count($this->processors) > 0) {
$record = $this->processRecord($record);
}

$record['formatted'] = $this->getFormatter()->format($record);
$record->formatted = $this->getFormatter()->format($record);

$this->write($record);

return false === $this->bubble;
}

/**
* Writes the record down to the log of the implementing handler
*
* @phpstan-param FormattedRecord $record
* Writes the (already formatted) record down to the log of the implementing handler
*/
abstract protected function write(array $record): void;
abstract protected function write(LogRecord $record): void;

/**
* @return void
*/
public function reset()
public function reset(): void
{
parent::reset();

76 changes: 37 additions & 39 deletions src/Monolog/Handler/AbstractSyslogHandler.php
Original file line number Diff line number Diff line change
@@ -11,70 +11,68 @@

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Level;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;

/**
* Common syslog functionality
*
* @phpstan-import-type Level from \Monolog\Logger
*/
abstract class AbstractSyslogHandler extends AbstractProcessingHandler
{
/** @var int */
protected $facility;
protected int $facility;

/**
* Translates Monolog log levels to syslog log priorities.
* @var array
* @phpstan-var array<Level, int>
*/
protected $logLevels = [
Logger::DEBUG => LOG_DEBUG,
Logger::INFO => LOG_INFO,
Logger::NOTICE => LOG_NOTICE,
Logger::WARNING => LOG_WARNING,
Logger::ERROR => LOG_ERR,
Logger::CRITICAL => LOG_CRIT,
Logger::ALERT => LOG_ALERT,
Logger::EMERGENCY => LOG_EMERG,
];
protected function toSyslogPriority(Level $level): int
{
return match ($level) {
Level::Debug => \LOG_DEBUG,
Level::Info => \LOG_INFO,
Level::Notice => \LOG_NOTICE,
Level::Warning => \LOG_WARNING,
Level::Error => \LOG_ERR,
Level::Critical => \LOG_CRIT,
Level::Alert => \LOG_ALERT,
Level::Emergency => \LOG_EMERG,
};
}

/**
* List of valid log facility names.
* @var array<string, int>
*/
protected $facilities = [
'auth' => LOG_AUTH,
'authpriv' => LOG_AUTHPRIV,
'cron' => LOG_CRON,
'daemon' => LOG_DAEMON,
'kern' => LOG_KERN,
'lpr' => LOG_LPR,
'mail' => LOG_MAIL,
'news' => LOG_NEWS,
'syslog' => LOG_SYSLOG,
'user' => LOG_USER,
'uucp' => LOG_UUCP,
protected array $facilities = [
'auth' => \LOG_AUTH,
'authpriv' => \LOG_AUTHPRIV,
'cron' => \LOG_CRON,
'daemon' => \LOG_DAEMON,
'kern' => \LOG_KERN,
'lpr' => \LOG_LPR,
'mail' => \LOG_MAIL,
'news' => \LOG_NEWS,
'syslog' => \LOG_SYSLOG,
'user' => \LOG_USER,
'uucp' => \LOG_UUCP,
];

/**
* @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant
*/
public function __construct($facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true)
public function __construct(string|int $facility = \LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true)
{
parent::__construct($level, $bubble);

if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->facilities['local0'] = LOG_LOCAL0;
$this->facilities['local1'] = LOG_LOCAL1;
$this->facilities['local2'] = LOG_LOCAL2;
$this->facilities['local3'] = LOG_LOCAL3;
$this->facilities['local4'] = LOG_LOCAL4;
$this->facilities['local5'] = LOG_LOCAL5;
$this->facilities['local6'] = LOG_LOCAL6;
$this->facilities['local7'] = LOG_LOCAL7;
$this->facilities['local0'] = \LOG_LOCAL0;
$this->facilities['local1'] = \LOG_LOCAL1;
$this->facilities['local2'] = \LOG_LOCAL2;
$this->facilities['local3'] = \LOG_LOCAL3;
$this->facilities['local4'] = \LOG_LOCAL4;
$this->facilities['local5'] = \LOG_LOCAL5;
$this->facilities['local6'] = \LOG_LOCAL6;
$this->facilities['local7'] = \LOG_LOCAL7;
} else {
$this->facilities['local0'] = 128; // LOG_LOCAL0
$this->facilities['local1'] = 136; // LOG_LOCAL1
@@ -97,7 +95,7 @@ public function __construct($facility = LOG_USER, $level = Logger::DEBUG, bool $
}

/**
* {@inheritDoc}
* @inheritDoc
*/
protected function getDefaultFormatter(): FormatterInterface
{
39 changes: 13 additions & 26 deletions src/Monolog/Handler/AmqpHandler.php
Original file line number Diff line number Diff line change
@@ -11,39 +11,29 @@

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Level;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\JsonFormatter;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Channel\AMQPChannel;
use AMQPExchange;
use Monolog\LogRecord;

/**
* @phpstan-import-type Record from \Monolog\Logger
*/
class AmqpHandler extends AbstractProcessingHandler
{
/**
* @var AMQPExchange|AMQPChannel $exchange
*/
protected $exchange;
protected AMQPExchange|AMQPChannel $exchange;

/**
* @var string
*/
protected $exchangeName;
protected string $exchangeName;

/**
* @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use
* @param string|null $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only
*/
public function __construct($exchange, ?string $exchangeName = null, $level = Logger::DEBUG, bool $bubble = true)
public function __construct(AMQPExchange|AMQPChannel $exchange, ?string $exchangeName = null, int|string|Level $level = Level::Debug, bool $bubble = true)
{
if ($exchange instanceof AMQPChannel) {
$this->exchangeName = (string) $exchangeName;
} elseif (!$exchange instanceof AMQPExchange) {
throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required');
} elseif ($exchangeName) {
} elseif ($exchangeName !== null) {
@trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED);
}
$this->exchange = $exchange;
@@ -52,11 +42,11 @@ public function __construct($exchange, ?string $exchangeName = null, $level = Lo
}

/**
* {@inheritDoc}
* @inheritDoc
*/
protected function write(array $record): void
protected function write(LogRecord $record): void
{
$data = $record["formatted"];
$data = $record->formatted;
$routingKey = $this->getRoutingKey($record);

if ($this->exchange instanceof AMQPExchange) {
@@ -79,7 +69,7 @@ protected function write(array $record): void
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function handleBatch(array $records): void
{
@@ -94,7 +84,6 @@ public function handleBatch(array $records): void
continue;
}

/** @var Record $record */
$record = $this->processRecord($record);
$data = $this->getFormatter()->format($record);

@@ -110,12 +99,10 @@ public function handleBatch(array $records): void

/**
* Gets the routing key for the AMQP exchange
*
* @phpstan-param Record $record
*/
protected function getRoutingKey(array $record): string
protected function getRoutingKey(LogRecord $record): string
{
$routingKey = sprintf('%s.%s', $record['level_name'], $record['channel']);
$routingKey = sprintf('%s.%s', $record->level->name, $record->channel);

return strtolower($routingKey);
}
@@ -132,7 +119,7 @@ private function createAmqpMessage(string $data): AMQPMessage
}

/**
* {@inheritDoc}
* @inheritDoc
*/
protected function getDefaultFormatter(): FormatterInterface
{
59 changes: 28 additions & 31 deletions src/Monolog/Handler/BrowserConsoleHandler.php
Original file line number Diff line number Diff line change
@@ -14,34 +14,30 @@
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Monolog\Utils;
use Monolog\LogRecord;

use function count;
use function headers_list;
use function stripos;
use function trigger_error;

use const E_USER_DEPRECATED;

/**
* Handler sending logs to browser's javascript console with no browser extension required
*
* @author Olivier Poitrey <rs@dailymotion.com>
*
* @phpstan-import-type FormattedRecord from AbstractProcessingHandler
*/
class BrowserConsoleHandler extends AbstractProcessingHandler
{
/** @var bool */
protected static $initialized = false;
/** @var FormattedRecord[] */
protected static $records = [];
protected static bool $initialized = false;

/** @var LogRecord[] */
protected static array $records = [];

protected const FORMAT_HTML = 'html';
protected const FORMAT_JS = 'js';
protected const FORMAT_UNKNOWN = 'unknown';

/**
* {@inheritDoc}
* @inheritDoc
*
* Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format.
*
@@ -55,9 +51,9 @@ protected function getDefaultFormatter(): FormatterInterface
}

/**
* {@inheritDoc}
* @inheritDoc
*/
protected function write(array $record): void
protected function write(LogRecord $record): void
{
// Accumulate records
static::$records[] = $record;
@@ -80,11 +76,11 @@ public static function send(): void
return;
}

if (count(static::$records)) {
if (count(static::$records) > 0) {
if ($format === self::FORMAT_HTML) {
static::writeOutput('<script>' . static::generateScript() . '</script>');
} elseif ($format === self::FORMAT_JS) {
static::writeOutput(static::generateScript());
static::writeOutput('<script>' . self::generateScript() . '</script>');
} else { // js format
static::writeOutput(self::generateScript());
}
static::resetStatic();
}
@@ -95,7 +91,7 @@ public function close(): void
self::resetStatic();
}

public function reset()
public function reset(): void
{
parent::reset();

@@ -173,18 +169,18 @@ private static function generateScript(): string
{
$script = [];
foreach (static::$records as $record) {
$context = static::dump('Context', $record['context']);
$extra = static::dump('Extra', $record['extra']);
$context = self::dump('Context', $record->context);
$extra = self::dump('Extra', $record->extra);

if (empty($context) && empty($extra)) {
$script[] = static::call_array('log', static::handleStyles($record['formatted']));
if (\count($context) === 0 && \count($extra) === 0) {
$script[] = self::call_array('log', self::handleStyles($record->formatted));
} else {
$script = array_merge(
$script,
[static::call_array('groupCollapsed', static::handleStyles($record['formatted']))],
[self::call_array('groupCollapsed', self::handleStyles($record->formatted))],
$context,
$extra,
[static::call('groupEnd')]
[self::call('groupEnd')]
);
}
}
@@ -203,14 +199,14 @@ private static function handleStyles(string $formatted): array

foreach (array_reverse($matches) as $match) {
$args[] = '"font-weight: normal"';
$args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0]));
$args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0]));

$pos = $match[0][1];
$format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + strlen($match[0][0]));
}

$args[] = static::quote('font-weight: normal');
$args[] = static::quote($format);
$args[] = self::quote('font-weight: normal');
$args[] = self::quote($format);

return array_reverse($args);
}
@@ -236,6 +232,7 @@ private static function handleCustomStyles(string $style, string $string): strin

if (null === $style) {
$pcreErrorCode = preg_last_error();

throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
}

@@ -250,16 +247,16 @@ private static function dump(string $title, array $dict): array
{
$script = [];
$dict = array_filter($dict);
if (empty($dict)) {
if (\count($dict) === 0) {
return $script;
}
$script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title));
$script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title));
foreach ($dict as $key => $value) {
$value = json_encode($value);
if (empty($value)) {
$value = static::quote('');
$value = self::quote('');
}
$script[] = static::call('log', static::quote('%s: %o'), static::quote((string) $key), $value);
$script[] = self::call('log', self::quote('%s: %o'), self::quote((string) $key), $value);
}

return $script;
@@ -280,7 +277,7 @@ private static function call(...$args): string
throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true));
}

return static::call_array($method, $args);
return self::call_array($method, $args);
}

/**
48 changes: 23 additions & 25 deletions src/Monolog/Handler/BufferHandler.php
Original file line number Diff line number Diff line change
@@ -11,9 +11,10 @@

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Level;
use Monolog\ResettableInterface;
use Monolog\Formatter\FormatterInterface;
use Monolog\LogRecord;

/**
* Buffers all records until closing the handler and then pass them as batch.
@@ -22,32 +23,30 @@
* sending one per log message.
*
* @author Christophe Coevoet <stof@notk.org>
*
* @phpstan-import-type Record from \Monolog\Logger
*/
class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface
{
use ProcessableHandlerTrait;

/** @var HandlerInterface */
protected $handler;
/** @var int */
protected $bufferSize = 0;
/** @var int */
protected $bufferLimit;
/** @var bool */
protected $flushOnOverflow;
/** @var Record[] */
protected $buffer = [];
/** @var bool */
protected $initialized = false;
protected HandlerInterface $handler;

protected int $bufferSize = 0;

protected int $bufferLimit;

protected bool $flushOnOverflow;

/** @var LogRecord[] */
protected array $buffer = [];

protected bool $initialized = false;

/**
* @param HandlerInterface $handler Handler.
* @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
*/
public function __construct(HandlerInterface $handler, int $bufferLimit = 0, $level = Logger::DEBUG, bool $bubble = true, bool $flushOnOverflow = false)
public function __construct(HandlerInterface $handler, int $bufferLimit = 0, int|string|Level $level = Level::Debug, bool $bubble = true, bool $flushOnOverflow = false)
{
parent::__construct($level, $bubble);
$this->handler = $handler;
@@ -56,11 +55,11 @@ public function __construct(HandlerInterface $handler, int $bufferLimit = 0, $le
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function handle(array $record): bool
public function handle(LogRecord $record): bool
{
if ($record['level'] < $this->level) {
if ($record->level->isLowerThan($this->level)) {
return false;
}

@@ -79,8 +78,7 @@ public function handle(array $record): bool
}
}

if ($this->processors) {
/** @var Record $record */
if (\count($this->processors) > 0) {
$record = $this->processRecord($record);
}

@@ -108,7 +106,7 @@ public function __destruct()
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function close(): void
{
@@ -126,7 +124,7 @@ public function clear(): void
$this->buffer = [];
}

public function reset()
public function reset(): void
{
$this->flush();

@@ -140,7 +138,7 @@ public function reset()
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function setFormatter(FormatterInterface $formatter): HandlerInterface
{
@@ -154,7 +152,7 @@ public function setFormatter(FormatterInterface $formatter): HandlerInterface
}

/**
* {@inheritDoc}
* @inheritDoc
*/
public function getFormatter(): FormatterInterface
{
Loading