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 .
This commit is contained in:
benoitc 2012-12-24 15:48:46 +01:00
parent ac1af72922
commit dae4d38705
4 changed files with 125 additions and 4 deletions

View File

@ -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)

View File

@ -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.
"""

View File

@ -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)

View File

@ -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):