You can wrap any function or method in Python using wrapt. Some use the builtin functools but I’ve found wrapt to be the better choice for many reasons - most of which is the ease, simplicity and consistency of use.
Wrapping (or monkey patching) is often done for various reasons such as:
- Adding performance measurement code
- Adding debug code to investigate a problem
- To augment or override functionality
pip install wrapt
import wrapt import urllib3 @wrapt.patch_function_wrapper('urllib3', 'HTTPConnectionPool.urlopen') def urlopen_with_instana(wrapped, instance, args, kwargs): # Perform any required steps beforehand rv = wrapped(*args, **kwargs) # Post method steps # Always make sure to return the original return value or # we change the behavior of the method or function that we're # wrapping and potentially break callers return rv except Exception as e: # Handle, log or print the exception # # Make sure to continue to raise the exception so as to not # change the behavior of the method or function that # we are wrapping. raise
With this, every call to
urllib3.HTTPConnectionPool.urlopen() will be intercepted by
urlopen_with_instana which allows us to add pre, post and exception handling code execution.
This is the pattern we use to intercept Python Redis calls in the Python sensor for Instana.
The following example uses OpenTracing APIs for a distributed tracing implementation.
@wrapt.patch_function_wrapper('redis.client','StrictRedis.execute_command') def execute_command_with_instana(wrapped, instance, args, kwargs): parent_span = tracer.active_span # If we're not tracing, just return if parent_span is None or parent_span.operation_name == "redis": return wrapped(*args, **kwargs) with tracer.start_active_span("redis", child_of=parent_span) as scope: try: collect_tags(scope.span, instance, args, kwargs) if (len(args) > 0): scope.span.set_tag("command", args) rv = wrapped(*args, **kwargs) except Exception as e: scope.span.log_exception(e) raise else: return rv