共用方式為


教學:使用 OpenTelemetry 分散式追蹤監控 Azure 函數

本文展示了 Azure Function 中的 OpenTelemetry 支援,透過整合的 Application Insights 與 OpenTelemetry 支援,實現跨多個函式呼叫的分散式追蹤。 為了幫助你開始,Azure Developer CLI (azd) 範本會用來建立你的程式碼專案以及執行應用程式的 Azure 部署。

在這個教學中,你會使用這個 azd 工具來:

  • 從範本初始化一個支援 OpenTelemetry 的專案。
  • 檢視啟用 OpenTelemetry 整合的程式碼。
  • 在本機執行並驗證您已啟用 OpenTelemetry 的應用程式。
  • 在 Azure 中建立函式應用程式及相關資源。
  • 將你的程式碼專案部署到 Azure 的函式應用程式。
  • 在 Application Insights 中驗證分散式追蹤。

此範本所建立的 Azure 必要資源遵循目前安全且可擴展的功能應用部署最佳實務。 同一個 azd 指令也會將你的程式碼專案部署到你在 Azure 裡的新函式應用程式。

預設情況下,Flex Consumption 方案採用按 使用量付費 的計費模式,這表示完成此快速啟動會在你的 Azure 帳戶中產生幾美元或更少的費用。

這很重要

本文目前僅支援 C#、Python 與 TypeScript。 若要完成快速入門,請在文章頂端選取其中一種支援的語言。

先決條件

初始化專案

使用 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-dotnet-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" 允許函式呼叫間分散式追蹤。

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 套件提供 OpenTelemetry 與 Application Insights 的整合。

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 套件提供 OpenTelemetry 與 Application Insights 的整合。

.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 儀器整合,用於分散式追蹤。

函數實作

函數 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/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);
    }
}

分散式追蹤流程

此架構建立完整的分散式追蹤場景,行為如下:

  1. 第一個 HTTP 函式 接收 HTTP 請求並呼叫第二個 HTTP 函式
  2. 第二個 HTTP 函式 回應並向服務匯流排發送訊息
  3. 服務匯流排觸發器會以延遲的方式處理訊息以模擬處理工作。

OpenTelemetry 實作的關鍵面向:

  • OpenTelemetry 整合: host.json 檔案可使用 "telemetryMode": "OpenTelemetry" 啟用 OpenTelemetry
  • 函式鏈結:第一個函式使用 HTTP 請求呼叫第二個函式,產生相關的追蹤
  • 服務匯流排整合:第二個函式輸出給服務匯流排,服務匯流排觸發第三個函數
  • 匿名認證:HTTP 函式使用 auth_level=func.AuthLevel.ANONYMOUS,因此不需要函式金鑰

您可以在這裡檢閱完整的範本專案。

  • OpenTelemetry 整合: index.ts 檔案會使用 Azure 監視器匯出器來設定 OpenTelemetry 以進行追蹤與記錄
  • 函式鏈:第一個函數使用公理呼叫第二個函數,並自動傳播軌跡
  • 服務匯流排整合:第二個函式透過輸出綁定輸出至服務匯流排,觸發第三個函式
  • 受管理身份:所有服務匯流排連線皆使用管理身份而非連線字串
  • 處理模擬:服務匯流排觸發器的 5 秒延遲模擬訊息處理工作

您可以在這裡檢閱完整的範本專案。

  • OpenTelemetry 整合Program.cs 文件使用 Azure Monitor 匯出器來配置 OpenTelemetry
  • 函式鏈結:第一個函式使用 HttpClient 搭配 OpenTelemetry 儀器呼叫第二個函式
  • 服務匯流排整合:第二個函式透過輸出綁定輸出至服務匯流排,觸發第三個函式
  • 受管理身份:所有服務匯流排連線皆使用管理身份而非連線字串
  • .NET 8 獨立工作者:使用最新的 Azure Functions .NET 孤立工作者模型,提升效能與彈性

您可以在這裡檢閱完整的範本專案。

在本機驗證函式之後,是時候將它們發佈至 Azure 了。

部署至 Azure

此專案配置為使用azd up命令,將此專案部署到支援 OpenTelemetry 的 Azure Flex Consumption 計劃中的新函數應用程式。

小提示

