logtail-python 0.4.0__py2.py3-none-any.whl → 0.4.1__py2.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.
- logtail/__init__.py +1 -1
- logtail/flusher.py +19 -2
- logtail/handler.py +28 -2
- logtail/rq.py +29 -0
- {logtail_python-0.4.0.dist-info → logtail_python-0.4.1.dist-info}/METADATA +2 -2
- logtail_python-0.4.1.dist-info/RECORD +14 -0
- {logtail_python-0.4.0.dist-info → logtail_python-0.4.1.dist-info}/WHEEL +1 -1
- logtail_python-0.4.0.dist-info/RECORD +0 -13
- {logtail_python-0.4.0.dist-info → logtail_python-0.4.1.dist-info}/licenses/LICENSE.md +0 -0
- {logtail_python-0.4.0.dist-info → logtail_python-0.4.1.dist-info}/top_level.txt +0 -0
logtail/__init__.py
CHANGED
logtail/flusher.py
CHANGED
|
@@ -9,6 +9,14 @@ from .compat import queue
|
|
|
9
9
|
RETRY_SCHEDULE = (1, 10, 60) # seconds
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
class TransportFrame(dict):
|
|
13
|
+
"""A record logged by a flush worker itself, i.e. urllib3 describing our own upload.
|
|
14
|
+
|
|
15
|
+
It rides along with the next batch of regular records but never triggers an upload
|
|
16
|
+
on its own, since that upload would log again and the queue would never drain.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
12
20
|
class FlushWorker(threading.Thread):
|
|
13
21
|
def __init__(self, upload, pipe, buffer_capacity, flush_interval, check_interval):
|
|
14
22
|
threading.Thread.__init__(self)
|
|
@@ -71,7 +79,7 @@ class FlushWorker(threading.Thread):
|
|
|
71
79
|
# count) and sends them to the Better Stack endpoint all at once. If the
|
|
72
80
|
# request fails in a way that can be retried, it is retried with an
|
|
73
81
|
# exponential backoff in between attempts.
|
|
74
|
-
if frame:
|
|
82
|
+
if frame and not all(isinstance(entry, TransportFrame) for entry in frame):
|
|
75
83
|
response = None
|
|
76
84
|
for delay in RETRY_SCHEDULE + (None, ):
|
|
77
85
|
response = self.upload(frame)
|
|
@@ -80,7 +88,12 @@ class FlushWorker(threading.Thread):
|
|
|
80
88
|
if delay is not None:
|
|
81
89
|
time.sleep(delay)
|
|
82
90
|
|
|
83
|
-
|
|
91
|
+
# A network error surfaces as a Fake500 that carries an `exception`
|
|
92
|
+
# attribute; a genuine server HTTP 500 is a real `requests.Response`
|
|
93
|
+
# that has no such attribute. Use a default so the latter doesn't
|
|
94
|
+
# raise AttributeError here and kill the flush thread (dropping all
|
|
95
|
+
# subsequent logs for the life of the process).
|
|
96
|
+
if response.status_code == 500 and getattr(response, "exception", None) is not None:
|
|
84
97
|
print('Failed to send logs to Better Stack after {} retries: {}'.format(len(RETRY_SCHEDULE), response.exception))
|
|
85
98
|
|
|
86
99
|
self._clean = True
|
|
@@ -93,6 +106,10 @@ class FlushWorker(threading.Thread):
|
|
|
93
106
|
time.sleep(self.check_interval)
|
|
94
107
|
self._flushing = False
|
|
95
108
|
|
|
109
|
+
def in_flush_worker():
|
|
110
|
+
return isinstance(threading.current_thread(), FlushWorker)
|
|
111
|
+
|
|
112
|
+
|
|
96
113
|
def _initial_time_remaining(flush_interval):
|
|
97
114
|
return flush_interval
|
|
98
115
|
|
logtail/handler.py
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
from __future__ import print_function, unicode_literals
|
|
3
3
|
import logging
|
|
4
4
|
import json
|
|
5
|
+
import os
|
|
6
|
+
import weakref
|
|
5
7
|
|
|
6
8
|
from .compat import queue
|
|
7
9
|
from .helpers import DEFAULT_CONTEXT
|
|
8
|
-
from .flusher import FlushWorker
|
|
10
|
+
from .flusher import FlushWorker, TransportFrame, in_flush_worker
|
|
9
11
|
from .uploader import Uploader
|
|
10
12
|
from .frame import create_frame
|
|
11
13
|
|
|
@@ -18,6 +20,8 @@ DEFAULT_DROP_EXTRA_EVENTS = True
|
|
|
18
20
|
DEFAULT_INCLUDE_EXTRA_ATTRIBUTES = True
|
|
19
21
|
DEFAULT_TIMEOUT = 30
|
|
20
22
|
|
|
23
|
+
_handlers = weakref.WeakSet()
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
class LogtailHandler(logging.Handler):
|
|
23
27
|
def __init__(self,
|
|
@@ -50,6 +54,7 @@ class LogtailHandler(logging.Handler):
|
|
|
50
54
|
self.dropcount = 0
|
|
51
55
|
# Do not initialize the flush thread yet because it causes issues on Render.
|
|
52
56
|
self.flush_thread = None
|
|
57
|
+
_handlers.add(self)
|
|
53
58
|
|
|
54
59
|
def ensure_flush_thread_alive(self):
|
|
55
60
|
if self.flush_thread and self.flush_thread.is_alive():
|
|
@@ -71,8 +76,12 @@ class LogtailHandler(logging.Handler):
|
|
|
71
76
|
message = self.format(record)
|
|
72
77
|
frame = create_frame(record, message, self.context, include_extra_attributes=self.include_extra_attributes)
|
|
73
78
|
serializable_frame = json.loads(json.dumps(frame, default=str))
|
|
79
|
+
transport = in_flush_worker()
|
|
80
|
+
if transport:
|
|
81
|
+
serializable_frame = TransportFrame(serializable_frame)
|
|
74
82
|
try:
|
|
75
|
-
|
|
83
|
+
# The flush worker must never block on its own queue, so its records are dropped when full.
|
|
84
|
+
self.pipe.put(serializable_frame, block=(not self.drop_extra_events) and not transport)
|
|
76
85
|
except queue.Full:
|
|
77
86
|
# Only raised when not blocking, which means that extra events
|
|
78
87
|
# should be dropped.
|
|
@@ -84,3 +93,20 @@ class LogtailHandler(logging.Handler):
|
|
|
84
93
|
def flush(self):
|
|
85
94
|
if self.flush_thread and self.flush_thread.is_alive():
|
|
86
95
|
self.flush_thread.flush()
|
|
96
|
+
|
|
97
|
+
def _reset_after_fork(self):
|
|
98
|
+
# A forked child inherits the parent's queue with whatever was still buffered in it,
|
|
99
|
+
# a flush thread object whose thread does not exist in the child, and an HTTP session
|
|
100
|
+
# whose socket it shares with the parent, so it starts over with fresh ones.
|
|
101
|
+
self.pipe = queue.Queue(maxsize=self.buffer_capacity)
|
|
102
|
+
self.flush_thread = None
|
|
103
|
+
self.uploader = Uploader(self.source_token, self.host, self.uploader.timeout)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _reset_handlers_after_fork():
|
|
107
|
+
for handler in _handlers:
|
|
108
|
+
handler._reset_after_fork()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if hasattr(os, 'register_at_fork'):
|
|
112
|
+
os.register_at_fork(after_in_child=_reset_handlers_after_fork)
|
logtail/rq.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
from __future__ import print_function, unicode_literals
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import rq
|
|
6
|
+
|
|
7
|
+
from .handler import LogtailHandler
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Worker(rq.Worker):
|
|
11
|
+
"""An RQ worker that sends buffered log records after every job.
|
|
12
|
+
|
|
13
|
+
RQ runs each job in a forked work horse and ends it with os._exit(), which skips the
|
|
14
|
+
interpreter shutdown that would otherwise let LogtailHandler flush. Use it with
|
|
15
|
+
`rq worker --worker-class logtail.rq.Worker` or `RQ = {"WORKER_CLASS": "logtail.rq.Worker"}`
|
|
16
|
+
in django-rq.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def perform_job(self, job, queue):
|
|
20
|
+
try:
|
|
21
|
+
return super(Worker, self).perform_job(job, queue)
|
|
22
|
+
finally:
|
|
23
|
+
for handler in _logtail_handlers():
|
|
24
|
+
handler.flush()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _logtail_handlers():
|
|
28
|
+
loggers = [logging.getLogger()] + [logger for logger in logging.Logger.manager.loggerDict.values() if isinstance(logger, logging.Logger)]
|
|
29
|
+
return {handler for logger in loggers for handler in logger.handlers if isinstance(handler, LogtailHandler)}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: logtail-python
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
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.4.
|
|
6
|
+
Download-URL: https://github.com/logtail/logtail-python/tarball/0.4.1
|
|
7
7
|
Author: Logtail
|
|
8
8
|
Author-email: hello@logtail.com
|
|
9
9
|
License: ISC
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
logtail/__init__.py,sha256=9PQmXLJchEiOwtJ6WfZuo9AT1aL3rl6ZPD7TxLKwuz0,252
|
|
2
|
+
logtail/compat.py,sha256=KbZmcmDpj_vbMotp16pADPwJ1FkEHGDNjKbOfCy-c10,141
|
|
3
|
+
logtail/flusher.py,sha256=EjLbAgooBjYs0KKCMVqxRddDpMD8qnVuhXSEMZTISQw,5017
|
|
4
|
+
logtail/formatter.py,sha256=nk4W1G95P19ijylPBJ8D9FlO_qbh3tfbv7xzCSfpDN8,968
|
|
5
|
+
logtail/frame.py,sha256=p7vVYcajZvjH4FJDt4ADLfYljVbQXKTGKk3kFec5JUs,3335
|
|
6
|
+
logtail/handler.py,sha256=CruppN_XD0W-Ifsl1iu99T0yWw66utkl7_un6V6OO1Y,4265
|
|
7
|
+
logtail/helpers.py,sha256=ezDTLsTBAtCtIh_XptSYOiBrZFa-E9wo2SWTnjKgOOU,1156
|
|
8
|
+
logtail/rq.py,sha256=SEjVwjp8lRLab2F0StdssBQ0-vFK1CekEgYwkSICBeo,1023
|
|
9
|
+
logtail/uploader.py,sha256=Ilhh8wuAGlz1YJcQpzXtFqwD-2RYmuamKv3dFNoTWUg,868
|
|
10
|
+
logtail_python-0.4.1.dist-info/licenses/LICENSE.md,sha256=aCBMqxfm7QNyFR9JiJ2GSMvhAURXN2E0yTcYNQloqI0,737
|
|
11
|
+
logtail_python-0.4.1.dist-info/METADATA,sha256=F29t1-2304x3Ofa8b1cASyECF4V85ef2Ndo5yovJzmI,2429
|
|
12
|
+
logtail_python-0.4.1.dist-info/WHEEL,sha256=4YBfCYNH4wlLpv3pzq1hbEuIlXA4WJabKLFurZ7eTL0,109
|
|
13
|
+
logtail_python-0.4.1.dist-info/top_level.txt,sha256=QZIbQogN1yFBgjiJ-8NlYArVCL00gBoYbEUT_Ew8l-Q,8
|
|
14
|
+
logtail_python-0.4.1.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
logtail/__init__.py,sha256=lz_ouCpEkpsK8Lq9fCQ7Sqj8xbowot6JzNZGFeEfOrI,252
|
|
2
|
-
logtail/compat.py,sha256=KbZmcmDpj_vbMotp16pADPwJ1FkEHGDNjKbOfCy-c10,141
|
|
3
|
-
logtail/flusher.py,sha256=KKb4IEuPC_XGKaNBrbMEiwc-X4MKsYURPjY7PqR3v4k,4176
|
|
4
|
-
logtail/formatter.py,sha256=nk4W1G95P19ijylPBJ8D9FlO_qbh3tfbv7xzCSfpDN8,968
|
|
5
|
-
logtail/frame.py,sha256=p7vVYcajZvjH4FJDt4ADLfYljVbQXKTGKk3kFec5JUs,3335
|
|
6
|
-
logtail/handler.py,sha256=9PNJPSc2S3W5uQADKE4iDFH16A7BZW90D-m7aR66Mo4,3188
|
|
7
|
-
logtail/helpers.py,sha256=ezDTLsTBAtCtIh_XptSYOiBrZFa-E9wo2SWTnjKgOOU,1156
|
|
8
|
-
logtail/uploader.py,sha256=Ilhh8wuAGlz1YJcQpzXtFqwD-2RYmuamKv3dFNoTWUg,868
|
|
9
|
-
logtail_python-0.4.0.dist-info/licenses/LICENSE.md,sha256=aCBMqxfm7QNyFR9JiJ2GSMvhAURXN2E0yTcYNQloqI0,737
|
|
10
|
-
logtail_python-0.4.0.dist-info/METADATA,sha256=Bwsctru3-I0ZkrvKuukF6j1jDB7P1nz5OYyBmBsw79M,2429
|
|
11
|
-
logtail_python-0.4.0.dist-info/WHEEL,sha256=TdQ5LtNwLuxTCjgxN51AgdU5w-KkB9ttmLbzjTH02pg,109
|
|
12
|
-
logtail_python-0.4.0.dist-info/top_level.txt,sha256=QZIbQogN1yFBgjiJ-8NlYArVCL00gBoYbEUT_Ew8l-Q,8
|
|
13
|
-
logtail_python-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|