eina_log: thread safe logging!

eina_log_threads_enable() and then get thread safe logging with
non-main threads being printed with special notation to easily spot
those.



SVN revision: 42199
This commit is contained in:
Gustavo Sverzut Barbieri 2009-09-03 01:31:26 +00:00
parent d8ee1b3a24
commit 8024360138
2 changed files with 293 additions and 48 deletions

View File

@ -28,6 +28,7 @@
#define EINA_COLOR_BLUE "\033[34;1m"
#define EINA_COLOR_GREEN "\033[32;1m"
#define EINA_COLOR_YELLOW "\033[33;1m"
#define EINA_COLOR_ORANGE "\033[0;33m"
#define EINA_COLOR_WHITE "\033[37;1m"
#define EINA_COLOR_LIGHTBLUE "\033[36;1m"
#define EINA_COLOR_RESET "\033[0m"
@ -177,6 +178,7 @@ typedef void (*Eina_Log_Print_Cb)(const Eina_Log_Domain *d, Eina_Log_Level level
EAPI int eina_log_init(void);
EAPI int eina_log_shutdown(void);
EAPI void eina_log_threads_enable(void);
/*
* Customization

View File

@ -342,6 +342,48 @@ static int _eina_log_init_count = 0;
static Eina_Bool _disable_color = EINA_FALSE;
static Eina_Bool _abort_on_critical = EINA_FALSE;
#ifdef EFL_HAVE_PTHREAD
#include <pthread.h>
static Eina_Bool _threads_enabled = EINA_FALSE;
static pthread_t _main_thread;
static pthread_spinlock_t _log_lock;
#define LOCK() \
do { \
if (0) \
fprintf(stderr, "+++LOG LOCKED! [%s, %lu]\n", \
__FUNCTION__, pthread_self()); \
if (EINA_UNLIKELY(_threads_enabled)) \
pthread_spin_lock(&_log_lock); \
} while (0)
#define UNLOCK() \
do { \
if (EINA_UNLIKELY(_threads_enabled)) \
pthread_spin_unlock(&_log_lock); \
if (0) \
fprintf(stderr, \
"---LOG UNLOCKED! [%s, %lu]\n", \
__FUNCTION__, pthread_self()); \
} while (0)
#define IS_MAIN(t) pthread_equal(t, _main_thread)
#define IS_OTHER(t) EINA_UNLIKELY(!IS_MAIN(t))
#define CHECK_MAIN(...) \
do { \
if (!IS_MAIN(pthread_self())) { \
fprintf(stderr, \
"ERR: not main thread! current=%lu, main=%lu\n", \
pthread_self(), _main_thread); \
return __VA_ARGS__; \
} \
} while (0)
#else
#define LOCK() do {} while (0)
#define UNLOCK() do {} while (0)
#define IS_MAIN(t) (1)
#define IS_OTHER(t) (0)
#define CHECK_MAIN(...) do {} while (0)
#endif
// List of domains registered
static Eina_Log_Domain *_log_domains = NULL;
static int _log_domains_count = 0;
@ -613,6 +655,10 @@ EAPI int EINA_LOG_DOMAIN_GLOBAL = 0;
* @c EINA_LOG_ABORT=1.
*
* @see eina_init()
*
* @warning Not-MT: just call this function from main thread! The
* place where this function was called the first time is
* considered the main thread.
*/
EAPI int
eina_log_init(void)
@ -625,6 +671,11 @@ eina_log_init(void)
assert((sizeof(_names)/sizeof(_names[0])) == EINA_LOG_LEVELS);
assert((sizeof(_colors)/sizeof(_colors[0])) == EINA_LOG_LEVELS + 1);
#ifdef EFL_HAVE_PTHREAD
_main_thread = pthread_self();
pthread_spin_init(&_log_lock, PTHREAD_PROCESS_PRIVATE);
#endif
// Check if color is disabled
if ((tmp = getenv(EINA_LOG_ENV_COLOR_DISABLE)) && (atoi(tmp) == 1))
_disable_color = EINA_TRUE;
@ -666,12 +717,18 @@ eina_log_init(void)
* the log list.
*
* @see eina_shutdown()
*
* @warning Not-MT: just call this function from main thread! The
* place where eina_log_init() was called the first time is
* considered the main thread.
*/
EAPI int
eina_log_shutdown(void)
{
Eina_Inlist *tmp;
CHECK_MAIN(0);
if (_eina_log_init_count != 1) return --_eina_log_init_count;
while (_log_domains_count--)
@ -692,19 +749,57 @@ eina_log_shutdown(void)
free(tmp);
}
#ifdef EFL_HAVE_PTHREAD
pthread_spin_destroy(&_log_lock);
_threads_enabled = 0;
#endif
return --_eina_log_init_count;
}
/**
* Enable logging module to handle threads.
*
* There is no disable option on purpose, if it is enabled, there is
* no way back until you call the last eina_log_shutdown().
*
* There is no function to retrieve if threads are enabled as one is
* not supposed to know this from outside.
*
* After this call is executed at least once, if Eina was compiled
* with threads support then logging will lock around debug messages
* and threads that are not the main thread will have its identifier
* printed.
*
* The main thread is considered the thread where the first
* eina_log_init() was called.
*/
EAPI void
eina_log_threads_enable(void)
{
#ifdef EFL_HAVE_PTHREAD
_threads_enabled = 1;
#endif
}
/**
* Sets logging method to use.
*
* By default, eina_log_print_cb_stderr() is used.
*
* @note MT: safe to call from any thread.
*
* @note MT: given function @a cb will be called protected by mutex.
* This means you're safe from other calls but you should never
* call eina_log_print(), directly or indirectly.
*/
EAPI void
eina_log_print_cb_set(Eina_Log_Print_Cb cb, void *data)
{
LOCK();
_print_cb = cb;
_print_cb_data = data;
UNLOCK();
}
/**
@ -715,26 +810,18 @@ eina_log_print_cb_set(Eina_Log_Print_Cb cb, void *data)
* This function sets the log log level @p level. It is used in
* eina_log_print().
*/
EAPI void eina_log_level_set(Eina_Log_Level level)
EAPI void
eina_log_level_set(Eina_Log_Level level)
{
_log_level = level;
_log_level = level;
}
/*
* @param name Domain name
* @param color Color of the domain name
*
* @return Domain index that will be used as the DOMAIN parameter on log
* macros. A negative return value means an log ocurred.
*/
EAPI int
eina_log_domain_register(const char *name, const char *color)
static inline int
eina_log_domain_register_unlocked(const char *name, const char *color)
{
Eina_Log_Domain_Level_Pending *pending = NULL;
int i;
EINA_SAFETY_ON_NULL_RETURN_VAL(name, -1);
for (i = 0; i < _log_domains_count; i++)
{
if (_log_domains[i].deleted)
@ -786,8 +873,31 @@ finish_register:
return i;
}
EAPI void
eina_log_domain_unregister(int domain)
/**
* @param name Domain name
* @param color Color of the domain name
*
* @return Domain index that will be used as the DOMAIN parameter on log
* macros. A negative return value means an log ocurred.
*
* @note MT: safe to call from any thread.
*/
EAPI int
eina_log_domain_register(const char *name, const char *color)
{
int r;
EINA_SAFETY_ON_NULL_RETURN_VAL(name, -1);
LOCK();
r = eina_log_domain_register_unlocked(name, color);
UNLOCK();
return r;
}
static inline void
eina_log_domain_unregister_unlocked(int domain)
{
Eina_Log_Domain *d;
@ -798,12 +908,33 @@ eina_log_domain_unregister(int domain)
d->deleted = 1;
}
/**
* Forget about a logging domain registered by eina_log_domain_register()
*
* @param domain domain identifier as reported by eina_log_domain_register(),
* must be >= 0.
*
* @note MT: safe to call from any thread.
*/
EAPI void
eina_log_domain_unregister(int domain)
{
EINA_SAFETY_ON_FALSE_RETURN(domain >= 0);
LOCK();
eina_log_domain_unregister_unlocked(domain);
UNLOCK();
}
/**
* Default logging method, this will output to standard error stream.
*
* This method will colorize output based on domain provided color and
* message logging level. To disable color, set environment variable
* EINA_LOG_COLOR_DISABLE=1
*
* @note MT: if threads are enabled, this function is called within locks.
* @note MT: Threads different from main thread will have thread id
* appended to domain name.
*/
EAPI void
eina_log_print_cb_stderr(const Eina_Log_Domain *d, Eina_Log_Level level,
@ -832,14 +963,43 @@ eina_log_print_cb_stderr(const Eina_Log_Domain *d, Eina_Log_Level level,
}
if (EINA_UNLIKELY(_disable_color))
fprintf(stderr,
"%s:%s %s:%d %s() ",
name, d->domain_str, file, line, fnc);
{
#ifdef EFL_HAVE_PTHREAD
if (_threads_enabled)
{
pthread_t cur = pthread_self();
if (IS_OTHER(cur))
{
fprintf(stderr, "%s:%s[T:%lu] %s:%d %s() ",
name, d->domain_str, cur, file, line, fnc);
goto end;
}
}
#endif
fprintf(stderr, "%s:%s %s:%d %s() ",
name, d->domain_str, file, line, fnc);
}
else
fprintf(stderr,
"%s%s" EINA_COLOR_RESET ":%s %s:%d "
EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ",
color, name, d->domain_str, file, line, fnc);
{
#ifdef EFL_HAVE_PTHREAD
if (_threads_enabled)
{
pthread_t cur = pthread_self();
if (IS_OTHER(cur))
{
fprintf(stderr, "%s%s" EINA_COLOR_RESET ":%s[T:"
EINA_COLOR_ORANGE "%lu" EINA_COLOR_RESET "] %s:%d "
EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ",
color, name, d->domain_str, cur, file, line, fnc);
goto end;
}
}
#endif
fprintf(stderr, "%s%s" EINA_COLOR_RESET ":%s %s:%d "
EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ",
color, name, d->domain_str, file, line, fnc);
}
end:
vfprintf(stderr, fmt, args);
}
@ -849,6 +1009,10 @@ eina_log_print_cb_stderr(const Eina_Log_Domain *d, Eina_Log_Level level,
* This method will colorize output based on domain provided color and
* message logging level. To disable color, set environment variable
* EINA_LOG_COLOR_DISABLE=1
*
* @note MT: if threads are enabled, this function is called within locks.
* @note MT: Threads different from main thread will have thread id
* appended to domain name.
*/
EAPI void
eina_log_print_cb_stdout(const Eina_Log_Domain *d, Eina_Log_Level level,
@ -877,12 +1041,43 @@ eina_log_print_cb_stdout(const Eina_Log_Domain *d, Eina_Log_Level level,
}
if (EINA_UNLIKELY(_disable_color))
printf("%s:%s %s:%d %s() ",
name, d->domain_str, file, line, fnc);
{
#ifdef EFL_HAVE_PTHREAD
if (_threads_enabled)
{
pthread_t cur = pthread_self();
if (IS_OTHER(cur))
{
printf("%s:%s[T:%lu] %s:%d %s() ",
name, d->domain_str, cur, file, line, fnc);
goto end;
}
}
#endif
printf("%s:%s %s:%d %s() ",
name, d->domain_str, file, line, fnc);
}
else
printf("%s%s" EINA_COLOR_RESET ":%s %s:%d "
EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ",
color, name, d->domain_str, file, line, fnc);
{
#ifdef EFL_HAVE_PTHREAD
if (_threads_enabled)
{
pthread_t cur = pthread_self();
if (IS_OTHER(cur))
{
printf("%s%s" EINA_COLOR_RESET ":%s[T:"
EINA_COLOR_ORANGE "%lu" EINA_COLOR_RESET "] %s:%d "
EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ",
color, name, d->domain_str, cur, file, line, fnc);
goto end;
}
}
#endif
printf("%s%s" EINA_COLOR_RESET ":%s %s:%d "
EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ",
color, name, d->domain_str, file, line, fnc);
}
end:
vprintf(fmt, args);
}
@ -890,6 +1085,10 @@ eina_log_print_cb_stdout(const Eina_Log_Domain *d, Eina_Log_Level level,
* Alternative logging method, this will output to given file stream.
*
* This method will never output color.
*
* @note MT: if threads are enabled, this function is called within locks.
* @note MT: Threads different from main thread will have thread id
* appended to domain name.
*/
EAPI void
eina_log_print_cb_file(const Eina_Log_Domain *d, __UNUSED__ Eina_Log_Level level,
@ -897,33 +1096,28 @@ eina_log_print_cb_file(const Eina_Log_Domain *d, __UNUSED__ Eina_Log_Level level
void *data, va_list args)
{
FILE *f = data;
#ifdef EFL_HAVE_PTHREAD
if (_threads_enabled)
{
pthread_t cur = pthread_self();
if (IS_OTHER(cur))
{
fprintf(f, "%s[T:%lu] %s:%d %s() ", d->name, cur, file, line, fnc);
goto end;
}
}
#endif
fprintf(f, "%s %s:%d %s() ", d->name, file, line, fnc);
end:
vfprintf(f, fmt, args);
}
EAPI void
eina_log_print(int domain, Eina_Log_Level level, const char *file,
const char *fnc, int line, const char *fmt, ...)
static inline void
eina_log_print_unlocked(int domain, Eina_Log_Level level, const char *file, const char *fnc, int line, const char *fmt, va_list args)
{
Eina_Log_Domain *d;
va_list args;
#ifdef EINA_SAFETY_CHECKS
if (EINA_UNLIKELY(file == NULL))
{
fputs("ERR: eina_log_print() file == NULL\n", stderr);
return;
}
if (EINA_UNLIKELY(fnc == NULL))
{
fputs("ERR: eina_log_print() fnc == NULL\n", stderr);
return;
}
if (EINA_UNLIKELY(fmt == NULL))
{
fputs("ERR: eina_log_print() fmt == NULL\n", stderr);
return;
}
if (EINA_UNLIKELY(domain >= _log_domains_count) ||
EINA_UNLIKELY(domain < 0))
{
@ -942,11 +1136,60 @@ eina_log_print(int domain, Eina_Log_Level level, const char *file,
if (level > d->level) return;
va_start(args, fmt);
_print_cb(d, level, file, fnc, line, fmt, _print_cb_data, args);
va_end(args);
if (EINA_UNLIKELY(_abort_on_critical) &&
EINA_UNLIKELY(level <= EINA_LOG_LEVEL_CRITICAL))
abort();
}
/**
* Print out log message using given domain and level.
*
* @note Usually you'll not use this function directly but the helper
* macros EINA_LOG(), EINA_LOG_DOM_CRIT(), EINA_LOG_CRIT() and
* so on. See eina_log.h
*
* @param domain logging domain to use or @c EINA_LOG_DOMAIN_GLOBAL if
* you registered none. It is recommended that modules and
* applications have their own logging domain.
* @param level message level, those with level greater than user
* specified value (eina_log_level_set() or environment
* variables EINA_LOG_LEVEL, EINA_LOG_LEVELS) will be ignored.
* @param file filename that originated the call, must @b not be @c NULL.
* @param fnc function that originated the call, must @b not be @c NULL.
* @param line originating line in @a file.
* @param fmt printf-like format to use.
*
* @note MT: this function may be called from different threads if
* eina_log_threads_enable() was called before.
*/
EAPI void
eina_log_print(int domain, Eina_Log_Level level, const char *file,
const char *fnc, int line, const char *fmt, ...)
{
va_list args;
#ifdef EINA_SAFETY_CHECKS
if (EINA_UNLIKELY(file == NULL))
{
fputs("ERR: eina_log_print() file == NULL\n", stderr);
return;
}
if (EINA_UNLIKELY(fnc == NULL))
{
fputs("ERR: eina_log_print() fnc == NULL\n", stderr);
return;
}
if (EINA_UNLIKELY(fmt == NULL))
{
fputs("ERR: eina_log_print() fmt == NULL\n", stderr);
return;
}
#endif
va_start(args, fmt);
LOCK();
eina_log_print_unlocked(domain, level, file, fnc, line, fmt, args);
UNLOCK();
va_end(args);
}