Skip to content

Commit 1098da0

Browse files
committed
feat: syslog
1 parent 3096f73 commit 1098da0

File tree

4 files changed

+89
-6
lines changed

4 files changed

+89
-6
lines changed

docs/api/cli.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Decorators
1717
----------
1818

1919
.. automodule:: metricq.cli.decorator
20-
:members: metricq_command, metricq_metric_option, metricq_server_option, metricq_token_option,
20+
:members: metricq_command, metricq_metric_option, metricq_server_option, metricq_syslog_option, metricq_token_option,
2121

2222

2323
Parameter

metricq/cli/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
metricq_command,
33
metricq_metric_option,
44
metricq_server_option,
5+
metricq_syslog_option,
56
metricq_token_option,
67
)
78
from .params import (
@@ -23,5 +24,6 @@
2324
"metricq_command",
2425
"metricq_metric_option",
2526
"metricq_server_option",
27+
"metricq_syslog_option",
2628
"metricq_token_option",
2729
]

metricq/cli/decorator.py

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import logging
2+
import sys
23
from typing import Any, Callable, Optional, TypeVar, Union, cast
34

45
import click
56
import click_log # type: ignore
6-
from click import option
7+
from click import Context, option
78
from dotenv import find_dotenv, load_dotenv
89

910
from .. import get_logger
1011
from .params import MetricParam, TemplateStringParam
12+
from .syslog import SyslogFormatter, get_syslog_handler
1113

1214
# We do not interpolate (i.e. replace ${VAR} with corresponding environment variables).
1315
# That is because we want to be able to interpolate ourselves for metrics and tokens
@@ -22,6 +24,41 @@
2224
FC = TypeVar("FC", bound=Union[Callable[..., Any], click.Command])
2325

2426

27+
def metricq_syslog_option() -> Callable[[FC], FC]:
28+
"""
29+
Exposes the -\\-syslog option as a click param.
30+
31+
The program will try read the 'token' from the click params.
32+
if the token is not set, the default value of 'metricq.program' will be used.
33+
That's why the @metricq_syslog_option should be the 2nd decorator in the chain.
34+
35+
It is recommended to use the :py:func:`~metricq.cli.decorator.metricq_command` decorator instead of using this
36+
function directly.
37+
"""
38+
39+
def enable_syslog(ctx: Context, param: Any | None, value: Optional[str]) -> None:
40+
if value is not None:
41+
logger = get_logger()
42+
if value == "":
43+
value = None
44+
45+
program_name = ctx.params.get("token", sys.argv[0])
46+
47+
handler = get_syslog_handler(value)
48+
handler.setFormatter(SyslogFormatter(name=program_name))
49+
logger.addHandler(handler)
50+
51+
return option(
52+
"--syslog",
53+
help="Enable syslog logging by specifying the a Unix socket or host:port for the logger. If --syslog is set "
54+
"but no value is specified, the default of localhost:514 will be used.",
55+
callback=enable_syslog,
56+
expose_value=False,
57+
is_flag=False,
58+
flag_value="",
59+
)
60+
61+
2562
def metricq_server_option() -> Callable[[FC], FC]:
2663
"""
2764
Allows the User to provide a -\\-server option. This option has no input validation and therefore can be any string.
@@ -144,10 +181,20 @@ def metricq_command(
144181
- -\\-token:
145182
The Token is used to identify each program on the metricq network. for example: sink-py-dummy
146183
147-
The token param can be set using the environment variable METRICQ_TOKEN or adding the --token {value} option to the cli command
184+
The token param can be set using the environment variable METRICQ_TOKEN or adding the --token {value} option
185+
to the cli command
186+
187+
- -\\-syslog:
188+
The Syslog param is used to enable syslog. It can be used with or without parameter.
189+
190+
If used without parameter (for example: ``metricq-check --syslog`` ) the Syslog will default to localhost:514.
191+
192+
You can also specify a Unix socket (for example: /dev/log) or a custom host (for example: example.com:514)
193+
by adding the value to the syslog flag (for example: ``metricq-check --syslog example.com:514``)
194+
148195
149196
Full example:
150-
``metricq-check --server amqp://localhost/ --token sink-py-dummy``
197+
``metricq-check --server amqp://localhost/ --token sink-py-dummy --syslog``
151198
152199
**Example**::
153200
@@ -185,8 +232,10 @@ def decorator(func: FC) -> click.Command:
185232
log_decorator(
186233
metricq_token_option(default_token)(
187234
metricq_server_option()(
188-
click.command(context_settings=context_settings, epilog=epilog)(
189-
func
235+
metricq_syslog_option()(
236+
click.command(
237+
context_settings=context_settings, epilog=epilog
238+
)(func)
190239
)
191240
)
192241
)

metricq/cli/syslog.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import logging
2+
import socket
3+
import time
4+
from logging.handlers import SysLogHandler
5+
6+
7+
class SyslogFormatter(logging.Formatter):
8+
def __init__(self, *args, name: str = "metricq", **kwargs): # type: ignore
9+
super().__init__(*args, **kwargs)
10+
self.program = name
11+
12+
def format(self, record: logging.LogRecord) -> str:
13+
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(record.created))
14+
hostname = socket.gethostname()
15+
pid = record.process
16+
program = self.program
17+
# Custom Formatter based on rfc3164
18+
# Format the header as "<PRI> TIMESTAMP HOSTNAME PROGRAM[PID]: MESSAGE"
19+
# <PRI> is already being set by the SysLogHandler, we only need to add the rest
20+
syslog_header = f"{timestamp} {hostname} {program}[{pid}]: "
21+
message = super().format(record)
22+
return syslog_header + message
23+
24+
25+
def get_syslog_handler(address: str | None) -> SysLogHandler:
26+
if address is None:
27+
return SysLogHandler()
28+
elif ":" in address:
29+
ip, port = address.split(":")
30+
return SysLogHandler(address=(ip, int(port)))
31+
else:
32+
return SysLogHandler(address=address)

0 commit comments

Comments
 (0)