本文展示了 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。 若要完成快速入門,請在文章頂端選取其中一種支援的語言。
先決條件
具有有效訂閱的 Azure 帳戶。 免費建立帳戶。
初始化專案
使用 azd init 指令從包含 OpenTelemetry 分散式追蹤的範本建立本地 Azure Functions 程式碼專案。
在您的本機終端機或命令提示字元中,於空白資料夾中執行此
azd init命令:azd init --template functions-quickstart-python-azd-otel -e flexquickstart-otel此命令會從範本存放庫提取專案檔,並初始化目前資料夾中的專案。
-e旗標會設定目前環境的名稱。 在azd中,該環境會為您的應用程式維護一個唯一的部署內容,而您可以定義多個部署內容。 環境名稱也會出現在你在 Azure 中建立的資源群組名稱中。
在您的本機終端機或命令提示字元中,於空白資料夾中執行此
azd init命令:azd init --template functions-quickstart-typescript-azd-otel -e flexquickstart-otel此命令會從範本存放庫提取專案檔,並初始化目前資料夾中的專案。
-e旗標會設定目前環境的名稱。 在azd中,該環境會為您的應用程式維護一個唯一的部署內容,而您可以定義多個部署內容。 環境名稱也會出現在你在 Azure 中建立的資源群組名稱中。
在您的本機終端機或命令提示字元中,於空白資料夾中執行此
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);
}
}
分散式追蹤流程
此架構建立完整的分散式追蹤場景,行為如下:
- 第一個 HTTP 函式 接收 HTTP 請求並呼叫第二個 HTTP 函式
- 第二個 HTTP 函式 回應並向服務匯流排發送訊息
- 服務匯流排觸發器會以延遲的方式處理訊息以模擬處理工作。
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 用來建立符合最佳做法之彈性使用量方案的安全部署,包括受控識別連線。
執行此命令,讓
azd在 Azure 中建立必要的 Azure 資源,並將程式碼專案部署至新的函數應用程式:azd up根資料夾包含
azd所需的azure.yaml定義檔。如果你還沒登入,系統會要求你用 Azure 帳號進行認證。
出現提示時,請提供這些必要的部署參數:
參數 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 函式是匿名存取,因此不需要函式金鑰。
在你的本地終端機或命令提示字元中,執行這個指令以取得函式應用程式名稱並建立 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命令會從本機環境取得函數應用程式名稱。在瀏覽器中透過以下網址測試此功能:
https://your-function-app.azurewebsites.net/api/first_http_function請將
your-function-app替換為您在上一個步驟中使用的實際函數應用程式名稱。 這個單一要求會建立一個分散式追蹤,並傳遞於這三個函式之間。
在應用洞察中查看分散式追蹤
呼叫該函式後,您可以在 Application Insights 中觀察完整的分散式追蹤:
備註
觸發你的功能後,遙測資料可能需要幾分鐘才會顯示在 Application Insights 中。 要是沒有立刻看到資料,請等幾分鐘再刷新視圖。
請前往 Azure 入口網站的 Application Insights 資源(你可以在與函式應用程式相同的資源群組中找到它)。
打開 應用程式映射,查看橫跨三個功能的分佈跟踪。 您應該可以看到從 HTTP 要求經過函式再到 Azure 服務匯流排的整個流程。
請查看 交易搜尋 以找到您的請求並查看完整的追蹤時間軸。 在您的功能應用程式中搜尋交易。
選取特定交易以查看端到端的追蹤,顯示以下項目:
- 向
first_http_function的 HTTP 請求 - 內部 HTTP 呼叫
second_http_function - 正在傳送 Azure 服務匯流排訊息
-
servicebus_queue_trigger正在處理來自 Azure 服務匯流排的訊息
- 向
在追蹤細節中,你可以看到:
- 時間資訊:每步所需時間
- 相依關係:函式之間的連結
- 日誌:與追蹤相關的應用程式日誌
- 效能指標:回應時間與吞吐量
此範例展示了跨多個 Azure 函式的端對端分散式追蹤,並結合 OpenTelemetry,提供對應用程式行為與效能的完整可視化。
重新部署程式碼
執行 azd up 這個指令可以讓你根據需要多次配置 Azure 資源,並將程式碼更新部署到你的函式應用程式。
備註
最新的部署套件總是會覆寫已部署的程式碼檔案。
您對 azd 提示的初始回應,以及 azd 所產生的任何環境變數都會儲存在本機的具名環境中。 使用這個 azd env get-values 指令來檢視該指令在建立 Azure 資源時所使用的環境中所有變數。
清理資源
完成函式應用程式及相關資源後,請使用此指令將函式應用程式及其相關資源從 Azure 刪除,避免產生額外費用:
azd down --no-prompt
備註
--no-prompt 選項會指示 azd 刪除您的資源群組,而不需要您確認。
此命令不會影響您的本機程式碼專案。