Skip to content

Commit

Permalink
Merge pull request #2384 from larribas/2066-statsd-socket
Browse files Browse the repository at this point in the history
Allow reporting StatsD metrics over UDS sockets
  • Loading branch information
benoitc committed May 7, 2023
2 parents 8c1747b + 2a16fcd commit 4a1c402
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 14 deletions.
7 changes: 6 additions & 1 deletion docs/source/settings.rst
Expand Up @@ -420,7 +420,12 @@ environment variable ``PYTHONUNBUFFERED`` .

**Default:** ``None``

``host:port`` of the statsd server to log to.
The address of the StatsD server to log to.

Address is a string of the form:

* ``unix://PATH`` : for a unix domain socket.
* ``HOST:PORT`` : for a network address

.. versionadded:: 19.1

Expand Down
31 changes: 23 additions & 8 deletions gunicorn/config.py
Expand Up @@ -514,15 +514,25 @@ def validate_chdir(val):
return path


def validate_hostport(val):
def validate_statsd_address(val):
val = validate_string(val)
if val is None:
return None
elements = val.split(":")
if len(elements) == 2:
return (elements[0], int(elements[1]))
else:
raise TypeError("Value must consist of: hostname:port")

# As of major release 20, util.parse_address would recognize unix:PORT
# as a UDS address, breaking backwards compatibility. We defend against
# that regression here (this is also unit-tested).
# Feel free to remove in the next major release.
unix_hostname_regression = re.match(r'^unix:(\d+)$', val)
if unix_hostname_regression:
return ('unix', int(unix_hostname_regression.group(1)))

try:
address = util.parse_address(val, default_port='8125')
except RuntimeError:
raise TypeError("Value must be one of ('host:port', 'unix://PATH')")

return address


def validate_reload_engine(val):
Expand Down Expand Up @@ -1630,9 +1640,14 @@ class StatsdHost(Setting):
cli = ["--statsd-host"]
meta = "STATSD_ADDR"
default = None
validator = validate_hostport
validator = validate_statsd_address
desc = """\
``host:port`` of the statsd server to log to.
The address of the StatsD server to log to.
Address is a string of the form:
* ``unix://PATH`` : for a unix domain socket.
* ``HOST:PORT`` : for a network address
.. versionadded:: 19.1
"""
Expand Down
13 changes: 8 additions & 5 deletions gunicorn/instrument/statsd.py
Expand Up @@ -24,14 +24,17 @@ class Statsd(Logger):
"""statsD-based instrumentation, that passes as a logger
"""
def __init__(self, cfg):
"""host, port: statsD server
"""
Logger.__init__(self, cfg)
self.prefix = sub(r"^(.+[^.]+)\.*$", "\\g<1>.", cfg.statsd_prefix)

if isinstance(cfg.statsd_host, str):
address_family = socket.AF_UNIX
else:
address_family = socket.AF_INET

try:
host, port = cfg.statsd_host
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.connect((host, int(port)))
self.sock = socket.socket(address_family, socket.SOCK_DGRAM)
self.sock.connect(cfg.statsd_host)
except Exception:
self.sock = None

Expand Down
24 changes: 24 additions & 0 deletions tests/test_config.py
Expand Up @@ -318,6 +318,30 @@ def nworkers_changed_3(server, new_value, old_value):
assert c.nworkers_changed(1, 2, 3) == 3


def test_statsd_host():
c = config.Config()
assert c.statsd_host is None
c.set("statsd_host", "localhost")
assert c.statsd_host == ("localhost", 8125)
c.set("statsd_host", "statsd:7777")
assert c.statsd_host == ("statsd", 7777)
c.set("statsd_host", "unix:///path/to.sock")
assert c.statsd_host == "/path/to.sock"
pytest.raises(TypeError, c.set, "statsd_host", 666)
pytest.raises(TypeError, c.set, "statsd_host", "host:string")


def test_statsd_host_with_unix_as_hostname():
# This is a regression test for major release 20. After this release
# we should consider modifying the behavior of util.parse_address to
# simplify gunicorn's code
c = config.Config()
c.set("statsd_host", "unix:7777")
assert c.statsd_host == ("unix", 7777)
c.set("statsd_host", "unix://some.socket")
assert c.statsd_host == "some.socket"


def test_statsd_changes_logger():
c = config.Config()
assert c.logger_class == glogging.Logger
Expand Down
12 changes: 12 additions & 0 deletions tests/test_statsd.py
Expand Up @@ -59,6 +59,18 @@ def test_statsd_fail():
logger.exception("No impact on logging")


def test_statsd_host_initialization():
c = Config()
c.set('statsd_host', 'unix:test.sock')
logger = Statsd(c)
logger.info("Can be initialized and used with a UDS socket")

# Can be initialized and used with a UDP address
c.set('statsd_host', 'host:8080')
logger = Statsd(c)
logger.info("Can be initialized and used with a UDP socket")


def test_dogstatsd_tags():
c = Config()
tags = 'yucatan,libertine:rhubarb'
Expand Down

0 comments on commit 4a1c402

Please sign in to comment.