From f6ce0466cdcb331fadb276a0e78df61853c53224 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=98=D0=B6=D0=B1=D1?=
 =?UTF-8?q?=83=D0=BB=D0=B0=D1=82=D0=BE=D0=B2?= <lrn1986@gmail.com>
Date: Thu, 29 Sep 2011 22:44:44 +0400
Subject: [PATCH] Now loglevels may be set per-component, at runtime

* GNUNET_BOTTOM_LOGLEVEL and GNUNET_TOP_LOGLEVEL set global levels
Use bottom level to force logging to be more verbose than configured
Use top level to force logging to be less verbose than configured
Obviously, bottom <= top

* GNUNET_LOG sets per-component levels
GNUNET_LOG looks like this:
name[/bottom[/top]]/...
name starts with a non-digit character, must not include '/'
bottom and top must consist only of digits, or be empty
a description is only used if it matches the component exactly
as a special exception (for now) the name '*' matches any component
per-component loglevels override global loglevels
global levels override whatever is given via arguments or in config
Examples:
test_client/8/8/
run test_client with DEBUG level (usually leads to a timeout, by the way)

*/2/2/core/8/8/transport/4/4
run everything with WARNING, core - with DEBUG, transport - with INFO

*//1/peerinfo/4/
run everything with top loglevel ERROR, global/configured bottom loglevel,
and peerinfo - with bottom loglevel INFO and global/configured top loglevel

statistics/
does nothing

TODO:
* rework the syntax to allow for symbolic loglevel names (make levels mandatory?)
* turn _log() into _log_from() calls to get finer separation
OR
* add a component sublevel and the ability to match it
(such as "tcp:connect" or "test_client:verbose")
---
 src/include/gnunet_common.h |   54 ++++++++++-
 src/util/common_logging.c   |  229 +++++++++++++++++++++++++++++++++++++++----
 2 files changed, 260 insertions(+), 23 deletions(-)

diff --git a/src/include/gnunet_common.h b/src/include/gnunet_common.h
index f484482..c14363f 100644
--- a/src/include/gnunet_common.h
+++ b/src/include/gnunet_common.h
@@ -161,6 +161,17 @@ typedef void (*GNUNET_Logger) (void *cls, enum GNUNET_ErrorType kind,
                                const char *component, const char *date,
                                const char *message);
 
+
+/**
+ * Minimum log level.
+ */
+extern enum GNUNET_ErrorType min_level;
+
+/**
+ * Number of log calls to ignore.
+ */
+extern unsigned int skip_log;
+
 /**
  * Main log function.
  *
@@ -169,8 +180,33 @@ typedef void (*GNUNET_Logger) (void *cls, enum GNUNET_ErrorType kind,
  * @param ... arguments for format string
  */
 void
-GNUNET_log (enum GNUNET_ErrorType kind, const char *message, ...);
+GNUNET_log_nocheck (enum GNUNET_ErrorType kind, const char *message, ...);
+
+/* from glib */
+#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__)
+#define _GNUNET_BOOLEAN_EXPR(expr)              \
+ __extension__ ({                               \
+   int _gnunet_boolean_var_;                    \
+   if (expr)                                    \
+      _gnunet_boolean_var_ = 1;                 \
+   else                                         \
+      _gnunet_boolean_var_ = 0;                 \
+   _gnunet_boolean_var_;                        \
+})
+#define GN_LIKELY(expr) (__builtin_expect (_GNUNET_BOOLEAN_EXPR(expr), 1))
+#define GN_UNLIKELY(expr) (__builtin_expect (_GNUNET_BOOLEAN_EXPR(expr), 0))
+#else
+#define GN_LIKELY(expr) (expr)
+#define GN_UNLIKELY(expr) (expr)
+#endif
 
+#define GNUNET_log(kind,...) do {\
+  if (GN_UNLIKELY(skip_log > 0)) {skip_log--;}\
+  else {\
+    if (GN_UNLIKELY(((kind) & (~GNUNET_ERROR_TYPE_BULK)) <= min_level))\
+      GNUNET_log_nocheck (kind, __VA_ARGS__);\
+  }\
+} while (0)
 
 
 /**
@@ -183,9 +219,23 @@ GNUNET_log (enum GNUNET_ErrorType kind, const char *message, ...);
  * @param ... arguments for format string
  */
 void
