summaryrefslogtreecommitdiff
path: root/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php
blob: cabdea854a9c281b06b6675b1386d613948161e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
<?php

namespace Guzzle\Plugin\Log;

use Guzzle\Common\Event;
use Guzzle\Log\LogAdapterInterface;
use Guzzle\Log\MessageFormatter;
use Guzzle\Log\ClosureLogAdapter;
use Guzzle\Http\EntityBody;
use Guzzle\Http\Message\EntityEnclosingRequestInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Plugin class that will add request and response logging to an HTTP request.
 *
 * The log plugin uses a message formatter that allows custom messages via template variable substitution.
 *
 * @see MessageLogger for a list of available log template variable substitutions
 */
class LogPlugin implements EventSubscriberInterface
{
    /** @var LogAdapterInterface Adapter responsible for writing log data */
    protected $logAdapter;

    /** @var MessageFormatter Formatter used to format messages before logging */
    protected $formatter;

    /** @var bool Whether or not to wire request and response bodies */
    protected $wireBodies;

    /**
     * @param LogAdapterInterface     $logAdapter Adapter object used to log message
     * @param string|MessageFormatter $formatter  Formatter used to format log messages or the formatter template
     * @param bool                    $wireBodies Set to true to track request and response bodies using a temporary
     *                                            buffer if the bodies are not repeatable.
     */
    public function __construct(
        LogAdapterInterface $logAdapter,
        $formatter = null,
        $wireBodies = false
    ) {
        $this->logAdapter = $logAdapter;
        $this->formatter = $formatter instanceof MessageFormatter ? $formatter : new MessageFormatter($formatter);
        $this->wireBodies = $wireBodies;
    }

    /**
     * Get a log plugin that outputs full request, response, and curl error information to stderr
     *
     * @param bool     $wireBodies Set to false to disable request/response body output when they use are not repeatable
     * @param resource $stream     Stream to write to when logging. Defaults to STDERR when it is available
     *
     * @return self
     */
    public static function getDebugPlugin($wireBodies = true, $stream = null)
    {
        if ($stream === null) {
            if (defined('STDERR')) {
                $stream = STDERR;
            } else {
                $stream = fopen('php://output', 'w');
            }
        }

        return new self(new ClosureLogAdapter(function ($m) use ($stream) {
            fwrite($stream, $m . PHP_EOL);
        }), "# Request:\n{request}\n\n# Response:\n{response}\n\n# Errors: {curl_code} {curl_error}", $wireBodies);
    }

    public static function getSubscribedEvents()
    {
        return array(
            'curl.callback.write' => array('onCurlWrite', 255),
            'curl.callback.read'  => array('onCurlRead', 255),
            'request.before_send' => array('onRequestBeforeSend', 255),
            'request.sent'        => array('onRequestSent', 255)
        );
    }

    /**
     * Event triggered when curl data is read from a request
     *
     * @param Event $event
     */
    public function onCurlRead(Event $event)
    {
        // Stream the request body to the log if the body is not repeatable
        if ($wire = $event['request']->getParams()->get('request_wire')) {
            $wire->write($event['read']);
        }
    }

    /**
     * Event triggered when curl data is written to a response
     *
     * @param Event $event
     */
    public function onCurlWrite(Event $event)
    {
        // Stream the response body to the log if the body is not repeatable
        if ($wire = $event['request']->getParams()->get('response_wire')) {
            $wire->write($event['write']);
        }
    }

    /**
     * Called before a request is sent
     *
     * @param Event $event
     */
    public function onRequestBeforeSend(Event $event)
    {
        if ($this->wireBodies) {
            $request = $event['request'];
            // Ensure that curl IO events are emitted
            $request->getCurlOptions()->set('emit_io', true);
            // We need to make special handling for content wiring and non-repeatable streams.
            if ($request instanceof EntityEnclosingRequestInterface && $request->getBody()
                && (!$request->getBody()->isSeekable() || !$request->getBody()->isReadable())
            ) {
                // The body of the request cannot be recalled so logging the body will require us to buffer it
                $request->getParams()->set('request_wire', EntityBody::factory());
            }
            if (!$request->getResponseBody()->isRepeatable()) {
                // The body of the response cannot be recalled so logging the body will require us to buffer it
                $request->getParams()->set('response_wire', EntityBody::factory());
            }
        }
    }

    /**
     * Triggers the actual log write when a request completes
     *
     * @param Event $event
     */
    public function onRequestSent(Event $event)
    {
        $request = $event['request'];
        $response = $event['response'];
        $handle = $event['handle'];

        if ($wire = $request->getParams()->get('request_wire')) {
            $request = clone $request;
            $request->setBody($wire);
        }

        if ($wire = $request->getParams()->get('response_wire')) {
            $response = clone $response;
            $response->setBody($wire);
        }

        // Send the log message to the adapter, adding a category and host
        $priority = $response && $response->isError() ? LOG_ERR : LOG_DEBUG;
        $message = $this->formatter->format($request, $response, $handle);
        $this->logAdapter->log($message, $priority, array(
            'request'  => $request,
            'response' => $response,
            'handle'   => $handle
        ));
    }
}