/* EINA - EFL data type library * Copyright (C) 2007-2009 Jorge Luis Zapata Muga, Cedric Bail, Andre Dieb * Martins * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 this library; * if not, see . */ /** * @page tutorial_log_page log Tutorial * * @section tutorial_log_introduction Introduction * * The Eina Log module provides logging facilities for libraries and * applications. It provides colored logging, basic logging levels (error, * warning, debug, info, critical) and loggers - called logging domains - * which will be covered on next sections. * * @section tutorial_log_basic_usage Basic Usage * * Log messages can be displayed using the following macros: * * @li EINA_LOG_ERR(), * @li EINA_LOG_INFO(), * @li EINA_LOG_WARN(), * @li EINA_LOG_DBG(). * * Here is an example: * * @code * #include * #include * * #include * * void test(int i) * { * EINA_LOG_DBG("Entering test"); * * if (i < 0) * { * EINA_LOG_ERR("Argument is negative"); * return; * } * * EINA_LOG_INFO("argument non negative"); * * EINA_LOG_DBG("Exiting test"); * } * * int main(void) * { * if (!eina_init()) * { * printf("log during the initialization of Eina_Log module\n"); * return EXIT_FAILURE; * } * * test(-1); * test(0); * * eina_shutdown(); * * return EXIT_SUCCESS; * } * @endcode * * If you compiled Eina without debug mode, execution will yield only one log * message, which is "argument is negative". * * Here we introduce the concept of logging domains (or loggers), which might * already be familiar to readers. It is basically a way to separate a set of * log messages into a context (e.g. a module) and provide a way of controlling * this set as a whole. * * For example, suppose you have 3 different modules on your application and you * want to get logging only from one of them (e.g. create some sort of filter). * For achieving that, all you need to do is create a logging domain for each * module so that all logging inside a module can be considered as a whole. * * Logging domains are specified by a name, color applied to the name and the * level. The first two (name and color) are set through code, that is, inside * your application/module/library. * * The level is used for controlling which messages should appear. It * specifies the lowest level that should be displayed (e.g. a message * with level 11 being logged on a domain with level set to 10 would be * displayed, while a message with level 9 wouldn't). * * The domain level is set during runtime (in contrast with the name and * color) through the environment variable EINA_LOG_LEVELS. This variable * expects a list in the form domain_name1:level1,domain_name2:level2,... . For * example: * * @code * * EINA_LOG_LEVELS=mymodule1:5,mymodule2:2,mymodule3:0 ./myapp * * @encode * * This line would set mymodule1 level to 5, mymodule2 level to 2 and mymodule3 * level to 0. * * * There's also a global logger to which EINA_LOG_(ERR, DBG, INFO, CRIT, WARN) * macros do log on. It is a logger that is created internally by Eina Log with * an empty name and can be used for general logging (where logging domains do * not apply). * * Since this global logger doesn't have a name, you can't set its level through * EINA_LOG_LEVELS variable. Here we introduce a second environment variable * that is a bit more special: EINA_LOG_LEVEL. * * This variable specifies the level of the global logging domain and the level * of domains that haven't been set through EINA_LOG_LEVELS. Here's an example: * * @code * * EINA_LOG_LEVEL=3 EINA_LOG_LEVELS=module1:10,module3:2 ./myapp * * @endcode * * Supposing you have modules named "module1", "module2" and "module3", this * line would result in module1 with level 10, module2 with level 3 and module3 * with level 2. Note that module2's level wasn't specified, so it's level is * set to the global level. This way we can easily apply filters to multiple * domains with only one parameter (EINA_LOG_LEVEL=num). * * The global level (EINA_LOG_LEVEL) can also be set through code, using * eina_log_level_set() function. * * * While developing your libraries or applications, you may notice that * EINA_LOG_DOM_(ERR, DBG, INFO, CRIT, WARN) macros also print out * messages from eina itself. Here we introduce another environment variable * that is a bit more special: EINA_LOG_LEVELS_GLOB. * * This variable allows you to disable the logging of any/all code in eina itself. * This is useful when developing your libraries or applications so that you can * see your own domain's messages easier without having to sift through a lot of * internal eina debug messages. Here's an example: * * @code * * EINA_LOG_LEVEL=3 EINA_LOG_LEVELS_GLOB=eina_*:0 ./myapp * * @endcode * * This will disable eina_log output from all internal eina code thus allowing * you to see your own domain messages easier. * * @section tutorial_log_advanced_display Advanced usage of print callbacks * * The log module allows the user to change the way * eina_log_print() displays the messages. It suffices to pass to * eina_log_print_cb_set() the function used to display the * message. That function must be of type #Eina_log_Print_Cb. As a * custom data can be passed to that callback, powerful display * messages can be displayed. * * It is suggested to not use __FILE__, __FUNCTION__ or __LINE__ when * writing that callback, but when defining macros (like * EINA_LOG_ERR() and other macros). * * Here is an example of custom callback, whose behavior can be * changed at runtime: * * @code * #include * #include * * #include * * #define log(fmt, ...) \ * eina_log_print(EINA_LOG_LEVEL_ERR, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) * * typedef struct _Data Data; * * struct _Data * { * int to_stderr; * }; * * void print_cb(const Eina_Log_Domain *domain, * Eina_Log_Level level, * const char *file, * const char *fnc, * int line, * const char *fmt, * void *data, * va_list args) * { * Data *d; * FILE *output; * char *str; * * d = (Data *)data; * if (d->to_stderr) * { * output = stderr; * str = "stderr"; * } * else * { * output = stdout; * str = "stdout"; * } * * fprintf(output, "%s:%s:%s (%d) %s: ", * domain->domain_str, file, fnc, line, str); * vfprintf(output, fmt, args); * putc('\n', output); * } * * void test(Data *data, int i) * { * if (i < 0) * data->to_stderr = 0; * else * data->to_stderr = 1; * * log("log message..."); * } * * int main(void) * { * Data data; * * if (!eina_init()) * { * printf("log during the initialization of Eina_Log module\n"); * return EXIT_FAILURE; * } * * eina_log_print_cb_set(print_cb, &data); * * test(&data, -1); * test(&data, 0); * * eina_shutdown(); * * return EXIT_SUCCESS; * } * @endcode * * @addtogroup Eina_Log_Group Log * * @{ * * The default log level value is set by default to * #EINA_LOG_LEVEL_DBG if Eina is compiled with debug mode, or to * #EINA_LOG_LEVEL_ERR otherwise. That value can be overwritten by * setting the environment variable EINA_LOG_LEVEL. This function * checks the value of that environment variable in the first * call. Its value must be a number between 0 and 4, to match the log * levels #EINA_LOG_LEVEL_CRITICAL, #EINA_LOG_LEVEL_ERR, * #EINA_LOG_LEVEL_WARN, #EINA_LOG_LEVEL_INFO and * #EINA_LOG_LEVEL_DBG. That value can also be set later with * eina_log_log_level_set(). When logging domains are created, they * will get either this value or specific value given with * EINA_LOG_LEVELS that takes the format * 'domain_name:level,another_name:other_level'. * * Format and verbosity of messages depend on the logging method, see * eina_log_print_cb_set(). The default logging method is * eina_log_print_cb_stderr(), which will output fancy colored * messages to standard error stream. See its documentation on how to * disable coloring, function or file/line print. * * This module will optionally abort program execution if message * level is below or equal to @c EINA_LOG_LEVEL_CRITICAL and * @c EINA_LOG_ABORT=1. * * @} */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #ifdef HAVE_EVIL # include #endif #include "eina_config.h" #include "eina_private.h" #include "eina_inlist.h" /* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */ #include "eina_safety_checks.h" #include "eina_log.h" #include /* TODO * + printing logs to stdout or stderr can be implemented * using a queue, useful for multiple threads printing * + add a wrapper for assert? */ /*============================================================================* * Local * *============================================================================*/ /** * @cond LOCAL */ #define EINA_LOG_ENV_ABORT "EINA_LOG_ABORT" #define EINA_LOG_ENV_ABORT_LEVEL "EINA_LOG_ABORT_LEVEL" #define EINA_LOG_ENV_LEVEL "EINA_LOG_LEVEL" #define EINA_LOG_ENV_LEVELS "EINA_LOG_LEVELS" #define EINA_LOG_ENV_LEVELS_GLOB "EINA_LOG_LEVELS_GLOB" #define EINA_LOG_ENV_COLOR_DISABLE "EINA_LOG_COLOR_DISABLE" #define EINA_LOG_ENV_FILE_DISABLE "EINA_LOG_FILE_DISABLE" #define EINA_LOG_ENV_FUNCTION_DISABLE "EINA_LOG_FUNCTION_DISABLE" // Structure for storing domain level settings passed from the command line // that will be matched with application-defined domains. typedef struct _Eina_Log_Domain_Level_Pending Eina_Log_Domain_Level_Pending; struct _Eina_Log_Domain_Level_Pending { EINA_INLIST; unsigned int level; char name[]; }; /* * List of levels for domains set by the user before the domains are registered, * updates the domain levels on the first log and clears itself. */ static Eina_Inlist *_pending_list = NULL; static Eina_Inlist *_glob_list = NULL; // Disable color flag (can be changed through the env var // EINA_LOG_ENV_COLOR_DISABLE). static Eina_Bool _disable_color = EINA_FALSE; static Eina_Bool _disable_file = EINA_FALSE; static Eina_Bool _disable_function = EINA_FALSE; static Eina_Bool _abort_on_critical = EINA_FALSE; static int _abort_level_on_critical = EINA_LOG_LEVEL_CRITICAL; #ifdef EFL_HAVE_PTHREAD #include static Eina_Bool _threads_enabled = EINA_FALSE; static pthread_t _main_thread; #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) #ifdef EFL_HAVE_PTHREAD_SPINLOCK static pthread_spinlock_t _log_lock; #define LOG_LOCK() \ if(_threads_enabled) \ do { \ if (0) \ fprintf(stderr, "+++LOG LOG_LOCKED! [%s, %lu]\n", \ __FUNCTION__, pthread_self()); \ if (EINA_UNLIKELY(_threads_enabled)) \ pthread_spin_lock(&_log_lock); \ } while (0) #define LOG_UNLOCK() \ if(_threads_enabled) \ do { \ if (EINA_UNLIKELY(_threads_enabled)) \ pthread_spin_unlock(&_log_lock); \ if (0) \ fprintf(stderr, \ "---LOG LOG_UNLOCKED! [%s, %lu]\n", \ __FUNCTION__, pthread_self()); \ } while (0) #define INIT() pthread_spin_init(&_log_lock, PTHREAD_PROCESS_PRIVATE); #define SHUTDOWN() pthread_spin_destroy(&_log_lock); #else static pthread_mutex_t _log_mutex = PTHREAD_MUTEX_INITIALIZER; #define LOG_LOCK() if(_threads_enabled) pthread_mutex_lock(&_log_mutex); #define LOG_UNLOCK() if(_threads_enabled) pthread_mutex_unlock(&_log_mutex); #define INIT() do {} while (0) #define SHUTDOWN() do {} while (0) #endif #else #define LOG_LOCK() do {} while (0) #define LOG_UNLOCK() do {} while (0) #define IS_MAIN(t) (1) #define IS_OTHER(t) (0) #define CHECK_MAIN(...) do {} while (0) #define INIT() do {} while (0) #define SHUTDOWN() do {} while (0) #endif // List of domains registered static Eina_Log_Domain *_log_domains = NULL; static int _log_domains_count = 0; static int _log_domains_allocated = 0; // Default function for printing on domains static Eina_Log_Print_Cb _print_cb = eina_log_print_cb_stderr; static void *_print_cb_data = NULL; #ifdef DEBUG static Eina_Log_Level _log_level = EINA_LOG_LEVEL_DBG; #elif DEBUG_CRITICAL static Eina_Log_Level _log_level = EINA_LOG_LEVEL_CRITICAL; #else static Eina_Log_Level _log_level = EINA_LOG_LEVEL_ERR; #endif // Default colors and levels static const char *_colors[] = { // + 1 for higher than debug EINA_COLOR_LIGHTRED, // EINA_LOG_LEVEL_CRITICAL EINA_COLOR_RED, // EINA_LOG_LEVEL_ERR EINA_COLOR_YELLOW, // EINA_LOG_LEVEL_WARN EINA_COLOR_GREEN, // EINA_LOG_LEVEL_INFO EINA_COLOR_LIGHTBLUE, // EINA_LOG_LEVEL_DBG EINA_COLOR_BLUE, // Higher than DEBUG }; static const char *_names[] = { "CRI", "ERR", "WRN", "INF", "DBG", }; static inline void eina_log_print_level_name_get(int level, const char **p_name) { static char buf[4]; if (EINA_UNLIKELY(level < 0)) { snprintf(buf, sizeof(buf), "%03d", level); *p_name = buf; } else if (EINA_UNLIKELY(level > EINA_LOG_LEVELS)) { snprintf(buf, sizeof(buf), "%03d", level); *p_name = buf; } else *p_name = _names[level]; } static inline void eina_log_print_level_name_color_get(int level, const char **p_name, const char **p_color) { static char buf[4]; if (EINA_UNLIKELY(level < 0)) { snprintf(buf, sizeof(buf), "%03d", level); *p_name = buf; *p_color = _colors[0]; } else if (EINA_UNLIKELY(level > EINA_LOG_LEVELS)) { snprintf(buf, sizeof(buf), "%03d", level); *p_name = buf; *p_color = _colors[EINA_LOG_LEVELS]; } else { *p_name = _names[level]; *p_color = _colors[level]; } } #define DECLARE_LEVEL_NAME(level) const char *name; eina_log_print_level_name_get(level, &name) #define DECLARE_LEVEL_NAME_COLOR(level) const char *name, *color; eina_log_print_level_name_color_get(level, &name, &color) /** No threads, No color */ static void eina_log_print_prefix_NOthreads_NOcolor_file_func(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc, int line) { DECLARE_LEVEL_NAME(level); fprintf(fp, "%s:%s %s:%d %s() ", name, d->domain_str, file, line, fnc); } static void eina_log_print_prefix_NOthreads_NOcolor_NOfile_func(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file __UNUSED__, const char *fnc, int line __UNUSED__) { DECLARE_LEVEL_NAME(level); fprintf(fp, "%s:%s %s() ", name, d->domain_str, fnc); } static void eina_log_print_prefix_NOthreads_NOcolor_file_NOfunc(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc __UNUSED__, int line) { DECLARE_LEVEL_NAME(level); fprintf(fp, "%s:%s %s:%d ", name, d->domain_str, file, line); } /* No threads, color */ static void eina_log_print_prefix_NOthreads_color_file_func(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc, int line) { DECLARE_LEVEL_NAME_COLOR(level); fprintf(fp, "%s%s" EINA_COLOR_RESET ":%s %s:%d " EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ", color, name, d->domain_str, file, line, fnc); } static void eina_log_print_prefix_NOthreads_color_NOfile_func(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file __UNUSED__, const char *fnc, int line __UNUSED__) { DECLARE_LEVEL_NAME_COLOR(level); fprintf(fp, "%s%s" EINA_COLOR_RESET ":%s " EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ", color, name, d->domain_str, fnc); } static void eina_log_print_prefix_NOthreads_color_file_NOfunc(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc __UNUSED__, int line) { DECLARE_LEVEL_NAME_COLOR(level); fprintf(fp, "%s%s" EINA_COLOR_RESET ":%s %s:%d ", color, name, d->domain_str, file, line); } /** threads, No color */ #ifdef EFL_HAVE_PTHREAD static void eina_log_print_prefix_threads_NOcolor_file_func(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc, int line) { DECLARE_LEVEL_NAME(level); pthread_t cur = pthread_self(); if (IS_OTHER(cur)) { fprintf(fp, "%s:%s[T:%lu] %s:%d %s() ", name, d->domain_str, cur, file, line, fnc); return; } fprintf(fp, "%s:%s %s:%d %s() ", name, d->domain_str, file, line, fnc); } static void eina_log_print_prefix_threads_NOcolor_NOfile_func(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file __UNUSED__, const char *fnc, int line __UNUSED__) { DECLARE_LEVEL_NAME(level); pthread_t cur = pthread_self(); if (IS_OTHER(cur)) { fprintf(fp, "%s:%s[T:%lu] %s() ", name, d->domain_str, cur, fnc); return; } fprintf(fp, "%s:%s %s() ", name, d->domain_str, fnc); } static void eina_log_print_prefix_threads_NOcolor_file_NOfunc(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc __UNUSED__, int line) { DECLARE_LEVEL_NAME(level); pthread_t cur = pthread_self(); if (IS_OTHER(cur)) { fprintf(fp, "%s:%s[T:%lu] %s:%d ", name, d->domain_str, cur, file, line); return; } fprintf(fp, "%s:%s %s:%d ", name, d->domain_str, file, line); } /* threads, color */ static void eina_log_print_prefix_threads_color_file_func(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc, int line) { DECLARE_LEVEL_NAME_COLOR(level); pthread_t cur = pthread_self(); if (IS_OTHER(cur)) { fprintf(fp, "%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); return; } fprintf(fp, "%s%s" EINA_COLOR_RESET ":%s %s:%d " EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ", color, name, d->domain_str, file, line, fnc); } static void eina_log_print_prefix_threads_color_NOfile_func(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file __UNUSED__, const char *fnc, int line __UNUSED__) { DECLARE_LEVEL_NAME_COLOR(level); pthread_t cur = pthread_self(); if (IS_OTHER(cur)) { fprintf(fp, "%s%s" EINA_COLOR_RESET ":%s[T:" EINA_COLOR_ORANGE "%lu" EINA_COLOR_RESET "] " EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ", color, name, d->domain_str, cur, fnc); return; } fprintf(fp, "%s%s" EINA_COLOR_RESET ":%s " EINA_COLOR_HIGH "%s()" EINA_COLOR_RESET " ", color, name, d->domain_str, fnc); } static void eina_log_print_prefix_threads_color_file_NOfunc(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc __UNUSED__, int line) { DECLARE_LEVEL_NAME_COLOR(level); pthread_t cur = pthread_self(); if (IS_OTHER(cur)) { fprintf(fp, "%s%s" EINA_COLOR_RESET ":%s[T:" EINA_COLOR_ORANGE "%lu" EINA_COLOR_RESET "] %s:%d ", color, name, d->domain_str, cur, file, line); return; } fprintf(fp, "%s%s" EINA_COLOR_RESET ":%s %s:%d ", color, name, d->domain_str, file, line); } #endif static void (*_eina_log_print_prefix)(FILE *fp, const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc, int line) = eina_log_print_prefix_NOthreads_color_file_func; static inline void eina_log_print_prefix_update(void) { if (_disable_file && _disable_function) { fprintf(stderr, "ERROR: cannot have " EINA_LOG_ENV_FILE_DISABLE " and " EINA_LOG_ENV_FUNCTION_DISABLE " set at the same time, will " "just disable function.\n"); _disable_file = 0; } #define S(NOthread, NOcolor, NOfile, NOfunc) \ _eina_log_print_prefix = eina_log_print_prefix_##NOthread##threads_##NOcolor##color_##NOfile##file_##NOfunc##func #ifdef EFL_HAVE_PTHREAD if (_threads_enabled) { if (_disable_color) { if (_disable_file) S(,NO,NO,); else if (_disable_function) S(,NO,,NO); else S(,NO,,); } else { if (_disable_file) S(,,NO,); else if (_disable_function) S(,,,NO); else S(,,,); } return; } #endif if (_disable_color) { if (_disable_file) S(NO,NO,NO,); else if (_disable_function) S(NO,NO,,NO); else S(NO,NO,,); } else { if (_disable_file) S(NO,,NO,); else if (_disable_function) S(NO,,,NO); else S(NO,,,); } #undef S } /* * Creates a colored domain name string. */ static const char * eina_log_domain_str_get(const char *name, const char *color) { const char *d; if (color) { size_t name_len; size_t color_len; name_len = strlen(name); color_len = strlen(color); d = malloc(sizeof(char) * (color_len + name_len + strlen(EINA_COLOR_RESET) + 1)); if (!d) return NULL; memcpy((char *)d, color, color_len); memcpy((char *)(d + color_len), name, name_len); memcpy((char *)(d + color_len + name_len), EINA_COLOR_RESET, strlen(EINA_COLOR_RESET)); ((char *)d)[color_len + name_len + strlen(EINA_COLOR_RESET)] = '\0'; } else d = strdup(name); return d; } /* * Setups a new logging domain to the name and color specified. Note that this * constructor acts upon an pre-allocated object. */ static Eina_Log_Domain * eina_log_domain_new(Eina_Log_Domain *d, const char *name, const char *color) { EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL); EINA_SAFETY_ON_NULL_RETURN_VAL(name, NULL); d->level = EINA_LOG_LEVEL_UNKNOWN; d->deleted = EINA_FALSE; if (name) { if ((color) && (!_disable_color)) d->domain_str = eina_log_domain_str_get(name, color); else d->domain_str = eina_log_domain_str_get(name, NULL); d->name = strdup(name); } else { d->domain_str = NULL; d->name = NULL; } return d; } /* * Frees internal strings of a log domain, keeping the log domain itself as a * slot for next domain registers. */ static void eina_log_domain_free(Eina_Log_Domain *d) { EINA_SAFETY_ON_NULL_RETURN(d); if (d->domain_str) free((char *)d->domain_str); if (d->name) free((char *)d->name); } /* * Parses domain levels passed through the env var. */ static void eina_log_domain_parse_pendings(void) { const char *start; if (!(start = getenv(EINA_LOG_ENV_LEVELS))) return; // name1:level1,name2:level2,name3:level3,... while (1) { Eina_Log_Domain_Level_Pending *p; char *end = NULL; char *tmp = NULL; long int level; end = strchr(start, ':'); if (!end) break; // Parse level, keep going if failed level = strtol((char *)(end + 1), &tmp, 10); if (tmp == (end + 1)) goto parse_end; // Parse name p = malloc(sizeof(Eina_Log_Domain_Level_Pending) + end - start + 1); if (!p) break; memcpy((char *)p->name, start, end - start); ((char *)p->name)[end - start] = '\0'; p->level = level; _pending_list = eina_inlist_append(_pending_list, EINA_INLIST_GET(p)); parse_end: start = strchr(tmp, ','); if (start) start++; else break; } } static void eina_log_domain_parse_pending_globs(void) { const char *start; if (!(start = getenv(EINA_LOG_ENV_LEVELS_GLOB))) return; // name1:level1,name2:level2,name3:level3,... while (1) { Eina_Log_Domain_Level_Pending *p; char *end = NULL; char *tmp = NULL; long int level; end = strchr(start, ':'); if (!end) break; // Parse level, keep going if failed level = strtol((char *)(end + 1), &tmp, 10); if (tmp == (end + 1)) goto parse_end; // Parse name p = malloc(sizeof(Eina_Log_Domain_Level_Pending) + end - start + 1); if (!p) break; memcpy((char *)p->name, start, end - start); ((char *)p->name)[end - start] = '\0'; p->level = level; _glob_list = eina_inlist_append(_glob_list, EINA_INLIST_GET(p)); parse_end: start = strchr(tmp, ','); if (start) start++; else break; } } /** * @endcond */ /*============================================================================* * Global * *============================================================================*/ /*============================================================================* * API * *============================================================================*/ /** * @addtogroup Eina_Log_Group log * * @brief These functions provide log management for projects. * * To use the log system Eina must be initialized with eina_init() and * later shut down with eina_shutdown(). The most generic way to print * logs is to use eina_log_print() but the helper macros * EINA_LOG_ERR(), EINA_LOG_INFO(), EINA_LOG_WARN() and EINA_LOG_DBG() * should be used instead. * * Here is a straightforward example: * * @code * #include * #include * * #include * * void test_warn(void) * { * EINA_LOG_WARN("Here is a warning message"); * } * * int main(void) * { * if (!eina_init()) * { * printf("log during the initialization of Eina_Log module\n"); * return EXIT_FAILURE; * } * * test_warn(); * * eina_shutdown(); * * return EXIT_SUCCESS; * } * @endcode * * Compile this code with the following command: * * @code * gcc -Wall -o test_Eina_Log test_eina.c `pkg-config --cflags --libs eina` * @endcode * * If Eina is compiled without debug mode, then executing the * resulting program displays nothing because the default log level * is #EINA_LOG_LEVEL_ERR and we want to display a warning * message, which level is strictly greater than the log level (see * eina_log_print() for more informations). Now execute the program * with: * * @code * EINA_LOG_LEVEL=2 ./test_eina_log * @endcode * * You should see a message displayed in the terminal. * * For more information, you can look at the @ref tutorial_log_page. * * @{ */ /** * @cond LOCAL */ EAPI int EINA_LOG_DOMAIN_GLOBAL = 0; /** * @endcond */ /** * @internal * @brief Initialize the log module. * * @return #EINA_TRUE on success, #EINA_FALSE on failure. * * This function sets up the log module of Eina. It is called by * eina_init(). * * @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. */ Eina_Bool eina_log_init(void) { const char *level, *tmp; assert((sizeof(_names)/sizeof(_names[0])) == EINA_LOG_LEVELS); assert((sizeof(_colors)/sizeof(_colors[0])) == EINA_LOG_LEVELS + 1); // Check if color is disabled if ((tmp = getenv(EINA_LOG_ENV_COLOR_DISABLE)) && (atoi(tmp) == 1)) _disable_color = EINA_TRUE; if ((tmp = getenv(EINA_LOG_ENV_FILE_DISABLE)) && (atoi(tmp) == 1)) _disable_file = EINA_TRUE; if ((tmp = getenv(EINA_LOG_ENV_FUNCTION_DISABLE)) && (atoi(tmp) == 1)) _disable_function = EINA_TRUE; if ((tmp = getenv(EINA_LOG_ENV_ABORT)) && (atoi(tmp) == 1)) _abort_on_critical = EINA_TRUE; if ((tmp = getenv(EINA_LOG_ENV_ABORT_LEVEL))) _abort_level_on_critical = atoi(tmp); eina_log_print_prefix_update(); // Global log level if ((level = getenv(EINA_LOG_ENV_LEVEL))) _log_level = atoi(level); // Register UNKNOWN domain, the default logger EINA_LOG_DOMAIN_GLOBAL = eina_log_domain_register("", NULL); if (EINA_LOG_DOMAIN_GLOBAL < 0) { fprintf(stderr, "Failed to create global logging domain.\n"); return EINA_FALSE; } // Parse pending domains passed through EINA_LOG_LEVELS_GLOB eina_log_domain_parse_pending_globs(); // Parse pending domains passed through EINA_LOG_LEVELS eina_log_domain_parse_pendings(); return EINA_TRUE; } /** * @internal * @brief Shut down the log module. * * @return #EINA_TRUE on success, #EINA_FALSE on failure. * * This function shuts down the log module set up by * eina_log_init(). It is called by eina_shutdown(). * * @see eina_shutdown() * * @warning Not-MT: just call this function from main thread! The * place where eina_log_init() (eina_init()) was called the * first time is considered the main thread. */ Eina_Bool eina_log_shutdown(void) { Eina_Inlist *tmp; while (_log_domains_count--) { if (_log_domains[_log_domains_count].deleted) continue; eina_log_domain_free(&_log_domains[_log_domains_count]); } free(_log_domains); _log_domains = NULL; _log_domains_count = 0; _log_domains_allocated = 0; while (_glob_list) { tmp = _glob_list; _glob_list = _glob_list->next; free(tmp); } while (_pending_list) { tmp = _pending_list; _pending_list = _pending_list->next; free(tmp); } return EINA_TRUE; } #ifdef EFL_HAVE_PTHREAD /** * @internal * @brief Activate the log mutex. * * This function activate the mutex in the eina log module. It is called by * eina_thread_init(). * * @see eina_thread_init() */ void eina_log_threads_init(void) { _main_thread = pthread_self(); _threads_enabled = EINA_TRUE; INIT(); } /** * @internal * @brief Shut down the log mutex. * * This function shuts down the mutex in the log module. * It is called by eina_thread_shutdown(). * * @see eina_thread_shutdown() */ void eina_log_threads_shutdown(void) { CHECK_MAIN(); SHUTDOWN(); _threads_enabled = EINA_FALSE; } #endif /** * 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_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_init() was called. */ EAPI void eina_log_threads_enable(void) { #ifdef EFL_HAVE_PTHREAD _threads_enabled = 1; eina_log_print_prefix_update(); #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) { LOG_LOCK(); _print_cb = cb; _print_cb_data = data; eina_log_print_prefix_update(); LOG_UNLOCK(); } /** * @brief Set the default log log level. * * @param level The log level. * * 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) { _log_level = level; } static inline int eina_log_domain_register_unlocked(const char *name, const char *color) { Eina_Log_Domain_Level_Pending *pending = NULL; int i; for (i = 0; i < _log_domains_count; i++) { if (_log_domains[i].deleted) { // Found a flagged slot, free domain_str and replace slot eina_log_domain_new(&_log_domains[i], name, color); goto finish_register; } } if (_log_domains_count >= _log_domains_allocated) { Eina_Log_Domain *tmp; size_t size; if (!_log_domains) // special case for init, eina itself will allocate a dozen of domains size = 24; else // grow 8 buckets to minimize reallocs size = _log_domains_allocated + 8; tmp = realloc(_log_domains, sizeof(Eina_Log_Domain) * size); if (tmp) { // Success! _log_domains = tmp; _log_domains_allocated = size; } else return -1; } // Use an allocated slot eina_log_domain_new(&_log_domains[i], name, color); _log_domains_count++; finish_register: EINA_INLIST_FOREACH(_glob_list, pending) { if (!fnmatch(pending->name, name, 0)) { _log_domains[i].level = pending->level; break; } } EINA_INLIST_FOREACH(_pending_list, pending) { if (!strcmp(pending->name, name)) { _log_domains[i].level = pending->level; _pending_list = eina_inlist_remove(_pending_list, EINA_INLIST_GET(pending)); free(pending); break; } } // Check if level is still UNKNOWN, set it to global if (_log_domains[i].level == EINA_LOG_LEVEL_UNKNOWN) _log_domains[i].level = _log_level; return i; } /** * @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); LOG_LOCK(); r = eina_log_domain_register_unlocked(name, color); LOG_UNLOCK(); return r; } static inline void eina_log_domain_unregister_unlocked(int domain) { Eina_Log_Domain *d; if (domain >= _log_domains_count) return; d = &_log_domains[domain]; eina_log_domain_free(d); 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); LOG_LOCK(); eina_log_domain_unregister_unlocked(domain); LOG_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. Similarly, to disable file and line * information, set EINA_LOG_FILE_DISABLE=1 or * EINA_LOG_FUNCTION_DISABLE=1 to avoid function name in output. It is * not acceptable to have both EINA_LOG_FILE_DISABLE and * EINA_LOG_FUNCTION_DISABLE at the same time, in this case just * EINA_LOG_FUNCTION_DISABLE will be considered and file information * will be printed anyways. * * @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, const char *file, const char *fnc, int line, const char *fmt, __UNUSED__ void *data, va_list args) { _eina_log_print_prefix(stderr, d, level, file, fnc, line); vfprintf(stderr, fmt, args); putc('\n', stderr); } /** * Alternative logging method, this will output to standard output 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. Similarly, to disable file and line * information, set EINA_LOG_FILE_DISABLE=1 or * EINA_LOG_FUNCTION_DISABLE=1 to avoid function name in output. It is * not acceptable to have both EINA_LOG_FILE_DISABLE and * EINA_LOG_FUNCTION_DISABLE at the same time, in this case just * EINA_LOG_FUNCTION_DISABLE will be considered and file information * will be printed anyways. * * @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, const char *file, const char *fnc, int line, const char *fmt, __UNUSED__ void *data, va_list args) { _eina_log_print_prefix(stdout, d, level, file, fnc, line); vprintf(fmt, args); putchar('\n'); } /** * 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, const char *file, const char *fnc, int line, const char *fmt, 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); putc('\n', f); } 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; #ifdef EINA_SAFETY_CHECKS if (EINA_UNLIKELY(domain >= _log_domains_count) || EINA_UNLIKELY(domain < 0)) { if (file && fnc && fmt) fprintf(stderr, "CRI: %s:%d %s() eina_log_print() unknown domain %d, original message format '%s'\n", file, line, fnc, domain, fmt); else fprintf(stderr, "CRI: eina_log_print() unknown domain %d, original message format '%s'\n", domain, fmt ? fmt : ""); if (_abort_on_critical) abort(); return; } #endif d = _log_domains + domain; #ifdef EINA_SAFETY_CHECKS if (EINA_UNLIKELY(d->deleted)) { fprintf(stderr, "ERR: eina_log_print() domain %d is deleted\n", domain); return; } #endif if (level > d->level) return; _print_cb(d, level, file, fnc, line, fmt, _print_cb_data, args); if (EINA_UNLIKELY(_abort_on_critical) && EINA_UNLIKELY(level <= _abort_level_on_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. Should not provide trailing * '\n' as it is automatically included. * * @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); LOG_LOCK(); eina_log_print_unlocked(domain, level, file, fnc, line, fmt, args); LOG_UNLOCK(); va_end(args); } /** * 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. Should not provide trailing * '\n' as it is automatically included. * * @note MT: this function may be called from different threads if * eina_log_threads_enable() was called before. * * @see eina_log_print() */ EAPI void eina_log_vprint(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 LOG_LOCK(); eina_log_print_unlocked(domain, level, file, fnc, line, fmt, args); LOG_UNLOCK(); }