From dae4d38705632513bfaaef51092fcc244361ac40 Mon Sep 17 00:00:00 2001 From: benoitc Date: Mon, 24 Dec 2012 15:48:46 +0100 Subject: [PATCH] add syslog support. Add options to setup logging to syslog: - `--log-syslog`: enable syslog. It default to `/var/run/syslog` on darwin, `/var/run/log` on freebsd, `/dev/log` on openbsd and udp://localhost:514 for other platforms. - `--log-syslog-prefix: Pass the parameter to use as the program name - `--log-syslog-to`: Setup the syslog address to send message. Address startinf by udp:// will send to udp, unix:// to a unix socket and tcp:// to tcp (useful for rsyslog) fix #452 . --- gunicorn/app/base.py | 1 - gunicorn/config.py | 61 ++++++++++++++++++++++++++++++++++++++++++-- gunicorn/glogging.py | 59 ++++++++++++++++++++++++++++++++++++++++++ gunicorn/util.py | 8 +++++- 4 files changed, 125 insertions(+), 4 deletions(-) diff --git a/gunicorn/app/base.py b/gunicorn/app/base.py index f4bad21f..442a3901 100644 --- a/gunicorn/app/base.py +++ b/gunicorn/app/base.py @@ -33,7 +33,6 @@ class Application(object): self.load_config() except Exception as e: sys.stderr.write("\nError: %s\n" % str(e)) - traceback.print_exc() sys.stderr.flush() sys.exit(1) diff --git a/gunicorn/config.py b/gunicorn/config.py index b8b86f00..a0b971c2 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -22,6 +22,7 @@ from gunicorn import six from gunicorn import util KNOWN_SETTINGS = [] +PLATFORM = sys.platform def wrap_method(func): @@ -70,9 +71,9 @@ class Config(object): "prog": self.prog } parser = argparse.ArgumentParser(**kwargs) + parser.add_argument("args", nargs="*", help=argparse.SUPPRESS) keys = list(self.settings) - def sorter(k): return (self.settings[k].section, self.settings[k].order) @@ -80,7 +81,8 @@ class Config(object): for k in keys: self.settings[k].add_option(parser) - parser.add_argument("args", nargs="*", help=argparse.SUPPRESS) + + return parser @property @@ -175,6 +177,10 @@ class Setting(object): default = None short = None desc = None + nargs = None + const = None + + def __init__(self): if self.default is not None: @@ -202,6 +208,12 @@ class Setting(object): if kwargs["action"] != "store": kwargs.pop("type") + if self.nargs is not None: + kwargs["nargs"] = self.nargs + + if self.const is not None: + kwargs["const"] = self.const + parser.add_argument(*args, **kwargs) def copy(self): @@ -1217,3 +1229,48 @@ class CertFile(Setting): desc = """\ SSL certificate file """ + + +class SyslogTo(Setting): + name = "syslog_addr" + section = "Logging" + cli = ["--log-syslog-to"] + meta = "SYSLOG_ADDR" + validator = validate_string + + if PLATFORM == "darwin": + default = "unix:///var/run/syslog" + elif PLATFORM in ('freebsd', 'dragonfly', ): + default = "unix:///var/run/log" + elif PLATFORM == "openbsd": + default = "unix:///dev/log" + else: + default = "udp://localhost:514" + + desc = """\ + Address to send syslog messages + """ + +class Syslog(Setting): + name = "syslog" + section = "Logging" + cli = ["--log-syslog"] + validator = validate_bool + action = 'store_true' + default = False + desc = """\ + Log to syslog. + """ + + +class SyslogPrefix(Setting): + name = "syslog_prefix" + section = "Logging" + cli = ["--log-syslog-prefix"] + meta = "SYSLOG_PREFIX" + validator = validate_string + default = None + desc = """\ + makes gunicorn use the parameter as program-name in the + syslog entry header. + """ diff --git a/gunicorn/glogging.py b/gunicorn/glogging.py index 78ba694d..3cc635d9 100644 --- a/gunicorn/glogging.py +++ b/gunicorn/glogging.py @@ -8,6 +8,7 @@ import logging logging.Logger.manager.emittedNoHandlerWarning = 1 from logging.config import fileConfig import os +import socket import sys import traceback import threading @@ -123,6 +124,41 @@ class SafeAtoms(dict): return '-' +def parse_syslog_address(addr): + + if addr.startswith("unix://"): + return (socket.SOCK_STREAM, addr.split("unix://")[1]) + + if addr.startswith("udp://"): + addr = addr.split("udp://")[1] + socktype = socket.SOCK_DGRAM + elif addr.startswith("tcp://"): + addr = addr.split("tcp://")[1] + socktype = socket.SOCK_STREAM + else: + raise RuntimeError("invalid syslog address") + + if '[' in addr and ']' in addr: + host = addr.split(']')[0][1:].lower() + elif ':' in addr: + host = addr.split(':')[0].lower() + elif addr == "": + host = "localhost" + else: + host = addr.lower() + + addr = addr.split(']')[-1] + if ":" in addr: + port = addr.split(':', 1)[1] + if not port.isdigit(): + raise RuntimeError("%r is not a valid port number." % port) + port = int(port) + else: + port = 514 + + return (socktype, (host, port)) + + class Logger(object): LOG_LEVELS = { @@ -137,10 +173,12 @@ class Logger(object): datefmt = r"%Y-%m-%d %H:%M:%S" access_fmt = "%(message)s" + syslog_fmt = "[%(process)d] %(message)s" def __init__(self, cfg): self.error_log = logging.getLogger("gunicorn.error") self.access_log = logging.getLogger("gunicorn.access") + self.error_handlers = [] self.access_handlers = [] self.cfg = cfg @@ -165,6 +203,11 @@ class Logger(object): if cfg.accesslog is not None: self._set_handler(self.access_log, cfg.accesslog, fmt=logging.Formatter(self.access_fmt)) + + + if cfg.syslog: + self._set_syslog_handler(self.error_log, cfg, self.syslog_fmt) + else: if os.path.exists(cfg.logconfig): fileConfig(cfg.logconfig, defaults=CONFIG_DEFAULTS, @@ -296,3 +339,19 @@ class Logger(object): h.setFormatter(fmt) h._gunicorn = True log.addHandler(h) + + def _set_syslog_handler(self, log, cfg, fmt): + # setup format + if not cfg.syslog_prefix: + prefix = cfg.proc_name.replace(":", ".") + prefix = "gunicorn.%s" % prefix + fmt = logging.Formatter(r"%s: %s" % (prefix, fmt)) + + # parse syslog address + socktype, addr = parse_syslog_address(cfg.syslog_addr) + + # finally setup the syslog handler + h = logging.handlers.SysLogHandler(address=addr, socktype=socktype) + h.setFormatter(fmt) + h._gunicorn = True + log.addHandler(h) diff --git a/gunicorn/util.py b/gunicorn/util.py index a8f9c947..718e424b 100644 --- a/gunicorn/util.py +++ b/gunicorn/util.py @@ -224,6 +224,13 @@ def parse_address(netloc, default_port=8000): if netloc.startswith("unix:"): return netloc.split("unix:")[1] + if netloc.startswith("unix://"): + return netloc.split("unix://")[1] + + if netloc.startswith("tcp://"): + netloc = netloc.split("tcp://")[1] + + # get host if '[' in netloc and ']' in netloc: host = netloc.split(']')[0][1:].lower() @@ -245,7 +252,6 @@ def parse_address(netloc, default_port=8000): port = default_port return (host, port) - def get_maxfd(): maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] if (maxfd == resource.RLIM_INFINITY):