Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  
   3  namespace GuzzleHttp;
   4  
   5  use Psr\Http\Message\MessageInterface;
   6  use Psr\Http\Message\RequestInterface;
   7  use Psr\Http\Message\ResponseInterface;
   8  
   9  /**
  10   * Formats log messages using variable substitutions for requests, responses,
  11   * and other transactional data.
  12   *
  13   * The following variable substitutions are supported:
  14   *
  15   * - {request}:        Full HTTP request message
  16   * - {response}:       Full HTTP response message
  17   * - {ts}:             ISO 8601 date in GMT
  18   * - {date_iso_8601}   ISO 8601 date in GMT
  19   * - {date_common_log} Apache common log date using the configured timezone.
  20   * - {host}:           Host of the request
  21   * - {method}:         Method of the request
  22   * - {uri}:            URI of the request
  23   * - {version}:        Protocol version
  24   * - {target}:         Request target of the request (path + query + fragment)
  25   * - {hostname}:       Hostname of the machine that sent the request
  26   * - {code}:           Status code of the response (if available)
  27   * - {phrase}:         Reason phrase of the response  (if available)
  28   * - {error}:          Any error messages (if available)
  29   * - {req_header_*}:   Replace `*` with the lowercased name of a request header to add to the message
  30   * - {res_header_*}:   Replace `*` with the lowercased name of a response header to add to the message
  31   * - {req_headers}:    Request headers
  32   * - {res_headers}:    Response headers
  33   * - {req_body}:       Request body
  34   * - {res_body}:       Response body
  35   *
  36   * @final
  37   */
  38  class MessageFormatter implements MessageFormatterInterface
  39  {
  40      /**
  41       * Apache Common Log Format.
  42       *
  43       * @link https://httpd.apache.org/docs/2.4/logs.html#common
  44       *
  45       * @var string
  46       */
  47      public const CLF = "{hostname} {req_header_User-Agent} - [{date_common_log}] \"{method} {target} HTTP/{version}\" {code} {res_header_Content-Length}";
  48      public const DEBUG = ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
  49      public const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';
  50  
  51      /**
  52       * @var string Template used to format log messages
  53       */
  54      private $template;
  55  
  56      /**
  57       * @param string $template Log message template
  58       */
  59      public function __construct(?string $template = self::CLF)
  60      {
  61          $this->template = $template ?: self::CLF;
  62      }
  63  
  64      /**
  65       * Returns a formatted message string.
  66       *
  67       * @param RequestInterface       $request  Request that was sent
  68       * @param ResponseInterface|null $response Response that was received
  69       * @param \Throwable|null        $error    Exception that was received
  70       */
  71      public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string
  72      {
  73          $cache = [];
  74  
  75          /** @var string */
  76          return \preg_replace_callback(
  77              '/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
  78              function (array $matches) use ($request, $response, $error, &$cache) {
  79                  if (isset($cache[$matches[1]])) {
  80                      return $cache[$matches[1]];
  81                  }
  82  
  83                  $result = '';
  84                  switch ($matches[1]) {
  85                      case 'request':
  86                          $result = Psr7\Message::toString($request);
  87                          break;
  88                      case 'response':
  89                          $result = $response ? Psr7\Message::toString($response) : '';
  90                          break;
  91                      case 'req_headers':
  92                          $result = \trim($request->getMethod()
  93                                  . ' ' . $request->getRequestTarget())
  94                              . ' HTTP/' . $request->getProtocolVersion() . "\r\n"
  95                              . $this->headers($request);
  96                          break;
  97                      case 'res_headers':
  98                          $result = $response ?
  99                              \sprintf(
 100                                  'HTTP/%s %d %s',
 101                                  $response->getProtocolVersion(),
 102                                  $response->getStatusCode(),
 103                                  $response->getReasonPhrase()
 104                              ) . "\r\n" . $this->headers($response)
 105                              : 'NULL';
 106                          break;
 107                      case 'req_body':
 108                          $result = $request->getBody()->__toString();
 109                          break;
 110                      case 'res_body':
 111                          if (!$response instanceof ResponseInterface) {
 112                              $result = 'NULL';
 113                              break;
 114                          }
 115  
 116                          $body = $response->getBody();
 117  
 118                          if (!$body->isSeekable()) {
 119                              $result = 'RESPONSE_NOT_LOGGEABLE';
 120                              break;
 121                          }
 122  
 123                          $result = $response->getBody()->__toString();
 124                          break;
 125                      case 'ts':
 126                      case 'date_iso_8601':
 127                          $result = \gmdate('c');
 128                          break;
 129                      case 'date_common_log':
 130                          $result = \date('d/M/Y:H:i:s O');
 131                          break;
 132                      case 'method':
 133                          $result = $request->getMethod();
 134                          break;
 135                      case 'version':
 136                          $result = $request->getProtocolVersion();
 137                          break;
 138                      case 'uri':
 139                      case 'url':
 140                          $result = $request->getUri()->__toString();
 141                          break;
 142                      case 'target':
 143                          $result = $request->getRequestTarget();
 144                          break;
 145                      case 'req_version':
 146                          $result = $request->getProtocolVersion();
 147                          break;
 148                      case 'res_version':
 149                          $result = $response
 150                              ? $response->getProtocolVersion()
 151                              : 'NULL';
 152                          break;
 153                      case 'host':
 154                          $result = $request->getHeaderLine('Host');
 155                          break;
 156                      case 'hostname':
 157                          $result = \gethostname();
 158                          break;
 159                      case 'code':
 160                          $result = $response ? $response->getStatusCode() : 'NULL';
 161                          break;
 162                      case 'phrase':
 163                          $result = $response ? $response->getReasonPhrase() : 'NULL';
 164                          break;
 165                      case 'error':
 166                          $result = $error ? $error->getMessage() : 'NULL';
 167                          break;
 168                      default:
 169                          // handle prefixed dynamic headers
 170                          if (\strpos($matches[1], 'req_header_') === 0) {
 171                              $result = $request->getHeaderLine(\substr($matches[1], 11));
 172                          } elseif (\strpos($matches[1], 'res_header_') === 0) {
 173                              $result = $response
 174                                  ? $response->getHeaderLine(\substr($matches[1], 11))
 175                                  : 'NULL';
 176                          }
 177                  }
 178  
 179                  $cache[$matches[1]] = $result;
 180                  return $result;
 181              },
 182              $this->template
 183          );
 184      }
 185  
 186      /**
 187       * Get headers from message as string
 188       */
 189      private function headers(MessageInterface $message): string
 190      {
 191          $result = '';
 192          foreach ($message->getHeaders() as $name => $values) {
 193              $result .= $name . ': ' . \implode(', ', $values) . "\r\n";
 194          }
 195  
 196          return \trim($result);
 197      }
 198  }