structlog-throttling 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of structlog-throttling might be problematic. Click here for more details.

@@ -0,0 +1,3 @@
1
+ __title__ = "structlog-throttling"
2
+ __author__ = "Tomás Farías Santana"
3
+ __copyright__ = "Copyright (c) 2025 Tomás Farías Santana"
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import typing
5
+
6
+ from structlog import DropEvent
7
+ from structlog.typing import EventDict
8
+
9
+ from .throttlers import ThrottlerProtocol, TimeThrottler
10
+
11
+ _T = typing.TypeVar("_T", bound=ThrottlerProtocol)
12
+
13
+ __all__ = [
14
+ "LogTimeThrottler",
15
+ ]
16
+
17
+
18
+ class LogTimeThrottler:
19
+ """Drop logs when throttled based on time in between calls.
20
+
21
+ This should generally be close to the top of your processor chain so that processors
22
+ run in a log that will ultimately be throttled.
23
+
24
+ Args:
25
+ key: Unique key in the ``event_dict`` to determine if log should be throttled.
26
+ every_seconds: How long to throttle logs for, in seconds.
27
+ """
28
+
29
+ def __init__(self, key: str, every_seconds: int | float) -> None:
30
+ self.key = key
31
+ self.throttler = TimeThrottler(every_seconds)
32
+
33
+ def __call__(self, _: logging.Logger, __: str, event_dict: EventDict) -> EventDict:
34
+ if self.throttler.is_throttled(event_dict[self.key]):
35
+ raise DropEvent
36
+ return event_dict
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ import typing
5
+ import weakref
6
+
7
+
8
+ class _Hashable(typing.Protocol):
9
+ def __hash__(self) -> int: ...
10
+
11
+
12
+ class ThrottlerProtocol(typing.Protocol):
13
+ def is_throttled(self, key: str) -> bool: ...
14
+
15
+
16
+ class _Link:
17
+ """A link in a doubly-linked list"""
18
+
19
+ __slots__ = "at", "previous", "next", "__weakref__"
20
+ previous: "_Link" | None
21
+ next: "_Link" | None
22
+ at: float | None
23
+
24
+
25
+ class TimeThrottler:
26
+ """A throttler for time-based throttling."""
27
+
28
+ def __init__(self, every_seconds: int | float) -> None:
29
+ self.every = every_seconds
30
+
31
+ self._last: _Link | None = None
32
+ self._indexes = weakref.WeakValueDictionary()
33
+
34
+ def is_throttled(self, key: _Hashable) -> bool:
35
+ """Determine whether ``key`` is throttled.
36
+
37
+ Examples:
38
+ >>> tt = TimeThrottler(every_seconds=1)
39
+ >>> tt.is_throttled("event")
40
+ False
41
+ >>> tt.is_throttled("event")
42
+ True
43
+ >>> tt.is_throttled("another-event")
44
+ False
45
+ >>> tt.is_throttled("another-event")
46
+ True
47
+ >>> time.sleep(1)
48
+ >>> tt.is_throttled("event")
49
+ False
50
+ >>> tt.is_throttled("another-event")
51
+ False
52
+ """
53
+ now = time.monotonic()
54
+
55
+ if key not in self._indexes:
56
+ new = _Link()
57
+ new.at = now
58
+ # Stores a weak reference
59
+ self._indexes[key] = new
60
+
61
+ if self._last:
62
+ # 'next' is a weak reference
63
+ self._last.next = self._indexes[key]
64
+ # 'previous' is not
65
+ new.previous = self._last
66
+
67
+ self._last = new
68
+
69
+ return False
70
+
71
+ link = self._indexes[key]
72
+ if (now - link.at) >= self.every:
73
+ del link
74
+ return False
75
+
76
+ return True
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: structlog-throttling
3
+ Version: 1.0.2
4
+ Summary: Log throttling utilities for structlog.
5
+ Keywords: structlog,throttling,throttle
6
+ License-Expression: MIT OR Apache-2.0
7
+ Classifier: Development Status :: 5 - Production/Stable
8
+ Classifier: License :: OSI Approved :: Apache Software License
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: System :: Logging
17
+ Classifier: Typing :: Typed
18
+ Requires-Dist: structlog>=25.5.0
19
+ Requires-Python: >=3.9
20
+ Project-URL: Codeberg, https://codeberg.org/tomasfarias/structlog-throttling
21
+ Description-Content-Type: text/markdown
22
+
23
+ # *structlog-throttling*: Throttling for *[structlog](https://www.structlog.org/)* loggers
24
+
25
+ <a href="https://pypi.org/project/structlog-throttling/"><img src="https://img.shields.io/pypi/pyversions/structlog-throttling.svg" alt="Supported Python versions from PyPI." /></a>
26
+
27
+ Logging offers a trade-off between visibility and performance. A particularly high performance cost can be incurred when logging in each iteration of a loop, common [hot spots](https://en.wikipedia.org/wiki/Hot_spot_%28computer_programming%29) in most programs. A solution to this problem is to space out the log calls such that they only happen every some time instead of on every iteration of the loop. By tweaking the time in between log calls we can move within the visibility-performance trade-off.
28
+
29
+ *structlog-throttling* brings this solution to *[structlog](https://www.structlog.org/)* in the form of processors to throttle log calls based on time.
30
+
31
+ ## Getting started
32
+
33
+ ### Installation
34
+
35
+ Install *structlog-throttling* from PyPI:
36
+
37
+ ```sh
38
+ pip install structlog-throttling
39
+ ```
40
+
41
+ ### Configure
42
+
43
+ When configuring *structlog*, use one of the processors offered by *structlog-throttling*. I recommend putting the processor close to the beginning of your processor chain, to avoid processing logs that will ultimately be dropped:
44
+
45
+ ```python
46
+ import structlog
47
+ from structlog_throttling.processors import LogTimeThrottler
48
+
49
+
50
+ structlog.configure(
51
+ processors=[
52
+ # Logs with the same 'event' will only be allowed through every 5 seconds.
53
+ LogTimeThrottler("event", every_seconds=5),
54
+ ...
55
+ ]
56
+ )
57
+ ```
58
+
59
+ ## Examples
60
+
61
+ Throttle logs based on log level:
62
+
63
+ ```python
64
+ import structlog
65
+ from structlog_throttling.processors import LogTimeThrottler
66
+
67
+
68
+ structlog.configure(
69
+ processors=[
70
+ structlog.processors.add_log_level,
71
+ LogTimeThrottler("level", every_seconds=5),
72
+ ...
73
+ ],
74
+ )
75
+ ```
@@ -0,0 +1,6 @@
1
+ structlog_throttling/__init__.py,sha256=KtJ2XWBLHTNeOBHq5r6OR7-m8Kmk570EEXItCTineco,133
2
+ structlog_throttling/processors.py,sha256=b61N6jsAOYno4usPuRxtDYxe6gjEsGl4wCqCaF3LPX8,1043
3
+ structlog_throttling/throttlers.py,sha256=hLqWLNp7OGRswy480C8YcwqDD04cRAeahwO5ezFZeOo,1885
4
+ structlog_throttling-1.0.2.dist-info/WHEEL,sha256=DpNsHFUm_gffZe1FgzmqwuqiuPC6Y-uBCzibcJcdupM,78
5
+ structlog_throttling-1.0.2.dist-info/METADATA,sha256=68LnuIrwOh_TkZv7b6qYeOwi2PJ0zpZlT5atL1LlKFw,2797
6
+ structlog_throttling-1.0.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.8
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any