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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body