1<?php
2
3namespace Guzzle\Log;
4
5use Guzzle\Http\Curl\CurlHandle;
6use Guzzle\Http\Message\RequestInterface;
7use Guzzle\Http\Message\EntityEnclosingRequestInterface;
8use Guzzle\Http\Message\Response;
9
10/**
11 * Message formatter used in various places in the framework
12 *
13 * Format messages using a template that can contain the the following variables:
14 *
15 * - {request}:       Full HTTP request message
16 * - {response}:      Full HTTP response message
17 * - {ts}:            Timestamp
18 * - {host}:          Host of the request
19 * - {method}:        Method of the request
20 * - {url}:           URL of the request
21 * - {host}:          Host of the request
22 * - {protocol}:      Request protocol
23 * - {version}:       Protocol version
24 * - {resource}:      Resource of the request (path + query + fragment)
25 * - {port}:          Port of the request
26 * - {hostname}:      Hostname of the machine that sent the request
27 * - {code}:          Status code of the response (if available)
28 * - {phrase}:        Reason phrase of the response  (if available)
29 * - {curl_error}:    Curl error message (if available)
30 * - {curl_code}:     Curl error code (if available)
31 * - {curl_stderr}:   Curl standard error (if available)
32 * - {connect_time}:  Time in seconds it took to establish the connection (if available)
33 * - {total_time}:    Total transaction time in seconds for last transfer (if available)
34 * - {req_header_*}:  Replace `*` with the lowercased name of a request header to add to the message
35 * - {res_header_*}:  Replace `*` with the lowercased name of a response header to add to the message
36 * - {req_body}:      Request body
37 * - {res_body}:      Response body
38 */
39class MessageFormatter
40{
41    const DEFAULT_FORMAT = "{hostname} {req_header_User-Agent} - [{ts}] \"{method} {resource} {protocol}/{version}\" {code} {res_header_Content-Length}";
42    const DEBUG_FORMAT = ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{curl_stderr}";
43    const SHORT_FORMAT = '[{ts}] "{method} {resource} {protocol}/{version}" {code}';
44
45    /**
46     * @var string Template used to format log messages
47     */
48    protected $template;
49
50    /**
51     * @param string $template Log message template
52     */
53    public function __construct($template = self::DEFAULT_FORMAT)
54    {
55        $this->template = $template ?: self::DEFAULT_FORMAT;
56    }
57
58    /**
59     * Set the template to use for logging
60     *
61     * @param string $template Log message template
62     *
63     * @return self
64     */
65    public function setTemplate($template)
66    {
67        $this->template = $template;
68
69        return $this;
70    }
71
72    /**
73     * Returns a formatted message
74     *
75     * @param RequestInterface $request    Request that was sent
76     * @param Response         $response   Response that was received
77     * @param CurlHandle       $handle     Curl handle associated with the message
78     * @param array            $customData Associative array of custom template data
79     *
80     * @return string
81     */
82    public function format(
83        RequestInterface $request,
84        Response $response = null,
85        CurlHandle $handle = null,
86        array $customData = array()
87    ) {
88        $cache = $customData;
89
90        return preg_replace_callback(
91            '/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
92            function (array $matches) use ($request, $response, $handle, &$cache) {
93
94                if (array_key_exists($matches[1], $cache)) {
95                    return $cache[$matches[1]];
96                }
97
98                $result = '';
99                switch ($matches[1]) {
100                    case 'request':
101                        $result = (string) $request;
102                        break;
103                    case 'response':
104                        $result = (string) $response;
105                        break;
106                    case 'req_body':
107                        $result = $request instanceof EntityEnclosingRequestInterface
108                            ? (string) $request->getBody() : '';
109                        break;
110                    case 'res_body':
111                        $result = $response ? $response->getBody(true) : '';
112                        break;
113                    case 'ts':
114                        $result = gmdate('c');
115                        break;
116                    case 'method':
117                        $result = $request->getMethod();
118                        break;
119                    case 'url':
120                        $result = (string) $request->getUrl();
121                        break;
122                    case 'resource':
123                        $result = $request->getResource();
124                        break;
125                    case 'protocol':
126                        $result = 'HTTP';
127                        break;
128                    case 'version':
129                        $result = $request->getProtocolVersion();
130                        break;
131                    case 'host':
132                        $result = $request->getHost();
133                        break;
134                    case 'hostname':
135                        $result = gethostname();
136                        break;
137                    case 'port':
138                        $result = $request->getPort();
139                        break;
140                    case 'code':
141                        $result = $response ? $response->getStatusCode() : '';
142                        break;
143                    case 'phrase':
144                        $result = $response ? $response->getReasonPhrase() : '';
145                        break;
146                    case 'connect_time':
147                        $result = $handle && $handle->getInfo(CURLINFO_CONNECT_TIME)
148                            ? $handle->getInfo(CURLINFO_CONNECT_TIME)
149                            : ($response ? $response->getInfo('connect_time') : '');
150                        break;
151                    case 'total_time':
152                        $result = $handle && $handle->getInfo(CURLINFO_TOTAL_TIME)
153                            ? $handle->getInfo(CURLINFO_TOTAL_TIME)
154                            : ($response ? $response->getInfo('total_time') : '');
155                        break;
156                    case 'curl_error':
157                        $result = $handle ? $handle->getError() : '';
158                        break;
159                    case 'curl_code':
160                        $result = $handle ? $handle->getErrorNo() : '';
161                        break;
162                    case 'curl_stderr':
163                        $result =  $handle ? $handle->getStderr() : '';
164                        break;
165                    default:
166                        if (strpos($matches[1], 'req_header_') === 0) {
167                            $result = $request->getHeader(substr($matches[1], 11));
168                        } elseif ($response && strpos($matches[1], 'res_header_') === 0) {
169                            $result = $response->getHeader(substr($matches[1], 11));
170                        }
171                }
172
173                $cache[$matches[1]] = $result;
174                return $result;
175            },
176            $this->template
177        );
178    }
179}
180