My Project
syslog.cpp
1 /*
2  * uuid-syslog - Microcontroller syslog service
3  * Copyright 2019,2021-2022 Simon Arlott
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "uuid/syslog.h"
20 
21 #include <Arduino.h>
22 #ifdef ARDUINO_ARCH_ESP8266
23 # include <ESP8266WiFi.h>
24 #else
25 # include <WiFi.h>
26 #endif
27 #include <WiFiUdp.h>
28 
29 #if defined __has_include
30 # if __has_include (<lwip/init.h>)
31 # include <lwip/init.h>
32 # endif
33 #endif
34 
35 #ifndef UUID_SYSLOG_HAVE_GETTIMEOFDAY
36 # if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
37 // time() does not return UTC on the ESP8266: https://github.com/esp8266/Arduino/issues/4637
38 # define UUID_SYSLOG_HAVE_GETTIMEOFDAY 1
39 # endif
40 #endif
41 
42 #ifndef UUID_SYSLOG_HAVE_GETTIMEOFDAY
43 # define UUID_SYSLOG_HAVE_GETTIMEOFDAY 0
44 #endif
45 
46 #ifndef UUID_SYSLOG_HAVE_IPADDRESS_TYPE
47 # if defined(ARDUINO_ARCH_ESP8266)
48 # define UUID_SYSLOG_HAVE_IPADDRESS_TYPE 1
49 # endif
50 #endif
51 
52 #ifndef UUID_SYSLOG_HAVE_IPADDRESS_TYPE
53 # define UUID_SYSLOG_HAVE_IPADDRESS_TYPE 0
54 #endif
55 
56 #ifndef UUID_SYSLOG_ARP_CHECK
57 # if defined(LWIP_VERSION_MAJOR) && defined(LWIP_IPV4) && LWIP_VERSION_MAJOR >= 2 && LWIP_IPV4
58 # define UUID_SYSLOG_ARP_CHECK 1
59 # endif
60 #endif
61 
62 #ifndef UUID_SYSLOG_ARP_CHECK
63 # define UUID_SYSLOG_ARP_CHECK 0
64 #endif
65 
66 #ifndef UUID_SYSLOG_NDP_CHECK
67 # if defined(LWIP_VERSION_MAJOR) && defined(LWIP_IPV6) && LWIP_VERSION_MAJOR >= 2 && LWIP_IPV6
68 # define UUID_SYSLOG_NDP_CHECK 1
69 # endif
70 #endif
71 
72 #ifndef UUID_SYSLOG_NDP_CHECK
73 # define UUID_SYSLOG_NDP_CHECK 0
74 #endif
75 
76 #if UUID_SYSLOG_ARP_CHECK or UUID_SYSLOG_NDP_CHECK
77 # include <lwip/netif.h>
78 #endif
79 #if UUID_SYSLOG_ARP_CHECK
80 # include <lwip/ip4_addr.h>
81 # include <lwip/etharp.h>
82 #endif
83 #if UUID_SYSLOG_NDP_CHECK
84 # include <lwip/ip6_addr.h>
85 # include <lwip/nd6.h>
86 #endif
87 
88 #include <algorithm>
89 #include <list>
90 #include <memory>
91 #if UUID_SYSLOG_THREAD_SAFE
92 # include <mutex>
93 #endif
94 #include <string>
95 
96 #include <uuid/common.h>
97 #include <uuid/log.h>
98 
99 #ifndef UUID_SYSLOG_UDP_BASE_MESSAGE_DELAY
100 # define UUID_SYSLOG_UDP_BASE_MESSAGE_DELAY 100
101 #endif
102 
103 #ifndef UUID_SYSLOG_UDP_IPV4_ARP_MESSAGE_DELAY
104 # define UUID_SYSLOG_UDP_IPV4_ARP_MESSAGE_DELAY 10
105 #endif
106 
107 #ifndef UUID_SYSLOG_UDP_IPV6_NDP_MESSAGE_DELAY
108 # define UUID_SYSLOG_UDP_IPV6_NDP_MESSAGE_DELAY 10
109 #endif
110 
111 #ifndef PSTR_ALIGN
112 # define PSTR_ALIGN 4
113 #endif
114 
115 static const char __pstr__logger_name[] __attribute__((__aligned__(PSTR_ALIGN))) PROGMEM = "syslog";
116 
117 namespace uuid {
118 
119 namespace syslog {
120 
121 uuid::log::Logger SyslogService::logger_{FPSTR(__pstr__logger_name), uuid::log::Facility::SYSLOG};
123 
125  uuid::log::Logger::register_handler(this, uuid::log::Level::ALL);
126 }
127 
130 }
131 
133 #if UUID_SYSLOG_THREAD_SAFE
134  std::lock_guard<std::mutex> lock{mutex_};
135 #endif
136  unsigned long offset = 0;
137 
138  for (auto it = log_messages_.begin(); it != log_messages_.end(); ) {
139  if (it->content_->level > level) {
140  offset++;
141  it = log_messages_.erase(it);
142  } else {
143  it->id_ -= offset;
144  it++;
145  }
146  }
147 
148  log_message_id_ -= offset;
149 }
150 
152  if (!started_) {
153  remove_queued_messages(level);
154  }
155 
156  bool level_changed = !level_set_ || (level != log_level());
157  level_set_ = true;
158 
159  if (level_changed && level < uuid::log::Level::NOTICE) {
160  logger_.info(F("Log level set to %S"), uuid::log::format_level_uppercase(level));
161  }
163  if (level_changed && level >= uuid::log::Level::NOTICE) {
164  logger_.info(F("Log level set to %S"), uuid::log::format_level_uppercase(level));
165  }
166 }
167 
169 #if UUID_SYSLOG_THREAD_SAFE
170  std::lock_guard<std::mutex> lock{mutex_};
171 #endif
172 
173  return maximum_log_messages_;
174 }
175 
177 #if UUID_SYSLOG_THREAD_SAFE
178  std::lock_guard<std::mutex> lock{mutex_};
179 #endif
180 
181  maximum_log_messages_ = std::max((size_t)1, count);
182 
183  while (log_messages_.size() > maximum_log_messages_) {
184  log_messages_.pop_front();
185  }
186 }
187 
189 #if UUID_SYSLOG_THREAD_SAFE
190  std::lock_guard<std::mutex> lock{mutex_};
191 #endif
192 
193  return log_messages_.size();
194 }
195 
196 std::pair<IPAddress,uint16_t> SyslogService::destination() const {
197  return std::make_pair(host_, port_);
198 }
199 
200 void SyslogService::destination(IPAddress host, uint16_t port) {
201  host_ = host;
202  port_ = port;
203 
204  if ((uint32_t)host_ == (uint32_t)0) {
205  started_ = false;
207  }
208 }
209 
210 std::string SyslogService::hostname() const {
211  return hostname_;
212 }
213 
214 void SyslogService::hostname(std::string hostname) {
215  if (hostname.empty() || hostname.find(' ') != std::string::npos) {
216  hostname_ = '-';
217  } else {
218  hostname_ = std::move(hostname);
219  }
220 }
221 
222 unsigned long SyslogService::mark_interval() const {
223  return mark_interval_ / 1000;
224 }
225 
226 void SyslogService::mark_interval(unsigned long interval) {
227  mark_interval_ = (uint64_t)interval * 1000;
228 }
229 
230 SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr<uuid::log::Message> &&content)
231  : id_(id), content_(std::move(content)) {
232  if (time_good_ || WiFi.status() == WL_CONNECTED) {
233 #if UUID_SYSLOG_HAVE_GETTIMEOFDAY
234  if (gettimeofday(&time_, nullptr) != 0) {
235  time_.tv_sec = (time_t)-1;
236  }
237 #else
238  time_.tv_sec = time(nullptr);
239  time_.tv_usec = 0;
240 #endif
241 
242  if (time_.tv_sec >= 0 && time_.tv_sec < 18140 * 86400) {
243  time_.tv_sec = (time_t)-1;
244  }
245 
246  if (time_.tv_sec != (time_t)-1) {
247  time_good_ = true;
248  }
249  } else {
250  time_.tv_sec = (time_t)-1;
251  }
252 }
253 
254 /* Mutex already locked by caller. */
255 void SyslogService::add_message(std::shared_ptr<uuid::log::Message> &message) {
256  if (log_messages_.size() >= maximum_log_messages_) {
257  log_messages_.pop_front();
258  }
259 
260  log_messages_.emplace_back(log_message_id_++, std::move(message));
261 }
262 
263 void SyslogService::operator<<(std::shared_ptr<uuid::log::Message> message) {
264 #if UUID_SYSLOG_THREAD_SAFE
265  std::lock_guard<std::mutex> lock{mutex_};
266 #endif
267 
268  add_message(message);
269 }
270 
272 #if UUID_SYSLOG_THREAD_SAFE
273  std::unique_lock<std::mutex> lock{mutex_};
274 #endif
275  size_t count = std::max((size_t)1, MAX_LOG_MESSAGES);
276 
277  while (!log_messages_.empty()) {
278 #if UUID_SYSLOG_THREAD_SAFE
279  lock.unlock();
280 #endif
281 
282  if (!can_transmit())
283  return;
284 
285 #if UUID_SYSLOG_THREAD_SAFE
286  lock.lock();
287 
288  if (log_messages_.empty())
289  break;
290 #endif
291 
292  auto message = log_messages_.front();
293 
294  started_ = true;
295 
296 #if UUID_SYSLOG_THREAD_SAFE
297  lock.unlock();
298 #endif
299 
300  auto ok = transmit(message);
301  if (ok) {
302 #if UUID_SYSLOG_THREAD_SAFE
303  lock.lock();
304 #endif
305 
307  if (!log_messages_.empty()
308  && log_messages_.front().content_ == message.content_) {
309  log_messages_.pop_front();
310  }
311 
312 #if UUID_SYSLOG_THREAD_SAFE
313  lock.unlock();
314 #endif
315  }
316 
317  ::yield();
318 
319 #if UUID_SYSLOG_THREAD_SAFE
320  lock.lock();
321 #endif
322 
323  if (!ok) {
324  break;
325  }
326 
327  count--;
328  if (count == 0) {
329  break;
330  }
331  }
332 
333  if (started_ && mark_interval_ != 0 && log_messages_.empty()) {
335  // This is generated manually because the log level may not
336  // be high enough to receive INFO messages.
337  auto message = std::make_shared<uuid::log::Message>(uuid::get_uptime_ms(),
338  uuid::log::Level::INFO,
339  uuid::log::Facility::SYSLOG,
340  reinterpret_cast<const __FlashStringHelper *>(__pstr__logger_name),
341  uuid::read_flash_string(F("-- MARK --")));
342  add_message(message);
343  }
344  }
345 }
346 
348 #if UUID_SYSLOG_HAVE_IPADDRESS_TYPE
349  if (host_.isV4() && (uint32_t)host_ == (uint32_t)0) {
350  return false;
351  }
352 #else
353  if ((uint32_t)host_ == (uint32_t)0) {
354  return false;
355  }
356 #endif
357 
358  if (WiFi.status() != WL_CONNECTED) {
359  return false;
360  }
361 
362  const uint64_t now = uuid::get_uptime_ms();
363  uint64_t message_delay = UUID_SYSLOG_UDP_BASE_MESSAGE_DELAY;
364 
365 #if UUID_SYSLOG_ARP_CHECK
366 # if UUID_SYSLOG_HAVE_IPADDRESS_TYPE
367  if (host_.isV4())
368 # endif
369  {
370  message_delay = UUID_SYSLOG_UDP_IPV4_ARP_MESSAGE_DELAY;
371  }
372 #endif
373 #if UUID_SYSLOG_NDP_CHECK && UUID_SYSLOG_HAVE_IPADDRESS_TYPE
374  if (host_.isV6()) {
375  message_delay = UUID_SYSLOG_UDP_IPV6_NDP_MESSAGE_DELAY;
376  }
377 #endif
378 
379  if (started_ && (now < last_transmit_ || now - last_transmit_ < message_delay)) {
380  return false;
381  }
382 
383 #if UUID_SYSLOG_ARP_CHECK
384 # if UUID_SYSLOG_HAVE_IPADDRESS_TYPE
385  if (host_.isV4())
386 # endif
387  {
388  ip4_addr_t ipaddr;
389 
390  ip4_addr_set_u32(&ipaddr, (uint32_t)host_);
391 
392  if (!ip4_addr_isloopback(&ipaddr)
393  && !ip4_addr_ismulticast(&ipaddr)
394  && !ip4_addr_isbroadcast(&ipaddr, netif_default)) {
395  struct eth_addr *eth_ret = nullptr;
396  const ip4_addr_t *ip_ret = nullptr;
397 
398  if (!ip4_addr_netcmp(&ipaddr, netif_ip4_addr(netif_default), netif_ip4_netmask(netif_default))) {
399  // Replace addresses outside the network with the gateway address
400  const ip4_addr_t *gw_addr = netif_ip4_gw(netif_default);
401 
402  if (gw_addr != nullptr) {
403  ipaddr = *gw_addr;
404  }
405  }
406 
407  if (etharp_find_addr(netif_default, &ipaddr, &eth_ret, &ip_ret) == -1) {
408  etharp_query(netif_default, &ipaddr, NULL);
409  // Avoid querying lwIP again for 1 second
410  last_transmit_ = uuid::get_uptime_ms() + (uint64_t)1000 - message_delay;
411 
412  return false;
413  }
414  }
415  }
416 #endif
417 
418 #if UUID_SYSLOG_NDP_CHECK && UUID_SYSLOG_HAVE_IPADDRESS_TYPE
419  if (host_.isV6()) {
420  ip6_addr_t ip6addr;
421 
422  IP6_ADDR(&ip6addr, host_.raw6()[0], host_.raw6()[1], host_.raw6()[2], host_.raw6()[3]);
423  ip6_addr_assign_zone(&ip6addr, IP6_UNICAST, netif_default);
424 
425  if (!ip6_addr_isloopback(&ip6addr)
426  && !ip6_addr_ismulticast(&ip6addr)) {
427  // Don't send to a scoped address until we have a valid address of the same type
428  bool have_address = false;
429  const u8_t *hwaddr = nullptr;
430 
431  for (size_t i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
432  if (ip6_addr_isvalid(netif_ip6_addr_state(netif_default, i))) {
433  if (ip6_addr_islinklocal(&ip6addr)) {
434  if (ip6_addr_islinklocal(netif_ip6_addr(netif_default, i))) {
435  have_address = true;
436  break;
437  }
438  } else if (ip6_addr_isglobal(&ip6addr)
439  || ip6_addr_isuniquelocal(&ip6addr)
440  || ip6_addr_issitelocal(&ip6addr)) {
441  if (ip6_addr_isglobal(netif_ip6_addr(netif_default, i))
442  || ip6_addr_isuniquelocal(netif_ip6_addr(netif_default, i))
443  || ip6_addr_issitelocal(netif_ip6_addr(netif_default, i))) {
444  have_address = true;
445  break;
446  }
447  } else {
448  have_address = true;
449  break;
450  }
451  }
452  }
453 
454  if (!have_address) {
455  // Avoid checking lwIP again for 1 second
456  last_transmit_ = uuid::get_uptime_ms() + (uint64_t)1000 - message_delay;
457 
458  return false;
459  } else if (nd6_get_next_hop_addr_or_queue(netif_default, NULL, &ip6addr, &hwaddr) != ERR_OK) {
460  // Avoid querying lwIP again for 1 second
461  last_transmit_ = uuid::get_uptime_ms() + (uint64_t)1000 - message_delay;
462 
463  return false;
464  }
465  }
466  }
467 #endif
468 
469  return true;
470 }
471 
473  struct tm tm;
474 
475  tm.tm_year = 0;
476  if (message.time_.tv_sec != (time_t)-1) {
477  gmtime_r(&message.time_.tv_sec, &tm);
478  }
479 
480  if (udp_.beginPacket(host_, port_) != 1) {
482  return false;
483  }
484  /*
485  * The level is constrained to 0-7 by design in RFC 5424 because higher
486  * severity values would be a different severity in another facility. The
487  * TRACE level and all other invalid values (because of the conversion to
488  * unsigned) are converted to DEBUG.
489  *
490  * RFC 5424 requires that the facility MUST be 0-23. Values here are allowed
491  * to go up to 31 because there is no obvious candidate to convert them to
492  * and because the multiplication by a factor of 256 means that (with the
493  * cast back to uint8_t) higher values just overflow into other facilities
494  * (which is easier than doing it by modulo 24). The enum doesn't allow
495  * values that are out of range of the RFC.
496  *
497  * The maximum possible priority value does not exceed the requirement that
498  * the PRI part MUST be 3-5 characters.
499  */
500  udp_.printf_P(PSTR("<%u>1 "), (uint8_t)(message.content_->facility * 8U)
501  + std::min(7U, (unsigned int)message.content_->level));
502  if (tm.tm_year != 0) {
503  udp_.printf_P(PSTR("%04u-%02u-%02uT%02u:%02u:%02u.%06luZ"),
504  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
505  tm.tm_hour, tm.tm_min, tm.tm_sec, (unsigned long)message.time_.tv_usec);
506  } else {
507  udp_.print('-');
508  }
509  udp_.printf_P(PSTR(" %s - - - - \xEF\xBB\xBF"), hostname_.c_str());
510  udp_.print(uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3).c_str());
511 #pragma GCC diagnostic push
512 #pragma GCC diagnostic ignored "-Wformat"
513  udp_.printf_P(PSTR(" %c %lu: [%S] "), uuid::log::format_level_char(message.content_->level), message.id_, message.content_->name);
514 #pragma GCC diagnostic pop
515  udp_.print(message.content_->text.c_str());
516  bool ok = (udp_.endPacket() == 1);
517 
519  return ok;
520 }
521 
522 } // namespace syslog
523 
524 } // namespace uuid
uuid::syslog::SyslogService::QueuedLogMessage::time_
struct timeval time_
Definition: syslog.h:244
uuid::syslog::SyslogService::current_log_messages
size_t current_log_messages() const
Get the current number of queued log messages.
Definition: syslog.cpp:188
uuid::log::Logger
Logger instance used to make log messages.
Definition: log.h:347
uuid::syslog::SyslogService::level_set_
bool level_set_
Definition: syslog.h:294
uuid::syslog::SyslogService::remove_queued_messages
void remove_queued_messages(uuid::log::Level level)
Remove messages that were queued before the log level was set.
Definition: syslog.cpp:132
uuid::get_uptime_ms
uint64_t get_uptime_ms()
Get the current uptime as a 64-bit milliseconds value.
Definition: get_uptime_ms.cpp:30
uuid::syslog::SyslogService::log_message_id_
unsigned long log_message_id_
Definition: syslog.h:304
uuid::log::format_level_uppercase
const __FlashStringHelper * format_level_uppercase(Level level)
Format a log level as an uppercase string.
Definition: format_level_uppercase.cpp:61
uuid::log::Logger::info
void info(const char *format,...) const
Log a message at level Level::INFO.
Definition: log.cpp:230
uuid::syslog::SyslogService::QueuedLogMessage
Log message that has been queued.
Definition: syslog.h:231
uuid::syslog::SyslogService::MAX_LOG_MESSAGES
static constexpr size_t MAX_LOG_MESSAGES
Definition: syslog.h:83
uuid::syslog::SyslogService::port_
uint16_t port_
Definition: syslog.h:297
uuid::log::Level
Level
Severity level of log messages.
Definition: log.h:84
uuid::syslog::SyslogService::hostname
std::string hostname() const
Get local hostname.
Definition: syslog.cpp:210
uuid::syslog::SyslogService::host_
IPAddress host_
Definition: syslog.h:296
uuid::log::Logger::register_handler
static void register_handler(Handler *handler, Level level)
Register a log handler.
Definition: log.cpp:71
uuid::syslog::SyslogService::log_messages_
std::list< QueuedLogMessage > log_messages_
Definition: syslog.h:305
uuid::log::Logger::get_log_level
static Level get_log_level(const Handler *handler)
Get the current log level of a handler.
Definition: log.cpp:96
uuid::syslog::SyslogService::last_message_
uint64_t last_message_
Definition: syslog.h:307
uuid::syslog::SyslogService::destination
std::pair< IPAddress, uint16_t > destination() const
Get the server to send messages to.
Definition: syslog.cpp:196
uuid::syslog::SyslogService::start
void start()
Register the log handler with the logging framework.
Definition: syslog.cpp:124
uuid::syslog::SyslogService::last_transmit_
uint64_t last_transmit_
Definition: syslog.h:298
uuid::syslog::SyslogService::add_message
void add_message(std::shared_ptr< uuid::log::Message > &message)
Add a new log message.
Definition: syslog.cpp:255
uuid::syslog::SyslogService::QueuedLogMessage::QueuedLogMessage
QueuedLogMessage(unsigned long id, std::shared_ptr< uuid::log::Message > &&content)
Create a queued log message.
Definition: syslog.cpp:230
uuid::syslog::SyslogService::QueuedLogMessage::id_
unsigned long id_
Definition: syslog.h:243
uuid::syslog::SyslogService::udp_
WiFiUDP udp_
Definition: syslog.h:295
uuid::read_flash_string
std::string read_flash_string(const __FlashStringHelper *flash_str)
Read a string from flash and convert it to a std::string.
Definition: read_flash_string.cpp:27
uuid::syslog::SyslogService::logger_
static uuid::log::Logger logger_
Definition: syslog.h:291
uuid
Common utilities.
Definition: get_uptime_ms.cpp:28
uuid::syslog::SyslogService::hostname_
std::string hostname_
Definition: syslog.h:299
uuid::syslog::SyslogService::QueuedLogMessage::content_
std::shared_ptr< const uuid::log::Message > content_
Definition: syslog.h:245
uuid::syslog::SyslogService::transmit
bool transmit(const QueuedLogMessage &message)
Attempt to transmit one message to the server.
Definition: syslog.cpp:472
uuid::log::format_timestamp_ms
std::string format_timestamp_ms(uint64_t timestamp_ms, unsigned int days_width)
Format a system uptime timestamp as a string.
Definition: format_timestamp_ms.cpp:31
uuid::syslog::SyslogService::operator<<
void operator<<(std::shared_ptr< uuid::log::Message > message) override
Add a new log message.
Definition: syslog.cpp:263
uuid::syslog::SyslogService::mark_interval
unsigned long mark_interval() const
Get mark interval.
Definition: syslog.cpp:222
uuid::syslog::SyslogService::log_level
uuid::log::Level log_level() const
Get the current log level.
Definition: syslog.cpp:128
uuid::syslog::SyslogService::maximum_log_messages_
size_t maximum_log_messages_
Definition: syslog.h:303
uuid::log::format_level_char
char format_level_char(Level level)
Format a log level as a single character.
Definition: format_level_char.cpp:25
uuid::syslog::SyslogService::maximum_log_messages
size_t maximum_log_messages() const
Get the maximum number of queued log messages.
Definition: syslog.cpp:168
uuid::syslog::SyslogService::started_
bool started_
Definition: syslog.h:293
uuid::syslog::SyslogService::can_transmit
bool can_transmit()
Check if it is possible to transmit to the server.
Definition: syslog.cpp:347
uuid::syslog::SyslogService::QueuedLogMessage::time_good_
static bool time_good_
Definition: syslog.h:248
uuid::syslog::SyslogService::mutex_
std::mutex mutex_
Definition: syslog.h:301
uuid::syslog::SyslogService::loop
void loop()
Dispatch queued log messages.
Definition: syslog.cpp:271
uuid::syslog::SyslogService::mark_interval_
uint64_t mark_interval_
Definition: syslog.h:306