此專案包含一組 Bicep 檔案,讓 azd 用來建立符合最佳做法之彈性使用量方案的安全部署,包括受控識別連線。

  1. 執行此命令,讓 azd 在 Azure 中建立必要的 Azure 資源,並將程式碼專案部署至新的函數應用程式:

    azd up
    

    根資料夾包含 azd 所需的 azure.yaml 定義檔。

    如果你還沒登入,系統會要求你用 Azure 帳號進行認證。

  2. 出現提示時,請提供這些必要的部署參數:

    參數 Description
    Azure 訂用帳戶 建立您資源所在位置的訂用帳戶。
    Azure 位置 要在其中建立包含新 Azure 資源之資源群組所在的 Azure 區域。 只會顯示目前支援彈性使用量方案的區域。

    azd up 命令會透過 Bicep 組態檔使用您對這些提示的回應,以完成這些部署工作:

    • 建立和設定這些必要的 Azure 資源 (相當於 azd provision):

      • Azure Functions 彈性使用量方案及已啟用 OpenTelemetry 的函數應用程式
      • Azure 儲存體 (必要) 和 Application Insights (建議)
      • 分散式追蹤示範的 Azure 服務匯流排命名空間及佇列
      • 存取帳戶的原則和角色
      • 使用受控識別的服務對服務連線 (而不是預存的連接字串)
    • 將程式碼封裝並部署至部署容器 (相當於 azd deploy)。 然後,應用程式會啟動並在已部署的套件中執行。

    命令成功完成之後,您會看到所建立資源的連結。

分散式追蹤技術測試

現在你可以透過呼叫已部署的函式並在 Application Insights 中觀察遙測,來測試 OpenTelemetry 的分散式追蹤功能。

在 Azure 上叫用函式

你可以透過對函式端點的 URL 發送 HTTP 請求,來呼叫 Azure 中的函式端點。 由於此範本中的 HTTP 函式是匿名存取,因此不需要函式金鑰。

  1. 在你的本地終端機或命令提示字元中,執行這個指令以取得函式應用程式名稱並建立 URL:

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

    azd env get-value 命令會從本機環境取得函數應用程式名稱。

  2. 在瀏覽器中透過以下網址測試此功能:

    https://your-function-app.azurewebsites.net/api/first_http_function
    

    請將 your-function-app 替換為您在上一個步驟中使用的實際函數應用程式名稱。 這個單一要求會建立一個分散式追蹤,並傳遞於這三個函式之間。

在應用洞察中查看分散式追蹤

呼叫該函式後,您可以在 Application Insights 中觀察完整的分散式追蹤:

備註

觸發你的功能後,遙測資料可能需要幾分鐘才會顯示在 Application Insights 中。 要是沒有立刻看到資料,請等幾分鐘再刷新視圖。

  1. 請前往 Azure 入口網站的 Application Insights 資源(你可以在與函式應用程式相同的資源群組中找到它)。

  2. 打開 應用程式映射,查看橫跨三個功能的分佈跟踪。 您應該可以看到從 HTTP 要求經過函式再到 Azure 服務匯流排的整個流程。

  3. 請查看 交易搜尋 以找到您的請求並查看完整的追蹤時間軸。 在您的功能應用程式中搜尋交易。

  4. 選取特定交易以查看端到端的追蹤,顯示以下項目:

    • first_http_function 的 HTTP 請求
    • 內部 HTTP 呼叫 second_http_function
    • 正在傳送 Azure 服務匯流排訊息
    • servicebus_queue_trigger 正在處理來自 Azure 服務匯流排的訊息
  5. 在追蹤細節中,你可以看到:

    • 時間資訊:每步所需時間
    • 相依關係:函式之間的連結
    • 日誌:與追蹤相關的應用程式日誌
    • 效能指標:回應時間與吞吐量

此範例展示了跨多個 Azure 函式的端對端分散式追蹤,並結合 OpenTelemetry,提供對應用程式行為與效能的完整可視化。

重新部署程式碼

執行 azd up 這個指令可以讓你根據需要多次配置 Azure 資源,並將程式碼更新部署到你的函式應用程式。

備註

最新的部署套件總是會覆寫已部署的程式碼檔案。

您對 azd 提示的初始回應,以及 azd 所產生的任何環境變數都會儲存在本機的具名環境中。 使用這個 azd env get-values 指令來檢視該指令在建立 Azure 資源時所使用的環境中所有變數。

清理資源

完成函式應用程式及相關資源後,請使用此指令將函式應用程式及其相關資源從 Azure 刪除,避免產生額外費用:

azd down --no-prompt

備註

--no-prompt 選項會指示 azd 刪除您的資源群組,而不需要您確認。

此命令不會影響您的本機程式碼專案。