Skip to content

Commit cfe731d

Browse files
committed
Logging overhaul
* Add more logging to the pool when acquiring and releasing connections. * Improved log format: * All debug messages should not start with `[#hhhh] x:` where `hhhh` is the hex representation of the local port number of the connection or 0000 if there is none. `x` is one of `C` if it's the driver writing/opening/closing onto the socket, `S` if it's the driver reading/receiving an error from the socket and `_` if it's not communication between the server and the driver. * Reduced logging noise in routing table. * API docs: explain how to manually log the async task id. * Logging helpers `neo4j.debug`: * Add config options to decide if thread id and/or task id should be logged. * Their docs make clear that the logging format is not following semver. * Changed logging format to accommodate the new config options.
1 parent 49ed471 commit cfe731d

20 files changed

+322
-108
lines changed

docs/source/api.rst

+10-3
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,8 @@ following code:
14621462
...
14631463
14641464
1465+
.. _logging-ref:
1466+
14651467
*******
14661468
Logging
14671469
*******
@@ -1472,6 +1474,11 @@ not able to connect to the database server or if undesired behavior is observed.
14721474

14731475
There are different ways of enabling logging as listed below.
14741476

1477+
.. note::
1478+
1479+
For an improved logging experience with the async driver, please see
1480+
:ref:`async-logging-ref`.
1481+
14751482
Simple Approach
14761483
===============
14771484

@@ -1484,8 +1491,8 @@ Context Manager
14841491
:members:
14851492
:special-members: __enter__, __exit__
14861493

1487-
Full Controll
1488-
=============
1494+
Full Control
1495+
============
14891496

14901497
.. code-block:: python
14911498
@@ -1502,7 +1509,7 @@ Full Controll
15021509
logging.getLogger("neo4j").addHandler(handler)
15031510
# make sure the logger logs on the desired log level
15041511
logging.getLogger("neo4j").setLevel(logging.DEBUG)
1505-
# from now on, DEBUG logging to stderr is enabled in the driver
1512+
# from now on, DEBUG logging to stdout is enabled in the driver
15061513
15071514
15081515
*********

docs/source/async_api.rst

+49
Original file line numberDiff line numberDiff line change
@@ -648,3 +648,52 @@ successfully executed on the server side or not, when a cancellation happens:
648648
``await transaction.commit()`` and other methods can throw
649649
:exc:`asyncio.CancelledError` but still have managed to complete from the
650650
server's perspective.
651+
652+
653+
.. _async-logging-ref:
654+
655+
*************
656+
Async Logging
657+
*************
658+
659+
For the most parts, logging works the same way as in the synchronous driver.
660+
See :ref:`logging-ref` for more information.
661+
662+
However, when following the manual approach to logging, it is recommended to
663+
include information about the current async task in the log record.
664+
Like so:
665+
666+
.. code-block:: python
667+
668+
import asyncio
669+
import logging
670+
import sys
671+
672+
class TaskIdFilter(logging.Filter):
673+
"""Injecting async task id into log records."""
674+
675+
def filter(self, record):
676+
try:
677+
record.taskId = id(asyncio.current_task())
678+
except RuntimeError:
679+
record.taskId = None
680+
return True
681+
682+
683+
# attache the filter injecting the task id to the driver's logger
684+
logging.getLogger("neo4j").addFilter(TaskIdFilter())
685+
686+
# create a handler, e.g. to log to stdout
687+
handler = logging.StreamHandler(sys.stdout)
688+
# configure the handler to your liking
689+
handler.setFormatter(logging.Formatter(
690+
"[%(levelname)-8s] [Task %(taskId)-15s] %(asctime)s %(message)s"
691+
# or when using threading AND asyncio
692+
# "[%(levelname)-8s] [Thread %(thread)d] [Task %(taskId)-15s] "
693+
# "%(asctime)s %(message)s"
694+
))
695+
# add the handler to the driver's logger
696+
logging.getLogger("neo4j").addHandler(handler)
697+
# make sure the logger logs on the desired log level
698+
logging.getLogger("neo4j").setLevel(logging.DEBUG)
699+
# from now on, DEBUG logging to stdout is enabled in the driver

