From 51afed2fe0070441d5c6a84f1aa81610b9878e47 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Fri, 8 Nov 2019 09:11:37 -0500 Subject: [PATCH] More advanced mpw-internal logging mechanism. Logging now happens at the mpw-core level, by default using sinks that can be registered. For iOS we forward log messages to os_log for unified logging. We also keep a record of log messages for future retrieval in a log view. This obsoletes and removes Pearl's logger entirely. --- .../Source/MasterPassword-Prefix.pch | 97 ++++++------ platform-independent/c/core/src/mpw-util.c | 142 +++++++++++++----- platform-independent/c/core/src/mpw-util.h | 57 ++++--- 3 files changed, 184 insertions(+), 112 deletions(-) diff --git a/platform-darwin/Source/MasterPassword-Prefix.pch b/platform-darwin/Source/MasterPassword-Prefix.pch index 742bd70d..f20ec33e 100644 --- a/platform-darwin/Source/MasterPassword-Prefix.pch +++ b/platform-darwin/Source/MasterPassword-Prefix.pch @@ -16,63 +16,60 @@ // LICENSE file. Alternatively, see . //============================================================================== -#import -#import "Pearl-Prefix.pch" +#define MP_LIBS_BEGIN \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Weverything\"") +#define MP_LIBS_END \ +_Pragma("clang diagnostic pop") + +MP_LIBS_BEGIN +#include +#include +#include +MP_LIBS_END + +#define mpw_log_os(level, file, line, function, format, ...) \ + do { \ + if (mpw_verbosity < level) { \ + break; \ + } \ + \ + switch (level) { \ + case LogLevelTrace: \ + os_log_debug( OS_LOG_DEFAULT, "%30s:%-3ld TRC | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ + break; \ + case LogLevelDebug: \ + os_log_debug( OS_LOG_DEFAULT, "%30s:%-3ld DBG | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ + break; \ + case LogLevelInfo: \ + os_log_info( OS_LOG_DEFAULT, "%30s:%-3ld INF | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ + break; \ + case LogLevelWarning: \ + os_log( OS_LOG_DEFAULT, "%30s:%-3ld WRN | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ + break; \ + case LogLevelError: \ + os_log_error( OS_LOG_DEFAULT, "%30s:%-3ld ERR | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ + break; \ + case LogLevelFatal: \ + os_log_fault( OS_LOG_DEFAULT, "%30s:%-3ld FTL | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ + break; \ + } \ + \ + mpw_log_sink( level, file, line, function, format, ##__VA_ARGS__ ); \ + } while (0) + +#define MPW_LOG mpw_log_os + +#include "mpw-util.h" #ifdef __OBJC__ +#import "Pearl-Prefix.pch" + #if TARGET_OS_IOS #import #elif TARGET_OS_OSX #import #endif -#import -#ifdef CRASHLYTICS -#import -#endif - -#if TARGET_OS_IOS -#import "MPTypes.h" -#import "MPiOSConfig.h" -#elif TARGET_OS_OSX -#import "MPTypes.h" -#import "MPMacConfig.h" -#endif - -#else - -#import -#import -#import -#import -#import -#import - -#define mpw_log(level, format, ...) \ - do { \ - char *_msg = NULL; \ - asprintf( &_msg, format, ##__VA_ARGS__ ); \ - if (_msg) { \ - CFStringRef fileStr = CFStringCreateWithCString( NULL, basename( (char *)__FILE__ ), kCFStringEncodingUTF8 ); \ - CFStringRef funcStr = CFStringCreateWithCString( NULL, __FUNCTION__, kCFStringEncodingUTF8 ); \ - CFStringRef msgStr = CFStringCreateWithCString( NULL, _msg, kCFStringEncodingUTF8 ); \ - id (*_getLogger)(id, SEL) = (void *)objc_msgSend; \ - void (*_sendMsg)(id, SEL, CFStringRef, NSInteger, CFStringRef, NSUInteger, CFStringRef) = (void *)objc_msgSend; \ - _sendMsg( _getLogger( (id)objc_getClass( "PearlLogger" ), sel_getUid( "get" ) ), \ - sel_getUid( "inFile:atLine:fromFunction:withLevel:text:" ), fileStr, __LINE__, funcStr, level, msgStr ); \ - if (fileStr) { CFRelease( fileStr ); } \ - if (funcStr) { CFRelease( funcStr ); } \ - if (msgStr) { CFRelease( msgStr ); } \ - free(_msg); \ - } \ - } while (0) - -#define trc(format, ...) mpw_log( 0, format, ##__VA_ARGS__ ); -#define dbg(format, ...) mpw_log( 1, format, ##__VA_ARGS__ ); -#define inf(format, ...) mpw_log( 2, format, ##__VA_ARGS__ ); -#define wrn(format, ...) mpw_log( 3, format, ##__VA_ARGS__ ); -#define err(format, ...) mpw_log( 4, format, ##__VA_ARGS__ ); -#define ftl(format, ...) mpw_log( 5, format, ##__VA_ARGS__ ); - #endif diff --git a/platform-independent/c/core/src/mpw-util.c b/platform-independent/c/core/src/mpw-util.c index e63558e6..5e195434 100644 --- a/platform-independent/c/core/src/mpw-util.c +++ b/platform-independent/c/core/src/mpw-util.c @@ -22,6 +22,7 @@ MP_LIBS_BEGIN #include #include #include +#include #if MPW_CPERCIVA #include @@ -34,54 +35,113 @@ MP_LIBS_BEGIN #include "aes.h" MP_LIBS_END -int mpw_verbosity = LogLevelInfo; -FILE *mpw_log_cli_file; +LogLevel mpw_verbosity = LogLevelInfo; +FILE *mpw_log_sink_file_target = NULL; -void mpw_log_cli(LogLevel level, const char *format, ...) { - va_list args; - va_start( args, format ); - mpw_vlog_cli( level, format, args ); - va_end( args ); -} -void mpw_vlog_cli(LogLevel level, const char *format, va_list args) { - if (!mpw_log_cli_file) - mpw_log_cli_file = stderr; +static MPLogSink **sinks; +static size_t sinks_count; - if (mpw_verbosity >= level) { - if (mpw_verbosity >= LogLevelDebug) { - switch (level) { - case LogLevelTrace: - fprintf( mpw_log_cli_file, "[TRC] " ); - break; - case LogLevelDebug: - fprintf( mpw_log_cli_file, "[DBG] " ); - break; - case LogLevelInfo: - fprintf( mpw_log_cli_file, "[INF] " ); - break; - case LogLevelWarning: - fprintf( mpw_log_cli_file, "[WRN] " ); - break; - case LogLevelError: - fprintf( mpw_log_cli_file, "[ERR] " ); - break; - case LogLevelFatal: - fprintf( mpw_log_cli_file, "[FTL] " ); - break; - default: - fprintf( mpw_log_cli_file, "[???] " ); - break; - } - } +bool mpw_log_sink_register(MPLogSink *sink) { - vfprintf( mpw_log_cli_file, format, args ); - fprintf( mpw_log_cli_file, "\n" ); + if (!mpw_realloc( &sinks, NULL, sizeof( MPLogSink * ) * ++sinks_count )) { + --sinks_count; + return false; } - if (level <= LogLevelFatal) + sinks[sinks_count - 1] = sink; + return true; +} + +bool mpw_log_sink_unregister(MPLogSink *sink) { + + for (unsigned int r = 0; r < sinks_count; ++r) { + if (sinks[r] == sink) { + sinks[r] = NULL; + return true; + } + } + + return false; +} + +void mpw_log_sink(LogLevel level, const char *file, int line, const char *function, const char *format, ...) { + + if (mpw_verbosity < level) + return; + + va_list args; + va_start( args, format ); + mpw_log_vsink( level, file, line, function, format, args ); + va_end( args ); +} + +void mpw_log_vsink(LogLevel level, const char *file, int line, const char *function, const char *format, va_list args) { + + if (mpw_verbosity < level) + return; + + return mpw_log_ssink( level, file, line, function, mpw_vstr( format, args ) ); +} + +void mpw_log_ssink(LogLevel level, const char *file, int line, const char *function, const char *message) { + + if (mpw_verbosity < level) + return; + + MPLogEvent record = (MPLogEvent){ + .occurrence = time( NULL ), + .level = level, + .file = file, + .line = line, + .function = function, + .message = message, + }; + + for (unsigned int s = 0; s < sinks_count; ++s) { + MPLogSink *sink = sinks[s]; + + if (sink) + sink( &record ); + } + + if (record.level <= LogLevelFatal) abort(); } +void mpw_log_sink_file(const MPLogEvent *record) { + + if (!mpw_log_sink_file_target) + mpw_log_sink_file_target = stderr; + + if (mpw_verbosity >= LogLevelDebug) { + switch (record->level) { + case LogLevelTrace: + fprintf( mpw_log_sink_file_target, "[TRC] " ); + break; + case LogLevelDebug: + fprintf( mpw_log_sink_file_target, "[DBG] " ); + break; + case LogLevelInfo: + fprintf( mpw_log_sink_file_target, "[INF] " ); + break; + case LogLevelWarning: + fprintf( mpw_log_sink_file_target, "[WRN] " ); + break; + case LogLevelError: + fprintf( mpw_log_sink_file_target, "[ERR] " ); + break; + case LogLevelFatal: + fprintf( mpw_log_sink_file_target, "[FTL] " ); + break; + default: + fprintf( mpw_log_sink_file_target, "[???] " ); + break; + } + } + + fprintf( mpw_log_sink_file_target, "%s\n", record->message ); +} + void mpw_uint16(const uint16_t number, uint8_t buf[2]) { buf[0] = (uint8_t)((number >> 8L) & UINT8_MAX); @@ -581,7 +641,7 @@ int mpw_strncasecmp(const char *s1, const char *s2, size_t max) { int cmp = 0; for (; !cmp && max-- > 0 && s1 && s2; ++s1, ++s2) - cmp = tolower( *(unsigned char *)s1 ) - tolower( *(unsigned char *)s2 ); + cmp = tolower( (unsigned char)*s1 ) - tolower( (unsigned char)*s2 ); return cmp; } diff --git a/platform-independent/c/core/src/mpw-util.h b/platform-independent/c/core/src/mpw-util.h index 3735c4c6..978d2a51 100644 --- a/platform-independent/c/core/src/mpw-util.h +++ b/platform-independent/c/core/src/mpw-util.h @@ -27,42 +27,57 @@ MP_LIBS_BEGIN MP_LIBS_END //// Logging. -typedef mpw_enum(int, LogLevel) { +typedef mpw_enum( int, LogLevel ) { /** Logging internal state. */ - LogLevelTrace = 3, + LogLevelTrace = 3, /** Logging state and events interesting when investigating issues. */ - LogLevelDebug = 2, + LogLevelDebug = 2, /** User messages. */ - LogLevelInfo = 1, + LogLevelInfo = 1, /** Recoverable issues and user suggestions. */ - LogLevelWarning = 0, + LogLevelWarning = 0, /** Unrecoverable issues. */ - LogLevelError = -1, + LogLevelError = -1, /** Issues that lead to abortion. */ - LogLevelFatal = -2, + LogLevelFatal = -2, }; extern LogLevel mpw_verbosity; -/** mpw_log_cli is a sink that writes log messages to the mpw_log_cli_file, which defaults to stderr. */ -extern FILE *mpw_log_cli_file; -void mpw_log_cli(LogLevel level, const char *format, ...); -void mpw_vlog_cli(LogLevel level, const char *format, va_list args); +typedef struct { + time_t occurrence; + LogLevel level; + const char *file; + int line; + const char *function; + const char *message; +} MPLogEvent; -/** mpw_log_app is a sink placeholder that an application can implement to consume log messages. */ -void mpw_log_app(LogLevel level, const char *format, ...); +/** A log sink describes a function that can receive log events when registered. */ +typedef void (MPLogSink)(const MPLogEvent *event); +bool mpw_log_sink_register(MPLogSink *sink); +bool mpw_log_sink_unregister(MPLogSink *sink); -/** The sink you want to channel the log messages into, defaults to mpw_log_cli. */ +/** mpw_log_sink_file is a sink that writes log messages to the mpw_log_cli_file, which defaults to stderr. */ +extern MPLogSink mpw_log_sink_file; +extern FILE *mpw_log_sink_file_target; + +/** These functions dispatch log events to the registered sinks. */ +void mpw_log_sink(LogLevel level, const char *file, int line, const char *function, const char *format, ...); +void mpw_log_vsink(LogLevel level, const char *file, int line, const char *function, const char *format, va_list args); +void mpw_log_ssink(LogLevel level, const char *file, int line, const char *function, const char *message); + +/** The log dispatcher you want to channel log messages into; defaults to mpw_log_sink, enabling the log sink mechanism. */ #ifndef MPW_LOG -#define MPW_LOG mpw_log_cli +#define MPW_LOG mpw_log_sink #endif #ifndef trc -#define trc(format, ...) MPW_LOG( LogLevelTrace, format, ##__VA_ARGS__ ) -#define dbg(format, ...) MPW_LOG( LogLevelDebug, format, ##__VA_ARGS__ ) -#define inf(format, ...) MPW_LOG( LogLevelInfo, format, ##__VA_ARGS__ ) -#define wrn(format, ...) MPW_LOG( LogLevelWarning, format, ##__VA_ARGS__ ) -#define err(format, ...) MPW_LOG( LogLevelError, format, ##__VA_ARGS__ ) -#define ftl(format, ...) MPW_LOG( LogLevelFatal, format, ##__VA_ARGS__ ) +#define trc(format, ...) MPW_LOG( LogLevelTrace, __FILE__, __LINE__, __func__, format, ##__VA_ARGS__ ) +#define dbg(format, ...) MPW_LOG( LogLevelDebug, __FILE__, __LINE__, __func__, format, ##__VA_ARGS__ ) +#define inf(format, ...) MPW_LOG( LogLevelInfo, __FILE__, __LINE__, __func__, format, ##__VA_ARGS__ ) +#define wrn(format, ...) MPW_LOG( LogLevelWarning, __FILE__, __LINE__, __func__, format, ##__VA_ARGS__ ) +#define err(format, ...) MPW_LOG( LogLevelError, __FILE__, __LINE__, __func__, format, ##__VA_ARGS__ ) +#define ftl(format, ...) MPW_LOG( LogLevelFatal, __FILE__, __LINE__, __func__, format, ##__VA_ARGS__ ) #endif #ifndef min