Codebase list logbook / 9949c7f
New upstream version 1.5.3 Downstreamer 4 years ago
23 changed file(s) with 447 addition(s) and 138 deletion(s). Raw diff Collapse all Expand all
6363 env*
6464 .vagrant
6565 flycheck-*
66 .idea
67 .python-version
00 language: python
1 dist: xenial
2 addons:
3 apt:
4 sources:
5 - chris-lea-redis-server
6 - sourceline: 'ppa:chris-lea/zeromq'
7 packages:
8 - redis-server
9 - libzmq3-dev
110 services:
211 - redis-server
312 python:
413 - '2.7'
514 - '3.5'
615 - '3.6'
7 - pypy
16 - '3.7'
817 before_install:
918 - pip install coveralls
1019 install:
11 - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
12 - sudo apt-add-repository -y ppa:chris-lea/zeromq
13 - sudo apt-get update
14 - sudo apt-get install -y libzmq3-dev
1520 - pip install -U pip
1621 - pip install cython
1722 - cython logbook/_speedups.pyx
2631
2732 matrix:
2833 exclude:
29 - python: pypy
30 env: CYBUILD=True
31 - python: pypy3
32 env: CYBUILD=True
3334 include:
3435 - python: "3.6"
3536 env: GEVENT=True CYBUILD=True
5657 password:
5758 secure: WFmuAbtBDIkeZArIFQRCwyO1TdvF2PaZpo75r3mFgnY+aWm75cdgjZKoNqVprF/f+v9EsX2kDdQ7ZfuhMLgP8MNziB+ty7579ZDGwh64jGoi+DIoeblAFu5xNAqjvhie540uCE8KySk9s+Pq5EpOA5w18V4zxTw+h6tnBQ0M9cQ=
5859 on:
60 python: "3.7"
61 condition: $CYBUILD = 'True'
5962 tags: true
6063 repo: getlogbook/logbook
6164 distributions: "sdist"
00 Logbook Changelog
11 =================
2
3 Version 1.5.1
4 -------------
5
6 Released on August 20th, 2019
7
8 - Added support for asyncio and contextvars
29
310 Version 1.4.3
411 -------------
2121 [ti]: https://secure.travis-ci.org/getlogbook/logbook.svg?branch=master
2222 [tl]: https://travis-ci.org/getlogbook/logbook
2323 [ai]: https://ci.appveyor.com/api/projects/status/quu99exa26e06npp?svg=true
24 [vi]: https://img.shields.io/badge/python-2.6%2C2.7%2C3.3%2C3.4%2C3.5-green.svg
24 [vi]: https://img.shields.io/badge/python-2.7%2C3.5%2C3.6%2C3.7-green.svg
2525 [di]: https://img.shields.io/pypi/dm/logbook.svg
2626 [al]: https://ci.appveyor.com/project/vmalloc/logbook
2727 [pi]: https://img.shields.io/pypi/v/logbook.svg
28 [pl]: https://pypi.python.org/pypi/Logbook
28 [pl]: https://pypi.org/pypi/Logbook
2929 [ci]: https://coveralls.io/repos/getlogbook/logbook/badge.svg?branch=master&service=github
3030 [cl]: https://coveralls.io/github/getlogbook/logbook?branch=master
3838 - PYTHON: "C:\\Python36-x64"
3939 CYBUILD: "TRUE"
4040
41 - PYTHON: "C:\\Python37"
42 - PYTHON: "C:\\Python37"
43 CYBUILD: "TRUE"
44
45 - PYTHON: "C:\\Python37-x64"
46 - PYTHON: "C:\\Python37-x64"
47 CYBUILD: "TRUE"
48
4149 init:
4250 - echo %PYTHON%
4351 - set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%
11 """
22 Runs the benchmarks
33 """
4 from __future__ import print_function
45 import sys
56 import os
67 import re
3839
3940
4041 def bench_wrapper(use_gevent=False):
41 print '=' * 80
42 print 'Running benchmark with Logbook %s (gevent enabled=%s)' % \
43 (version, use_gevent)
44 print '-' * 80
42 print('=' * 80)
43 print('Running benchmark with Logbook %s (gevent enabled=%s)' % (version, use_gevent))
44 print('-' * 80)
4545 os.chdir(bench_directory)
4646 for bench in list_benchmarks():
4747 run_bench(bench, use_gevent)
48 print '-' * 80
48 print('-' * 80)
4949
5050
5151 def main():
5050 * `Mailing list`_
5151 * IRC: ``#pocoo`` on freenode
5252
53 .. _Download from PyPI: http://pypi.python.org/pypi/Logbook
53 .. _Download from PyPI: https://pypi.org/pypi/Logbook
5454 .. _Master repository on GitHub: https://github.com/getlogbook/logbook
5555 .. _Mailing list: http://groups.google.com/group/pocoo-libs
0 __version__ = "1.4.3"
0 __version__ = "1.5.3"
1111 from logbook.helpers import get_iterator_next_method
1212 from logbook.concurrency import (
1313 thread_get_ident, greenlet_get_ident, thread_local, greenlet_local,
14 ThreadLock, GreenletRLock, is_gevent_enabled)
14 ThreadLock, GreenletRLock, is_gevent_enabled, ContextVar, context_get_ident,
15 is_context_enabled)
1516
1617 _missing = object()
1718 _MAX_CONTEXT_OBJECT_CACHE = 256
6667 """Pops the stacked object from the greenlet stack."""
6768 raise NotImplementedError()
6869
70 def push_context(self):
71 """Pushes the stacked object to the context stack."""
72 raise NotImplementedError()
73
74 def pop_context(self):
75 """Pops the stacked object from the context stack."""
76 raise NotImplementedError()
77
6978 def push_thread(self):
7079 """Pushes the stacked object to the thread stack."""
7180 raise NotImplementedError()
100109 execute code while the object is bound to the greenlet.
101110 """
102111 return _cls(self, self.push_greenlet, self.pop_greenlet)
112
113 def contextbound(self, _cls=_StackBound):
114 """Can be used in combination with the `with` statement to
115 execute code while the object is bound to the concurrent
116 context.
117 """
118 return _cls(self, self.push_context, self.pop_context)
103119
104120 def threadbound(self, _cls=_StackBound):
105121 """Can be used in combination with the `with` statement to
125141 self._thread_context = thread_local()
126142 self._greenlet_context_lock = GreenletRLock()
127143 self._greenlet_context = greenlet_local()
144 self._context_stack = ContextVar('stack')
128145 self._cache = {}
129146 self._stackop = get_iterator_next_method(count())
130147
133150 application and context cache.
134151 """
135152 use_gevent = is_gevent_enabled()
136 tid = greenlet_get_ident() if use_gevent else thread_get_ident()
153 use_context = is_context_enabled()
154
155 if use_gevent:
156 tid = greenlet_get_ident()
157 elif use_context:
158 tid = context_get_ident()
159 else:
160 tid = thread_get_ident()
161
137162 objects = self._cache.get(tid)
138163 if objects is None:
139164 if len(self._cache) > _MAX_CONTEXT_OBJECT_CACHE:
140165 self._cache.clear()
141166 objects = self._global[:]
142167 objects.extend(getattr(self._thread_context, 'stack', ()))
168
143169 if use_gevent:
144170 objects.extend(getattr(self._greenlet_context, 'stack', ()))
171
172 if use_context:
173 objects.extend(self._context_stack.get([]))
174
145175 objects.sort(reverse=True)
146176 objects = [x[1] for x in objects]
147177 self._cache[tid] = objects
172202 finally:
173203 self._greenlet_context_lock.release()
174204
205 def push_context(self, obj):
206 self._cache.pop(context_get_ident(), None)
207 item = (self._stackop(), obj)
208 stack = self._context_stack.get(None)
209 if stack is None:
210 stack = [item]
211 self._context_stack.set(stack)
212 else:
213 stack.append(item)
214
215 def pop_context(self):
216 self._cache.pop(context_get_ident(), None)
217 stack = self._context_stack.get(None)
218 assert stack, 'no objects on stack'
219 return stack.pop()[1]
220
175221 def push_thread(self, obj):
176222 self._thread_context_lock.acquire()
177223 try:
00 # -*- coding: utf-8 -*-
1 # cython: language_level=2
12 """
23 logbook._speedups
34 ~~~~~~~~~~~~~~~~~
89 :license: BSD, see LICENSE for more details.
910 """
1011
11 import platform
12
1213 from logbook.concurrency import (is_gevent_enabled, thread_get_ident, greenlet_get_ident, thread_local,
13 GreenletRLock, greenlet_local)
14 GreenletRLock, greenlet_local, ContextVar, context_get_ident, is_context_enabled)
1415
1516 from cpython.dict cimport PyDict_Clear, PyDict_SetItem
16 from cpython.list cimport PyList_New, PyList_Append, PyList_Sort, \
17 PyList_SET_ITEM, PyList_GET_SIZE
17 from cpython.list cimport PyList_Append, PyList_Sort, PyList_GET_SIZE
18
1819 from cpython.pythread cimport PyThread_type_lock, PyThread_allocate_lock, \
1920 PyThread_release_lock, PyThread_acquire_lock, WAIT_LOCK
2021
21 cdef object _missing = object()
22 _missing = object()
2223
2324 cdef enum:
2425 _MAX_CONTEXT_OBJECT_CACHE = 256
3940 def __get__(self, obj, type):
4041 if obj is None:
4142 return self
42 rv = getattr3(obj, self._name, _missing)
43 rv = getattr(obj, self._name, _missing)
4344 if rv is not _missing and rv != self.fallback:
4445 return rv
4546 if obj.group is None:
9596
9697
9798 cdef class StackedObject:
98 """Baseclass for all objects that provide stack manipulation
99 """Base class for all objects that provide stack manipulation
99100 operations.
100101 """
102 cpdef push_context(self):
103 """Pushes the stacked object to the asyncio (via contextvar) stack."""
104 raise NotImplementedError()
105
106 cpdef pop_context(self):
107 """Pops the stacked object from the asyncio (via contextvar) stack."""
108 raise NotImplementedError()
101109
102110 cpdef push_greenlet(self):
103111 """Pushes the stacked object to the greenlet stack."""
153161 execute code while the object is bound to the application.
154162 """
155163 return _StackBound(self, self.push_application, self.pop_application)
164
165 cpdef contextbound(self):
166 """Can be used in combination with the `with` statement to
167 execute code while the object is bound to the asyncio context.
168 """
169 return _StackBound(self, self.push_context, self.pop_context)
156170
157171
158172 cdef class ContextStackManager:
161175 cdef object _thread_context
162176 cdef object _greenlet_context_lock
163177 cdef object _greenlet_context
178 cdef object _context_stack
164179 cdef dict _cache
165180 cdef int _stackcnt
166181
170185 self._thread_context = thread_local()
171186 self._greenlet_context_lock = GreenletRLock()
172187 self._greenlet_context = greenlet_local()
188 self._context_stack = ContextVar('stack')
173189 self._cache = {}
174190 self._stackcnt = 0
175191
179195
180196 cpdef iter_context_objects(self):
181197 use_gevent = is_gevent_enabled()
182 tid = greenlet_get_ident() if use_gevent else thread_get_ident()
198 use_context = is_context_enabled()
199
200 if use_gevent:
201 tid = greenlet_get_ident()
202 elif use_context:
203 tid = context_get_ident()
204 else:
205 tid = thread_get_ident()
206
183207 objects = self._cache.get(tid)
184208 if objects is None:
185209 if PyList_GET_SIZE(self._cache) > _MAX_CONTEXT_OBJECT_CACHE:
186210 PyDict_Clear(self._cache)
187211 objects = self._global[:]
188 objects.extend(getattr3(self._thread_context, 'stack', ()))
212 objects.extend(getattr(self._thread_context, 'stack', ()))
213
189214 if use_gevent:
190 objects.extend(getattr3(self._greenlet_context, 'stack', ()))
215 objects.extend(getattr(self._greenlet_context, 'stack', ()))
216
217 if use_context:
218 objects.extend(self._context_stack.get([]))
219
191220 PyList_Sort(objects)
192221 objects = [(<_StackItem>x).val for x in objects]
193222 PyDict_SetItem(self._cache, tid, objects)
198227 try:
199228 self._cache.pop(greenlet_get_ident(), None)
200229 item = _StackItem(self._stackop(), obj)
201 stack = getattr3(self._greenlet_context, 'stack', None)
230 stack = getattr(self._greenlet_context, 'stack', None)
202231 if stack is None:
203232 self._greenlet_context.stack = [item]
204233 else:
210239 self._greenlet_context_lock.acquire()
211240 try:
212241 self._cache.pop(greenlet_get_ident(), None)
213 stack = getattr3(self._greenlet_context, 'stack', None)
242 stack = getattr(self._greenlet_context, 'stack', None)
214243 assert stack, 'no objects on stack'
215244 return (<_StackItem>stack.pop()).val
216245 finally:
217246 self._greenlet_context_lock.release()
218247
248 cpdef push_context(self, obj):
249 self._cache.pop(context_get_ident(), None)
250 item = _StackItem(self._stackop(), obj)
251 stack = self._context_stack.get(None)
252
253 if stack is None:
254 stack = [item]
255 self._context_stack.set(stack)
256 else:
257 PyList_Append(stack, item)
258
259 cpdef pop_context(self):
260 self._cache.pop(context_get_ident(), None)
261 stack = self._context_stack.get(None)
262 assert stack, 'no objects on stack'
263 return (<_StackItem>stack.pop()).val
264
219265 cpdef push_thread(self, obj):
220266 PyThread_acquire_lock(self._thread_context_lock, WAIT_LOCK)
221267 try:
222268 self._cache.pop(thread_get_ident(), None)
223269 item = _StackItem(self._stackop(), obj)
224 stack = getattr3(self._thread_context, 'stack', None)
270 stack = getattr(self._thread_context, 'stack', None)
225271 if stack is None:
226272 self._thread_context.stack = [item]
227273 else:
233279 PyThread_acquire_lock(self._thread_context_lock, WAIT_LOCK)
234280 try:
235281 self._cache.pop(thread_get_ident(), None)
236 stack = getattr3(self._thread_context, 'stack', None)
282 stack = getattr(self._thread_context, 'stack', None)
237283 assert stack, 'no objects on stack'
238284 return (<_StackItem>stack.pop()).val
239285 finally:
2222 parse_iso8601, string_types, to_safe_json, u,
2323 xrange)
2424
25 _has_speedups = False
2526 try:
27 if os.environ.get('DISABLE_LOGBOOK_CEXT_AT_RUNTIME'):
28 raise ImportError("Speedups disabled via DISABLE_LOGBOOK_CEXT_AT_RUNTIME")
29
2630 from logbook._speedups import (
2731 _missing, group_reflected_property, ContextStackManager, StackedObject)
32
33 _has_speedups = True
2834 except ImportError:
2935 from logbook._fallback import (
3036 _missing, group_reflected_property, ContextStackManager, StackedObject)
7278 logbook.set_datetime_format("local")
7379
7480 Other uses rely on your supplied :py:obj:`datetime_format`.
75 Using `pytz <https://pypi.python.org/pypi/pytz>`_ for example::
81 Using `pytz <https://pypi.org/pypi/pytz>`_ for example::
7682
7783 from datetime import datetime
7884 import logbook
205211 popped = self.stack_manager.pop_greenlet()
206212 assert popped is self, 'popped unexpected object'
207213
214 def push_context(self):
215 """Pushes the context object to the context stack."""
216 self.stack_manager.push_context(self)
217
218 def pop_context(self):
219 """Pops the context object from the stack."""
220 popped = self.stack_manager.pop_context()
221 assert popped is self, 'popped unexpected object'
222
208223 def push_thread(self):
209224 """Pushes the context object to the thread stack."""
210225 self.stack_manager.push_thread(self)
255270 def pop_greenlet(self):
256271 for obj in reversed(self.objects):
257272 obj.pop_greenlet()
273
274 def push_context(self):
275 for obj in self.objects:
276 obj.push_context()
277
278 def pop_context(self):
279 for obj in reversed(self.objects):
280 obj.pop_context()
258281
259282
260283 class Processor(ContextObject):
1515 from datetime import date, datetime
1616
1717 import logbook
18 from logbook.helpers import u, string_types, iteritems
18 from logbook.helpers import u, string_types, iteritems, collections_abc
1919
2020 _epoch_ord = date(1970, 1, 1).toordinal()
2121
132132 kwargs = None
133133
134134 # Logging allows passing a mapping object, in which case args will be a mapping.
135 if isinstance(args, collections.Mapping):
135 if isinstance(args, collections_abc.Mapping):
136136 kwargs = args
137137 args = None
138138 record = LoggingCompatRecord(old_record.name,
163163 return GreenletRLock()
164164 else:
165165 return ThreadRLock()
166
167
168 has_contextvars = True
169 try:
170 import contextvars
171 except ImportError:
172 has_contextvars = False
173
174 if has_contextvars:
175 from contextvars import ContextVar
176 from itertools import count
177
178 context_ident_counter = count()
179 context_ident = ContextVar('context_ident')
180
181 def context_get_ident():
182 try:
183 return context_ident.get()
184 except LookupError:
185 ident = 'context-%s' % next(context_ident_counter)
186 context_ident.set(ident)
187 return ident
188
189 def is_context_enabled():
190 try:
191 context_ident.get()
192 return True
193 except LookupError:
194 return False
195
196 else:
197 class ContextVar(object):
198 def __init__(self, name):
199 self.name = name
200 self.local = thread_local()
201
202 def set(self, value):
203 self.local = value
204
205 def get(self, default=None):
206 if self.local is None:
207 return default
208
209 return default
210
211 def context_get_ident():
212 return 1
213
214 def is_context_enabled():
215 return False
3131 _missing, lookup_level, Flags, ContextObject, ContextStackManager,
3232 _datetime_factory)
3333 from logbook.helpers import (
34 rename, b, _is_text_stream, is_unicode, PY2, zip, xrange, string_types,
34 rename, b, _is_text_stream, is_unicode, PY2, zip, xrange, string_types, collections_abc,
3535 integer_types, reraise, u, with_metaclass)
3636 from logbook.concurrency import new_fine_grained_lock
3737
13541354 # - tuple to be unpacked to variables keyfile and certfile.
13551355 # - secure=() equivalent to secure=True for backwards compatibility.
13561356 # - secure=False equivalent to secure=None to disable.
1357 if isinstance(self.secure, collections.Mapping):
1357 if isinstance(self.secure, collections_abc.Mapping):
13581358 keyfile = self.secure.get('keyfile', None)
13591359 certfile = self.secure.get('certfile', None)
1360 elif isinstance(self.secure, collections.Iterable):
1360 elif isinstance(self.secure, collections_abc.Iterable):
13611361 # Allow empty tuple for backwards compatibility
13621362 if len(self.secure) == 0:
13631363 keyfile = certfile = None
13801380 con.ehlo()
13811381
13821382 # Allow credentials to be a tuple or dict.
1383 if isinstance(self.credentials, collections.Mapping):
1383 if isinstance(self.credentials, collections_abc.Mapping):
13841384 credentials_args = ()
13851385 credentials_kwargs = self.credentials
13861386 else:
18991899 Handler.pop_thread(self)
19001900 self.rollover()
19011901
1902 def pop_context(self):
1903 Handler.pop_context(self)
1904 self.rollover()
1905
19021906 def pop_greenlet(self):
19031907 Handler.pop_greenlet(self)
19041908 self.rollover()
1919
2020 if PY2:
2121 import __builtin__ as _builtins
22 import collections as collections_abc
2223 else:
2324 import builtins as _builtins
25 import collections.abc as collections_abc
2426
2527 try:
2628 import json
326326 .. versionchanged:: 1.0.0
327327 Added Windows support if `colorama`_ is installed.
328328
329 .. _`colorama`: https://pypi.python.org/pypi/colorama
329 .. _`colorama`: https://pypi.org/pypi/colorama
330330 """
331331 _use_color = None
332332
382382 .. versionchanged:: 1.0
383383 Added Windows support if `colorama`_ is installed.
384384
385 .. _`colorama`: https://pypi.python.org/pypi/colorama
385 .. _`colorama`: https://pypi.org/pypi/colorama
386386 """
387387 def __init__(self, *args, **kwargs):
388388 StderrHandler.__init__(self, *args, **kwargs)
470470
471471 def pop_thread(self):
472472 Handler.pop_thread(self)
473 self.flush()
474
475 def pop_context(self):
476 Handler.pop_context(self)
473477 self.flush()
474478
475479 def pop_greenlet(self):
603603 queue and sends it to a handler. Both queue and handler are
604604 taken from the passed :class:`ThreadedWrapperHandler`.
605605 """
606 _sentinel = object()
606 class Command(object):
607 stop = object()
608 emit = object()
609 emit_batch = object()
607610
608611 def __init__(self, wrapper_handler):
609612 self.wrapper_handler = wrapper_handler
620623 def stop(self):
621624 """Stops the task thread."""
622625 if self.running:
623 self.wrapper_handler.queue.put_nowait(self._sentinel)
626 self.wrapper_handler.queue.put_nowait((self.Command.stop, ))
624627 self._thread.join()
625628 self._thread = None
626629
627630 def _target(self):
628631 while 1:
629 record = self.wrapper_handler.queue.get()
630 if record is self._sentinel:
632 item = self.wrapper_handler.queue.get()
633 command, data = item[0], item[1:]
634 if command is self.Command.stop:
631635 self.running = False
632636 break
633 self.wrapper_handler.handler.handle(record)
637 elif command is self.Command.emit:
638 (record, ) = data
639 self.wrapper_handler.handler.emit(record)
640 elif command is self.Command.emit_batch:
641 record, reason = data
642 self.wrapper_handler.handler.emit_batch(record, reason)
634643
635644
636645 class ThreadedWrapperHandler(WrapperHandler):
662671 self.handler.close()
663672
664673 def emit(self, record):
665 try:
666 self.queue.put_nowait(record)
674 item = (TWHThreadController.Command.emit, record)
675 try:
676 self.queue.put_nowait(item)
677 except Full:
678 # silently drop
679 pass
680
681 def emit_batch(self, records, reason):
682 item = (TWHThreadController.Command.emit_batch, records, reason)
683 try:
684 self.queue.put_nowait(item)
667685 except Full:
668686 # silently drop
669687 pass
00 #! /usr/bin/python
1 import pip
1 from pip._internal import main as pip_main
22 import sys
33
44 if __name__ == '__main__':
1313 ]
1414
1515 print("Setting up dependencies...")
16 result = pip.main(["install"] + deps)
16 result = pip_main(["install"] + deps)
1717 sys.exit(result)
0 import sys
1
02 import logbook
13 import pytest
24
105107 @request.addfinalizer
106108 def fin():
107109 _disable_gevent()
110
111
112 def pytest_ignore_collect(path, config):
113 if 'test_asyncio.py' in path.basename and (sys.version_info.major < 3 or sys.version_info.minor < 5):
114 return True
115
116 return False
0 import pytest
1 import logbook
2 import asyncio
3 from logbook.concurrency import has_contextvars
4
5 ITERATIONS = 100
6
7
8 @pytest.mark.skipif(not has_contextvars, reason="Contexvars not available")
9 def test_asyncio_context_management(logger):
10 h1 = logbook.TestHandler()
11 h2 = logbook.TestHandler()
12
13 async def task(handler, msg):
14 for _ in range(ITERATIONS):
15 with handler.contextbound():
16 logger.info(msg)
17
18 await asyncio.sleep(0) # allow for context switch
19
20 asyncio.get_event_loop().run_until_complete(asyncio.gather(task(h1, 'task1'), task(h2, 'task2')))
21
22 assert len(h1.records) == ITERATIONS
23 assert all(['task1' == r.msg for r in h1.records])
24
25 assert len(h2.records) == ITERATIONS
26 assert all(['task2' == r.msg for r in h2.records])
8888 assert test_handler.has_warning('Hello World')
8989
9090
91 class BatchTestHandler(logbook.TestHandler):
92 def __init__(self, *args, **kwargs):
93 super(BatchTestHandler, self).__init__(*args, **kwargs)
94 self.batches = []
95
96 def emit(self, record):
97 super(BatchTestHandler, self).emit(record)
98 self.batches.append([record])
99
100 def emit_batch(self, records, reason):
101 for record in records:
102 super(BatchTestHandler, self).emit(record)
103 self.batches.append(records)
104
105
91106 def test_threaded_wrapper_handler(logger):
92107 from logbook.queues import ThreadedWrapperHandler
93 test_handler = logbook.TestHandler()
108 test_handler = BatchTestHandler()
94109 with ThreadedWrapperHandler(test_handler) as handler:
95110 logger.warn('Just testing')
96111 logger.error('More testing')
99114 handler.close()
100115
101116 assert (not handler.controller.running)
117 assert len(test_handler.records) == 2
118 assert len(test_handler.batches) == 2
119 assert all((len(records) == 1 for records in test_handler.batches))
120 assert test_handler.has_warning('Just testing')
121 assert test_handler.has_error('More testing')
122
123
124 def test_threaded_wrapper_handler_emit():
125 from logbook.queues import ThreadedWrapperHandler
126 test_handler = BatchTestHandler()
127 with ThreadedWrapperHandler(test_handler) as handler:
128 lr = logbook.LogRecord('Test Logger', logbook.WARNING, 'Just testing')
129 test_handler.emit(lr)
130 lr = logbook.LogRecord('Test Logger', logbook.ERROR, 'More testing')
131 test_handler.emit(lr)
132
133 # give it some time to sync up
134 handler.close()
135
136 assert (not handler.controller.running)
137 assert len(test_handler.records) == 2
138 assert len(test_handler.batches) == 2
139 assert all((len(records) == 1 for records in test_handler.batches))
140 assert test_handler.has_warning('Just testing')
141 assert test_handler.has_error('More testing')
142
143
144 def test_threaded_wrapper_handler_emit_batched():
145 from logbook.queues import ThreadedWrapperHandler
146 test_handler = BatchTestHandler()
147 with ThreadedWrapperHandler(test_handler) as handler:
148 test_handler.emit_batch([
149 logbook.LogRecord('Test Logger', logbook.WARNING, 'Just testing'),
150 logbook.LogRecord('Test Logger', logbook.ERROR, 'More testing'),
151 ], 'group')
152
153 # give it some time to sync up
154 handler.close()
155
156 assert (not handler.controller.running)
157 assert len(test_handler.records) == 2
158 assert len(test_handler.batches) == 1
159 (records, ) = test_handler.batches
160 assert len(records) == 2
102161 assert test_handler.has_warning('Just testing')
103162 assert test_handler.has_error('More testing')
104163
163222 import redis
164223 from logbook.queues import RedisHandler
165224
166 KEY = 'redis'
225 KEY = 'redis-{}'.format(os.getpid())
167226 FIELDS = ['message', 'host']
168227 r = redis.Redis(decode_responses=True)
169 redis_handler = RedisHandler(level=logbook.INFO, bubble=True)
228 redis_handler = RedisHandler(key=KEY, level=logbook.INFO, bubble=True)
170229 # We don't want output for the tests, so we can wrap everything in a
171230 # NullHandler
172231 null_handler = logbook.NullHandler()
184243 assert message.find(LETTERS)
185244
186245 # Change the key of the handler and check on redis
187 KEY = 'test_another_key'
246 KEY = 'test_another_key-{}'.format(os.getpid())
188247 redis_handler.key = KEY
189248
190249 with null_handler.applicationbound():
233292 from logbook.queues import RedisHandler
234293 null_handler = logbook.NullHandler()
235294
236 redis_handler = RedisHandler(key='lpushed', push_method='lpush',
295 KEY = 'lpushed-'.format(os.getpid())
296 redis_handler = RedisHandler(key=KEY, push_method='lpush',
237297 level=logbook.INFO, bubble=True)
238298
239299 with null_handler.applicationbound():
244304 time.sleep(1.5)
245305
246306 r = redis.Redis(decode_responses=True)
247 logs = r.lrange('lpushed', 0, -1)
307 logs = r.lrange(KEY, 0, -1)
248308 assert logs
249309 assert "new item" in logs[0]
250 r.delete('lpushed')
310 r.delete(KEY)
251311
252312
253313 @require_module('redis')
260320 from logbook.queues import RedisHandler
261321 null_handler = logbook.NullHandler()
262322
263 redis_handler = RedisHandler(key='rpushed', push_method='rpush',
323 KEY = 'rpushed-' + str(os.getpid())
324 redis_handler = RedisHandler(key=KEY, push_method='rpush',
264325 level=logbook.INFO, bubble=True)
265326
266327 with null_handler.applicationbound():
271332 time.sleep(1.5)
272333
273334 r = redis.Redis(decode_responses=True)
274 logs = r.lrange('rpushed', 0, -1)
335 logs = r.lrange(KEY, 0, -1)
275336 assert logs
276337 assert "old item" in logs[0]
277 r.delete('rpushed')
338 r.delete(KEY)
278339
279340
280341 @pytest.fixture
33 from contextlib import closing
44
55 import logbook
6 from logbook.helpers import u
7
86 import pytest
97
10 unix_socket = "/tmp/__unixsock_logbook.test"
8 UNIX_SOCKET = "/tmp/__unixsock_logbook.test"
119
12 to_test = [
10 DELIMITERS = {
11 socket.AF_INET: '\n'
12 }
13
14 TO_TEST = [
1315 (socket.AF_INET, socket.SOCK_DGRAM, ('127.0.0.1', 0)),
1416 (socket.AF_INET, socket.SOCK_STREAM, ('127.0.0.1', 0)),
1517 ]
16 if hasattr(socket, 'AF_UNIX'):
17 to_test.append((socket.AF_UNIX, socket.SOCK_DGRAM, unix_socket))
18
19 UNIX_SOCKET_AVAILABLE = hasattr(socket, 'AF_UNIX')
20
21 if UNIX_SOCKET_AVAILABLE:
22 DELIMITERS[socket.AF_UNIX] = '\x00'
23 TO_TEST.append((socket.AF_UNIX, socket.SOCK_DGRAM, UNIX_SOCKET))
24
1825
1926 @pytest.mark.usefixtures("unix_sock_path")
20 @pytest.mark.parametrize("sock_family,socktype,address", to_test)
21 def test_syslog_handler(logger, activation_strategy,
22 sock_family, socktype, address):
23 delimiter = {socket.AF_UNIX: '\x00',
24 socket.AF_INET: '\n'}[sock_family]
27 @pytest.mark.parametrize("sock_family,socktype,address", TO_TEST)
28 @pytest.mark.parametrize("app_name", [None, 'Testing'])
29 def test_syslog_handler(logger, activation_strategy, sock_family, socktype, address, app_name):
30 delimiter = DELIMITERS[sock_family]
2531 with closing(socket.socket(sock_family, socktype)) as inc:
2632 inc.bind(address)
33
2734 if socktype == socket.SOCK_STREAM:
2835 inc.listen(0)
36
2937 inc.settimeout(1)
30 for app_name in [None, 'Testing']:
31 if sock_family == socket.AF_UNIX:
32 expected = (r'^<12>%stestlogger: Syslog is weird%s$' %
33 (app_name + ':' if app_name else '',
34 delimiter))
35 else:
36 expected = (r'^<12>1 \d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z %s %s %d '
37 '- - %sSyslog is weird%s$' %
38 (socket.gethostname(),
39 app_name if app_name else 'testlogger',
40 os.getpid(), 'testlogger: ' if app_name else '',
41 delimiter))
4238
43 handler = logbook.SyslogHandler(app_name, inc.getsockname(),
44 socktype=socktype)
45 with activation_strategy(handler):
46 logger.warn('Syslog is weird')
47 try:
48 if socktype == socket.SOCK_STREAM:
49 with closing(inc.accept()[0]) as inc2:
50 rv = inc2.recv(1024)
51 else:
52 rv = inc.recvfrom(1024)[0]
53 except socket.error:
54 assert False, 'got timeout on socket'
55 rv = rv.decode('utf-8')
56 assert re.match(expected, rv), \
57 'expected {}, got {}'.format(expected, rv)
39 if UNIX_SOCKET_AVAILABLE and sock_family == socket.AF_UNIX:
40 expected = (r'^<12>%stestlogger: Syslog is weird%s$' % (app_name + ':' if app_name else '', delimiter))
41 else:
42 expected = (r'^<12>1 \d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z %s %s %d - - %sSyslog is weird%s$' % (
43 socket.gethostname(),
44 app_name if app_name else 'testlogger',
45 os.getpid(), 'testlogger: ' if app_name else '',
46 delimiter))
47
48 handler = logbook.SyslogHandler(app_name, inc.getsockname(), socktype=socktype)
49
50 with activation_strategy(handler):
51 logger.warn('Syslog is weird')
52
53 if socktype == socket.SOCK_STREAM:
54 with closing(inc.accept()[0]) as inc2:
55 rv = inc2.recv(1024)
56 else:
57 rv = inc.recvfrom(1024)[0]
58
59 rv = rv.decode('utf-8')
60 assert re.match(expected, rv), \
61 'expected {}, got {}'.format(expected, rv)
5862
5963
6064 @pytest.fixture
61 def unix_sock_path(request):
62 returned = unix_socket
63
64 @request.addfinalizer
65 def cleanup():
66 if os.path.exists(returned):
67 os.unlink(returned)
68 return returned
65 def unix_sock_path():
66 try:
67 yield UNIX_SOCKET
68 finally:
69 if os.path.exists(UNIX_SOCKET):
70 os.unlink(UNIX_SOCKET)
00 [tox]
1 envlist=py27,py34,py35,py36,pypy,docs
2 skipsdist=True
1 envlist = py{27,35,36,37}{,-speedups},pypy,py37-docs
2 skipsdist = True
33
44 [testenv]
5 whitelist_externals=
6 rm
7 deps=
8 py{27}: mock
9 pytest
10 Cython
11 changedir={toxinidir}
12 commands=
13 {envbindir}/cython logbook/_speedups.pyx
14 {envpython} {toxinidir}/setup.py develop
15 {envpython} {toxinidir}/scripts/test_setup.py
16 py.test {toxinidir}/tests
17 rm -f {toxinidir}/logbook/_speedups.\{so,c\}
5 whitelist_externals =
6 rm
7 deps =
8 py{27}: mock
9 pytest
10 speedups: Cython
11 setenv =
12 !speedups: DISABLE_LOGBOOK_CEXT=1
13 !speedups: DISABLE_LOGBOOK_CEXT_AT_RUNTIME=1
14 changedir = {toxinidir}
15 commands =
16 {envpython} -m pip install -e {toxinidir}[all]
1817
19 [testenv:pypy]
20 deps=
21 mock
22 pytest
23 commands=
24 {envpython} {toxinidir}/setup.py develop
25 {envpython} {toxinidir}/scripts/test_setup.py
26 py.test {toxinidir}/tests
18 # Make sure that speedups are available/not available, as needed.
19 speedups: {envpython} -c "from logbook.base import _has_speedups; exit(0 if _has_speedups else 1)"
20 !speedups: {envpython} -c "from logbook.base import _has_speedups; exit(1 if _has_speedups else 0)"
2721
28 [testenv:docs]
29 deps=
30 Sphinx==1.1.3
31 changedir=docs
32 commands=
33 sphinx-build -W -b html . _build/html
34 sphinx-build -W -b linkcheck . _build/linkcheck
22 {envpython} {toxinidir}/scripts/test_setup.py
23 py.test {toxinidir}/tests
24
25 [testenv:py37-docs]
26 deps =
27 Sphinx>=1.3
28 changedir = docs
29 commands =
30 sphinx-build -W -b html . _build/html
31 sphinx-build -W -b linkcheck . _build/linkcheck