-GNUNET_log_from (enum GNUNET_ErrorType kind, const char *comp,
+GNUNET_log_from_nocheck (enum GNUNET_ErrorType kind, const char *comp,
                  const char *message, ...);
 
+#define GNUNET_log_from(kind,...) \
+do\
+{\
+  if (GN_UNLIKELY(skip_log > 0))\
+  {\
+    skip_log--;\
+  }\
+  else\
+  {\
+    if (GN_UNLIKELY(((kind) & (~GNUNET_ERROR_TYPE_BULK)) <= min_level))\
+      GNUNET_log_from_nocheck (kind, __VA_ARGS__);\
+  }\
+} while (0)
+
 
 /**
  * Ignore the next n calls to the log function.
diff --git a/src/util/common_logging.c b/src/util/common_logging.c
index ff3ac12..4eb67aa 100644
--- a/src/util/common_logging.c
+++ b/src/util/common_logging.c
@@ -117,7 +117,7 @@ static char *component;
 /**
  * Minimum log level.
  */
-static enum GNUNET_ErrorType min_level;
+enum GNUNET_ErrorType min_level;
 
 /**
  * Linked list of our custom loggres.
@@ -127,7 +127,7 @@ static struct CustomLogger *loggers;
 /**
  * Number of log calls to ignore.
  */
-static unsigned int skip_log;
+unsigned int skip_log;
 
 /**
  * File descriptor to use for "stderr", or NULL for none.
@@ -164,6 +164,191 @@ get_type (const char *log)
   return GNUNET_ERROR_TYPE_INVALID;
 }
 
+void
+process_definition (char *name, const char *comp, int blevel, int tlevel, int *bot, int *top)
+{
+  if (name[0] != '*' && strcmp (name, comp) != 0)
+    return;
+  if (blevel != -1 && blevel > *bot)
+    *bot = blevel;
+  if (tlevel != -1 && (tlevel < *top || *top < 0))
+    *top = tlevel;
+}
+
+
+/**
+ * Get a string component loglevel definition from the environment,
+ * and match @comp against it. If it matches, set @bot and @top to
+ * the values from the definition (or to defaults, if definition omits them).
+ * Definition format looks like this:
+ * name[/bottomlevel[/toplevel]]/...
+ * name must not include slashes, and must begin with a non-digit char.
+ * name may be * to indicate that this applies to all components
+ * (there is, however, no wildcard matching algorithm at the moment).
+ * bottomlevel and toplevel must be valid non-negative integers.
+ * bottomlevel and toplevel can be zero-length (useful, if you want to
+ * specify toplevel, but not bottomlevel).
+ *
+ * @param comp component name to match
+ * @param bot pointer to an integer receiving bottom log threshold
+ * @param top pointer to an integer receiving top log threshold
+ */
+static void
+get_component_log_levels (const char *comp, int *bot, int *top)
+{
+  const char *logdef;
+  char *p;
+  char *start;
+  short state;
+  char *tmp;
+  char *name = NULL;
+  int blevel, tlevel;
+  logdef = getenv ("GNUNET_LOG");
+  if (logdef == NULL)
+    return;
+  tmp = strdup (logdef);
+  for (p = tmp, state = 0, start = tmp; p[0] != '\0'; p++)
+  {
+    if (state == 0)
+    {
+      /* beginning of the string - must not be digit, and must not be slash */
+      if ((p[0] >= '0' && p[0] <= '9') || (p[0] == '/'))
+      {
+        free (tmp);
+        return;
+      }
+      blevel = -1;
+      tlevel = -1;
+      state = 1;
+      continue;
+    }
+    if (state == 1)
+    {
+      /* within a name, ends with a slash */
+      if (p[0] == '/')
+      {
+        p[0] = '\0';
+        state = 2;
+        name = start;
+        start = p + 1;
+      }
+      continue;
+    }
+    if (state == 2)
+    {
+      /* after a name there will be either another name, or bottomlevel */
+      if (p[0] >= '0' && p[1] <= '9')
+      {
+        /* bottomlevel */
+        state = 3;
+      }
+      else if (p[0] == '/')
+      {
+        blevel = -1;
+        /* bottomlevel is 0-length, look for toplevel */
+        state = 4;
+        start = p + 1;
+      }
+      else
+      {
+        state = 0;
+        start = p;
+        /* another name starts, process what we've got */
+        process_definition (name, comp, blevel, tlevel, bot, top);
+        p--;
+      }
+      continue;
+    }
+    if (state == 3)
+    {
+      /* within a bottomlevel, consists of digits, ends with a slash */
+      if (p[0] == '/')
+      {
+        p[0] = '\0';
+        if (strlen (start) > 0)
+        {
+          errno = 0;
+          blevel = strtol (start, NULL, 10);
+          if (errno != 0 || blevel < 0)
+          {
+            free (tmp);
+            return;
+          }
+        }
+        start = p + 1;
+        /* look for toplevel or for a new name */
+        state = 4;
+      }
+      else if (p[0] < '0' || p[1] > '9')
+      {
+        free (tmp);
+        return;
+      }
+      continue;
+    }
+    if (state == 4)
+    {
+      /* after a bottomlevel there will be either a new name, or toplevel */
+      if (p[0] >= '0' && p[1] <= '9')
+      {
+        /* toplevel */
+        state = 5;
+      }
+      else if (p[0] == '/')
+      {
+        tlevel = -1;
+        /* toplevel is 0-length, look for a new name */
+        state = 0;
+        start = p + 1;
+        /* process what we've got */
+        process_definition (name, comp, blevel, tlevel, bot, top);
+      }
+      else
+      {
+        state = 0;
+        start = p;
+        /* another name starts, process what we've got */
+        process_definition (name, comp, blevel, tlevel, bot, top);
+        p--;
+      }
+      continue;
+    }
+    if (state == 5)
+    {
+      /* within a toplevel, consists of digits, ends with a slash */
+      if (p[0] == '/')
+      {
+        p[0] = '\0';
+        if (strlen (start) > 0)
+        {
+          errno = 0;
+          tlevel = strtol (start, NULL, 10);
+          if (errno != 0 || tlevel < 0)
+          {
+            free (tmp);
+            return;
+          }
+        }
+        start = p + 1;
+        /* look for a new name */
+        state = 0;
+        /* another name starts, process what we've got */
+        process_definition (name, comp, blevel, tlevel, bot, top);
+      }
+      else if (p[0] < '0' || p[1] > '9')
+      {
+        free (tmp);
+        return;
+      }
+      continue;
+    }
+    free (tmp);
+    return;
+  }
+  if (state == 2)
+  {
+  }
+}
 
 /**
  * Setup logging.
@@ -180,25 +365,32 @@ GNUNET_log_setup (const char *comp, const char *loglevel, const char *logfile)
   int dirwarn;
   char *fn;
   const char *env_loglevel;
-  int env_minlevel = 0;
-  int env_min_force_level = 100000;
+  int env_bottom_level = 0;
+  int env_top_level = 100000;
+  int comp_bottom_level = -1;
+  int comp_top_level = -1;
 
 #ifdef WINDOWS
   QueryPerformanceFrequency (&performance_frequency);
 #endif
   GNUNET_free_non_null (component);
   GNUNET_asprintf (&component, "%s-%d", comp, getpid ());
-  env_loglevel = getenv ("GNUNET_LOGLEVEL");
+  env_loglevel = getenv ("GNUNET_BOTTOM_LOGLEVEL");
   if (env_loglevel != NULL)
-    env_minlevel = get_type (env_loglevel);
-  env_loglevel = getenv ("GNUNET_FORCE_LOGLEVEL");
+    env_bottom_level = get_type (env_loglevel);
+  env_loglevel = getenv ("GNUNET_TOP_LOGLEVEL");
   if (env_loglevel != NULL)
-    env_min_force_level = get_type (env_loglevel);
+    env_top_level = get_type (env_loglevel);
+  get_component_log_levels (comp, &comp_bottom_level, &comp_top_level);
+  if (comp_bottom_level != -1)
+    env_bottom_level = comp_bottom_level;
+  if (comp_top_level != -1)
+    env_top_level = comp_top_level;
   min_level = get_type (loglevel);
-  if (env_minlevel > min_level)
-    min_level = env_minlevel;
-  if (env_min_force_level < min_level)
-    min_level = env_min_force_level;
+  if (env_bottom_level > min_level)
+    min_level = env_bottom_level;
+  if (env_top_level < min_level)
+    min_level = env_top_level;
   if (logfile == NULL)
     return GNUNET_OK;
   fn = GNUNET_STRINGS_filename_expand (logfile);
@@ -383,13 +575,6 @@ mylog (enum GNUNET_ErrorType kind, const char *comp, const char *message,
   char *buf;
   va_list vacp;
 
-  if (skip_log > 0)
-  {
-    skip_log--;
-    return;
-  }
-  if ((kind & (~GNUNET_ERROR_TYPE_BULK)) > min_level)
-    return;
   va_copy (vacp, va);
   size = VSNPRINTF (NULL, 0, message, vacp) + 1;
   va_end (vacp);
@@ -448,7 +633,7 @@ mylog (enum GNUNET_ErrorType kind, const char *comp, const char *message,
  * @param ... arguments for format string
  */
 void
-GNUNET_log (enum GNUNET_ErrorType kind, const char *message, ...)
+GNUNET_log_nocheck (enum GNUNET_ErrorType kind, const char *message, ...)
 {
   va_list va;
 
@@ -468,7 +653,7 @@ GNUNET_log (enum GNUNET_ErrorType kind, const char *message, ...)
  * @param ... arguments for format string
  */
 void
-GNUNET_log_from (enum GNUNET_ErrorType kind, const char *comp,
+GNUNET_log_from_nocheck (enum GNUNET_ErrorType kind, const char *comp,
                  const char *message, ...)
 {
   va_list va;
@@ -498,6 +683,8 @@ GNUNET_error_type_to_string (enum GNUNET_ErrorType kind)
     return _("INFO");
   if ((kind & GNUNET_ERROR_TYPE_DEBUG) > 0)
     return _("DEBUG");
+  if ((kind & ~GNUNET_ERROR_TYPE_BULK) == 0)
+    return _("NONE");
   return _("INVALID");
 }
 
-- 
1.7.4

