FirePHPHandler.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of the Monolog package.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Monolog\Handler;
  11. use Monolog\Formatter\WildfireFormatter;
  12. use Monolog\Formatter\FormatterInterface;
  13. /**
  14. * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol.
  15. *
  16. * @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
  17. */
  18. class FirePHPHandler extends AbstractProcessingHandler
  19. {
  20. /**
  21. * WildFire JSON header message format
  22. */
  23. const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2';
  24. /**
  25. * FirePHP structure for parsing messages & their presentation
  26. */
  27. const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1';
  28. /**
  29. * Must reference a "known" plugin, otherwise headers won't display in FirePHP
  30. */
  31. const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3';
  32. /**
  33. * Header prefix for Wildfire to recognize & parse headers
  34. */
  35. const HEADER_PREFIX = 'X-Wf';
  36. /**
  37. * Whether or not Wildfire vendor-specific headers have been generated & sent yet
  38. */
  39. protected static $initialized = false;
  40. /**
  41. * Shared static message index between potentially multiple handlers
  42. * @var int
  43. */
  44. protected static $messageIndex = 1;
  45. protected static $sendHeaders = true;
  46. /**
  47. * Base header creation function used by init headers & record headers
  48. *
  49. * @param array $meta Wildfire Plugin, Protocol & Structure Indexes
  50. * @param string $message Log message
  51. * @return array Complete header string ready for the client as key and message as value
  52. */
  53. protected function createHeader(array $meta, string $message): array
  54. {
  55. $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta));
  56. return [$header => $message];
  57. }
  58. /**
  59. * Creates message header from record
  60. *
  61. * @see createHeader()
  62. */
  63. protected function createRecordHeader(array $record): array
  64. {
  65. // Wildfire is extensible to support multiple protocols & plugins in a single request,
  66. // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake.
  67. return $this->createHeader(
  68. [1, 1, 1, self::$messageIndex++],
  69. $record['formatted']
  70. );
  71. }
  72. /**
  73. * {@inheritDoc}
  74. */
  75. protected function getDefaultFormatter(): FormatterInterface
  76. {
  77. return new WildfireFormatter();
  78. }
  79. /**
  80. * Wildfire initialization headers to enable message parsing
  81. *
  82. * @see createHeader()
  83. * @see sendHeader()
  84. * @return array
  85. */
  86. protected function getInitHeaders()
  87. {
  88. // Initial payload consists of required headers for Wildfire
  89. return array_merge(
  90. $this->createHeader(['Protocol', 1], self::PROTOCOL_URI),
  91. $this->createHeader([1, 'Structure', 1], self::STRUCTURE_URI),
  92. $this->createHeader([1, 'Plugin', 1], self::PLUGIN_URI)
  93. );
  94. }
  95. /**
  96. * Send header string to the client
  97. *
  98. * @param string $header
  99. * @param string $content
  100. */
  101. protected function sendHeader($header, $content)
  102. {
  103. if (!headers_sent() && self::$sendHeaders) {
  104. header(sprintf('%s: %s', $header, $content));
  105. }
  106. }
  107. /**
  108. * Creates & sends header for a record, ensuring init headers have been sent prior
  109. *
  110. * @see sendHeader()
  111. * @see sendInitHeaders()
  112. * @param array $record
  113. */
  114. protected function write(array $record)
  115. {
  116. if (!self::$sendHeaders) {
  117. return;
  118. }
  119. // WildFire-specific headers must be sent prior to any messages
  120. if (!self::$initialized) {
  121. self::$initialized = true;
  122. self::$sendHeaders = $this->headersAccepted();
  123. if (!self::$sendHeaders) {
  124. return;
  125. }
  126. foreach ($this->getInitHeaders() as $header => $content) {
  127. $this->sendHeader($header, $content);
  128. }
  129. }
  130. $header = $this->createRecordHeader($record);
  131. if (trim(current($header)) !== '') {
  132. $this->sendHeader(key($header), current($header));
  133. }
  134. }
  135. /**
  136. * Verifies if the headers are accepted by the current user agent
  137. *
  138. * @return Boolean
  139. */
  140. protected function headersAccepted()
  141. {
  142. if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) {
  143. return true;
  144. }
  145. return isset($_SERVER['HTTP_X_FIREPHP_VERSION']);
  146. }
  147. }