Skip to content

Commit dbe3f2d

Browse files
committed
Improve logging helper neo4j.debug
- no ANSI color codes by default - log thread name and id - add documentation for logging
1 parent 3d7826f commit dbe3f2d

File tree

3 files changed

+148
-26
lines changed

3 files changed

+148
-26
lines changed

CHANGELOG.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
- Python 3.6 support has been dropped.
77
- `Result`, `Session`, and `Transaction` can no longer be imported from
88
`neo4j.work`. They should've been imported from `neo4j` all along.
9-
Remark: It's recommended to import everything needed directly from `noe4j`,
10-
not its submodules or subpackages.
9+
Remark: It's recommended to import everything needed directly from `noe4j` if
10+
available, not its submodules or subpackages.
1111
- Experimental pipelines feature has been removed.
1212
- Experimental async driver has been added.
1313
- `ResultSummary.server.version_info` has been removed.
@@ -51,9 +51,9 @@
5151
Use `hour_minute_second_nanosecond` instead.
5252
- The property `second` returns an `int` instead of a `float`.
5353
Use `nanosecond` to get the sub-second information.
54-
- Creation of a driver with `bolt[+s[sc]]://` scheme has been deprecated and
55-
will raise an error in the Future. The routing context was and will be
56-
silently ignored until then.
54+
- Creation of a driver with `bolt[+s[sc]]://` scheme and a routing context has
55+
been deprecated and will raise an error in the Future. The routing context was
56+
and will be silently ignored until then.
5757
- Bookmarks
5858
- `Session.last_bookmark` was deprecated. Its behaviour is partially incorrect
5959
and cannot be fixed without breaking its signature.
@@ -94,6 +94,10 @@
9494
- `ServerInfo.connection_id` has been deprecated and will be removed in a
9595
future release. There is no replacement as this is considered internal
9696
information.
97+
- Output of logging helper `neo4j.debug.watch` changes
98+
- ANSI colour codes for log output are now opt-in
99+
- Prepend log format with log-level (if colours are disabled)
100+
- Prepend log format with thread name and id
97101

98102

99103
## Version 4.4

docs/source/api.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,50 @@ following code:
13501350
...
13511351
13521352
1353+
*******
1354+
Logging
1355+
*******
1356+
1357+
The driver offers logging for debugging purposes. It is not recommended to
1358+
enable logging for anything other than debugging. For instance, if the driver is
1359+
not able to connect to the database server or if undesired behavior is observed.
1360+
1361+
There are different ways of enabling logging as listed below.
1362+
1363+
Simple Approach
1364+
===============
1365+
1366+
.. autofunction:: neo4j.debug.watch(*logger_names, level=logging.DEBUG, out=sys.stderr, colour=False)
1367+
1368+
Context Manager
1369+
===============
1370+
1371+
.. autoclass:: neo4j.debug.Watcher(*logger_names, default_level=logging.DEBUG, default_out=sys.stderr, colour=False)
1372+
:members:
1373+
:special-members: __enter__, __exit__
1374+
1375+
Full Controll
1376+
=============
1377+
1378+
.. code-block:: python
1379+
1380+
import logging
1381+
import sys
1382+
1383+
# create a handler, e.g. to log to stdout
1384+
handler = logging.StreamHandler(sys.stdout)
1385+
# configure the handler to your liking
1386+
handler.setFormatter(logging.Formatter(
1387+
"%(threadName)s(%(thread)d) %(asctime)s %(message)s"
1388+
))
1389+
# add the handler to the driver's logger
1390+
logging.getLogger("neo4j").addHandler(handler)
1391+
# make sure the logger logs on the desired log level
1392+
logging.getLogger("neo4j").setLevel(logging.DEBUG)
1393+
1394+
# from now on, DEBUG logging to stderr is enabled in the driver
1395+
1396+
13531397
*********
13541398
Bookmarks
13551399
*********

neo4j/debug.py

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
from sys import stderr
3030

3131

32+
__all__ = [
33+
"Watcher",
34+
"watch"
35+
]
36+
37+
3238
class ColourFormatter(Formatter):
3339
""" Colour formatter for pretty log output.
3440
"""
@@ -50,49 +56,117 @@ def format(self, record):
5056

5157

