logging: allow per-domain log level overrides

Allow specifying log domains like "DEFAULT,WIFI:DEBUG" to override the
log level on a per-domain basis.
This commit is contained in:
Dan Winship 2013-10-01 11:40:22 -04:00
parent 0e3432fbea
commit fc2a14d0f9
5 changed files with 166 additions and 88 deletions

View file

@ -226,8 +226,11 @@
VPN, SHARING, SUPPLICANT, AGENTS, SETTINGS, SUSPEND, CORE, DEVICE, VPN, SHARING, SUPPLICANT, AGENTS, SETTINGS, SUSPEND, CORE, DEVICE,
OLPC, WIMAX, INFINIBAND, FIREWALL, ADSL, BOND, VLAN]. In addition to OLPC, WIMAX, INFINIBAND, FIREWALL, ADSL, BOND, VLAN]. In addition to
these domains, the following special domains can be used: [NONE, ALL, these domains, the following special domains can be used: [NONE, ALL,
DEFAULT, DHCP, IP]. If an empty string is given, the log level is DEFAULT, DHCP, IP]. You can also specify that some domains should
changed but the current set of log domains remains unchanged. log at a different level from the default by appending a colon (':')
and a log level (eg, 'WIFI:DEBUG'). If an empty string is given, the
log level is changed but the current set of log domains remains
unchanged.
</tp:docstring> </tp:docstring>
</arg> </arg>
</method> </method>

View file

@ -270,7 +270,8 @@ unmanaged-devices=mac:00:22:68:1c:59:b1;mac:00:1E:65:30:D1:C4;interface-name:eth
<variablelist> <variablelist>
<varlistentry> <varlistentry>
<term><varname>level</varname></term> <term><varname>level</varname></term>
<listitem><para>One of <literal>ERR</literal>, <listitem><para>The default logging verbosity level.
One of <literal>ERR</literal>,
<literal>WARN</literal>, <literal>INFO</literal>, <literal>WARN</literal>, <literal>INFO</literal>,
<literal>DEBUG</literal>. The ERR level logs only critical <literal>DEBUG</literal>. The ERR level logs only critical
errors. WARN logs warnings that may reflect operation. errors. WARN logs warnings that may reflect operation.
@ -289,7 +290,10 @@ unmanaged-devices=mac:00:22:68:1c:59:b1;mac:00:1E:65:30:D1:C4;interface-name:eth
INFINIBAND, FIREWALL, ADSL, BOND, VLAN, BRIDGE, DBUS_PROPS, INFINIBAND, FIREWALL, ADSL, BOND, VLAN, BRIDGE, DBUS_PROPS,
TEAM, CONCHECK, DCB.</para> TEAM, CONCHECK, DCB.</para>
<para>In addition, these special domains can be used: NONE, <para>In addition, these special domains can be used: NONE,
ALL, DEFAULT, DHCP, IP.</para></listitem> ALL, DEFAULT, DHCP, IP.</para>
<para>You can specify per-domain log level overrides by
adding a colon and a log level to any domain. Eg,
"<literal>WIFI:DEBUG</literal>".</para></listitem>
</varlistentry> </varlistentry>
</variablelist> </variablelist>
</para> </para>

View file

