vortexpy 4.1.5__tar.gz → 4.1.7__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.
- {vortexpy-4.1.5 → vortexpy-4.1.7}/PKG-INFO +1 -1
- {vortexpy-4.1.5 → vortexpy-4.1.7}/pyproject.toml +1 -1
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/PayloadEndpoint.py +4 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/PayloadIO.py +9 -2
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/PayloadResponse.py +7 -3
- vortexpy-4.1.7/vortex/PayloadResponse_test.py +42 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/__init__.py +1 -1
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleActionProcessorProxy.py +1 -1
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleDataObservableProxyHandler.py +1 -1
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleDataObserverClient.py +2 -2
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/rpc/RPC.py +50 -4
- vortexpy-4.1.7/vortex/rpc/RPC_test.py +114 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortexpy.egg-info/PKG-INFO +1 -1
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortexpy.egg-info/SOURCES.txt +2 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/README.md +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/setup.cfg +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/DataWrapTuple.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/DeferUtil.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/Jsonable.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/Payload.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/PayloadEndpointTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/PayloadEnvelope.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/PayloadFilterKeys.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/PayloadPriority.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/PayloadResponseTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/PayloadTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/PerformTestActionTuple.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/SerialiseUtil.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/SerialiseUtil_test.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/TestTuple.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/Tuple.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/TupleAction.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/TupleDataForTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/TuplePerfTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/TupleRestful.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/TupleRestfulTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/TupleSelector.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/TupleTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexABC.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexAccessControlContextABC.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexAuthenticatorABC.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexClientHttp.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexClientHttpTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexClientTcp.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexClientWebsocketFactory.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexConnectionABC.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexEndpointPermission.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexFactory.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexPayloadProtocol.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexPermission.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexPermissionError.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexServer.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexServerConnection.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexServerHttpResource.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexServerTcp.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexServerWebsocket.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexSession.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexTcpMemoryLeakPayloadTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexUtil.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/VortexWritePushProducer.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/data_loader/TupleDataLiveLockManager.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/data_loader/TupleDataLoader.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/data_loader/TupleDataLoaderDelegate.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/data_loader/TupleDataLoaderTuples.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/data_loader/__init__.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/event_bus/TupleChangeEventABC.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/event_bus/TupleChangeEventBus.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/event_bus/__init__.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleActionProcessor.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleActionProcessorTestHandler.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleDataAccessGranterABC.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleDataActionClient.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleDataObservableCache.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleDataObservableHandler.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/TupleDataProviderABC.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/handler/__init__.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/mypy_plugin.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/py.typed +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/restful/GzippedDataHttpClient.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/restful/RestfulHttpClient.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/restful/RestfulHttpClientTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/restful/RestfulResource.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/restful/RestfulResourceTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/restful/__init__.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/rpc/RPCTest.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/rpc/__init__.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/sqla_orm/OrmCrudHandler.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/sqla_orm/VortexTupleJson.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/sqla_orm/__init__.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/storage/TupleStorageSqlite.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/storage/__init__.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/tuple_json_schema/__init__.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/tuple_json_schema/constants.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/tuple_json_schema/json_schema_node_tuple.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/tuple_json_schema/tuple_json_schema_builder.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/tuple_json_schema/tuple_json_schema_test.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortexpy.egg-info/dependency_links.txt +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortexpy.egg-info/entry_points.txt +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortexpy.egg-info/requires.txt +0 -0
- {vortexpy-4.1.5 → vortexpy-4.1.7}/vortexpy.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vortexpy"
|
|
7
|
-
version = "4.1.
|
|
7
|
+
version = "4.1.7"
|
|
8
8
|
license = {text = "MIT"}
|
|
9
9
|
authors = [{name = "Flying Ion Pty Ltd", email = "contact@flyingion.com"}]
|
|
10
10
|
description = "Flying Ion's observable, routable, data serialisation and transport code."
|
|
@@ -71,6 +71,7 @@ class PayloadEndpoint:
|
|
|
71
71
|
acceptOnlyFromVortex: Optional[Union[str, tuple]] = None,
|
|
72
72
|
ignoreFromVortex: Optional[Union[str, tuple]] = None,
|
|
73
73
|
requiredPermissions: Optional[PermissionsType] = None,
|
|
74
|
+
logProcessException: bool = True,
|
|
74
75
|
) -> None:
|
|
75
76
|
"""
|
|
76
77
|
:param filt: The filter to match against payloads
|
|
@@ -82,10 +83,13 @@ class PayloadEndpoint:
|
|
|
82
83
|
the caller's session must hold. Pass an empty frozenset to
|
|
83
84
|
explicitly declare "no permissions required". Omitting the
|
|
84
85
|
parameter (``None``) emits a deprecation warning.
|
|
86
|
+
:param logProcessException: When True, handler exceptions log at ERROR.
|
|
87
|
+
When False, the same events log at DEBUG with neutral wording.
|
|
85
88
|
"""
|
|
86
89
|
self._requiredPermissions: Optional[PermissionsType] = (
|
|
87
90
|
requiredPermissions
|
|
88
91
|
)
|
|
92
|
+
self._logProcessException = logProcessException
|
|
89
93
|
|
|
90
94
|
if requiredPermissions is None:
|
|
91
95
|
logger.warning(self._NO_PERMISSIONS_WARNING, filt)
|
|
@@ -115,8 +115,15 @@ class PayloadIO(object):
|
|
|
115
115
|
except Exception as e:
|
|
116
116
|
logger.exception(e)
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
if endpoint._logProcessException:
|
|
119
|
+
vortexLogFailure(failure, logger)
|
|
120
|
+
logger.error(payloadEnvelope.filt)
|
|
121
|
+
else:
|
|
122
|
+
logger.debug(
|
|
123
|
+
"Payload handler response for filt=%s: %s",
|
|
124
|
+
payloadEnvelope.filt,
|
|
125
|
+
failure.getErrorMessage(),
|
|
126
|
+
)
|
|
120
127
|
|
|
121
128
|
def callback(value, blocking=False):
|
|
122
129
|
# A blocking call taking more than 100ms is BAD
|
|
@@ -67,7 +67,7 @@ class PayloadResponse(Deferred):
|
|
|
67
67
|
destVortexUuid: Optional[str] = None,
|
|
68
68
|
timeout: Optional[float] = None,
|
|
69
69
|
resultCheck=True,
|
|
70
|
-
|
|
70
|
+
logTimeoutException=True,
|
|
71
71
|
) -> None:
|
|
72
72
|
"""Constructor
|
|
73
73
|
|
|
@@ -81,6 +81,8 @@ class PayloadResponse(Deferred):
|
|
|
81
81
|
:param timeout The timeout to wait for a response
|
|
82
82
|
:param resultCheck Should the response payload.result be checked, if it fails
|
|
83
83
|
it errback will be called.
|
|
84
|
+
:param logTimeoutException: When True, payload timeouts log at ERROR.
|
|
85
|
+
When False, timeouts log at DEBUG with neutral wording.
|
|
84
86
|
"""
|
|
85
87
|
Deferred.__init__(self)
|
|
86
88
|
|
|
@@ -88,7 +90,7 @@ class PayloadResponse(Deferred):
|
|
|
88
90
|
timeout = self.TIMEOUT
|
|
89
91
|
|
|
90
92
|
self._resultCheck = resultCheck
|
|
91
|
-
self.
|
|
93
|
+
self._logTimeoutException = logTimeoutException
|
|
92
94
|
|
|
93
95
|
# uuid4 can have collisions
|
|
94
96
|
self._messageId = str(uuid4()) + str(PayloadResponse.__SEQ)
|
|
@@ -142,8 +144,10 @@ class PayloadResponse(Deferred):
|
|
|
142
144
|
self._endpoint.shutdown()
|
|
143
145
|
self._endpoint = None
|
|
144
146
|
|
|
145
|
-
if self.
|
|
147
|
+
if self._logTimeoutException:
|
|
146
148
|
logger.error("Timed out for payload %s", self._filt)
|
|
149
|
+
else:
|
|
150
|
+
logger.debug("Payload response timed out for %s", self._filt)
|
|
147
151
|
self._status = self.TIMED_OUT
|
|
148
152
|
return failure
|
|
149
153
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
from twisted.internet.defer import TimeoutError
|
|
5
|
+
from twisted.python.failure import Failure
|
|
6
|
+
|
|
7
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
8
|
+
from vortex.PayloadResponse import PayloadResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PayloadResponseLoggingTest(unittest.TestCase):
|
|
12
|
+
@patch("vortex.PayloadResponse.VortexFactory.sendVortexMsg")
|
|
13
|
+
@patch("vortex.PayloadResponse.logger")
|
|
14
|
+
def testTimeoutLogsDebugWhenLogTimeoutExceptionFalse(
|
|
15
|
+
self, mockLogger: MagicMock, _mockSend: MagicMock
|
|
16
|
+
) -> None:
|
|
17
|
+
pr = PayloadResponse(
|
|
18
|
+
PayloadEnvelope(filt={"key": "example"}),
|
|
19
|
+
timeout=30.0,
|
|
20
|
+
logTimeoutException=False,
|
|
21
|
+
)
|
|
22
|
+
pr._timedOut(Failure(TimeoutError("timed out")))
|
|
23
|
+
mockLogger.error.assert_not_called()
|
|
24
|
+
mockLogger.debug.assert_called_once()
|
|
25
|
+
|
|
26
|
+
@patch("vortex.PayloadResponse.VortexFactory.sendVortexMsg")
|
|
27
|
+
@patch("vortex.PayloadResponse.logger")
|
|
28
|
+
def testTimeoutLogsErrorWhenLogTimeoutExceptionTrue(
|
|
29
|
+
self, mockLogger: MagicMock, _mockSend: MagicMock
|
|
30
|
+
) -> None:
|
|
31
|
+
pr = PayloadResponse(
|
|
32
|
+
PayloadEnvelope(filt={"key": "example"}),
|
|
33
|
+
timeout=30.0,
|
|
34
|
+
logTimeoutException=True,
|
|
35
|
+
)
|
|
36
|
+
pr._timedOut(Failure(TimeoutError("timed out")))
|
|
37
|
+
mockLogger.debug.assert_not_called()
|
|
38
|
+
mockLogger.error.assert_called_once()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
unittest.main()
|
|
@@ -167,7 +167,7 @@ class TupleActionProcessorProxy:
|
|
|
167
167
|
payloadEnvelope,
|
|
168
168
|
timeout=PayloadResponse.TIMEOUT - 5, # 5 seconds less
|
|
169
169
|
resultCheck=False,
|
|
170
|
-
|
|
170
|
+
logTimeoutException=False,
|
|
171
171
|
)
|
|
172
172
|
|
|
173
173
|
# This is not a lambda, so that it can have a breakpoint
|
|
@@ -355,7 +355,7 @@ class TupleDataObservableProxyHandler(TupleDataObservableCache):
|
|
|
355
355
|
pr = PayloadResponse(
|
|
356
356
|
payloadEnvelope,
|
|
357
357
|
timeout=PayloadResponse.TIMEOUT - 5, # 5 seconds less
|
|
358
|
-
|
|
358
|
+
logTimeoutException=False,
|
|
359
359
|
)
|
|
360
360
|
|
|
361
361
|
pr.addErrback(self._handlePrFailure, tupleSelector)
|
|
@@ -83,7 +83,7 @@ class TupleDataObserverClient(TupleDataObservableCache):
|
|
|
83
83
|
TupleDataObservableCache.shutdown(self)
|
|
84
84
|
|
|
85
85
|
def pollForTuples(
|
|
86
|
-
self, tupleSelector: TupleSelector,
|
|
86
|
+
self, tupleSelector: TupleSelector, logTimeoutException: bool = True
|
|
87
87
|
) -> Deferred:
|
|
88
88
|
startFilt = copy(self._sendFilt)
|
|
89
89
|
startFilt.update({"subscribe": False, "tupleSelector": tupleSelector})
|
|
@@ -97,7 +97,7 @@ class TupleDataObserverClient(TupleDataObservableCache):
|
|
|
97
97
|
pr = PayloadResponse(
|
|
98
98
|
payloadEnvelope=PayloadEnvelope(startFilt),
|
|
99
99
|
destVortexName=self._destVortexName,
|
|
100
|
-
|
|
100
|
+
logTimeoutException=logTimeoutException,
|
|
101
101
|
)
|
|
102
102
|
pr.addCallback(updateCacheCallback)
|
|
103
103
|
pr.addCallback(
|
|
@@ -21,7 +21,7 @@ from twisted.internet.defer import (
|
|
|
21
21
|
from twisted.internet.threads import deferToThread
|
|
22
22
|
from twisted.python.failure import Failure
|
|
23
23
|
|
|
24
|
-
from vortex.DeferUtil import yesMainThread
|
|
24
|
+
from vortex.DeferUtil import yesMainThread, vortexLogFailure
|
|
25
25
|
from vortex.Payload import Payload
|
|
26
26
|
from vortex.PayloadEndpoint import PayloadEndpoint
|
|
27
27
|
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
@@ -103,6 +103,8 @@ class _VortexRPC:
|
|
|
103
103
|
deferToThread: bool,
|
|
104
104
|
inlineCallbacks: bool,
|
|
105
105
|
requiredPermissions: Optional[PermissionsType] = None,
|
|
106
|
+
logTimeoutException: bool = True,
|
|
107
|
+
logRpcException: bool = True,
|
|
106
108
|
) -> None:
|
|
107
109
|
"""
|
|
108
110
|
|
|
@@ -124,6 +126,15 @@ class _VortexRPC:
|
|
|
124
126
|
:param inlineCallbacks: Should the function be wrapped in the twisted
|
|
125
127
|
@inlinecallbacks decorator before it's called?.
|
|
126
128
|
|
|
129
|
+
:param logTimeoutException: When True, RPC timeouts log at ERROR in
|
|
130
|
+
PayloadResponse. When False, timeouts log at DEBUG with neutral
|
|
131
|
+
wording. Callers that expect timeouts (e.g. backend readiness
|
|
132
|
+
probes) should leave this False.
|
|
133
|
+
|
|
134
|
+
:param logRpcException: When True, RPC handler and caller paths log
|
|
135
|
+
exceptions at ERROR. When False, the same events log at DEBUG
|
|
136
|
+
with neutral wording so log analysers are not triggered.
|
|
137
|
+
|
|
127
138
|
"""
|
|
128
139
|
|
|
129
140
|
self.__func = func
|
|
@@ -138,6 +149,8 @@ class _VortexRPC:
|
|
|
138
149
|
self.__requiredPermissions: Optional[PermissionsType] = (
|
|
139
150
|
requiredPermissions
|
|
140
151
|
)
|
|
152
|
+
self.__logTimeoutException = logTimeoutException
|
|
153
|
+
self.__logRpcException = logRpcException
|
|
141
154
|
|
|
142
155
|
self.__funcAcceptsAccessControlContext = (
|
|
143
156
|
checkAccessControlContextSignature(func)
|
|
@@ -176,6 +189,7 @@ class _VortexRPC:
|
|
|
176
189
|
self._filt,
|
|
177
190
|
self._processCall,
|
|
178
191
|
requiredPermissions=self.__requiredPermissions,
|
|
192
|
+
logProcessException=self.__logRpcException,
|
|
179
193
|
)
|
|
180
194
|
logger.debug("RPC %s listening", self.__funcName)
|
|
181
195
|
|
|
@@ -315,7 +329,9 @@ class _VortexRPC:
|
|
|
315
329
|
payloadEnvelope,
|
|
316
330
|
timeout=self.__timeoutSeconds,
|
|
317
331
|
resultCheck=False,
|
|
318
|
-
|
|
332
|
+
logTimeoutException=(
|
|
333
|
+
self.__logRpcException and self.__logTimeoutException
|
|
334
|
+
),
|
|
319
335
|
destVortexName=vortexName,
|
|
320
336
|
destVortexUuid=vortexUuid,
|
|
321
337
|
)
|
|
@@ -340,6 +356,18 @@ class _VortexRPC:
|
|
|
340
356
|
"""
|
|
341
357
|
|
|
342
358
|
if not payloadEnvelope.result in (None, True):
|
|
359
|
+
if self.__logRpcException:
|
|
360
|
+
logger.error(
|
|
361
|
+
"Received RPC failure for %s: %s",
|
|
362
|
+
self.__funcName,
|
|
363
|
+
payloadEnvelope.result,
|
|
364
|
+
)
|
|
365
|
+
else:
|
|
366
|
+
logger.debug(
|
|
367
|
+
"RPC response for %s: %s",
|
|
368
|
+
self.__funcName,
|
|
369
|
+
payloadEnvelope.result,
|
|
370
|
+
)
|
|
343
371
|
return Failure(
|
|
344
372
|
Exception(payloadEnvelope.result).with_traceback(stack),
|
|
345
373
|
exc_tb=stack,
|
|
@@ -366,8 +394,6 @@ class _VortexRPC:
|
|
|
366
394
|
"""
|
|
367
395
|
|
|
368
396
|
if failure.check(TimeoutError):
|
|
369
|
-
logger.error("Received RPC timeout for %s", self.__funcName)
|
|
370
|
-
|
|
371
397
|
return Failure(
|
|
372
398
|
Exception(
|
|
373
399
|
"RPC call timed out for %s", self.__funcName
|
|
@@ -375,6 +401,15 @@ class _VortexRPC:
|
|
|
375
401
|
exc_tb=stack,
|
|
376
402
|
)
|
|
377
403
|
|
|
404
|
+
if not failure.check(TimeoutError) and self.__logRpcException:
|
|
405
|
+
vortexLogFailure(failure, logger)
|
|
406
|
+
elif not failure.check(TimeoutError):
|
|
407
|
+
logger.debug(
|
|
408
|
+
"RPC response for %s: %s",
|
|
409
|
+
self.__funcName,
|
|
410
|
+
failure.getErrorMessage(),
|
|
411
|
+
)
|
|
412
|
+
|
|
378
413
|
return failure
|
|
379
414
|
|
|
380
415
|
def callLocally(
|
|
@@ -423,6 +458,8 @@ def vortexRPC(
|
|
|
423
458
|
deferToThread: bool = False,
|
|
424
459
|
inlineCallbacks: bool = False,
|
|
425
460
|
requiredPermissions: Optional[PermissionsType] = None,
|
|
461
|
+
logTimeoutException: bool = False,
|
|
462
|
+
logRpcException: bool = True,
|
|
426
463
|
):
|
|
427
464
|
"""Vortex RPC Decorator
|
|
428
465
|
|
|
@@ -444,6 +481,13 @@ def vortexRPC(
|
|
|
444
481
|
:param inlineCallbacks: Should the function be wrapped in the twisted
|
|
445
482
|
@inlinecallbacks decorator before it's called?.
|
|
446
483
|
|
|
484
|
+
:param logTimeoutException: When True, RPC timeouts log at ERROR.
|
|
485
|
+
When False, timeouts log at DEBUG with neutral wording.
|
|
486
|
+
|
|
487
|
+
:param logRpcException: When True, RPC handler and caller paths log at
|
|
488
|
+
ERROR. When False, the same events log at DEBUG with neutral
|
|
489
|
+
wording.
|
|
490
|
+
|
|
447
491
|
:return A wrapped function, that will now work as an RPC call.
|
|
448
492
|
|
|
449
493
|
|
|
@@ -485,6 +529,8 @@ def vortexRPC(
|
|
|
485
529
|
deferToThread,
|
|
486
530
|
inlineCallbacks,
|
|
487
531
|
requiredPermissions=requiredPermissions,
|
|
532
|
+
logTimeoutException=logTimeoutException,
|
|
533
|
+
logRpcException=logRpcException,
|
|
488
534
|
)
|
|
489
535
|
|
|
490
536
|
return decorator
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
from unittest.mock import ANY, MagicMock, patch
|
|
4
|
+
|
|
5
|
+
from twisted.internet.defer import TimeoutError
|
|
6
|
+
from twisted.python.failure import Failure
|
|
7
|
+
|
|
8
|
+
from vortex.PayloadEndpoint import PayloadEndpoint
|
|
9
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
10
|
+
from vortex.rpc.RPC import _VortexRPC
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _makeFunc(qualname: str = "testFunc") -> MagicMock:
|
|
14
|
+
func = MagicMock()
|
|
15
|
+
func.__qualname__ = qualname
|
|
16
|
+
func.__globals__ = {"__spec__": SimpleNamespace(name="vortex.rpc.RPC_test")}
|
|
17
|
+
return func
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RpcLoggingTest(unittest.TestCase):
|
|
21
|
+
def setUp(self) -> None:
|
|
22
|
+
_VortexRPC._VortexRPC__registeredFuncNames.clear()
|
|
23
|
+
|
|
24
|
+
def _makeRpc(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
logRpcException: bool = True,
|
|
28
|
+
logTimeoutException: bool = False,
|
|
29
|
+
qualname: str = "testFunc",
|
|
30
|
+
) -> _VortexRPC:
|
|
31
|
+
return _VortexRPC(
|
|
32
|
+
_makeFunc(qualname),
|
|
33
|
+
"listenVortexName",
|
|
34
|
+
5.0,
|
|
35
|
+
None,
|
|
36
|
+
{},
|
|
37
|
+
False,
|
|
38
|
+
False,
|
|
39
|
+
logTimeoutException=logTimeoutException,
|
|
40
|
+
logRpcException=logRpcException,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@patch("vortex.rpc.RPC.logger")
|
|
44
|
+
def testRemoteResultLogsDebugWhenLogRpcExceptionFalse(
|
|
45
|
+
self, mockLogger: MagicMock
|
|
46
|
+
) -> None:
|
|
47
|
+
rpc = self._makeRpc(logRpcException=False, qualname="testFuncRemote")
|
|
48
|
+
payloadEnvelope = PayloadEnvelope(filt={}, result="probe not ready")
|
|
49
|
+
|
|
50
|
+
rpc._processResponseCallback(payloadEnvelope, None)
|
|
51
|
+
|
|
52
|
+
mockLogger.error.assert_not_called()
|
|
53
|
+
mockLogger.debug.assert_called_once_with(
|
|
54
|
+
"RPC response for %s: %s",
|
|
55
|
+
rpc._VortexRPC__funcName,
|
|
56
|
+
"probe not ready",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@patch("vortex.rpc.RPC.logger")
|
|
60
|
+
def testRemoteResultLogsErrorWhenLogRpcExceptionTrue(
|
|
61
|
+
self, mockLogger: MagicMock
|
|
62
|
+
) -> None:
|
|
63
|
+
rpc = self._makeRpc(logRpcException=True, qualname="testFuncRemote2")
|
|
64
|
+
payloadEnvelope = PayloadEnvelope(filt={}, result="probe not ready")
|
|
65
|
+
|
|
66
|
+
rpc._processResponseCallback(payloadEnvelope, None)
|
|
67
|
+
|
|
68
|
+
mockLogger.debug.assert_not_called()
|
|
69
|
+
mockLogger.error.assert_called_once()
|
|
70
|
+
|
|
71
|
+
@patch("vortex.rpc.RPC.logger")
|
|
72
|
+
def testTimeoutErrbackDoesNotLogWhenPayloadResponseAlreadyDid(
|
|
73
|
+
self, mockLogger: MagicMock
|
|
74
|
+
) -> None:
|
|
75
|
+
rpc = self._makeRpc(logRpcException=False, logTimeoutException=True)
|
|
76
|
+
rpc._processResponseErrback(Failure(TimeoutError("timed out")), None)
|
|
77
|
+
mockLogger.error.assert_not_called()
|
|
78
|
+
mockLogger.debug.assert_not_called()
|
|
79
|
+
|
|
80
|
+
@patch("vortex.rpc.RPC.vortexLogFailure")
|
|
81
|
+
@patch("vortex.rpc.RPC.logger")
|
|
82
|
+
def testCallerExceptionLogsDebugWhenLogRpcExceptionFalse(
|
|
83
|
+
self, mockLogger: MagicMock, mockLogFailure: MagicMock
|
|
84
|
+
) -> None:
|
|
85
|
+
rpc = self._makeRpc(logRpcException=False, qualname="testFunc2")
|
|
86
|
+
rpc._processResponseErrback(Failure(ValueError("boom")), None)
|
|
87
|
+
mockLogFailure.assert_not_called()
|
|
88
|
+
mockLogger.error.assert_not_called()
|
|
89
|
+
mockLogger.debug.assert_called_once_with(
|
|
90
|
+
"RPC response for %s: %s",
|
|
91
|
+
rpc._VortexRPC__funcName,
|
|
92
|
+
"boom",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@patch("vortex.rpc.RPC.vortexLogFailure")
|
|
96
|
+
def testCallerExceptionLoggedWhenLogRpcExceptionTrue(
|
|
97
|
+
self, mockLogFailure: MagicMock
|
|
98
|
+
) -> None:
|
|
99
|
+
rpc = self._makeRpc(logRpcException=True, qualname="testFunc3")
|
|
100
|
+
failure = Failure(ValueError("boom"))
|
|
101
|
+
rpc._processResponseErrback(failure, None)
|
|
102
|
+
mockLogFailure.assert_called_once_with(failure, ANY)
|
|
103
|
+
|
|
104
|
+
def testPayloadEndpointStoresLogProcessException(self) -> None:
|
|
105
|
+
endpoint = PayloadEndpoint(
|
|
106
|
+
dict(key="example"),
|
|
107
|
+
lambda **kwargs: None,
|
|
108
|
+
logProcessException=False,
|
|
109
|
+
)
|
|
110
|
+
self.assertFalse(endpoint._logProcessException)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
unittest.main()
|
|
@@ -12,6 +12,7 @@ vortex/PayloadIO.py
|
|
|
12
12
|
vortex/PayloadPriority.py
|
|
13
13
|
vortex/PayloadResponse.py
|
|
14
14
|
vortex/PayloadResponseTest.py
|
|
15
|
+
vortex/PayloadResponse_test.py
|
|
15
16
|
vortex/PayloadTest.py
|
|
16
17
|
vortex/PerformTestActionTuple.py
|
|
17
18
|
vortex/SerialiseUtil.py
|
|
@@ -86,6 +87,7 @@ vortex/restful/RestfulResourceTest.py
|
|
|
86
87
|
vortex/restful/__init__.py
|
|
87
88
|
vortex/rpc/RPC.py
|
|
88
89
|
vortex/rpc/RPCTest.py
|
|
90
|
+
vortex/rpc/RPC_test.py
|
|
89
91
|
vortex/rpc/__init__.py
|
|
90
92
|
vortex/sqla_orm/OrmCrudHandler.py
|
|
91
93
|
vortex/sqla_orm/TupleProviderForDb.py
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|