scratch – Blame information for rev 120

Subversion Repositories:
Rev:
Rev Author Line No. Line
120 office 1 <?php
2  
3 /*
4 * This file is part of the Monolog package.
5 *
6 * (c) Jordi Boggiano <j.boggiano@seld.be>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11  
12 namespace Monolog\Handler;
13  
14 use Monolog\Logger;
15  
16 /**
17 * Sends notifications through the hipchat api to a hipchat room
18 *
19 * Notes:
20 * API token - HipChat API token
21 * Room - HipChat Room Id or name, where messages are sent
22 * Name - Name used to send the message (from)
23 * notify - Should the message trigger a notification in the clients
24 * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2)
25 *
26 * @author Rafael Dohms <rafael@doh.ms>
27 * @see https://www.hipchat.com/docs/api
28 */
29 class HipChatHandler extends SocketHandler
30 {
31 /**
32 * Use API version 1
33 */
34 const API_V1 = 'v1';
35  
36 /**
37 * Use API version v2
38 */
39 const API_V2 = 'v2';
40  
41 /**
42 * The maximum allowed length for the name used in the "from" field.
43 */
44 const MAXIMUM_NAME_LENGTH = 15;
45  
46 /**
47 * The maximum allowed length for the message.
48 */
49 const MAXIMUM_MESSAGE_LENGTH = 9500;
50  
51 /**
52 * @var string
53 */
54 private $token;
55  
56 /**
57 * @var string
58 */
59 private $room;
60  
61 /**
62 * @var string
63 */
64 private $name;
65  
66 /**
67 * @var bool
68 */
69 private $notify;
70  
71 /**
72 * @var string
73 */
74 private $format;
75  
76 /**
77 * @var string
78 */
79 private $host;
80  
81 /**
82 * @var string
83 */
84 private $version;
85  
86 /**
87 * @param string $token HipChat API Token
88 * @param string $room The room that should be alerted of the message (Id or Name)
89 * @param string $name Name used in the "from" field.
90 * @param bool $notify Trigger a notification in clients or not
91 * @param int $level The minimum logging level at which this handler will be triggered
92 * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
93 * @param bool $useSSL Whether to connect via SSL.
94 * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages)
95 * @param string $host The HipChat server hostname.
96 * @param string $version The HipChat API version (default HipChatHandler::API_V1)
97 */
98 public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1)
99 {
100 if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) {
101 throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.');
102 }
103  
104 $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80';
105 parent::__construct($connectionString, $level, $bubble);
106  
107 $this->token = $token;
108 $this->name = $name;
109 $this->notify = $notify;
110 $this->room = $room;
111 $this->format = $format;
112 $this->host = $host;
113 $this->version = $version;
114 }
115  
116 /**
117 * {@inheritdoc}
118 *
119 * @param array $record
120 * @return string
121 */
122 protected function generateDataStream($record)
123 {
124 $content = $this->buildContent($record);
125  
126 return $this->buildHeader($content) . $content;
127 }
128  
129 /**
130 * Builds the body of API call
131 *
132 * @param array $record
133 * @return string
134 */
135 private function buildContent($record)
136 {
137 $dataArray = array(
138 'notify' => $this->version == self::API_V1 ?
139 ($this->notify ? 1 : 0) :
140 ($this->notify ? 'true' : 'false'),
141 'message' => $record['formatted'],
142 'message_format' => $this->format,
143 'color' => $this->getAlertColor($record['level']),
144 );
145  
146 if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) {
147 if (function_exists('mb_substr')) {
148 $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
149 } else {
150 $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
151 }
152 }
153  
154 // if we are using the legacy API then we need to send some additional information
155 if ($this->version == self::API_V1) {
156 $dataArray['room_id'] = $this->room;
157 }
158  
159 // append the sender name if it is set
160 // always append it if we use the v1 api (it is required in v1)
161 if ($this->version == self::API_V1 || $this->name !== null) {
162 $dataArray['from'] = (string) $this->name;
163 }
164  
165 return http_build_query($dataArray);
166 }
167  
168 /**
169 * Builds the header of the API Call
170 *
171 * @param string $content
172 * @return string
173 */
174 private function buildHeader($content)
175 {
176 if ($this->version == self::API_V1) {
177 $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n";
178 } else {
179 // needed for rooms with special (spaces, etc) characters in the name
180 $room = rawurlencode($this->room);
181 $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n";
182 }
183  
184 $header .= "Host: {$this->host}\r\n";
185 $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
186 $header .= "Content-Length: " . strlen($content) . "\r\n";
187 $header .= "\r\n";
188  
189 return $header;
190 }
191  
192 /**
193 * Assigns a color to each level of log records.
194 *
195 * @param int $level
196 * @return string
197 */
198 protected function getAlertColor($level)
199 {
200 switch (true) {
201 case $level >= Logger::ERROR:
202 return 'red';
203 case $level >= Logger::WARNING:
204 return 'yellow';
205 case $level >= Logger::INFO:
206 return 'green';
207 case $level == Logger::DEBUG:
208 return 'gray';
209 default:
210 return 'yellow';
211 }
212 }
213  
214 /**
215 * {@inheritdoc}
216 *
217 * @param array $record
218 */
219 protected function write(array $record)
220 {
221 parent::write($record);
222 $this->closeSocket();
223 }
224  
225 /**
226 * {@inheritdoc}
227 */
228 public function handleBatch(array $records)
229 {
230 if (count($records) == 0) {
231 return true;
232 }
233  
234 $batchRecords = $this->combineRecords($records);
235  
236 $handled = false;
237 foreach ($batchRecords as $batchRecord) {
238 if ($this->isHandling($batchRecord)) {
239 $this->write($batchRecord);
240 $handled = true;
241 }
242 }
243  
244 if (!$handled) {
245 return false;
246 }
247  
248 return false === $this->bubble;
249 }
250  
251 /**
252 * Combines multiple records into one. Error level of the combined record
253 * will be the highest level from the given records. Datetime will be taken
254 * from the first record.
255 *
256 * @param $records
257 * @return array
258 */
259 private function combineRecords($records)
260 {
261 $batchRecord = null;
262 $batchRecords = array();
263 $messages = array();
264 $formattedMessages = array();
265 $level = 0;
266 $levelName = null;
267 $datetime = null;
268  
269 foreach ($records as $record) {
270 $record = $this->processRecord($record);
271  
272 if ($record['level'] > $level) {
273 $level = $record['level'];
274 $levelName = $record['level_name'];
275 }
276  
277 if (null === $datetime) {
278 $datetime = $record['datetime'];
279 }
280  
281 $messages[] = $record['message'];
282 $messageStr = implode(PHP_EOL, $messages);
283 $formattedMessages[] = $this->getFormatter()->format($record);
284 $formattedMessageStr = implode('', $formattedMessages);
285  
286 $batchRecord = array(
287 'message' => $messageStr,
288 'formatted' => $formattedMessageStr,
289 'context' => array(),
290 'extra' => array(),
291 );
292  
293 if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) {
294 // Pop the last message and implode the remaining messages
295 $lastMessage = array_pop($messages);
296 $lastFormattedMessage = array_pop($formattedMessages);
297 $batchRecord['message'] = implode(PHP_EOL, $messages);
298 $batchRecord['formatted'] = implode('', $formattedMessages);
299  
300 $batchRecords[] = $batchRecord;
301 $messages = array($lastMessage);
302 $formattedMessages = array($lastFormattedMessage);
303  
304 $batchRecord = null;
305 }
306 }
307  
308 if (null !== $batchRecord) {
309 $batchRecords[] = $batchRecord;
310 }
311  
312 // Set the max level and datetime for all records
313 foreach ($batchRecords as &$batchRecord) {
314 $batchRecord = array_merge(
315 $batchRecord,
316 array(
317 'level' => $level,
318 'level_name' => $levelName,
319 'datetime' => $datetime,
320 )
321 );
322 }
323  
324 return $batchRecords;
325 }
326  
327 /**
328 * Validates the length of a string.
329 *
330 * If the `mb_strlen()` function is available, it will use that, as HipChat
331 * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`.
332 *
333 * Note that this might cause false failures in the specific case of using
334 * a valid name with less than 16 characters, but 16 or more bytes, on a
335 * system where `mb_strlen()` is unavailable.
336 *
337 * @param string $str
338 * @param int $length
339 *
340 * @return bool
341 */
342 private function validateStringLength($str, $length)
343 {
344 if (function_exists('mb_strlen')) {
345 return (mb_strlen($str) <= $length);
346 }
347  
348 return (strlen($str) <= $length);
349 }
350 }