@ -185,14 +185,19 @@
<listitem><para> <listitem><para>
Sets how much information NetworkManager sends to the log destination (usually Sets how much information NetworkManager sends to the log destination (usually
syslog's "daemon" facility). By default, only informational, warning, and error syslog's "daemon" facility). By default, only informational, warning, and error
messages are logged. messages are logged. See the section on <literal>logging</literal> in
<citerefentry><refentrytitle>NetworkManager.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more information.
</para></listitem> </para></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><option>--log-domains</option></term> <term><option>--log-domains</option></term>
<listitem><para>A comma-separated list specifying which <listitem><para>
operations are logged to the log destination (usually syslog). A comma-separated list specifying which operations are logged to the log
By default, most domains are logging-enabled. destination (usually syslog). By default, most domains are logging-enabled.
See the section on <literal>logging</literal> in
<citerefentry><refentrytitle>NetworkManager.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more information.
</para></listitem> </para></listitem>
</varlistentry> </varlistentry>
</variablelist> </variablelist>

View file

@ -54,8 +54,9 @@ nm_log_handler (const gchar *log_domain,
#define LOGD_DEFAULT (LOGD_ALL & ~(LOGD_WIFI_SCAN | LOGD_DBUS_PROPS)) #define LOGD_DEFAULT (LOGD_ALL & ~(LOGD_WIFI_SCAN | LOGD_DBUS_PROPS))
static guint32 log_level = LOGL_INFO | LOGL_WARN | LOGL_ERR; static guint32 log_level = LOGL_INFO;
static guint64 log_domains = LOGD_DEFAULT; static char *log_domains;
static guint64 logging[LOGL_MAX];
static gboolean syslog_opened; static gboolean syslog_opened;
typedef struct { typedef struct {
@ -63,12 +64,12 @@ typedef struct {
const char *name; const char *name;
} LogDesc; } LogDesc;
static const LogDesc level_descs[] = { static const char *level_names[LOGL_MAX] = {
{ LOGL_ERR, "ERR" }, /* Must be in sync with nm-logging.h */
{ LOGL_WARN | LOGL_ERR, "WARN" }, "DEBUG",
{ LOGL_INFO | LOGL_WARN | LOGL_ERR, "INFO" }, "INFO",
{ LOGL_DEBUG | LOGL_INFO | LOGL_WARN | LOGL_ERR, "DEBUG" }, "WARN",
{ 0, NULL } "ERR",
}; };
static const LogDesc domain_descs[] = { static const LogDesc domain_descs[] = {
@ -130,95 +131,124 @@ nm_logging_error_quark (void)
/************************************************************************/ /************************************************************************/
static gboolean
match_log_level (const char *level,
guint32 *out_level,
GError **error)
{
int i;
for (i = 0; i < LOGL_MAX; i++) {
if (!strcasecmp (level_names[i], level)) {
*out_level = i;
return TRUE;
}
}
g_set_error (error, NM_LOGGING_ERROR, NM_LOGGING_ERROR_UNKNOWN_LEVEL,
_("Unknown log level '%s'"), level);
return FALSE;
}
gboolean gboolean
nm_logging_setup (const char *level, const char *domains, GError **error) nm_logging_setup (const char *level, const char *domains, GError **error)
{ {
char **tmp, **iter; guint64 new_logging[LOGL_MAX];
guint64 new_domains = 0; guint32 new_log_level = log_level;
int i;
if (!domains)
domains = log_domains ? log_domains : "DEFAULT";
for (i = 0; i < LOGL_MAX; i++)
new_logging[i] = 0;
/* levels */ /* levels */
if (level && strlen (level)) { if (level && strlen (level)) {
gboolean found = FALSE; if (!match_log_level (level, &new_log_level, error))
const LogDesc *diter;
for (diter = &level_descs[0]; diter->name; diter++) {
if (!strcasecmp (diter->name, level)) {
log_level = diter->num;
found = TRUE;
break;
}
}
if (!found) {
g_set_error (error, NM_LOGGING_ERROR, NM_LOGGING_ERROR_UNKNOWN_LEVEL,
_("Unknown log level '%s'"), level);
return FALSE; return FALSE;
}
} }
/* domains */ /* domains */
if (domains && strlen (domains)) { if (domains && strlen (domains)) {
char **tmp, **iter;
tmp = g_strsplit_set (domains, ", ", 0); tmp = g_strsplit_set (domains, ", ", 0);
for (iter = tmp; iter && *iter; iter++) { for (iter = tmp; iter && *iter; iter++) {
const LogDesc *diter; const LogDesc *diter;
gboolean found = FALSE; guint32 domain_log_level;
guint64 bits;
char *p;
if (!strlen (*iter)) if (!strlen (*iter))
continue; continue;
for (diter = &domain_descs[0]; diter->name; diter++) { p = strchr (*iter, ':');
if (!strcasecmp (diter->name, *iter)) { if (p) {
new_domains |= diter->num; *p = '\0';
found = TRUE; if (!match_log_level (p + 1, &domain_log_level, error)) {
break; g_strfreev (tmp);
return FALSE;
}
} else
domain_log_level = new_log_level;
bits = 0;
/* Check for combined domains */
if (!strcasecmp (*iter, LOGD_ALL_STRING))
bits = LOGD_ALL;
else if (!strcasecmp (*iter, LOGD_DEFAULT_STRING))
bits = LOGD_DEFAULT;
else if (!strcasecmp (*iter, LOGD_DHCP_STRING))
bits = LOGD_DHCP;
else if (!strcasecmp (*iter, LOGD_IP_STRING))
bits = LOGD_IP;
/* Check for compatibility domains */
else if (!strcasecmp (*iter, "HW"))
bits = LOGD_PLATFORM;
else {
for (diter = &domain_descs[0]; diter->name; diter++) {
if (!strcasecmp (diter->name, *iter)) {
bits = diter->num;
break;
}
} }
} }
/* Check for combined domains */ if (!bits) {
if (!strcasecmp (*iter, LOGD_ALL_STRING)) {
new_domains = LOGD_ALL;
found = TRUE;
} else if (!strcasecmp (*iter, LOGD_DEFAULT_STRING)) {
new_domains = LOGD_DEFAULT;
found = TRUE;
} else if (!strcasecmp (*iter, LOGD_DHCP_STRING)) {
new_domains |= LOGD_DHCP;
found = TRUE;
} else if (!strcasecmp (*iter, LOGD_IP_STRING)) {
new_domains |= LOGD_IP;
found = TRUE;
}
/* Check for compatibility domains */
if (!strcasecmp (*iter, "HW")) {
new_domains |= LOGD_PLATFORM;
found = TRUE;
}
if (!found) {
g_set_error (error, NM_LOGGING_ERROR, NM_LOGGING_ERROR_UNKNOWN_DOMAIN, g_set_error (error, NM_LOGGING_ERROR, NM_LOGGING_ERROR_UNKNOWN_DOMAIN,
_("Unknown log domain '%s'"), *iter); _("Unknown log domain '%s'"), *iter);
return FALSE; return FALSE;
} }
for (i = 0; i < domain_log_level; i++)
new_logging[i] &= ~bits;
for (i = domain_log_level; i < LOGL_MAX; i++)
new_logging[i] |= bits;
} }
g_strfreev (tmp); g_strfreev (tmp);
log_domains = new_domains;
if (log_domains != (char *)domains) {
g_free (log_domains);
log_domains = g_strdup (domains);
}
for (i = 0; i < LOGL_MAX; i++)
logging[i] = new_logging[i];
} }
log_level = new_log_level;
return TRUE; return TRUE;
} }
const char * const char *
nm_logging_level_to_string (void) nm_logging_level_to_string (void)
{ {
const LogDesc *diter; return level_names[log_level];
for (diter = &level_descs[0]; diter->name; diter++) {
if (diter->num == log_level)
return diter->name;
}
g_warn_if_reached ();
return "";
} }
const char * const char *
@ -227,13 +257,13 @@ nm_logging_all_levels_to_string (void)
static GString *str; static GString *str;
if (G_UNLIKELY (!str)) { if (G_UNLIKELY (!str)) {
const LogDesc *diter; int i;
str = g_string_new (NULL); str = g_string_new (NULL);
for (diter = &level_descs[0]; diter->name; diter++) { for (i = 0; i < LOGL_MAX; i++) {
if (str->len) if (str->len)
g_string_append_c (str, ','); g_string_append_c (str, ',');
g_string_append (str, diter->name); g_string_append (str, level_names[i]);
} }
} }
@ -245,13 +275,37 @@ nm_logging_domains_to_string (void)
{ {
const LogDesc *diter; const LogDesc *diter;
GString *str; GString *str;
int i;
/* We don't just return g_strdup (log_domains) because we want to expand
* "DEFAULT" and "ALL".
*/
str = g_string_sized_new (75); str = g_string_sized_new (75);
for (diter = &domain_descs[0]; diter->name; diter++) { for (diter = &domain_descs[0]; diter->name; diter++) {
if (diter->num & log_domains) { /* If it's set for any lower level, it will also be set for LOGL_ERR */
if (str->len) if (!(diter->num & logging[LOGL_ERR]))
g_string_append_c (str, ','); continue;
g_string_append (str, diter->name);
if (str->len)
g_string_append_c (str, ',');
g_string_append (str, diter->name);
/* Check if it's logging at a lower level than the default. */
for (i = 0; i < log_level; i++) {
if (diter->num & logging[i]) {
g_string_append_printf (str, ":%s", level_names[i]);
break;
}
}
/* Check if it's logging at a higher level than the default. */
if (!(diter->num & logging[log_level])) {
for (i = log_level + 1; i < LOGL_MAX; i++) {
if (diter->num & logging[i]) {
g_string_append_printf (str, ":%s", level_names[i]);
break;
}
}
} }
} }
return g_string_free (str, FALSE); return g_string_free (str, FALSE);
@ -283,7 +337,9 @@ nm_logging_all_domains_to_string (void)
gboolean gboolean
nm_logging_enabled (guint32 level, guint64 domain) nm_logging_enabled (guint32 level, guint64 domain)
{ {
return !!(log_level & level) && !!(log_domains & domain); g_return_val_if_fail (level < LOGL_MAX, FALSE);
return !!(logging[level] & domain);
} }
void void
@ -300,29 +356,37 @@ _nm_log (const char *loc,
GTimeVal tv; GTimeVal tv;
int syslog_level = LOG_INFO; int syslog_level = LOG_INFO;
if (!(log_level & level) || !(log_domains & domain)) g_return_if_fail (level < LOGL_MAX);
if (!(logging[level] & domain))
return; return;
va_start (args, fmt); va_start (args, fmt);
msg = g_strdup_vprintf (fmt, args); msg = g_strdup_vprintf (fmt, args);
va_end (args); va_end (args);
if ((log_level & LOGL_DEBUG) && (level == LOGL_DEBUG)) { switch (level) {
case LOGL_DEBUG:
g_get_current_time (&tv); g_get_current_time (&tv);
syslog_level = LOG_INFO; syslog_level = LOG_INFO;
fullmsg = g_strdup_printf ("<debug> [%ld.%ld] [%s] %s(): %s", tv.tv_sec, tv.tv_usec, loc, func, msg); fullmsg = g_strdup_printf ("<debug> [%ld.%ld] [%s] %s(): %s", tv.tv_sec, tv.tv_usec, loc, func, msg);
} else if ((log_level & LOGL_INFO) && (level == LOGL_INFO)) { break;
case LOGL_INFO:
syslog_level = LOG_INFO; syslog_level = LOG_INFO;
fullmsg = g_strconcat ("<info> ", msg, NULL); fullmsg = g_strconcat ("<info> ", msg, NULL);
} else if ((log_level & LOGL_WARN) && (level == LOGL_WARN)) { break;
case LOGL_WARN:
syslog_level = LOG_WARNING; syslog_level = LOG_WARNING;
fullmsg = g_strconcat ("<warn> ", msg, NULL); fullmsg = g_strconcat ("<warn> ", msg, NULL);
} else if ((log_level & LOGL_ERR) && (level == LOGL_ERR)) { break;
case LOGL_ERR:
syslog_level = LOG_ERR; syslog_level = LOG_ERR;
g_get_current_time (&tv); g_get_current_time (&tv);
fullmsg = g_strdup_printf ("<error> [%ld.%ld] [%s] %s(): %s", tv.tv_sec, tv.tv_usec, loc, func, msg); fullmsg = g_strdup_printf ("<error> [%ld.%ld] [%s] %s(): %s", tv.tv_sec, tv.tv_usec, loc, func, msg);
} else break;
default:
g_assert_not_reached (); g_assert_not_reached ();
}
if (syslog_opened) if (syslog_opened)
syslog (syslog_level, "%s", fullmsg); syslog (syslog_level, "%s", fullmsg);

View file

@ -70,10 +70,12 @@ enum {
/* Log levels */ /* Log levels */
enum { enum {
LOGL_ERR = 1, LOGL_DEBUG,
LOGL_WARN = 2, LOGL_INFO,
LOGL_INFO = 3, LOGL_WARN,
LOGL_DEBUG = 4 LOGL_ERR,
LOGL_MAX
}; };
typedef enum { typedef enum {