elastic / apm-agent-python

https://www.elastic.co/guide/en/apm/agent/python/current/index.html
BSD 3-Clause "New" or "Revised" License
415 stars 220 forks source link

APM-Instrumented Azure Function crashes when querying to Azure Table #2118

Open cpiment opened 2 months ago

cpiment commented 2 months ago

Describe the bug:

When using Azure Table Client (package azure-data-tables) inside an Azure Function instrumented with elasticapm.contrib.serverless.azure.ElasticAPMExtension and querying a data table using PartitionKey and RowKey, the underlying HTTP request to the table service sends the request parameters in the query string and the request has no body. When Azure Tables instrumentation from Elastic APM reaches this line:

https://github.com/elastic/apm-agent-python/blob/fc8ef22e3427f23d77d26cbb647eceafd9ec3f19/elasticapm/instrumentation/packages/azure.py#L302-L307

It crashes because the body is None

To Reproduce

  1. Create an Storage Account in Azure and create a Table named 'test' and insert an entity with PartitionKey 'KEY' and RowKey 'ROW'
  2. Get the Storage Account SAS_TOKEN to connect to the table service and query the test table
  3. Create an Azure Function with this code
import azure.functions as func

from elasticapm.contrib.serverless.azure import ElasticAPMExtension
from azure.data.tables import TableServiceClient
from azure.core.credentials import AzureSasCredential

ElasticAPMExtension.configure()

def main(req: func.HttpRequest) -> func.HttpResponse:
    _table_service_client = TableServiceClient(endpoint=TABLE_ENDPOINT,credential=AzureSasCredential(TABLE_SAS_TOKEN))
    _table_client = _table_service_client.get_table_client('test')
    entity = _table_client.get_entity(
            partition_key='KEY',
            row_key='ROW'
        )
  1. Run func host start to start the local development environment
  2. Connect to the URL of the function and see the exception in the func logs

Environment (please complete the following information)

Additional context

Add any other context about the problem here.

xrmx commented 2 months ago

Thanks for reporting, I think if you are keen to contributing a possible patch would be to just skip the json decoding if body is falsy and set it to {}.

cpiment commented 2 months ago

Thanks @xrmx , will do that. What would be the best way to check "falsyness"? Something in the line of this would be enough?

if not body:
  body = {}
else:
  try: 
    body = json.loads(body) 
  except json.decoder.JSONDecodeError:  # str not bytes 
    body = {} 
xrmx commented 2 months ago

I would keep the happy path before (if body:) but yeah, something like that.