From dbf09dd0556986fd749c778087097c95fc4226bf Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Thu, 12 Sep 2024 12:56:54 +0200 Subject: [PATCH 1/3] doc: Remove time from example It's not required and more confusing than helping. --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 51af733..e7b3f62 100644 --- a/README.md +++ b/README.md @@ -63,14 +63,11 @@ from datetime import datetime,timedelta client = axiom_py.Client() -time = datetime.utcnow() - timedelta(hours=1) -time_formatted = rfc3339.format(time) - client.ingest_events( dataset="my-dataset", events=[ - {"foo": "bar", "_time": time_formatted}, - {"bar": "baz", "_time": time_formatted}, + {"foo": "bar"}, + {"bar": "baz"}, ]) client.query(r"['my-dataset'] | where foo == 'bar' | limit 100") ``` From 9cc845b69518b0e5f1e0f5125116fda6a3f98006 Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Thu, 12 Sep 2024 12:57:03 +0200 Subject: [PATCH 2/3] feat: Add support for structlog --- README.md | 24 ++++++++++++++++++++++ examples/structlog.py | 26 ++++++++++++++++++++++++ src/axiom_py/logging.py | 2 +- src/axiom_py/structlog.py | 42 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 examples/structlog.py create mode 100644 src/axiom_py/structlog.py diff --git a/README.md b/README.md index e7b3f62..31bc891 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,30 @@ def setup_logger(): For a full example, see [`examples/logger.py`](examples/logger.py). +If you use [structlog](https://github.com/hynek/structlog), you can set up the +`AxiomProcessor` like this: + +```python +from axiom_py import Client +from axiom_py.structlog import AxiomProcessor + + +def setup_logger(): + client = Client() + + structlog.configure( + processors=[ + # ... + structlog.processors.add_log_level, + structlog.processors.TimeStamper(fmt="iso", key="_time"), + AxiomProcessor(client, "my-dataset"), + # ... + ] + ) +``` + +For a full example, see [`examples/structlog.py`](examples/structlog.py). + ## Contributing This project uses [uv](https://docs.astral.sh/uv) for dependency management diff --git a/examples/structlog.py b/examples/structlog.py new file mode 100644 index 0000000..21585ab --- /dev/null +++ b/examples/structlog.py @@ -0,0 +1,26 @@ +from axiom_py import Client +from axiom_py.structlog import AxiomProcessor +import structlog + + +def main(): + client = Client() + + structlog.configure( + processors=[ + structlog.contextvars.merge_contextvars, + structlog.processors.add_log_level, + structlog.processors.StackInfoRenderer(), + structlog.dev.set_exc_info, + structlog.processors.TimeStamper(fmt="iso", key="_time"), + AxiomProcessor(client, "my-dataset"), + structlog.dev.ConsoleRenderer(), + ] + ) + + log = structlog.get_logger() + log.info("hello", who="world") + + +if __name__ == "__main__": + main() diff --git a/src/axiom_py/logging.py b/src/axiom_py/logging.py index 0ff62cc..f6d0aac 100644 --- a/src/axiom_py/logging.py +++ b/src/axiom_py/logging.py @@ -12,7 +12,7 @@ class AxiomHandler(Handler): client: Client dataset: str - logcache: list + buffer: list interval: int last_run: float diff --git a/src/axiom_py/structlog.py b/src/axiom_py/structlog.py new file mode 100644 index 0000000..41c0285 --- /dev/null +++ b/src/axiom_py/structlog.py @@ -0,0 +1,42 @@ +"""Structlog contains the AxiomProcessor for structlog.""" + +from typing import List +import time +import atexit + +from .client import Client + + +class AxiomProcessor: + """A processor for sending structlogs to Axiom.""" + + client: Client + dataset: str + buffer: List[object] + interval: int + last_run: float + + def __init__(self, client: Client, dataset: str, interval=1): + self.client = client + self.dataset = dataset + self.buffer = [] + self.last_run = time.monotonic() + self.interval = interval + + atexit.register(self._flush) + + def _flush(self): + self.last_run = time.monotonic() + if len(self.buffer) == 0: + return + self.client.ingest_events(self.dataset, self.buffer) + self.buffer = [] + + def __call__(self, logger: object, method_name: str, event_dict: object): + self.buffer.append(event_dict.copy()) + if ( + len(self.buffer) >= 1000 + or time.monotonic() - self.last_run > self.interval + ): + self.flush() + return event_dict From 161b3c544e79545c99fbe6e42ab2a13702da1d37 Mon Sep 17 00:00:00 2001 From: Arne Bahlo Date: Thu, 12 Sep 2024 13:53:09 +0200 Subject: [PATCH 3/3] chore: Rename {examples/*.py => examples/*_example.py} --- examples/{client.py => client_example.py} | 0 examples/{logger.py => logger_example.py} | 0 examples/{structlog.py => structlog_example.py} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename examples/{client.py => client_example.py} (100%) rename examples/{logger.py => logger_example.py} (100%) rename examples/{structlog.py => structlog_example.py} (100%) diff --git a/examples/client.py b/examples/client_example.py similarity index 100% rename from examples/client.py rename to examples/client_example.py diff --git a/examples/logger.py b/examples/logger_example.py similarity index 100% rename from examples/logger.py rename to examples/logger_example.py diff --git a/examples/structlog.py b/examples/structlog_example.py similarity index 100% rename from examples/structlog.py rename to examples/structlog_example.py