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,
OLPC, WIMAX, INFINIBAND, FIREWALL, ADSL, BOND, VLAN]. In addition to
these domains, the following special domains can be used: [NONE, ALL,
DEFAULT, DHCP, IP]. If an empty string is given, the log level is
changed but the current set of log domains remains unchanged.
DEFAULT, DHCP, IP]. You can also specify that some domains should
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>
</arg>
</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>
<varlistentry>
<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>DEBUG</literal>. The ERR level logs only critical
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,
TEAM, CONCHECK, DCB.</para>
<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>
</variablelist>
</para>

View file

@ -185,14 +185,19 @@
<listitem><para>
Sets how much information NetworkManager sends to the log destination (usually
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>
</varlistentry>
<varlistentry>
<term><option>--log-domains</option></term>
<listitem><para>A comma-separated list specifying which
operations are logged to the log destination (usually syslog).
By default, most domains are logging-enabled.
<listitem><para>
A comma-separated list specifying which operations are logged to the log
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>
</varlistentry>
</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))
static guint32 log_level = LOGL_INFO | LOGL_WARN | LOGL_ERR;
static guint64 log_domains = LOGD_DEFAULT;
static guint32 log_level = LOGL_INFO;
static char *log_domains;
static guint64 logging[LOGL_MAX];
static gboolean syslog_opened;
typedef struct {
@ -63,12 +64,12 @@ typedef struct {
const char *name;
} LogDesc;
static const LogDesc level_descs[] = {
{ LOGL_ERR, "ERR" },
{ LOGL_WARN | LOGL_ERR, "WARN" },
{ LOGL_INFO | LOGL_WARN | LOGL_ERR, "INFO" },
{ LOGL_DEBUG | LOGL_INFO | LOGL_WARN | LOGL_ERR, "DEBUG" },
{ 0, NULL }
static const char *level_names[LOGL_MAX] = {
/* Must be in sync with nm-logging.h */
"DEBUG",
"INFO",
"WARN",
"ERR",
};
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
nm_logging_setup (const char *level, const char *domains, GError **error)
{
char **tmp, **iter;
guint64 new_domains = 0;
guint64 new_logging[LOGL_MAX];
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 */
if (level && strlen (level)) {
gboolean found = FALSE;
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);
if (!match_log_level (level, &new_log_level, error))
return FALSE;
}
}
/* domains */
if (domains && strlen (domains)) {
char **tmp, **iter;
tmp = g_strsplit_set (domains, ", ", 0);
for (iter = tmp; iter && *iter; iter++) {
const LogDesc *diter;
gboolean found = FALSE;
guint32 domain_log_level;
guint64 bits;
char *p;
if (!strlen (*iter))
continue;
for (diter = &domain_descs[0]; diter->name; diter++) {
if (!strcasecmp (diter->name, *iter)) {
new_domains |= diter->num;
found = TRUE;
break;
p = strchr (*iter, ':');
if (p) {
*p = '\0';
if (!match_log_level (p + 1, &domain_log_level, error)) {
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 (!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) {
if (!bits) {
g_set_error (error, NM_LOGGING_ERROR, NM_LOGGING_ERROR_UNKNOWN_DOMAIN,
_("Unknown log domain '%s'"), *iter);
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);
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;
}
const char *
nm_logging_level_to_string (void)
{
const LogDesc *diter;
for (diter = &level_descs[0]; diter->name; diter++) {
if (diter->num == log_level)
return diter->name;
}
g_warn_if_reached ();
return "";
return level_names[log_level];
}
const char *
@ -227,13 +257,13 @@ nm_logging_all_levels_to_string (void)
static GString *str;
if (G_UNLIKELY (!str)) {
const LogDesc *diter;
int i;
str = g_string_new (NULL);
for (diter = &level_descs[0]; diter->name; diter++) {
for (i = 0; i < LOGL_MAX; i++) {
if (str->len)
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;
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);
for (diter = &domain_descs[0]; diter->name; diter++) {
if (diter->num & log_domains) {
if (str->len)
g_string_append_c (str, ',');
g_string_append (str, diter->name);
/* If it's set for any lower level, it will also be set for LOGL_ERR */
if (!(diter->num & logging[LOGL_ERR]))
continue;
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);
@ -283,7 +337,9 @@ nm_logging_all_domains_to_string (void)
gboolean
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
@ -300,29 +356,37 @@ _nm_log (const char *loc,
GTimeVal tv;
int syslog_level = LOG_INFO;
if (!(log_level & level) || !(log_domains & domain))
g_return_if_fail (level < LOGL_MAX);
if (!(logging[level] & domain))
return;
va_start (args, fmt);
msg = g_strdup_vprintf (fmt, args);
va_end (args);
if ((log_level & LOGL_DEBUG) && (level == LOGL_DEBUG)) {
switch (level) {
case LOGL_DEBUG:
g_get_current_time (&tv);
syslog_level = LOG_INFO;
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;
fullmsg = g_strconcat ("<info> ", msg, NULL);
} else if ((log_level & LOGL_WARN) && (level == LOGL_WARN)) {
break;
case LOGL_WARN:
syslog_level = LOG_WARNING;
fullmsg = g_strconcat ("<warn> ", msg, NULL);
} else if ((log_level & LOGL_ERR) && (level == LOGL_ERR)) {
break;
case LOGL_ERR:
syslog_level = LOG_ERR;
g_get_current_time (&tv);
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 ();
}
if (syslog_opened)
syslog (syslog_level, "%s", fullmsg);

View file

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