syslog.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. /*
  2. * syslog.c
  3. *
  4. *
  5. * Copyright 2015 Susi's Strolch
  6. *
  7. * For license information see projects "License.txt"
  8. *
  9. */
  10. #include <esp8266.h>
  11. #include "config.h"
  12. #include "syslog.h"
  13. #include "time.h"
  14. #include "task.h"
  15. #include "sntp.h"
  16. extern void * mem_trim(void *m, size_t s); // not well documented...
  17. #ifdef SYSLOG_DBG
  18. #define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0)
  19. #else
  20. #define DBG(format, ...) do { } while(0)
  21. #endif
  22. #define WIFI_CHK_INTERVAL 1000 // ms to check Wifi statis
  23. static struct espconn *syslog_espconn = NULL;
  24. static uint32_t syslog_msgid = 1;
  25. static uint8_t syslog_task = 0;
  26. static syslog_host_t syslogHost;
  27. static syslog_entry_t *syslogQueue = NULL;
  28. static enum syslog_state syslogState = SYSLOG_NONE;
  29. static bool syslog_timer_armed = false;
  30. static void ICACHE_FLASH_ATTR syslog_add_entry(syslog_entry_t *entry);
  31. static void ICACHE_FLASH_ATTR syslog_chk_status(void);
  32. static void ICACHE_FLASH_ATTR syslog_udp_sent_cb(void *arg);
  33. static syslog_entry_t ICACHE_FLASH_ATTR *syslog_compose(uint8_t facility, uint8_t severity, const char *tag, const char *fmt, ...);
  34. #ifdef SYSLOG_UDP_RECV
  35. static void ICACHE_FLASH_ATTR syslog_udp_recv_cb(void *arg, char *pusrdata, unsigned short length);
  36. #endif
  37. #define syslog_send_udp() post_usr_task(syslog_task,0)
  38. static char ICACHE_FLASH_ATTR *syslog_get_status(void) {
  39. switch (syslogState)
  40. {
  41. case SYSLOG_NONE:
  42. return "SYSLOG_NONE";
  43. case SYSLOG_WAIT:
  44. return "SYSLOG_WAIT";
  45. case SYSLOG_INIT:
  46. return "SYSLOG_INIT";
  47. case SYSLOG_INITDONE:
  48. return "SYSLOG_INITDONE";
  49. case SYSLOG_DNSWAIT:
  50. return "SYSLOG_DNSWAIT";
  51. case SYSLOG_READY:
  52. return "SYSLOG_READY";
  53. case SYSLOG_SENDING:
  54. return "SYSLOG_SENDING";
  55. case SYSLOG_SEND:
  56. return "SYSLOG_SEND";
  57. case SYSLOG_SENT:
  58. return "SYSLOG_SENT";
  59. case SYSLOG_HALTED:
  60. return "SYSLOG_HALTED";
  61. case SYSLOG_ERROR:
  62. return "SYSLOG_ERROR";
  63. default:
  64. break;
  65. }
  66. return "UNKNOWN ";
  67. }
  68. static void ICACHE_FLASH_ATTR syslog_set_status(enum syslog_state state) {
  69. syslogState = state;
  70. DBG("[%dµs] %s: %s (%d)\n", WDEV_NOW(), __FUNCTION__, syslog_get_status(), state);
  71. #ifndef SYSLOG_DBG
  72. os_printf("Syslog state: %s\n", syslog_get_status());
  73. #endif
  74. }
  75. static void ICACHE_FLASH_ATTR syslog_timer_arm(int delay) {
  76. static os_timer_t wifi_chk_timer = {};
  77. syslog_timer_armed = true;
  78. os_timer_disarm(&wifi_chk_timer);
  79. os_timer_setfn(&wifi_chk_timer, (os_timer_func_t *)syslog_chk_status, NULL);
  80. os_timer_arm(&wifi_chk_timer, delay, 0);
  81. }
  82. /******************************************************************************
  83. * FunctionName : syslog_chk_status
  84. * Description : check whether get ip addr or not
  85. * Parameters : none
  86. * Returns : none
  87. *******************************************************************************/
  88. static void ICACHE_FLASH_ATTR syslog_chk_status(void)
  89. {
  90. struct ip_info ipconfig;
  91. DBG("[%uµs] %s: id=%lu ", WDEV_NOW(), __FUNCTION__, syslogQueue ? syslogQueue->msgid : 0);
  92. //disarm timer first
  93. syslog_timer_armed = false;
  94. //try to get ip info of ESP8266 station
  95. wifi_get_ip_info(STATION_IF, &ipconfig);
  96. int wifi_status = wifi_station_get_connect_status();
  97. if (wifi_status == STATION_GOT_IP && ipconfig.ip.addr != 0)
  98. {
  99. // it seems we have to add an additional delay after the Wifi is up and running.
  100. // so we simply add an intermediate state with 25ms delay
  101. switch (syslogState)
  102. {
  103. case SYSLOG_WAIT:
  104. DBG("%s: Wifi connected\n", syslog_get_status());
  105. syslog_set_status(SYSLOG_INIT);
  106. syslog_timer_arm(100);
  107. break;
  108. case SYSLOG_INIT:
  109. DBG("%s: init syslog\n", syslog_get_status());
  110. syslog_set_status(SYSLOG_INITDONE);
  111. syslog_init(flashConfig.syslog_host);
  112. syslog_timer_arm(10);
  113. break;
  114. case SYSLOG_DNSWAIT:
  115. DBG("%s: wait for DNS resolver\n", syslog_get_status());
  116. syslog_timer_arm(10);
  117. break;
  118. case SYSLOG_READY:
  119. DBG("%s: enforce sending\n", syslog_get_status());
  120. syslog_send_udp();
  121. break;
  122. case SYSLOG_SENDING:
  123. DBG("%s: delay\n", syslog_get_status());
  124. syslog_set_status(SYSLOG_SEND);
  125. syslog_timer_arm(2);
  126. break;
  127. case SYSLOG_SEND:
  128. DBG("%s: start sending\n", syslog_get_status());
  129. syslog_send_udp();
  130. break;
  131. default:
  132. DBG("%s: %d\n", syslog_get_status(), syslogState);
  133. break;
  134. }
  135. } else {
  136. if ((wifi_status == STATION_WRONG_PASSWORD ||
  137. wifi_status == STATION_NO_AP_FOUND ||
  138. wifi_status == STATION_CONNECT_FAIL)) {
  139. syslog_set_status(SYSLOG_ERROR);
  140. os_printf("*** connect failure!!!\n");
  141. } else {
  142. DBG("re-arming timer...\n");
  143. syslog_timer_arm(WIFI_CHK_INTERVAL);
  144. }
  145. }
  146. }
  147. /******************************************************************************
  148. * FunctionName : syslog_sent_cb
  149. * Description : udp sent successfully
  150. * fetch next syslog package, free old message
  151. * Parameters : arg -- Additional argument to pass to the callback function
  152. * Returns : none
  153. ******************************************************************************/
  154. static void ICACHE_FLASH_ATTR syslog_udp_sent_cb(void *arg)
  155. {
  156. struct espconn *pespconn = arg;
  157. (void) pespconn;
  158. DBG("[%uµs] %s: id=%lu\n", WDEV_NOW(), __FUNCTION__, syslogQueue ? syslogQueue->msgid : 0);
  159. // datagram is delivered - free and advance queue
  160. syslog_entry_t *pse = syslogQueue;
  161. syslogQueue = syslogQueue -> next;
  162. os_free(pse);
  163. if (syslogQueue == NULL)
  164. syslog_set_status(SYSLOG_READY);
  165. else {
  166. // UDP seems timecritical - we must ensure a minimum delay after each package...
  167. syslog_set_status(SYSLOG_SENDING);
  168. if (! syslog_timer_armed)
  169. syslog_chk_status();
  170. }
  171. }
  172. static void ICACHE_FLASH_ATTR
  173. syslog_udp_send_event(os_event_t *events) {
  174. // os_printf("syslog_udp_send_event: %d %lu, %lu\n", syslogState, syslogQueue->msgid, syslogQueue->tick);
  175. DBG("[%uµs] %s: id=%lu\n", WDEV_NOW(), __FUNCTION__, syslogQueue ? syslogQueue->msgid : 0);
  176. if (syslogQueue == NULL)
  177. syslog_set_status(SYSLOG_READY);
  178. else {
  179. int res = 0;
  180. syslog_espconn->proto.udp->remote_port = syslogHost.port; // ESP8266 udp remote port
  181. os_memcpy(syslog_espconn->proto.udp->remote_ip, &syslogHost.addr.addr, 4); // ESP8266 udp remote IP
  182. res = espconn_send(syslog_espconn, (uint8_t *)syslogQueue->datagram, syslogQueue->datagram_len);
  183. if (res != 0) {
  184. os_printf("syslog_udp_send: error %d\n", res);
  185. }
  186. }
  187. }
  188. /*****************************************************************************
  189. * FunctionName : syslog_recv_cb
  190. * Description : Processing the received udp packet
  191. * Parameters : arg -- Additional argument to pass to the callback function
  192. * pusrdata -- The received data (or NULL when the connection has been closed!)
  193. * length -- The length of received data
  194. * Returns : none
  195. ******************************************************************************/
  196. #ifdef SYSLOG_UDP_RECV
  197. static void ICACHE_FLASH_ATTR syslog_udp_recv_cb(void *arg, char *pusrdata, unsigned short length)
  198. {
  199. DBG("syslog_udp_recv_cb: %p, %p, %d\n", arg, pusrdata, length);
  200. }
  201. #endif
  202. /******************************************************************************
  203. *
  204. ******************************************************************************/
  205. static void ICACHE_FLASH_ATTR syslog_gethostbyname_cb(const char *name, ip_addr_t *ipaddr, void *arg)
  206. {
  207. struct espconn *pespconn = (struct espconn *)arg;
  208. (void) pespconn;
  209. DBG("[%uµs] %s\n", WDEV_NOW(), __FUNCTION__);
  210. if (ipaddr != NULL) {
  211. syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_NOTICE, "SYSLOG",
  212. "resolved hostname: %s: " IPSTR, name, IP2STR(ipaddr));
  213. syslogHost.addr.addr = ipaddr->addr;
  214. syslog_set_status(SYSLOG_READY);
  215. } else {
  216. syslog_set_status(SYSLOG_ERROR);
  217. DBG("syslog_gethostbyname_cb: status=%s\n", syslog_get_status());
  218. }
  219. DBG("[%uµs] ex syslog_gethostbyname_cb()\n", WDEV_NOW());
  220. }
  221. /******************************************************************************
  222. * FunctionName : initSyslog
  223. * Description : Initialize the syslog library
  224. * Parameters : syslog_host -- the syslog host (host:port)
  225. * host: IP-Addr | hostname
  226. * Returns : none
  227. *******************************************************************************/
  228. void ICACHE_FLASH_ATTR syslog_init(char *syslog_host)
  229. {
  230. DBG("[%uµs] %s\n", WDEV_NOW(), __FUNCTION__);
  231. os_printf("SYSLOG host=%s *host=0x%x\n", syslog_host, *syslog_host);
  232. if (!*syslog_host) {
  233. syslog_set_status(SYSLOG_HALTED);
  234. return;
  235. }
  236. if (syslog_host == NULL) {
  237. // disable and unregister syslog handler
  238. syslog_set_status(SYSLOG_HALTED);
  239. if (syslog_espconn != NULL) {
  240. if (syslog_espconn->proto.udp) {
  241. // there's no counterpart to espconn_create...
  242. os_free(syslog_espconn->proto.udp);
  243. }
  244. os_free(syslog_espconn);
  245. }
  246. syslog_espconn = NULL;
  247. // clean up syslog queue
  248. syslog_entry_t *pse = syslogQueue;
  249. while (pse != NULL) {
  250. syslog_entry_t *next = pse->next;
  251. os_free(pse);
  252. pse = next;
  253. }
  254. syslogQueue = NULL;
  255. return;
  256. }
  257. char host[32], *port = &host[0];
  258. os_strncpy(host, syslog_host, 32);
  259. while (*port && *port != ':') // find port delimiter
  260. port++;
  261. if (*port) {
  262. *port++ = '\0';
  263. syslogHost.port = atoi(port);
  264. }
  265. if (syslogHost.port == 0)
  266. syslogHost.port = 514;
  267. // allocate structures, init syslog_handler
  268. if (syslog_espconn == NULL)
  269. syslog_espconn = (espconn *)os_zalloc(sizeof(espconn));
  270. if (syslog_espconn->proto.udp == NULL)
  271. syslog_espconn->proto.udp = (esp_udp *)os_zalloc(sizeof(esp_udp));
  272. syslog_espconn->type = ESPCONN_UDP;
  273. syslog_espconn->proto.udp->local_port = espconn_port(); // set a available port
  274. #ifdef SYSLOG_UDP_RECV
  275. espconn_regist_recvcb(syslog_espconn, syslog_udp_recv_cb); // register a udp packet receiving callback
  276. #endif
  277. espconn_regist_sentcb(syslog_espconn, syslog_udp_sent_cb); // register a udp packet sent callback
  278. syslog_task = register_usr_task(syslog_udp_send_event);
  279. syslogHost.min_heap_size = flashConfig.syslog_minheap;
  280. // the wifi_set_broadcast_if must be handled global in connection handler...
  281. // wifi_set_broadcast_if(STATIONAP_MODE); // send UDP broadcast from both station and soft-AP interface
  282. espconn_create(syslog_espconn); // create udp
  283. syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_NOTICE, "SYSLOG",
  284. "syslogserver: %s:%d %d", host, syslogHost.port, syslog_espconn->proto.udp->local_port);
  285. if (UTILS_StrToIP((const char *)host, (void*)&syslogHost.addr)) {
  286. syslog_set_status(SYSLOG_READY);
  287. } else {
  288. syslog_set_status(SYSLOG_DNSWAIT);
  289. syslog(SYSLOG_FAC_USER, SYSLOG_PRIO_NOTICE, "SYSLOG",
  290. "must resolve hostname \"%s\"", host);
  291. espconn_gethostbyname(syslog_espconn, host, &syslogHost.addr, syslog_gethostbyname_cb);
  292. }
  293. }
  294. /******************************************************************************
  295. * FunctionName : syslog_add_entry
  296. * Description : add a syslog_entry_t to the syslogQueue
  297. * Parameters : entry: the syslog_entry_t
  298. * Returns : none
  299. ******************************************************************************/
  300. static void ICACHE_FLASH_ATTR
  301. syslog_add_entry(syslog_entry_t *entry)
  302. {
  303. syslog_entry_t *pse = syslogQueue;
  304. DBG("[%dµs] %s id=%lu\n", WDEV_NOW(), __FUNCTION__, entry->msgid);
  305. // append msg to syslog_queue
  306. if (pse == NULL)
  307. syslogQueue = entry;
  308. else {
  309. while (pse->next != NULL)
  310. pse = pse->next;
  311. pse->next = entry; // append msg to syslog queue
  312. }
  313. // DBG("%p %lu %d\n", entry, entry->msgid, system_get_free_heap_size());
  314. // ensure we have sufficient heap for the rest of the system
  315. if (system_get_free_heap_size() < syslogHost.min_heap_size) {
  316. if (syslogState != SYSLOG_HALTED) {
  317. os_printf("syslog_add_entry: Warning: queue filled up, halted\n");
  318. entry->next = syslog_compose(SYSLOG_FAC_USER, SYSLOG_PRIO_CRIT, "SYSLOG", "queue filled up, halted");
  319. if (syslogState == SYSLOG_READY)
  320. syslog_send_udp();
  321. syslog_set_status(SYSLOG_HALTED);
  322. }
  323. }
  324. }
  325. /******************************************************************************
  326. * FunctionName : syslog_compose
  327. * Description : compose a syslog_entry_t from va_args
  328. * Parameters : va_args
  329. * Returns : the malloced syslog_entry_t
  330. ******************************************************************************/
  331. LOCAL syslog_entry_t ICACHE_FLASH_ATTR *
  332. syslog_compose(uint8_t facility, uint8_t severity, const char *tag, const char *fmt, ...)
  333. {
  334. DBG("[%dµs] %s id=%lu\n", WDEV_NOW(), __FUNCTION__, syslog_msgid);
  335. syslog_entry_t *se = os_zalloc(sizeof (syslog_entry_t) + 1024); // allow up to 1k datagram
  336. if (se == NULL) return NULL;
  337. char *p = se->datagram;
  338. se->tick = WDEV_NOW(); // 0 ... 4294.967295s
  339. se->msgid = syslog_msgid;
  340. // The Priority value is calculated by first multiplying the Facility
  341. // number by 8 and then adding the numerical value of the Severity.
  342. p += os_sprintf(p, "<%d> ", facility * 8 + severity);
  343. // strftime doesn't work as expected - or adds 8k overhead.
  344. // so let's do poor man conversion - format is fixed anyway
  345. if (flashConfig.syslog_showdate == 0)
  346. p += os_sprintf(p, "- ");
  347. else {
  348. time_t now = NULL;
  349. struct tm *tp = NULL;
  350. // create timestamp: FULL-DATE "T" PARTIAL-TIME "Z": 'YYYY-mm-ddTHH:MM:SSZ '
  351. // as long as realtime_stamp is 0 we use tick div 10⁶ as date
  352. uint32_t realtime_stamp = sntp_get_current_timestamp();
  353. now = (realtime_stamp == 0) ? (se->tick / 1000000) : realtime_stamp;
  354. tp = gmtime(&now);
  355. p += os_sprintf(p, "%4d-%02d-%02dT%02d:%02d:%02d",
  356. tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday,
  357. tp->tm_hour, tp->tm_min, tp->tm_sec);
  358. if (realtime_stamp == 0)
  359. p += os_sprintf(p, ".%06uZ ", se->tick % 1000000);
  360. else
  361. p += os_sprintf(p, "%+03d:00 ", flashConfig.timezone_offset);
  362. }
  363. // add HOSTNAME APP-NAME PROCID MSGID
  364. if (flashConfig.syslog_showtick)
  365. p += os_sprintf(p, "%s %s %u.%06u %u ", flashConfig.hostname, tag, se->tick / 1000000,
  366. se->tick % 1000000, syslog_msgid++);
  367. else
  368. p += os_sprintf(p, "%s %s - %u ", flashConfig.hostname, tag, syslog_msgid++);
  369. // append syslog message
  370. va_list arglist;
  371. va_start(arglist, fmt);
  372. p += ets_vsprintf(p, fmt, arglist );
  373. va_end(arglist);
  374. se->datagram_len = p - se->datagram;
  375. se = mem_trim(se, sizeof(syslog_entry_t) + se->datagram_len + 1);
  376. return se;
  377. }
  378. /*****************************************************************************
  379. * FunctionName : syslog
  380. * Description : compose and queue a new syslog message
  381. * Parameters : facility
  382. * severity
  383. * tag
  384. * message
  385. * ...
  386. *
  387. * SYSLOG-MSG = HEADER SP STRUCTURED-DATA [SP MSG]
  388. HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME
  389. SP APP-NAME SP PROCID SP MSGID
  390. PRI = "<" PRIVAL ">"
  391. PRIVAL = 1*3DIGIT ; range 0 .. 191
  392. VERSION = NONZERO-DIGIT 0*2DIGIT
  393. HOSTNAME = NILVALUE / 1*255PRINTUSASCII
  394. APP-NAME = NILVALUE / 1*48PRINTUSASCII
  395. PROCID = NILVALUE / 1*128PRINTUSASCII
  396. MSGID = NILVALUE / 1*32PRINTUSASCII
  397. TIMESTAMP = NILVALUE / FULL-DATE "T" FULL-TIME
  398. FULL-DATE = DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY
  399. DATE-FULLYEAR = 4DIGIT
  400. DATE-MONTH = 2DIGIT ; 01-12
  401. DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
  402. ; month/year
  403. FULL-TIME = PARTIAL-TIME TIME-OFFSET
  404. PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND
  405. [TIME-SECFRAC]
  406. TIME-HOUR = 2DIGIT ; 00-23
  407. TIME-MINUTE = 2DIGIT ; 00-59
  408. TIME-SECOND = 2DIGIT ; 00-59
  409. TIME-SECFRAC = "." 1*6DIGIT
  410. TIME-OFFSET = "Z" / TIME-NUMOFFSET
  411. TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE
  412. STRUCTURED-DATA = NILVALUE / 1*SD-ELEMENT
  413. SD-ELEMENT = "[" SD-ID *(SP SD-PARAM) "]"
  414. SD-PARAM = PARAM-NAME "=" %d34 PARAM-VALUE %d34
  415. SD-ID = SD-NAME
  416. PARAM-NAME = SD-NAME
  417. PARAM-VALUE = UTF-8-STRING ; characters '"', '\' and
  418. ; ']' MUST be escaped.
  419. SD-NAME = 1*32PRINTUSASCII
  420. ; except '=', SP, ']', %d34 (")
  421. MSG = MSG-ANY / MSG-UTF8
  422. MSG-ANY = *OCTET ; not starting with BOM
  423. MSG-UTF8 = BOM UTF-8-STRING
  424. BOM = %xEF.BB.BF
  425. UTF-8-STRING = *OCTET ; UTF-8 string as specified
  426. ; in RFC 3629
  427. OCTET = %d00-255
  428. SP = %d32
  429. PRINTUSASCII = %d33-126
  430. NONZERO-DIGIT = %d49-57
  431. DIGIT = %d48 / NONZERO-DIGIT
  432. NILVALUE = "-"
  433. *
  434. * TIMESTAMP: realtime_clock == 0 ? timertick / 10⁶ : realtime_clock
  435. * HOSTNAME hostname
  436. * APPNAME: ems-meta-id
  437. * PROCID: timertick
  438. * MSGID: NILVALUE
  439. *
  440. * Returns : none
  441. *******************************************************************************/
  442. void ICACHE_FLASH_ATTR syslog(uint8_t facility, uint8_t severity, const char *tag, const char *fmt, ...)
  443. {
  444. DBG("[%dµs] %s status: %s\n", WDEV_NOW(), __FUNCTION__, syslog_get_status());
  445. if (flashConfig.syslog_host[0] == 0 || syslogState == SYSLOG_ERROR || syslogState == SYSLOG_HALTED)
  446. return;
  447. if (severity > flashConfig.syslog_filter)
  448. return;
  449. // compose the syslog message
  450. void *arg = __builtin_apply_args();
  451. void *res = __builtin_apply((void*)syslog_compose, arg, 128);
  452. if (res == NULL) return; // compose failed, probably due to malloc failure
  453. syslog_entry_t *se = *(syslog_entry_t **)res;
  454. // and append it to the message queue
  455. syslog_add_entry(se);
  456. if (syslogState == SYSLOG_NONE)
  457. syslog_set_status(SYSLOG_WAIT);
  458. if (! syslog_timer_armed)
  459. syslog_chk_status();
  460. }