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

Client::configure()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

Changes 3
Bugs 2 Features 2
Metric Value
c 3
b 2
f 2
dl 0
loc 22
ccs 15
cts 15
cp 1
rs 8.6737
cc 5
eloc 14
nc 2
nop 1
crap 5
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|array $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 array        $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 = (array) $metrics;
261 14
        $data = [];
262 14
        if ($sampleRate < 1) {
263 1
            foreach ($metrics as $metric) {
264 1
                if ((mt_rand() / mt_getrandmax()) <= $sampleRate) {
265 1
                    $data[$metric] = $delta . '|c|@' . $sampleRate;
266 1
                }
267 1
            }
268 1
        } else {
269 13
            foreach ($metrics as $metric) {
270 13
                $data[$metric] = $delta . '|c';
271 13
            }
272
        }
273 14
        return $this->send($data, $tags);
274
    }
275
276
    /**
277
     * Decrement a metric
278
     *
279
     * @param  string|array $metrics    Metric(s) to decrement
280
     * @param  int          $delta      Value to increment the metric by
281
     * @param  int          $sampleRate Sample rate of metric
282
     * @param  array        $tags       List of tags for this metric
283
     *
284
     * @return Client This instance
285
     */
286 2
    public function decrement($metrics, $delta = 1, $sampleRate = 1, array $tags = [])
287
    {
288 2
        return $this->increment($metrics, 0 - $delta, $sampleRate, $tags);
289
    }
290
291
    /**
292
     * Timing
293
     *
294
     * @param  string $metric Metric to track
295
     * @param  float  $time   Time in milliseconds
296
     * @param  array  $tags   List of tags for this metric
297
     *
298
     * @return Client This instance
299
     */
300 3
    public function timing($metric, $time, array $tags = [])
301
    {
302 3
        return $this->send(
303
            [
304 3
                $metric => $time . '|ms',
305 3
            ],
306
            $tags
307 3
        );
308
    }
309
310
    /**
311
     * Time a function
312
     *
313
     * @param  string   $metric Metric to time
314
     * @param  callable $func   Function to record
315
     * @param  array    $tags   List of tags for this metric
316
     *
317
     * @return Client This instance
318
     */
319 1
    public function time($metric, $func, array $tags = [])
320
    {
321 1
        $timer_start = microtime(true);
322 1
        $func();
323 1
        $timer_end = microtime(true);
324 1
        $time = round(($timer_end - $timer_start) * 1000, 4);
325 1
        return $this->timing($metric, $time, $tags);
326
    }
327
328
329
    /**
330
     * Gauges
331
     *
332
     * @param  string $metric Metric to gauge
333
     * @param  int    $value  Set the value of the gauge
334
     * @param  array  $tags   List of tags for this metric
335
     *
336
     * @return Client This instance
337
     */
338 2
    public function gauge($metric, $value, array $tags = [])
339
    {
340 2
        return $this->send(
341
            [
342 2
                $metric => $value . '|g',
343 2
            ],
344
            $tags
345 2
        );
346
    }
347
348
    /**
349
     * Sets - count the number of unique values passed to a key
350
     *
351
     * @param  string $metric
352
     * @param  mixed  $value
353
     * @param  array  $tags List of tags for this metric
354
     *
355
     * @return Client This instance
356
     */
357 2
    public function set($metric, $value, array $tags = [])
358
    {
359 2
        return $this->send(
360
            [
361 2
                $metric => $value . '|s',
362 2
            ],
363
            $tags
364 2
        );
365
    }
366
367
    /**
368
     * Send a event notification
369
     *
370
     * @link http://docs.datadoghq.com/guides/dogstatsd/#events
371
     *
372
     * @param  string $title    Event Title
373
     * @param  string $text     Event Text
374
     * @param  array  $metadata Set of metadata for this event:
375
     *                          - time - Assign a timestamp to the event.
376
     *                          - hostname - Assign a hostname to the event
377
     *                          - key - Assign an aggregation key to th event, to group it with some others
378
     *                          - priority - Can be 'normal' or 'low'
379
     *                          - source - Assign a source type to the event
380
     *                          - alert - Can be 'error', 'warning', 'info' or 'success'
381
     * @param  array  $tags     List of tags for this event
382
     *
383
     * @return Client This instance
384
     * @throws ConnectionException If there is a connection problem with the host
385
     */
386 6
    public function event($title, $text, array $metadata = [], array $tags = [])