5258
class Watcher:
53-
""" Log watcher for monitoring driver and protocol activity.
59+
"""Log watcher for easier logging setup.
60+
61+
Example::
62+
63+
from neo4j.debug import Watcher
64+
65+
with Watcher("neo4j"):
66+
# DEBUG logging to stderr enabled within this context
67+
... # do something
68+
69+
.. note:: The Watcher class is not thread-safe. Having Watchers in multiple
70+
threads can lead to duplicate log messages as the context manager will
71+
enable logging for all threads.
72+
73+
:param logger_names: Names of loggers to watch.
74+
:type logger_names: str
75+
:param default_level: Default minimum log level to show.
76+
The level can be overridden by setting the level a level when calling
77+
:meth:`.watch`.
78+
:type default_level: int
79+
:param default_out: Default output stream for all loggers.
80+
The level can be overridden by setting the level a level when calling
81+
:meth:`.watch`.
82+
:type default_out: stream or file-like object
83+
:param colour: Whether the log levels should be indicated with ANSI colour
84+
codes.
85+
:type colour: bool
5486
"""
5587

56-
handlers = {}
57-
58-
def __init__(self, *logger_names):
88+
def __init__(self, *logger_names, default_level=DEBUG, default_out=stderr,
89+
colour=False):
5990
super(Watcher, self).__init__()
6091
self.logger_names = logger_names
61-
self.loggers = [getLogger(name) for name in self.logger_names]
62-
self.formatter = ColourFormatter("%(asctime)s %(message)s")
92+
self._loggers = [getLogger(name) for name in self.logger_names]
93+
self.default_level = default_level
94+
self.default_out = default_out
95+
self._handlers = {}
96+
97+
format = "%(threadName)s(%(thread)d) %(asctime)s %(message)s"
98+
if not colour:
99+
format = "[%(levelname)s] " + format
100+
101+
formatter_cls = ColourFormatter if colour else Formatter
102+
self.formatter = formatter_cls(format)
63103

64104
def __enter__(self):
105+
"""Enable logging for all loggers."""
65106
self.watch()
66107
return self
67108

68109
def __exit__(self, exc_type, exc_val, exc_tb):
110+
"""Disable logging for all loggers."""
69111
self.stop()
70112

71-
def watch(self, level=DEBUG, out=stderr):
113+
def watch(self, level=None, out=None):
114+
"""Enable logging for all loggers.
115+
116+
:param level: Minimum log level to show.
117+
If :const:`None`, the ``default_level`` is used.
118+
:type level: int or :const:`None`
119+
:param out: Output stream for all loggers.
120+
If :const:`None`, the ``default_out`` is used.
121+
:type out: stream or file-like object or :const:`None`
122+
"""
123+
if level is None:
124+
level = self.default_level
125+
if out is None:
126+
out = self.default_out
72127
self.stop()
73128
handler = StreamHandler(out)
74129
handler.setFormatter(self.formatter)
75-
for logger in self. loggers:
76-
self.handlers[logger.name] = handler
130+
for logger in self. _loggers:
131+
self._handlers[logger.name] = handler
77132
logger.addHandler(handler)
78133
logger.setLevel(level)
79134

80135
def stop(self):
81-
try:
82-
for logger in self.loggers:
83-
logger.removeHandler(self.handlers[logger.name])
84-
except KeyError:
85-
pass
136+
"""Disable logging for all loggers."""
137+
for logger in self._loggers:
138+
try:
139+
logger.removeHandler(self._handlers.pop(logger.name))
140+
except KeyError:
141+
pass
142+
143+
144+
def watch(*logger_names, level=DEBUG, out=stderr, colour=False):
145+
"""Quick wrapper for using :class:`.Watcher`.
146+
147+
Create a Wathcer with the given configuration, enable watching and return
148+
it.
149+
150+
Example::
151+
152+
from neo4j.debug import watch
86153
154+
watch("neo4j")
155+
# from now on, DEBUG logging to stderr is enabled in the driver
87156
88-
def watch(*logger_names, level=DEBUG, out=stderr):
89-
""" Quick wrapper for using the Watcher.
157+
:param logger_names: Names of loggers to watch.
158+
:type logger_names: str
159+
:param level: see ``default_level`` of :class:`.Watcher`.
160+
:type level: int
161+
:param out: see ``default_out`` of :class:`.Watcher`.
162+
:type out: stream or file-like object
163+
:param colour: see ``colour`` of :class:`.Watcher`.
164+
:type colour: bool
90165
91-
:param logger_name: name of logger to watch
92-
:param level: minimum log level to show (default DEBUG)
93-
:param out: where to send output (default stderr)
94166
:return: Watcher instance
167+
:rtype: :class:`.Watcher`
95168
"""
96-
watcher = Watcher(*logger_names)
97-
watcher.watch(level, out)
169+
watcher = Watcher(*logger_names, colour=colour, default_level=level,
170+
default_out=out)
171+
watcher.watch()
98172
return watcher

0 commit comments

Comments
 (0)