commit a3f9647832dab69c5eda8047748d0de534fac073
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Dec 31 12:54:49 2011 -0800
Routing all logging through stem.util.log
Back and forth I go - first I wrote my own logging then used the builtin python
logging module. Now I'm going to a hybrid of the two. The builtin logging
module is rich but somewhat hard to customize without a wrapper.
Changes we want on the logging module are aliased commands, additional
runlevels ('NOTICE' and 'TRACE'), and logging sends complaints to stdout if
there isn't a listener (very, very dumb of its author and I've been working
around this via a __init__ module hack).
Functionally this does not change anything. I'll be changing the modules to
take advantage of these new runlevels later.
---
stem/connection.py | 18 +++++-------
stem/socket.py | 27 +++++++++----------
stem/util/__init__.py | 2 +-
stem/util/conf.py | 23 ++++++----------
stem/util/log.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++
stem/util/proc.py | 10 ++-----
stem/util/system.py | 22 +++++++--------
stem/version.py | 3 --
8 files changed, 114 insertions(+), 61 deletions(-)
diff --git a/stem/connection.py b/stem/connection.py
index eb5da62..7cb76be 100644
--- a/stem/connection.py
+++ b/stem/connection.py
@@ -82,15 +82,13 @@ AuthenticationFailure - Base exception raised for authentication failures.
import os
import getpass
-import logging
import binascii
import stem.socket
import stem.version
import stem.util.enum
import stem.util.system
-
-LOGGER = logging.getLogger("stem")
+import stem.util.log as log
# enums representing classes that the connect_* methods can return
Controller = stem.util.enum.Enum("NONE")
@@ -375,7 +373,7 @@ def authenticate(control_socket, password = None, protocolinfo_response = None):
exc_msg = "unrecognized authentication method%s (%s)" % (plural_label, methods_label)
auth_exceptions.append(UnrecognizedAuthMethods(exc_msg, unknown_methods))
else:
- LOGGER.debug("Authenticating to a socket with unrecognized auth method%s, ignoring them: %s" % (plural_label, methods_label))
+ log.debug("Authenticating to a socket with unrecognized auth method%s, ignoring them: %s" % (plural_label, methods_label))
if AuthMethod.COOKIE in auth_methods and protocolinfo_response.cookie_path == None:
auth_methods.remove(AuthMethod.COOKIE)
@@ -405,12 +403,12 @@ def authenticate(control_socket, password = None, protocolinfo_response = None):
except PasswordAuthRejected, exc:
# Since the PROTOCOLINFO says password auth is available we can assume
# that if PasswordAuthRejected is raised it's being raised in error.
- LOGGER.debug("The authenticate_password method raised a PasswordAuthRejected when password auth should be available. Stem may need to be corrected to recognize this response: %s" % exc)
+ log.debug("The authenticate_password method raised a PasswordAuthRejected when password auth should be available. Stem may need to be corrected to recognize this response: %s" % exc)
auth_exceptions.append(IncorrectPassword(str(exc)))
except (IncorrectCookieSize, UnreadableCookieFile, IncorrectCookieValue), exc:
auth_exceptions.append(exc)
except CookieAuthRejected, exc:
- LOGGER.debug("The authenticate_cookie method raised a CookieAuthRejected when cookie auth should be available. Stem may need to be corrected to recognize this response: %s" % exc)
+ log.debug("The authenticate_cookie method raised a CookieAuthRejected when cookie auth should be available. Stem may need to be corrected to recognize this response: %s" % exc)
auth_exceptions.append(IncorrectCookieValue(str(exc)))
except stem.socket.ControllerError, exc:
auth_exceptions.append(AuthenticationFailure(str(exc)))
@@ -678,7 +676,7 @@ def _expand_cookie_path(protocolinfo_response, pid_resolver, pid_resolution_arg)
}
pid_resolver_label = resolver_labels.get(pid_resolver, "")
- LOGGER.debug("unable to expand relative tor cookie path%s: %s" % (pid_resolver_label, exc))
+ log.debug("unable to expand relative tor cookie path%s: %s" % (pid_resolver_label, exc))
protocolinfo_response.cookie_path = cookie_path
@@ -774,7 +772,7 @@ class ProtocolInfoResponse(stem.socket.ControlMessage):
# an effort to parse like a v1 response.
if self.protocol_version != 1:
- LOGGER.warn("We made a PROTOCOLINFO v1 query but got a version %i response instead. We'll still try to use it, but this may cause problems." % self.protocol_version)
+ log.warn("We made a PROTOCOLINFO v1 query but got a version %i response instead. We'll still try to use it, but this may cause problems." % self.protocol_version)
elif line_type == "AUTH":
# Line format:
# AuthLine = "250-AUTH" SP "METHODS=" AuthMethod *("," AuthMethod)
@@ -796,7 +794,7 @@ class ProtocolInfoResponse(stem.socket.ControlMessage):
auth_methods.append(AuthMethod.COOKIE)
else:
unknown_auth_methods.append(method)
- LOGGER.info("PROTOCOLINFO response had an unrecognized authentication method: %s" % method)
+ log.info("PROTOCOLINFO response had an unrecognized authentication method: %s" % method)
# our auth_methods should have a single AuthMethod.UNKNOWN entry if
# any unknown authentication methods exist
@@ -825,7 +823,7 @@ class ProtocolInfoResponse(stem.socket.ControlMessage):
except ValueError, exc:
raise stem.socket.ProtocolError(exc)
else:
- LOGGER.debug("unrecognized PROTOCOLINFO line type '%s', ignoring entry: %s" % (line_type, line))
+ log.debug("unrecognized PROTOCOLINFO line type '%s', ignoring entry: %s" % (line_type, line))
self.auth_methods = tuple(auth_methods)
self.unknown_auth_methods = tuple(unknown_auth_methods)
diff --git a/stem/socket.py b/stem/socket.py
index aa094ed..961fece 100644
--- a/stem/socket.py
+++ b/stem/socket.py
@@ -44,10 +44,9 @@ ControllerError - Base exception raised when using the controller.
from __future__ import absolute_import
import re
import socket
-import logging
import threading
-LOGGER = logging.getLogger("stem.socket")
+import stem.util.log as log
KEY_ARG = re.compile("^(\S+)=")
@@ -657,19 +656,19 @@ def send_message(control_file, message, raw = False):
# uses a newline divider if this is a multi-line message (more readable)
log_message = message.replace("\r\n", "\n").rstrip()
div = "\n" if "\n" in log_message else " "
- LOGGER.debug("Sending:" + div + log_message)
+ log.debug("Sending:" + div + log_message)
try:
control_file.write(message)
control_file.flush()
except socket.error, exc:
- LOGGER.info("Failed to send message: %s" % exc)
+ log.info("Failed to send message: %s" % exc)
raise SocketError(exc)
except AttributeError:
# if the control_file has been closed then flush will receive:
# AttributeError: 'NoneType' object has no attribute 'sendall'
- LOGGER.info("Failed to send message: file has been closed")
+ log.info("Failed to send message: file has been closed")
raise SocketClosed("file has been closed")
def recv_message(control_file):
@@ -698,13 +697,13 @@ def recv_message(control_file):
# if the control_file has been closed then we will receive:
# AttributeError: 'NoneType' object has no attribute 'recv'
- LOGGER.warn("SocketClosed: socket file has been closed")
+ log.warn("SocketClosed: socket file has been closed")
raise SocketClosed("socket file has been closed")
except socket.error, exc:
# when disconnected we get...
# socket.error: [Errno 107] Transport endpoint is not connected
- LOGGER.warn("SocketClosed: received an exception (%s)" % exc)
+ log.warn("SocketClosed: received an exception (%s)" % exc)
raise SocketClosed(exc)
raw_content += line
@@ -716,16 +715,16 @@ def recv_message(control_file):
# if the socket is disconnected then the readline() method will provide
# empty content
- LOGGER.warn("SocketClosed: empty socket content")
+ log.warn("SocketClosed: empty socket content")
raise SocketClosed("Received empty socket content.")
elif len(line) < 4:
- LOGGER.warn("ProtocolError: line too short (%s)" % line)
+ log.warn("ProtocolError: line too short (%s)" % line)
raise ProtocolError("Badly formatted reply line: too short")
elif not re.match(r'^[a-zA-Z0-9]{3}[-+ ]', line):
- LOGGER.warn("ProtocolError: malformed status code/divider (%s)" % line)
+ log.warn("ProtocolError: malformed status code/divider (%s)" % line)
raise ProtocolError("Badly formatted reply line: beginning is malformed")
elif not line.endswith("\r\n"):
- LOGGER.warn("ProtocolError: no CRLF linebreak (%s)" % line)
+ log.warn("ProtocolError: no CRLF linebreak (%s)" % line)
raise ProtocolError("All lines should end with CRLF")
line = line[:-2] # strips off the CRLF
@@ -741,7 +740,7 @@ def recv_message(control_file):
# uses a newline divider if this is a multi-line message (more readable)
log_message = raw_content.replace("\r\n", "\n").rstrip()
div = "\n" if "\n" in log_message else " "
- LOGGER.debug("Received:" + div + log_message)
+ log.debug("Received:" + div + log_message)
return ControlMessage(parsed_content, raw_content)
elif divider == "+":
@@ -755,7 +754,7 @@ def recv_message(control_file):
raw_content += line
if not line.endswith("\r\n"):
- LOGGER.warn("ProtocolError: no CRLF linebreak for data entry (%s)" % line)
+ log.warn("ProtocolError: no CRLF linebreak for data entry (%s)" % line)
raise ProtocolError("All lines should end with CRLF")
elif line == ".\r\n":
break # data block termination
@@ -776,7 +775,7 @@ def recv_message(control_file):
else:
# this should never be reached due to the prefix regex, but might as well
# be safe...
- LOGGER.warn("ProtocolError: unrecognized divider type (%s)" % line)
+ log.warn("ProtocolError: unrecognized divider type (%s)" % line)
raise ProtocolError("Unrecognized type '%s': %s" % (divider, line))
def send_formatting(message):
diff --git a/stem/util/__init__.py b/stem/util/__init__.py
index 42569c9..14692fe 100644
--- a/stem/util/__init__.py
+++ b/stem/util/__init__.py
@@ -16,5 +16,5 @@ stem_logger = logging.getLogger("stem")
if not stem_logger.handlers:
stem_logger.addHandler(NullHandler())
-__all__ = ["conf", "enum", "proc", "system", "term"]
+__all__ = ["conf", "enum", "log", "proc", "system", "term"]
diff --git a/stem/util/conf.py b/stem/util/conf.py
index 2bfa6e1..7111149 100644
--- a/stem/util/conf.py
+++ b/stem/util/conf.py
@@ -26,10 +26,10 @@ Config - Custom configuration.
+- get_int_csv - gets a value as a comma separated list of integers
"""
-import logging
import threading
-LOGGER = logging.getLogger("stem")
+import stem.util.log as log
+
CONFS = {} # mapping of identifier to singleton instances of configs
# TODO: methods that will be needed if we want to allow for runtime
@@ -303,20 +303,17 @@ class Config():
if val.lower() == "true": val = True
elif val.lower() == "false": val = False
else:
- msg = "Config entry '%s' is expected to be a boolean, defaulting to '%s'" % (key, str(default))
- LOGGER.info(msg)
+ log.info("Config entry '%s' is expected to be a boolean, defaulting to '%s'" % (key, str(default)))
val = default
elif isinstance(default, int):
try: val = int(val)
except ValueError:
- msg = "Config entry '%s' is expected to be an integer, defaulting to '%i'" % (key, default)
- LOGGER.info(msg)
+ log.info("Config entry '%s' is expected to be an integer, defaulting to '%i'" % (key, default))
val = default
elif isinstance(default, float):
try: val = float(val)
except ValueError:
- msg = "Config entry '%s' is expected to be a float, defaulting to '%f'" % (key, default)
- LOGGER.info(msg)
+ log.info("Config entry '%s' is expected to be a float, defaulting to '%f'" % (key, default))
val = default
elif isinstance(default, list):
pass # nothing special to do (already a list)
@@ -329,8 +326,7 @@ class Config():
entryKey, entryVal = entry.split("=>", 1)
valMap[entryKey.strip()] = entryVal.strip()
else:
- msg = "Ignoring invalid %s config entry (expected a mapping, but \"%s\" was missing \"=>\")" % (key, entry)
- LOGGER.info(msg)
+ log.info("Ignoring invalid %s config entry (expected a mapping, but \"%s\" was missing \"=>\")" % (key, entry))
val = valMap
return val
@@ -357,8 +353,7 @@ class Config():
if not multiple: val = val[-1]
self._requested_keys.add(key)
else:
- msg = "config entry '%s' not found, defaulting to '%s'" % (key, default)
- LOGGER.debug(msg)
+ log.debug("config entry '%s' not found, defaulting to '%s'" % (key, default))
val = default
self._contents_lock.release()
@@ -392,7 +387,7 @@ class Config():
defaultStr = ", ".join([str(i) for i in default])
msg += ", defaulting to '%s'" % defaultStr
- LOGGER.info(msg)
+ log.info(msg)
return default
return conf_comp
@@ -439,7 +434,7 @@ class Config():
break
if error_msg:
- LOGGER.info(error_msg)
+ log.info(error_msg)
return default
else: return [int(val) for val in conf_comp]
diff --git a/stem/util/log.py b/stem/util/log.py
new file mode 100644
index 0000000..6476a67
--- /dev/null
+++ b/stem/util/log.py
@@ -0,0 +1,70 @@
+"""
+Functions to aid library logging. Default logging is usually NOTICE and above,
+runlevels being used as follows...
+
+ ERROR - critical issue occured, the user needs to be notified
+ WARN - non-critical issue occured that the user should be aware of
+ NOTICE - information that is helpful to the user
+ INFO - high level library activity
+ DEBUG - low level library activity
+ TRACE - request/reply logging
+"""
+
+import logging
+
+import stem.util.enum
+
+# Logging runlevels. These are *very* commonly used so including shorter
+# aliases (so they can be referenced as log.DEBUG, log.WARN, etc).
+
+Runlevel = stem.util.enum.Enum(
+ ("TRACE", "TRACE"), ("DEBUG", "DEBUG"), ("INFO", "INFO"),
+ ("NOTICE", "NOTICE"), ("WARN", "WARN"), ("ERROR", "ERROR"))
+
+TRACE, DEBUG, INFO, NOTICE, WARN, ERR = list(Runlevel)
+
+# mapping of runlevels to the logger module's values, TRACE and DEBUG aren't
+# built into the module
+
+LOG_VALUES = {
+ Runlevel.TRACE: logging.DEBUG - 5,
+ Runlevel.DEBUG: logging.DEBUG,
+ Runlevel.INFO: logging.INFO,
+ Runlevel.NOTICE: logging.INFO + 5,
+ Runlevel.WARN: logging.WARN,
+ Runlevel.ERROR: logging.ERROR,
+}
+
+LOGGER = logging.getLogger("stem")
+
+def get_logger():
+ """
+ Provides the stem logger.
+
+ Returns:
+ logging.Logger for stem
+ """
+
+ return LOGGER
+
+def log(runlevel, message):
+ """
+ Logs a message at the given runlevel.
+
+ Arguments:
+ runlevel (Runlevel) - runlevel to log the message at, logging is skipped if
+ None
+ message (str) - message to be logged
+ """
+
+ if runlevel:
+ LOGGER.log(LOG_VALUES[runlevel], message)
+
+# shorter aliases for logging at a runlevel
+def trace(message): log(Runlevel.TRACE, message)
+def debug(message): log(Runlevel.DEBUG, message)
+def info(message): log(Runlevel.INFO, message)
+def notice(message): log(Runlevel.NOTICE, message)
+def warn(message): log(Runlevel.WARN, message)
+def error(message): log(Runlevel.ERROR, message)
+
diff --git a/stem/util/proc.py b/stem/util/proc.py
index 32ab920..bf91ace 100644
--- a/stem/util/proc.py
+++ b/stem/util/proc.py
@@ -27,11 +27,9 @@ import sys
import time
import socket
import base64
-import logging
import stem.util.enum
-
-LOGGER = logging.getLogger("stem")
+import stem.util.log as log
# cached system values
IS_PROC_AVAILABLE, SYS_START_TIME, SYS_PHYSICAL_MEMORY = None, None, None
@@ -441,8 +439,7 @@ def _log_runtime(parameter, proc_location, start_time):
"""
runtime = time.time() - start_time
- msg = "proc call (%s): %s (runtime: %0.4f)" % (parameter, proc_location, runtime)
- LOGGER.debug(msg)
+ log.debug("proc call (%s): %s (runtime: %0.4f)" % (parameter, proc_location, runtime))
def _log_failure(parameter, exc):
"""
@@ -453,6 +450,5 @@ def _log_failure(parameter, exc):
exc (Exception) - exception that we're raising
"""
- msg = "proc call failed (%s): %s" % (parameter, exc)
- LOGGER.debug(msg)
+ log.debug("proc call failed (%s): %s" % (parameter, exc))
diff --git a/stem/util/system.py b/stem/util/system.py
index bb50093..86a2e27 100644
--- a/stem/util/system.py
+++ b/stem/util/system.py
@@ -18,12 +18,10 @@ call - runs the given system command and provides back the results
import os
import time
-import logging
import subprocess
import stem.util.proc
-
-LOGGER = logging.getLogger("stem")
+import stem.util.log as log
# Functor for mocking the call function, which should either return a string
# (or None) to emulate responses, or a boolean to indicate if we should filter
@@ -247,7 +245,7 @@ def get_pid_by_name(process_name):
pid = results[0].strip()
if pid.isdigit(): return int(pid)
- LOGGER.debug("failed to resolve a pid for %s" % process_name)
+ log.debug("failed to resolve a pid for %s" % process_name)
return None
def get_pid_by_port(port):
@@ -426,11 +424,11 @@ def get_cwd(pid):
results = call(GET_CWD_PWDX % pid)
if not results:
- LOGGER.debug("%s pwdx didn't return any results" % logging_prefix)
+ log.debug("%s pwdx didn't return any results" % logging_prefix)
elif results[0].endswith("No such process"):
- LOGGER.debug("%s pwdx processes reported for this pid" % logging_prefix)
+ log.debug("%s pwdx processes reported for this pid" % logging_prefix)
elif len(results) != 1 or results[0].count(" ") != 1 or not results[0].startswith("%s: " % pid):
- LOGGER.debug("%s we got unexpected output from pwdx: %s" % (logging_prefix, results))
+ log.debug("%s we got unexpected output from pwdx: %s" % (logging_prefix, results))
else:
return results[0].split(" ", 1)[1].strip()
@@ -455,7 +453,7 @@ def get_cwd(pid):
if results and len(results) == 2 and results[1].startswith("n/"):
return results[1][1:].strip()
else:
- LOGGER.debug("%s we got unexpected output from lsof: %s" % (logging_prefix, results))
+ log.debug("%s we got unexpected output from lsof: %s" % (logging_prefix, results))
return None # all queries failed
@@ -489,9 +487,9 @@ def get_bsd_jail_id(pid):
os_name = os.uname()[0]
if os_name == "FreeBSD":
- LOGGER.warn("Failed to figure out the FreeBSD jail id for pid %s. Guessing that it's not in a jail." % pid)
+ log.warn("Failed to figure out the FreeBSD jail id for pid %s. Guessing that it's not in a jail." % pid)
else:
- LOGGER.debug("get_bsd_jail_id(%s): this function isn't supported on %s" % (pid, os_name))
+ log.debug("get_bsd_jail_id(%s): this function isn't supported on %s" % (pid, os_name))
return 0
@@ -592,12 +590,12 @@ def call(command, suppress_exc = True):
msg = "system call: %s (runtime: %0.2f)" % (command, runtime)
if stderr: msg += "\nstderr: %s" % stderr
- LOGGER.debug(msg)
+ log.debug(msg)
if stdout: return stdout.split("\n")
else: return []
except OSError, exc:
- LOGGER.debug("system call (failed): %s (error: %s)" % (command, exc))
+ log.debug("system call (failed): %s (error: %s)" % (command, exc))
if suppress_exc: return None
else: raise exc
diff --git a/stem/version.py b/stem/version.py
index b984125..0b57301 100644
--- a/stem/version.py
+++ b/stem/version.py
@@ -19,13 +19,10 @@ Requirement - Enumerations for the version requirements of features.
"""
import re
-import logging
import stem.util.enum
import stem.util.system
-LOGGER = logging.getLogger("stem")
-
# cache for the get_tor_version function
VERSION_CACHE = {}