logtail-python 0.4.1__py2.py3-none-any.whl → 0.4.3__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 CHANGED
@@ -5,6 +5,6 @@ from .handler import LogtailHandler
5
5
  from .helpers import LogtailContext, DEFAULT_CONTEXT
6
6
  from .formatter import LogtailFormatter
7
7
 
8
- __version__ = '0.4.1'
8
+ __version__ = '0.4.3'
9
9
 
10
10
  context = DEFAULT_CONTEXT
logtail/flusher.py CHANGED
@@ -100,11 +100,30 @@ class FlushWorker(threading.Thread):
100
100
  if shutdown and self.pipe.empty():
101
101
  self.should_run = False
102
102
 
103
- def flush(self):
103
+ def flush(self, timeout=None):
104
+ """Block until the worker has drained the queue.
105
+
106
+ If ``timeout`` is given (in seconds), return after at most that much
107
+ wall time has elapsed even if the queue is still non-empty. Returns
108
+ ``True`` if the queue was drained, ``False`` if the timeout fired.
109
+
110
+ The bound matters when the caller holds the global logging lock, as
111
+ ``logging.config.dictConfig()`` does while it flushes and closes the
112
+ handlers it replaces: if this worker is inside an upload whose urllib3
113
+ ``debug()`` call needs that same lock, an unbounded wait deadlocks the
114
+ process. It also keeps a slow or unreachable endpoint from stalling a
115
+ shutdown forever.
116
+ """
104
117
  self._flushing = True
105
- while not self._clean or not self.pipe.empty():
106
- time.sleep(self.check_interval)
107
- self._flushing = False
118
+ try:
119
+ deadline = None if timeout is None else time.time() + timeout
120
+ while not self._clean or not self.pipe.empty():
121
+ if deadline is not None and time.time() >= deadline:
122
+ return False
123
+ time.sleep(self.check_interval)
124
+ return True
125
+ finally:
126
+ self._flushing = False
108
127
 
109
128
  def in_flush_worker():
110
129
  return isinstance(threading.current_thread(), FlushWorker)
logtail/frame.py CHANGED
@@ -93,4 +93,9 @@ def _relative_to_main_module_if_possible(pathname):
93
93
  return _relative_to_main_module(pathname) if has_main_module else pathname
94
94
 
95
95
  def _relative_to_main_module(pathname):
96
- return path.relpath(pathname, path.dirname(__main__.__file__))
96
+ try:
97
+ return path.relpath(pathname, path.dirname(__main__.__file__))
98
+ except ValueError:
99
+ # Windows has no relative path between drives, and a PyInstaller build unpacks the
100
+ # script to the system drive while the application may run from another one.
101
+ return pathname
logtail/handler.py CHANGED
@@ -19,6 +19,8 @@ DEFAULT_RAISE_EXCEPTIONS = False
19
19
  DEFAULT_DROP_EXTRA_EVENTS = True
20
20
  DEFAULT_INCLUDE_EXTRA_ATTRIBUTES = True
21
21
  DEFAULT_TIMEOUT = 30
22
+ DEFAULT_FLUSH_TIMEOUT = 30
23
+
22
24
 
23
25
  _handlers = weakref.WeakSet()
24
26
 
@@ -35,6 +37,7 @@ class LogtailHandler(logging.Handler):
35
37
  include_extra_attributes=DEFAULT_INCLUDE_EXTRA_ATTRIBUTES,
36
38
  context=DEFAULT_CONTEXT,
37
39
  timeout=DEFAULT_TIMEOUT,
40
+ flush_timeout=DEFAULT_FLUSH_TIMEOUT,
38
41
  level=logging.NOTSET):
39
42
  super(LogtailHandler, self).__init__(level=level)
40
43
  self.source_token = source_token
@@ -51,6 +54,7 @@ class LogtailHandler(logging.Handler):
51
54
  self.flush_interval = flush_interval
52
55
  self.check_interval = check_interval
53
56
  self.raise_exceptions = raise_exceptions
57
+ self.flush_timeout = flush_timeout
54
58
  self.dropcount = 0
55
59
  # Do not initialize the flush thread yet because it causes issues on Render.
56
60
  self.flush_thread = None
@@ -91,8 +95,16 @@ class LogtailHandler(logging.Handler):
91
95
  raise e
92
96
 
93
97
  def flush(self):
94
- if self.flush_thread and self.flush_thread.is_alive():
95
- self.flush_thread.flush()
98
+ if not (self.flush_thread and self.flush_thread.is_alive()):
99
+ return
100
+ if logging._lock._is_owned():
101
+ # logging.config.dictConfig() flushes the handlers it replaces while holding the global
102
+ # logging lock, and the worker's upload needs that lock whenever urllib3's logger has a
103
+ # level-cache miss, so waiting here could only deadlock. Nothing is lost: the worker
104
+ # keeps running and sends what is queued as soon as the lock is released.
105
+ return
106
+ if not self.flush_thread.flush(timeout=self.flush_timeout):
107
+ print('Gave up waiting for Better Stack uploads after {}s, logs are still buffered'.format(self.flush_timeout))
96
108
 
97
109
  def _reset_after_fork(self):
98
110
  # A forked child inherits the parent's queue with whatever was still buffered in it,
