From 247772df3bc333f32d8372114a4337df29548cc1 Mon Sep 17 00:00:00 2001 From: Grissiom Date: Wed, 26 Jun 2013 10:10:26 +0800 Subject: [PATCH] add logtrace component Logtrace is a component that could output the log into either a device or a file. It has the ability to filter the log messages according to a pre-module level. Define RT_USING_LOGTRACE in rtconfig.h if you want to have a try. --- components/logtrace/SConscript | 9 + components/logtrace/log_file.c | 115 +++++++++ components/logtrace/log_trace.c | 427 ++++++++++++++++++++++++++++++++ components/logtrace/log_trace.h | 151 +++++++++++ 4 files changed, 702 insertions(+) create mode 100644 components/logtrace/SConscript create mode 100644 components/logtrace/log_file.c create mode 100644 components/logtrace/log_trace.c create mode 100644 components/logtrace/log_trace.h diff --git a/components/logtrace/SConscript b/components/logtrace/SConscript new file mode 100644 index 0000000000..d8be5ddc27 --- /dev/null +++ b/components/logtrace/SConscript @@ -0,0 +1,9 @@ +from building import * + +cwd = GetCurrentDir() +src = Glob('*.c') +CPPPATH = [cwd] + +group = DefineGroup('LogTrace', src, depend = ['RT_USING_LOGTRACE'], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/logtrace/log_file.c b/components/logtrace/log_file.c new file mode 100644 index 0000000000..c5815536b2 --- /dev/null +++ b/components/logtrace/log_file.c @@ -0,0 +1,115 @@ +/* + * File : log_file.c + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2013, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * Bernard the first version + * 2013-06-26 Grissiom refactor + */ + +#ifdef RT_USING_DFS + +#include +#include +#include + +struct file_device +{ + struct rt_device parent; + + int fd; + char *filename; +}; + +/* file device for log trace */ +static struct file_device _file_device; + +/* common device interface */ +rt_err_t fdevice_open(rt_device_t dev, rt_uint16_t oflag) +{ + int fd; + struct file_device *file = (struct file_device *)dev; + if (file->fd >= 0) return -RT_EBUSY; + + fd = open(file->filename, O_RDONLY, 0); + if (fd >= 0) + { + close(fd); + + /* file exists */ + fd = open(file->filename, O_WRONLY | O_APPEND, 0); + } + else + { + /* file not exists */ + fd = open(file->filename, O_WRONLY | O_CREAT, 0); + } + + file->fd = fd; + return RT_EOK; +} + +rt_err_t fdevice_close(rt_device_t dev) +{ + rt_err_t result; + + struct file_device *file = (struct file_device *)dev; + if (file->fd < 0) return -RT_EBUSY; + + result = close(file->fd); + if (result == 0) + { + file->fd = -1; + } + + return result; +} + +rt_size_t fdevice_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) +{ + struct file_device *file = (struct file_device *)dev; + if (file->fd < 0) return 0; + + return write(file->fd, buffer, size); +} + +void log_trace_file_init(const char *filename) +{ + rt_device_t device; + + device = rt_device_find("logfile"); + if (device == RT_NULL) + { + rt_memset(&_file_device, 0x00, sizeof(_file_device)); + + _file_device.parent.type = RT_Device_Class_Char; + + _file_device.parent.init = RT_NULL; + _file_device.parent.open = fdevice_open; + _file_device.parent.close = fdevice_close; + _file_device.parent.write = fdevice_write; + + rt_device_register(&_file_device.parent, "logfile", O_RDWR); + } + + _file_device.filename = rt_strdup(filename); + _file_device.fd = -1; +} + +#endif // RT_USING_DFS diff --git a/components/logtrace/log_trace.c b/components/logtrace/log_trace.c new file mode 100644 index 0000000000..7b17874445 --- /dev/null +++ b/components/logtrace/log_trace.c @@ -0,0 +1,427 @@ +/* + * File : log_trace.c + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2013, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * Bernard the first version + * 2013-06-26 Grissiom refactor + */ + +#include +#include +#include +#include "log_trace.h" + +#ifdef RT_USING_FINSH +#include +#else +#define FINSH_FUNCTION_EXPORT(...) +#define FINSH_FUNCTION_EXPORT_ALIAS(...) +#endif + +/* log pseudo device */ +static struct rt_device _log_device; + +static rt_device_t _traceout_device = RT_NULL; + +/* define a default lg session. The name is empty. */ +static struct log_trace_session _def_session = {{0}, LOG_TRACE_LEVEL_INFO}; +static struct log_trace_session *_the_sessions[LOG_TRACE_MAX_SESSION] = {&_def_session}; +/* there is a default session at least */ +static rt_uint16_t _the_sess_nr = 1; + +rt_inline int _idname_len(log_trace_idnum_t id) +{ + /* little endian */ + if ((id & 0x000000FF) == 0) + return 0; + if ((id & 0x0000FF00) == 0) + return 1; + if ((id & 0x00FF0000) == 0) + return 2; + if ((id & 0xFF000000) == 0) + return 3; +#ifndef LOG_TRACE_USE_LONGNAME + return 4; +#else + { + rt_uint32_t id2 = id >> 32; + if ((id2 & 0x000000FF) == 0) + return 4; + if ((id2 & 0x0000FF00) == 0) + return 5; + if ((id2 & 0x00FF0000) == 0) + return 6; + if ((id2 & 0xFF000000) == 0) + return 7; + return 8; + } +#endif +} + +/* lookup the session according to name. + * + * @param len is the length of the name + * @return the pointer to the named session. RT_NULL when there is no such a + * session. + */ +static struct log_trace_session* _lg_lookup_session(log_trace_idnum_t num) +{ + static struct log_trace_session *_cache = &_def_session; + rt_uint16_t first, last; + + if (_cache->id.num == num) + return _cache; + + first = 0; + last = _the_sess_nr; + do { + unsigned int i = (first + last)/2; + if (_the_sessions[i]->id.num == num) + { + /* there is no need to protect the _cache because write a pointer + * is atomic. So we cannot get a invalid pointer. The worst thing + * could happen is there is an interrupt in the read/modify/write + * process and we wrote the old one to _cache. But it doesn't harm + * a lot because it will be flushed in the next time. */ + _cache = _the_sessions[i]; + return _the_sessions[i]; + } + else if (_the_sessions[i]->id.num > num) + { + last = i; + } + else // _the_sessions[i]->id.num < num + { + first = i; + } + } while (first != last-1); + + return RT_NULL; +} + +rt_err_t log_trace_register_session(struct log_trace_session *session) +{ + unsigned int lvl, i; + + if (_the_sess_nr == LOG_TRACE_MAX_SESSION) + return -RT_EFULL; + + if (session == RT_NULL) + return RT_EOK; + + lvl = rt_hw_interrupt_disable(); + /* inserting the sessions in ascending order. + * + * this might take relatively long time. But since the register should only + * happen when initialize the whole system, this should not be a matter. */ + for (i = 0; i < _the_sess_nr; i++) + { + if (_the_sessions[i]->id.num > session->id.num) + { + rt_memmove(_the_sessions+i, _the_sessions+i+1, _the_sess_nr-i); + _the_sessions[i] = session; + break; + } + else if (_the_sessions[i]->id.num == session->id.num) + { + rt_kprintf("registering session 0x%p twice\n", session); + rt_hw_interrupt_enable(lvl); + return -RT_ERROR; + } + } + if (i == _the_sess_nr) + _the_sessions[i] = session; + _the_sess_nr++; + rt_hw_interrupt_enable(lvl); + + return RT_EOK; +} + +struct log_trace_session* log_trace_session_find(const char *name) +{ + union log_trace_id *idp; + + RT_ASSERT(name); + idp = (union log_trace_id*)name; + return _lg_lookup_session(idp->num); +} + +void log_trace_set_level(rt_uint8_t level) +{ + _def_session.lvl = level; +} +FINSH_FUNCTION_EXPORT_ALIAS(log_trace_set_level, log_level, set the filter level of log trace); + +void log_trace_session_set_level(struct log_trace_session *sess, rt_uint8_t level) +{ + RT_ASSERT(sess); + sess->lvl = level; +} + +/* parse the level info in fmt + * + * @param flen the length of the format. + * @param lvlp the pointer to level. It will store the level in the memory the + * lvlp points to. The default value is LOG_TRACE_LEVEL_DEFAULT. + * @return the number of char it scaned. + */ +static rt_size_t _lg_parse_lvl(const char *fmt, rt_size_t flen, int *lvlp) +{ + RT_ASSERT(fmt); + RT_ASSERT(lvlp); + + /* setup default value */ + *lvlp = LOG_TRACE_LEVEL_DEFAULT; + + if (flen < 3) + { + return 0; + } + + if (fmt[0] == '<' && fmt[2] == '>') + { + *lvlp = fmt[1] - '0'; + return 3; + } + return 0; +} + +/* parse the header in fmt + * + * @param flen the length of the format. + * @param sessp the pointer of pointer to the session. It will store the + * session pointer in the memory the sessp points to. When failed to + * find the session, it will be setted to the default session. + * @return the number of char it scaned, i.e., the length of header. + */ +static rt_size_t _lg_parse_session( + const char *fmt, rt_size_t flen, struct log_trace_session **sessp) +{ + unsigned int i; + struct log_trace_session *tmpsess; + union log_trace_id id; + + RT_ASSERT(fmt); + RT_ASSERT(sessp); + + /* setup default value */ + *sessp = &_def_session; + + /* no name space left */ + if (flen < sizeof(id) + 2) + return 0; + + if (fmt[0] != '[') + return 0; + + id.num = 0; + /* skip '[' and convert the string to id number. */ + for (i = 1; fmt[i] != ']'; i++) + { + if (i - 1 == sizeof(id)) + return 0; + id.name[i-1] = fmt[i]; + } + tmpsess = _lg_lookup_session(id.num); + if (tmpsess != RT_NULL) + { + *sessp = tmpsess; + /* only count the header length when we found the session. So + * the wrong [name] will be printed out. */ + return i + 1; + } + + return 0; +} + +static void _lg_fmtout( + struct log_trace_session *session, const char *fmt, va_list argptr) +{ + /* 1 for ']' */ + static char _trace_buf[1+LOG_TRACE_BUFSZ]; + char *ptr; + rt_size_t length; + + RT_ASSERT(session); + RT_ASSERT(fmt); + + rt_snprintf(_trace_buf, sizeof(_trace_buf), "[%08x][", rt_tick_get()); + if (_traceout_device != RT_NULL) + { + rt_device_write(_traceout_device, -1, _trace_buf, 11); + rt_device_write(_traceout_device, -1, + session->id.name, _idname_len(session->id.num)); + } + + _trace_buf[0] = ']'; + ptr = &_trace_buf[1]; + length = vsnprintf(ptr, LOG_TRACE_BUFSZ, fmt, argptr); + + if (length >= LOG_TRACE_BUFSZ) + length = LOG_TRACE_BUFSZ - 1; + + if (_traceout_device != RT_NULL) + { + rt_device_write(_traceout_device, -1, _trace_buf, length + 1); + } +} + +void log_trace(const char *fmt, ...) +{ + va_list args; + int level; + struct log_trace_session *session; + + RT_ASSERT(fmt); + + fmt += _lg_parse_lvl(fmt, strlen(fmt), &level); + fmt += _lg_parse_session(fmt, strlen(fmt), &session); + + /* filter by level */ + if (level > session->lvl) + return; + + va_start(args, fmt); + _lg_fmtout(session, fmt, args); + va_end(args); +} +FINSH_FUNCTION_EXPORT(log_trace, log trace); + +void log_session(struct log_trace_session *session, const char *fmt, ...) +{ + va_list args; + int level; + + RT_ASSERT(session); + RT_ASSERT(fmt); + + fmt += _lg_parse_lvl(fmt, strlen(fmt), &level); + if (level > session->lvl) + return; + + va_start(args, fmt); + _lg_fmtout(session, fmt, args); + va_end(args); +} + +void log_trace_flush(void) +{ + rt_device_control(_traceout_device, LOG_TRACE_CTRL_FLUSH, RT_NULL); +} +FINSH_FUNCTION_EXPORT_ALIAS(log_trace_flush, log_flush, flush log on the buffer); + +/* RT-Thread common device interface */ +static rt_size_t _log_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) +{ + char c; + int level; + rt_size_t head_len; + const char *ptr = buffer; + struct log_trace_session *session; + + head_len = _lg_parse_lvl(ptr, size, &level); + head_len += _lg_parse_session(ptr+head_len, size-head_len, &session); + + /* filter by level */ + if (level > session->lvl) + return size; + + if (_traceout_device != RT_NULL) + { + c = '['; + rt_device_write(_traceout_device, -1, &c, 1); + rt_device_write(_traceout_device, -1, session->id.name, _idname_len(session->id.num)); + c = ']'; + rt_device_write(_traceout_device, -1, &c, 1); + rt_device_write(_traceout_device, -1, ((char*)buffer)+head_len, size - head_len); + } + + return size; +} + +static rt_err_t _log_control(rt_device_t dev, rt_uint8_t cmd, void *arg) +{ + if (_traceout_device == RT_NULL) return -RT_ERROR; + + return rt_device_control(_traceout_device, cmd, arg); +} + +void log_trace_init(void) +{ + rt_memset(&_log_device, 0x00, sizeof(_log_device)); + + _log_device.type = RT_Device_Class_Char; + _log_device.init = RT_NULL; + _log_device.open = RT_NULL; + _log_device.close = RT_NULL; + _log_device.read = RT_NULL; + _log_device.write = _log_write; + _log_device.control = _log_control; + + /* no indication and complete callback */ + _log_device.rx_indicate = RT_NULL; + _log_device.tx_complete = RT_NULL; + + rt_device_register(&_log_device, "log", RT_DEVICE_FLAG_STREAM | RT_DEVICE_FLAG_RDWR); + return ; +} + +rt_device_t log_trace_get_device(void) +{ + return _traceout_device; +} + +rt_err_t log_trace_set_device(const char *device_name) +{ + struct rt_device *output_device; + + /* find out output device */ + output_device = rt_device_find(device_name); + if (output_device != RT_NULL) + { + rt_err_t result; + + /* open device */ + result = rt_device_open(output_device, RT_DEVICE_FLAG_STREAM | RT_DEVICE_FLAG_RDWR); + if (result != RT_EOK) + { + rt_kprintf("Open trace device failed.\n"); + return -RT_ERROR; + } + } + + /* set trace out device */ + if (_traceout_device != RT_NULL) + rt_device_close(_traceout_device); + _traceout_device = output_device; + + return RT_EOK; +} +FINSH_FUNCTION_EXPORT_ALIAS(log_trace_set_device, log_device, set device of log trace); + +#ifdef RT_USING_DFS +void log_trace_set_file(const char *filename) +{ + log_trace_file_init(filename); + log_trace_set_device("logfile"); +} +FINSH_FUNCTION_EXPORT_ALIAS(log_trace_set_file, log_file, set output filename of log trace); +#endif // RT_USING_DFS + diff --git a/components/logtrace/log_trace.h b/components/logtrace/log_trace.h new file mode 100644 index 0000000000..d5a028f553 --- /dev/null +++ b/components/logtrace/log_trace.h @@ -0,0 +1,151 @@ +/* + * File : log_trace.h + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2013, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * Bernard the first version + * 2013-06-26 Grissiom refactor + */ + +#ifndef __LOG_TRACE_H__ +#define __LOG_TRACE_H__ + +#include + +#define LOG_TRACE_LEVEL_MASK 0x0f +#define LOG_TRACE_LEVEL_NOTRACE 0x00 +#define LOG_TRACE_LEVEL_ERROR 0x01 +#define LOG_TRACE_LEVEL_WARNING 0x02 +#define LOG_TRACE_LEVEL_INFO 0x03 +#define LOG_TRACE_LEVEL_DEBUG 0x04 +#define LOG_TRACE_LEVEL_ALL 0x0f + +#ifndef LOG_TRACE_LEVEL_DEFAULT +#define LOG_TRACE_LEVEL_DEFAULT LOG_TRACE_LEVEL_INFO +#endif + +#define LOG_TRACE_ERROR "<1>" +#define LOG_TRACE_WARNING "<2>" +#define LOG_TRACE_INFO "<3>" +#define LOG_TRACE_DEBUG "<4>" + +#define LOG_TRACE_OPT_NOTS 0x10 /* no timestamp */ +#define LOG_TRACE_OPT_LN 0x20 /* terminate the current line */ + +#define LOG_TRACE_CTRL_FLUSH 0x10 +#define LOG_TRACE_CTRL_RESET 0x11 +#define LOG_TRACE_CTRL_DUMP 0x12 + +//#define LOG_TRACE_USE_LONGNAME + +#ifndef LOG_TRACE_BUFSZ +#define LOG_TRACE_BUFSZ RT_CONSOLEBUF_SIZE +#endif + +/** maximum number of sessions that can be registered to the log_trace system + */ +#ifndef LOG_TRACE_MAX_SESSION +#define LOG_TRACE_MAX_SESSION 16 +#endif + +#ifdef LOG_TRACE_USE_LONGNAME +typedef rt_uint64_t log_trace_idnum_t; +#else +typedef rt_uint32_t log_trace_idnum_t; +#endif + +/* use a integer to represent a string to avoid strcmp. Even 4 chars + * should be enough for most of the cases. + * NOTE: take care when converting the name string to id number, you + * can trapped in endianness. + */ +union log_trace_id { + char name[sizeof(log_trace_idnum_t)]; + log_trace_idnum_t num; +}; + +struct log_trace_session +{ + union log_trace_id id; + rt_uint8_t lvl; +}; + +/** initialize the log_trace system */ +void log_trace_init(void); + +/** register a session. + * + * @return RT_EOK on success. -RT_EFULL if there is no space for registration. + */ +rt_err_t log_trace_register_session(struct log_trace_session *session); + +/** find a session with name + * + * The name is not enclosed by parenthesis([]). + * + * @return RT_NULL if there is no such a session. + */ +struct log_trace_session* log_trace_session_find(const char *name); + +/** set the log level of the default session. */ +void log_trace_set_level(rt_uint8_t level); + +/** set the log level of the session */ +void log_trace_session_set_level( + struct log_trace_session *session, rt_uint8_t level); + +/** log according to the format + * + * the format of log_trace is: "[name]log messages". It will output + * "[systick][name]log messages" When there is no session named name, the + * default session will be used. + * + * We have keep the level tag before the name tag because we don't print put + * the level tag to the output and it's easier to implement if we place the + * level tag in the very beginning. + */ +void log_trace(const char *fmt, ...); + +/** log into session. + * + * the format of log_trace is: "log messages". It will output + * "[systick][name]log messages". The name is the name of the session. It is + * faster than bare log_trace. + */ +void log_session(struct log_trace_session *session, const char *fmt, ...); + +/* here comes the global part. All sessions share the some output backend. */ + +/** get the backend device */ +rt_device_t log_trace_get_device(void); + +/** set the backend device */ +rt_err_t log_trace_set_device(const char *device_name); + +void log_trace_flush(void); + +/** set the backend to file */ +void log_trace_set_file(const char *filename); + +/* log trace for NAND Flash */ +void log_trace_nand_init(const char *nand_device); +void log_trace_file_init(const char *filename); + +#endif +