Skip to content

Commit ec772e1

Browse files
committed
feat: syslog
1 parent 581d3ff commit ec772e1

File tree

4 files changed

+88
-6
lines changed

4 files changed

+88
-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: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33

44
import click
55
import click_log # type: ignore
6-
from click import option
6+
from click import Context, option
77
from dotenv import find_dotenv, load_dotenv
88

99
from .. import get_logger
1010
from .params import MetricParam, TemplateStringParam
11+
from .syslog import SyslogFormatter, get_syslog_handler
1112

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

2425

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

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)