From 12a803bcc90fde7ca74a7a1d295d7929fe9b1ab3 Mon Sep 17 00:00:00 2001 From: armink Date: Tue, 30 Oct 2018 09:00:30 +0800 Subject: [PATCH] [component] Add ulog logger basic component. --- components/utilities/Kconfig | 180 +++ components/utilities/ulog/SConscript | 16 + .../utilities/ulog/backend/console_be.c | 56 + components/utilities/ulog/syslog/syslog.c | 260 ++++ components/utilities/ulog/syslog/syslog.h | 100 ++ components/utilities/ulog/ulog.c | 1152 +++++++++++++++++ components/utilities/ulog/ulog.h | 95 ++ components/utilities/ulog/ulog_def.h | 189 +++ examples/ulog/ulog_example.c | 93 ++ 9 files changed, 2141 insertions(+) create mode 100644 components/utilities/ulog/SConscript create mode 100644 components/utilities/ulog/backend/console_be.c create mode 100644 components/utilities/ulog/syslog/syslog.c create mode 100644 components/utilities/ulog/syslog/syslog.h create mode 100644 components/utilities/ulog/ulog.c create mode 100644 components/utilities/ulog/ulog.h create mode 100644 components/utilities/ulog/ulog_def.h create mode 100644 examples/ulog/ulog_example.c diff --git a/components/utilities/Kconfig b/components/utilities/Kconfig index 09ab2cfe8e..45e0791c9e 100644 --- a/components/utilities/Kconfig +++ b/components/utilities/Kconfig @@ -46,4 +46,184 @@ config RT_USING_RYM bool "Enable Ymodem" default n +config RT_USING_ULOG + bool "Enable ulog" + default n + + if RT_USING_ULOG + if !ULOG_USING_SYSLOG + choice + prompt "The static output log level." + default ULOG_OUTPUT_LVL_DEBUG + help + When the log level is less than this option and it will stop output. + These log will not compile into ROM when using LOG_X api. + NOTE: It's not available on syslog mode. + config ULOG_OUTPUT_LVL_ASSERT + bool "Assert" + config ULOG_OUTPUT_LVL_ERROR + bool "Error" + config ULOG_OUTPUT_LVL_WARNING + bool "Warning" + config ULOG_OUTPUT_LVL_INFO + bool "Information" + config ULOG_OUTPUT_LVL_DEBUG + bool "Debug" + endchoice + endif + + if ULOG_USING_SYSLOG + choice + prompt "The static output log level." + default ULOG_OUTPUT_LVL_DEBUG + help + When the log level is less than this option and it will stop output. + These log will not compile into ROM when using LOG_X api. + NOTE: It's not available on syslog mode. + config ULOG_OUTPUT_LVL_EMERG + bool "EMERG" + config ULOG_OUTPUT_LVL_ALERT + bool "ALERT" + config ULOG_OUTPUT_LVL_CRIT + bool "CRIT" + config ULOG_OUTPUT_LVL_ERROR + bool "ERR" + config ULOG_OUTPUT_LVL_WARNING + bool "WARNING" + config ULOG_OUTPUT_LVL_NOTICE + bool "NOTICE" + config ULOG_OUTPUT_LVL_INFO + bool "INFO" + config ULOG_OUTPUT_LVL_DEBUG + bool "DEBUG" + endchoice + endif + + config ULOG_OUTPUT_LVL + int + default 0 if ULOG_OUTPUT_LVL_ASSERT + default 0 if ULOG_OUTPUT_LVL_EMERG + default 1 if ULOG_OUTPUT_LVL_ALERT + default 2 if ULOG_OUTPUT_LVL_CRIT + default 3 if ULOG_OUTPUT_LVL_ERROR + default 4 if ULOG_OUTPUT_LVL_WARNING + default 5 if ULOG_OUTPUT_LVL_NOTICE + default 6 if ULOG_OUTPUT_LVL_INFO + default 7 if ULOG_OUTPUT_LVL_DEBUG + default 7 + + config ULOG_USING_ISR_LOG + bool "Enable ISR log." + default n + help + The log output API can using in ISR (Interrupt Service Routines) also. + + config ULOG_ASSERT_ENABLE + bool "Enable assert check." + default y + + config ULOG_LINE_BUF_SIZE + int "The log's max width." + default 128 + help + The buffer size for every line log. + + config ULOG_USING_ASYNC_OUTPUT + bool "Enable async output mode." + default n + help + When enable asynchronous output mode. The log output is not immediately and the log will stored to buffer. + The another thread (Such as idle) will read the buffer and output the log. So it will using more RAM. + + if ULOG_USING_ASYNC_OUTPUT + config ULOG_ASYNC_OUTPUT_BUF_SIZE + int "The async output buffer size." + default 2048 + + config ULOG_ASYNC_OUTPUT_BY_THREAD + bool "Enable async output by thread." + default y + help + This thread will output the asynchronous logs. The logs can output by other user thread when this option is disable. + + if ULOG_ASYNC_OUTPUT_BY_THREAD + + config ULOG_ASYNC_OUTPUT_THREAD_STACK + int "The async output thread stack size." + default 1024 + + config ULOG_ASYNC_OUTPUT_THREAD_PRIORITY + int "The async output thread stack priority." + range 0 RT_THREAD_PRIORITY_MAX + default 30 + + endif + endif + + menu "log format" + config ULOG_OUTPUT_FLOAT + bool "Enable float number support." + select RT_USING_LIBC + default n + help + The default formater is using rt_vsnprint and it not supported float number. + When enable this option then it will enable libc. The formater will change to vsnprint on libc. + + if !ULOG_USING_SYSLOG + config ULOG_USING_COLOR + bool "Enable color log." + default y + help + The log will has different color by level. + endif + + config ULOG_OUTPUT_TIME + bool "Enable time information." + default y + + config ULOG_TIME_USING_TIMESTAMP + bool "Enable timestamp format for time." + default n + select RT_USING_LIBC + depends on ULOG_OUTPUT_TIME + + config ULOG_OUTPUT_LEVEL + bool "Enable level information." + default y + + config ULOG_OUTPUT_TAG + bool "Enable tag information." + default y + + config ULOG_OUTPUT_THREAD_NAME + bool "Enable thread information." + default n + endmenu + + config ULOG_BACKEND_USING_CONSOLE + bool "Enable console backend." + default y + help + The low level output using rt_kprintf(). + + config ULOG_USING_FILTER + bool "Enable runtime log filter." + default n + help + It will enable the log filter. + Such as level filter, log tag filter, log kw filter and tag's level filter. + + config ULOG_USING_SYSLOG + bool "Enable syslog format log and API." + select ULOG_OUTPUT_TIME + select ULOG_USING_FILTER + default n + + config ULOG_SW_VERSION_NUM + int + default 0x00100 + help + sfotware module version number + endif + endmenu diff --git a/components/utilities/ulog/SConscript b/components/utilities/ulog/SConscript new file mode 100644 index 0000000000..03ea1b5528 --- /dev/null +++ b/components/utilities/ulog/SConscript @@ -0,0 +1,16 @@ +from building import * + +cwd = GetCurrentDir() +src = Glob('*.c') +path = [cwd] + +if GetDepend('ULOG_BACKEND_USING_CONSOLE'): + src += ['backend/console_be.c'] + +if GetDepend('ULOG_USING_SYSLOG'): + path += [cwd + '/syslog'] + src += Glob('syslog/*.c') + +group = DefineGroup('Utilities', src, depend = ['RT_USING_ULOG'], CPPPATH = path) + +Return('group') diff --git a/components/utilities/ulog/backend/console_be.c b/components/utilities/ulog/backend/console_be.c new file mode 100644 index 0000000000..b2ed3d565d --- /dev/null +++ b/components/utilities/ulog/backend/console_be.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-09-04 armink the first version + */ + +#include +#include + +#ifdef ULOG_BACKEND_USING_CONSOLE + +#if defined(ULOG_ASYNC_OUTPUT_BY_THREAD) && ULOG_ASYNC_OUTPUT_THREAD_STACK < 384 +#error "The thread stack size must more than 384 when using async output by thread (ULOG_ASYNC_OUTPUT_BY_THREAD)" +#endif + +static struct ulog_backend console; + +void ulog_console_backend_output(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw, + const char *log, size_t len) +{ + rt_device_t dev = rt_console_get_device(); + +#ifdef RT_USING_DEVICE + if (dev == RT_NULL) + { + rt_hw_console_output(log); + } + else + { + rt_uint16_t old_flag = dev->open_flag; + + dev->open_flag |= RT_DEVICE_FLAG_STREAM; + rt_device_write(dev, 0, log, len); + dev->open_flag = old_flag; + } +#else + rt_hw_console_output(log); +#endif + +} + +int ulog_console_backend_init(void) +{ + console.output = ulog_console_backend_output; + + ulog_backend_register(&console, "console", RT_TRUE); + + return 0; +} +INIT_COMPONENT_EXPORT(ulog_console_backend_init); + +#endif /* ULOG_BACKEND_USING_CONSOLE */ diff --git a/components/utilities/ulog/syslog/syslog.c b/components/utilities/ulog/syslog/syslog.c new file mode 100644 index 0000000000..0df99c77f2 --- /dev/null +++ b/components/utilities/ulog/syslog/syslog.c @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-09-07 armink the first version + */ + +#include +#include +#include +#include "syslog.h" + +#ifdef ULOG_OUTPUT_FLOAT +#include +#endif + +/* + * reference: + * http://pubs.opengroup.org/onlinepubs/7908799/xsh/syslog.h.html + * https://www.gnu.org/software/libc/manual/html_node/Submitting-Syslog-Messages.html + * http://man7.org/linux/man-pages/man3/syslog.3.html + */ + +#ifdef ULOG_USING_SYSLOG + +#ifndef ULOG_SYSLOG_IDENT_MAX_LEN +#define ULOG_SYSLOG_IDENT_MAX_LEN ULOG_FILTER_TAG_MAX_LEN +#endif + +static char local_ident[ULOG_SYSLOG_IDENT_MAX_LEN + 1]; +static int local_facility = LOG_USER; +static int local_option = LOG_USER; +static rt_bool_t is_open = RT_FALSE; + +/** + * open connection to syslog + * + * @param ident is an arbitrary identification string which future syslog invocations will prefix to each message. + * @param option is not using on ulog. + * @param facility is the default facility code for this connection. + */ +void openlog(const char *ident, int option, int facility) +{ + rt_base_t level; + + ulog_init(); + + level = rt_hw_interrupt_disable(); + + rt_memset(local_ident, 0, sizeof(local_ident)); + if (ident) + { + rt_strncpy(local_ident, ident, ULOG_SYSLOG_IDENT_MAX_LEN); + } + else + { + rt_strncpy(local_ident, "rtt", ULOG_SYSLOG_IDENT_MAX_LEN); + } + + local_option = option; + + if (facility) + { + local_facility = facility; + } + else + { + /* default facility is LOG_USER */ + local_facility = LOG_USER; + } + /* output all level log */ + setlogmask(LOG_UPTO(LOG_DEBUG)); + + is_open = RT_TRUE; + + rt_hw_interrupt_enable(level); + +} + +/** + * This is functionally identical to syslog. + * + * @param priority log priority, can be generated by the macro LOG_MAKEPRI + * @param format log format + * @param args log arguments + */ +void vsyslog(int priority, const char *format, va_list args) +{ + if (LOG_FAC(priority) == 0) + { + /* using local facility */ + priority |= local_facility; + } + + ulog_voutput(priority, local_ident, format, args); +} + +/** + * generates a log message + * + * @param priority log priority, can be generated by the macro LOG_MAKEPRI + * @param format log format, like printf() + */ +void syslog(int priority, const char *format, ...) +{ + va_list args; + + if (!is_open) + { + openlog(0, 0, 0); + } + /* args point to the first variable parameter */ + va_start(args, format); + + vsyslog(priority, format, args); + + va_end(args); +} + +/** + * close the syslog + */ +void closelog(void) +{ + ulog_deinit(); + + is_open = RT_FALSE; +} + +/** + * set log priority mask + * + * @param mask The log priority mask which generate by macro LOG_MASK and LOG_UPTO. + * + * @return This function returns the previous log priority mask. + */ +int setlogmask(int mask) +{ + static int old_mask = 0; + int return_mask = old_mask; + + ulog_tag_lvl_filter_set(local_ident, mask); + + old_mask = mask; + + return return_mask; +} + +static const char *get_month_str(uint8_t month) +{ + switch(month) + { + case 1: return "Jan"; + case 2: return "Feb"; + case 3: return "Mar"; + case 4: return "Apr"; + case 5: return "May"; + case 6: return "June"; + case 7: return "July"; + case 8: return "Aug"; + case 9: return "Sept"; + case 10: return "Oct"; + case 11: return "Nov"; + case 12: return "Dec"; + default: return "Unknown"; + } +} + +RT_WEAK rt_size_t syslog_formater(char *log_buf, int level, const char *tag, const char *format, va_list args) +{ + extern size_t ulog_strcpy(size_t cur_len, char *dst, const char *src); + + rt_size_t log_len = 0, newline_len = rt_strlen(ULOG_NEWLINE_SIGN); + int fmt_result; + + RT_ASSERT(log_buf); + RT_ASSERT(LOG_PRI(level) <= LOG_DEBUG); + RT_ASSERT(tag); + RT_ASSERT(format); + + /* add time and priority (level) info */ + { + time_t now = time(RT_NULL); + struct tm *tm, tm_tmp; + + tm = gmtime_r(&now, &tm_tmp); + +#ifdef ULOG_OUTPUT_LEVEL + rt_snprintf(log_buf + log_len, ULOG_LINE_BUF_SIZE - log_len, "<%d>%s%3d %02d:%02d:%02d", level, + get_month_str(tm->tm_mon + 1), tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, rt_tick_get() % 1000); +#else + rt_snprintf(log_buf + log_len, ULOG_LINE_BUF_SIZE - log_len, "%s%3d %02d:%02d:%02d", + get_month_str(tm->tm_mon + 1), tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, rt_tick_get() % 1000); +#endif /* ULOG_OUTPUT_LEVEL */ + + log_len += rt_strlen(log_buf + log_len); + } + +#ifdef ULOG_OUTPUT_TAG + /* add identification (tag) info */ + { + log_len += ulog_strcpy(log_len, log_buf + log_len, " "); + log_len += ulog_strcpy(log_len, log_buf + log_len, tag); + } +#endif /* ULOG_OUTPUT_TAG */ + +#ifdef ULOG_OUTPUT_THREAD_NAME + /* add thread info */ + { + log_len += ulog_strcpy(log_len, log_buf + log_len, " "); + /* is not in interrupt context */ + if (rt_interrupt_get_nest() == 0) + { + log_len += ulog_strcpy(log_len, log_buf + log_len, rt_thread_self()->name); + } + else + { + log_len += ulog_strcpy(log_len, log_buf + log_len, "ISR"); + } + } +#endif /* ULOG_OUTPUT_THREAD_NAME */ + + log_len += ulog_strcpy(log_len, log_buf + log_len, ": "); + +#ifdef ULOG_OUTPUT_FLOAT + fmt_result = vsnprintf(log_buf + log_len, ULOG_LINE_BUF_SIZE - log_len, format, args); +#else + fmt_result = rt_vsnprintf(log_buf + log_len, ULOG_LINE_BUF_SIZE - log_len, format, args); +#endif /* ULOG_OUTPUT_FLOAT */ + + /* calculate log length */ + if ((log_len + fmt_result <= ULOG_LINE_BUF_SIZE) && (fmt_result > -1)) + { + log_len += fmt_result; + } + else + { + /* using max length */ + log_len = ULOG_LINE_BUF_SIZE; + } + + /* overflow check and reserve some space for newline sign */ + if (log_len + newline_len > ULOG_LINE_BUF_SIZE) + { + /* using max length */ + log_len = ULOG_LINE_BUF_SIZE; + /* reserve some space for newline sign */ + log_len -= newline_len; + } + + /* package newline sign */ + log_len += ulog_strcpy(log_len, log_buf + log_len, ULOG_NEWLINE_SIGN); + + return log_len; +} + +#endif /* ULOG_USING_SYSLOG */ diff --git a/components/utilities/ulog/syslog/syslog.h b/components/utilities/ulog/syslog/syslog.h new file mode 100644 index 0000000000..43eec7125f --- /dev/null +++ b/components/utilities/ulog/syslog/syslog.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-09-07 armink the first version + */ + +#ifndef _SYSLOG_H_ +#define _SYSLOG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * priorities/facilities are encoded into a single 32-bit quantity, where the + * bottom 3 bits are the priority (0-7) and the top 28 bits are the facility + * (0-big number). Both the priorities and the facilities map roughly + * one-to-one to strings in the syslogd(8) source code. This mapping is + * included in this file. + * + * priorities (these are ordered) + */ +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOG_PRIMASK 0x07 + +#define LOG_PRI(p) ((p) & LOG_PRIMASK) +#define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) + +/* facility codes */ +#define LOG_KERN (0<<3) /* kernel messages */ +#define LOG_USER (1<<3) /* random user-level messages */ +#define LOG_MAIL (2<<3) /* mail system */ +#define LOG_DAEMON (3<<3) /* system daemons */ +#define LOG_AUTH (4<<3) /* security/authorization messages */ +#define LOG_SYSLOG (5<<3) /* messages generated internally by syslogd */ +#define LOG_LPR (6<<3) /* line printer subsystem */ +#define LOG_NEWS (7<<3) /* network news subsystem */ +#define LOG_UUCP (8<<3) /* UUCP subsystem */ +#define LOG_CRON (9<<3) /* clock daemon */ +#define LOG_AUTHPRIV (10<<3) /* security/authorization messages (private) */ + +/* other codes through 15 reserved for system use */ +#define LOG_LOCAL0 (16<<3) /* reserved for local use */ +#define LOG_LOCAL1 (17<<3) /* reserved for local use */ +#define LOG_LOCAL2 (18<<3) /* reserved for local use */ +#define LOG_LOCAL3 (19<<3) /* reserved for local use */ +#define LOG_LOCAL4 (20<<3) /* reserved for local use */ +#define LOG_LOCAL5 (21<<3) /* reserved for local use */ +#define LOG_LOCAL6 (22<<3) /* reserved for local use */ +#define LOG_LOCAL7 (23<<3) /* reserved for local use */ + +#define LOG_NFACILITIES 24 /* current number of facilities */ +#define LOG_FACMASK 0x03f8 /* mask to extract facility part */ +/* facility of pri */ +#define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3) + +/* + * arguments to setlogmask. + */ +#define LOG_MASK(pri) (1 << (pri)) /* mask for one priority */ +#define LOG_UPTO(pri) ((1 << ((pri)+1)) - 1) /* all priorities through pri */ + +/* + * Option flags for openlog. + * + * LOG_ODELAY no longer does anything. + * LOG_NDELAY is the inverse of what it used to be. + */ +#define LOG_PID 0x01 /* log the pid with each message */ +#define LOG_CONS 0x02 /* log on the console if errors in sending */ +#define LOG_ODELAY 0x04 /* delay open until first syslog() (default) */ +#define LOG_NDELAY 0x08 /* don't delay open */ +#define LOG_NOWAIT 0x10 /* don't wait for console forks: DEPRECATED */ +#define LOG_PERROR 0x20 /* log to stderr as well */ + +#include + +void closelog(void); +void openlog(const char *ident, int option, int facility); +int setlogmask(int mask); +void syslog(int priority, const char *format, ...); +void vsyslog(int priority, const char *format, va_list args); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYSLOG_H_ */ diff --git a/components/utilities/ulog/ulog.c b/components/utilities/ulog/ulog.c new file mode 100644 index 0000000000..2f950fe0d9 --- /dev/null +++ b/components/utilities/ulog/ulog.c @@ -0,0 +1,1152 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-08-25 armink the first version + */ + +#include +#include "ulog.h" +#include "rthw.h" + +#ifdef ULOG_USING_SYSLOG +#include +#endif + +#ifdef ULOG_OUTPUT_FLOAT +#include +#endif + +#ifdef ULOG_TIME_USING_TIMESTAMP +#include +#endif + +#ifdef ULOG_USING_ASYNC_OUTPUT +#include +#endif + +#ifdef RT_USING_ULOG + +/* the number which is max stored line logs */ +#ifndef ULOG_ASYNC_OUTPUT_STORE_LINES +#define ULOG_ASYNC_OUTPUT_STORE_LINES (ULOG_ASYNC_OUTPUT_BUF_SIZE * 3 / 2 / ULOG_LINE_BUF_SIZE) +#endif + +#ifdef ULOG_USING_COLOR +/** + * CSI(Control Sequence Introducer/Initiator) sign + * more information on https://en.wikipedia.org/wiki/ANSI_escape_code + */ +#define CSI_START "\033[" +#define CSI_END "\033[0m" +/* output log front color */ +#define F_BLACK "30m" +#define F_RED "31m" +#define F_GREEN "32m" +#define F_YELLOW "33m" +#define F_BLUE "34m" +#define F_MAGENTA "35m" +#define F_CYAN "36m" +#define F_WHITE "37m" + +/* output log default color definition */ +#ifndef ULOG_COLOR_DEBUG +#define ULOG_COLOR_DEBUG NULL +#endif +#ifndef ULOG_COLOR_INFO +#define ULOG_COLOR_INFO (F_GREEN) +#endif +#ifndef ULOG_COLOR_WARN +#define ULOG_COLOR_WARN (F_YELLOW) +#endif +#ifndef ULOG_COLOR_ERROR +#define ULOG_COLOR_ERROR (F_RED) +#endif +#ifndef ULOG_COLOR_ASSERT +#define ULOG_COLOR_ASSERT (F_MAGENTA) +#endif +#endif /* ULOG_USING_COLOR */ + +#if ULOG_LINE_BUF_SIZE < 80 +#error "the log line buffer size must more than 80" +#endif + +/* tag's level filter */ +struct tag_lvl_filter +{ + char tag[ULOG_FILTER_TAG_MAX_LEN + 1]; + rt_uint32_t level; + rt_slist_t list; +}; +typedef struct tag_lvl_filter *tag_lvl_filter_t; + +struct rt_ulog +{ + rt_bool_t init_ok; + struct rt_mutex output_locker; + /* all backends */ + rt_slist_t backend_list; + /* the thread log's line buffer */ + char log_buf_th[ULOG_LINE_BUF_SIZE]; + +#ifdef ULOG_USING_ISR_LOG + /* the ISR log's line buffer */ + rt_base_t output_locker_isr_lvl; + char log_buf_isr[ULOG_LINE_BUF_SIZE]; +#endif /* ULOG_USING_ISR_LOG */ + +#ifdef ULOG_USING_ASYNC_OUTPUT + rt_rbb_t async_rbb; + rt_thread_t async_th; + struct rt_semaphore async_notice; +#endif + +#ifdef ULOG_USING_FILTER + struct + { + /* all tag's level filter */ + rt_slist_t tag_lvl_list; + /* global filter level, tag and keyword */ + rt_uint32_t level; + char tag[ULOG_FILTER_TAG_MAX_LEN + 1]; + char keyword[ULOG_FILTER_KW_MAX_LEN + 1]; + } filter; +#endif /* ULOG_USING_FILTER */ +}; + +/* level output info */ +static const char * const level_output_info[] = +{ + "A/", + NULL, + NULL, + "E/", + "W/", + NULL, + "I/", + "D/", +}; + +#ifdef ULOG_USING_COLOR +/* color output info */ +static const char * const color_output_info[] = +{ + ULOG_COLOR_ASSERT, + NULL, + NULL, + ULOG_COLOR_ERROR, + ULOG_COLOR_WARN, + NULL, + ULOG_COLOR_INFO, + ULOG_COLOR_DEBUG, +}; +#endif /* ULOG_USING_COLOR */ + +/* ulog local object */ +static struct rt_ulog ulog = { 0 }; + +size_t ulog_strcpy(size_t cur_len, char *dst, const char *src) +{ + const char *src_old = src; + + RT_ASSERT(dst); + RT_ASSERT(src); + + while (*src != 0) + { + /* make sure destination has enough space */ + if (cur_len++ <= ULOG_LINE_BUF_SIZE) + { + *dst++ = *src++; + } + else + { + break; + } + } + return src - src_old; +} + +static void output_unlock(void) +{ + /* is in thread context */ + if (rt_interrupt_get_nest() == 0) + { + rt_mutex_release(&ulog.output_locker); + } + else + { +#ifdef ULOG_USING_ISR_LOG + rt_hw_interrupt_enable(ulog.output_locker_isr_lvl); +#endif + } +} + +static void output_lock(void) +{ + /* is in thread context */ + if (rt_interrupt_get_nest() == 0) + { + rt_mutex_take(&ulog.output_locker, RT_WAITING_FOREVER); + } + else + { +#ifdef ULOG_USING_ISR_LOG + ulog.output_locker_isr_lvl = rt_hw_interrupt_disable(); +#endif + } +} + +static char *get_log_buf(void) +{ + /* is in thread context */ + if (rt_interrupt_get_nest() == 0) + { + return ulog.log_buf_th; + } + else + { +#ifdef ULOG_USING_ISR_LOG + return ulog.log_buf_isr; +#else + rt_kprintf("Error: Current mode not supported run in ISR. Please enable ULOG_USING_ISR_LOG.\n"); + return NULL; +#endif + } +} + +RT_WEAK rt_size_t ulog_formater(char *log_buf, rt_uint32_t level, const char *tag, const char *format, va_list args) +{ + rt_size_t log_len = 0, newline_len = rt_strlen(ULOG_NEWLINE_SIGN); + int fmt_result; + + RT_ASSERT(log_buf); + RT_ASSERT(level <= LOG_LVL_DBG); + RT_ASSERT(tag); + RT_ASSERT(format); + +#ifdef ULOG_USING_COLOR + /* add CSI start sign and color info */ + if (color_output_info[level]) + { + log_len += ulog_strcpy(log_len, log_buf + log_len, CSI_START); + log_len += ulog_strcpy(log_len, log_buf + log_len, color_output_info[level]); + } +#endif /* ULOG_USING_COLOR */ + +#ifdef ULOG_OUTPUT_TIME + /* add time info */ + { +#ifdef ULOG_TIME_USING_TIMESTAMP + time_t now = time(NULL); + struct tm *tm, tm_tmp; + + tm = gmtime_r(&now, &tm_tmp); + +#ifdef RT_USING_SOFT_RTC + rt_snprintf(log_buf + log_len, ULOG_LINE_BUF_SIZE - log_len, "%02d-%02d %02d:%02d:%02d.%03d", tm->tm_mon + 1, + tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, rt_tick_get() % 1000); +#else + rt_snprintf(log_buf + log_len, ULOG_LINE_BUF_SIZE - log_len, "%02d-%02d %02d:%02d:%02d", tm->tm_mon + 1, + tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); +#endif /* RT_USING_SOFT_RTC */ + +#else + rt_snprintf(log_buf + log_len, ULOG_LINE_BUF_SIZE - log_len, "[%d]", rt_tick_get()); +#endif /* ULOG_TIME_USING_TIMESTAMP */ + + log_len += rt_strlen(log_buf + log_len); + } +#endif /* ULOG_OUTPUT_TIME */ + +#ifdef ULOG_OUTPUT_LEVEL + +#ifdef ULOG_OUTPUT_TIME + log_len += ulog_strcpy(log_len, log_buf + log_len, " "); +#endif + + /* add level info */ + log_len += ulog_strcpy(log_len, log_buf + log_len, level_output_info[level]); +#endif /* ULOG_OUTPUT_LEVEL */ + +#ifdef ULOG_OUTPUT_TAG + +#if !defined(ULOG_OUTPUT_LEVEL) && defined(ULOG_OUTPUT_TIME) + log_len += ulog_strcpy(log_len, log_buf + log_len, " "); +#endif + + /* add tag info */ + log_len += ulog_strcpy(log_len, log_buf + log_len, tag); +#endif /* ULOG_OUTPUT_TAG */ + +#ifdef ULOG_OUTPUT_THREAD_NAME + /* add thread info */ + { + +#if defined(ULOG_OUTPUT_TIME) || defined(ULOG_OUTPUT_LEVEL) || defined(ULOG_OUTPUT_TAG) + log_len += ulog_strcpy(log_len, log_buf + log_len, " "); +#endif + + /* is not in interrupt context */ + if (rt_interrupt_get_nest() == 0) + { + log_len += ulog_strcpy(log_len, log_buf + log_len, rt_thread_self()->name); + } + else + { + log_len += ulog_strcpy(log_len, log_buf + log_len, "ISR"); + } + } +#endif /* ULOG_OUTPUT_THREAD_NAME */ + + log_len += ulog_strcpy(log_len, log_buf + log_len, ": "); + +#ifdef ULOG_OUTPUT_FLOAT + fmt_result = vsnprintf(log_buf + log_len, ULOG_LINE_BUF_SIZE - log_len, format, args); +#else + fmt_result = rt_vsnprintf(log_buf + log_len, ULOG_LINE_BUF_SIZE - log_len, format, args); +#endif /* ULOG_OUTPUT_FLOAT */ + + /* calculate log length */ + if ((log_len + fmt_result <= ULOG_LINE_BUF_SIZE) && (fmt_result > -1)) + { + log_len += fmt_result; + } + else + { + /* using max length */ + log_len = ULOG_LINE_BUF_SIZE; + } + + /* overflow check and reserve some space for CSI end sign and newline sign */ +#ifdef ULOG_USING_COLOR + if (log_len + (sizeof(CSI_END) - 1) + newline_len > ULOG_LINE_BUF_SIZE) + { + /* using max length */ + log_len = ULOG_LINE_BUF_SIZE; + /* reserve some space for CSI end sign */ + log_len -= (sizeof(CSI_END) - 1); +#else + if (log_len + newline_len > ULOG_LINE_BUF_SIZE) + { + /* using max length */ + log_len = ULOG_LINE_BUF_SIZE; +#endif /* ULOG_USING_COLOR */ + /* reserve some space for newline sign */ + log_len -= newline_len; + } + + /* package newline sign */ + log_len += ulog_strcpy(log_len, log_buf + log_len, ULOG_NEWLINE_SIGN); + +#ifdef ULOG_USING_COLOR + /* add CSI end sign */ + if (color_output_info[level]) + { + log_len += ulog_strcpy(log_len, log_buf + log_len, CSI_END); + } +#endif /* ULOG_USING_COLOR */ + + return log_len; +} + +void ulog_output_to_all_backend(rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log, rt_size_t size) +{ + rt_slist_t *node; + ulog_backend_t backend; + + if (!ulog.init_ok) + return; + + /* output for all backends */ + for (node = rt_slist_first(&ulog.backend_list); node; node = rt_slist_next(node)) + { + backend = rt_slist_entry(node, struct ulog_backend, list); +#if !defined(ULOG_USING_COLOR) || defined(ULOG_USING_SYSLOG) + backend->output(backend, level, tag, is_raw, log, size); +#else + if (backend->support_color) + { + backend->output(backend, level, tag, is_raw, log, size); + } + else + { + /* recalculate the log start address and log size when backend not supported color */ + rt_size_t color_info_len = rt_strlen(color_output_info[level]); + if (color_info_len) + { + rt_size_t color_hdr_len = rt_strlen(CSI_START) + color_info_len; + + log += color_hdr_len; + size -= (color_hdr_len + (sizeof(CSI_END) - 1)); + } + backend->output(backend, level, tag, is_raw, log, size); + } +#endif /* !defined(ULOG_USING_COLOR) || defined(ULOG_USING_SYSLOG) */ + } +} + +static void do_output(rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log_buf, rt_size_t log_len) +{ +#ifdef ULOG_USING_ASYNC_OUTPUT + rt_rbb_blk_t log_blk; + ulog_frame_t log_frame; + + /* allocate log frame */ + log_blk = rt_rbb_blk_alloc(ulog.async_rbb, RT_ALIGN(sizeof(struct ulog_frame) + log_len, RT_ALIGN_SIZE)); + if (log_blk) + { + /* package the log frame */ + log_frame = (ulog_frame_t) log_blk->buf; + log_frame->magic = ULOG_FRAME_MAGIC; + log_frame->is_raw = is_raw; + log_frame->level = level; + log_frame->log_len = log_len; + log_frame->tag = tag; + log_frame->log = (const char *)log_blk->buf + sizeof(struct ulog_frame); + /* copy log data */ + rt_memcpy(log_blk->buf + sizeof(struct ulog_frame), log_buf, log_len); + /* put the block */ + rt_rbb_blk_put(log_blk); + /* send a notice */ + rt_sem_release(&ulog.async_notice); + } + else + { + static rt_bool_t already_output = RT_FALSE; + if (already_output == RT_FALSE) + { + rt_kprintf("Warning: There is no enough buffer for saving async log," + " please increase the ULOG_ASYNC_OUTPUT_BUF_SIZE option.\n"); + already_output = RT_TRUE; + } + } +#else + /* is in thread context */ + if (rt_interrupt_get_nest() == 0) + { + /* output to all backends */ + ulog_output_to_all_backend(level, tag, is_raw, log_buf, log_len); + } + else + { +#ifdef ULOG_BACKEND_USING_CONSOLE + /* We can't ensure that all backends support ISR context output. + * So only using rt_kprintf when context is ISR */ + extern void ulog_console_backend_output(struct ulog_backend *backend, rt_uint32_t level, const char *tag, + rt_bool_t is_raw, const char *log, size_t len); + ulog_console_backend_output(NULL, level, tag, is_raw, log_buf, log_len); +#endif /* ULOG_BACKEND_USING_CONSOLE */ + } +#endif /* ULOG_USING_ASYNC_OUTPUT */ +} + +/** + * output the log by variable argument list + * + * @param level level + * @param tag tag + * @param format output format + * @param args variable argument list + */ +void ulog_voutput(rt_uint32_t level, const char *tag, const char *format, va_list args) +{ + char *log_buf = NULL; + rt_size_t log_len = 0; + +#ifndef ULOG_USING_SYSLOG + RT_ASSERT(level <= LOG_LVL_DBG); +#else + RT_ASSERT(LOG_PRI(level) <= LOG_DEBUG); +#endif /* ULOG_USING_SYSLOG */ + + RT_ASSERT(tag); + RT_ASSERT(format); + + if (!ulog.init_ok) + { + return; + } + +#ifdef ULOG_USING_FILTER + /* level filter */ +#ifndef ULOG_USING_SYSLOG + if (level > ulog.filter.level || level > ulog_tag_lvl_filter_get(tag)) + { + return; + } +#else + if (((LOG_MASK(LOG_PRI(level)) & ulog.filter.level) == 0) + || ((LOG_MASK(LOG_PRI(level)) & ulog_tag_lvl_filter_get(tag)) == 0)) + { + return; + } +#endif /* ULOG_USING_SYSLOG */ + else if (!rt_strstr(tag, ulog.filter.tag)) + { + /* tag filter */ + return; + } +#endif /* ULOG_USING_FILTER */ + + /* get log buffer */ + log_buf = get_log_buf(); + + /* lock output */ + output_lock(); + +#ifndef ULOG_USING_SYSLOG + log_len = ulog_formater(log_buf, level, tag, format, args); +#else + extern rt_size_t syslog_formater(char *log_buf, rt_uint8_t level, const char *tag, const char *format, va_list args); + log_len = syslog_formater(log_buf, level, tag, format, args); +#endif /* ULOG_USING_SYSLOG */ + +#ifdef ULOG_USING_FILTER + /* keyword filter */ + if (ulog.filter.keyword[0] != '\0') + { + /* add string end sign */ + log_buf[log_len] = '\0'; + /* find the keyword */ + if (!rt_strstr(log_buf, ulog.filter.keyword)) + { + /* unlock output */ + output_unlock(); + return; + } + } +#endif /* ULOG_USING_FILTER */ + /* do log output */ + do_output(level, tag, RT_FALSE, log_buf, log_len); + + /* unlock output */ + output_unlock(); +} + +/** + * output the log + * + * @param level level + * @param tag tag + * @param format output format + * @param ... args + */ +void ulog_output(rt_uint32_t level, const char *tag, const char *format, ...) +{ + va_list args; + + /* args point to the first variable parameter */ + va_start(args, format); + + ulog_voutput(level, tag, format, args); + + va_end(args); +} + +/** + * output RAW string format log + * + * @param format output format + * @param ... args + */ +void ulog_raw(const char *format, ...) +{ + rt_size_t log_len = 0; + char *log_buf = NULL; + va_list args; + int fmt_result; + + RT_ASSERT(ulog.init_ok); + + /* get log buffer */ + log_buf = get_log_buf(); + + /* lock output */ + output_lock(); + /* args point to the first variable parameter */ + va_start(args, format); + +#ifdef ULOG_OUTPUT_FLOAT + fmt_result = vsnprintf(log_buf, ULOG_LINE_BUF_SIZE, format, args); +#else + fmt_result = rt_vsnprintf(log_buf, ULOG_LINE_BUF_SIZE, format, args); +#endif /* ULOG_OUTPUT_FLOAT */ + + va_end(args); + + /* calculate log length */ + if ((fmt_result > -1) && (fmt_result <= ULOG_LINE_BUF_SIZE)) + { + log_len = fmt_result; + } + else + { + log_len = ULOG_LINE_BUF_SIZE; + } + + /* do log output */ + do_output(LOG_LVL_DBG, NULL, RT_TRUE, log_buf, log_len); + + /* unlock output */ + output_unlock(); +} + +/** + * dump the hex format data to log + * + * @param name name for hex object, it will show on log header + * @param width hex number for every line, such as: 16, 32 + * @param buf hex buffer + * @param size buffer size + */ +void ulog_hexdump(const char *name, rt_size_t width, rt_uint8_t *buf, rt_size_t size) +{ +#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ') + + rt_size_t i, j; + rt_size_t log_len = 0; + char *log_buf = NULL, dump_string[8]; + int fmt_result; + + RT_ASSERT(ulog.init_ok); + + /* get log buffer */ + log_buf = get_log_buf(); + + /* lock output */ + output_lock(); + + for (i = 0, log_len = 0; i < size; i += width) + { + /* package header */ + fmt_result = rt_snprintf(log_buf, ULOG_LINE_BUF_SIZE, "D/HEX %s: %04X-%04X: ", name, i, i + width); + /* calculate log length */ + if ((fmt_result > -1) && (fmt_result <= ULOG_LINE_BUF_SIZE)) + { + log_len = fmt_result; + } + else + { + log_len = ULOG_LINE_BUF_SIZE; + } + /* dump hex */ + for (j = 0; j < width; j++) + { + if (i + j < size) + { + rt_snprintf(dump_string, sizeof(dump_string), "%02X ", buf[i + j]); + } + else + { + rt_strncpy(dump_string, " ", sizeof(dump_string)); + } + log_len += ulog_strcpy(log_len, log_buf + log_len, dump_string); + if ((j + 1) % 8 == 0) + { + log_len += ulog_strcpy(log_len, log_buf + log_len, " "); + } + } + log_len += ulog_strcpy(log_len, log_buf + log_len, " "); + /* dump char for hex */ + for (j = 0; j < width; j++) + { + if (i + j < size) + { + rt_snprintf(dump_string, sizeof(dump_string), "%c", __is_print(buf[i + j]) ? buf[i + j] : '.'); + log_len += ulog_strcpy(log_len, log_buf + log_len, dump_string); + } + } + /* overflow check and reserve some space for newline sign */ + if (log_len + rt_strlen(ULOG_NEWLINE_SIGN) > ULOG_LINE_BUF_SIZE) + { + log_len = ULOG_LINE_BUF_SIZE - rt_strlen(ULOG_NEWLINE_SIGN); + } + /* package newline sign */ + log_len += ulog_strcpy(log_len, log_buf + log_len, ULOG_NEWLINE_SIGN); + /* do log output */ + do_output(LOG_LVL_DBG, NULL, RT_TRUE, log_buf, log_len); + } + /* unlock output */ + output_unlock(); +} + +#ifdef ULOG_USING_FILTER +/** + * Set the filter's level by different tag. + * The log on this tag which level is less than it will stop output. + * + * example: + * // the example tag log enter silent mode + * ulog_set_filter_lvl("example", LOG_FILTER_LVL_SILENT); + * // the example tag log which level is less than INFO level will stop output + * ulog_set_filter_lvl("example", LOG_LVL_INFO); + * // remove example tag's level filter, all level log will resume output + * ulog_set_filter_lvl("example", LOG_FILTER_LVL_ALL); + * + * @param tag log tag + * @param level The filter level. When the level is LOG_FILTER_LVL_SILENT, the log enter silent mode. + * When the level is LOG_FILTER_LVL_ALL, it will remove this tag's level filer. + * Then all level log will resume output. + * + * @return 0: success + * -5: no memory + */ +int ulog_tag_lvl_filter_set(const char *tag, rt_uint32_t level) +{ + rt_slist_t *node; + tag_lvl_filter_t tag_lvl = NULL; + int result = RT_EOK; + + RT_ASSERT(level <= LOG_FILTER_LVL_ALL); + + if (!ulog.init_ok) + return result; + + /* lock output */ + output_lock(); + /* find the tag in list */ + for (node = rt_slist_first(&ulog.filter.tag_lvl_list); node; node = rt_slist_next(node)) + { + tag_lvl = rt_slist_entry(node, struct tag_lvl_filter, list); + if (!rt_strncmp(tag_lvl->tag, tag, ULOG_FILTER_TAG_MAX_LEN)) + { + break; + } + else + { + tag_lvl = NULL; + } + } + /* find OK */ + if (tag_lvl) + { + if (level == LOG_FILTER_LVL_ALL) + { + /* remove current tag's level filter when input level is the lowest level */ + rt_slist_remove(&ulog.filter.tag_lvl_list, &tag_lvl->list); + } + else + { + /* update level */ + tag_lvl->level = level; + } + } + else + { + /* only add the new tag's level filer when level is not LOG_FILTER_LVL_ALL */ + if (level != LOG_FILTER_LVL_ALL) + { + /* new a tag's level filter */ + tag_lvl = (tag_lvl_filter_t)rt_malloc(sizeof(struct tag_lvl_filter)); + if (tag_lvl) + { + rt_memset(tag_lvl->tag, 0 , sizeof(tag_lvl->tag)); + rt_strncpy(tag_lvl->tag, tag, ULOG_FILTER_TAG_MAX_LEN); + tag_lvl->level = level; + rt_slist_append(&ulog.filter.tag_lvl_list, &tag_lvl->list); + } + else + { + result = -RT_ENOMEM; + } + } + } + /* unlock output */ + output_unlock(); + + return result; +} + +/** + * get the level on tag's level filer + * + * @param tag log tag + * + * @return It will return the lowest level when tag was not found. + * Other level will return when tag was found. + */ +rt_uint32_t ulog_tag_lvl_filter_get(const char *tag) +{ + rt_slist_t *node; + tag_lvl_filter_t tag_lvl = NULL; + rt_uint32_t level = LOG_FILTER_LVL_ALL; + + if (!ulog.init_ok) + return level; + + /* lock output */ + output_lock(); + /* find the tag in list */ + for (node = rt_slist_first(&ulog.filter.tag_lvl_list); node; node = rt_slist_next(node)) + { + tag_lvl = rt_slist_entry(node, struct tag_lvl_filter, list); + if (!rt_strncmp(tag_lvl->tag, tag, ULOG_FILTER_TAG_MAX_LEN)) + { + level = tag_lvl->level; + break; + } + } + /* unlock output */ + output_unlock(); + + return level; +} + +/** + * set log global filter level + * + * @param level log level: LOG_LVL_ASSERT, LOG_LVL_ERROR, LOG_LVL_WARNING, LOG_LVL_INFO, LOG_LVL_DBG + * LOG_FILTER_LVL_SILENT: disable all log output, except assert level + * LOG_FILTER_LVL_ALL: enable all log output + */ +void ulog_global_filter_lvl_set(rt_uint32_t level) +{ + RT_ASSERT(level <= LOG_FILTER_LVL_ALL); + + ulog.filter.level = level; +} + +/** + * set log global filter tag + * + * @param tag tag + */ +void ulog_global_filter_tag_set(const char *tag) +{ + RT_ASSERT(tag); + + rt_strncpy(ulog.filter.tag, tag, ULOG_FILTER_TAG_MAX_LEN); +} + +/** + * set log global filter keyword + * + * @param keyword keyword + */ +void ulog_global_filter_kw_set(const char *keyword) +{ + RT_ASSERT(keyword); + + rt_strncpy(ulog.filter.keyword, keyword, ULOG_FILTER_KW_MAX_LEN); +} + +#if defined(RT_USING_FINSH) && defined(FINSH_USING_MSH) +#include + +static void ulog_tag_lvl(uint8_t argc, char **argv) +{ + if (argc > 2) + { + if ((atoi(argv[2]) <= LOG_FILTER_LVL_ALL) && (atoi(argv[2]) >= 0)) + { + ulog_tag_lvl_filter_set(argv[1], atoi(argv[2])); + } + else + { + rt_kprintf("Please input correct level (0-%d).\n", LOG_FILTER_LVL_ALL); + } + } + else + { + rt_kprintf("Please input: ulog_tag_lvl .\n"); +#ifndef ULOG_USING_SYSLOG + rt_kprintf("Assert : 0\n"); + rt_kprintf("Error : 3\n"); + rt_kprintf("Warning : 4\n"); + rt_kprintf("Info : 6\n"); + rt_kprintf("Debug : 7\n"); +#else + rt_kprintf("EMERG : 1 (1 << 0)\n"); + rt_kprintf("ALERT : 2 (1 << 1)\n"); + rt_kprintf("CRIT : 4 (1 << 2)\n"); + rt_kprintf("ERR : 8 (1 << 3)\n"); + rt_kprintf("WARNING : 16 (1 << 4)\n"); + rt_kprintf("NOTICE : 32 (1 << 5)\n"); + rt_kprintf("INFO : 64 (1 << 6)\n"); + rt_kprintf("DEBUG : 128 (1 << 7)\n"); +#endif /* ULOG_USING_SYSLOG */ + } +} +MSH_CMD_EXPORT(ulog_tag_lvl, Set ulog filter level by different tag.); + +static void ulog_lvl(uint8_t argc, char **argv) +{ + if (argc > 1) + { + if ((atoi(argv[1]) <= LOG_FILTER_LVL_ALL) && (atoi(argv[1]) >= 0)) + { + ulog_global_filter_lvl_set(atoi(argv[1])); + } + else + { + rt_kprintf("Please input correct level (0-%d).\n", LOG_FILTER_LVL_ALL); + } + } + else + { + rt_kprintf("Please input: ulog_lvl .\n"); +#ifndef ULOG_USING_SYSLOG + rt_kprintf("Assert : 0\n"); + rt_kprintf("Error : 3\n"); + rt_kprintf("Warning : 4\n"); + rt_kprintf("Info : 6\n"); + rt_kprintf("Debug : 7\n"); +#else + rt_kprintf("EMERG : 1 (1 << 0)\n"); + rt_kprintf("ALERT : 2 (1 << 1)\n"); + rt_kprintf("CRIT : 4 (1 << 2)\n"); + rt_kprintf("ERR : 8 (1 << 3)\n"); + rt_kprintf("WARNING : 16 (1 << 4)\n"); + rt_kprintf("NOTICE : 32 (1 << 5)\n"); + rt_kprintf("INFO : 64 (1 << 6)\n"); + rt_kprintf("DEBUG : 128 (1 << 7)\n"); +#endif /* ULOG_USING_SYSLOG */ + } +} +MSH_CMD_EXPORT(ulog_lvl, Set ulog global filter level.); + +static void ulog_tag(uint8_t argc, char **argv) +{ + if (argc > 1) + { + if (rt_strlen(argv[1]) <= ULOG_FILTER_TAG_MAX_LEN) + { + ulog_global_filter_tag_set(argv[1]); + } + else + { + rt_kprintf("The tag length is too long. Max is %d.\n", ULOG_FILTER_TAG_MAX_LEN); + } + } + else + { + ulog_global_filter_tag_set(""); + } +} +MSH_CMD_EXPORT(ulog_tag, Set ulog global filter tag); + +static void ulog_kw(uint8_t argc, char **argv) +{ + if (argc > 1) + { + if (rt_strlen(argv[1]) <= ULOG_FILTER_KW_MAX_LEN) + { + ulog_global_filter_kw_set(argv[1]); + } + else + { + rt_kprintf("The keyword length is too long. Max is %d.\n", ULOG_FILTER_KW_MAX_LEN); + } + } + else + { + ulog_global_filter_kw_set(""); + } +} +MSH_CMD_EXPORT(ulog_kw, Set ulog global filter keyword); +#endif /* defined(RT_USING_FINSH) && defined(FINSH_USING_MSH) */ +#endif /* ULOG_USING_FILTER */ + +rt_err_t ulog_backend_register(ulog_backend_t backend, const char *name, rt_bool_t support_color) +{ + rt_base_t level; + + RT_ASSERT(backend); + RT_ASSERT(name); + RT_ASSERT(ulog.init_ok); + RT_ASSERT(backend->output); + + if (backend->init) + { + backend->init(backend); + } + + backend->support_color = support_color; + rt_memcpy(backend->name, name, RT_NAME_MAX); + + level = rt_hw_interrupt_disable(); + rt_slist_append(&ulog.backend_list, &backend->list); + rt_hw_interrupt_enable(level); + + return RT_EOK; +} + +rt_err_t ulog_backend_unregister(ulog_backend_t backend) +{ + rt_base_t level; + + RT_ASSERT(backend); + RT_ASSERT(ulog.init_ok); + + level = rt_hw_interrupt_disable(); + rt_slist_remove(&ulog.backend_list, &backend->list); + rt_hw_interrupt_enable(level); + + return RT_EOK; +} + +#ifdef ULOG_USING_ASYNC_OUTPUT +/** + * asynchronous output logs to all backends + * + * @note you must call this function when ULOG_ASYNC_OUTPUT_BY_THREAD is disable + */ +void ulog_async_output(void) +{ + rt_rbb_blk_t log_blk; + ulog_frame_t log_frame; + + while ((log_blk = rt_rbb_blk_get(ulog.async_rbb)) != NULL) + { + log_frame = (ulog_frame_t) log_blk->buf; + if (log_frame->magic == ULOG_FRAME_MAGIC) + { + /* output to all backends */ + ulog_output_to_all_backend(log_frame->level, log_frame->tag, log_frame->is_raw, log_frame->log, + log_frame->log_len); + } + rt_rbb_blk_free(ulog.async_rbb, log_blk); + } +} + +/** + * waiting for get asynchronous output log + * + * @param time the waiting time + */ +void ulog_async_waiting_log(rt_int32_t time) +{ + rt_sem_control(&ulog.async_notice, RT_IPC_CMD_RESET, RT_NULL); + rt_sem_take(&ulog.async_notice, time); +} + +static void async_output_thread_entry(void *param) +{ + while (1) + { + ulog_async_waiting_log(RT_WAITING_FOREVER); + ulog_async_output(); + } +} +#endif /* ULOG_USING_ASYNC_OUTPUT */ + +/** + * flush all backends's log + */ +void ulog_flush(void) +{ + rt_slist_t *node; + ulog_backend_t backend; + + if (!ulog.init_ok) + return; + +#ifdef ULOG_USING_ASYNC_OUTPUT + ulog_async_output(); +#endif + + /* flush all backends */ + for (node = rt_slist_first(&ulog.backend_list); node; node = rt_slist_next(node)) + { + backend = rt_slist_entry(node, struct ulog_backend, list); + if (backend->flush) + { + backend->flush(backend); + } + } +} + +int ulog_init(void) +{ + if (ulog.init_ok) + return 0; + + rt_mutex_init(&ulog.output_locker, "ulog lock", RT_IPC_FLAG_FIFO); + rt_slist_init(&ulog.backend_list); + +#ifdef ULOG_USING_FILTER + rt_slist_init(&ulog.filter.tag_lvl_list); +#endif + +#ifdef ULOG_USING_ASYNC_OUTPUT + RT_ASSERT(ULOG_ASYNC_OUTPUT_STORE_LINES >= 2); + /* async output ring block buffer */ + ulog.async_rbb = rt_rbb_create(RT_ALIGN(ULOG_ASYNC_OUTPUT_BUF_SIZE, RT_ALIGN_SIZE), ULOG_ASYNC_OUTPUT_STORE_LINES); + if (ulog.async_rbb == NULL) + { + rt_kprintf("Error: ulog init failed! No memory for async rbb.\n"); + rt_mutex_detach(&ulog.output_locker); + return -RT_ENOMEM; + } + /* async output thread */ + ulog.async_th = rt_thread_create("ulog_async", async_output_thread_entry, &ulog, ULOG_ASYNC_OUTPUT_THREAD_STACK, + ULOG_ASYNC_OUTPUT_THREAD_PRIORITY, 20); + if (ulog.async_th == NULL) + { + rt_kprintf("Error: ulog init failed! No memory for async output thread.\n"); + rt_mutex_detach(&ulog.output_locker); + rt_rbb_destroy(ulog.async_rbb); + return -RT_ENOMEM; + } + + rt_sem_init(&ulog.async_notice, "ulog", 0, RT_IPC_FLAG_FIFO); + /* async output thread startup */ + rt_thread_startup(ulog.async_th); + +#endif /* ULOG_USING_ASYNC_OUTPUT */ + +#ifdef ULOG_USING_FILTER + ulog_global_filter_lvl_set(LOG_FILTER_LVL_ALL); +#endif + + ulog.init_ok = RT_TRUE; + + return 0; +} +INIT_PREV_EXPORT(ulog_init); + +void ulog_deinit(void) +{ + rt_slist_t *node; + ulog_backend_t backend; + + if (!ulog.init_ok) + return; + + /* deinit all backends */ + for (node = rt_slist_first(&ulog.backend_list); node; node = rt_slist_next(node)) + { + backend = rt_slist_entry(node, struct ulog_backend, list); + if (backend->deinit) + { + backend->deinit(backend); + } + } + +#ifdef ULOG_USING_FILTER + /* deinit tag's level filter */ + { + tag_lvl_filter_t tag_lvl; + for (node = rt_slist_first(&ulog.filter.tag_lvl_list); node; node = rt_slist_next(node)) + { + tag_lvl = rt_slist_entry(node, struct tag_lvl_filter, list); + rt_free(tag_lvl); + } + } +#endif /* ULOG_USING_FILTER */ + + rt_mutex_detach(&ulog.output_locker); + +#ifdef ULOG_USING_ASYNC_OUTPUT + rt_rbb_destroy(ulog.async_rbb); + rt_thread_delete(ulog.async_th); +#endif + + ulog.init_ok = RT_FALSE; +} + +#endif /* RT_USING_ULOG */ diff --git a/components/utilities/ulog/ulog.h b/components/utilities/ulog/ulog.h new file mode 100644 index 0000000000..85b76c9cdd --- /dev/null +++ b/components/utilities/ulog/ulog.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-08-25 armink the first version + */ + +#ifndef _ULOG_H_ +#define _ULOG_H_ + +#include +#include "ulog_def.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ULOG_VERSION_STR "0.1.0" + +/* + * ulog init and deint + */ +int ulog_init(void); +void ulog_deinit(void); + +/* + * output different level log by LOG_X API + * + * NOTE: The `LOG_TAG` and `LOG_LVL` must be defined before including the when you want to use LOG_X API. + * + * #define LOG_TAG "example" + * #define LOG_LVL LOG_LVL_DBG + * #include + * + * Then you can using LOG_X API to output log + * + * LOG_D("this is a debug log!"); + * LOG_E("this is a error log!"); + */ +#define LOG_E(...) ulog_e(LOG_TAG, __VA_ARGS__) +#define LOG_W(...) ulog_w(LOG_TAG, __VA_ARGS__) +#define LOG_I(...) ulog_i(LOG_TAG, __VA_ARGS__) +#define LOG_D(...) ulog_d(LOG_TAG, __VA_ARGS__) +#define LOG_RAW(...) ulog_raw(__VA_ARGS__) + +/* + * backend register and unregister + */ +rt_err_t ulog_backend_register(ulog_backend_t backend, const char *name, rt_bool_t support_color); +rt_err_t ulog_backend_unregister(ulog_backend_t backend); + +#ifdef ULOG_USING_FILTER +/* + * log filter setting + */ +int ulog_tag_lvl_filter_set(const char *tag, rt_uint32_t level); +rt_uint32_t ulog_tag_lvl_filter_get(const char *tag); +void ulog_global_filter_lvl_set(rt_uint32_t level); +void ulog_global_filter_tag_set(const char *tag); +void ulog_global_filter_kw_set(const char *keyword); +#endif /* ULOG_USING_FILTER */ + +/* + * flush all backends's log + */ +void ulog_flush(void); + +#ifdef ULOG_USING_ASYNC_OUTPUT +/* + * asynchronous output API + */ +void ulog_async_output(void); +void ulog_async_waiting_log(rt_int32_t time); +#endif + +/* + * dump the hex format data to log + */ +void ulog_hexdump(const char *name, rt_size_t width, rt_uint8_t *buf, rt_size_t size); + +/* + * Another log output API. This API is difficult to use than LOG_X API. + */ +void ulog_voutput(rt_uint32_t level, const char *tag, const char *format, va_list args); +void ulog_output(rt_uint32_t level, const char *tag, const char *format, ...); +void ulog_raw(const char *format, ...); + +#ifdef __cplusplus +} +#endif + +#endif /* _ULOG_H_ */ diff --git a/components/utilities/ulog/ulog_def.h b/components/utilities/ulog/ulog_def.h new file mode 100644 index 0000000000..64962fd5c4 --- /dev/null +++ b/components/utilities/ulog/ulog_def.h @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-08-25 armink the first version + */ +#ifndef _ULOG_DEF_H_ +#define _ULOG_DEF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* logger level, the number is compatible for syslog */ +#define LOG_LVL_ASSERT 0 +#define LOG_LVL_ERROR 3 +#define LOG_LVL_WARNING 4 +#define LOG_LVL_INFO 6 +#define LOG_LVL_DBG 7 + +/* the output silent level and all level for filter setting */ +#ifndef ULOG_USING_SYSLOG +#define LOG_FILTER_LVL_SILENT 0 +#define LOG_FILTER_LVL_ALL 7 +#else +#define LOG_FILTER_LVL_SILENT 1 +#define LOG_FILTER_LVL_ALL 255 +#endif /* ULOG_USING_SYSLOG */ + +/* compatible for rtdbg */ +#undef LOG_D +#undef LOG_I +#undef LOG_W +#undef LOG_E +#undef LOG_RAW +#undef DBG_ERROR +#undef DBG_WARNING +#undef DBG_INFO +#undef DBG_LOG +#define DBG_ERROR LOG_LVL_ERROR +#define DBG_WARNING LOG_LVL_WARNING +#define DBG_INFO LOG_LVL_INFO +#define DBG_LOG LOG_LVL_DBG + +#if !defined(LOG_TAG) + /* compatible for rtdbg */ + #if defined(DBG_SECTION_NAME) + #define LOG_TAG DBG_SECTION_NAME + #else + #define LOG_TAG "NO_TAG" + #endif +#endif /* !defined(LOG_TAG) */ + +#if !defined(LOG_LVL) + /* compatible for rtdbg */ + #if defined(DBG_LEVEL) + #define LOG_LVL DBG_LEVEL + #else + #define LOG_LVL LOG_LVL_DBG + #endif +#endif /* !defined(LOG_LVL) */ + +#if (LOG_LVL >= LOG_LVL_DBG) && (ULOG_OUTPUT_LVL >= LOG_LVL_DBG) + #define ulog_d(TAG, ...) ulog_output(LOG_LVL_DBG, TAG, __VA_ARGS__) +#else + #define ulog_d(TAG, ...) +#endif /* (LOG_LVL >= LOG_LVL_DBG) && (ULOG_OUTPUT_LVL >= LOG_LVL_DBG) */ + +#if (LOG_LVL >= LOG_LVL_INFO) && (ULOG_OUTPUT_LVL >= LOG_LVL_INFO) + #define ulog_i(TAG, ...) ulog_output(LOG_LVL_INFO, TAG, __VA_ARGS__) +#else + #define ulog_i(TAG, ...) +#endif /* (LOG_LVL >= LOG_LVL_INFO) && (ULOG_OUTPUT_LVL >= LOG_LVL_INFO) */ + +#if (LOG_LVL >= LOG_LVL_WARNING) && (ULOG_OUTPUT_LVL >= LOG_LVL_WARNING) + #define ulog_w(TAG, ...) ulog_output(LOG_LVL_WARNING, TAG, __VA_ARGS__) +#else + #define ulog_w(TAG, ...) +#endif /* (LOG_LVL >= LOG_LVL_WARNING) && (ULOG_OUTPUT_LVL >= LOG_LVL_WARNING) */ + +#if (LOG_LVL >= LOG_LVL_ERROR) && (ULOG_OUTPUT_LVL >= LOG_LVL_ERROR) + #define ulog_e(TAG, ...) ulog_output(LOG_LVL_ERROR, TAG, __VA_ARGS__) +#else + #define ulog_e(TAG, ...) +#endif /* (LOG_LVL >= LOG_LVL_ERROR) && (ULOG_OUTPUT_LVL >= LOG_LVL_ERROR) */ + +/* assert for developer. */ +#ifdef ULOG_ASSERT_ENABLE + #define ULOG_ASSERT(EXPR) \ + if (!(EXPR)) \ + { \ + ulog_output(LOG_LVL_ASSERT, LOG_TAG, "(%s) has assert failed at %s:%ld.", #EXPR, __FUNCTION__, __LINE__); \ + ulog_flush(); \ + while (1); \ + } +#else + #define ULOG_ASSERT(EXPR) +#endif + +/* ASSERT API definition */ +#if !defined(ASSERT) + #define ASSERT ULOG_ASSERT +#endif + +/* compatible for elog */ +#undef assert +#undef log_e +#undef log_w +#undef log_i +#undef log_d +#undef log_v +#undef ELOG_LVL_ASSERT +#undef ELOG_LVL_ERROR +#undef ELOG_LVL_WARN +#undef ELOG_LVL_INFO +#undef ELOG_LVL_DEBUG +#undef ELOG_LVL_VERBOSE +#define assert ASSERT +#define log_e LOG_E +#define log_w LOG_W +#define log_i LOG_I +#define log_d LOG_D +#define log_v LOG_D +#define log_raw LOG_RAW +#define ELOG_LVL_ASSERT LOG_LVL_ASSERT +#define ELOG_LVL_ERROR LOG_LVL_ERROR +#define ELOG_LVL_WARN LOG_LVL_WARNING +#define ELOG_LVL_INFO LOG_LVL_INFO +#define ELOG_LVL_DEBUG LOG_LVL_DBG +#define ELOG_LVL_VERBOSE LOG_LVL_DBG + +/* setting static output log level */ +#ifndef ULOG_OUTPUT_LVL +#define ULOG_OUTPUT_LVL LOG_LVL_DBG +#endif + +/* buffer size for every line's log */ +#ifndef ULOG_LINE_BUF_SIZE +#define ULOG_LINE_BUF_SIZE 128 +#endif + +/* output filter's tag max length */ +#ifndef ULOG_FILTER_TAG_MAX_LEN +#define ULOG_FILTER_TAG_MAX_LEN 23 +#endif + +/* output filter's keyword max length */ +#ifndef ULOG_FILTER_KW_MAX_LEN +#define ULOG_FILTER_KW_MAX_LEN 15 +#endif + +#ifndef ULOG_NEWLINE_SIGN +#define ULOG_NEWLINE_SIGN "\r\n" +#endif + +#define ULOG_FRAME_MAGIC 0x10 + +struct ulog_frame +{ + /* magic word is 0x10 ('lo') */ + rt_uint32_t magic:8; + rt_uint32_t is_raw:1; + rt_uint32_t log_len:23; + rt_uint32_t level; + const char *log; + const char *tag; +}; +typedef struct ulog_frame *ulog_frame_t; + +struct ulog_backend +{ + char name[RT_NAME_MAX]; + rt_bool_t support_color; + void (*init) (struct ulog_backend *backend); + void (*output)(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log, size_t len); + void (*flush) (struct ulog_backend *backend); + void (*deinit)(struct ulog_backend *backend); + rt_slist_t list; +}; +typedef struct ulog_backend *ulog_backend_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _ULOG_DEF_H_ */ diff --git a/examples/ulog/ulog_example.c b/examples/ulog/ulog_example.c new file mode 100644 index 0000000000..88572f5152 --- /dev/null +++ b/examples/ulog/ulog_example.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2018-08-02 armink the first version + */ + +#include +#include + +#ifndef ULOG_USING_SYSLOG +#define LOG_TAG "example" +#define LOG_LVL LOG_LVL_DBG +#include +#else +#include +#endif /* ULOG_USING_SYSLOG */ + +void ulog_example(void) +{ + int count = 0; + +#ifdef ULOG_USING_SYSLOG + openlog("example1", 0, 0); +#endif + + while (count++ < 50) + { +#ifndef ULOG_USING_SYSLOG + /* output different level log by LOG_X API */ + LOG_D("LOG_D(%d): RT-Thread is an open source IoT operating system from China.", count); + LOG_I("LOG_I(%d): RT-Thread is an open source IoT operating system from China.", count); + LOG_W("LOG_W(%d): RT-Thread is an open source IoT operating system from China.", count); + LOG_E("LOG_E(%d): RT-Thread is an open source IoT operating system from China.", count); + ulog_d("test", "ulog_d(%d): RT-Thread is an open source IoT operating system from China.", count); + ulog_i("test", "ulog_i(%d): RT-Thread is an open source IoT operating system from China.", count); + ulog_w("test", "ulog_w(%d): RT-Thread is an open source IoT operating system from China.", count); + ulog_e("test", "ulog_e(%d): RT-Thread is an open source IoT operating system from China.", count); + +#ifdef ULOG_USING_FILTER + if (count == 20) + { + /* Set the global filer level is INFO. All of DEBUG log will stop output */ + ulog_global_filter_lvl_set(LOG_LVL_INFO); + /* Set the test tag's level filter's level is ERROR. The DEBUG, INFO, WARNING log will stop output. */ + ulog_tag_lvl_filter_set("test", LOG_LVL_ERROR); + } + else if (count == 30) + { + /* Set the example tag's level filter's level is LOG_FILTER_LVL_SILENT, the log enter silent mode. */ + ulog_tag_lvl_filter_set("example", LOG_FILTER_LVL_SILENT); + /* Set the test tag's level filter's level is WARNING. The DEBUG, INFO log will stop output. */ + ulog_tag_lvl_filter_set("test", LOG_LVL_WARNING); + } + else if (count == 40) + { + /* Set the test tag's level filter's level is LOG_FILTER_LVL_ALL. All level log will resume output. */ + ulog_tag_lvl_filter_set("test", LOG_FILTER_LVL_ALL); + /* Set the global filer level is LOG_FILTER_LVL_ALL. All level log will resume output */ + ulog_global_filter_lvl_set(LOG_FILTER_LVL_ALL); + } +#endif /* ULOG_USING_FILTER */ + +#else + /* output different priority log by syslog API */ + syslog(LOG_INFO, "syslog(%d) LOG_INFO: RT-Thread is an open source IoT operating system from China.", count); + syslog(LOG_DEBUG, "syslog(%d) LOG_DEBUG: RT-Thread is an open source IoT operating system from China.", count); + syslog(LOG_WARNING, "syslog(%d) LOG_WARNING: RT-Thread is an open source IoT operating system from China.", count); + syslog(LOG_ERR, "syslog(%d) LOG_ERR: RT-Thread is an open source IoT operating system from China.", count); + syslog(LOG_INFO | LOG_MAIL, "syslog(%d) LOG_INFO | LOG_MAIL: RT-Thread is an open source IoT operating system from China.", count); + syslog(LOG_DEBUG | LOG_DAEMON, "syslog(%d) LOG_DEBUG | LOG_DAEMON: RT-Thread is an open source IoT operating system from China.", count); + syslog(LOG_WARNING | LOG_AUTH, "syslog(%d) LOG_WARNING | LOG_AUTH: RT-Thread is an open source IoT operating system from China.", count); + syslog(LOG_ERR | LOG_SYSLOG, "syslog(%d) LOG_ERR | LOG_SYSLOG: RT-Thread is an open source IoT operating system from China.", count); + + if (count == 20) + { + /* Set log priority mask. Only output ERR and WARNING log. */ + setlogmask(LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING)); + } + else if (count == 40) + { + /* Set log priority mask. The log which level is less than ERROR will stop output. */ + setlogmask(LOG_UPTO(LOG_ERR)); + } +#endif /* ULOG_USING_SYSLOG */ + + rt_thread_delay(rt_tick_from_millisecond(rand() % 1000)); + } +} +MSH_CMD_EXPORT(ulog_example, run ulog example)