Skip to content

Commit

Permalink
feat: syslog option
Browse files Browse the repository at this point in the history
  • Loading branch information
devmaxde committed Dec 4, 2024
1 parent 3efde16 commit 8bea285
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 6 deletions.
60 changes: 54 additions & 6 deletions metricq/cli/decorator.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import logging
from typing import Callable, Optional, cast
from typing import Callable, Optional, cast, Any

import click
import click_log # type: ignore
from click import option
from click import option, Context
from dotenv import find_dotenv, load_dotenv

from .syslog import get_syslog_handler, SyslogFormatter
from .. import get_logger
from .params import MetricParam, TemplateStringParam
from .types import FC
Expand All @@ -20,6 +21,41 @@
load_dotenv(dotenv_path=find_dotenv(".metricq"), interpolate=False, override=False)


def metricq_syslog_option() -> Callable[[FC], FC]:
"""
Exposes the -\\-syslog option as a click param.
The program will try read the 'token' from the click params.
if the token is not set, the default value of 'metricq.program' will be used.
That's why the @metricq_syslog_option should be the 2nd decorator in the chain.
It is recommended to use the :py:func:`~metricq.cli.decorator.metricq_command` decorator instead of using this
function directly.
"""

def enable_syslog(ctx: Context, param: Any | None, value: Optional[str]) -> None:
if value is not None:
logger = get_logger()
if value == "":
value = None

program_name = ctx.params.get("token", "metricq.program")

handler = get_syslog_handler(value)
handler.setFormatter(SyslogFormatter(name=program_name))
logger.addHandler(handler)

return option(
"--syslog",
help="Enable syslog logging by specifying the a Unix socket or host:port for the logger. If --syslog is set "
"but no value is specified, the default of localhost:514 will be used.",
callback=enable_syslog,
expose_value=False,
is_flag=False,
flag_value="",
)


def metricq_server_option() -> Callable[[FC], FC]:
"""
Allows the User to provide a -\\-server option. This option has no input validation and therefore can be any string.
Expand Down Expand Up @@ -140,10 +176,20 @@ def metricq_command(
- -\\-token:
The Token is used to identify each program on the metricq network. for example: sink-py-dummy
The token param can be set using the environment variable METRICQ_TOKEN or adding the --token {value} option to the cli command
The token param can be set using the environment variable METRICQ_TOKEN or adding the --token {value} option
to the cli command
- -\\-syslog:
The Syslog param is used to enable syslog. It can be used with or without parameter.
If used without parameter (for example: ``metricq-check --syslog`` ) the Syslog will default to localhost:514.
You can also specify a Unix socket (for example: /dev/log) or a custom host (for example: example.com:5114)
by adding the value to the syslog flag (for example: ``metricq-check --syslog example.com:5114``)
Full example:
``metricq-check --server amqp://localhost/ --token sink-py-dummy``
``metricq-check --server amqp://localhost/ --token sink-py-dummy --syslog``
**Example**::
Expand Down Expand Up @@ -181,8 +227,10 @@ def decorator(func: FC) -> click.Command:
log_decorator(
metricq_token_option(default_token)(
metricq_server_option()(
click.command(context_settings=context_settings, epilog=epilog)(
func
metricq_syslog_option()(
click.command(
context_settings=context_settings, epilog=epilog
)(func)
)
)
)
Expand Down
32 changes: 32 additions & 0 deletions metricq/cli/syslog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import logging
import socket
import time
from logging.handlers import SysLogHandler


class SyslogFormatter(logging.Formatter):
def __init__(self, *args, name: str = "metricq", **kwargs): # type: ignore
super().__init__(*args, **kwargs)
self.program = name

def format(self, record: logging.LogRecord) -> str:
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(record.created))
hostname = socket.gethostname()
pid = record.process
program = self.program
# Custom Formatter based on rfc3164
# Format the header as "<PRI> TIMESTAMP HOSTNAME PROGRAM[PID]: MESSAGE"
# <PRI> is already beeing set by the SysLogHanlder, we only need to add the rest
syslog_header = f"{timestamp} {hostname} {program}[{pid}]: "
message = super().format(record)
return syslog_header + message


def get_syslog_handler(address: str|None) -> SysLogHandler:
if address is None:
return SysLogHandler()
elif ":" in address:
ip, port = address.split(":")
return SysLogHandler(address=(ip, int(port)))
else:
return SysLogHandler(address=address)

0 comments on commit 8bea285

Please sign in to comment.