logtail-python 0.4.1__tar.gz → 0.4.2__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.1 → logtail_python-0.4.2}/PKG-INFO +3 -2
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail/__init__.py +1 -1
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail/flusher.py +23 -4
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail/handler.py +7 -2
- logtail_python-0.4.2/logtail/uploader.py +47 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail_python.egg-info/PKG-INFO +3 -2
- {logtail_python-0.4.1 → logtail_python-0.4.2}/setup.py +2 -1
- {logtail_python-0.4.1 → logtail_python-0.4.2}/tests/test_flusher.py +30 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/tests/test_handler.py +45 -3
- logtail_python-0.4.2/tests/test_uploader.py +66 -0
- logtail_python-0.4.1/logtail/uploader.py +0 -27
- logtail_python-0.4.1/tests/test_uploader.py +0 -36
- {logtail_python-0.4.1 → logtail_python-0.4.2}/LICENSE.md +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/MANIFEST.in +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/README.md +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail/compat.py +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail/formatter.py +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail/frame.py +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail/helpers.py +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail/rq.py +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail_python.egg-info/SOURCES.txt +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail_python.egg-info/dependency_links.txt +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail_python.egg-info/requires.txt +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/logtail_python.egg-info/top_level.txt +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/requirements.txt +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/setup.cfg +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/test-requirements.txt +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/tests/__init__.py +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/tests/test_formatter.py +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/tests/test_frame.py +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/tests/test_helpers.py +0 -0
- {logtail_python-0.4.1 → logtail_python-0.4.2}/tests/test_rq.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: logtail-python
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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.2
|
|
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
|
|
@@ -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
|
-
|
|
106
|
-
time.
|
|
107
|
-
|
|
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)
|
|
@@ -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
|
|
@@ -92,7 +96,8 @@ class LogtailHandler(logging.Handler):
|
|
|
92
96
|
|
|
93
97
|
def flush(self):
|
|
94
98
|
if self.flush_thread and self.flush_thread.is_alive():
|
|
95
|
-
|
|
99
|
+
if not self.flush_thread.flush(timeout=self.flush_timeout):
|
|
100
|
+
print('Gave up waiting for Better Stack uploads after {}s, logs are still buffered'.format(self.flush_timeout))
|
|
96
101
|
|
|
97
102
|
def _reset_after_fork(self):
|
|
98
103
|
# A forked child inherits the parent's queue with whatever was still buffered in it,
|
|
@@ -100,7 +105,7 @@ class LogtailHandler(logging.Handler):
|
|
|
100
105
|
# whose socket it shares with the parent, so it starts over with fresh ones.
|
|
101
106
|
self.pipe = queue.Queue(maxsize=self.buffer_capacity)
|
|
102
107
|
self.flush_thread = None
|
|
103
|
-
self.uploader
|
|
108
|
+
self.uploader.reset()
|
|
104
109
|
|
|
105
110
|
|
|
106
111
|
def _reset_handlers_after_fork():
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
from __future__ import print_function, unicode_literals
|
|
3
|
+
import os
|
|
4
|
+
import msgpack
|
|
5
|
+
import requests
|
|
6
|
+
import requests.utils
|
|
7
|
+
|
|
8
|
+
class Fake500(object):
|
|
9
|
+
def __init__(self, exception):
|
|
10
|
+
self.status_code = 500
|
|
11
|
+
self.exception = exception
|
|
12
|
+
|
|
13
|
+
class Uploader(object):
|
|
14
|
+
def __init__(self, source_token, host, timeout):
|
|
15
|
+
self.source_token = source_token
|
|
16
|
+
self.host = host
|
|
17
|
+
self.timeout = timeout
|
|
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
|
|
24
|
+
self.headers = {
|
|
25
|
+
'Authorization': 'Bearer %s' % source_token,
|
|
26
|
+
'Content-Type': 'application/msgpack',
|
|
27
|
+
}
|
|
28
|
+
self.session = self._new_session()
|
|
29
|
+
|
|
30
|
+
def __call__(self, frame):
|
|
31
|
+
data = msgpack.packb(frame, use_bin_type=True)
|
|
32
|
+
try:
|
|
33
|
+
return self.session.post(self.host, data=data, headers=self.headers, timeout=self.timeout)
|
|
34
|
+
except requests.RequestException as e:
|
|
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.
|
|
3
|
+
Version: 0.4.2
|
|
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.2
|
|
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
|
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
from setuptools import setup
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
VERSION = '0.4.
|
|
6
|
+
VERSION = '0.4.2'
|
|
7
7
|
ROOT_DIR = os.path.dirname(__file__)
|
|
8
8
|
|
|
9
9
|
REQUIREMENTS = [
|
|
@@ -40,6 +40,7 @@ setup(
|
|
|
40
40
|
'Programming Language :: Python :: 3.12',
|
|
41
41
|
'Programming Language :: Python :: 3.13',
|
|
42
42
|
'Programming Language :: Python :: 3.14',
|
|
43
|
+
'Programming Language :: Python :: 3.15',
|
|
43
44
|
'Programming Language :: Python',
|
|
44
45
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
45
46
|
],
|
|
@@ -233,3 +233,33 @@ class TestFlushWorker(unittest.TestCase):
|
|
|
233
233
|
self.assertFalse(threading.excepthook.called)
|
|
234
234
|
|
|
235
235
|
threading.excepthook = original_excepthook
|
|
236
|
+
|
|
237
|
+
def test_flush_returns_immediately_on_empty_clean_queue(self):
|
|
238
|
+
_, _, fw = self._setup_worker()
|
|
239
|
+
# _clean defaults to True and pipe is empty — flush should return at once.
|
|
240
|
+
t1 = time.time()
|
|
241
|
+
result = fw.flush()
|
|
242
|
+
elapsed = time.time() - t1
|
|
243
|
+
self.assertTrue(result)
|
|
244
|
+
self.assertLess(elapsed, 0.1)
|
|
245
|
+
|
|
246
|
+
def test_flush_with_timeout_returns_false_when_queue_not_drained(self):
|
|
247
|
+
pipe, _, fw = self._setup_worker()
|
|
248
|
+
# Put an item in but never start the worker thread → queue stays full,
|
|
249
|
+
# so flush() will spin until the timeout fires.
|
|
250
|
+
pipe.put(object(), block=False)
|
|
251
|
+
|
|
252
|
+
t1 = time.time()
|
|
253
|
+
result = fw.flush(timeout=0.05)
|
|
254
|
+
elapsed = time.time() - t1
|
|
255
|
+
|
|
256
|
+
self.assertFalse(result)
|
|
257
|
+
# Bounded: should return shortly after the timeout, not block forever.
|
|
258
|
+
self.assertLess(elapsed, 1.0)
|
|
259
|
+
self.assertGreaterEqual(elapsed, 0.04)
|
|
260
|
+
|
|
261
|
+
def test_flush_clears_flushing_flag_even_on_timeout(self):
|
|
262
|
+
pipe, _, fw = self._setup_worker()
|
|
263
|
+
pipe.put(object(), block=False)
|
|
264
|
+
fw.flush(timeout=0.05)
|
|
265
|
+
self.assertFalse(fw._flushing)
|
|
@@ -27,11 +27,49 @@ class TestLogtailHandler(unittest.TestCase):
|
|
|
27
27
|
# Test default timeout
|
|
28
28
|
handler = LogtailHandler(source_token=self.source_token, host=self.host)
|
|
29
29
|
self.assertEqual(handler.uploader.timeout, 30)
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
# Test custom timeout
|
|
32
32
|
handler = LogtailHandler(source_token=self.source_token, host=self.host, timeout=10)
|
|
33
33
|
self.assertEqual(handler.uploader.timeout, 10)
|
|
34
34
|
|
|
35
|
+
@patch('logtail.handler.FlushWorker')
|
|
36
|
+
def test_handler_stores_flush_timeout(self, MockWorker):
|
|
37
|
+
# Default
|
|
38
|
+
handler = LogtailHandler(source_token=self.source_token, host=self.host)
|
|
39
|
+
self.assertEqual(handler.flush_timeout, 30)
|
|
40
|
+
# Custom
|
|
41
|
+
handler = LogtailHandler(source_token=self.source_token, host=self.host, flush_timeout=2)
|
|
42
|
+
self.assertEqual(handler.flush_timeout, 2)
|
|
43
|
+
# Explicit None disables the timeout (legacy unbounded wait)
|
|
44
|
+
handler = LogtailHandler(source_token=self.source_token, host=self.host, flush_timeout=None)
|
|
45
|
+
self.assertIsNone(handler.flush_timeout)
|
|
46
|
+
|
|
47
|
+
@patch('logtail.handler.FlushWorker')
|
|
48
|
+
def test_handler_passes_flush_timeout_to_worker(self, MockWorker):
|
|
49
|
+
handler = LogtailHandler(source_token=self.source_token, host=self.host, flush_timeout=7)
|
|
50
|
+
# Trigger flush_thread creation by emitting once.
|
|
51
|
+
logger = logging.getLogger(__name__)
|
|
52
|
+
logger.handlers = []
|
|
53
|
+
logger.addHandler(handler)
|
|
54
|
+
logger.critical('hello')
|
|
55
|
+
# The mocked FlushWorker is alive by default; flush() forwards timeout.
|
|
56
|
+
handler.flush()
|
|
57
|
+
handler.flush_thread.flush.assert_called_with(timeout=7)
|
|
58
|
+
|
|
59
|
+
@patch('logtail.handler.print', create=True)
|
|
60
|
+
@patch('logtail.handler.FlushWorker')
|
|
61
|
+
def test_flush_reports_giving_up(self, MockWorker, mock_print):
|
|
62
|
+
handler = LogtailHandler(source_token=self.source_token, host=self.host, flush_timeout=3)
|
|
63
|
+
logger = logging.getLogger(__name__)
|
|
64
|
+
logger.handlers = []
|
|
65
|
+
logger.addHandler(handler)
|
|
66
|
+
logger.critical('hello')
|
|
67
|
+
handler.flush_thread.flush.return_value = False
|
|
68
|
+
|
|
69
|
+
handler.flush()
|
|
70
|
+
|
|
71
|
+
mock_print.assert_called_once_with('Gave up waiting for Better Stack uploads after 3s, logs are still buffered')
|
|
72
|
+
|
|
35
73
|
@patch('logtail.handler.FlushWorker')
|
|
36
74
|
def test_handler_creates_pipe_from_args(self, MockWorker):
|
|
37
75
|
buffer_capacity = 9
|
|
@@ -285,11 +323,15 @@ class TestLogtailHandler(unittest.TestCase):
|
|
|
285
323
|
logger.handlers = []
|
|
286
324
|
logger.addHandler(handler)
|
|
287
325
|
logger.critical('queued in the parent')
|
|
288
|
-
|
|
326
|
+
uploader = handler.uploader
|
|
327
|
+
parent_session = uploader.session
|
|
289
328
|
|
|
290
329
|
pid = os.fork()
|
|
291
330
|
if pid == 0:
|
|
292
|
-
|
|
331
|
+
# The child gets its own session, but never resolves proxy settings again: on macOS
|
|
332
|
+
# that means SystemConfiguration, which refuses to run in a forked child.
|
|
333
|
+
fresh = (handler.pipe.empty() and handler.flush_thread is None and handler.uploader is uploader
|
|
334
|
+
and uploader.session is not parent_session and uploader.session.trust_env is False)
|
|
293
335
|
os._exit(0 if fresh else 1)
|
|
294
336
|
_, status = os.waitpid(pid, 0)
|
|
295
337
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
from __future__ import print_function, unicode_literals
|
|
3
|
+
import msgpack
|
|
4
|
+
import mock
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
|
|
9
|
+
from logtail.uploader import Uploader
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestUploader(unittest.TestCase):
|
|
13
|
+
host = 'https://in.logtail.com'
|
|
14
|
+
source_token = 'dummy_source_token'
|
|
15
|
+
frame = [1, 2, 3]
|
|
16
|
+
timeout = 30
|
|
17
|
+
|
|
18
|
+
@patch('logtail.uploader.requests.Session.post')
|
|
19
|
+
def test_call(self, post):
|
|
20
|
+
def mock_post(endpoint, data=None, headers=None, timeout=None):
|
|
21
|
+
# Check that the data is sent to ther correct endpoint
|
|
22
|
+
self.assertEqual(endpoint, self.host)
|
|
23
|
+
# Check the content-type
|
|
24
|
+
self.assertIsInstance(headers, dict)
|
|
25
|
+
self.assertIn('Authorization', headers)
|
|
26
|
+
self.assertEqual('application/msgpack', headers.get('Content-Type'))
|
|
27
|
+
# Check the content was msgpacked correctly
|
|
28
|
+
self.assertEqual(msgpack.unpackb(data, raw=False), self.frame)
|
|
29
|
+
# Check that timeout is passed to the request
|
|
30
|
+
self.assertEqual(timeout, 30)
|
|
31
|
+
|
|
32
|
+
post.side_effect = mock_post
|
|
33
|
+
u = Uploader(self.source_token, self.host, self.timeout)
|
|
34
|
+
u(self.frame)
|
|
35
|
+
|
|
36
|
+
self.assertTrue(post.called)
|
|
37
|
+
|
|
38
|
+
@patch('logtail.uploader.requests.utils.get_environ_proxies', return_value={'https': 'http://proxy.internal:3128'})
|
|
39
|
+
def test_resolves_proxy_settings_once_instead_of_on_every_request(self, get_environ_proxies):
|
|
40
|
+
u = Uploader(self.source_token, self.host, self.timeout)
|
|
41
|
+
|
|
42
|
+
with patch('logtail.uploader.requests.Session.post'):
|
|
43
|
+
u(self.frame)
|
|
44
|
+
u(self.frame)
|
|
45
|
+
|
|
46
|
+
get_environ_proxies.assert_called_once_with(self.host)
|
|
47
|
+
self.assertEqual(u.session.proxies, {'https': 'http://proxy.internal:3128'})
|
|
48
|
+
self.assertFalse(u.session.trust_env)
|
|
49
|
+
|
|
50
|
+
@patch.dict('os.environ', {'REQUESTS_CA_BUNDLE': '/etc/ssl/corporate-ca.pem'})
|
|
51
|
+
def test_uses_the_ca_bundle_from_the_environment(self):
|
|
52
|
+
u = Uploader(self.source_token, self.host, self.timeout)
|
|
53
|
+
|
|
54
|
+
self.assertEqual(u.session.verify, '/etc/ssl/corporate-ca.pem')
|
|
55
|
+
|
|
56
|
+
@patch('logtail.uploader.requests.utils.get_environ_proxies', return_value={'https': 'http://proxy.internal:3128'})
|
|
57
|
+
def test_reset_replaces_the_session_but_keeps_the_resolved_settings(self, get_environ_proxies):
|
|
58
|
+
u = Uploader(self.source_token, self.host, self.timeout)
|
|
59
|
+
session = u.session
|
|
60
|
+
|
|
61
|
+
u.reset()
|
|
62
|
+
|
|
63
|
+
self.assertIsNot(u.session, session)
|
|
64
|
+
self.assertEqual(u.session.proxies, {'https': 'http://proxy.internal:3128'})
|
|
65
|
+
self.assertFalse(u.session.trust_env)
|
|
66
|
+
get_environ_proxies.assert_called_once_with(self.host)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# coding: utf-8
|
|
2
|
-
from __future__ import print_function, unicode_literals
|
|
3
|
-
import msgpack
|
|
4
|
-
import requests
|
|
5
|
-
|
|
6
|
-
class Fake500(object):
|
|
7
|
-
def __init__(self, exception):
|
|
8
|
-
self.status_code = 500
|
|
9
|
-
self.exception = exception
|
|
10
|
-
|
|
11
|
-
class Uploader(object):
|
|
12
|
-
def __init__(self, source_token, host, timeout):
|
|
13
|
-
self.source_token = source_token
|
|
14
|
-
self.host = host
|
|
15
|
-
self.timeout = timeout
|
|
16
|
-
self.session = requests.Session()
|
|
17
|
-
self.headers = {
|
|
18
|
-
'Authorization': 'Bearer %s' % source_token,
|
|
19
|
-
'Content-Type': 'application/msgpack',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
def __call__(self, frame):
|
|
23
|
-
data = msgpack.packb(frame, use_bin_type=True)
|
|
24
|
-
try:
|
|
25
|
-
return self.session.post(self.host, data=data, headers=self.headers, timeout=self.timeout)
|
|
26
|
-
except requests.RequestException as e:
|
|
27
|
-
return Fake500(e)
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# coding: utf-8
|
|
2
|
-
from __future__ import print_function, unicode_literals
|
|
3
|
-
import msgpack
|
|
4
|
-
import mock
|
|
5
|
-
import unittest
|
|
6
|
-
|
|
7
|
-
from unittest.mock import patch
|
|
8
|
-
|
|
9
|
-
from logtail.uploader import Uploader
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestUploader(unittest.TestCase):
|
|
13
|
-
host = 'https://in.logtail.com'
|
|
14
|
-
source_token = 'dummy_source_token'
|
|
15
|
-
frame = [1, 2, 3]
|
|
16
|
-
timeout = 30
|
|
17
|
-
|
|
18
|
-
@patch('logtail.uploader.requests.Session.post')
|
|
19
|
-
def test_call(self, post):
|
|
20
|
-
def mock_post(endpoint, data=None, headers=None, timeout=None):
|
|
21
|
-
# Check that the data is sent to ther correct endpoint
|
|
22
|
-
self.assertEqual(endpoint, self.host)
|
|
23
|
-
# Check the content-type
|
|
24
|
-
self.assertIsInstance(headers, dict)
|
|
25
|
-
self.assertIn('Authorization', headers)
|
|
26
|
-
self.assertEqual('application/msgpack', headers.get('Content-Type'))
|
|
27
|
-
# Check the content was msgpacked correctly
|
|
28
|
-
self.assertEqual(msgpack.unpackb(data, raw=False), self.frame)
|
|
29
|
-
# Check that timeout is passed to the request
|
|
30
|
-
self.assertEqual(timeout, 30)
|
|
31
|
-
|
|
32
|
-
post.side_effect = mock_post
|
|
33
|
-
u = Uploader(self.source_token, self.host, self.timeout)
|
|
34
|
-
u(self.frame)
|
|
35
|
-
|
|
36
|
-
self.assertTrue(post.called)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|