次の方法で共有

教程:使用 OpenTelemetry 分布式追踪来监控 Azure 函数

本文展示了 Azure Function 中对 OpenTelemetry 的支持,启用了集成的 Application Insights 和 OpenTelemetry支持,从而可以在多个函数调用之间实现分布式跟踪。 为了帮助你入门,Azure 开发人员 CLI (azd) 模板用于创建代码项目以及运行应用的 Azure 部署。

在本教程中,你将使用 azd 该工具来:

  • 从模板中初始化一个已启用 OpenTelemetry 的项目。
  • 请查看支持 OpenTelemetry 集成的代码。
  • 在本地运行并验证已启用 OpenTelemetry 的应用。
  • 在 Azure 中创建函数应用和相关资源。
  • 将代码项目部署到 Azure 中的函数应用。
  • 在 Application Insights 中验证分布式跟踪。

此模板创建所需的 Azure 资源遵循 Azure 中安全且可缩放的函数应用部署的当前最佳做法。 同一 azd 命令还会将代码项目部署到 Azure 中的新函数应用。

默认情况下,Flex Consumption 计划遵循按需付费的计费模型,这意味着使用此计划完成本快速入门会在您的 Azure 帐户中产生少量费用,约为几美元美分或更少。

重要

本文目前不支持 PowerShell。

先决条件

初始化项目

azd init使用命令从包含 OpenTelemetry 分布式跟踪的模板创建本地 Azure Functions 代码项目。

  1. 在本地终端或命令提示符下,在空文件夹中运行此 azd init 命令:

    azd init --template functions-quickstart-python-azd-otel -e flexquickstart-otel
    

    此命令从模板存储库中拉取项目文件,并在当前文件夹中初始化项目。 -e 标志设置当前环境的名称。 在 azd,环境维护您应用程序的独特部署上下文,并且您可以定义多个。 环境名称也会显示在你在 Azure 中创建的资源组的名称中。

  1. 在本地终端或命令提示符下,在空文件夹中运行此 azd init 命令:

    azd init --template functions-quickstart-typescript-azd-otel -e flexquickstart-otel
    

    此命令从模板存储库中拉取项目文件,并在当前文件夹中初始化项目。 -e 标志设置当前环境的名称。 在 azd,环境维护您应用程序的独特部署上下文,并且您可以定义多个。 环境名称也会显示在你在 Azure 中创建的资源组的名称中。

  1. 在本地终端或命令提示符下,在空文件夹中运行此 azd init 命令:

    azd init --template functions-quickstart-javascript-azd-otel -e flexquickstart-otel
    

    此命令从模板存储库中拉取项目文件,并在当前文件夹中初始化项目。 -e 标志设置当前环境的名称。 在 azd,环境维护您应用程序的独特部署上下文,并且您可以定义多个。 环境名称也会显示在你在 Azure 中创建的资源组的名称中。

  1. 在本地终端或命令提示符下,在空文件夹中运行此 azd init 命令:

    azd init --template functions-quickstart-dotnet-azd-otel -e flexquickstart-otel
    

    此命令从模板存储库中拉取项目文件,并在当前文件夹中初始化项目。 -e 标志设置当前环境的名称。 在 azd,环境维护您应用程序的独特部署上下文,并且您可以定义多个。 环境名称也会显示在你在 Azure 中创建的资源组的名称中。

  1. 在本地终端或命令提示符下,在空文件夹中运行此 azd init 命令:

    azd init --template functions-quickstart-java-azd-otel -e flexquickstart-otel
    

    此命令从模板存储库中拉取项目文件,并在当前文件夹中初始化项目。 -e 标志设置当前环境的名称。 在 azd,环境维护您应用程序的独特部署上下文,并且您可以定义多个。 环境名称也会显示在你在 Azure 中创建的资源组的名称中。

查看代码

该模板创建一个完整的分布式跟踪方案,其中包含三个协同工作的函数。 查看与 OpenTelemetry 相关的关键方面。

OpenTelemetry 配置

