| /* | |
| * SPDX-License-Identifier: MIT | |
| * Copyright (c) 2017 wonder-mice | |
| * | |
| * zf log taken from: https://github.com/wonder-mice/zf_log | |
| */ | |
| #include <utils/zf_log_config.h> | |
| /* Controls Android log (android/log.h) support. When defined, must be 1 | |
| * (enable) or 0 (disable). Disabled by default. | |
| */ | |
| #ifndef ZF_LOG_ANDROID_LOG | |
| #define ZF_LOG_ANDROID_LOG 0 | |
| #endif | |
| /* Controls Apple log (asl.h) support. When defined, must be 1 (enable) or 0 | |
| * (disable). Disabled by default. Doesn't use asl directly, but piggybacks on | |
| * non-public CFLog() function. | |
| */ | |
| #ifndef ZF_LOG_APPLE_LOG | |
| #define ZF_LOG_APPLE_LOG 0 | |
| #endif | |
| /* Controls whether to add timestamp, pid, tid and level in the log message. | |
| * When defined, must be 1 (enable) or 0 (disable). If not defined, default | |
| * will be used. | |
| */ | |
| #ifndef ZF_LOG_PUT_CTX | |
| #if ZF_LOG_ANDROID_LOG || ZF_LOG_APPLE_LOG | |
| #define ZF_LOG_PUT_CTX 0 | |
| #else | |
| #define ZF_LOG_PUT_CTX 1 | |
| #endif | |
| #endif | |
| /* Size of the log line buffer. The buffer is allocated on the stack. It limits | |
| * the maximum length of the log line. | |
| */ | |
| #ifndef ZF_LOG_BUF_SZ | |
| #define ZF_LOG_BUF_SZ 256 | |
| #endif | |
| /* String to put in the end of each log line when necessary (can be empty). | |
| */ | |
| #ifndef ZF_LOG_EOL | |
| #define ZF_LOG_EOL "\n" | |
| #endif | |
| /* Number of bytes to reserve for EOL in the log line buffer (must be >0). | |
| */ | |
| #ifndef ZF_LOG_EOL_SZ | |
| #define ZF_LOG_EOL_SZ sizeof(ZF_LOG_EOL) | |
| #endif | |
| /* Default number of bytes in one line of memory output. For large values | |
| * ZF_LOG_BUF_SZ also must be increased. | |
| */ | |
| #ifndef ZF_LOG_MEM_WIDTH | |
| #define ZF_LOG_MEM_WIDTH 32 | |
| #endif | |
| /* Compile instrumented version of the library to facilitate unit testing. | |
| */ | |
| #ifndef ZF_LOG_INSTRUMENTED | |
| #define ZF_LOG_INSTRUMENTED 0 | |
| #endif | |
| #include <assert.h> | |
| #include <string.h> | |
| #include <stdarg.h> | |
| #include <stddef.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <ctype.h> | |
| #include <unistd.h> | |
| #include <time.h> | |
| #include <sys/time.h> | |
| #include <utils/zf_log.h> | |
| #if defined(__linux__) | |
| #include <sys/prctl.h> | |
| #include <sys/types.h> | |
| #endif | |
| #if defined(__linux__) && !defined(__ANDROID__) | |
| #include <sys/syscall.h> | |
| /* avoid defining _GNU_SOURCE */ | |
| int syscall(int number, ...); | |
| #endif | |
| #if defined(__MACH__) && ZF_LOG_PUT_CTX | |
| #include <pthread.h> | |
| #endif | |
| #if ZF_LOG_ANDROID_LOG | |
| #include <android/log.h> | |
| #endif | |
| #if ZF_LOG_APPLE_LOG | |
| #include <CoreFoundation/CoreFoundation.h> | |
| CF_EXPORT void CFLog(int32_t level, CFStringRef format, ...); | |
| #endif | |
| #if ZF_LOG_INSTRUMENTED | |
| #define INSTRUMENTED_CONST | |
| #else | |
| #define INSTRUMENTED_CONST const | |
| #endif | |
| #define STATIC_ASSERT(name, cond) \ | |
| typedef char assert_##name[(cond)? 1: -1] | |
| typedef void (*time_cb)(struct tm *const tm, unsigned *const usec); | |
| typedef void (*pid_cb)(int *const pid, int *const tid); | |
| typedef void (*buffer_cb)(zf_log_output_ctx *ctx, char *buf); | |
| #if ZF_LOG_PUT_CTX | |
| static void time_callback(struct tm *const tm, unsigned *const usec); | |
| static void pid_callback(int *const pid, int *const tid); | |
| #endif | |
| static void buffer_callback(zf_log_output_ctx *ctx, char *buf); | |
| static void output_callback(zf_log_output_ctx *const ctx); | |
| STATIC_ASSERT(eol_fits_eol_sz, sizeof(ZF_LOG_EOL) <= ZF_LOG_EOL_SZ); | |
| STATIC_ASSERT(eol_sz_greater_than_zero, 0 < ZF_LOG_EOL_SZ); | |
| static const char c_hex[] = "0123456789abcdef"; | |
| static const char *g_tag_prefix = 0; | |
| static size_t g_mem_width = ZF_LOG_MEM_WIDTH; | |
| static INSTRUMENTED_CONST unsigned g_buf_sz = ZF_LOG_BUF_SZ - ZF_LOG_EOL_SZ; | |
| #if ZF_LOG_PUT_CTX | |
| static INSTRUMENTED_CONST time_cb g_time_cb = time_callback; | |
| static INSTRUMENTED_CONST pid_cb g_pid_cb = pid_callback; | |
| #endif | |
| static INSTRUMENTED_CONST buffer_cb g_buffer_cb = buffer_callback; | |
| static zf_log_output_cb g_output_cb = output_callback; | |
| int _zf_log_output_lvl = 0; | |
| #if ZF_LOG_ANDROID_LOG | |
| static int android_lvl(const int lvl) | |
| { | |
| switch (lvl) | |
| { | |
| case ZF_LOG_VERBOSE: | |
| return ANDROID_LOG_VERBOSE; | |
| case ZF_LOG_DEBUG: | |
| return ANDROID_LOG_DEBUG; | |
| case ZF_LOG_INFO: | |
| return ANDROID_LOG_INFO; | |
| case ZF_LOG_WARN: | |
| return ANDROID_LOG_WARN; | |
| case ZF_LOG_ERROR: | |
| return ANDROID_LOG_ERROR; | |
| case ZF_LOG_FATAL: | |
| return ANDROID_LOG_FATAL; | |
| default: | |
| assert(!"Bad log level"); | |
| return ANDROID_LOG_UNKNOWN; | |
| } | |
| } | |
| #endif | |
| #if ZF_LOG_APPLE_LOG | |
| static int apple_lvl(const int lvl) | |
| { | |
| switch (lvl) | |
| { | |
| case ZF_LOG_VERBOSE: | |
| return 7; /* ASL_LEVEL_DEBUG / kCFLogLevelDebug */; | |
| case ZF_LOG_DEBUG: | |
| return 7; /* ASL_LEVEL_DEBUG / kCFLogLevelDebug */; | |
| case ZF_LOG_INFO: | |
| return 6; /* ASL_LEVEL_INFO / kCFLogLevelInfo */; | |
| case ZF_LOG_WARN: | |
| return 4; /* ASL_LEVEL_WARNING / kCFLogLevelWarning */; | |
| case ZF_LOG_ERROR: | |
| return 3; /* ASL_LEVEL_ERR / kCFLogLevelError */; | |
| case ZF_LOG_FATAL: | |
| return 0; /* ASL_LEVEL_EMERG / kCFLogLevelEmergency */; | |
| default: | |
| assert(!"Bad log level"); | |
| return 0; /* ASL_LEVEL_EMERG / kCFLogLevelEmergency */; | |
| } | |
| } | |
| #endif | |
| #if ZF_LOG_PUT_CTX | |
| static char lvl_char(const int lvl) | |
| { | |
| switch (lvl) | |
| { | |
| case ZF_LOG_VERBOSE: | |
| return 'V'; | |
| case ZF_LOG_DEBUG: | |
| return 'D'; | |
| case ZF_LOG_INFO: | |
| return 'I'; | |
| case ZF_LOG_WARN: | |
| return 'W'; | |
| case ZF_LOG_ERROR: | |
| return 'E'; | |
| case ZF_LOG_FATAL: | |
| return 'F'; | |
| default: | |
| assert(!"Bad log level"); | |
| return '?'; | |
| } | |
| } | |
| static void time_callback(struct tm *const tm, unsigned *const usec) | |
| { | |
| struct timeval tv; | |
| gettimeofday(&tv, 0); | |
| const time_t t = tv.tv_sec; | |
| *tm = *localtime(&t); | |
| *usec = tv.tv_usec; | |
| } | |
| static void pid_callback(int *const pid, int *const tid) | |
| { | |
| *pid = getpid(); | |
| #if defined(__ANDROID__) | |
| *tid = gettid(); | |
| #elif defined(__linux__) | |
| *tid = syscall(SYS_gettid); | |
| #elif defined(__MACH__) | |
| *tid = pthread_mach_thread_np(pthread_self()); | |
| #else | |
| #define Platform not supported | |
| #endif | |
| } | |
| #endif // ZF_LOG_PUT_CTX | |
| static void buffer_callback(zf_log_output_ctx *ctx, char *buf) | |
| { | |
| ctx->e = (ctx->p = ctx->buf = buf) + g_buf_sz; | |
| } | |
| static void output_callback(zf_log_output_ctx *const ctx) | |
| { | |
| #if ZF_LOG_ANDROID_LOG | |
| *ctx->p = 0; | |
| const char *tag = ctx->p; | |
| if (ctx->tag_e != ctx->tag_b) | |
| { | |
| tag = ctx->tag_b; | |
| *ctx->tag_e = 0; | |
| } | |
| __android_log_print(android_lvl(ctx->lvl), tag, "%s", ctx->msg_b); | |
| #elif ZF_LOG_APPLE_LOG | |
| *ctx->p = 0; | |
| CFLog(apple_lvl(ctx->lvl), CFSTR("%s"), ctx->tag_b); | |
| #else | |
| strcpy(ctx->p, ZF_LOG_EOL); | |
| fputs(ctx->buf, stderr); | |
| #endif | |
| if (ZF_LOG_FATAL == ctx->lvl) | |
| { | |
| fflush(stderr); | |
| abort(); | |
| } | |
| } | |
| static const char *filename(const char *file) | |
| { | |
| const char *f = file; | |
| for (const char *p = file; 0 != *p; ++p) | |
| { | |
| if ('/' == *p || '\\' == *p) | |
| { | |
| f = p + 1; | |
| } | |
| } | |
| return f; | |
| } | |
| static inline size_t nprintf_size(zf_log_output_ctx *const ctx) | |
| { | |
| // *nprintf() always puts 0 in the end when input buffer is not empty. This | |
| // 0 is not desired because its presence sets (ctx->p) to (ctx->e - 1) which | |
| // leaves space for one more character. Some put_xxx() functions don't use | |
| // *nprintf() and could use that last character. In that case log line will | |
| // have multiple (two) half-written parts which is confusing. To workaround | |
| // that we allow *nprintf() to write its 0 in the eol area (which is always | |
| // not empty). | |
| return ctx->e - ctx->p + 1; | |
| } | |
| static inline void put_nprintf(zf_log_output_ctx *const ctx, const int n) | |
| { | |
| if (0 < n && ctx->e < (ctx->p += n)) | |
| { | |
| ctx->p = ctx->e; | |
| } | |
| } | |
| static void put_ctx(zf_log_output_ctx *const ctx) | |
| { | |
| #if ZF_LOG_PUT_CTX | |
| int n; | |
| struct tm tm; | |
| unsigned usec; | |
| int pid, tid; | |
| g_time_cb(&tm, &usec); | |
| g_pid_cb(&pid, &tid); | |
| n = snprintf(ctx->p, nprintf_size(ctx), | |
| "%02u-%02u %02u:%02u:%02u.%03u %5i %5i %c ", | |
| (unsigned)tm.tm_mon, (unsigned)tm.tm_mday, | |
| (unsigned)tm.tm_hour, (unsigned)tm.tm_min, (unsigned)tm.tm_sec, | |
| (unsigned)(usec / 1000), | |
| pid, tid, (char)lvl_char(ctx->lvl)); | |
| put_nprintf(ctx, n); | |
| #else | |
| (void)ctx; | |
| #endif | |
| } | |
| static void put_tag(zf_log_output_ctx *const ctx, const char *const tag) | |
| { | |
| const char *ch; | |
| ctx->tag_b = ctx->p; | |
| if (0 != (ch = g_tag_prefix)) | |
| { | |
| for (;ctx->e != ctx->p && 0 != (*ctx->p = *ch); ++ctx->p, ++ch) | |
| { | |
| } | |
| } | |
| if (0 != (ch = tag) && 0 != tag[0]) | |
| { | |
| if (ctx->tag_b != ctx->p && ctx->e != ctx->p) | |
| { | |
| *ctx->p++ = '.'; | |
| } | |
| for (;ctx->e != ctx->p && 0 != (*ctx->p = *ch); ++ctx->p, ++ch) | |
| { | |
| } | |
| } | |
| ctx->tag_e = ctx->p; | |
| if (ctx->tag_b != ctx->p && ctx->e != ctx->p) | |
| { | |
| *ctx->p++ = ' '; | |
| } | |
| } | |
| static void put_src(zf_log_output_ctx *const ctx, const char *const func, | |
| const char *const file, const unsigned line) | |
| { | |
| int n; | |
| n = snprintf(ctx->p, nprintf_size(ctx), "%s@%s:%u ", | |
| func, filename(file), line); | |
| put_nprintf(ctx, n); | |
| } | |
| static void put_msg(zf_log_output_ctx *const ctx, | |
| const char *const fmt, va_list va) | |
| { | |
| int n; | |
| ctx->msg_b = ctx->p; | |
| n = vsnprintf(ctx->p, nprintf_size(ctx), fmt, va); | |
| put_nprintf(ctx, n); | |
| } | |
| static void output_mem(zf_log_output_ctx *const ctx, | |
| const void *const d, const unsigned d_sz) | |
| { | |
| if (0 == d || 0 == d_sz) | |
| { | |
| return; | |
| } | |
| const unsigned char *mem_p = (const unsigned char *)d; | |
| const unsigned char *const mem_e = mem_p + d_sz; | |
| const unsigned char *mem_cut; | |
| const ptrdiff_t mem_width = g_mem_width; | |
| char *const hex_b = ctx->msg_b; | |
| char *const ascii_b = hex_b + 2 * mem_width + 2; | |
| char *const ascii_e = ascii_b + mem_width; | |
| if (ctx->e < ascii_e) | |
| { | |
| return; | |
| } | |
| while (mem_p != mem_e) | |
| { | |
| char *hex = hex_b; | |
| char *ascii = ascii_b; | |
| for (mem_cut = mem_width < mem_e - mem_p? mem_p + mem_width: mem_e; | |
| mem_cut != mem_p; ++mem_p) | |
| { | |
| const char ch = *mem_p; | |
| *hex++ = c_hex[(0xf0 & ch) >> 4]; | |
| *hex++ = c_hex[(0x0f & ch)]; | |
| *ascii++ = isprint(ch)? ch: '?'; | |
| } | |
| while (hex != ascii_b) | |
| { | |
| *hex++ = ' '; | |
| } | |
| ctx->p = ascii; | |
| g_output_cb(ctx); | |
| } | |
| } | |
| void zf_log_set_tag_prefix(const char *const prefix) | |
| { | |
| g_tag_prefix = prefix; | |
| } | |
| void zf_log_set_mem_width(const unsigned w) | |
| { | |
| g_mem_width = w; | |
| } | |
| void zf_log_set_output_level(const int lvl) | |
| { | |
| _zf_log_output_lvl = lvl; | |
| } | |
| void zf_log_set_output_callback(const zf_log_output_cb cb) | |
| { | |
| g_output_cb = cb; | |
| } | |
| #define CTX(lvl_, tag_) \ | |
| zf_log_output_ctx ctx; \ | |
| char buf[ZF_LOG_BUF_SZ]; \ | |
| ctx.lvl = (lvl_); \ | |
| ctx.tag = (tag_); \ | |
| g_buffer_cb(&ctx, buf); \ | |
| (void)0 | |
| void _zf_log_write_d(const char *const func, | |
| const char *const file, const unsigned line, | |
| const int lvl, const char *const tag, | |
| const char *const fmt, ...) | |
| { | |
| CTX(lvl, tag); | |
| va_list va; | |
| va_start(va, fmt); | |
| put_ctx(&ctx); | |
| put_tag(&ctx, tag); | |
| put_src(&ctx, func, file, line); | |
| put_msg(&ctx, fmt, va); | |
| g_output_cb(&ctx); | |
| va_end(va); | |
| } | |
| void _zf_log_write(const int lvl, const char *const tag, | |
| const char *const fmt, ...) | |
| { | |
| CTX(lvl, tag); | |
| va_list va; | |
| va_start(va, fmt); | |
| put_ctx(&ctx); | |
| put_tag(&ctx, tag); | |
| put_msg(&ctx, fmt, va); | |
| g_output_cb(&ctx); | |
| va_end(va); | |
| } | |
| void _zf_log_write_mem_d(const char *const func, | |
| const char *const file, const unsigned line, | |
| const int lvl, const char *const tag, | |
| const void *const d, const unsigned d_sz, | |
| const char *const fmt, ...) | |
| { | |
| CTX(lvl, tag); | |
| va_list va; | |
| va_start(va, fmt); | |
| put_ctx(&ctx); | |
| put_tag(&ctx, tag); | |
| put_src(&ctx, func, file, line); | |
| put_msg(&ctx, fmt, va); | |
| g_output_cb(&ctx); | |
| output_mem(&ctx, d, d_sz); | |
| va_end(va); | |
| } | |
| void _zf_log_write_mem(const int lvl, const char *const tag, | |
| const void *const d, const unsigned d_sz, | |
| const char *const fmt, ...) | |
| { | |
| CTX(lvl, tag); | |
| va_list va; | |
| va_start(va, fmt); | |
| put_ctx(&ctx); | |
| put_tag(&ctx, tag); | |
| put_msg(&ctx, fmt, va); | |
| g_output_cb(&ctx); | |
| output_mem(&ctx, d, d_sz); | |
| va_end(va); | |
| } |