/* * Copyright (c) 2017 rxi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ /* Original source is from https://github.com/rxi/log.c * McuLib integration and extensions: Copyright (c) 2020 Erich Styger */ #include "McuLog.h" #if McuLog_CONFIG_IS_ENABLED #include #include #include #include #include #include #include "McuTimeDate.h" #include "McuUtility.h" #include "McuXFormat.h" #include "McuShell.h" #if McuLog_CONFIG_USE_MUTEX #include "McuRTOS.h" #endif #if McuLog_CONFIG_USE_FILE #include "McuFatFS.h" #endif #if McuLog_CONFIG_USE_RTT_DATA_LOGGER || McuLog_CONFIG_USE_RTT_CONSOLE #include "McuRTT.h" #endif static struct { McuLog_Levels_e level; /* 0 (TRACE) to 5 (FATAL) */ #if McuLog_CONFIG_USE_MUTEX SemaphoreHandle_t McuLog_Mutex; /* built-in FreeRTOS mutex used for lock below */ #endif #if McuLog_CONFIG_USE_MUTEX log_LockFn lock; /* user mutex for synchronization */ void *udata; /* optional data for lock */ #endif McuShell_ConstStdIOType *consoleIo[McuLog_CONFIG_NOF_CONSOLE_LOGGER]; /* I/O for console logging */ bool quiet; /* if console logging is silent/quiet */ #if McuLog_CONFIG_USE_COLOR bool color; /* if using color for terminal */ #endif #if McuLog_CONFIG_USE_FILE McuFatFS_FIL *fp; /* file handle */ McuFatFS_FIL logFile; /* FatFS log file descriptor */ #endif #if McuLog_CONFIG_USE_RTT_DATA_LOGGER bool rttDataLogger; #endif } McuLog_ConfigData; static const char *const level_names[] = { "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" }; #if McuLog_CONFIG_USE_COLOR static const char *const level_colors[] = { /* color codes for messages */ McuShell_ANSI_COLOR_TEXT_BRIGHT_BLUE, /* trace */ McuShell_ANSI_COLOR_TEXT_BRIGHT_GREEN, /* debug */ McuShell_ANSI_COLOR_TEXT_BRIGHT_CYAN, /* info */ McuShell_ANSI_COLOR_TEXT_BRIGHT_YELLOW, /* warn */ McuShell_ANSI_COLOR_TEXT_BRIGHT_RED, /* error */ McuShell_ANSI_COLOR_TEXT_BRIGHT_MAGENTA /* fatal */ }; #endif #if McuLog_CONFIG_USE_MUTEX static void lock(void) { if (McuLog_ConfigData.lock!=NULL) { McuLog_ConfigData.lock(McuLog_ConfigData.udata, true); } } #endif #if McuLog_CONFIG_USE_MUTEX static void unlock(void) { if (McuLog_ConfigData.lock!=NULL) { McuLog_ConfigData.lock(McuLog_ConfigData.udata, false); } } #endif void McuLog_set_console(McuShell_ConstStdIOType *io, uint8_t index) { assert(index1 && !(file[pos-1]=='/' || file[pos-1]=='\\')) { /* scan back to find the last separator */ pos--; } if (pos==1 && !(file[pos-1]=='/' || file[pos-1]=='\\')) { /* no separator at all? */ pos = 0; /* no separator, start from the beginning */ } p = (const unsigned char*)&file[pos]; } OutString(p, outchar, param); #else OutString((unsigned char*)file, outchar, param); #endif /* line number */ buf[0] = '\0'; McuUtility_chcat(buf, sizeof(buf), ':'); McuUtility_strcatNum32u(buf, sizeof(buf), line); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)": "); OutString(buf, outchar, param); } #if McuLog_CONFIG_USE_PRINTF_STYLE void McuLog_ChannelLog(uint8_t channel, McuLog_Levels_e level, const char *file, int line, const char *fmt, ...) { #if McuLog_CONFIG_LOG_TIMESTAMP_DATE DATEREC date; #define DATE_PTR &date #else #define DATE_PTR NULL #endif #if McuLog_CONFIG_LOG_TIMESTAMP_TIME TIMEREC time; #define TIME_PTR &time #else #define TIME_PTR NULL #endif va_list list; if (level < McuLog_ConfigData.level) { return; } if (channel>=McuLog_CONFIG_NOF_CONSOLE_LOGGER) { return; /* wrong channel number? */ } #if McuLog_CONFIG_USE_MUTEX lock(); /* Acquire lock */ #endif #if McuLog_CONFIG_LOG_TIMESTAMP_DATE || McuLog_CONFIG_LOG_TIMESTAMP_TIME (void)McuTimeDate_GetTimeDate(TIME_PTR, DATE_PTR); /* Get current date and time */ #endif if (!McuLog_ConfigData.quiet) { if(McuLog_ConfigData.consoleIo[channel]!=NULL) { /* log to console */ LogHeader(DATE_PTR, TIME_PTR, level, true, file, line, OutputCharFctConsole, McuLog_ConfigData.consoleIo[channel]->stdErr); /* open argument list */ va_start(list, fmt); McuXFormat_xvformat(OutputCharFctConsole, McuLog_ConfigData.consoleIo[channel]->stdErr, fmt, list); va_end(list); OutString((unsigned char *)"\n", OutputCharFctConsole, McuLog_ConfigData.consoleIo[channel]->stdErr); } } #if McuLog_CONFIG_USE_MUTEX unlock(); /* Release lock */ #endif } #endif #if McuLog_CONFIG_USE_PRINTF_STYLE void McuLog_log(McuLog_Levels_e level, const char *file, int line, const char *fmt, ...) { #if McuLog_CONFIG_LOG_TIMESTAMP_DATE DATEREC date; #define DATE_PTR &date #else #define DATE_PTR NULL #endif #if McuLog_CONFIG_LOG_TIMESTAMP_TIME TIMEREC time; #define TIME_PTR &time #else #define TIME_PTR NULL #endif va_list list; if (level < McuLog_ConfigData.level) { return; } #if McuLog_CONFIG_USE_MUTEX lock(); /* Acquire lock */ #endif #if McuLog_CONFIG_LOG_TIMESTAMP_DATE || McuLog_CONFIG_LOG_TIMESTAMP_TIME (void)McuTimeDate_GetTimeDate(TIME_PTR, DATE_PTR); /* Get current date and time */ #endif if (!McuLog_ConfigData.quiet) { for(int i=0; istdErr); /* open argument list */ va_start(list, fmt); McuXFormat_xvformat(OutputCharFctConsole, McuLog_ConfigData.consoleIo[i]->stdErr, fmt, list); va_end(list); OutString((unsigned char *)"\n", OutputCharFctConsole, McuLog_ConfigData.consoleIo[i]->stdErr); } } /* for */ } #if McuLog_CONFIG_USE_RTT_DATA_LOGGER /* log to RTT Data Logger */ if (McuLog_ConfigData.rttDataLogger) { LogHeader(DATE_PTR, TIME_PTR, level, false, file, line, OutputCharRttLoggerFct, NULL); /* open argument list */ va_start(list, fmt); McuXFormat_xvformat(OutputCharRttLoggerFct, NULL, fmt, list); va_end(list); OutString((unsigned char *)"\n", OutputCharRttLoggerFct, NULL); } #endif #if McuLog_CONFIG_USE_FILE /* Log to file */ if (McuLog_ConfigData.fp) { LogHeader(DATE_PTR, &time, level, false, file, line, OutputCharFctFile, McuLog_ConfigData.fp); /* open argument list */ va_start(list, fmt); McuXFormat_xvformat(OutputCharFctFile, McuLog_ConfigData.fp, fmt, list); va_end(list); OutString((unsigned char *)"\n", OutputCharFctFile, McuLog_ConfigData.fp); f_sync(McuLog_ConfigData.fp); } #endif #if McuLog_CONFIG_USE_MUTEX unlock(); /* Release lock */ #endif } #endif void McuLog_logString(McuLog_Levels_e level, const char *file, int line, const char *str) { #if McuLog_CONFIG_LOG_TIMESTAMP_DATE DATEREC date; #define DATE_PTR &date #else #define DATE_PTR NULL #endif #if McuLog_CONFIG_LOG_TIMESTAMP_TIME TIMEREC time; #define TIME_PTR &time #else #define TIME_PTR NULL #endif if (level < McuLog_ConfigData.level) { return; } #if McuLog_CONFIG_USE_MUTEX lock(); /* Acquire lock */ #endif #if McuLog_CONFIG_LOG_TIMESTAMP_DATE || McuLog_CONFIG_LOG_TIMESTAMP_TIME (void)McuTimeDate_GetTimeDate(TIME_PTR, DATE_PTR); /* Get current date and time */ #endif if (!McuLog_ConfigData.quiet) { for(int i=0; istdErr); OutString((unsigned char *)str, OutputCharFctConsole, McuLog_ConfigData.consoleIo[i]->stdErr); OutString((unsigned char *)"\n", OutputCharFctConsole, McuLog_ConfigData.consoleIo[i]->stdErr); } } /* for */ } #if McuLog_CONFIG_USE_RTT_DATA_LOGGER /* log to RTT Data Logger */ if (McuLog_ConfigData.rttDataLogger) { LogHeader(DATE_PTR, TIME_PTR, level, false, file, line, OutputCharRttLoggerFct, NULL); OutString((unsigned char *)str, OutputCharRttLoggerFct, NULL); OutString((unsigned char *)"\n", OutputCharRttLoggerFct, NULL); } #endif #if McuLog_CONFIG_USE_FILE /* Log to file */ if (McuLog_ConfigData.fp) { LogHeader(DATE_PTR, &time, level, false, file, line, OutputCharFctFile, McuLog_ConfigData.fp); OutString((unsigned char *)str, OutputCharFctFile, McuLog_ConfigData.fp); OutString((unsigned char *)"\n", OutputCharFctFile, McuLog_ConfigData.fp); f_sync(McuLog_ConfigData.fp); } #endif #if McuLog_CONFIG_USE_MUTEX unlock(); /* Release lock */ #endif } #if McuLog_CONFIG_USE_MUTEX static void LockUnlockCallback(void *data, bool lock) { (void)data; /* unused */ if (lock) { (void)xSemaphoreTakeRecursive(McuLog_ConfigData.McuLog_Mutex, portMAX_DELAY); } else { (void)xSemaphoreGiveRecursive(McuLog_ConfigData.McuLog_Mutex); } } #endif #if McuLog_CONFIG_PARSE_COMMAND_ENABLED static uint8_t PrintStatus(const McuShell_StdIOType *io) { uint8_t buf[16]; McuShell_SendStatusStr((unsigned char*)"McuLog", (unsigned char*)"Log status\r\n", io->stdOut); McuUtility_Num8uToStr(buf, sizeof(buf), McuLog_CONFIG_NOF_CONSOLE_LOGGER); McuUtility_strcat(buf, sizeof(buf), (unsigned char*)" channel\r\n"); McuShell_SendStatusStr((unsigned char*)" console", buf, io->stdOut); #if McuLog_CONFIG_USE_FILE McuShell_SendStatusStr((unsigned char*)" file", McuLog_ConfigData.fp!=NULL?(unsigned char*)"yes\r\n":(unsigned char*)"no\r\n", io->stdOut); #endif #if McuLog_CONFIG_USE_RTT_DATA_LOGGER McuShell_SendStatusStr((unsigned char*)" rttlogger", McuLog_ConfigData.rttDataLogger?(unsigned char*)"on\r\n":(unsigned char*)"off\r\n", io->stdOut); #endif #if McuLog_CONFIG_USE_MUTEX McuShell_SendStatusStr((unsigned char*)" lock", McuLog_ConfigData.lock!=NULL?(unsigned char*)"yes\r\n":(unsigned char*)"no\r\n", io->stdOut); #endif McuShell_SendStatusStr((unsigned char*)" level", (unsigned char*)level_names[McuLog_ConfigData.level], io->stdOut); McuShell_SendStr((unsigned char*)" (", io->stdOut); McuShell_SendNum8u(McuLog_ConfigData.level, io->stdOut); McuShell_SendStr((unsigned char*)"), logging for this level and higher\r\n", io->stdOut); #if McuLog_CONFIG_USE_COLOR McuShell_SendStatusStr((unsigned char*)" color", McuLog_ConfigData.color?(unsigned char*)"on\r\n":(unsigned char*)"off\r\n", io->stdOut); #endif return ERR_OK; } #endif /* McuLog_CONFIG_PARSE_COMMAND_ENABLED */ #if McuLog_CONFIG_PARSE_COMMAND_ENABLED static uint8_t PrintHelp(const McuShell_StdIOType *io) { McuShell_SendHelpStr((unsigned char*)"McuLog", (unsigned char*)"Group of McuLog commands\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Print help or status information\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" level ", (unsigned char*)"Set log level, 0 (TRACE), 1 (DEBUG), 2 (INFO), 3 (WARN), 4 (ERROR), 5 (FATAL)\r\n", io->stdOut); McuShell_SendHelpStr((unsigned char*)" quiet ", (unsigned char*)"Set quiet mode for console\r\n", io->stdOut); #if McuLog_CONFIG_USE_COLOR McuShell_SendHelpStr((unsigned char*)" color ", (unsigned char*)"Set color mode\r\n", io->stdOut); #endif #if McuLog_CONFIG_USE_RTT_DATA_LOGGER McuShell_SendHelpStr((unsigned char*)" rttlogger ", (unsigned char*)"Set rtt data logger mode\r\n", io->stdOut); #endif return ERR_OK; } #endif /* McuLog_CONFIG_PARSE_COMMAND_ENABLED */ #if McuLog_CONFIG_PARSE_COMMAND_ENABLED uint8_t McuLog_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) { uint8_t res = ERR_OK; const unsigned char *p; if (McuUtility_strcmp((char*)cmd, McuShell_CMD_HELP) == 0 || McuUtility_strcmp((char*)cmd, "McuLog help") == 0) { *handled = TRUE; return PrintHelp(io); } else if ( (McuUtility_strcmp((char*)cmd, McuShell_CMD_STATUS)==0) || (McuUtility_strcmp((char*)cmd, "McuLog status")==0) ) { *handled = TRUE; res = PrintStatus(io); } else if (McuUtility_strcmp((char*)cmd, "McuLog quiet on")==0) { *handled = TRUE; McuLog_set_quiet(true); } else if (McuUtility_strcmp((char*)cmd, "McuLog quiet off")==0) { *handled = TRUE; McuLog_set_quiet(false); #if McuLog_CONFIG_USE_COLOR } else if (McuUtility_strcmp((char*)cmd, "McuLog color on")==0) { *handled = TRUE; McuLog_set_color(true); } else if (McuUtility_strcmp((char*)cmd, "McuLog color off")==0) { *handled = TRUE; McuLog_set_color(false); #endif } else if (McuUtility_strncmp((char*)cmd, "McuLog level ", sizeof("McuLog level ")-1)==0) { uint8_t level; *handled = TRUE; p = cmd+sizeof("McuLog level ")-1; if (McuUtility_ScanDecimal8uNumber(&p, &level)==ERR_OK && level<=McuLog_FATAL) { McuLog_set_level(level); } } return res; } #endif /* McuLog_CONFIG_PARSE_COMMAND_ENABLED */ #if McuLog_CONFIG_USE_RTT_DATA_LOGGER static char McuLog_RttUpBuffer[McuLog_CONFIG_RTT_DATA_LOGGER_BUFFER_SIZE]; #endif void McuLog_Init(void) { #if McuLog_CONFIG_USE_MUTEX McuLog_ConfigData.McuLog_Mutex = xSemaphoreCreateRecursiveMutex(); if (McuLog_ConfigData.McuLog_Mutex==NULL) { /* semaphore creation failed */ for(;;) {} /* error, not enough memory? */ } vQueueAddToRegistry(McuLog_ConfigData.McuLog_Mutex, "McuLog_Mutex"); McuLog_set_lock(LockUnlockCallback); #endif #if McuLog_CONFIG_USE_COLOR McuLog_set_color(true); #endif #if McuLog_CONFIG_USE_RTT_DATA_LOGGER McuLog_set_rtt_logger(true); #endif McuLog_ConfigData.consoleIo[0] = McuShell_GetStdio(); /* default */ McuLog_set_level(McuLog_CONFIG_DEFAULT_LEVEL); /* default level */ #if McuLog_CONFIG_USE_RTT_DATA_LOGGER #if McuLib_CONFIG_SDK_USE_FREERTOS && configUSE_SEGGER_SYSTEM_VIEWER_HOOKS && McuSystemView_CONFIG_RTT_CHANNEL==McuLog_RTT_DATA_LOGGER_CHANNEL #error "Both RTT Logger and SystemViewer are using the same channel! Change McuSystemView_CONFIG_RTT_CHANNEL to a different value." #endif SEGGER_RTT_ConfigUpBuffer(McuLog_RTT_DATA_LOGGER_CHANNEL, "Logger", &McuLog_RttUpBuffer[0], sizeof(McuLog_RttUpBuffer), McuLog_CONFIG_RTT_DATA_LOGGER_CHANNEL_MODE); #endif for(int i=0; i