Trace Azure Functions written in Python
The dynatrace-opentelemetry-azure-functions
package provides APIs for tracing Python Azure Functions.
Prerequisites
Ensure that you have followed the initial configuration steps described in Set up OpenTelemetry monitoring for Azure Functions on Consumption Plan before using the packages below.
- dynatrace-opentelemetry-azure-functions version 1.245+
Installation
To set up OpenTelemetry Python integration on Azure Functions, add the following line to the requirements.txt
file of your function app:
1dynatrace-opentelemetry-azure-functions
This adds the latest version of the dynatrace-opentelemetry-azure-functions
package as a dependency to your function app. For more information about managing dependencies, consult the Azure Functions Python developer guide.
Trace export
To export traces to Dynatrace, you need to initialize tracing and then instrument your handler functions.
Initialize tracing
Select one of the two ways below to initialize tracing.
configure_dynatrace
function—This is the recommended option unless you need to manually set up the tracing components.Manual tracing setup—This allows for a more fine-grained setup of tracing components.
Because it's possible to bundle several Azure Functions into a single Azure Function app, it's important to initialize tracing only once per Azure Function app instead of once per function. The simplest way to do this is to put the tracing setup code into a shared file as described in the Azure Functions Python developer guide and import it at the top of the files that define a function.
1from opentelemetry.sdk.resources import Resource2from opentelemetry.semconv.resource import ResourceAttributes34from dynatrace.opentelemetry.tracing.api import configure_dynatrace56tracer_provider = configure_dynatrace(7 resource=Resource.create({ResourceAttributes.SERVICE_NAME: "My Service"})8)
1from opentelemetry.propagate import set_global_textmap2from opentelemetry.sdk.resources import Resource3from opentelemetry.sdk.trace import TracerProvider4from opentelemetry.semconv.resource import ResourceAttributes5from opentelemetry.trace import set_tracer_provider67from dynatrace.opentelemetry.tracing.api import (8 DtSampler,9 DtSpanProcessor,10 DtTextMapPropagator,11)1213span_processor = DtSpanProcessor()1415tracer_provider = TracerProvider(16 sampler=DtSampler(),17 resource=Resource.create({ResourceAttributes.SERVICE_NAME: "My Service"}),18)19tracer_provider.add_span_processor(span_processor)2021set_global_textmap(DtTextMapPropagator())22set_tracer_provider(tracer_provider)
The tracing setup code should be implemented to set up tracing only once before any other third-party module is imported. If you use isort
to sort your imports, we suggest that you deactivate it while importing the tracing setup module, as shown in the following example:
1# isort: off2import setup_tracing # import the module containing your setup code3# isort: on45# import other modules
Instrument a handler function
Use the wrap_handler
decorator to instrument your handler function, as shown in the example below:
1from azure import functions as func23# import the wrap_handler decorator4from dynatrace.opentelemetry.azure.functions import wrap_handler56@wrap_handler7def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:8 # From here the created span is available in the OpenTelemetry context as the current span.910 # do something ...11 return func.HttpResponse(f"Hello world.", status_code=200)
The context parameter is optional and can be omitted from the handler's signature.
If your HTTP function handler doesn't return an explicit result and uses multiple Out
bindings, you should provide the name of the response binding as a binding hint to the decorator, so that result attributes can be properly set on the span.
Example:
1from azure import functions as func23# import the wrap_handler decorator4from dynatrace.opentelemetry.azure.functions import wrap_handler56@wrap_handler(http_result_param_name="res")7def main(req: func.HttpRequest, other: func.Out[str], res: func.Out[func.HttpResponse]):8 # do something ...9 res.set(func.HttpResponse(f"Hello world.", status_code=200))
Limitations
- The Azure runtime dynamically passes the invocation context argument to your handler function if the handler's signature contains a parameter with name
context
. However, if there's also a binding with the namecontext
in yourfunction.json
file, the invocation context is ignored by the binding and the binding is passed instead. Thewrap_handler
decorator won't work in this case, since it requires the invocation context to extract certain span attributes. Be sure not to use the namecontext
for any binding in yourfunction.json
file. - HTTP handler functions with multiple
Out
bindings and no explicit return result should provide the name of the response output binding to the function decorator, so that result span attributes can be properly set. DtSpanProcessor
only works together withDtSampler
. Make sure to setDtSampler
as a sampler when manually setting up tracing; spans might not be exported otherwise.