Verteiltes Tracing für Funktionen
Erfahren Sie, wie Sie das Tracing aktivieren und Funktionstraces beim Debugging mit OCI Functions anzeigen.
Wenn eine Funktion aufgerufen, aber nicht wie erwartet ausgeführt oder ausgeführt wird, müssen Sie das Problem auf einer detaillierten Ebene untersuchen. Das verteilte Tracing-Feature beobachtet die Ausführung der Funktion, während es sich durch die verschiedenen Komponenten des Systems bewegt. Sie können Standalone-Funktionen verfolgen und instrumentieren, um Ausführungs- und Performanceprobleme zu debuggen. Sie können das Funktionstracing auch verwenden, um Probleme mit vollständigen serverlosen Anwendungen zu debuggen, die mehrere Funktionen und Services umfassen, wie:
- eine Funktion, die eine andere Funktion aufruft
- eine Funktion, die andere Services aufruft, wie den Object Storage-Service
- eine Funktion, die als Backend für ein API-Gateway dient, das im API-Gateway-Service bereitgestellt ist
- eine Funktion, die als Reaktion auf ein Ereignis durch den Events-Service, den Notifications-Service oder den Connector Hub ausgelöst wird
Die OCI Functions-Tracingfunktionen werden vom Oracle Cloud Infrastructure Application Performance Monitoring-Service bereitgestellt. Mit den Features in Application Performance Monitoring (APM) können Sie Fehler und Latenzprobleme in den von Ihnen erstellten und bereitgestellten Funktionen identifizieren und beheben.
Gehen Sie im Application Performance Monitoring-Service wie folgt vor:
- Eine APM-Domain enthält die von Application Performance Monitoring überwachten Systeme. Eine APM-Domain ist eine Instanz eines Sammlers von Trace- und Span-Daten, in denen die Daten gespeichert, aggregiert, angezeigt und visualisiert werden.
- Ein Trace ist der vollständige Ablauf einer Anforderung, während sie alle Komponenten eines verteilten Systems in einem bestimmten Zeitraum durchläuft. Sie besteht aus einem gesamten Baum aus Spans, die alle mit demselben einzelnen allgemeinen Anforderungsfluss verknüpft sind.
- Ein Span ist ein Vorgang oder eine logische Arbeitseinheit mit einem Namen, einer Startzeit und einer Dauer in einem Trace. Ein Zeitraum ist ein Zeitsegment, das der Dauer einer Arbeitseinheit im gesamten Anforderungsablauf zugeordnet ist.
Mit dem Trace-Explorer von Application Performance Monitoring können Sie den gesamten Anforderungsfluss visualisieren und Trace- und Span-Details für Diagnosen untersuchen. Sie können langsame Traces und Traces mit Fehlern anzeigen und überwachen. Um Traceprobleme zu isolieren und zu identifizieren, können Sie einen Drilldown zu bestimmten Spans durchführen, z.B. Seitenladevorgänge, AJAX-Aufrufe und Serviceanfragen. Weitere Informationen zum Application Performance Monitoring-Service finden Sie in Application Performance Monitoring.
Um das Tracing für eine Funktion zu aktivieren, müssen Sie:
- Richten Sie eine Policy ein, um dem OCI Functions-Service die Berechtigung zum Zugriff auf APM-Domains zu erteilen, wenn die Policy noch nicht vorhanden ist (siehe Policy-Anweisungen zum Erteilen des Zugriffs des OCI Functions-Service und der OCI Functions-Benutzer auf Tracingressourcen).
- Richten Sie eine APM-Domain ein.
- Aktivieren Sie das Tracing für die Functions-Anwendung, und wählen Sie die von Ihnen erstellte APM-Domain aus.
- Aktivieren Sie das Tracing für eine oder mehrere Funktionen.
Wenn Sie das Tracing für eine Funktion aktivieren, generiert OCI Functions automatisch einen "Default Function Invocation Span". Der Default Span erfasst Informationen zum Ausführungskontext der Funktion, einschließlich der Gesamtzeit für die Verarbeitung der Anforderung und die Rückgabe einer Antwort an den Aufrufer. Zusätzlich zum standardmäßigen Funktionsaufrufbereich können Sie Funktionen Code hinzufügen, um benutzerdefinierte Spans zu definieren. Verwenden Sie benutzerdefinierte Spans, um mehr funktionsspezifische Informationen für das Debugging zu erfassen. Beispiel: Sie können benutzerdefinierte Spans definieren, um den Beginn und das Ende bestimmter Arbeitseinheiten zu erfassen. Beispiel: Zu den Arbeitseinheiten gehören das Abrufen des Datenbankkennworts aus dem Vault, das Öffnen einer Datenbankverbindung und das Abrufen von Datensätzen aus der Datenbank.
Dem OCI Functions-Kontext wurden vier Variablen hinzugefügt, die hilfreiche Tracinginformationen bereitstellen. Diese Variablen umfassen:
FN_APP_NAME:
Der Name der Funktionsanwendung.FN_FN_NAME:
Der Funktionsname.OCI_TRACE_COLLECTOR_URL
: Die APM-Domain-URL mit Datenschlüssel.OCI_TRACING_ENABLED:
Ist Tracing aktiviert?- Beim Abrufen aus Umgebungsvariablen wird 0 oder 1 zurückgegeben.
- Beim Abrufen aus dem Funktionskontext wird
true
oderfalse
für die verwendete Sprache zurückgegeben.
Erforderliche IAM Policy zur Aktivierung von Tracing
Bevor Sie Tracing aktivieren können, muss die Gruppe, zu der Sie gehören, berechtigt sein, auf vorhandene APM-Domains zuzugreifen oder APM-Domains zu erstellen. Darüber hinaus muss OCI Functions über die Berechtigung zum Zugriff auf APM-Domains verfügen. Siehe Policy-Anweisungen, um dem OCI Functions-Service und OCI Functions-Benutzern Zugriff auf Tracingressourcen zu erteilen.
Funktionstraces mit der Konsole aktivieren und anzeigen
Um das Tracing zu aktivieren und Funktionstraces für den Oracle Cloud Infrastructure Application Performance Monitoring-(APM-)Service anzuzeigen, sind einige Schritte erforderlich. Aktivieren Sie zunächst das Tracing für die Anwendung, die diese Funktion enthält. Aktivieren Sie dann das Tracing für eine oder mehrere Funktionen. Anschließend können Sie Funktionstraces im APM-Trace-Explorer anzeigen.
Tracing mit der Konsole aktivieren
Gehen Sie folgendermaßen vor, um das Tracing zu aktivieren.
- Wählen Sie auf der Listenseite Anwendungen die Anwendung mit Funktionen aus, für die Sie das Tracing aktivieren möchten. Wenn Sie Hilfe beim Suchen der Listenseite oder der Anwendung benötigen, finden Sie weitere Informationen unter Anwendungen auflisten.
- So aktivieren Sie das Tracing für die Anwendung:
- Wählen Sie unter Ressourcen die Option Traces aus.
- Wählen Sie die Option Trace aktiviert, und geben Sie Folgendes an:
- Compartment: Das Compartment, in dem das Trace erstellt werden soll. Standardmäßig ist dies das aktuelle Compartment.
- APM-Domain: Die APM-Domain (im Application Performance Monitoring-Service definiert), in der das Trace erstellt werden soll. Um eine vorhandene APM-Domain zu verwenden, wählen Sie eine vorhandene APM-Domain aus der Liste aus. Sie können auch eine neue APM-Domain erstellen, indem Sie APM-Domain auswählen. Weitere Informationen zu APM-Domains finden Sie unter Erste Schritte mit Application Performance Monitoring. Hinweis
Die APM-Domain muss sowohl öffentliche als auch private Datenschlüssel haben, damit das Funktionstracing funktioniert. Wenn die Schlüssel nicht vorhanden sind, können Sie sie über die Konsolenoberfläche erstellen.
- Wählen Sie Trace aktivieren aus, um das Tracing für die Anwendung zu aktivieren.
Nachdem Sie das Tracing für die Functions-Anwendung aktiviert haben, können Sie jetzt das Tracing für eine oder mehrere Funktionen in der Anwendung aktivieren.
- So aktivieren Sie Tracing für bestimmte Funktionen in der Anwendung:
- Wählen Sie unter Ressourcen die Option Funktionen aus.
-
Wählen Sie neben einer oder mehreren Funktionen, für die Sie das Tracing aktivieren möchten, die Option Trace aktivieren.
Die Option Trace aktivieren wird nur angezeigt, wenn Sie das Tracing zuvor für die Anwendung aktiviert haben. Folgendes ist zu beachten:
- Wenn die Option Trace aktivieren nicht angezeigt wird, müssen Sie das Tracing für die Anwendung aktivieren. Wenn Sie das Tracing für die Anwendung noch nicht aktiviert haben, lesen Sie den vorherigen Schritt.
- Wenn Sie das Tracing für die Anwendung zuvor aktiviert, aber später deaktiviert haben, wird der Link Anwendungstracing aktivieren angezeigt. Wählen Sie den Link Anwendungstracing aktivieren, um Tracing für die Anwendung erneut zu aktivieren (siehe vorherigen Schritt). Nachdem Sie das Tracing für die Anwendung erneut aktiviert haben, können Sie das Tracing für bestimmte Funktionen aktivieren.
Wenn Sie das Tracing für die Anwendung und eine oder mehrere Funktionen aktiviert haben, können Sie Funktionstraces anzeigen.
Funktionstraces mit der Konsole anzeigen
So zeigen Sie Traces für Funktionen an, für die Tracing aktiviert ist:
- Wählen Sie auf der Listenseite Anwendungen die Anwendung mit den Funktionen aus, für die Sie Traces anzeigen möchten. Wenn Sie Hilfe beim Suchen der Listenseite oder der Anwendung benötigen, finden Sie weitere Informationen unter Anwendungen auflisten.
- So zeigen Sie Traces für Funktionen an:
- So zeigen Sie Traces für alle Funktionen an, für die Tracing in der Anwendung aktiviert ist:
- Wählen Sie unter Ressourcen die Option Traces aus.
- Wählen Sie den Namen des Traces aus. Hinweis
Ein Tracename wird nur angezeigt, wenn Sie das Tracing für die Anwendung bereits aktiviert haben.
- So zeigen Sie das Trace für eine bestimmte Funktion an, für die Tracing aktiviert ist:
- Wählen Sie unter Ressourcen die Option Funktionen aus.
- Wählen Sie neben der Funktion das Menü
aus, und wählen Sie Trace anzeigen aus.
Hinweis
Die Option Trace anzeigen wird nur angezeigt, wenn Sie das Tracing für die Funktion bereits aktiviert haben.
Die Traces für die ausgewählten Funktionen werden im APM-Trace-Explorer angezeigt. Standardmäßig wird ein Trace für den Standardaufrufbereich der Funktion und für die Funktion definierte benutzerdefinierte Spans angezeigt.
- So zeigen Sie Traces für alle Funktionen an, für die Tracing in der Anwendung aktiviert ist:
- Im APM-Trace-Explorer:
- Wählen Sie ein Trace aus, um die Spans für dieses Trace anzuzeigen.
- Wählen Sie einen Span aus, um die für diesen Span erfassten Details anzuzeigen.
Weitere Informationen zur Verwendung des APM-Trace-Explorers finden Sie unter Trace-Explorer verwenden.
Ketten von Funktionen verfolgen
Standardmäßig stellt Funktionstracing ein Trace für einen gesamten Funktionsaufruf bereit. Bei modernen Cloud-Anwendungen müssen Sie jedoch häufig Funktionsaufrufe verketten. Mit dem OCI Functions-Tracing kann die Ausführung einer Funktion, die von einer anderen Funktion aufgerufen wird, verfolgt werden. Mit dieser Fähigkeit können Sie die Ausführung jeder Funktion in einer Aufrufkette in einem einzigen Spansbaum im APM-Trace-Explorer prüfen.
Um eine Funktionskette zu verfolgen, müssen Sie die X-B3-Header X-B3-TraceId
, X-B3-SpanId
, X-B3-ParentSpanId
und X-B3-Sampled
in der Funktionsaufrufanforderung aus Ihrem Funktionscode propagieren.
Nachdem die Funktion ausgeführt wurde, werden die Tracedaten aus Ihren Funktionen erfasst und im APM Trace Explorer verfügbar. Weitere Informationen zur Verwendung des APM-Trace-Explorers finden Sie unter Trace-Explorer verwenden.
Im Folgenden finden Sie ein Beispiel dafür, wie Sie eine Funktionskette verfolgen können. Wenn Sie dieses Beispiel ausprobieren möchten, müssen Sie zwei Beispielfunktionen erstellen. Führen Sie die folgenden Schritte aus, um Ihre Funktionen einzurichten.
- Erstellen Sie die Tracing-Python-Funktion:
fn init --runtime python <your-function-name-1>
- Erstellen Sie Ihre "Hello World!" Python-Funktion:
fn init --runtime python <your-function-name-2>
- Stellen Sie beide Funktionen bereit:
fn -v deploy --app <app-name>
- Rufen Sie die zweite Funktions-OCID ab, und rufen Sie den Endpunkt auf:
fn inspect function your-app-name your-function-name-2
- Erstellen Sie eine JSON-Datei, um die erforderlichen Informationen an die erste Funktion zu übergeben. Ihre
test.json
-Datei könnte beispielsweise wie folgt aussehen:{ "function_ocid": "ocid1.fnfunc.oc1.iad.aaaaaaaaxxxxxxxxxxx", "function_endpoint": "https://xxxxxxxxx.us-ashburn-1.functions.oci.oraclecloud.com", "function_body": "", "__comment": "Alternatively, you can set function_body to { \"name\": \"Oracle\" }" }
- Wenn die erste Funktion aufgerufen wird, können Sie die Informationen der zweiten Funktion mit
test.json
übergeben:fn invoke <app-name> <your-function-name-1> < test.json
Sie können nun die erste Funktion mit den erforderlichen Code-Updates aktualisieren.
Packages konfigurieren
Aktualisieren Sie die Datei requirements.txt
so, dass sie die folgenden Packages enthält:
fdk
oci
Speichern Sie die Datei.
Aktualisieren Sie den Funktionscode, um die X-B3-Header zu propagieren
Die Python-Funktion ruft die Funktion handler
auf und übergibt die JSON-Informationen aus dem Aufrufbefehl. Die Funktion handler
wird zur Einfachheit in mehrere kleine Blöcke unterteilt. Die vollständige Quelldatei finden Sie unten in diesem Abschnitt.
JSON-Daten laden
In diesem ersten Teil werden die JSON-Daten aus dem Funktionsaufruf geladen.
import io
import json
import logging
import oci
from fdk import response
def handler(ctx, data: io.BytesIO=None):
app_name = ctx.AppName()
func_name = ctx.FnName()
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler")
try:
body = json.loads(data.getvalue())
function_endpoint = body.get("function_endpoint")
function_ocid = body.get("function_ocid")
function_body = body.get("function_body")
except (Exception) as ex:
print('ERROR: Missing key in payload', ex, flush=True)
raise
Aufrufclient erstellen und Headerinformationen erfassen
Erstellen Sie den Functions-Aufrufclient mit den OCI-Python-SDK- und Functions-Ressourcen-Principals. Rufen Sie dann tracing_context
ab, und extrahieren Sie die erforderlichen Informationen, um die HTTP-Header zu erstellen.
signer = oci.auth.signers.get_resource_principals_signer()
client = oci.functions.FunctionsInvokeClient(config={}, signer=signer, service_endpoint=function_endpoint)
#
# Zipkin X-B3- header propagation
#
tracing_context = ctx.TracingContext()
trace_id = tracing_context.trace_id()
span_id = tracing_context.span_id()
parent_span_id = tracing_context.parent_span_id()
is_sampled = tracing_context.is_sampled()
X-B3-Header propagieren
Mit dem OCI-Python-SDK können Sie benutzerdefinierte Header festlegen. Mit dieser Technik übergeben Sie die X-B3-Header an den zweiten Funktionsaufruf. Headerinformationen werden für trace_id
, span_id
, parent_span_id
und is_sampled
übergeben. Schließlich wird die zweite Funktion mit client
aufgerufen, und die Antwort wird an die Antwort dieser Funktion übergeben.
# if tracing is enabled, is_sampled will be true in the tracing context
if is_sampled:
# To propagate headers in the OCI SDK in the request to the next function,
# add the X-B3- headers in the request. This header will be included in ALL
# subsequent calls made.
if trace_id is not None:
client.base_client.session.headers['X-B3-TraceId'] = trace_id
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | trace_id: " + trace_id)
if span_id is not None:
client.base_client.session.headers['X-B3-SpanId'] = span_id
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | span_id: " + span_id)
if parent_span_id is not None:
client.base_client.session.headers['X-B3-ParentSpanId'] = parent_span_id
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | parent_span_id: " + parent_span_id)
client.base_client.session.headers['X-B3-Sampled'] = str(int(is_sampled))
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | is_sampled: " + str(int(is_sampled)))
else:
# function.trace is DISABLED
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | function tracing is DISABLED")
resp = client.invoke_function(function_id=function_ocid, invoke_function_body=function_body)
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | Response: " + resp.data.text)
return response.Response(
ctx,
response_data=resp.data.text,
headers={"Content-Type": "application/json"}
)
Hier ist der vollständige Quellcode für die Python-Beispielfunktion.
#
# oci-invoke-function-python version 2.0.
#
# Copyright (c) 2021 Oracle, Inc.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
#
import io
import json
import logging
import oci
from fdk import response
def handler(ctx, data: io.BytesIO=None):
app_name = ctx.AppName()
func_name = ctx.FnName()
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler")
try:
body = json.loads(data.getvalue())
function_endpoint = body.get("function_endpoint")
function_ocid = body.get("function_ocid")
function_body = body.get("function_body")
except (Exception) as ex:
print('ERROR: Missing key in payload', ex, flush=True)
raise
signer = oci.auth.signers.get_resource_principals_signer()
client = oci.functions.FunctionsInvokeClient(config={}, signer=signer, service_endpoint=function_endpoint)
#
# Zipkin X-B3- header propagation
#
tracing_context = ctx.TracingContext()
trace_id = tracing_context.trace_id()
span_id = tracing_context.span_id()
parent_span_id = tracing_context.parent_span_id()
is_sampled = tracing_context.is_sampled()
# if tracing is enabled, is_sampled will be true in the tracing context
if is_sampled:
# To propagate headers in the OCI SDK in the request to the next function,
# add the X-B3- headers in the request. This header will be included in ALL
# subsequent calls made.
if trace_id is not None:
client.base_client.session.headers['X-B3-TraceId'] = trace_id
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | trace_id: " + trace_id)
if span_id is not None:
client.base_client.session.headers['X-B3-SpanId'] = span_id
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | span_id: " + span_id)
if parent_span_id is not None:
client.base_client.session.headers['X-B3-ParentSpanId'] = parent_span_id
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | parent_span_id: " + parent_span_id)
client.base_client.session.headers['X-B3-Sampled'] = str(int(is_sampled))
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | is_sampled: " + str(int(is_sampled)))
else:
# function.trace is DISABLED
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | function tracing is DISABLED")
resp = client.invoke_function(function_id=function_ocid, invoke_function_body=function_body)
logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | Response: " + resp.data.text)
return response.Response(
ctx,
response_data=resp.data.text,
headers={"Content-Type": "application/json"}
)
Benutzerdefinierte Spans zu Funktionen hinzufügen
Wenn das Funktionstracing aktiviert ist, stellt der Default-Funktionsaufruf-Span einen Trace für den gesamten Funktionsaufruf bereit. Der Standard-Span kann gute Informationen liefern, aber wenn Sie Ihren Code untersuchen, möchten Sie vielleicht tiefer graben. Benutzerdefinierte Spans werden direkt zu Ihrem Code hinzugefügt und ermöglichen es Ihnen, Spans für eine Methode oder einen Codeblock zu definieren. Die resultierenden Daten liefern ein besseres Bild Ihrer Funktion, während sie ausgeführt werden.
Bevor Sie benutzerdefinierte Spans verwenden können, müssen Sie das Tracing für Ihre Anwendungen und Funktionen mit dem Oracle Cloud Infrastructure Application Performance Monitoring-(APM-)Service aktivieren. So richten Sie das Tracing ein:
- Richten Sie eine Policy ein, um dem OCI Functions-Service die Berechtigung zum Zugriff auf APM-Domains zu erteilen, wenn die Policy noch nicht vorhanden ist (siehe Policy-Anweisungen zum Erteilen des Zugriffs des OCI Functions-Service und der OCI Functions-Benutzer auf Tracingressourcen).
- Richten Sie eine APM-Domain ein.
- Aktivieren Sie das Tracing für die Functions-Anwendung, und wählen Sie die von Ihnen erstellte APM-Domain aus.
- Aktivieren Sie das Tracing für eine oder mehrere Funktionen.
Diese Schritte wurden bereits behandelt. Für benutzerdefinierte Spans sind jedoch ein paar weitere Dinge erforderlich:
- Wählen Sie eine verteilte Tracing-Clientbibliothek, z.B. Zipkin.
- Fügen Sie Ihren Funktionsabhängigkeiten Client-Librarys hinzu.
- Verwenden Sie im Funktionscode die Kontextvariable der Funktion
OCI_TRACING_ENABLED
, um zu prüfen, ob Tracing aktiviert ist. - Verwenden Sie in Ihrem Funktionscode die Kontextvariable der Funktion
OCI_TRACE_COLLECTOR_URL
, um Ihre benutzerdefinierten Spans an Ihre APM-Domain zu senden. - Fügen Sie Instrumentierung zu Ihrem Funktionscode hinzu.
Um benutzerdefinierte Spans verwenden zu können, benötigen Sie die folgenden Mindestversionen der Fn-Projekt-FDKs:
- Java-DATEISYSTEM: 1.0.129
- Python-FDK: 0.1.22
- Knoten-FDK: 0.0.20
Im Folgenden finden Sie ein Beispiel für die Verwendung von Zipkin zum Hinzufügen benutzerdefinierter Spans zu Ihrer Java-Funktion. Wenn Sie dieses Beispiel ausprobieren möchten, können Sie eine Java-Funktion "Hallo Welt!" erstellen und benutzerdefinierten Span-Code hinzufügen. So erstellen Sie eine Beispielfunktion:
- Erstellen Sie eine Java-Funktion:
fn init --runtime java apm-fn-java
- Entfernen Sie der Einfachheit halber das Verzeichnis
src/test
.
Maven konfigurieren
Fügen Sie die folgenden Abhängigkeiten zum Abschnitt <dependencies> der Maven-Datei pom.xml
hinzu.
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-urlconnection</artifactId>
<version>2.16.3</version>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
<version>2.16.3</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave</artifactId>
<version>5.13.3</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-core</artifactId>
<version>4.13.6</version>
</dependency>
Speichern Sie die Datei.
Die HandleRequest-Methode
Beobachtungen zur Methode folgen dem Quellcode handleRequest
.
package com.example.fn;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.*;
import brave.sampler.Sampler;
import com.fnproject.fn.api.tracing.TracingContext;
import com.github.kristofa.brave.IdConversion;
import zipkin2.reporter.Sender;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import zipkin2.reporter.urlconnection.URLConnectionSender;
public class HelloFunction {
Sender sender;
AsyncZipkinSpanHandler zipkinSpanHandler;
Tracing tracing;
Tracer tracer;
String apmUrl;
TraceContext traceContext;
public String handleRequest(String input, TracingContext tracingContext) {
try {
intializeZipkin(tracingContext);
// Start a new trace or a span within an existing trace representing an operation
Span span = tracer.newChild(traceContext).name("MainHandle").start();
System.out.println("Inside Java Hello World function");
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
method1();
method2();
method3();
} catch (RuntimeException | Error e) {
span.error(e); // Unless you handle exceptions, you might not know the operation failed!
throw e;
} finally {
span.finish(); // note the scope is independent of the span. Always finish a span.
tracing.close();
zipkinSpanHandler.flush();
}
} catch (Exception e) {
return e.getMessage();
}
return "Hello, AppName " + tracingContext.getAppName() + " :: fnName " + tracingContext.getFunctionName();
}
- Das Objekt
TracingContext tracingConext
übergibt alle APM-bezogenen Informationen, die zum Herstellen von Verbindungen zum APM-Service erforderlich sind. - Die Methode
intializeZipkin
wird aufgerufen, dietracingContext
aktualisiert und eintracer
-Objekt erstellt, mit dem benutzerdefinierte Spans eingerichtet werden. - Eine
span
wird für den übergeordneten benutzerdefinierten Span erstellt. Anschließend werden drei Methoden im Geltungsbereich des übergeordneten Span aufgerufen. - Beachten Sie, dass im Block
finally
alle Tracing-Objekte geschlossen sind.
Die initializeZipkin-Methode
Beobachtungen zur Methode intializeZipkin
folgen dem Quellcode.
public void intializeZipkin(TracingContext tracingContext) throws Exception {
System.out.println("Initializing the variables");
apmUrl = tracingContext.getTraceCollectorURL();
sender = URLConnectionSender.create(apmUrl);
zipkinSpanHandler = AsyncZipkinSpanHandler.create(sender);
tracing = Tracing.newBuilder()
.localServiceName(tracingContext.getServiceName())
.sampler(Sampler.NEVER_SAMPLE)
.addSpanHandler(zipkinSpanHandler)
.build();
tracer = tracing.tracer();
tracing.setNoop(!tracingContext.isTracingEnabled());
traceContext = TraceContext.newBuilder()
.traceId(IdConversion.convertToLong(tracingContext.getTraceId()))
.spanId(IdConversion.convertToLong(tracingContext.getSpanId()))
.sampled(tracingContext.isSampled()).build();
}
- Die
traceContext
wird übergeben, um alle Objekte zu erstellen, mit denen benutzerdefinierte Spans erstellt werden. - Die
apmURL
wird von der MethodegetTraceCollectorURL()
abgerufen. Die URL ist der Endpunkt der APM-Domain und wird zum Erstellen des Objektstracer
verwendet, das die benutzerdefinierten Spans erstellt. - Ein Builder erstellt mit
zipkinSpanHandler
und dem Servicenamen eintracer
-Objekt. Diesestracer
-Objekt wird zum Erstellen benutzerdefinierter Spans verwendet.
Benutzerdefinierte Spans erstellen
Wenn das Objekt tracer
initialisiert ist, können benutzerdefinierte Spans erstellt werden.
public void method1() {
System.out.println("Inside Method1 function");
TraceContext traceContext = tracing.currentTraceContext().get();
Span span = tracer.newChild(traceContext).name("Method1").start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
span.finish();
}
}
- Die Methode
method1
erstellt einen benutzerdefinierten Span namens "Method1".
Hier ist der vollständige Quellcode für die Java-Tracing-Beispielfunktion.
package com.example.fn;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.*;
import brave.sampler.Sampler;
import com.fnproject.fn.api.tracing.TracingContext;
import com.github.kristofa.brave.IdConversion;
import zipkin2.reporter.Sender;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import zipkin2.reporter.urlconnection.URLConnectionSender;
public class HelloFunction {
Sender sender;
AsyncZipkinSpanHandler zipkinSpanHandler;
Tracing tracing;
Tracer tracer;
String apmUrl;
TraceContext traceContext;
public void intializeZipkin(TracingContext tracingContext) throws Exception {
System.out.println("Initializing the variables");
apmUrl = tracingContext.getTraceCollectorURL();
sender = URLConnectionSender.create(apmUrl);
zipkinSpanHandler = AsyncZipkinSpanHandler.create(sender);
tracing = Tracing.newBuilder()
.localServiceName(tracingContext.getServiceName())
.sampler(Sampler.NEVER_SAMPLE)
.addSpanHandler(zipkinSpanHandler)
.build();
tracer = tracing.tracer();
tracing.setNoop(!tracingContext.isTracingEnabled());
traceContext = TraceContext.newBuilder()
.traceId(IdConversion.convertToLong(tracingContext.getTraceId()))
.spanId(IdConversion.convertToLong(tracingContext.getSpanId()))
.sampled(tracingContext.isSampled()).build();
}
public String handleRequest(String input, TracingContext tracingContext) {
try {
intializeZipkin(tracingContext);
// Start a new trace or a span within an existing trace representing an operation
Span span = tracer.newChild(traceContext).name("MainHandle").start();
System.out.println("Inside Java Hello World function");
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
method1();
method2();
method3();
} catch (RuntimeException | Error e) {
span.error(e); // Unless you handle exceptions, you might not know the operation failed!
throw e;
} finally {
span.finish(); // note the scope is independent of the span. Always finish a span.
tracing.close();
zipkinSpanHandler.flush();
}
} catch (Exception e) {
return e.getMessage();
}
return "Hello, AppName " + tracingContext.getAppName() + " :: fnName " + tracingContext.getFunctionName();
}
public void method1() {
System.out.println("Inside Method1 function");
TraceContext traceContext = tracing.currentTraceContext().get();
Span span = tracer.newChild(traceContext).name("Method1").start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
span.finish();
}
}
public void method2() {
System.out.println("Inside Method2 function");
TraceContext traceContext = tracing.currentTraceContext().get();
Span span = tracer.newChild(traceContext).name("Method2").start();
try {
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
span.finish();
}
}
public void method3() {
System.out.println("Inside Method3 function");
TraceContext traceContext = tracing.currentTraceContext().get();
Span span = tracer.newChild(traceContext).name("Method3").start();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
span.finish();
}
}
}
Im Folgenden finden Sie ein Beispiel für die Verwendung von Zipkin zum Hinzufügen benutzerdefinierter Spans zu Ihrer Python-Funktion. Wenn Sie dieses Beispiel ausprobieren möchten, können Sie eine Python-Funktion "Hallo Welt!" erstellen und benutzerdefinierten Span-Code hinzufügen. So erstellen Sie eine Beispielfunktion:
- Erstellen Sie eine Python-Funktion:
fn init --runtime python apm-fn-python
Packages konfigurieren
Aktualisieren Sie die Datei requirements.txt
so, dass sie die folgenden Packages enthält:
fdk
requests
py_zipkin
Speichern Sie die Datei.
Handler-Klasse und übergeordneten benutzerdefinierten Span erstellen
Die Python-Funktion ruft die Funktion handler
auf und übergibt sie im Funktionskontext, um benutzerdefinierte Spans zu erstellen.
def handler(ctx, data: io.BytesIO = None):
tracing_context = ctx.TracingContext()
with zipkin_span(
service_name=tracing_context.service_name(),
span_name="Customer Code",
transport_handler=(
lambda encoded_span: transport_handler(
encoded_span, tracing_context
)
),
zipkin_attrs=tracing_context.zipkin_attrs(),
encoding=Encoding.V2_JSON,
binary_annotations=tracing_context.annotations()
):
name = "World"
try:
body = json.loads(data.getvalue())
name = body.get("name")
except (Exception, ValueError) as ex:
logging.getLogger().info('error parsing json payload: ' + str(ex))
logging.getLogger().info("Inside Python Hello World function")
time.sleep(0.005)
example(ctx)
return response.Response(
ctx, response_data=json.dumps(
{"message": "Hello {0}".format(name)}),
headers={"Content-Type": "application/json"}
)
- Die
tracing_context
wird aus dem Funktionskontext übergeben und enthält alle Informationen, die zum Erstellen und Konfigurieren benutzerdefinierter Spans erforderlich sind.Hinweis
Wenn das Tracing nicht aktiviert ist, ist der Tracingkontext ein leeres Objekt. Bei einem leeren Tracing-Kontext wird das Flagis_sampled
aufNone
gesetzt, undpy_zipkin
gibt keine Spans aus. - Mit der
with zipkin_span
-Anweisung werden Spans erstellt.- Die Informationen in
tracing_context
werden verwendet, um dieservice_name
abzurufen,transport_handler
aufzurufen undzipking_attrs
festzulegen. - Ein benutzerdefinierter Span-Name wird nur durch Festlegen von
span_name
angegeben. - Für Zipkin erforderliche Tracingattribute werden aus dem Tracingkontext abgerufen:
tracing_context.zipkin_attrs()
.
- Die Informationen in
- Mit dem Custom Span Setup läuft der Hauptblock Boilerplate "Hello World!" Code. Mit der einzigen Ausnahme wird die Funktion
example
aufgerufen.
Die Funktion transport_handler
Die Funktion transport_handler
kommuniziert mit der APM-Domain mit Nachrichten zur Span-Ausführung.
# transport handler, needed by py_zipkin
def transport_handler(encoded_span, tracing_context):
return requests.post(
tracing_context.trace_collector_url(),
data=encoded_span,
headers={"Content-Type": "application/json"},
)
- Die
trace_collector_url
wird vom Funktionskontext zurückgegeben. Diese URL stellt den Kommunikationsendpunkt für Ihre benutzerdefinierten Spans zur APM-Domain bereit.
Benutzerdefinierte Span in Beispielfunktion erstellen
Die Beispielfunktion zeigt die Erstellung eines benutzerdefinierten Span.
def example(ctx):
with zipkin_span(
service_name=ctx.TracingContext().service_name(),
span_name="Get ADB Password from OCI Vault",
binary_annotations=ctx.TracingContext().annotations()
) as example_span_context:
try:
logging.getLogger().debug("Get ADB Password from OCI Vault")
time.sleep(0.005)
# throwing an exception to show how to add error messages to spans
raise Exception('Request failed')
except (Exception, ValueError) as error:
example_span_context.update_binary_annotations(
{"Error": True, "errorMessage": str(error)}
)
else:
FakeResponse = namedtuple("FakeResponse", "status, message")
fakeResponse = FakeResponse(200, "OK")
# how to update the span dimensions/annotations
example_span_context.update_binary_annotations(
{
"responseCode": fakeResponse.status,
"responseMessage": fakeResponse.message
}
)
- Die
with zipkin_span
-Anweisung wird verwendet, um den benutzerdefinierten Span zu identifizieren und ihm einen Namen zu geben. - Der Block
example_span_context
löst eine Ausnahme aus und gibt eine Fehlermeldung zurück.
Hier ist der vollständige Quellcode für die Beispiel-Python-Tracingfunktion.
import io
import json
import logging
from fdk import response
import requests
import time
from py_zipkin import Encoding
from py_zipkin.zipkin import zipkin_span
from collections import namedtuple
# transport handler, needed by py_zipkin
def transport_handler(encoded_span, tracing_context):
return requests.post(
tracing_context.trace_collector_url(),
data=encoded_span,
headers={"Content-Type": "application/json"},
)
def handler(ctx, data: io.BytesIO = None):
tracing_context = ctx.TracingContext()
with zipkin_span(
service_name=tracing_context.service_name(),
span_name="Customer Code",
transport_handler=(
lambda encoded_span: transport_handler(
encoded_span, tracing_context
)
),
zipkin_attrs=tracing_context.zipkin_attrs(),
encoding=Encoding.V2_JSON,
binary_annotations=tracing_context.annotations()
):
name = "World"
try:
body = json.loads(data.getvalue())
name = body.get("name")
except (Exception, ValueError) as ex:
logging.getLogger().info('error parsing json payload: ' + str(ex))
logging.getLogger().info("Inside Python Hello World function")
time.sleep(0.005)
example(ctx)
return response.Response(
ctx, response_data=json.dumps(
{"message": "Hello {0}".format(name)}),
headers={"Content-Type": "application/json"}
)
def example(ctx):
with zipkin_span(
service_name=ctx.TracingContext().service_name(),
span_name="Get ADB Password from OCI Vault",
binary_annotations=ctx.TracingContext().annotations()
) as example_span_context:
try:
logging.getLogger().debug("Get ADB Password from OCI Vault")
time.sleep(0.005)
# throwing an exception to show how to add error messages to spans
raise Exception('Request failed')
except (Exception, ValueError) as error:
example_span_context.update_binary_annotations(
{"Error": True, "errorMessage": str(error)}
)
else:
FakeResponse = namedtuple("FakeResponse", "status, message")
fakeResponse = FakeResponse(200, "OK")
# how to update the span dimensions/annotations
example_span_context.update_binary_annotations(
{
"responseCode": fakeResponse.status,
"responseMessage": fakeResponse.message
}
)
Im Folgenden finden Sie ein Beispiel für die Verwendung von Zipkin zum Hinzufügen benutzerdefinierter Spans zu Ihrer Node.js-Funktion. Wenn Sie dieses Beispiel ausprobieren möchten, können Sie eine Node-Funktion "Hallo Welt!" erstellen und benutzerdefinierten Span-Code hinzufügen. So erstellen Sie eine Beispielfunktion:
- Knotenfunktion erstellen:
fn init --runtime node apm-fn-node
Knotenabhängigkeiten konfigurieren
Aktualisieren Sie die Datei package.json
so, dass sie die folgenden Packages enthält:
{
"name": "apm-tracing-node-fdk-simple-trace-final",
"version": "1.0.0",
"description": "Example APM tracing function",
"main": "func.js",
"author": "",
"license": "Apache-2.0",
"dependencies": {
"@fnproject/fdk": ">=0.0.13",
"node-fetch": "^2.6.1",
"zipkin": "^0.22.0",
"zipkin-transport-http": "^0.22.0"
}
}
Speichern Sie die Datei.
Aktualisierungs-Handle-Methode
Wichtige Beobachtungen zur Methode fdk.handle
folgen dem Quellcode.
// ZipkinJS core components.
const {
ExplicitContext,
Annotation,
Tracer,
TraceId,
BatchRecorder,
jsonEncoder,
sampler,
option
} = require('zipkin');
// An HTTP transport for dispatching Zipkin traces.
const {HttpLogger} = require('zipkin-transport-http');
fdk.handle(async function(input, ctx){
tracer = createOCITracer(ctx);
var result;
// Start a new 'scoped' server handling span.
await tracer.scoped(async function () {
// Fetch some resource
result = await tracer.local('fetchResource', () => {
return fetchResource();
});
// Perform some processing
result = await tracer.local('processResource', () => {
return someComputation(result);
});
// Update some resource
result = await tracer.local('updateResource', () => {
return updateResource(result);
});
await flush();
});
return result;
})
- Die
tracer
wird erstellt und dann zum Erstellen eines übergeordneten benutzerdefinierten Span verwendet. Anschließend werden untergeordnete Spans für die FunktionenfetchResource
,processResource
undupdateResource
erstellt.
Funktion createOCITracer prüfen
Wichtige Beobachtungen zur Funktion folgen dem Quellcode.
/**
* Creates a basic Zipkin Tracer using values from context of the function
* invocation.
*
* @param {*} ctx The function invocation context.
* @returns A configured Tracer for automatically tracing calls.
*/
function createOCITracer (ctx) {
// An OCI APM configured Tracer
//
const tracingCxt = ctx.tracingContext
const tracer = new Tracer({
ctxImpl: new ExplicitContext(),
recorder: new BatchRecorder({
logger: new HttpLogger({
// The configured OCI APM endpoint is available in the function
// invocation context.
endpoint: tracingCxt.traceCollectorUrl,
jsonEncoder: jsonEncoder.JSON_V2
})
}),
// APM Dimensions that should be included in all traces can be configured
// directly on Tracer.
defaultTags: createOCITags(ctx),
// A custom sampling strategy can be defined.
sampler: createOCISampler(ctx),
localServiceName: tracingCxt.serviceName,
supportsJoin: true,
traceId128Bit: true
})
// The initial function invocation trace identifiers can be added directly.
// If this is not defined a default TraceId is created.
const traceId = createOCITraceId(tracer, ctx)
tracer.setId(traceId)
return tracer
}
- Der Funktionskontext (
ctx
) wird an diese Funktion übergeben, die Informationen bereitstellt, die für die Verbindung mit der APM-Domain erforderlich sind. Wenn Sie die Funktionsaufrufe befolgen, können Sie sehen, wie die Tracing-IDs und -Felder erstellt werden.
Hier ist der vollständige Quellcode für die Sample Node Tracing-Funktion.
const fdk = require('@fnproject/fdk')
// ZipkinJS core components.
const {
ExplicitContext,
Tracer,
TraceId,
BatchRecorder,
jsonEncoder,
sampler,
option
} = require('zipkin')
// An HTTP transport for dispatching Zipkin traces.
const { HttpLogger } = require('zipkin-transport-http')
fdk.handle(async function (input, ctx) {
var tracer = createOCITracer(ctx)
var result
// Start a new 'scoped' server handling span.
await tracer.scoped(async function () {
// Fetch some resource
result = await tracer.local('fetchResource', () => {
return fetchResource()
})
// Perform some processing
result = await tracer.local('processResource', () => {
return someComputation(result)
})
// Update some resource
result = await tracer.local('updateResource', () => {
return updateResource(result)
})
await flush()
})
return result
})
// ----------------------------------------------------------------------------
// App Simulation Functions
//
/**
* Simulate fetching some required resource. This could be another OCI service
* or an external call.
*
* @returns A Promise with the success or failure of the operation.
*/
function fetchResource () {
return simulate(1000, { fetchResource: 'OK' })
}
/**
* Simulate some work. This could be another OCI service.
*
* @returns A Promise with the success or failure of the operation.
*/
async function someComputation (toReturn) {
var i
for (i = 0; i < 5; i++) {
await simulate(1000)
}
toReturn.processResource = 'OK'
return toReturn
}
/**
* Simulate updating some resource. This could be another OCI service or an
* external call.
*
* @returns A Promise with the success or failure of the operation.
*/
async function updateResource (toReturn) {
await simulate(500)
toReturn.updateResource = 'OK'
return toReturn
}
/**
* A helper function to simulate an operation that takes a specified amount of time.
*
* @param {*} ms The simulated time for the activity in milliseconds.
* @returns A promise that resolves when the simulated activity finishes.
*/
function simulate (ms, result) {
return new Promise(resolve => setTimeout(resolve, ms, result))
}
/**
* Functions service may freeze or terminate the container on completion.
* This function gives extra time to allow the runtime to flush any pending traces.
* See: https://github.com/openzipkin/zipkin-js/issues/507
*
* @returns A Promise to await on.
*/
function flush () {
return new Promise(resolve => setTimeout(resolve, 1000))
}
// ----------------------------------------------------------------------------
// OpenZipkin ZipkinJS Utility Functions
//
/**
* Creates a basic Zipkin Tracer using values from context of the function
* invocation.
*
* @param {*} ctx The function invocation context.
* @returns A configured Tracer for automatically tracing calls.
*/
function createOCITracer (ctx) {
// An OCI APM configured Tracer
//
const tracingCxt = ctx.tracingContext
const tracer = new Tracer({
ctxImpl: new ExplicitContext(),
recorder: new BatchRecorder({
logger: new HttpLogger({
// The configured OCI APM endpoint is available in the function
// invocation context.
endpoint: tracingCxt.traceCollectorUrl,
jsonEncoder: jsonEncoder.JSON_V2
})
}),
// APM Dimensions that should be included in all traces can be configured
// directly on Tracer.
defaultTags: createOCITags(ctx),
// A custom sampling strategy can be defined.
sampler: createOCISampler(ctx),
localServiceName: tracingCxt.serviceName,
supportsJoin: true,
traceId128Bit: true
})
// The initial function invocation trace identifiers can be added directly.
// If this is not defined a default TraceId is created.
const traceId = createOCITraceId(tracer, ctx)
tracer.setId(traceId)
return tracer
}
/**
* A ZipkinJS 'TraceId' can be created directly from the function invocation
* context.
*
* @param {*} ctx The function invocation context.
* @returns A ZipkinJS 'TraceId' created from the invocation context.
*/
function createOCITraceId (tracer, ctx) {
const tracingCxt = ctx.tracingContext
if (tracingCxt.traceId && tracingCxt.spanId) {
return new TraceId({
traceId: tracingCxt.traceId,
spanId: tracingCxt.spanId,
sampled: new option.Some(tracingCxt.sampled),
debug: new option.Some(tracingCxt.debug),
shared: false
})
} else {
return tracer.createRootId(
new option.Some(tracingCxt.sampled),
new option.Some(tracingCxt.debug)
)
}
}
/**
* A ZipkinJS 'TraceId' can be crated directly from the function invocation
* context.
*
* This configurations will automatically add the function meta-data as APM
* dimensions to each trace. Function environment variable and other dimensions
* could also be added.
*
* @param {*} ctx The function invocation context.
* @returns A map of key-value pairs, that will be added as APM
* dimensions to the traces.
*/
function createOCITags (ctx) {
return {
appID: ctx.appID,
appName: ctx.appName,
fnID: ctx.fnID,
fnName: ctx.fnName
}
}
/**
* A ZipkinJS 'Sampler' can be created directly from the function invocation
* context.
*
* This configuration will only create a trace if the function is configured
* for tracing.
*
* @param {*} ctx The function invocation context.
* @returns A ZipkinJS 'TraceId' created from the invocation context.
*/
function createOCISampler (ctx) {
return new sampler.Sampler((traceId) => ctx.tracingContext.isEnabled)
}
API verwenden
Informationen zur Verwendung der API und zu Signieranforderungen finden Sie unter REST-API-Dokumentation und Sicherheitszugangsdaten. Informationen zu SDKs finden Sie unter SDKs und die CLI.
Mit diesen API-Vorgängen können Sie das Tracing für Anwendungen und die darin enthaltenen Funktionen aktivieren und deaktivieren: