/** * @file mce-log.c * Logging functions for Mode Control Entity *

* Copyright (c) 2006 - 2007, 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (c) 2012 - 2020 Jolla Ltd. * Copyright (c) 2020 Open Mobile Platform LLC. *

* @author David Weinehall * @author Simo Piiroinen * * mce is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * mce 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mce. If not, see . */ #ifdef OSSOLOG_COMPILE #include "mce-log.h" #include #include #include #include #include #include static unsigned int logverbosity = LL_WARN; /**< Log verbosity */ static int logtype = MCE_LOG_STDERR; /**< Output for log messages */ static char *logname = NULL; /** Get process identity to use for logging * * Will default to "mce" before mce_log_open() and after mce_log_close(). */ static const char *mce_log_name(void) { return logname ?: "mce"; } /** Get monotonic time as struct timeval */ static void monotime(struct timeval *tv) { struct timespec ts; clock_gettime(CLOCK_BOOTTIME, &ts); TIMESPEC_TO_TIMEVAL(tv, &ts); } static void timestamp(struct timeval *tv) { static struct timeval start, prev; struct timeval diff; monotime(tv); if( !timerisset(&start) ) prev = start = *tv; timersub(tv, &prev, &diff); if( diff.tv_sec >= 4 ) { timersub(tv, &start, &diff); fprintf(stderr, "%s: T+%ld.%03ld %s\n\n", mce_log_name(), (long)diff.tv_sec, (long)(diff.tv_usec/1000), "END OF BURST"); fflush(stderr); start = *tv; } prev = *tv; timersub(tv, &start, tv); } /** Make sure loglevel is in the supported range * * @param loglevel level to check * * @return log level clipped to LL_CRIT ... LL_DEBUG range */ static loglevel_t mce_log_level_clip(loglevel_t loglevel) { if( loglevel <= LL_MINIMUM ) { loglevel = LL_MINIMUM; } else if( loglevel > LL_MAXIMUM ) { loglevel = LL_MAXIMUM; } return loglevel; } /** Get level indication tag to include in stderr logging * * @param loglevel level for the message * * @return level indication string */ static const char *mce_log_level_tag(loglevel_t loglevel) { const char *res = "?"; switch( loglevel ) { case LL_CRUCIAL:res = "T"; break; case LL_EXTRA: res = "X"; break; case LL_CRIT: res = "C"; break; case LL_ERR: res = "E"; break; case LL_WARN: res = "W"; break; case LL_NOTICE: res = "N"; break; case LL_INFO: res = "I"; break; case LL_DEBUG: res = "D"; break; default: break; } return res; } /** Looking at white (ascii) character predicate */ static inline bool mce_log_white_p(const char *str) { int c = (unsigned char)*str; return (c > 0x00) && (c <= 0x20); } /** Looking at non-white (ascii) character predicate */ static inline bool mce_log_black_p(const char *str) { int c = (unsigned char)*str; return c > 0x20; } /** Strip whitespace from log string, similarly to what syslog does */ static char *mce_log_strip_string(char *str) { if( !str ) goto EXIT; char *src = str; char *dst = str; // skip leading white space while( mce_log_white_p(src) ) ++src; for( ;; ) { // copy non-white as-is while( mce_log_black_p(src) ) *dst++ = *src++; // skip internal / trailing white space while( mce_log_white_p(src) ) ++src; if( !*src ) break; // compress internal whitespace to single space *dst++ = ' '; } *dst = 0; EXIT: return str; } /* There is false positive warning during compilation: * * "function might be possible candidate for 'gnu_printf' format attribute" * * Instead of disabling the whole check via -Wno-suggest-attribute=format, * use pragmas locally to sweep the issue under carpet... */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsuggest-attribute=format" void mce_log_unconditional_va(loglevel_t loglevel, const char *const file, const char *const function, const char *const fmt, va_list va) { gchar *msg = 0; g_vasprintf(&msg, fmt, va); if( file && function ) { gchar *tmp = g_strconcat(file, ": ", function, "(): ", mce_log_strip_string(msg), NULL); g_free(msg), msg = tmp; } if (logtype == MCE_LOG_STDERR) { struct timeval tv; timestamp(&tv); fprintf(stderr, "%s: T+%ld.%03ld %s: %s\n", mce_log_name(), (long)tv.tv_sec, (long)(tv.tv_usec/1000), mce_log_level_tag(loglevel), msg); fflush(stderr); } else { /* Use NOTICE priority when reporting LL_EXTRA * and LL_CRUCIAL logging */ switch( loglevel ) { case LL_EXTRA: case LL_CRUCIAL: loglevel = LL_NOTICE; break; default: break; } /* loglevels are subset of syslog priorities, so * we can use loglevel as is for syslog priority */ syslog(loglevel, "%s", msg); } g_free(msg); } #pragma GCC diagnostic pop void mce_log_unconditional(loglevel_t loglevel, const char *const file, const char *const function, const char *const fmt, ...) { va_list va; va_start(va, fmt); mce_log_unconditional_va(loglevel, file, function, fmt, va); va_end(va); } /** * Log debug message with optional filename and function name attached * * @param loglevel The level of severity for this message * @param fmt The format string for this message * @param ... Input to the format string */ void mce_log_file(loglevel_t loglevel, const char *const file, const char *const function, const char *const fmt, ...) { loglevel = mce_log_level_clip(loglevel); if( mce_log_p_(loglevel, file, function) ) { va_list va; va_start(va, fmt); mce_log_unconditional_va(loglevel, file, function, fmt, va); va_end(va); } } /** * Set log verbosity * messages with loglevel higher than or equal to verbosity will be logged * * @param verbosity minimum level for log level */ void mce_log_set_verbosity(int verbosity) { if( verbosity < LL_MINIMUM ) verbosity = LL_MINIMUM; else if( verbosity > LL_MAXIMUM ) verbosity = LL_MAXIMUM; logverbosity = verbosity; } /** Set log verbosity * * @return current verbosity level */ int mce_log_get_verbosity(void) { return logverbosity; } /** * Open log * * @param name identifier to use for log messages * @param facility the log facility; normally LOG_USER or LOG_DAEMON * @param type log type to use; MCE_LOG_STDERR or MCE_LOG_SYSLOG */ void mce_log_open(const char *const name, const int facility, const int type) { logtype = type; logname = g_strdup(name); if (logtype == MCE_LOG_SYSLOG) openlog(name, LOG_PID | LOG_NDELAY, facility); } /** * Close log */ void mce_log_close(void) { /* Logging (to stderr) after this will use default identity */ g_free(logname), logname = 0; /* Any further syslog() calls will automatically reopen */ if (logtype == MCE_LOG_SYSLOG) closelog(); } static GSList *mce_log_patterns = 0; static GHashTable *mce_log_functions = 0; void mce_log_add_pattern(const char *pat) { // NB these are never released by desing mce_log_patterns = g_slist_prepend(mce_log_patterns, strdup(pat)); if( !mce_log_functions ) mce_log_functions = g_hash_table_new_full(g_str_hash, g_str_equal, free, 0); } static bool mce_log_check_pattern(const char *func) { gpointer hit = 0; if( !mce_log_functions ) goto EXIT; if( (hit = g_hash_table_lookup(mce_log_functions, func)) ) goto EXIT; hit = GINT_TO_POINTER(1); for( GSList *item = mce_log_patterns; item; item = item->next ) { const char *pat = item->data; if( fnmatch(pat, func, 0) != 0 ) continue; hit = GINT_TO_POINTER(2); break; } g_hash_table_replace(mce_log_functions, strdup(func), hit); EXIT: return GPOINTER_TO_INT(hit) > 1; } /** * Log level testing predicate * * For testing whether given level of logging is allowed * before spending cpu time for gathering parameters etc * * @param loglevel level of logging we might do * * @return 1 if logging at givel level is enabled, 0 if not */ int mce_log_p_(loglevel_t loglevel, const char *const file, const char *const func) { if( mce_log_functions && file && func ) { char temp[256]; snprintf(temp, sizeof temp, "%s:%s", file, func); if( mce_log_check_pattern(temp) ) return true; } /* LL_EXTRA & LL_CRUCIAL are evaluated as WARNING level */ switch( loglevel ) { case LL_EXTRA: case LL_CRUCIAL: loglevel = LL_WARN;; break; default: break; } return logverbosity >= loglevel; } #endif /* OSSOLOG_COMPILE */