census-instrumentation/opencensus-python

The record is not being sent to Azure if it has exc_info

greatvovan opened this issue · 2 comments

Environment

Python 3.10.8

pip freeze
azure-core==1.26.3
azure-identity==1.12.0
cachetools==5.3.0
certifi==2022.12.7
cffi==1.15.1
charset-normalizer==3.0.1
cryptography==39.0.1
google-api-core==2.11.0
google-auth==2.16.0
googleapis-common-protos==1.58.0
idna==3.4
msal==1.21.0
msal-extensions==1.0.0
opencensus==0.11.1
opencensus-context==0.1.3
opencensus-ext-azure==1.1.8
portalocker==2.7.0
protobuf==4.21.12
psutil==5.9.4
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycparser==2.21
PyJWT==2.6.0
requests==2.28.2
rsa==4.9
six==1.16.0
typing_extensions==4.4.0
urllib3==1.26.14

Steps to reproduce

Run the code (with a valid InstrumentationKey):

import sys
import logging
from opencensus.ext.azure.log_exporter import AzureLogHandler

logger = logging.getLogger('ribbit')
logger.addHandler(AzureLogHandler(connection_string='InstrumentationKey=...'))
logger.addHandler(logging.StreamHandler(sys.stdout))
logger.warning(42)

try:
    1 / 0
except Exception:
    logger.exception('1/0')

What is the expected behaviour?

Equivalent output of both handlers.

What is the actual behaviour?

The code will print both 42 and 1/0 locally, but only 42 will go to Azure.

Additional context

I figured out that if I remove exc_info from the record here


before sending to the queue, it starts working. I did not chase it further. Also it does not print any errors.

This does not seem to be a recent problem. I tried it with versions 1.1.0, 1.0.0, 0.7.1, and the result is the same.

The workaround I invented so far:

def patch_azure_log_handler():
    """
    As of version opencensus-ext-azure==1.1.8, the handler fails to send
    a log record to the cloud if the record has exc_info.
    Here we convert most useful information from exc_info into
    customDimensions dynamic field of traces table and reset exc_info.

    See https://github.com/census-instrumentation/opencensus-python/issues/1190
    """
    from opencensus.ext.azure.log_exporter import BaseLogHandler
    from traceback import format_exception

    def emit(self, record):
        if record.exc_info:
            exc_type, exc_object, exc_trace = record.exc_info
            cd = getattr(record, 'custom_dimensions', None)

            if cd is None:
                cd = {}
                record.custom_dimensions = cd

            cd['excType'] = exc_type.__name__
            cd['excMessage'] = str(exc_object)
            cd['excTrace'] = ''.join(format_exception(exc_object))

            record.exc_info = None

        BaseLogHandler.old_emit(self, record)

    BaseLogHandler.old_emit = BaseLogHandler.emit
    BaseLogHandler.emit = emit

Please post if you have improvement suggestions.

My apologies for false alert, I am a new user and I did not realize that the exceptions go into exceptions table rather than regular traces table. With that caveat everything works great.

Having said that, I think it is a questionable design to differentiate destinations based on presence of exc_info, because Logger.warning(obj, exc_info=True) will go to exceptions table besides being a warning level record.