Repository: benoitc/gunicorn

Issue:
unix socket for syslog is a dgram not a stream
Hi,

 With apologies for not knowing much about gunicorn or syslog,  I was trying to use gunicorn with syslog over a socket, but i kept getting "socket.error: [Errno 91] Protocol wrong type for socket"  errors.
this change fixed it for me and makes it work on my system.

details of my system:
gunicorn is invoked as: gunicorn -b 0.0.0.0:5000 <app> --log-syslog --log-syslog-prefix=gateway --log-syslog-facility local1 --log-syslog-to unix:///hostdev/log --debug --access-logfile syslog --error-logfile syslog

gunicorn is running on a docker container running ubuntu 13.04,
but the syslog daemon is running on the host (outside the container), which is an EC2 instance running ubuntu 12.04.
/hostdev/log is a docker mount for the host's /dev/log socket.

It's not clear to me whether this fix is universal,  or if it only applies in some situations.
  sorry if this pullreq is a waste of anybody's time, but it fixes my problem so maybe it's legit!?



Discussion:

Resolving patch:
diff --git a/gunicorn/config.py b/gunicorn/config.py
--- a/gunicorn/config.py
+++ b/gunicorn/config.py
@@ -983,7 +983,7 @@ class ErrorLog(Setting):
     cli = ["--error-logfile", "--log-file"]
     meta = "FILE"
     validator = validate_string
-    default = "-"
+    default = None
     desc = """\
         The Error log file to write to.
 
@@ -1039,10 +1039,11 @@ class LogConfig(Setting):
     validator = validate_string
     default = None
     desc = """\
-The log config file to use.
-Gunicorn uses the standard Python logging module's Configuration
-file format.
-"""
+    The log config file to use.
+    Gunicorn uses the standard Python logging module's Configuration
+    file format.
+    """
+
 
 class SyslogTo(Setting):
     name = "syslog_addr"
@@ -1061,7 +1062,15 @@ class SyslogTo(Setting):
         default = "udp://localhost:514"
 
     desc = """\
-    Address to send syslog messages
+    Address to send syslog messages.
+
+    Adress are a string of the form:
+    - 'unix://PATH#TYPE' : for unix domain socket. TYPE can be 'stream'
+      for the stream driver or 'dgram' for the dgram driver. 'stream' is
+      the default.
+    - 'udp://HOST:PORT' : for UDP sockets
+    - 'tcp://HOST:PORT' : for TCP sockets
+
     """
 
 
@@ -1073,7 +1082,7 @@ class Syslog(Setting):
     action = 'store_true'
     default = False
     desc = """\
-    Log to syslog.
+    Send *Gunicorn* logs to syslog.
     """
 
 
diff --git a/gunicorn/glogging.py b/gunicorn/glogging.py
--- a/gunicorn/glogging.py
+++ b/gunicorn/glogging.py
@@ -11,8 +11,6 @@
 import socket
 import sys
 import traceback
-import threading
-
 
 from gunicorn import util
 from gunicorn.six import string_types
@@ -107,7 +105,16 @@ def __getitem__(self, k):
 def parse_syslog_address(addr):
 
     if addr.startswith("unix://"):
-        return (socket.SOCK_STREAM, addr.split("unix://")[1])
+        sock_type = socket.SOCK_STREAM
+
+        # are we using a different socket type?
+        parts = addr.split("#", 1)
+        if len(parts) == 2:
+            addr = parts[0]
+            if part[1] == "dgram":
+                sock_type = socket.SOCK_DGRAM
+
+        return (sock_type, addr.split("unix://")[1])
 
     if addr.startswith("udp://"):
         addr = addr.split("udp://")[1]
@@ -159,49 +166,39 @@ class Logger(object):
 
     def __init__(self, cfg):
         self.error_log = logging.getLogger("gunicorn.error")
+        self.error_log.propagate = False
         self.access_log = logging.getLogger("gunicorn.access")
+        self.access_log.propagate = False
         self.error_handlers = []
         self.access_handlers = []
         self.cfg = cfg
