logtail-python 0.4.3__tar.gz → 0.5.0__tar.gz
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.
- {logtail_python-0.4.3 → logtail_python-0.5.0}/PKG-INFO +2 -2
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/__init__.py +1 -1
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/compat.py +1 -1
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/flusher.py +13 -12
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/formatter.py +6 -5
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/frame.py +18 -12
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/handler.py +23 -22
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/helpers.py +13 -13
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/rq.py +2 -2
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail/uploader.py +6 -5
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail_python.egg-info/PKG-INFO +2 -2
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail_python.egg-info/SOURCES.txt +1 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/setup.py +2 -1
- logtail_python-0.5.0/test-requirements.txt +8 -0
- logtail_python-0.5.0/tests/__init__.py +0 -0
- logtail_python-0.4.3/test-requirements.txt +0 -5
- {logtail_python-0.4.3 → logtail_python-0.5.0}/LICENSE.md +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/MANIFEST.in +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/README.md +0 -0
- /logtail_python-0.4.3/tests/__init__.py → /logtail_python-0.5.0/logtail/py.typed +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail_python.egg-info/dependency_links.txt +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail_python.egg-info/requires.txt +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/logtail_python.egg-info/top_level.txt +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/requirements.txt +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/setup.cfg +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_flusher.py +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_formatter.py +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_frame.py +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_handler.py +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_helpers.py +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_rq.py +0 -0
- {logtail_python-0.4.3 → logtail_python-0.5.0}/tests/test_uploader.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: logtail-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Better Stack client library
|
|
5
5
|
Home-page: https://github.com/logtail/logtail-python
|
|
6
|
-
Download-URL: https://github.com/logtail/logtail-python/tarball/0.
|
|
6
|
+
Download-URL: https://github.com/logtail/logtail-python/tarball/0.5.0
|
|
7
7
|
Author: Logtail
|
|
8
8
|
Author-email: hello@logtail.com
|
|
9
9
|
License: ISC
|
|
@@ -3,13 +3,14 @@ from __future__ import print_function, unicode_literals
|
|
|
3
3
|
|
|
4
4
|
import threading
|
|
5
5
|
import time
|
|
6
|
+
from typing import Any, Callable, Optional
|
|
6
7
|
|
|
7
8
|
from .compat import queue
|
|
8
9
|
|
|
9
10
|
RETRY_SCHEDULE = (1, 10, 60) # seconds
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
class TransportFrame(dict):
|
|
13
|
+
class TransportFrame(dict[str, Any]):
|
|
13
14
|
"""A record logged by a flush worker itself, i.e. urllib3 describing our own upload.
|
|
14
15
|
|
|
15
16
|
It rides along with the next batch of regular records but never triggers an upload
|
|
@@ -18,7 +19,7 @@ class TransportFrame(dict):
|
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class FlushWorker(threading.Thread):
|
|
21
|
-
def __init__(self, upload, pipe, buffer_capacity, flush_interval, check_interval):
|
|
22
|
+
def __init__(self, upload: Callable[[list[Any]], Any], pipe: 'queue.Queue[Any]', buffer_capacity: int, flush_interval: float, check_interval: float) -> None:
|
|
22
23
|
threading.Thread.__init__(self)
|
|
23
24
|
self.parent_thread = threading.current_thread()
|
|
24
25
|
self.upload = upload
|
|
@@ -30,11 +31,11 @@ class FlushWorker(threading.Thread):
|
|
|
30
31
|
self._flushing = False
|
|
31
32
|
self._clean = True
|
|
32
33
|
|
|
33
|
-
def run(self):
|
|
34
|
+
def run(self) -> None:
|
|
34
35
|
while self.should_run:
|
|
35
36
|
self.step()
|
|
36
37
|
|
|
37
|
-
def _is_parent_alive(self):
|
|
38
|
+
def _is_parent_alive(self) -> bool:
|
|
38
39
|
try:
|
|
39
40
|
return self.parent_thread.is_alive()
|
|
40
41
|
except RuntimeError:
|
|
@@ -44,10 +45,10 @@ class FlushWorker(threading.Thread):
|
|
|
44
45
|
# interpreter shutdown). Treat that as "no longer alive".
|
|
45
46
|
return False
|
|
46
47
|
|
|
47
|
-
def step(self):
|
|
48
|
+
def step(self) -> None:
|
|
48
49
|
last_flush = time.time()
|
|
49
50
|
time_remaining = _initial_time_remaining(self.flush_interval)
|
|
50
|
-
frame = []
|
|
51
|
+
frame: list[Any] = []
|
|
51
52
|
self._clean = True
|
|
52
53
|
|
|
53
54
|
# If the parent thread has exited but there are still outstanding
|
|
@@ -80,7 +81,7 @@ class FlushWorker(threading.Thread):
|
|
|
80
81
|
# request fails in a way that can be retried, it is retried with an
|
|
81
82
|
# exponential backoff in between attempts.
|
|
82
83
|
if frame and not all(isinstance(entry, TransportFrame) for entry in frame):
|
|
83
|
-
response = None
|
|
84
|
+
response: Any = None
|
|
84
85
|
for delay in RETRY_SCHEDULE + (None, ):
|
|
85
86
|
response = self.upload(frame)
|
|
86
87
|
if not _should_retry(response.status_code):
|
|
@@ -100,7 +101,7 @@ class FlushWorker(threading.Thread):
|
|
|
100
101
|
if shutdown and self.pipe.empty():
|
|
101
102
|
self.should_run = False
|
|
102
103
|
|
|
103
|
-
def flush(self, timeout=None):
|
|
104
|
+
def flush(self, timeout: Optional[float] = None) -> bool:
|
|
104
105
|
"""Block until the worker has drained the queue.
|
|
105
106
|
|
|
106
107
|
If ``timeout`` is given (in seconds), return after at most that much
|
|
@@ -125,19 +126,19 @@ class FlushWorker(threading.Thread):
|
|
|
125
126
|
finally:
|
|
126
127
|
self._flushing = False
|
|
127
128
|
|
|
128
|
-
def in_flush_worker():
|
|
129
|
+
def in_flush_worker() -> bool:
|
|
129
130
|
return isinstance(threading.current_thread(), FlushWorker)
|
|
130
131
|
|
|
131
132
|
|
|
132
|
-
def _initial_time_remaining(flush_interval):
|
|
133
|
+
def _initial_time_remaining(flush_interval: float) -> float:
|
|
133
134
|
return flush_interval
|
|
134
135
|
|
|
135
136
|
|
|
136
|
-
def _calculate_time_remaining(last_flush, flush_interval):
|
|
137
|
+
def _calculate_time_remaining(last_flush: float, flush_interval: float) -> float:
|
|
137
138
|
elapsed = time.time() - last_flush
|
|
138
139
|
time_remaining = max(flush_interval - elapsed, 0)
|
|
139
140
|
return time_remaining
|
|
140
141
|
|
|
141
142
|
|
|
142
|
-
def _should_retry(status_code):
|
|
143
|
+
def _should_retry(status_code: int) -> bool:
|
|
143
144
|
return 500 <= status_code < 600
|
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
from __future__ import print_function, unicode_literals
|
|
3
3
|
import logging
|
|
4
4
|
import json
|
|
5
|
+
from typing import Any, Callable, Optional
|
|
5
6
|
|
|
6
|
-
from .helpers import DEFAULT_CONTEXT
|
|
7
|
+
from .helpers import DEFAULT_CONTEXT, LogtailContext
|
|
7
8
|
from .frame import create_frame
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class LogtailFormatter(logging.Formatter):
|
|
11
12
|
def __init__(self,
|
|
12
|
-
context=DEFAULT_CONTEXT,
|
|
13
|
-
json_default=None,
|
|
14
|
-
json_encoder=None):
|
|
13
|
+
context: LogtailContext = DEFAULT_CONTEXT,
|
|
14
|
+
json_default: Optional[Callable[[Any], Any]] = None,
|
|
15
|
+
json_encoder: Optional[type[json.JSONEncoder]] = None) -> None:
|
|
15
16
|
self.context = context
|
|
16
17
|
self.json_default = json_default
|
|
17
18
|
self.json_encoder = json_encoder
|
|
18
19
|
|
|
19
|
-
def format(self, record):
|
|
20
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
20
21
|
# Because the formatter does not have an underlying format string for
|
|
21
22
|
# which `extra` may be used to substitute arguments (see
|
|
22
23
|
# https://docs.python.org/2/library/logging.html#logging.debug ), we
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
from __future__ import print_function, unicode_literals
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
|
-
|
|
4
|
+
import logging
|
|
5
5
|
from os import path
|
|
6
|
+
from typing import Any, Optional
|
|
6
7
|
import __main__
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
from .helpers import LogtailContext
|
|
10
|
+
|
|
11
|
+
def create_frame(record: logging.LogRecord, message: str, context: LogtailContext, include_extra_attributes: bool = False) -> dict[str, Any]:
|
|
9
12
|
r = record.__dict__
|
|
10
13
|
# Django sends a request object in the record, which is not JSON serializable
|
|
11
14
|
if "request" in r and not isinstance(r["request"], (dict, list, bool, int, float, str)) :
|
|
12
15
|
del r["request"]
|
|
13
|
-
frame = {}
|
|
16
|
+
frame: dict[str, Any] = {}
|
|
14
17
|
frame['dt'] = datetime.fromtimestamp(r['created'], timezone.utc).isoformat()
|
|
15
18
|
frame['level'] = _levelname(r['levelname'])
|
|
16
19
|
frame['severity'] = int(r['levelno'] / 10)
|
|
17
20
|
frame['message'] = message
|
|
18
|
-
|
|
21
|
+
ctx: dict[str, Any] = {}
|
|
22
|
+
frame['context'] = ctx
|
|
19
23
|
|
|
20
24
|
# Runtime context
|
|
21
|
-
|
|
25
|
+
runtime: dict[str, Any] = {}
|
|
26
|
+
ctx['runtime'] = runtime
|
|
22
27
|
runtime['function'] = r['funcName']
|
|
23
28
|
runtime['file'] = _relative_to_main_module_if_possible(r['pathname'])
|
|
24
29
|
runtime['line'] = r['lineno']
|
|
@@ -27,7 +32,8 @@ def create_frame(record, message, context, include_extra_attributes=False):
|
|
|
27
32
|
runtime['logger_name'] = r['name']
|
|
28
33
|
|
|
29
34
|
# Runtime context
|
|
30
|
-
|
|
35
|
+
system: dict[str, Any] = {}
|
|
36
|
+
ctx['system'] = system
|
|
31
37
|
system['pid'] = r['process']
|
|
32
38
|
system['process_name'] = r['processName']
|
|
33
39
|
|
|
@@ -41,14 +47,14 @@ def create_frame(record, message, context, include_extra_attributes=False):
|
|
|
41
47
|
|
|
42
48
|
return _remove_circular_dependencies(frame)
|
|
43
49
|
|
|
44
|
-
def _parse_custom_events(record, include_extra_attributes):
|
|
50
|
+
def _parse_custom_events(record: logging.LogRecord, include_extra_attributes: bool) -> dict[str, Any]:
|
|
45
51
|
default_keys = {
|
|
46
52
|
'args', 'asctime', 'created', 'exc_info', 'exc_text', 'pathname',
|
|
47
53
|
'funcName', 'levelname', 'levelno', 'lineno', 'module', 'msecs',
|
|
48
54
|
'message', 'msg', 'name', 'process', 'processName',
|
|
49
55
|
'relativeCreated', 'thread', 'threadName'
|
|
50
56
|
}
|
|
51
|
-
events = {}
|
|
57
|
+
events: dict[str, Any] = {}
|
|
52
58
|
for key, val in record.__dict__.items():
|
|
53
59
|
if key in default_keys:
|
|
54
60
|
continue
|
|
@@ -57,7 +63,7 @@ def _parse_custom_events(record, include_extra_attributes):
|
|
|
57
63
|
events[key] = val
|
|
58
64
|
return events
|
|
59
65
|
|
|
60
|
-
def _remove_circular_dependencies(obj, memo=None):
|
|
66
|
+
def _remove_circular_dependencies(obj: Any, memo: Optional[set[int]] = None) -> Any:
|
|
61
67
|
if memo is None:
|
|
62
68
|
memo = set()
|
|
63
69
|
|
|
@@ -85,14 +91,14 @@ def _remove_circular_dependencies(obj, memo=None):
|
|
|
85
91
|
else:
|
|
86
92
|
return obj
|
|
87
93
|
|
|
88
|
-
def _levelname(level):
|
|
94
|
+
def _levelname(level: str) -> str:
|
|
89
95
|
return level.lower()
|
|
90
96
|
|
|
91
|
-
def _relative_to_main_module_if_possible(pathname):
|
|
97
|
+
def _relative_to_main_module_if_possible(pathname: str) -> str:
|
|
92
98
|
has_main_module = hasattr(__main__, '__file__')
|
|
93
99
|
return _relative_to_main_module(pathname) if has_main_module else pathname
|
|
94
100
|
|
|
95
|
-
def _relative_to_main_module(pathname):
|
|
101
|
+
def _relative_to_main_module(pathname: str) -> str:
|
|
96
102
|
try:
|
|
97
103
|
return path.relpath(pathname, path.dirname(__main__.__file__))
|
|
98
104
|
except ValueError:
|
|
@@ -4,9 +4,10 @@ import logging
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import weakref
|
|
7
|
+
from typing import Any, Optional, Union
|
|
7
8
|
|
|
8
9
|
from .compat import queue
|
|
9
|
-
from .helpers import DEFAULT_CONTEXT
|
|
10
|
+
from .helpers import DEFAULT_CONTEXT, LogtailContext
|
|
10
11
|
from .flusher import FlushWorker, TransportFrame, in_flush_worker
|
|
11
12
|
from .uploader import Uploader
|
|
12
13
|
from .frame import create_frame
|
|
@@ -22,23 +23,23 @@ DEFAULT_TIMEOUT = 30
|
|
|
22
23
|
DEFAULT_FLUSH_TIMEOUT = 30
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
_handlers = weakref.WeakSet()
|
|
26
|
+
_handlers: 'weakref.WeakSet[LogtailHandler]' = weakref.WeakSet()
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class LogtailHandler(logging.Handler):
|
|
29
30
|
def __init__(self,
|
|
30
|
-
source_token,
|
|
31
|
-
host=DEFAULT_HOST,
|
|
32
|
-
buffer_capacity=DEFAULT_BUFFER_CAPACITY,
|
|
33
|
-
flush_interval=DEFAULT_FLUSH_INTERVAL,
|
|
34
|
-
check_interval=DEFAULT_CHECK_INTERVAL,
|
|
35
|
-
raise_exceptions=DEFAULT_RAISE_EXCEPTIONS,
|
|
36
|
-
drop_extra_events=DEFAULT_DROP_EXTRA_EVENTS,
|
|
37
|
-
include_extra_attributes=DEFAULT_INCLUDE_EXTRA_ATTRIBUTES,
|
|
38
|
-
context=DEFAULT_CONTEXT,
|
|
39
|
-
timeout=DEFAULT_TIMEOUT,
|
|
40
|
-
flush_timeout=DEFAULT_FLUSH_TIMEOUT,
|
|
41
|
-
level=logging.NOTSET):
|
|
31
|
+
source_token: str,
|
|
32
|
+
host: str = DEFAULT_HOST,
|
|
33
|
+
buffer_capacity: int = DEFAULT_BUFFER_CAPACITY,
|
|
34
|
+
flush_interval: float = DEFAULT_FLUSH_INTERVAL,
|
|
35
|
+
check_interval: float = DEFAULT_CHECK_INTERVAL,
|
|
36
|
+
raise_exceptions: bool = DEFAULT_RAISE_EXCEPTIONS,
|
|
37
|
+
drop_extra_events: bool = DEFAULT_DROP_EXTRA_EVENTS,
|
|
38
|
+
include_extra_attributes: bool = DEFAULT_INCLUDE_EXTRA_ATTRIBUTES,
|
|
39
|
+
context: LogtailContext = DEFAULT_CONTEXT,
|
|
40
|
+
timeout: Union[float, tuple[float, float], None] = DEFAULT_TIMEOUT,
|
|
41
|
+
flush_timeout: Optional[float] = DEFAULT_FLUSH_TIMEOUT,
|
|
42
|
+
level: Union[int, str] = logging.NOTSET) -> None:
|
|
42
43
|
super(LogtailHandler, self).__init__(level=level)
|
|
43
44
|
self.source_token = source_token
|
|
44
45
|
if host.startswith('https://') or host.startswith('http://'):
|
|
@@ -46,7 +47,7 @@ class LogtailHandler(logging.Handler):
|
|
|
46
47
|
else:
|
|
47
48
|
self.host = "https://" + host
|
|
48
49
|
self.context = context
|
|
49
|
-
self.pipe = queue.Queue(maxsize=buffer_capacity)
|
|
50
|
+
self.pipe: 'queue.Queue[dict[str, Any]]' = queue.Queue(maxsize=buffer_capacity)
|
|
50
51
|
self.uploader = Uploader(self.source_token, self.host, timeout)
|
|
51
52
|
self.drop_extra_events = drop_extra_events
|
|
52
53
|
self.include_extra_attributes = include_extra_attributes
|
|
@@ -57,10 +58,10 @@ class LogtailHandler(logging.Handler):
|
|
|
57
58
|
self.flush_timeout = flush_timeout
|
|
58
59
|
self.dropcount = 0
|
|
59
60
|
# Do not initialize the flush thread yet because it causes issues on Render.
|
|
60
|
-
self.flush_thread = None
|
|
61
|
+
self.flush_thread: Optional[FlushWorker] = None
|
|
61
62
|
_handlers.add(self)
|
|
62
63
|
|
|
63
|
-
def ensure_flush_thread_alive(self):
|
|
64
|
+
def ensure_flush_thread_alive(self) -> None:
|
|
64
65
|
if self.flush_thread and self.flush_thread.is_alive():
|
|
65
66
|
return
|
|
66
67
|
|
|
@@ -73,7 +74,7 @@ class LogtailHandler(logging.Handler):
|
|
|
73
74
|
)
|
|
74
75
|
self.flush_thread.start()
|
|
75
76
|
|
|
76
|
-
def emit(self, record):
|
|
77
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
77
78
|
try:
|
|
78
79
|
self.ensure_flush_thread_alive()
|
|
79
80
|
|
|
@@ -94,10 +95,10 @@ class LogtailHandler(logging.Handler):
|
|
|
94
95
|
if self.raise_exceptions:
|
|
95
96
|
raise e
|
|
96
97
|
|
|
97
|
-
def flush(self):
|
|
98
|
+
def flush(self) -> None:
|
|
98
99
|
if not (self.flush_thread and self.flush_thread.is_alive()):
|
|
99
100
|
return
|
|
100
|
-
if logging._lock._is_owned():
|
|
101
|
+
if logging._lock._is_owned(): # type: ignore[attr-defined] # private, see below
|
|
101
102
|
# logging.config.dictConfig() flushes the handlers it replaces while holding the global
|
|
102
103
|
# logging lock, and the worker's upload needs that lock whenever urllib3's logger has a
|
|
103
104
|
# level-cache miss, so waiting here could only deadlock. Nothing is lost: the worker
|
|
@@ -106,7 +107,7 @@ class LogtailHandler(logging.Handler):
|
|
|
106
107
|
if not self.flush_thread.flush(timeout=self.flush_timeout):
|
|
107
108
|
print('Gave up waiting for Better Stack uploads after {}s, logs are still buffered'.format(self.flush_timeout))
|
|
108
109
|
|
|
109
|
-
def _reset_after_fork(self):
|
|
110
|
+
def _reset_after_fork(self) -> None:
|
|
110
111
|
# A forked child inherits the parent's queue with whatever was still buffered in it,
|
|
111
112
|
# a flush thread object whose thread does not exist in the child, and an HTTP session
|
|
112
113
|
# whose socket it shares with the parent, so it starts over with fresh ones.
|
|
@@ -115,7 +116,7 @@ class LogtailHandler(logging.Handler):
|
|
|
115
116
|
self.uploader.reset()
|
|
116
117
|
|
|
117
118
|
|
|
118
|
-
def _reset_handlers_after_fork():
|
|
119
|
+
def _reset_handlers_after_fork() -> None:
|
|
119
120
|
for handler in _handlers:
|
|
120
121
|
handler._reset_after_fork()
|
|
121
122
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
from __future__ import print_function, unicode_literals
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
from typing import Any, Optional
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
class LogtailContext(object):
|
|
6
|
-
def __init__(self):
|
|
7
|
-
self.extras = []
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
self.extras: list[dict[str, dict[str, Any]]] = []
|
|
8
10
|
|
|
9
|
-
def context(self, *args, **kwargs):
|
|
11
|
+
def context(self, *args: Any, **kwargs: dict[str, Any]) -> 'LogtailContext':
|
|
10
12
|
if args:
|
|
11
13
|
raise ValueError(
|
|
12
14
|
'All contexts must be passed by name as keyword arguments'
|
|
@@ -19,23 +21,21 @@ class LogtailContext(object):
|
|
|
19
21
|
self.extras.append(kwargs)
|
|
20
22
|
return self
|
|
21
23
|
|
|
22
|
-
def __call__(self, *args, **kwargs):
|
|
24
|
+
def __call__(self, *args: Any, **kwargs: dict[str, Any]) -> 'LogtailContext':
|
|
23
25
|
return self.context(*args, **kwargs)
|
|
24
26
|
|
|
25
|
-
def __enter__(self):
|
|
27
|
+
def __enter__(self) -> 'LogtailContext':
|
|
26
28
|
return self
|
|
27
29
|
|
|
28
|
-
def __exit__(self, type_, value, traceback):
|
|
29
|
-
if type_ is
|
|
30
|
-
|
|
31
|
-
self.extras.pop()
|
|
32
|
-
return self
|
|
30
|
+
def __exit__(self, type_: Optional[type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]) -> None:
|
|
31
|
+
if type_ is None:
|
|
32
|
+
self.extras.pop()
|
|
33
33
|
|
|
34
|
-
def exists(self):
|
|
34
|
+
def exists(self) -> bool:
|
|
35
35
|
return bool(self.extras)
|
|
36
36
|
|
|
37
|
-
def collapse(self):
|
|
38
|
-
x = {}
|
|
37
|
+
def collapse(self) -> dict[str, dict[str, Any]]:
|
|
38
|
+
x: dict[str, dict[str, Any]] = {}
|
|
39
39
|
for contexts in self.extras:
|
|
40
40
|
for name, data in contexts.items():
|
|
41
41
|
x.setdefault(name, {}).update(data)
|
|
@@ -16,7 +16,7 @@ class Worker(rq.Worker):
|
|
|
16
16
|
in django-rq.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
def perform_job(self, job, queue):
|
|
19
|
+
def perform_job(self, job: rq.job.Job, queue: rq.Queue) -> bool:
|
|
20
20
|
try:
|
|
21
21
|
return super(Worker, self).perform_job(job, queue)
|
|
22
22
|
finally:
|
|
@@ -24,6 +24,6 @@ class Worker(rq.Worker):
|
|
|
24
24
|
handler.flush()
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def _logtail_handlers():
|
|
27
|
+
def _logtail_handlers() -> set[LogtailHandler]:
|
|
28
28
|
loggers = [logging.getLogger()] + [logger for logger in logging.Logger.manager.loggerDict.values() if isinstance(logger, logging.Logger)]
|
|
29
29
|
return {handler for logger in loggers for handler in logger.handlers if isinstance(handler, LogtailHandler)}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
from __future__ import print_function, unicode_literals
|
|
3
3
|
import os
|
|
4
|
+
from typing import Any, Union
|
|
4
5
|
import msgpack
|
|
5
6
|
import requests
|
|
6
7
|
import requests.utils
|
|
7
8
|
|
|
8
9
|
class Fake500(object):
|
|
9
|
-
def __init__(self, exception):
|
|
10
|
+
def __init__(self, exception: Exception) -> None:
|
|
10
11
|
self.status_code = 500
|
|
11
12
|
self.exception = exception
|
|
12
13
|
|
|
13
14
|
class Uploader(object):
|
|
14
|
-
def __init__(self, source_token, host, timeout):
|
|
15
|
+
def __init__(self, source_token: str, host: str, timeout: Union[float, tuple[float, float], None]) -> None:
|
|
15
16
|
self.source_token = source_token
|
|
16
17
|
self.host = host
|
|
17
18
|
self.timeout = timeout
|
|
@@ -27,19 +28,19 @@ class Uploader(object):
|
|
|
27
28
|
}
|
|
28
29
|
self.session = self._new_session()
|
|
29
30
|
|
|
30
|
-
def __call__(self, frame):
|
|
31
|
+
def __call__(self, frame: list[dict[str, Any]]) -> Union[requests.Response, Fake500]:
|
|
31
32
|
data = msgpack.packb(frame, use_bin_type=True)
|
|
32
33
|
try:
|
|
33
34
|
return self.session.post(self.host, data=data, headers=self.headers, timeout=self.timeout)
|
|
34
35
|
except requests.RequestException as e:
|
|
35
36
|
return Fake500(e)
|
|
36
37
|
|
|
37
|
-
def reset(self):
|
|
38
|
+
def reset(self) -> None:
|
|
38
39
|
# A forked child shares the parent's pooled socket, so it needs a session of its own,
|
|
39
40
|
# built from the settings resolved above rather than looked up again.
|
|
40
41
|
self.session = self._new_session()
|
|
41
42
|
|
|
42
|
-
def _new_session(self):
|
|
43
|
+
def _new_session(self) -> requests.Session:
|
|
43
44
|
session = requests.Session()
|
|
44
45
|
session.trust_env = False
|
|
45
46
|
session.proxies = self.proxies
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: logtail-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Better Stack client library
|
|
5
5
|
Home-page: https://github.com/logtail/logtail-python
|
|
6
|
-
Download-URL: https://github.com/logtail/logtail-python/tarball/0.
|
|
6
|
+
Download-URL: https://github.com/logtail/logtail-python/tarball/0.5.0
|
|
7
7
|
Author: Logtail
|
|
8
8
|
Author-email: hello@logtail.com
|
|
9
9
|
License: ISC
|
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
from setuptools import setup
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
VERSION = '0.
|
|
6
|
+
VERSION = '0.5.0'
|
|
7
7
|
ROOT_DIR = os.path.dirname(__file__)
|
|
8
8
|
|
|
9
9
|
REQUIREMENTS = [
|
|
@@ -18,6 +18,7 @@ setup(
|
|
|
18
18
|
name='logtail-python',
|
|
19
19
|
version=VERSION,
|
|
20
20
|
packages=['logtail'],
|
|
21
|
+
package_data={'logtail': ['py.typed']},
|
|
21
22
|
include_package_data=True,
|
|
22
23
|
license='ISC',
|
|
23
24
|
description='Better Stack client library',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|