1 | // |
2 | // RemoteSyslogListener.cpp |
3 | // |
4 | // Library: Net |
5 | // Package: Logging |
6 | // Module: RemoteSyslogListener |
7 | // |
8 | // Copyright (c) 2007, Applied Informatics Software Engineering GmbH. |
9 | // and Contributors. |
10 | // |
11 | // SPDX-License-Identifier: BSL-1.0 |
12 | // |
13 | |
14 | |
15 | #include "Poco/Net/RemoteSyslogListener.h" |
16 | #include "Poco/Net/RemoteSyslogChannel.h" |
17 | #include "Poco/Net/DatagramSocket.h" |
18 | #include "Poco/Net/SocketAddress.h" |
19 | #include "Poco/Runnable.h" |
20 | #include "Poco/Notification.h" |
21 | #include "Poco/AutoPtr.h" |
22 | #include "Poco/NumberParser.h" |
23 | #include "Poco/NumberFormatter.h" |
24 | #include "Poco/DateTimeParser.h" |
25 | #include "Poco/Message.h" |
26 | #include "Poco/LoggingFactory.h" |
27 | #include "Poco/Buffer.h" |
28 | #include "Poco/Ascii.h" |
29 | #include <cstddef> |
30 | |
31 | |
32 | namespace Poco { |
33 | namespace Net { |
34 | |
35 | |
36 | // |
37 | // MessageNotification |
38 | // |
39 | |
40 | |
41 | class MessageNotification: public Poco::Notification |
42 | { |
43 | public: |
44 | MessageNotification(const char* buffer, std::size_t length, const Poco::Net::SocketAddress& sourceAddress): |
45 | _message(buffer, length), |
46 | _sourceAddress(sourceAddress) |
47 | { |
48 | } |
49 | |
50 | MessageNotification(const std::string& message, const Poco::Net::SocketAddress& sourceAddress): |
51 | _message(message), |
52 | _sourceAddress(sourceAddress) |
53 | { |
54 | } |
55 | |
56 | ~MessageNotification() |
57 | { |
58 | } |
59 | |
60 | const std::string& message() const |
61 | { |
62 | return _message; |
63 | } |
64 | |
65 | const Poco::Net::SocketAddress& sourceAddress() const |
66 | { |
67 | return _sourceAddress; |
68 | } |
69 | |
70 | private: |
71 | std::string _message; |
72 | Poco::Net::SocketAddress _sourceAddress; |
73 | }; |
74 | |
75 | |
76 | // |
77 | // RemoteUDPListener |
78 | // |
79 | |
80 | |
81 | class RemoteUDPListener: public Poco::Runnable |
82 | { |
83 | public: |
84 | enum |
85 | { |
86 | WAITTIME_MILLISEC = 1000, |
87 | BUFFER_SIZE = 65536 |
88 | }; |
89 | |
90 | RemoteUDPListener(Poco::NotificationQueue& queue, Poco::UInt16 port); |
91 | ~RemoteUDPListener(); |
92 | |
93 | void run(); |
94 | void safeStop(); |
95 | |
96 | private: |
97 | Poco::NotificationQueue& _queue; |
98 | DatagramSocket _socket; |
99 | bool _stopped; |
100 | }; |
101 | |
102 | |
103 | RemoteUDPListener::RemoteUDPListener(Poco::NotificationQueue& queue, Poco::UInt16 port): |
104 | _queue(queue), |
105 | _socket(Poco::Net::SocketAddress(Poco::Net::IPAddress(), port)), |
106 | _stopped(false) |
107 | { |
108 | } |
109 | |
110 | |
111 | RemoteUDPListener::~RemoteUDPListener() |
112 | { |
113 | } |
114 | |
115 | |
116 | void RemoteUDPListener::run() |
117 | { |
118 | Poco::Buffer<char> buffer(BUFFER_SIZE); |
119 | Poco::Timespan waitTime(WAITTIME_MILLISEC* 1000); |
120 | while (!_stopped) |
121 | { |
122 | try |
123 | { |
124 | if (_socket.poll(waitTime, Socket::SELECT_READ)) |
125 | { |
126 | Poco::Net::SocketAddress sourceAddress; |
127 | int n = _socket.receiveFrom(buffer.begin(), BUFFER_SIZE, sourceAddress); |
128 | if (n > 0) |
129 | { |
130 | _queue.enqueueNotification(new MessageNotification(buffer.begin(), n, sourceAddress)); |
131 | } |
132 | } |
133 | } |
134 | catch (...) |
135 | { |
136 | // lazy exception catching |
137 | } |
138 | } |
139 | } |
140 | |
141 | |
142 | void RemoteUDPListener::safeStop() |
143 | { |
144 | _stopped = true; |
145 | } |
146 | |
147 | |
148 | // |
149 | // SyslogParser |
150 | // |
151 | |
152 | |
153 | class SyslogParser: public Poco::Runnable |
154 | { |
155 | public: |
156 | static const std::string NILVALUE; |
157 | |
158 | enum |
159 | { |
160 | WAITTIME_MILLISEC = 1000 |
161 | }; |
162 | |
163 | SyslogParser(Poco::NotificationQueue& queue, RemoteSyslogListener* pListener); |
164 | ~SyslogParser(); |
165 | |
166 | void parse(const std::string& line, Poco::Message& message); |
167 | void run(); |
168 | void safeStop(); |
169 | |
170 | static Poco::Message::Priority convert(RemoteSyslogChannel::Severity severity); |
171 | |
172 | private: |
173 | void parsePrio(const std::string& line, std::size_t& pos, RemoteSyslogChannel::Severity& severity, RemoteSyslogChannel::Facility& fac); |
174 | void parseNew(const std::string& line, RemoteSyslogChannel::Severity severity, RemoteSyslogChannel::Facility fac, std::size_t& pos, Poco::Message& message); |
175 | void parseBSD(const std::string& line, RemoteSyslogChannel::Severity severity, RemoteSyslogChannel::Facility fac, std::size_t& pos, Poco::Message& message); |
176 | |
177 | static std::string parseUntilSpace(const std::string& line, std::size_t& pos); |
178 | /// Parses until it encounters the next space char, returns the string from pos, excluding space |
179 | /// pos will point past the space char |
180 | |
181 | private: |
182 | Poco::NotificationQueue& _queue; |
183 | bool _stopped; |
184 | RemoteSyslogListener* _pListener; |
185 | }; |
186 | |
187 | |
188 | const std::string SyslogParser::NILVALUE("-" ); |
189 | |
190 | |
191 | SyslogParser::SyslogParser(Poco::NotificationQueue& queue, RemoteSyslogListener* pListener): |
192 | _queue(queue), |
193 | _stopped(false), |
194 | _pListener(pListener) |
195 | { |
196 | poco_check_ptr (_pListener); |
197 | } |
198 | |
199 | |
200 | SyslogParser::~SyslogParser() |
201 | { |
202 | } |
203 | |
204 | |
205 | void SyslogParser::run() |
206 | { |
207 | while (!_stopped) |
208 | { |
209 | try |
210 | { |
211 | Poco::AutoPtr<Poco::Notification> pNf(_queue.waitDequeueNotification(WAITTIME_MILLISEC)); |
212 | if (pNf) |
213 | { |
214 | Poco::AutoPtr<MessageNotification> pMsgNf = pNf.cast<MessageNotification>(); |
215 | Poco::Message message; |
216 | parse(pMsgNf->message(), message); |
217 | message["addr" ] =pMsgNf->sourceAddress().host().toString(); |
218 | _pListener->log(message); |
219 | } |
220 | } |
221 | catch (Poco::Exception&) |
222 | { |
223 | // parsing exception, what should we do? |
224 | } |
225 | catch (...) |
226 | { |
227 | } |
228 | } |
229 | } |
230 | |
231 | |
232 | void SyslogParser::safeStop() |
233 | { |
234 | _stopped = true; |
235 | } |
236 | |
237 | |
238 | void SyslogParser::parse(const std::string& line, Poco::Message& message) |
239 | { |
240 | // <int> -> int: lower 3 bits severity, upper bits: facility |
241 | std::size_t pos = 0; |
242 | RemoteSyslogChannel::Severity severity; |
243 | RemoteSyslogChannel::Facility fac; |
244 | parsePrio(line, pos, severity, fac); |
245 | |
246 | // the next field decide if we parse an old BSD message or a new syslog message |
247 | // BSD: expects a month value in string form: Jan, Feb... |
248 | // SYSLOG expects a version number: 1 |
249 | |
250 | if (Poco::Ascii::isDigit(line[pos])) |
251 | { |
252 | parseNew(line, severity, fac, pos, message); |
253 | } |
254 | else |
255 | { |
256 | parseBSD(line, severity, fac, pos, message); |
257 | } |
258 | poco_assert (pos == line.size()); |
259 | } |
260 | |
261 | |
262 | void SyslogParser::parsePrio(const std::string& line, std::size_t& pos, RemoteSyslogChannel::Severity& severity, RemoteSyslogChannel::Facility& fac) |
263 | { |
264 | poco_assert (pos < line.size()); |
265 | poco_assert (line[pos] == '<'); |
266 | ++pos; |
267 | std::size_t start = pos; |
268 | |
269 | while (pos < line.size() && Poco::Ascii::isDigit(line[pos])) |
270 | ++pos; |
271 | |
272 | poco_assert (line[pos] == '>'); |
273 | poco_assert (pos - start > 0); |
274 | std::string valStr = line.substr(start, pos - start); |
275 | ++pos; // skip the > |
276 | |
277 | int val = Poco::NumberParser::parse(valStr); |
278 | poco_assert (val >= 0 && val <= (RemoteSyslogChannel::SYSLOG_LOCAL7 + RemoteSyslogChannel::SYSLOG_DEBUG)); |
279 | |
280 | Poco::UInt16 pri = static_cast<Poco::UInt16>(val); |
281 | // now get the lowest 3 bits |
282 | severity = static_cast<RemoteSyslogChannel::Severity>(pri & 0x0007u); |
283 | fac = static_cast<RemoteSyslogChannel::Facility>(pri & 0xfff8u); |
284 | } |
285 | |
286 | |
287 | void SyslogParser::parseNew(const std::string& line, RemoteSyslogChannel::Severity severity, RemoteSyslogChannel::Facility fac, std::size_t& pos, Poco::Message& message) |
288 | { |
289 | Poco::Message::Priority prio = convert(severity); |
290 | // rest of the unparsed header is: |
291 | // VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID |
292 | std::string versionStr(parseUntilSpace(line, pos)); |
293 | std::string timeStr(parseUntilSpace(line, pos)); // can be the nilvalue! |
294 | std::string hostName(parseUntilSpace(line, pos)); |
295 | std::string appName(parseUntilSpace(line, pos)); |
296 | std::string procId(parseUntilSpace(line, pos)); |
297 | std::string msgId(parseUntilSpace(line, pos)); |
298 | std::string messageText(line.substr(pos)); |
299 | pos = line.size(); |
300 | Poco::DateTime date; |
301 | int tzd = 0; |
302 | bool hasDate = Poco::DateTimeParser::tryParse(RemoteSyslogChannel::SYSLOG_TIMEFORMAT, timeStr, date, tzd); |
303 | Poco::Message logEntry(msgId, messageText, prio); |
304 | logEntry[RemoteSyslogListener::LOG_PROP_HOST] = hostName; |
305 | logEntry[RemoteSyslogListener::LOG_PROP_APP] = appName; |
306 | |
307 | if (hasDate) |
308 | logEntry.setTime(date.timestamp()); |
309 | int lval(0); |
310 | Poco::NumberParser::tryParse(procId, lval); |
311 | logEntry.setPid(lval); |
312 | message.swap(logEntry); |
313 | } |
314 | |
315 | |
316 | void SyslogParser::parseBSD(const std::string& line, RemoteSyslogChannel::Severity severity, RemoteSyslogChannel::Facility fac, std::size_t& pos, Poco::Message& message) |
317 | { |
318 | Poco::Message::Priority prio = convert(severity); |
319 | // rest of the unparsed header is: |
320 | // "%b %f %H:%M:%S" SP hostname|ipaddress |
321 | // detect three spaces |
322 | int spaceCnt = 0; |
323 | std::size_t start = pos; |
324 | while (spaceCnt < 3 && pos < line.size()) |
325 | { |
326 | if (line[pos] == ' ') |
327 | { |
328 | spaceCnt++; |
329 | if (spaceCnt == 1) |
330 | { |
331 | // size must be 3 chars for month |
332 | if (pos - start != 3) |
333 | { |
334 | // probably a shortened time value, or the hostname |
335 | // assume hostName |
336 | Poco::Message logEntry(line.substr(start, pos-start), line.substr(pos+1), prio); |
337 | message.swap(logEntry); |
338 | return; |
339 | } |
340 | } |
341 | else if (spaceCnt == 2) |
342 | { |
343 | // a day value! |
344 | if (!(Poco::Ascii::isDigit(line[pos-1]) && (Poco::Ascii::isDigit(line[pos-2]) || Poco::Ascii::isSpace(line[pos-2])))) |
345 | { |
346 | // assume the next field is a hostname |
347 | spaceCnt = 3; |
348 | } |
349 | } |
350 | if (pos + 1 < line.size() && line[pos+1] == ' ') |
351 | { |
352 | // we have two spaces when the day value is smaller than 10! |
353 | ++pos; // skip one |
354 | } |
355 | } |
356 | ++pos; |
357 | } |
358 | std::string timeStr(line.substr(start, pos-start-1)); |
359 | int tzd(0); |
360 | Poco::DateTime date; |
361 | int year = date.year(); // year is not included, use the current one |
362 | bool hasDate = Poco::DateTimeParser::tryParse(RemoteSyslogChannel::BSD_TIMEFORMAT, timeStr, date, tzd); |
363 | if (hasDate) |
364 | { |
365 | int m = date.month(); |
366 | int d = date.day(); |
367 | int h = date.hour(); |
368 | int min = date.minute(); |
369 | int sec = date.second(); |
370 | date = Poco::DateTime(year, m, d, h, min, sec); |
371 | } |
372 | // next entry is host SP |
373 | std::string hostName(parseUntilSpace(line, pos)); |
374 | |
375 | // TAG: at most 32 alphanumeric chars, ANY non alphannumeric indicates start of message content |
376 | // ignore: treat everything as content |
377 | std::string messageText(line.substr(pos)); |
378 | pos = line.size(); |
379 | Poco::Message logEntry(hostName, messageText, prio); |
380 | logEntry.setTime(date.timestamp()); |
381 | message.swap(logEntry); |
382 | } |
383 | |
384 | |
385 | std::string SyslogParser::parseUntilSpace(const std::string& line, std::size_t& pos) |
386 | { |
387 | std::size_t start = pos; |
388 | while (pos < line.size() && !Poco::Ascii::isSpace(line[pos])) |
389 | ++pos; |
390 | // skip space |
391 | ++pos; |
392 | return line.substr(start, pos-start-1); |
393 | } |
394 | |
395 | |
396 | Poco::Message::Priority SyslogParser::convert(RemoteSyslogChannel::Severity severity) |
397 | { |
398 | switch (severity) |
399 | { |
400 | case RemoteSyslogChannel::SYSLOG_EMERGENCY: |
401 | return Poco::Message::PRIO_FATAL; |
402 | case RemoteSyslogChannel::SYSLOG_ALERT: |
403 | return Poco::Message::PRIO_FATAL; |
404 | case RemoteSyslogChannel::SYSLOG_CRITICAL: |
405 | return Poco::Message::PRIO_CRITICAL; |
406 | case RemoteSyslogChannel::SYSLOG_ERROR: |
407 | return Poco::Message::PRIO_ERROR; |
408 | case RemoteSyslogChannel::SYSLOG_WARNING: |
409 | return Poco::Message::PRIO_WARNING; |
410 | case RemoteSyslogChannel::SYSLOG_NOTICE: |
411 | return Poco::Message::PRIO_NOTICE; |
412 | case RemoteSyslogChannel::SYSLOG_INFORMATIONAL: |
413 | return Poco::Message::PRIO_INFORMATION; |
414 | case RemoteSyslogChannel::SYSLOG_DEBUG: |
415 | return Poco::Message::PRIO_DEBUG; |
416 | } |
417 | throw Poco::LogicException("Illegal severity value in message" ); |
418 | } |
419 | |
420 | |
421 | // |
422 | // RemoteSyslogListener |
423 | // |
424 | |
425 | |
426 | const std::string RemoteSyslogListener::PROP_PORT("port" ); |
427 | const std::string RemoteSyslogListener::PROP_THREADS("threads" ); |
428 | |
429 | const std::string RemoteSyslogListener::LOG_PROP_APP("app" ); |
430 | const std::string RemoteSyslogListener::LOG_PROP_HOST("host" ); |
431 | |
432 | |
433 | RemoteSyslogListener::RemoteSyslogListener(): |
434 | _pListener(0), |
435 | _pParser(0), |
436 | _port(RemoteSyslogChannel::SYSLOG_PORT), |
437 | _threads(1) |
438 | { |
439 | } |
440 | |
441 | |
442 | RemoteSyslogListener::RemoteSyslogListener(Poco::UInt16 port): |
443 | _pListener(0), |
444 | _pParser(0), |
445 | _port(port), |
446 | _threads(1) |
447 | { |
448 | } |
449 | |
450 | |
451 | RemoteSyslogListener::RemoteSyslogListener(Poco::UInt16 port, int threads): |
452 | _pListener(0), |
453 | _pParser(0), |
454 | _port(port), |
455 | _threads(threads) |
456 | { |
457 | } |
458 | |
459 | |
460 | RemoteSyslogListener::~RemoteSyslogListener() |
461 | { |
462 | } |
463 | |
464 | |
465 | void RemoteSyslogListener::processMessage(const std::string& messageText) |
466 | { |
467 | Poco::Message message; |
468 | _pParser->parse(messageText, message); |
469 | log(message); |
470 | } |
471 | |
472 | |
473 | void RemoteSyslogListener::enqueueMessage(const std::string& messageText, const Poco::Net::SocketAddress& senderAddress) |
474 | { |
475 | _queue.enqueueNotification(new MessageNotification(messageText, senderAddress)); |
476 | } |
477 | |
478 | |
479 | void RemoteSyslogListener::setProperty(const std::string& name, const std::string& value) |
480 | { |
481 | if (name == PROP_PORT) |
482 | { |
483 | int val = Poco::NumberParser::parse(value); |
484 | if (val >= 0 && val < 65536) |
485 | _port = static_cast<Poco::UInt16>(val); |
486 | else |
487 | throw Poco::InvalidArgumentException("Not a valid port number" , value); |
488 | } |
489 | else if (name == PROP_THREADS) |
490 | { |
491 | int val = Poco::NumberParser::parse(value); |
492 | if (val > 0 && val < 16) |
493 | _threads = val; |
494 | else |
495 | throw Poco::InvalidArgumentException("Invalid number of threads" , value); |
496 | } |
497 | else |
498 | { |
499 | SplitterChannel::setProperty(name, value); |
500 | } |
501 | } |
502 | |
503 | |
504 | std::string RemoteSyslogListener::getProperty(const std::string& name) const |
505 | { |
506 | if (name == PROP_PORT) |
507 | return Poco::NumberFormatter::format(_port); |
508 | else if (name == PROP_THREADS) |
509 | return Poco::NumberFormatter::format(_threads); |
510 | else |
511 | return SplitterChannel::getProperty(name); |
512 | } |
513 | |
514 | |
515 | void RemoteSyslogListener::open() |
516 | { |
517 | SplitterChannel::open(); |
518 | _pParser = new SyslogParser(_queue, this); |
519 | if (_port > 0) |
520 | { |
521 | _pListener = new RemoteUDPListener(_queue, _port); |
522 | } |
523 | for (int i = 0; i < _threads; i++) |
524 | { |
525 | _threadPool.start(*_pParser); |
526 | } |
527 | if (_pListener) |
528 | { |
529 | _threadPool.start(*_pListener); |
530 | } |
531 | } |
532 | |
533 | |
534 | void RemoteSyslogListener::close() |
535 | { |
536 | if (_pListener) |
537 | { |
538 | _pListener->safeStop(); |
539 | } |
540 | if (_pParser) |
541 | { |
542 | _pParser->safeStop(); |
543 | } |
544 | _queue.clear(); |
545 | _threadPool.joinAll(); |
546 | delete _pListener; |
547 | delete _pParser; |
548 | _pListener = 0; |
549 | _pParser = 0; |
550 | SplitterChannel::close(); |
551 | } |
552 | |
553 | |
554 | void RemoteSyslogListener::registerChannel() |
555 | { |
556 | Poco::LoggingFactory::defaultFactory().registerChannelClass("RemoteSyslogListener" , new Poco::Instantiator<RemoteSyslogListener, Poco::Channel>); |
557 | } |
558 | |
559 | |
560 | } } // namespace Poco::Net |
561 | |