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 error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.