neo4j/_async/io/_bolt.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ def __del__(self):
160160
if not asyncio.iscoroutinefunction(self.close):
161161
self.close()
162162

163+
@property
164+
def connection_id(self):
165+
return self.server_info._metadata.get("connection_id", "<unknown id>")
166+
163167
@property
164168
@abc.abstractmethod
165169
def supports_multiple_results(self):
@@ -689,7 +693,8 @@ async def _set_defunct(self, message, error=None, silent=False):
689693
user_cancelled = isinstance(error, asyncio.CancelledError)
690694

691695
if error:
692-
log.debug("[#%04X] %r", self.local_port, error)
696+
log.debug("[#%04X] _: <CONNECTION> error: %r", self.local_port,
697+
error)
693698
if not user_cancelled:
694699
log.error(message)
695700
# We were attempting to receive data but the connection
@@ -751,7 +756,7 @@ async def close(self):
751756
try:
752757
await self._send_all()
753758
except (OSError, BoltError, DriverError) as exc:
754-
log.debug("[#%04X] ignoring failed close %r",
759+
log.debug("[#%04X] _: <CONNECTION> ignoring failed close %r",
755760
self.local_port, exc)
756761
log.debug("[#%04X] C: <CLOSE>", self.local_port)
757762
try:
@@ -770,7 +775,7 @@ def kill(self):
770775
try:
771776
self.socket.kill()
772777
except OSError as exc:
773-
log.debug("[#%04X] ignoring failed kill %r",
778+
log.debug("[#%04X] _: <CONNECTION> ignoring failed kill %r",
774779
self.local_port, exc)
775780
finally:
776781
self._closed = True

neo4j/_async/io/_bolt3.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def __init__(self, *args, **kwargs):
111111
)
112112

113113
def _on_server_state_change(self, old_state, new_state):
114-
log.debug("[#%04X] State: %s > %s", self.local_port,
114+
log.debug("[#%04X] _: <CONNECTION> state: %s > %s", self.local_port,
115115
old_state.name, new_state.name)
116116

117117
@property

neo4j/_async/io/_bolt4.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def __init__(self, *args, **kwargs):
6969
)
7070

7171
def _on_server_state_change(self, old_state, new_state):
72-
log.debug("[#%04X] State: %s > %s", self.local_port,
72+
log.debug("[#%04X] _: <CONNECTION> state: %s > %s", self.local_port,
7373
old_state.name, new_state.name)
7474

7575
@property
@@ -426,7 +426,8 @@ def on_success(metadata):
426426
if isinstance(recv_timeout, int) and recv_timeout > 0:
427427
self.socket.settimeout(recv_timeout)
428428
else:
429-
log.info("[#%04X] Server supplied an invalid value for "
429+
log.info("[#%04X] _: <CONNECTION> Server supplied an "
430+
"invalid value for "
430431
"connection.recv_timeout_seconds (%r). Make sure "
431432
"the server and network is set up correctly.",
432433
self.local_port, recv_timeout)

neo4j/_async/io/_bolt5.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def __init__(self, *args, **kwargs):
6666
)
6767

6868
def _on_server_state_change(self, old_state, new_state):
69-
log.debug("[#%04X] State: %s > %s", self.local_port,
69+
log.debug("[#%04X] _: <CONNECTION> state: %s > %s", self.local_port,
7070
old_state.name, new_state.name)
7171

7272
@property
@@ -104,7 +104,8 @@ def on_success(metadata):
104104
if isinstance(recv_timeout, int) and recv_timeout > 0:
105105
self.socket.settimeout(recv_timeout)
106106
else:
107-
log.info("[#%04X] Server supplied an invalid value for "
107+
log.info("[#%04X] _: <CONNECTION> Server supplied an "
108+
"invalid value for "
108109
"connection.recv_timeout_seconds (%r). Make sure "
109110
"the server and network is set up correctly.",
110111
self.local_port, recv_timeout)

0 commit comments

Comments
 (0)