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:
dynatrace-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.
from opentelemetry.sdk.resources import Resourcefrom opentelemetry.semconv.resource import ResourceAttributesfrom dynatrace.opentelemetry.tracing.api import configure_dynatracetracer_provider = configure_dynatrace(resource=Resource.create({"my.resource.attribute": "My Resource"}))
from opentelemetry.propagate import set_global_textmapfrom opentelemetry.sdk.resources import Resourcefrom opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.semconv.resource import ResourceAttributesfrom opentelemetry.trace import set_tracer_providerfrom dynatrace.opentelemetry.tracing.api import (DtSampler,DtSpanProcessor,DtTextMapPropagator,)span_processor = DtSpanProcessor()tracer_provider = TracerProvider(sampler=DtSampler(),resource=Resource.create({"my.resource.attribute": "My Resource"}),)tracer_provider.add_span_processor(span_processor)set_global_textmap(DtTextMapPropagator())set_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:
# isort: offimport setup_tracing # import the module containing your setup code# isort: on# import other modules
Instrument a handler function
Programming model v1
Use the wrap_handler
decorator to instrument your handler function, as shown in the example below:
from azure import functions as func# import the wrap_handler decoratorfrom dynatrace.opentelemetry.azure.functions import wrap_handler@wrap_handlerdef main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:# From here the created span is available in the OpenTelemetry context as the current span.# do something ...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:
from azure import functions as func# import the wrap_handler decoratorfrom dynatrace.opentelemetry.azure.functions import wrap_handler@wrap_handler(http_result_param_name="res")def main(req: func.HttpRequest, other: func.Out[str], res: func.Out[func.HttpResponse]):# do something ...res.set(func.HttpResponse(f"Hello world.", status_code=200))
Programming model v2
Functions implemented using the v2 programming model can also be instrumented using the wrap_handler
decorator.
Because the Azure functions framework for programming model V2 uses decorators to mark handler functions, the order in which the wrap_handler
and the Azure decorators are applied is important.
The snippet below shows the correct order: the handler needs to be decorated with wrap_handler
before the app.route
decorator.
import azure.functions as func# import the wrap_handler decoratorfrom dynatrace.opentelemetry.azure.functions import wrap_handlerapp = func.FunctionApp()@app.function_name("MyHttpFunc")@app.route(route="hello")@wrap_handler # Note: wrap_handler must be located after the app.route decorator, so it's executed first.def main(req: func.HttpRequest) -> func.HttpResponse:# do something ...return func.HttpResponse("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.- Instrumentation of WSGI and ASGI web frameworks is currently not supported for the v2 programming model because of the handler's different signature.