PluginProbe
ManageWP Worker / 4.9.28
ManageWP Worker v4.9.28
4.9.38 4.9.37 4.9.36 4.9.35 4.9.34 3.8.7 3.8.8 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.18 3.9.19 3.9.2 3.9.20 3.9.21 3.9.22 3.9.23 3.9.24 All 73 releases
worker / src / Gelf / Publisher.php

Publisher.php in ManageWP Worker 4.9.28, at src/Gelf/Publisher.php

271 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class Gelf_Publisher
4 {
5 const CHUNK_SIZE_WAN = 1420;
6
7 const CHUNK_SIZE_LAN = 8154;
8
9 const GRAYLOG2_DEFAULT_PORT = 12201;
10
11 const GRAYLOG2_PROTOCOL_VERSION = '1.0';
12
13 /**
14 * @var string
15 */
16 protected $hostname;
17
18 /**
19 * @var int
20 */
21 protected $port;
22
23 /**
24 * @var int|null
25 */
26 protected $fallbackPort;
27
28 /**
29 * @var int
30 */
31 protected $chunkSize;
32
33 /**
34 * @var resource|null
35 */
36 protected $streamSocketClient = null;
37
38 /**
39 * @var bool
40 */
41 private static $brokenSocket = false;
42
43 /**
44 * Creates a new publisher that sends errors to a Graylog2 server via UDP
45 *
46 * @throws InvalidArgumentException
47 *
48 * @param string $hostname
49 * @param integer $port
50 * @param integer|null $fallbackPort
51 * @param integer $chunkSize
52 */
53 public function __construct($hostname, $port = null, $fallbackPort = null, $chunkSize = null)
54 {
55 // Check whether the parameters are set correctly
56 if (!$hostname) {
57 throw new InvalidArgumentException('$hostname must be set');
58 }
59
60 if ($port === null) {
61 $port = self::GRAYLOG2_DEFAULT_PORT;
62 } elseif (!is_numeric($port)) {
63 throw new InvalidArgumentException('$port must be an integer');
64 }
65
66 if ($fallbackPort !== null && !is_numeric($fallbackPort)) {
67 throw new InvalidArgumentException('$fallbackPort must be an integer');
68 }
69
70 if ($chunkSize === null) {
71 $chunkSize = self::CHUNK_SIZE_WAN;
72 } elseif (!is_numeric($chunkSize)) {
73 throw new InvalidArgumentException('$chunkSize must be an integer');
74 }
75
76 $this->hostname = $hostname;
77 $this->port = $port;
78 $this->fallbackPort = $fallbackPort;
79 $this->chunkSize = $chunkSize;
80 }
81
82 /**
83 * Publishes a Gelf_Message, returns false if an error occurred during write.
84 *
85 * @throws UnexpectedValueException
86 *
87 * @param Gelf_Message $message
88 *
89 * @return boolean
90 */
91 public function publish(Gelf_Message $message)
92 {
93 if (self::$brokenSocket) {
94 return false;
95 }
96 // Check if required message parameters are set
97 if (!$message->getShortMessage() || !$message->getHost()) {
98 throw new UnexpectedValueException(
99 'Missing required data parameter: "version", "short_message" and "host" are required.'
100 );
101 }
102
103 // Set Graylog protocol version
104 $message->setVersion(self::GRAYLOG2_PROTOCOL_VERSION);
105
106 // Encode the message as json string and compress it using gzip
107 $preparedMessage = $this->getPreparedMessage($message);
108
109 // Infinite-loop break.
110 self::$brokenSocket = true;
111 // Open a connection to GrayLog server.
112 $socket = $this->getSocketConnection();
113
114 if (!$socket) {
115 return false;
116 }
117 self::$brokenSocket = false;
118
119 // Several udp writes are required to publish the message
120 if ($this->isMessageSizeGreaterChunkSize($preparedMessage)) {
121 // A unique id which consists of the microtime and a random value
122 $messageId = $this->getMessageId();
123
124 // Split the message into chunks.
125 $messageChunks = $this->getMessageChunks($preparedMessage);
126 $messageChunksCount = count($messageChunks);
127
128 // Send chunks to GrayLog server.
129 foreach (array_values($messageChunks) as $messageChunkIndex => $messageChunk) {
130 $bytesWritten = $this->writeMessageChunkToSocket(
131 $socket,
132 $messageId,
133 $messageChunk,
134 $messageChunkIndex,
135 $messageChunksCount
136 );
137
138 if (false === $bytesWritten) {
139 // Abort due to write error
140 return false;
141 }
142 }
143 } else {
144 // A single write is enough to get the message published
145 if (false === $this->writeMessageToSocket($socket, $preparedMessage)) {
146 // Abort due to write error
147 return false;
148 }
149 }
150
151 // This increases stability a lot if messages are sent in a loop
152 // A value of 20 means 0.02 ms
153 usleep(20);
154
155 // Message successful sent
156 return true;
157 }
158
159 /**
160 * @param Gelf_Message $message
161 *
162 * @return string
163 */
164 protected function getPreparedMessage(Gelf_Message $message)
165 {
166 return gzcompress(json_encode($message->toArray()));
167 }
168
169 /**
170 * @return resource|false
171 */
172 protected function getSocketConnection()
173 {
174 if (!$this->streamSocketClient) {
175 $hostname = gethostbyname($this->hostname);
176 $this->streamSocketClient = stream_socket_client(sprintf('udp://%s:%d', $hostname, $this->port));
177 if ($this->streamSocketClient === false && $this->fallbackPort) {
178 $this->streamSocketClient = stream_socket_client(sprintf('tcp://%s:%d', $hostname, $this->fallbackPort));
179 }
180 }
181
182 return $this->streamSocketClient;
183 }
184
185 /**
186 * @param string $preparedMessage
187 *
188 * @return boolean
189 */
190 protected function isMessageSizeGreaterChunkSize($preparedMessage)
191 {
192 return (strlen($preparedMessage) > $this->chunkSize);
193 }
194
195 /**
196 * @return float
197 */
198 protected function getMessageId()
199 {
200 return (float)(microtime(true).mt_rand(0, 10000));
201 }
202
203 /**
204 * @param string $preparedMessage
205 *
206 * @return array
207 */
208 protected function getMessageChunks($preparedMessage)
209 {
210 return str_split($preparedMessage, $this->chunkSize);
211 }
212
213 /**
214 * @param float $messageId
215 * @param string $data
216 * @param integer $sequence
217 * @param integer $sequenceSize
218 *
219 * @throws InvalidArgumentException
220 * @return string
221 */
222 protected function prependChunkInformation($messageId, $data, $sequence, $sequenceSize)
223 {
224 if (!is_string($data) || $data === '') {
225 throw new InvalidArgumentException('Data must be a string and not be empty.');
226 }
227
228 if (!is_integer($sequence) || !is_integer($sequenceSize)) {
229 throw new InvalidArgumentException('Sequence number and size must be integer.');
230 }
231
232 if ($sequenceSize <= 0) {
233 throw new InvalidArgumentException('Sequence size must be greater than 0.');
234 }
235
236 if ($sequence > $sequenceSize) {
237 throw new InvalidArgumentException('Sequence size must be greater than sequence number.');
238 }
239
240 return pack('CC', 30, 15).substr(md5($messageId, true), 0, 8).pack('CC', $sequence, $sequenceSize).$data;
241 }
242
243 /**
244 * @param resource $socket
245 * @param float $messageId
246 * @param string $messageChunk
247 * @param integer $messageChunkIndex
248 * @param integer $messageChunksCount
249 *
250 * @return integer|boolean
251 */
252 protected function writeMessageChunkToSocket($socket, $messageId, $messageChunk, $messageChunkIndex, $messageChunksCount)
253 {
254 return fwrite(
255 $socket,
256 $this->prependChunkInformation($messageId, $messageChunk, $messageChunkIndex, $messageChunksCount)
257 );
258 }
259
260 /**
261 * @param resource $socket
262 * @param string $preparedMessage
263 *
264 * @return integer|boolean
265 */
266 protected function writeMessageToSocket($socket, $preparedMessage)
267 {
268 return fwrite($socket, $preparedMessage);
269 }
270 }
271