# Python ZOPE Application Framework Integration to Elastic APM

**URL:** <https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277>\
**Category:** APM\
**Tags:** python\
**Created:** [September 27, 2022, 2:44pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277 "2022-09-27T14:44:11Z")\
**Posts on this page:** 14\
**Page:** 3

<div class="post-metadata">

**Author:** ![Dixit](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/dixit/32/111369_2.png) [@Dixit](https://discuss.elastic.co/u/Dixit)\
**Post date:** [November 29, 2022, 9:33am UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/41 "2022-11-29T09:33:34Z")

</div>

hi @basepi & @lwintergerst ,

We are experiencing lot of exceptions/errors in the Zope application and even though **adding "Above Code"** doesn't display exceptions/errors in the APM UI.

> [@Python ZOPE Application Framework Integration to Elastic APM](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/40):
>
> No, typically you would surround the contents. Something like this: client.begin\_transaction('request') try: response = publish(request, module\_name, after\_list, debug=debug) except Exception: elasticapm.capture\_exception(handled=False) raise finally: client.end\_transaction(request.method + " " + request.get('PATH\_INFO'), response.status)

If logging and Errors are taken care then pretty much am done with Elastic APM implementation for Zope Python framework.

**Last thing need a help though** @basepi @lwintergerst !! Appreciate it.

 ![image](https://us1.discourse-cdn.com/elastic/original/3X/9/9/9926db9fe63d9661a5f777ae6ee50b04d3403a1b.png)

 ![image](https://us1.discourse-cdn.com/elastic/original/3X/2/b/2b752cf924129b07eb3a3beaed1ea9b02c396bb0.png)

---

<div class="post-metadata">

**Author:** ![basepi](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/basepi/32/53056_2.png) [@basepi](https://discuss.elastic.co/u/basepi)\
**Post date:** [November 29, 2022, 5:42pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/42 "2022-11-29T17:42:36Z")

</div>

Looks like you're seeing the transactions, which leads me to believe your `Client` object is configured correctly. Could you show me your code? I want to make sure we didn't miss something between my example and your implementation.

---

<div class="post-metadata">

**Author:** ![Dixit](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/dixit/32/111369_2.png) [@Dixit](https://discuss.elastic.co/u/Dixit)\
**Post date:** [November 29, 2022, 6:10pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/43 "2022-11-29T18:10:37Z")

</div>

hi @basepi ,

Here is the code in full , where we use Zope Framework. Help me to understand what am missing here @basepi @lwintergerst @beniwohli

```auto

####### ELASTIC APM INTEGRATION #######
import elasticapm
logger = get_logger("elasticapm.errors.client")

client = elasticapm.Client(service_name="JAAAVA-CI-NSTANCE", framework_name="Zope", framework_version="4.0", server_url="http://APM93.zzzzga:8200")

elasticapm.instrument()

_default_debug_mode = False
_default_realm = None

def call_object(object, args, request):
    return object(*args)

def missing_name(name, request):
    if name == 'self':
        return request['PARENTS'][0]
    request.response.badRequestError(name)

def dont_publish_class(klass, request):
    request.response.forbiddenError("class %s" % klass. __name__ )

def validate_user(request, user):
    newSecurityManager(request, user)

def set_default_debug_mode(debug_mode):
    global _default_debug_mode
    _default_debug_mode = debug_mode

def set_default_authentication_realm(realm):
    global _default_realm
    _default_realm = realm

def publish(request, module_name, after_list, debug=0,
            # Optimize:
            call_object=call_object,
            missing_name=missing_name,
            dont_publish_class=dont_publish_class,
            mapply=mapply,
            ):

    (bobo_before, bobo_after, object, realm, debug_mode, err_hook,
     validated_hook, transactions_manager) = get_module_info(module_name)

    parents = None
    response = None

    try:
        notify(pubevents.PubStart(request))
        # TODO pass request here once BaseRequest implements IParticipation
        newInteraction()

        request.processInputs()

        request_get = request.get
        response = request.response

        # First check for "cancel" redirect:
        if request_get('SUBMIT', '').strip().lower() == 'cancel':
            cancel = request_get('CANCEL_ACTION', '')
            if cancel:
                # Relative URLs aren't part of the spec, but are accepted by
                # some browsers.
                for part, base in zip(urlparse(cancel)[:3],
                                      urlparse(request['BASE1'])[:3]):
                    if not part:
                        continue
                    if not part.startswith(base):
                        cancel = ''
                        break
            if cancel:
                raise Redirect(cancel)

        after_list[0] = bobo_after
        if debug_mode:
            response.debug_mode = debug_mode
        if realm and not request.get('REMOTE_USER', None):
            response.realm = realm

        noSecurityManager()
        if bobo_before is not None:
            bobo_before()

        # Get the path list.
        # According to RFC1738 a trailing space in the path is valid.
        path = request_get('PATH_INFO')

        request['PARENTS'] = parents = [object]

        if transactions_manager:
            transactions_manager.begin()

        object = request.traverse(path, validated_hook=validated_hook)

        if IBrowserPage.providedBy(object):
            request.postProcessInputs()

        notify(pubevents.PubAfterTraversal(request))

        if transactions_manager:
            recordMetaData(object, request)
		
        result = mapply(object, request.args, request,
                        call_object, 1,
                        missing_name,
                        dont_publish_class,
                        request, bind=1)
       
        if result is not response:
            response.setBody(result)

        notify(pubevents.PubBeforeCommit(request))

        if transactions_manager:
            transactions_manager.commit()

        notify(pubevents.PubSuccess(request))
        endInteraction()

        return response
    except:
        # save in order to give 'PubFailure' the original exception info
        exc_info = sys.exc_info()
        # DM: provide nicer error message for FTP
        sm = None
        if response is not None:
            sm = getattr(response, "setMessage", None)

        if sm is not None:
            from asyncore import compact_traceback
            cl, val = sys.exc_info()[:2]
            sm('%s: %s %s' % (
                getattr(cl, ' __name__', cl), val,
                debug_mode and compact_traceback()[-1] or ''))

        # debug is just used by tests (has nothing to do with debug_mode!)
        if not debug and err_hook is not None:
            retry = False
            if parents:
                parents = parents[0]
            try:
                try:
                    return err_hook(parents, request,
                                    sys.exc_info()[0],
                                    sys.exc_info()[1],
                                    sys.exc_info()[2],
                                    )
                except Retry:
                    if not request.supports_retry():
                        return err_hook(parents, request,
                                        sys.exc_info()[0],
                                        sys.exc_info()[1],
                                        sys.exc_info()[2],
                                        )
                    retry = True
            finally:
                # Note: 'abort's can fail.
                # Nevertheless, we want end request handling.
                try:
                    try:
                        notify(pubevents.PubBeforeAbort(
                            request, exc_info, retry))
                    finally:
                        if transactions_manager:
                            transactions_manager.abort()
                finally:
                    endInteraction()
                    notify(pubevents.PubFailure(request, exc_info, retry))

            # Only reachable if Retry is raised and request supports retry.
            newrequest = request.retry()
            request.close() # Free resources held by the request.

            # Set the default layer/skin on the newly generated request
            if ISkinnable.providedBy(newrequest):
                setDefaultSkin(newrequest)
            try:
                return publish(newrequest, module_name, after_list, debug)
            finally:
                newrequest.close()

        else:
            # Note: 'abort's can fail.
            # Nevertheless, we want end request handling.
            try:
                try:
                    notify(pubevents.PubBeforeAbort(request, exc_info, False))
                finally:
                    if transactions_manager:
                        transactions_manager.abort()
            finally:
                endInteraction()
                notify(pubevents.PubFailure(request, exc_info, False))
            raise

@elasticapm.capture_span()
def publish_module_standard(
        module_name,
        stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
        environ=os.environ, debug=0, request=None, response=None):
    must_die = 0
    status = 200
    after_list = [None]
    try:
        try:
            if response is None:
                response = Response(stdout=stdout, stderr=stderr)
            else:
                stdout = response.stdout

            # debug is just used by tests (has nothing to do with debug_mode!)
            response.handle_errors = not debug

            if request is None:
                request = Request(stdin, environ, response)

            setRequest(request)

            # make sure that the request we hand over has the
            # default layer/skin set on it; subsequent code that
            # wants to look up views will likely depend on it
            if ISkinnable.providedBy(request):
                setDefaultSkin(request)
			
			########################### Elastic APM ####################
            client.begin_transaction('request')
            try:
                response = publish(request, module_name, after_list, debug=debug)
            except Exception:
                client.capture_exception()
                raise
            finally:
                client.end_transaction(request.method + " " + request["ACTUAL_URL"], response.status)
			########################### Elastic APM ####################
        
		except (SystemExit, ImportError):
            # XXX: Rendered ImportErrors were never caught here because they
            # were re-raised as string exceptions. Maybe we should handle
            # ImportErrors like all other exceptions. Currently they are not
            # re-raised at all, so they don't show up here.
            must_die = sys.exc_info()
            request.response.exception(1)
        except:
            # debug is just used by tests (has nothing to do with debug_mode!)
            if debug:
                raise
            request.response.exception()
            status = response.getStatus()

        if response:
            outputBody = getattr(response, 'outputBody', None)
            if outputBody is not None:
                outputBody()
            else:
                response = str(response)
                if response:
                    stdout.write(response)

        # The module defined a post-access function, call it
        if after_list[0] is not None:
            after_list[0]()

    finally:
        if request is not None:
            request.close()
            clearRequest()

    if must_die:
        # Try to turn exception value into an exit code.
        try:
            if hasattr(must_die[1], 'code'):
                code = must_die[1].code
            else:
                code = int(must_die[1])
        except:
            code = must_die[1] and 1 or 0
        if hasattr(request.response, '_requestShutdown'):
            request.response._requestShutdown(code)

        try:
            reraise(must_die[0], must_die[1], must_die[2])
        finally:
            must_die = None

    return status

_l = allocate_lock()

def get_module_info(module_name, modules={},
                    acquire=_l.acquire,
                    release=_l.release):

    if module_name in modules:
        return modules[module_name]

    if module_name[-4:] == '.cgi':
        module_name = module_name[:-4]

    acquire()
    tb = None
    g = globals()
    try:
        try:
            module = __import__ (module_name, g, g, (' __doc__',))

            # Let the app specify a realm
            if hasattr(module, ' __bobo_realm__'):
                realm = module. __bobo_realm__
            elif _default_realm is not None:
                realm = _default_realm
            else:
                realm = module_name

            # Check for debug mode
            if hasattr(module, ' __bobo_debug_mode__'):
                debug_mode = bool(module. __bobo_debug_mode__ )
            else:
                debug_mode = _default_debug_mode

            bobo_before = getattr(module, " __bobo_before__", None)
            bobo_after = getattr(module, " __bobo_after__", None)

            if hasattr(module, 'bobo_application'):
                object = module.bobo_application
            elif hasattr(module, 'web_objects'):
                object = module.web_objects
            else:
                object = module

            error_hook = getattr(module, 'zpublisher_exception_hook', None)
            validated_hook = getattr(
                module, 'zpublisher_validated_hook', validate_user)

            transactions_manager = getattr(
                module, 'zpublisher_transactions_manager', None)
            if not transactions_manager:
                # Create a default transactions manager for use
                # by software that uses ZPublisher and ZODB but
                # not the rest of Zope.
                transactions_manager = DefaultTransactionsManager()

            info = (bobo_before, bobo_after, object, realm, debug_mode,
                    error_hook, validated_hook, transactions_manager)

            modules[module_name] = modules[module_name + '.cgi'] = info

            return info
        except Exception:
            t, v, tb = sys.exc_info()
            reraise(t, str(v), tb)
    finally:
        tb = None
        release()

class DefaultTransactionsManager(object):

    def begin(self):
        transaction.begin()

    def commit(self):
        if transaction.isDoomed():
            transaction.abort()
        else:
            transaction.commit()

    def abort(self):
        transaction.abort()

def publish_module(module_name,
                   stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
                   environ=os.environ, debug=0, request=None, response=None):
    """ publish a Python module """
    return publish_module_standard(module_name, stdin, stdout, stderr,
                                   environ, debug, request, response)

```

---

<div class="post-metadata">

**Author:** ![Dixit](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/dixit/32/111369_2.png) [@Dixit](https://discuss.elastic.co/u/Dixit)\
**Post date:** [November 30, 2022, 4:12pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/44 "2022-11-30T16:12:10Z")

</div>

HI @basepi ,

Please do shed some light on this .

Appreciate your help !!

---

<div class="post-metadata">

**Author:** ![basepi](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/basepi/32/53056_2.png) [@basepi](https://discuss.elastic.co/u/basepi)\
**Post date:** [November 30, 2022, 7:49pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/45 "2022-11-30T19:49:06Z")

</div>

I noticed a couple of anomalies in your code.

The first is that you're creating a transaction inside of a function you have decorated with `capture_span`:

```python
@elasticapm.capture_span()
def publish_module_standard(
   ...
   client.begin_transaction('request')

```

A span can only be captured inside a running transaction. So I'm wondering if you're creating duplicate transactions or something, or if you should just remove that `capture_span()` decorator.

Additionally, the only place you're capturing errors is in this single function call:

```python
response = publish(request, module_name, after_list, debug=debug)

```

And inside of this function call you have a very broad `try:except:` (with a [bare `except:` clause](https://www.flake8rules.com/rules/E722.html)) which doesn't `raise` exceptions it catches. In fact, you have a `raise` in your `else` clause! [In a `try:except:else:` clause, the `else` is only executed if there was no exception](https://www.w3schools.com/python/python_try_except.asp), so your `raise` doesn't do anything.

Almost all errors will be caught by your `try:except:` in your `publish()` function and will never be seen by the outer `elasticapm.capture_exception()` block.

---

<div class="post-metadata">

**Author:** ![Dixit](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/dixit/32/111369_2.png) [@Dixit](https://discuss.elastic.co/u/Dixit)\
**Post date:** [December 1, 2022, 3:19pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/46 "2022-12-01T15:19:36Z")

</div>

hi @basepi ,

Thanks for your suggestion . Removed `capture_span()` decorator.

**What would be the ideal way to capture the exception and display(send the details to APM Server) it in ELASTIC APM UI .**

Here I have the code and above as well. Can you help me here please

```auto
########################### Elastic APM ####################
            client.begin_transaction('request')
            #try:
            response = publish(request, module_name, after_list, debug=debug)
            #except Exception:
                #message = traceback.format_exc()
                #client.capture_exception()
                #raise
            #import pdb; pdb.set_trace()
            #elasticapm.set_context(lambda: get_data_from_request(request), "request")
            #elasticapm.set_context(lambda: get_data_from_response(response), "response")
            #finally:
            client.end_transaction(request.method + " " + (request["ACTUAL_URL"]).split("/",3)[3], response.status)
            #client.end_transaction()
        except (SystemExit, ImportError):
            # XXX: Rendered ImportErrors were never caught here because they
            # were re-raised as string exceptions. Maybe we should handle
            # ImportErrors like all other exceptions. Currently they are not
            # re-raised at all, so they don't show up here.
            must_die = sys.exc_info()
            request.response.exception(1)
        except:
            # debug is just used by tests (has nothing to do with debug_mode!)
            if debug:
                raise
            request.response.exception()
            status = response.getStatus()

```

Please Advice , Thank you!!

---

<div class="post-metadata">

**Author:** ![basepi](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/basepi/32/53056_2.png) [@basepi](https://discuss.elastic.co/u/basepi)\
**Post date:** [December 1, 2022, 4:59pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/47 "2022-12-01T16:59:18Z")

</div>

As I noted before, the problem is in the `publish()` function. The errors are not reaching the capture\_exception() call because you're catching them in the `try:except:` in the `publish()` function. You either need to put the `capture_exception()` call inside the `publish()` `try:except`, or you need to properly handle exceptions and re-raise them so the outer call sees them.

---

<div class="post-metadata">

**Author:** ![Dixit](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/dixit/32/111369_2.png) [@Dixit](https://discuss.elastic.co/u/Dixit)\
**Post date:** [December 1, 2022, 6:22pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/48 "2022-12-01T18:22:32Z")

</div>

Hi @basepi,

Please post the code here. I would like to try both.

Am not getting how to interpret your statements.

Just need help with the snippet.

Help me as am in the last stages of POC

---

<div class="post-metadata">

**Author:** ![basepi](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/basepi/32/53056_2.png) [@basepi](https://discuss.elastic.co/u/basepi)\
**Post date:** [December 2, 2022, 5:27pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/49 "2022-12-02T17:27:05Z")

</div>

If you just add an `elasticapm.capture_exception()` call to your `except:` block inside of your `publish()` function, it may fix most of your problem:

```python
    except:
        # save in order to give 'PubFailure' the original exception info
        exc_info = sys.exc_info()
        elasticapm.capture_exception() # this is the new line, l130 in your original paste

```

---

<div class="post-metadata">

**Author:** ![Dixit](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/dixit/32/111369_2.png) [@Dixit](https://discuss.elastic.co/u/Dixit)\
**Post date:** [December 5, 2022, 4:57pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/50 "2022-12-05T16:57:31Z")

</div>

Hi @basepi

How to populate error rate graph on the overview window in APM ui , not getting though ?

Thanks

---

<div class="post-metadata">

**Author:** ![Dixit](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/dixit/32/111369_2.png) [@Dixit](https://discuss.elastic.co/u/Dixit)\
**Post date:** [December 13, 2022, 8:04am UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/51 "2022-12-13T08:04:19Z")

</div>

hi @basepi , @lwintergerst

We are getting the "Request Traced" for MEMCACHE, how do I get "STACK TRACE" for all the MEMCACHE related calls.

Currently we observe - Client.gets is being logged/displayed in the UI, no further details though.

How do we make "Client.gets" to have more information ? like what is being sent to memcache

![image](https://us1.discourse-cdn.com/elastic/original/3X/f/0/f02964e19445ab797825ce381f356064c7139c9e.png)

 ![image](https://us1.discourse-cdn.com/elastic/original/3X/f/d/fdbd81696acf26628c52b5c9271b37724ef458d1.png)

Appreciate your help here @basepi@lwintergerst

---

<div class="post-metadata">

**Author:** ![basepi](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/basepi/32/53056_2.png) [@basepi](https://discuss.elastic.co/u/basepi)\
**Post date:** [December 14, 2022, 6:41pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/52 "2022-12-14T18:41:32Z")

</div>

We don't collect stack traces by default on spans shorter than 5ms. This is because collecting the stack trace does cause some overhead, especially when there are many very short spans.

You can change this behavior with [the `span_stack_trace_min_duration` config](https://www.elastic.co/guide/en/apm/agent/python/current/configuration.html#config-span-stack-trace-min-duration).

As far as naming of the spans goes, we want the span names to be low-cardinality so we don't include query information in the name. We don't currently collect the query in the metadata for the span -- nobody has asked for that information before. 😅

If you wanted to [open an issue](https://github.com/elastic/apm-agent-python/issues/new?assignees=&labels=&template=Feature_request.md), we could add that to our backlog. You could make a PR to add that context information to the span. See [the existing instrumentation](https://github.com/elastic/apm-agent-python/blob/a7f44accdf5f243ad2670c825e48ce9717404050/elasticapm/instrumentation/packages/python_memcached.py#L84) (the linked one is for `python_memcached`, there's a [different instrumentation for `pymemcache`](https://github.com/elastic/apm-agent-python/blob/main/elasticapm/instrumentation/packages/pymemcache.py)) and add `db.statement` as shown [here](https://github.com/elastic/apm-agent-python/blob/a7f44accdf5f243ad2670c825e48ce9717404050/elasticapm/instrumentation/packages/dbapi2.py#L239-L242) in the `dbapi2` instrumentation.

---

<div class="post-metadata">

**Author:** ![Dixit](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/dixit/32/111369_2.png) [@Dixit](https://discuss.elastic.co/u/Dixit)\
**Post date:** [December 14, 2022, 11:53pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/53 "2022-12-14T23:53:51Z")

</div>

Hi @basepi,

Let me open an issue and see how feasible it would be to get pymemcached metadata in the span names

Thank you

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/elastic/original/3X/1/a/1ac57faf039f6b580b3f104ef42a2a89e41014de.png) [@system](https://discuss.elastic.co/u/system)\
**Post date:** [January 4, 2023, 7:54pm UTC](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277/54 "2023-01-04T19:54:19Z")

</div>

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.

[Previous page](https://discuss.elastic.co/t/python-zope-application-framework-integration-to-elastic-apm/315277.md?page=2)