@@ -100,7 +112,7 @@ class LogtailHandler(logging.Handler):
100
112
  # whose socket it shares with the parent, so it starts over with fresh ones.
101
113
  self.pipe = queue.Queue(maxsize=self.buffer_capacity)
102
114
  self.flush_thread = None
103
- self.uploader = Uploader(self.source_token, self.host, self.uploader.timeout)
115
+ self.uploader.reset()
104
116
 
105
117
 
106
118
  def _reset_handlers_after_fork():
logtail/uploader.py CHANGED
@@ -1,7 +1,9 @@
1
1
  # coding: utf-8
2
2
  from __future__ import print_function, unicode_literals
3
+ import os
3
4
  import msgpack
4
5
  import requests
6
+ import requests.utils
5
7
 
6
8
  class Fake500(object):
7
9
  def __init__(self, exception):
@@ -13,11 +15,17 @@ class Uploader(object):
13
15
  self.source_token = source_token
14
16
  self.host = host
15
17
  self.timeout = timeout
16
- self.session = requests.Session()
18
+ # requests would otherwise ask the environment, and on macOS SystemConfiguration, for
19
+ # proxy settings on every request. Resolving them once here keeps the flush thread out
20
+ # of CoreFoundation, which refuses to run in a child forked from a multithreaded
21
+ # process, such as an RQ work horse. The CA bundle variables follow the same rule.
22
+ self.proxies = requests.utils.get_environ_proxies(host)
23
+ self.verify = os.environ.get('REQUESTS_CA_BUNDLE') or os.environ.get('CURL_CA_BUNDLE') or True
17
24
  self.headers = {
18
25
  'Authorization': 'Bearer %s' % source_token,
19
26
  'Content-Type': 'application/msgpack',
20
27
  }
28
+ self.session = self._new_session()
21
29
 
22
30
  def __call__(self, frame):
23
31
  data = msgpack.packb(frame, use_bin_type=True)
@@ -25,3 +33,15 @@ class Uploader(object):
25
33
  return self.session.post(self.host, data=data, headers=self.headers, timeout=self.timeout)
26
34
  except requests.RequestException as e:
27
35
  return Fake500(e)
36
+
37
+ def reset(self):
38
+ # A forked child shares the parent's pooled socket, so it needs a session of its own,
39
+ # built from the settings resolved above rather than looked up again.
40
+ self.session = self._new_session()
41
+
42
+ def _new_session(self):
43
+ session = requests.Session()
44
+ session.trust_env = False
45
+ session.proxies = self.proxies
46
+ session.verify = self.verify
47
+ return session
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logtail-python
3
- Version: 0.4.1
3
+ Version: 0.4.3
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.1
6
+ Download-URL: https://github.com/logtail/logtail-python/tarball/0.4.3
7
7
  Author: Logtail
8
8
  Author-email: hello@logtail.com
9
9
  License: ISC
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
20
21
  Classifier: Programming Language :: Python
21
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
23
  Requires-Python: >=3.10
@@ -0,0 +1,14 @@
1
+ logtail/__init__.py,sha256=s4LOmVNtxWLWS5XoL0bE-d7v8zZAqP_d-xh77sTZ_z8,252
2
+ logtail/compat.py,sha256=KbZmcmDpj_vbMotp16pADPwJ1FkEHGDNjKbOfCy-c10,141
3
+ logtail/flusher.py,sha256=bPSCvKNniXBZWYY5AVZ0w6lFkxswe739Ooa3ZAj608s,5987
4
+ logtail/formatter.py,sha256=nk4W1G95P19ijylPBJ8D9FlO_qbh3tfbv7xzCSfpDN8,968
5
+ logtail/frame.py,sha256=Vmh6UZWz86i16faH1fYqeCS6UUy7-29-XHDgMPwGfB0,3571
6
+ logtail/handler.py,sha256=Cuz8QmFGzBVTpMb6DZZV6VEmJ9smlM3RaqTTBei3GXY,4952
7
+ logtail/helpers.py,sha256=ezDTLsTBAtCtIh_XptSYOiBrZFa-E9wo2SWTnjKgOOU,1156
8
+ logtail/rq.py,sha256=SEjVwjp8lRLab2F0StdssBQ0-vFK1CekEgYwkSICBeo,1023
9
+ logtail/uploader.py,sha256=5oeNxmnyogq1w9MoK2YkxQGxQy_bJ-yS7RqWzltQrKY,1873
10
+ logtail_python-0.4.3.dist-info/licenses/LICENSE.md,sha256=aCBMqxfm7QNyFR9JiJ2GSMvhAURXN2E0yTcYNQloqI0,737
11
+ logtail_python-0.4.3.dist-info/METADATA,sha256=ry2U2ZRaMnPu10JiI7AaSjB2BSu5K-45Q7QfVe4UBTM,2480
12
+ logtail_python-0.4.3.dist-info/WHEEL,sha256=4YBfCYNH4wlLpv3pzq1hbEuIlXA4WJabKLFurZ7eTL0,109
13
+ logtail_python-0.4.3.dist-info/top_level.txt,sha256=QZIbQogN1yFBgjiJ-8NlYArVCL00gBoYbEUT_Ew8l-Q,8
14
+ logtail_python-0.4.3.dist-info/RECORD,,
@@ -1,14 +0,0 @@
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,,