blob: 7d449d640d87adb67339e5fccaf19e73084329d1 [file] [log] [blame]
/*
* 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);
}