-        self.logfile = None
-        self.lock = threading.Lock()
         self.setup(cfg)
 
     def setup(self, cfg):
-        if not cfg.logconfig:
-            loglevel = self.LOG_LEVELS.get(cfg.loglevel.lower(), logging.INFO)
-            self.error_log.setLevel(loglevel)
-            self.access_log.setLevel(logging.INFO)
-
-            if cfg.errorlog != "-":
-                # if an error log file is set redirect stdout & stderr to
-                # this log file.
-                for stream in sys.stdout, sys.stderr:
-                    stream.flush()
-
-                self.logfile = open(cfg.errorlog, 'a+')
-                os.dup2(self.logfile.fileno(), sys.stdout.fileno())
-                os.dup2(self.logfile.fileno(), sys.stderr.fileno())
-
-            # set gunicorn.error handler
-            self._set_handler(self.error_log, cfg.errorlog,
-                    logging.Formatter(self.error_fmt, self.datefmt))
-
-            # set gunicorn.access handler
-            if cfg.accesslog is not None:
-                self._set_handler(self.access_log, cfg.accesslog,
-                    fmt=logging.Formatter(self.access_fmt))
-
-            # set syslog handler
-            if cfg.syslog:
-                self._set_syslog_handler(self.error_log, cfg, self.syslog_fmt)
+        loglevel = self.LOG_LEVELS.get(cfg.loglevel.lower(), logging.INFO)
+        self.error_log.setLevel(loglevel)
+        self.access_log.setLevel(logging.INFO)
 
-        else:
+        # set gunicorn.error handler
+        self._set_handler(self.error_log, cfg.errorlog,
+                logging.Formatter(self.error_fmt, self.datefmt))
+
+        # set gunicorn.access handler
+        if cfg.accesslog is not None:
+            self._set_handler(self.access_log, cfg.accesslog,
+                fmt=logging.Formatter(self.access_fmt))
+
+        # set syslog handler
+        if cfg.syslog:
+            self._set_syslog_handler(self.error_log, cfg, self.syslog_fmt)
+
+        if cfg.logconfig:
             if os.path.exists(cfg.logconfig):
                 fileConfig(cfg.logconfig, defaults=CONFIG_DEFAULTS,
                         disable_existing_loggers=False)
             else:
-                raise RuntimeError("Error: log config '%s' not found" % cfg.logconfig)
+                raise RuntimeError("Error: log config '%s' not found" %
+                        cfg.logconfig)
 
     def critical(self, msg, *args, **kwargs):
         self.error_log.critical(msg, *args, **kwargs)
@@ -286,20 +283,6 @@ def now(self):
                 now.year, now.hour, now.minute, now.second)
 
     def reopen_files(self):
-        if self.cfg.errorlog != "-":
-            # if an error log file is set redirect stdout & stderr to
-            # this log file.
-            for stream in sys.stdout, sys.stderr:
-                stream.flush()
-
-            with self.lock:
-                if self.logfile is not None:
-                    self.logfile.close()
-
-                self.logfile = open(self.cfg.errorlog, 'a+')
-                os.dup2(self.logfile.fileno(), sys.stdout.fileno())
-                os.dup2(self.logfile.fileno(), sys.stderr.fileno())
-
         for log in loggers():
             for handler in log.handlers:
                 if isinstance(handler, logging.FileHandler):
@@ -334,15 +317,16 @@ def _set_handler(self, log, output, fmt):
         if h:
             log.handlers.remove(h)
 
-        if output == "-":
-            h = logging.StreamHandler()
-        else:
-            util.check_is_writeable(output)
-            h = logging.FileHandler(output)
+        if output is not None:
+            if output == "-":
+                h = logging.StreamHandler()
+            else:
+                util.check_is_writeable(output)
+                h = logging.FileHandler(output)
 
-        h.setFormatter(fmt)
-        h._gunicorn = True
-        log.addHandler(h)
+            h.setFormatter(fmt)
+            h._gunicorn = True
+            log.addHandler(h)
 
     def _set_syslog_handler(self, log, cfg, fmt):
         # setup format


Test patch: