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