1
|
|
|
<?php |
2
|
|
|
namespace Bankiru\MonologLogstash; |
3
|
|
|
|
4
|
|
|
use Monolog\Handler\AbstractProcessingHandler; |
5
|
|
|
use Monolog\Logger; |
6
|
|
|
use Psr\Log\InvalidArgumentException; |
7
|
|
|
|
8
|
|
|
class ZMQHandler extends AbstractProcessingHandler |
9
|
|
|
{ |
10
|
|
|
protected $dsn; |
11
|
|
|
protected $persistent; |
12
|
|
|
protected $options = array(); |
13
|
|
|
protected $socketType; |
14
|
|
|
protected $socketOptions = array(); |
15
|
|
|
/** @var \ZMQSocket */ |
16
|
|
|
private $socket; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @param int $dsn |
20
|
|
|
* @param bool $persistent |
21
|
|
|
* @param array $options |
22
|
|
|
* @param $socketType |
23
|
|
|
* @param array $socketOptions |
24
|
|
|
* @param integer $level The minimum logging level at which this handler will be triggered |
25
|
|
|
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not |
26
|
|
|
* @throws \Psr\Log\InvalidArgumentException |
27
|
|
|
*/ |
28
|
|
|
public function __construct($dsn, $persistent = true, $options = array(), $socketType = \ZMQ::SOCKET_PUSH, $socketOptions = array(), |
29
|
|
|
$level = Logger::DEBUG, $bubble = true) |
30
|
|
|
{ |
31
|
|
|
parent::__construct($level, $bubble); |
32
|
|
|
|
33
|
|
|
if (!is_string($dsn) || !trim($dsn) || parse_url($dsn) === false) { |
34
|
|
|
throw new InvalidArgumentException('dsn is invalid'); |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
if (parse_url($dsn, PHP_URL_SCHEME) != 'tcp') { |
38
|
|
|
throw new InvalidArgumentException('dsn has invalid schema'); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
if (!parse_url($dsn, PHP_URL_PORT)) { |
42
|
|
|
throw new InvalidArgumentException('no port specified in the dsn'); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
if (!preg_match('@^(/)?$@', parse_url($dsn, PHP_URL_PATH)) |
46
|
|
|
|| parse_url($dsn, PHP_URL_QUERY) != '' |
47
|
|
|
|| parse_url($dsn, PHP_URL_FRAGMENT) != '' |
48
|
|
|
) { |
49
|
|
|
throw new InvalidArgumentException("dsn '{$dsn}' has unexpected path or query"); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
if (!in_array($socketType, array(\ZMQ::SOCKET_PUSH, \ZMQ::SOCKET_REQ))) { |
53
|
|
|
throw new InvalidArgumentException('socketType is invalid'); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
$this->dsn = $dsn; |
57
|
|
|
$this->persistent = $persistent; |
58
|
|
|
$this->options = $options; |
59
|
|
|
$this->socketType = $socketType; |
60
|
|
|
$this->socketOptions = $socketOptions; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* {@inheritdoc} |
65
|
|
|
*/ |
66
|
|
|
public function close() |
67
|
|
|
{ |
68
|
|
|
if ($this->socket instanceof \ZMQSocket && !$this->socket->isPersistent()) { |
69
|
|
|
$this->socket->disconnect($this->dsn); |
70
|
|
|
} |
71
|
|
|
$this->socket = null; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* {@inheritdoc} |
76
|
|
|
* @throws \RuntimeException |
77
|
|
|
*/ |
78
|
|
|
protected function write(array $record) |
79
|
|
|
{ |
80
|
|
|
$socket = $this->getSocket(); |
81
|
|
|
if ($socket) { |
82
|
|
|
try { |
83
|
|
|
$socket->send((string)$record['formatted'], \ZMQ::MODE_DONTWAIT); |
84
|
|
|
} catch (\ZMQSocketException $e) { |
85
|
|
|
throw new \RuntimeException(sprintf('Could not write logs to logstash through ZMQ: %s', (string)$e)); |
86
|
|
|
} |
87
|
|
|
} |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @return \ZMQSocket |
92
|
|
|
* @throws \RuntimeException |
93
|
|
|
*/ |
94
|
|
|
protected function getSocket() |
95
|
|
|
{ |
96
|
|
|
if ($this->socket === null) { |
97
|
|
|
$exception = null; |
98
|
|
|
|
99
|
|
|
$socketOptions = $this->socketOptions; |
100
|
|
|
$dsn = $this->dsn; |
101
|
|
|
|
102
|
|
|
$this->socket = $this->getContext()->getSocket( |
103
|
|
|
$this->socketType, |
104
|
|
|
$this->persistent ? get_class($this) : null, |
105
|
|
|
function (\ZMQSocket $socket) use (&$exception, $socketOptions, $dsn) { |
106
|
|
|
foreach ($socketOptions as $optKey => $optValue) { |
107
|
|
|
$socket->setSockOpt($optKey, $optValue); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
try { |
111
|
|
|
$socket->connect($dsn); |
112
|
|
|
} catch (\ZMQSocketException $e) { |
113
|
|
|
$exception = $e; |
114
|
|
|
$socket->disconnect($dsn); |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
); |
118
|
|
|
|
119
|
|
|
if ($exception) { |
120
|
|
|
$this->socket = null; |
121
|
|
|
throw new \RuntimeException(sprintf('Could not connect to logstash through ZMQ: %s', (string)$exception)); |
122
|
|
|
} |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
return $this->socket; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* @return \ZMQContext |
130
|
|
|
*/ |
131
|
|
|
protected function getContext() |
132
|
|
|
{ |
133
|
|
|
$context = new \ZMQContext(); |
134
|
|
|
if ($this->options && method_exists($context, 'setOpt')) { |
|
|
|
|
135
|
|
|
foreach ($this->options as $optKey => $optValue) { |
136
|
|
|
$context->setOpt($optKey, $optValue); |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
return $context; |
141
|
|
|
} |
142
|
|
|
} |
143
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.