/** * \file */ #include #include #include #include #include "mono-compiler.h" #include "mono-logger-internals.h" typedef struct { GLogLevelFlags level; MonoTraceMask mask; } MonoLogLevelEntry; GLogLevelFlags mono_internal_current_level = INT_MAX; MonoTraceMask mono_internal_current_mask = MONO_TRACE_ALL; gboolean mono_trace_log_header = FALSE; static GQueue *level_stack = NULL; static const char *mono_log_domain = "Mono"; static MonoPrintCallback print_callback, printerr_callback; static MonoLogCallParm logCallback = { .opener = NULL, .writer = NULL, .closer = NULL, .header = FALSE }; typedef struct { MonoLogCallback legacy_callback; gpointer user_data; } UserSuppliedLoggerUserData; /** * mono_trace_init: * * Initializes the mono tracer. */ void mono_trace_init (void) { if(level_stack == NULL) { mono_internal_current_level = G_LOG_LEVEL_ERROR; level_stack = g_queue_new(); char *mask = g_getenv ("MONO_LOG_MASK"); char *level = g_getenv ("MONO_LOG_LEVEL"); char *header = g_getenv ("MONO_LOG_HEADER"); char *dest = g_getenv ("MONO_LOG_DEST"); mono_trace_set_mask_string(mask); mono_trace_set_level_string(level); mono_trace_set_logheader_string(header); mono_trace_set_logdest_string(dest); g_free (mask); g_free (level); g_free (header); g_free (dest); } } /** * mono_trace_cleanup: * * Releases the mono tracer. */ void mono_trace_cleanup (void) { if(level_stack != NULL) { while(!g_queue_is_empty (level_stack)) { g_free (g_queue_pop_head (level_stack)); } logCallback.closer(); g_queue_free (level_stack); level_stack = NULL; } } /** * mono_tracev_inner: * \param level Verbose level of the specified message * \param mask Type of the specified message * Traces a new message, depending on the current logging level * and trace mask. */ void mono_tracev_inner (GLogLevelFlags level, MonoTraceMask mask, const char *format, va_list args) { char *log_message; if (level_stack == NULL) { mono_trace_init (); if(level > mono_internal_current_level || !(mask & mono_internal_current_mask)) return; } g_assert (logCallback.opener); // mono_trace_init should have provided us with one! if (g_vasprintf (&log_message, format, args) < 0) return; logCallback.writer (mono_log_domain, level, logCallback.header, log_message); g_free (log_message); } /** * mono_trace_set_level: * \param level Verbose level to set * Sets the current logging level. Every subsequent call to * \c mono_trace will check the visibility of a message against this * value. */ void mono_trace_set_level (GLogLevelFlags level) { if(level_stack == NULL) mono_trace_init(); mono_internal_current_level = level; } /** * mono_trace_set_mask: * \param mask Mask of visible message types. * Sets the current logging level. Every subsequent call to * \c mono_trace will check the visibility of a message against this * value. */ void mono_trace_set_mask (MonoTraceMask mask) { if(level_stack == NULL) mono_trace_init(); mono_internal_current_mask = mask; } /** * mono_trace_set_logdest: * \param dest Destination for logging * Sets the current logging destination. This can be a file or, if supported, * syslog. */ void mono_trace_set_logdest_string (const char *dest) { MonoLogCallParm logger; if(level_stack == NULL) mono_trace_init(); #if HOST_ANDROID logger.opener = mono_log_open_logcat; logger.writer = mono_log_write_logcat; logger.closer = mono_log_close_logcat; logger.dest = (char*) dest; #elif defined (HOST_IOS) logger.opener = mono_log_open_asl; logger.writer = mono_log_write_asl; logger.closer = mono_log_close_asl; logger.dest = (char*) dest; #else if ((dest == NULL) || (strcmp("syslog", dest) != 0)) { logger.opener = mono_log_open_logfile; logger.writer = mono_log_write_logfile; logger.closer = mono_log_close_logfile; logger.dest = (char *) dest; } else { logger.opener = mono_log_open_syslog; logger.writer = mono_log_write_syslog; logger.closer = mono_log_close_syslog; logger.dest = (char *) dest; } #endif mono_trace_set_log_handler_internal(&logger, NULL); } /** * mono_trace_set_logheader: * \param head Whether we want pid/date/time header on log messages * Sets the current logging header option. */ void mono_trace_set_logheader_string(const char *head) { if (head == NULL) { mono_trace_log_header = FALSE; } else { mono_trace_log_header = TRUE; } } /** * mono_trace_push: * \param level Verbose level to set * \param mask Mask of visible message types. * Saves the current values of level and mask then calls \c mono_trace_set * with the specified new values. */ void mono_trace_push (GLogLevelFlags level, MonoTraceMask mask) { if(level_stack == NULL) g_error("%s: cannot use mono_trace_push without calling mono_trace_init first.", __func__); else { MonoLogLevelEntry *entry = (MonoLogLevelEntry *) g_malloc(sizeof(MonoLogLevelEntry)); entry->level = mono_internal_current_level; entry->mask = mono_internal_current_mask; g_queue_push_head (level_stack, (gpointer)entry); /* Set the new level and mask */ mono_internal_current_level = level; mono_internal_current_mask = mask; } } /** * mono_trace_pop: * * Restores level and mask values saved from a previous call to mono_trace_push. */ void mono_trace_pop (void) { if(level_stack == NULL) g_error("%s: cannot use mono_trace_pop without calling mono_trace_init first.", __func__); else { if(!g_queue_is_empty (level_stack)) { MonoLogLevelEntry *entry = (MonoLogLevelEntry*)g_queue_pop_head (level_stack); /* Restore previous level and mask */ mono_internal_current_level = entry->level; mono_internal_current_mask = entry->mask; g_free (entry); } } } void mono_trace_set_level_string (const char *value) { int i = 0; const char *valid_vals[] = {"error", "critical", "warning", "message", "info", "debug", NULL}; const GLogLevelFlags valid_ids[] = {G_LOG_LEVEL_ERROR, G_LOG_LEVEL_CRITICAL, G_LOG_LEVEL_WARNING, G_LOG_LEVEL_MESSAGE, G_LOG_LEVEL_INFO, G_LOG_LEVEL_DEBUG }; if(!value) return; while(valid_vals[i]) { if(!strcmp(valid_vals[i], value)){ mono_trace_set_level(valid_ids[i]); return; } i++; } if(*value) g_print("Unknown trace loglevel: %s\n", value); } void mono_trace_set_mask_string (const char *value) { int i; const char *tok; guint32 flags = 0; const char *valid_flags[] = {"asm", "type", "dll", "gc", "cfg", "aot", "security", "threadpool", "io-threadpool", "io-layer", "w32handle", "all", NULL}; const MonoTraceMask valid_masks[] = {MONO_TRACE_ASSEMBLY, MONO_TRACE_TYPE, MONO_TRACE_DLLIMPORT, MONO_TRACE_GC, MONO_TRACE_CONFIG, MONO_TRACE_AOT, MONO_TRACE_SECURITY, MONO_TRACE_THREADPOOL, MONO_TRACE_IO_THREADPOOL, MONO_TRACE_IO_LAYER, MONO_TRACE_W32HANDLE, MONO_TRACE_ALL }; if(!value) return; tok = value; while (*tok) { if (*tok == ',') { tok++; continue; } for (i = 0; valid_flags[i]; i++) { size_t len = strlen (valid_flags[i]); if (strncmp (tok, valid_flags[i], len) == 0 && (tok[len] == 0 || tok[len] == ',')) { flags |= valid_masks[i]; tok += len; break; } } if (!valid_flags[i]) { g_print("Unknown trace flag: %s\n", tok); break; } } mono_trace_set_mask ((MonoTraceMask) flags); } /* * mono_trace_is_traced: * * Returns whenever a message with @level and @mask will be printed or not. */ gboolean mono_trace_is_traced (GLogLevelFlags level, MonoTraceMask mask) { return (level <= mono_internal_current_level && mask & mono_internal_current_mask); } /** * log_level_get_name * @log_level severity level * * Convert log level into a string for legacy log handlers */ static const char * log_level_get_name (GLogLevelFlags log_level) { switch (log_level & G_LOG_LEVEL_MASK) { case G_LOG_LEVEL_ERROR: return "error"; case G_LOG_LEVEL_CRITICAL: return "critical"; case G_LOG_LEVEL_WARNING: return "warning"; case G_LOG_LEVEL_MESSAGE: return "message"; case G_LOG_LEVEL_INFO: return "info"; case G_LOG_LEVEL_DEBUG: return "debug"; default: return "unknown"; } } /** * callback_adapter * * @log_domain Message prefix * @log_level Severity * @message Message to be written * @fatal Fatal flag - write then abort * @user_data Argument passed to @callback * * This adapts the old callback writer exposed by MonoCallback to the newer method of * logging. We ignore the header request as legacy handlers never had headers. */ static void callback_adapter (const char *domain, GLogLevelFlags level, mono_bool fatal, const char *message) { UserSuppliedLoggerUserData *ll =logCallback.user_data; ll->legacy_callback (domain, log_level_get_name(level), message, fatal, ll->user_data); } static void eglib_log_adapter (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { UserSuppliedLoggerUserData *ll = logCallback.user_data; ll->legacy_callback (log_domain, log_level_get_name (log_level), message, log_level & G_LOG_LEVEL_ERROR, ll->user_data); } /** * legacy_opener * * Dummy routine for older style loggers */ static void legacy_opener(const char *path, void *user_data) { /* nothing to do */ } /** * legacy_closer * * Cleanup routine for older style loggers */ static void legacy_closer(void) { if (logCallback.user_data != NULL) { g_free (logCallback.user_data); /* This is a UserSuppliedLoggerUserData struct */ logCallback.opener = NULL; logCallback.writer = NULL; logCallback.closer = NULL; logCallback.user_data = NULL; logCallback.header = FALSE; } } /** * mono_trace_set_log_handler: * * @callback The callback that will replace the default logging handler * @user_data Argument passed to @callback * * The log handler replaces the default runtime logger. All logging requests with be routed to it. * If the fatal argument in the callback is true, the callback must abort the current process. The runtime expects that * execution will not resume after a fatal error. */ void mono_trace_set_log_handler (MonoLogCallback callback, void *user_data) { g_assert (callback); if (logCallback.closer != NULL) logCallback.closer(); UserSuppliedLoggerUserData *ll = g_malloc (sizeof (UserSuppliedLoggerUserData)); ll->legacy_callback = callback; ll->user_data = user_data; logCallback.opener = legacy_opener; logCallback.writer = callback_adapter; logCallback.closer = legacy_closer; logCallback.user_data = ll; logCallback.dest = NULL; g_log_set_default_handler (eglib_log_adapter, user_data); } static void structured_log_adapter (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { logCallback.writer (log_domain, log_level, logCallback.header, message); } /** * mono_trace_set_log_handler_internal: * \param callback The callback that will replace the default logging handler * \param user_data Argument passed to \p callback * The log handler replaces the default runtime logger. All logging requests with be routed to it. * If the fatal argument in the callback is true, the callback must abort the current process. The runtime expects that * execution will not resume after a fatal error. */ void mono_trace_set_log_handler_internal (MonoLogCallParm *callback, void *user_data) { g_assert (callback); if (logCallback.closer != NULL) logCallback.closer(); logCallback.opener = callback->opener; logCallback.writer = callback->writer; logCallback.closer = callback->closer; logCallback.header = mono_trace_log_header; logCallback.dest = callback->dest; logCallback.opener (logCallback.dest, user_data); g_log_set_default_handler (structured_log_adapter, user_data); } static void print_handler (const char *string) { print_callback (string, TRUE); } static void printerr_handler (const char *string) { printerr_callback (string, FALSE); } /** * mono_trace_set_print_handler: * \param callback The callback that will replace the default runtime behavior for stdout output. * The print handler replaces the default runtime stdout output handler. This is used by free form output done by the runtime. */ void mono_trace_set_print_handler (MonoPrintCallback callback) { g_assert (callback); print_callback = callback; g_set_print_handler (print_handler); } /** * mono_trace_set_printerr_handler: * \param callback The callback that will replace the default runtime behavior for stderr output. * The print handler replaces the default runtime stderr output handler. This is used by free form output done by the runtime. */ void mono_trace_set_printerr_handler (MonoPrintCallback callback) { g_assert (callback); printerr_callback = callback; g_set_printerr_handler (printerr_handler); }