387
    {
388 6
        if (!$this->dataDog) {
389 1
            return $this;
390
        }
391
392 5
        $text = str_replace(["\r", "\n"], ['', "\\n"], $text);
393 5
        $metric = sprintf('_e{%d,%d}', strlen($title), strlen($text));
394 5
        $prefix = $this->namespace ? $this->namespace . '.' : '';
395 5
        $value = sprintf('%s|%s', $prefix . $title, $text);
396
397 5
        foreach ($metadata as $key => $data) {
398 2
            if (isset($this->eventMetaData[$key])) {
399 2
                $value .= sprintf('|%s:%s', $this->eventMetaData[$key], $data);
400 2
            }
401 5
        }
402
403 5
        $value .= $this->formatTags($tags);
404
405 5
        return $this->sendMessages([
406 5
            sprintf('%s:%s', $metric, $value),
407 5
        ]);
408
    }
409
410
    /**
411
     * Service Checks
412
     *
413
     * @link http://docs.datadoghq.com/guides/dogstatsd/#service-checks
414
     *
415
     * @param string $name     Name of the service
416
     * @param int    $status   digit corresponding to the status you’re reporting (OK = 0, WARNING = 1, CRITICAL = 2,
417
     *                         UNKNOWN = 3)
418
     * @param array  $metadata - time - Assign a timestamp to the service check
419
     *                         - hostname - Assign a hostname to the service check
420
     * @param array  $tags     List of tags for this event
421
     *
422
     * @return Client This instance
423
     * @throws ConnectionException If there is a connection problem with the host
424
     */
425 6
    public function serviceCheck($name, $status, array $metadata = [], array $tags = [])
426
    {
427 6
        if (!$this->dataDog) {
428 1
            return $this;
429
        }
430
431 5
        $prefix = $this->namespace ? $this->namespace . '.' : '';
432 5
        $value = sprintf('_sc|%s|%d', $prefix . $name, $status);
433
434 5
        $applyMetadata = function ($metadata, $definition) use (&$value) {
435 5
            foreach ($metadata as $key => $data) {
436 3
                if (isset($definition[$key])) {
437 3
                    $value .= sprintf('|%s:%s', $definition[$key], $data);
438 3
                }
439 5
            }
440 5
        };
441
442 5
        $applyMetadata($metadata, $this->serviceCheckMetaData);
443 5
        $value .= $this->formatTags($tags);
444 5
        $applyMetadata($metadata, $this->serviceCheckMessage);
445
446 5
        return $this->sendMessages([
447 5
            $value,
448 5
        ]);
449
    }
450
451
    /**
452
     * @param array $tags A list of tags to apply to each message
453
     *
454
     * @return string
455
     */
456 31
    private function formatTags(array $tags = [])
457
    {
458 31
        if (!$this->dataDog || count($tags) === 0) {
459 20
            return '';
460
        }
461
462 11
        $result = [];
463 11
        foreach ($tags as $key => $value) {
464 11
            if (is_numeric($key)) {
465 10
                $result[] = $value;
466 10
            } else {
467 2
                $result[] = sprintf('%s:%s', $key, $value);
468
            }
469 11
        }
470
471 11
        return sprintf('|#%s', implode(',', $result));
472
    }
473
474
    /**
475
     * Send Data to StatsD Server
476
     *
477
     * @param  array $data A list of messages to send to the server
478
     * @param  array $tags A list of tags to apply to each message
479
     *
480
     * @return Client This instance
481
     * @throws ConnectionException If there is a connection problem with the host
482
     */
483 21
    protected function send(array $data, array $tags = [])
484
    {
485 21
        $messages = array();
486 21
        $prefix = $this->namespace ? $this->namespace . '.' : '';
487 21
        $formattedTags = $this->formatTags(array_merge($this->tags, $tags));
488 21
        foreach ($data as $key => $value) {
489 21
            $messages[] = $prefix . $key . ':' . $value . $formattedTags;
490 21
        }
491 21
        return $this->sendMessages($messages);
492
    }
493
494
    /**
495
     * @param array $messages
496
     *
497
     * @return Client This instance
498
     * @throws ConnectionException If there is a connection problem with the host
499
     */
500 31
    protected function sendMessages(array $messages)
501
    {
502 31
        $socket = @fsockopen('udp://' . $this->host, $this->port, $errno, $errstr, $this->timeout);
503 31
        if (!$socket) {
504 2
            if ($this->throwExceptions) {
505 1
                throw new ConnectionException($this, '(' . $errno . ') ' . $errstr);
506
            } else {
507 1
                trigger_error(
508 1
                    sprintf('StatsD server connection failed (udp://%s:%d)', $this->host, $this->port),
509
                    E_USER_WARNING
510 1
                );
511 1
                return $this;
512
            }
513
        }
514 29
        $this->message = implode("\n", $messages);
515 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...
516 29
        fclose($socket);
517 29
        return $this;
518
    }
519
}
520