Completed
Pull Request — master (#1)
by Harry
03:02
created

Client::__toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Graze\DogStatsD;
4
5
use Graze\DogStatsD\Exception\ConfigurationException;
6
use Graze\DogStatsD\Exception\ConnectionException;
7
8
/**
9
 * StatsD Client Class - Modified to support DataDogs statsd server
10
 */
11
class Client
12
{
13
    const STATUS_OK       = 0;
14
    const STATUS_WARNING  = 1;
15
    const STATUS_CRITICAL = 2;
16
    const STATUS_UNKNOWN  = 3;
17
18
    const PRIORITY_LOW    = 'low';
19
    const PRIORITY_NORMAL = 'normal';
20
21
    const ALERT_ERROR   = 'error';
22
    const ALERT_WARNING = 'warning';
23
    const ALERT_INFO    = 'info';
24
    const ALERT_SUCCESS = 'success';
25
26
    /**
27
     * Instance instances array
28
     *
29
     * @var array
30
     */
31
    protected static $instances = array();
32
33
    /**
34
     * Instance ID
35
     *
36
     * @var string
37
     */
38
    protected $instanceId;
39
40
    /**
41
     * Server Host
42
     *
43
     * @var string
44
     */
45
    protected $host = '127.0.0.1';
46
47
    /**
48
     * Server Port
49
     *
50
     * @var integer
51
     */
52
    protected $port = 8125;
53
54
    /**
55
     * Last message sent to the server
56
     *
57
     * @var string
58
     */
59
    protected $message = '';
60
61
    /**
62
     * Class namespace
63
     *
64
     * @var string
65
     */
66
    protected $namespace = '';
67
68
    /**
69
     * Timeout for creating the socket connection
70
     *
71
     * @var null|int
72
     */
73
    protected $timeout;
74
75
    /**
76
     * Whether or not an exception should be thrown on failed connections
77
     *
78
     * @var bool
79
     */
80
    protected $throwExceptions = true;
81
82
    /**
83
     * Metadata for the DataDog event message
84
     *
85
     * @var array - time - Assign a timestamp to the event.
86
     *            - hostname - Assign a hostname to the event
87
     *            - key - Assign an aggregation key to th event, to group it with some others
88
     *            - priority - Can be 'normal' or 'low'
89
     *            - source - Assign a source type to the event
90
     *            - alert - Can be 'error', 'warning', 'info' or 'success'
91
     */
92
    protected $eventMetaData = [
93
        'time'     => 'd',
94
        'hostname' => 'h',
95
        'key'      => 'k',
96
        'priority' => 'p',
97
        'source'   => 's',
98
        'alert'    => 't',
99
    ];
100
101
    /**
102
     * @var array - time - Assign a timestamp to the service check
103
     *            - hostname - Assign a hostname to the service check
104
     */
105
    protected $serviceCheckMetaData = [
106
        'time'     => 'd',
107
        'hostname' => 'h',
108
    ];
109
110
    /**
111
     * @var array - message - Assign a message to the service check
112
     */
113
    protected $serviceCheckMessage = [
114
        'message' => 'm',
115
    ];
116
117
    /**
118
     * Is the server type DataDog implementation
119
     *
120
     * @var bool
121
     */
122
    protected $dataDog = true;
123
124
    /**
125
     * Set of default tags to send to every request
126
     *
127
     * @var array
128
     */
129
    protected $tags = [];
130
131
    /**
132
     * Singleton Reference
133
     *
134
     * @param  string $name Instance name
135
     *
136
     * @return Client Client instance
137
     */
138 1
    public static function instance($name = 'default')
139
    {
140 1
        if (!isset(static::$instances[$name])) {
141 1
            static::$instances[$name] = new static($name);
142 1
        }
143 1
        return static::$instances[$name];
144
    }
145
146
    /**
147
     * Create a new instance
148
     *
149
     * @param string|null $instanceId
150
     */
151 45
    public function __construct($instanceId = null)
152
    {
153 45
        $this->instanceId = $instanceId ?: uniqid();
154
155 45
        if (empty($this->timeout)) {
156 45
            $this->timeout = (int) ini_get('default_socket_timeout');
157 45
        }
158 45
    }
159
160
    /**
161
     * Get string value of instance
162
     *
163
     * @return string String representation of this instance
164
     */
165 2
    public function __toString()
166
    {
167 2
        return 'DogStatsD\Client::[' . $this->instanceId . ']';
168
    }
169
170
    /**
171
     * Initialize Connection Details
172
     *
173
     * @param array $options Configuration options
174
     *                       :host <string|ip> - host to talk to
175
     *                       :port <int> - Port to communicate with
176
     *                       :namespace <string> - Default namespace
177
     *                       :timeout <int> - Timeout in seconds
178
     *                       :throwExceptions <bool> - Throw an exception on connection error
179
     *                       :dataDog <bool> - Use DataDog's version of statsd (Default: true)
180
     *                       :tags <array> - List of tags to add to each message
181
     *
182
     * @return Client This instance
183
     * @throws ConfigurationException If port is invalid
184
     */
185 45
    public function configure(array $options = [])
186
    {
187
        $setOption = function ($name) use ($options) {
188 45
            if (isset($options[$name])) {
189 15
                $this->{$name} = $options[$name];
190 15
            }
191 45
        };
192
193 45
        $setOption('host');
194 45
        $setOption('port');
195 45
        $setOption('namespace');
196 45
        $setOption('timeout');
197 45
        $setOption('throwExceptions');
198 45
        $setOption('dataDog');
199 45
        $setOption('tags');
200
201 45
        if (!$this->port || !is_numeric($this->port) || $this->port > 65535) {
202 2
            throw new ConfigurationException($this, 'Port is out of range');
203
        }
204
205 45
        return $this;
206
    }
207
208
    /**
209
     * Get Host
210
     *
211
     * @return string Host
212
     */
213 1
    public function getHost()
214
    {
215 1
        return $this->host;
216
    }
217
218
    /**
219
     * Get Port
220
     *
221
     * @return int Port
222
     */
223 2
    public function getPort()
224
    {
225 2
        return $this->port;
226
    }
227
228
    /**
229
     * Get Namespace
230
     *
231
     * @return string Namespace
232
     */
233 1
    public function getNamespace()
234
    {
235 1
        return $this->namespace;
236
    }
237
238
    /**
239
     * Get Last Message
240
     *
241
     * @return string Last message sent to server
242
     */
243 31
    public function getLastMessage()
244
    {
245 31
        return $this->message;
246
    }
247
248
    /**
249
     * Increment a metric
250
     *
251
     * @param string|string[] $metrics    Metric(s) to increment
252
     * @param int             $delta      Value to decrement the metric by
253
     * @param int             $sampleRate Sample rate of metric
254
     * @param string[]        $tags       List of tags for this metric
255
     *
256
     * @return Client This instance
257
     */
258 14
    public function increment($metrics, $delta = 1, $sampleRate = 1, array $tags = [])
259
    {
260 14
        $metrics = is_array($metrics) ? $metrics : [$metrics];
261
262 14
        $data = [];
263 14
        if ($sampleRate < 1) {
264 1
            foreach ($metrics as $metric) {
265 1
                if ((mt_rand() / mt_getrandmax()) <= $sampleRate) {
266 1
                    $data[$metric] = $delta . '|c|@' . $sampleRate;
267 1
                }
268 1
            }
269 1
        } else {
270 13
            foreach ($metrics as $metric) {
271 13
                $data[$metric] = $delta . '|c';
272 13
            }
273
        }
274 14
        return $this->send($data, $tags);
275
    }
276
277
    /**
278
     * Decrement a metric
279
     *
280
     * @param string|string[] $metrics    Metric(s) to decrement
281
     * @param int             $delta      Value to increment the metric by
282
     * @param int             $sampleRate Sample rate of metric
283
     * @param string[]        $tags       List of tags for this metric
284
     *
285
     * @return Client This instance
286
     */
287 2
    public function decrement($metrics, $delta = 1, $sampleRate = 1, array $tags = [])
288
    {
289 2
        return $this->increment($metrics, 0 - $delta, $sampleRate, $tags);
290
    }
291
292
    /**
293
     * Timing
294
     *
295
     * @param string   $metric Metric to track
296
     * @param float    $time   Time in milliseconds
297
     * @param string[] $tags   List of tags for this metric
298
     *
299
     * @return Client This instance
300
     */
301 3
    public function timing($metric, $time, array $tags = [])
302
    {
303 3
        return $this->send(
304
            [
305 3
                $metric => $time . '|ms',
306 3
            ],
307
            $tags
308 3
        );
309
    }
310
311
    /**
312
     * Time a function
313
     *
314
     * @param string   $metric Metric to time
315
     * @param callable $func   Function to record
316
     * @param string[] $tags   List of tags for this metric
317
     *
318
     * @return Client This instance
319
     */
320 1
    public function time($metric, $func, array $tags = [])
321
    {
322 1
        $timer_start = microtime(true);
323 1
        $func();
324 1
        $timer_end = microtime(true);
325 1
        $time = round(($timer_end - $timer_start) * 1000, 4);
326 1
        return $this->timing($metric, $time, $tags);
327
    }
328
329
330
    /**
331
     * Gauges
332
     *
333
     * @param string   $metric Metric to gauge
334
     * @param int      $value  Set the value of the gauge
335
     * @param string[] $tags   List of tags for this metric
336
     *
337
     * @return Client This instance
338
     */
339 2
    public function gauge($metric, $value, array $tags = [])
340
    {
341 2
        return $this->send(
342
            [
343 2
                $metric => $value . '|g',
344 2
            ],
345
            $tags
346 2
        );
347
    }
348
349
    /**
350
     * Sets - count the number of unique elements for a group
351
     *
352
     * @param string   $metric
353
     * @param int      $value
354
     * @param string[] $tags List of tags for this metric
355
     *
356
     * @return Client This instance
357
     */
358 2
    public function set($metric, $value, array $tags = [])
359
    {
360 2
        return $this->send(
361
            [
362 2
                $metric => $value . '|s',
363 2
            ],
364
            $tags
365 2
        );
366
    }
367
368
    /**
369
     * Send a event notification
370
     *
371
     * @link http://docs.datadoghq.com/guides/dogstatsd/#events
372
     *
373
     * @param string   $title     Event Title
374
     * @param string   $text      Event Text
375
     * @param array    $metadata  Set of metadata for this event:
376
     *                            - time - Assign a timestamp to the event.
377
     *                            - hostname - Assign a hostname to the event
378
     *                            - key - Assign an aggregation key to th event, to group it with some others
379
     *                            - priority - Can be 'normal' or 'low'
380
     *                            - source - Assign a source type to the event
381
     *                            - alert - Can be 'error', 'warning', 'info' or 'success'
382
     * @param string[] $tags      List of tags for this event
383
     *
384
     * @return Client This instance
385
     * @throws ConnectionException If there is a connection problem with the host
386
     */
387 6
    public function event($title, $text, array $metadata = [], array $tags = [])
388
    {
389 6
        if (!$this->dataDog) {
390 1
            return $this;
391
        }
392
393 5
        $text = str_replace(["\r", "\n"], ['', "\\n"], $text);
394 5
        $metric = sprintf('_e{%d,%d}', strlen($title), strlen($text));
395 5
        $prefix = $this->namespace ? $this->namespace . '.' : '';
396 5
        $value = sprintf('%s|%s', $prefix . $title, $text);
397
398 5
        foreach ($metadata as $key => $data) {
399 2
            if (isset($this->eventMetaData[$key])) {
400 2
                $value .= sprintf('|%s:%s', $this->eventMetaData[$key], $data);
401 2
            }
402 5
        }
403
404 5
        $value .= $this->formatTags($tags);
405
406 5
        return $this->sendMessages([
407 5
            sprintf('%s:%s', $metric, $value),
408 5
        ]);
409
    }
410
411
    /**
412
     * Service Checks
413
     *
414
     * @link http://docs.datadoghq.com/guides/dogstatsd/#service-checks
415
     *
416
     * @param string   $name     Name of the service
417
     * @param int      $status   digit corresponding to the status you’re reporting (OK = 0, WARNING = 1, CRITICAL = 2,
418
     *                           UNKNOWN = 3)
419
     * @param array    $metadata - time - Assign a timestamp to the service check
420
     *                           - hostname - Assign a hostname to the service check
421
     * @param string[] $tags     List of tags for this event
422
     *
423
     * @return Client This instance
424
     * @throws ConnectionException If there is a connection problem with the host
425
     */
426 6
    public function serviceCheck($name, $status, array $metadata = [], array $tags = [])
427
    {
428 6
        if (!$this->dataDog) {
429 1
            return $this;
430
        }
431
432 5
        $prefix = $this->namespace ? $this->namespace . '.' : '';
433 5
        $value = sprintf('_sc|%s|%d', $prefix . $name, $status);
434
435 5
        $applyMetadata = function ($metadata, $definition) use (&$value) {
436 5
            foreach ($metadata as $key => $data) {
437 3
                if (isset($definition[$key])) {
438 3
                    $value .= sprintf('|%s:%s', $definition[$key], $data);
439 3
                }
440 5
            }
441 5
        };
442
443 5
        $applyMetadata($metadata, $this->serviceCheckMetaData);
444 5
        $value .= $this->formatTags($tags);
445 5
        $applyMetadata($metadata, $this->serviceCheckMessage);
446
447 5
        return $this->sendMessages([
448 5
            $value,
449 5
        ]);
450
    }
451
452
    /**
453
     * @param string[] $tags A list of tags to apply to each message
454
     *
455
     * @return string
456
     */
457 31
    private function formatTags(array $tags = [])
458
    {
459 31
        if (!$this->dataDog || count($tags) === 0) {
460 20
            return '';
461
        }
462
463 11
        $result = [];
464 11
        foreach ($tags as $key => $value) {
465 11
            if (is_numeric($key)) {
466 10
                $result[] = $value;
467 10
            } else {
468 2
                $result[] = sprintf('%s:%s', $key, $value);
469
            }
470 11
        }
471
472 11
        return sprintf('|#%s', implode(',', $result));
473
    }
474
475
    /**
476
     * Send Data to StatsD Server
477
     *
478
     * @param string[] $data A list of messages to send to the server
479
     * @param string[] $tags A list of tags to apply to each message
480
     *
481
     * @return Client This instance
482
     * @throws ConnectionException If there is a connection problem with the host
483
     */
484 21
    protected function send(array $data, array $tags = [])
485
    {
486 21
        $messages = array();
487 21
        $prefix = $this->namespace ? $this->namespace . '.' : '';
488 21
        $formattedTags = $this->formatTags(array_merge($this->tags, $tags));
489 21
        foreach ($data as $key => $value) {
490 21
            $messages[] = $prefix . $key . ':' . $value . $formattedTags;
491 21
        }
492 21
        return $this->sendMessages($messages);
493
    }
494
495
    /**
496
     * @param string[] $messages
497
     *
498
     * @return Client This instance
499
     * @throws ConnectionException If there is a connection problem with the host
500
     */
501 31
    protected function sendMessages(array $messages)
502
    {
503 31
        $socket = @fsockopen('udp://' . $this->host, $this->port, $errno, $errstr, $this->timeout);
504 31
        if (!$socket) {
505 2
            if ($this->throwExceptions) {
506 1
                throw new ConnectionException($this, '(' . $errno . ') ' . $errstr);
507
            } else {
508 1
                trigger_error(
509 1
                    sprintf('StatsD server connection failed (udp://%s:%d)', $this->host, $this->port),
510
                    E_USER_WARNING
511 1
                );
512 1
                return $this;
513
            }
514
        }
515 29
        $this->message = implode("\n", $messages);
516 29
        @fwrite($socket, $this->message);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
517 29
        fclose($socket);
518 29
        return $this;
519
    }
520
}
521