src/otel-sample/host.json 文件为 Functions 主机启用 OpenTelemetry。

{
  "version": "2.0",
  "telemetryMode": "OpenTelemetry",
  "extensions": {
    "serviceBus": {
        "maxConcurrentCalls": 10
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

密钥设置 "telemetryMode": "OpenTelemetry" 允许跨函数调用进行分布式跟踪。

host.json 文件为 Functions 主机启用 OpenTelemetry。

{
  "version": "2.0",
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  },
  "telemetryMode": "OpenTelemetry"
}

密钥设置 "telemetryMode": "OpenTelemetry" 允许跨函数调用进行分布式跟踪。

src/OTelSample/host.json 文件为 Functions 主机启用 OpenTelemetry。

{
  "version": "2.0",
  "telemetryMode": "OpenTelemetry",
  "logging": {
    "OpenTelemetry": {
      "logLevel": {
        "Host.General": "Warning"
      }
    }
  }
}

密钥设置 "telemetryMode": "OpenTelemetry" 允许跨函数调用进行分布式跟踪。

OpenTelemetry 的依赖项

src/otel-sample/requirements.txt 文件包含 OpenTelemetry 集成所需的包:

azure-functions
azure-monitor-opentelemetry
requests

azure-monitor-opentelemetry 包提供与 Application Insights 的 OpenTelemetry 集成。

src/otel-sample/package.json 文件包含 OpenTelemetry 集成所需的包:

{
  "dependencies": {
    "@azure/functions": "^4.0.0",
    "@azure/functions-opentelemetry-instrumentation": "^0.1.0",
    "@azure/monitor-opentelemetry-exporter": "^1.0.0",
    "axios": "^1.6.0"
  }
}

@azure/functions-opentelemetry-instrumentation@azure/monitor-opentelemetry-exporter 包提供 Application Insights 与 OpenTelemetry 的集成。

src/otel-sample/package.json 文件包含 OpenTelemetry 集成所需的包:

{
  "dependencies": {
    "@azure/functions": "4.7.0",
    "@azure/functions-opentelemetry-instrumentation": "^0.2.0",
    "@azure/monitor-opentelemetry-exporter": "^1.0.0-beta.32",
    "@opentelemetry/api": "^1.9.0",
    "@opentelemetry/auto-instrumentations-node": "^0.67.0",
    "axios": "^1.12.0"
  }
}

@azure/functions-opentelemetry-instrumentation@azure/monitor-opentelemetry-exporter 包提供 Application Insights 与 OpenTelemetry 的集成。 该 @opentelemetry/auto-instrumentations-node 包为 Node.js 库提供自动检测。

.csproj 文件包含 OpenTelemetry 集成所需的包:

<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.OpenTelemetry" Version="1.4.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.10.0" />

这些包提供 OpenTelemetry 与 Application Insights 的集成,以及用于分布式跟踪的 HTTP 监测工具。

该文件 pom.xml 包含 OpenTelemetry 集成所需的依赖项:

<dependency>
    <groupId>com.microsoft.azure.functions</groupId>
    <artifactId>azure-functions-java-library</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>com.microsoft.azure.functions</groupId>
    <artifactId>azure-functions-java-opentelemetry</artifactId>
    <version>1.1.0</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-api</artifactId>
    <version>1.49.0</version>
</dependency>

azure-functions-java-opentelemetry 包提供 OpenTelemetry 集成,而 opentelemetry-api 包提供用于分布式跟踪的核心 OpenTelemetry API。

该项目还使用 Maven 依赖项插件在生成过程中下载 OpenTelemetry Java 代理:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>3.6.0</version>
    <executions>
        <execution>
            <id>copy-otel-agent</id>
            <phase>package</phase>
            <goals>
                <goal>copy</goal>
            </goals>
            <configuration>
                <artifactItems>
                    <artifactItem>
                        <groupId>io.opentelemetry.javaagent</groupId>
                        <artifactId>opentelemetry-javaagent</artifactId>
                        <version>2.11.0</version>
                        <type>jar</type>
                        <outputDirectory>${project.build.directory}/azure-functions/${functionAppName}</outputDirectory>
                        <destFileName>opentelemetry-javaagent.jar</destFileName>
                    </artifactItem>
                </artifactItems>
            </configuration>
        </execution>
    </executions>
</plugin>

此代理会自动加载到 Java 运行时,以支持分布式跟踪设备。

函数实现

src/otel-sample/function_app.py 中的函数演示了分布式跟踪的流程。

第一个 HTTP 函数

@app.function_name("first_http_function")
@app.route(route="first_http_function", auth_level=func.AuthLevel.ANONYMOUS)
def first_http_function(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function (first) processed a request.')

    # Call the second function
    base_url = f"{req.url.split('/api/')[0]}/api"
    second_function_url = f"{base_url}/second_http_function"

    response = requests.get(second_function_url)
    second_function_result = response.text

    result = {
        "message": "Hello from the first function!",
        "second_function_response": second_function_result
    }

    return func.HttpResponse(
        json.dumps(result),
        status_code=200,
        mimetype="application/json"
    )

第二个 HTTP 函数

@app.function_name("second_http_function")
@app.route(route="second_http_function", auth_level=func.AuthLevel.ANONYMOUS)
@app.service_bus_queue_output(arg_name="outputsbmsg", queue_name="%ServiceBusQueueName%",
                              connection="ServiceBusConnection")
def second_http_function(req: func.HttpRequest, outputsbmsg: func.Out[str]) -> func.HttpResponse:
    logging.info('Python HTTP trigger function (second) processed a request.')

    message = "This is the second function responding."

    # Send a message to the Service Bus queue
    queue_message = "Message from second HTTP function to trigger ServiceBus queue processing"
    outputsbmsg.set(queue_message)
    logging.info('Sent message to ServiceBus queue: %s', queue_message)

    return func.HttpResponse(
        message,
        status_code=200
    )

服务总线队列触发器

@app.service_bus_queue_trigger(arg_name="azservicebus", queue_name="%ServiceBusQueueName%",
                               connection="ServiceBusConnection") 
def servicebus_queue_trigger(azservicebus: func.ServiceBusMessage):
    logging.info('Python ServiceBus Queue trigger start processing a message: %s',
                azservicebus.get_body().decode('utf-8'))
    time.sleep(5)  # Simulate processing work
    logging.info('Python ServiceBus Queue trigger end processing a message')

OpenTelemetry 配置设置在 src/otel-sample/index.ts 中。

import { AzureFunctionsInstrumentation } from '@azure/functions-opentelemetry-instrumentation';
import { AzureMonitorTraceExporter, AzureMonitorLogExporter } from '@azure/monitor-opentelemetry-exporter';
import { getNodeAutoInstrumentations, getResourceDetectors } from '@opentelemetry/auto-instrumentations-node';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { detectResources } from '@opentelemetry/resources';
import { LoggerProvider, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { NodeTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';

const resource = detectResources({ detectors: getResourceDetectors() });

const tracerProvider = new NodeTracerProvider({ 
  resource, 
  spanProcessors: [new SimpleSpanProcessor(new AzureMonitorTraceExporter())] 
});
tracerProvider.register();

const loggerProvider = new LoggerProvider({
  resource,
  processors: [new SimpleLogRecordProcessor(new AzureMonitorLogExporter())],
});

registerInstrumentations({
    tracerProvider,
    loggerProvider,
    instrumentations: [getNodeAutoInstrumentations(), new AzureFunctionsInstrumentation()],
});

函数在 src/otel-sample/src/functions 文件夹中定义:

第一个 HTTP 函数

export async function firstHttpFunction(
  request: HttpRequest,
  context: InvocationContext
): Promise<HttpResponseInit> {
  context.log("TypeScript HTTP trigger function (first) processed a request.");

  try {
    // Call the second function
    const baseUrl = request.url.split("/api/")[0];
    const secondFunctionUrl = `${baseUrl}/api/second_http_function`;

    const response = await axios.get(secondFunctionUrl);
    const secondFunctionResult = response.data;

    const result = {
      message: "Hello from the first function!",
      second_function_response: secondFunctionResult,
    };

    return {
      status: 200,
      body: JSON.stringify(result),
      headers: { "Content-Type": "application/json" },
    };
  } catch (error) {
    return {
      status: 500,
      body: JSON.stringify({ error: "Failed to process request" }),
    };
  }
}

第二个 HTTP 函数

export async function secondHttpFunction(
  request: HttpRequest,
  context: InvocationContext
): Promise<HttpResponseInit> {
  context.log("TypeScript HTTP trigger function (second) processed a request.");

  const message = "This is the second function responding.";

  // Send a message to the Service Bus queue
  const queueMessage =
    "Message from second HTTP function to trigger ServiceBus queue processing";

  context.extraOutputs.set(serviceBusOutput, queueMessage);
  context.log("Sent message to ServiceBus queue:", queueMessage);

  return {
    status: 200,
    body: message,
  };
}

服务总线队列触发器

export async function serviceBusQueueTrigger(
  message: unknown,
  context: InvocationContext
): Promise<void> {
  context.log("TypeScript ServiceBus Queue trigger start processing a message:", message);

  // Simulate processing time
  await new Promise((resolve) => setTimeout(resolve, 5000));

  context.log("TypeScript ServiceBus Queue trigger end processing a message");
}

OpenTelemetry 配置设置在 src/otel-sample/src/index.js 中。

const { AzureFunctionsInstrumentation } = require('@azure/functions-opentelemetry-instrumentation');
const { AzureMonitorLogExporter, AzureMonitorTraceExporter } = require('@azure/monitor-opentelemetry-exporter');
const { getNodeAutoInstrumentations, getResourceDetectors } = require('@opentelemetry/auto-instrumentations-node');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { detectResources } = require('@opentelemetry/resources');
const { LoggerProvider, SimpleLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { NodeTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-node');

const resource = detectResources({ detectors: getResourceDetectors() });

const tracerProvider = new NodeTracerProvider({ 
  resource, 
  spanProcessors: [new SimpleSpanProcessor(new AzureMonitorTraceExporter())] 
});
tracerProvider.register();

const loggerProvider = new LoggerProvider({
  resource,
  processors: [new SimpleLogRecordProcessor(new AzureMonitorLogExporter())],
});

registerInstrumentations({
    tracerProvider,
    loggerProvider,
    instrumentations: [getNodeAutoInstrumentations(), new AzureFunctionsInstrumentation()],
});

函数在 src/otel-sample/src/functions 文件夹中定义:

第一个 HTTP 函数

const { app } = require("@azure/functions");
const axios = require("axios");

async function firstHttpFunction(request, context) {
  context.log("JavaScript HTTP trigger function (first) processed a request.");

  try {
    // Call the second function
    const baseUrl = request.url.split("/api/")[0];
    const secondFunctionUrl = `${baseUrl}/api/second_http_function`;

    const response = await axios.get(secondFunctionUrl);
    const secondFunctionResult = response.data;

    const result = {
      message: "Hello from the first function!",
      second_function_response: secondFunctionResult,
    };

    context.log("Successfully called second function");

    return {
      status: 200,
      body: JSON.stringify(result),
      headers: { "Content-Type": "application/json" },
    };
  } catch (error) {
    context.log("Error occurred:", error);
    return {
      status: 500,
      body: JSON.stringify({ error: "Failed to process request" }),
    };
  }
}

app.http("first_http_function", {
  methods: ["GET", "POST"],
  authLevel: "anonymous",
  handler: firstHttpFunction,
});

第二个 HTTP 函数

const { app, output } = require("@azure/functions");

const serviceBusOutput = output.serviceBusQueue({
  queueName: "%ServiceBusQueueName%",
  connection: "ServiceBusConnection",
});

async function secondHttpFunction(request, context) {
  context.log("JavaScript HTTP trigger function (second) processed a request.");

  const message = "This is the second function responding.";

  // Send a message to the Service Bus queue
  const queueMessage =
    "Message from second HTTP function to trigger ServiceBus queue processing";

  context.extraOutputs.set(serviceBusOutput, queueMessage);
  context.log("Sent message to ServiceBus queue:", queueMessage);

  return {
    status: 200,
    body: message,
  };
}

app.http("second_http_function", {
  methods: ["GET", "POST"],
  authLevel: "anonymous",
  extraOutputs: [serviceBusOutput],
  handler: secondHttpFunction,
});

服务总线队列触发器

const { app } = require("@azure/functions");

async function serviceBusQueueTrigger(message, context) {
  context.log(
    "JavaScript ServiceBus Queue trigger start processing a message:",
    message
  );

  // Simulate processing time
  await new Promise((resolve) => setTimeout(resolve, 5000));

  context.log("JavaScript ServiceBus Queue trigger end processing a message");
}

app.serviceBusQueue("servicebus_queue_trigger", {
  queueName: "%ServiceBusQueueName%",
  connection: "ServiceBusConnection",
  handler: serviceBusQueueTrigger,
});

OpenTelemetry 配置设置在 src/OTelSample/Program.cs 中。

using Azure.Monitor.OpenTelemetry.Exporter;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.OpenTelemetry;
using OpenTelemetry.Trace;

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();

builder.Logging.AddOpenTelemetry(logging =>
{
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
});

builder.Services.AddOpenTelemetry()    
    .WithTracing(tracing =>
    {
        tracing.AddHttpClientInstrumentation();
    });

builder.Services.AddOpenTelemetry().UseAzureMonitorExporter();
builder.Services.AddOpenTelemetry().UseFunctionsWorkerDefaults();

builder.Services.AddHttpClient();

builder.Build().Run();

函数在单独的类文件中定义:

第一个 HTTP 函数

public class FirstHttpTrigger
{
    private readonly ILogger<FirstHttpTrigger> _logger;
    private readonly IHttpClientFactory _httpClientFactory;

    public FirstHttpTrigger(ILogger<FirstHttpTrigger> logger, IHttpClientFactory httpClientFactory)
    {
        _logger = logger;
        _httpClientFactory = httpClientFactory;
    }

    [Function("first_http_function")]
    public async Task<IActionResult> Run(
         [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
    {
        _logger.LogInformation("first_http_function function processed a request.");

        var baseUrl = $"{req.Url.AbsoluteUri.Split("/api/")[0]}/api";
        var targetUri = $"{baseUrl}/second_http_function";

        var client = _httpClientFactory.CreateClient();
        var response = await client.GetAsync(targetUri);
        var content = await response.Content.ReadAsStringAsync();

        return new OkObjectResult($"Called second_http_function, status: {response.StatusCode}, content: {content}");
    }
}

第二个 HTTP 函数

public class SecondHttpTrigger
{
    private readonly ILogger<SecondHttpTrigger> _logger;

    public SecondHttpTrigger(ILogger<SecondHttpTrigger> logger)
    {
        _logger = logger;
    }

    [Function("second_http_function")]
    public MultiResponse Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
    {
        _logger.LogInformation("second_http_function function processed a request.");

        return new MultiResponse
        {
            Messages = new string[] { "Hello" },
            HttpResponse = req.CreateResponse(System.Net.HttpStatusCode.OK)
        };
    }
}

public class MultiResponse
{
    [ServiceBusOutput("%ServiceBusQueueName%", Connection = "ServiceBusConnection")]
    public string[]? Messages { get; set; }

    [HttpResult]
    public HttpResponseData? HttpResponse { get; set; }
}

服务总线队列触发器

public class ServiceBusQueueTrigger
{
    private readonly ILogger<ServiceBusQueueTrigger> _logger;

    public ServiceBusQueueTrigger(ILogger<ServiceBusQueueTrigger> logger)
    {
        _logger = logger;
    }

    [Function("servicebus_queue_trigger")]
    public async Task Run(
        [ServiceBusTrigger("%ServiceBusQueueName%", Connection = "ServiceBusConnection")]
        ServiceBusReceivedMessage message,
        ServiceBusMessageActions messageActions)
    {
        _logger.LogInformation("Message ID: {id}", message.MessageId);
        _logger.LogInformation("Message Body: {body}", message.Body);

        // Complete the message
        await messageActions.CompleteMessageAsync(message);
    }
}

你可以在 src/main/java/com/function 下的单独 Java 类文件中定义函数。

第一个 HTTP 函数

public class FirstHttpTrigger {

    @FunctionName("first_http_function")
    public HttpResponseMessage run(
            @HttpTrigger(
                name = "req", 
                methods = {HttpMethod.GET, HttpMethod.POST}, 
                authLevel = AuthorizationLevel.ANONYMOUS) 
            HttpRequestMessage<String> request,
            final ExecutionContext context) {

        context.getLogger().info("first_http_function function processed a request.");

        // Build base URI from the incoming request
        URI requestUri = request.getUri();
        String incomingUrl = requestUri.toString();
        String baseUrl = incomingUrl.split("/api/")[0] + "/api";
        String targetUri = baseUrl + "/second_http_function";

        try {
            var client = HttpClient.newBuilder()
                    .connectTimeout(Duration.ofSeconds(5))
                    .build();

            var requestBuilder = HttpRequest.newBuilder(URI.create(targetUri))
                    .timeout(Duration.ofSeconds(60))
                    .GET();

            // Get trace context from ExecutionContext and propagate it
            TraceContext traceContext = context.getTraceContext();
            if (traceContext != null) {
                String traceparent = traceContext.getTraceparent();
                String tracestate = traceContext.getTracestate();

                if (traceparent != null && !traceparent.isEmpty()) {
                    context.getLogger().info("Propagating traceparent: " + traceparent);
                    requestBuilder.header("traceparent", traceparent);
                }
                if (tracestate != null && !tracestate.isEmpty()) {
                    requestBuilder.header("tracestate", tracestate);
                }
            }

            var httpReq = requestBuilder.build();
            var resp = client.send(httpReq, HttpResponse.BodyHandlers.ofString());

            return request.createResponseBuilder(HttpStatus.OK)
                    .header("Content-Type", "text/plain")
                    .body("Called second_http_function, status: " + resp.statusCode() + ", content: " + resp.body())
                    .build();

        } catch (Exception e) {
            context.getLogger().severe("Call to second_http_function failed: " + e);
            return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("Failed to call second_http_function: " + e.getMessage())
                    .build();
        }
    }
}

第二个 HTTP 函数

public class SecondHttpTrigger {

    @FunctionName("second_http_function")
    public HttpResponseMessage run(
            @HttpTrigger(
                name = "req",
                methods = {HttpMethod.GET, HttpMethod.POST},
                authLevel = AuthorizationLevel.ANONYMOUS)
                HttpRequestMessage<String> request,
            @ServiceBusQueueOutput(
                name = "message",
                queueName = "%ServiceBusQueueName%",
                connection = "ServiceBusConnection")
                OutputBinding<String[]> serviceBusMessages,
            final ExecutionContext context) {

        context.getLogger().info("second_http_function function processed a request.");

        // Send message to Service Bus queue
        serviceBusMessages.setValue(new String[] { "Hello" });

        return request.createResponseBuilder(HttpStatus.OK)
                .header("Content-Type", "text/plain")
                .body("Hello from second_http_function!")
                .build();
    }
}

服务总线队列触发器

public class ServiceBusQueueTriggerFunction {

    @FunctionName("servicebus_queue_trigger")
    public void run(
        @ServiceBusQueueTrigger(
            name = "message",
            queueName = "%ServiceBusQueueName%",
            connection = "ServiceBusConnection")
            String message,
        final ExecutionContext context
    ) {
        context.getLogger().info("Message Body: " + message);
    }
}

分布式跟踪流

此体系结构创建完整的分布式跟踪方案,并具有以下行为:

  1. 第一个 HTTP 函数 接收 HTTP 请求并调用第二个 HTTP 函数
  2. 第二个 HTTP 函数 响应并向服务总线发送消息
  3. 服务总线触发器 会延迟处理消息,以模拟处理工作

OpenTelemetry 实施的关键方面:

  • OpenTelemetry 集成:该index.js文件使用 Azure Monitor 导出程序配置 OpenTelemetry,以支持跟踪和日志
  • 函数链接:第一个函数使用 axios 调用第二个函数,并自动进行跟踪传播
  • 服务总线集成:第二个函数使用输出绑定输出到服务总线,这会触发第三个函数
  • 托管标识:所有服务总线连接都使用托管标识而不是连接字符串
  • 处理模拟:服务总线触发器中的 5 秒延迟模拟消息处理工作

可在此处查看完整的模板项目。

  • OpenTelemetry 集成:该文件 host.json 支持 OpenTelemetry "telemetryMode": "OpenTelemetry"
  • 函数链接:第一个函数使用 HTTP 请求调用第二个函数,创建相关跟踪
  • 服务总线集成:第二个函数输出到服务总线,这会触发第三个函数
  • 匿名身份验证:HTTP 函数使用 auth_level=func.AuthLevel.ANONYMOUS,因此不需要函数密钥

可在此处查看完整的模板项目。

  • OpenTelemetry 集成:该index.ts文件使用 Azure Monitor 导出程序配置 OpenTelemetry,以支持跟踪和日志
  • 函数链接:第一个函数使用 axios 调用第二个函数,并自动进行跟踪传播
  • 服务总线集成:第二个函数使用输出绑定输出到服务总线,这会触发第三个函数
  • 托管标识:所有服务总线连接都使用托管标识而不是连接字符串
  • 处理模拟:服务总线触发器中的 5 秒延迟模拟消息处理工作

可在此处查看完整的模板项目。

  • OpenTelemetry 集成Program.cs 该文件使用 Azure Monitor 导出程序配置 OpenTelemetry
  • 函数链接:第一个函数使用 HttpClient 和 OpenTelemetry 检测调用第二个函数
  • 服务总线集成:第二个函数使用输出绑定输出到服务总线,这会触发第三个函数
  • 托管标识:所有服务总线连接都使用托管标识而不是连接字符串
  • .NET 8 独立辅助角色:使用最新的 Azure Functions .NET 独立辅助角色模型来提高性能和灵活性

可在此处查看完整的模板项目。

  • OpenTelemetry 集成:该文件通过 host.json"telemetryMode": "OpenTelemetry" 启用 OpenTelemetry,并将 OpenTelemetry Java 代理与部署过程打包在一起。
  • 函数链接:第一个函数使用 Java 的内置 HttpClient 和手动跟踪上下文传播调用第二个函数
  • 跟踪上下文传播TraceContext 提供 ExecutionContexttraceparent 标头,用于 W3C 跟踪上下文传播的 tracestate
  • 服务总线集成:第二个函数使用输出绑定输出到服务总线,这会触发第三个函数

可在此处查看完整的模板项目。

在本地验证函数后,可以将其发布到 Azure。

部署到 Azure 云

此项目配置为使用 azd up 命令将此项目部署到 Azure 中具有 OpenTelemetry 支持的 Flex Consumption 计划中的新函数应用。

小窍门

此项目包括一组 Bicep 文件, azd 这些文件用于创建安全部署到 Flex 消耗计划,该计划遵循最佳做法,包括托管标识连接。

  1. azd运行以下命令,在 Azure 中创建所需的 Azure 资源,并将代码项目部署到新的函数应用:

    azd up
    

    根文件夹包含 azure.yaml 所需的 azd 定义文件。

    如果尚未登录,系统会要求你使用 Azure 帐户进行身份验证。

  2. 出现提示时,请提供以下所需的部署参数:

    参数 Description
    Azure 订阅 在其中创建资源的订阅。
    Azure 位置 要在其中创建包含新 Azure 资源的资源组的 Azure 区域。 仅显示当前支持 Flex 消耗计划的区域。

    azd up 命令使用你对这些提示的回复和 Bicep 配置文件来完成这些部署任务:

    • 创建和配置这些所需的 Azure 资源(等效于 azd provision):

      • 启用 OpenTelemetry 的 Azure Functions 灵活消费计划和函数应用
      • Azure 存储(必需)和 Application Insights(推荐)
      • 用于分布式跟踪演示的服务总线命名空间和队列
      • 您的帐户的访问控制策略和角色权限
      • 使用托管标识(而不是存储的连接字符串)的服务到服务连接
    • 打包代码并将其部署到部署容器(等效于 azd deploy)。 然后,应用将启动并在已部署的包中运行。

    命令成功完成后,你会看到指向所创建资源的链接。

测试分布式跟踪

现在,可以通过调用已部署的函数并观察 Application Insights 中的遥测数据来测试 OpenTelemetry 分布式跟踪功能。

在 Azure 上调用函数

可以通过向 Azure 中的函数终结点发出 HTTP 请求来调用其 URL。 由于此模板中的 HTTP 函数配置了匿名访问,因此不需要任何函数密钥。

  1. 在本地终端或命令提示符中,运行以下命令以获取函数应用名称并构造 URL:

    APP_NAME=$(azd env get-value AZURE_FUNCTION_NAME)
    echo "Function URL: https://$APP_NAME.chinacloudsites.cn/api/first_http_function"
    

    azd env get-value 命令从本地环境中获取函数应用名称。

  2. 在浏览器中访问 URL 来测试功能:

    https://your-function-app.chinacloudsites.cn/api/first_http_function
    

    your-function-app替换为上一步中的实际函数应用名称。 此单个请求创建一个分布式跟踪,该跟踪流经所有三个函数。

在 Application Insights 中查看分布式跟踪

调用函数后,可以在 Application Insights 中观察完整的分布式跟踪:

注释

调用函数后,遥测数据可能需要几分钟才会显示在 Application Insights 中。 如果未立即看到数据,请等待几分钟并刷新视图。

  1. 在 Azure 门户中访问您的 Application Insights 资源(可以在与您的函数应用相同的资源组中找到)。

  2. 打开 应用程序映射 ,查看所有三个函数的分布式跟踪。 您应该能够看到从 HTTP 请求,通过您的函数,到达服务总线的流程。

  3. 检查 事务搜索 以查找请求,并查看完整的跟踪时间线。 在函数应用中搜索事务。

  4. 选择一个特定事务以查看其显示的端到端跟踪:

    • first_http_function进行HTTP请求
    • 对内部 HTTP 调用 second_http_function
    • 正在发送的Service Bus消息
    • servicebus_queue_trigger 正在处理来自服务总线的消息
  5. 在跟踪详细信息中,可以看到:

    • 计时信息:每个步骤花费的时间
    • 依赖项:函数之间的连接
    • 日志:与跟踪关联的应用程序日志
    • 性能指标:响应时间和吞吐量

此示例演示了使用 OpenTelemetry 集成跨多个 Azure Functions 的端到端分布式跟踪,从而完全了解应用程序的行为和性能。

重新部署代码

azd up根据需要多次运行命令,以便预配 Azure 资源并将代码更新部署到函数应用。

注释

最新的部署包始终覆盖已部署的代码文件。

你的对azd提示的初始响应和azd生成的任何环境变量都本地存储在你的命名环境中。 azd env get-values使用命令查看创建 Azure 资源时该命令使用的环境中的所有变量。

清理资源

使用完函数应用和相关资源后,请使用此命令从 Azure 中删除函数应用及其相关资源,并避免产生任何进一步的成本:

azd down --no-prompt

注释

--no-prompt 选项指示 azd 在未经你确认的情况下删除资源组。

此命令不会影响本地代码项目。