Trace distribuito per le funzioni
Scopri come abilitare il trace e visualizzare i trace delle funzioni durante il debug con OCI Functions.
Quando una funzione viene richiamata ma non viene eseguita o non viene eseguita come previsto, è necessario analizzare il problema a livello dettagliato. La funzione di trace distribuito osserva l'esecuzione della funzione mentre si sposta attraverso i diversi componenti del sistema. È possibile tracciare e strumentare le funzioni standalone per eseguire il debug dei problemi di esecuzione e prestazioni. È inoltre possibile utilizzare il trace delle funzioni per eseguire il debug dei problemi con applicazioni serverless complete che includono più funzioni e servizi, ad esempio:
- una funzione che chiama un'altra funzione
- una funzione che chiama altri servizi, ad esempio il servizio di storage degli oggetti
- una funzione che funge da backend per un gateway API distribuito nel servizio gateway API
- una funzione attivata in risposta a un evento dal servizio Eventi, dal servizio Notifiche o dall'hub connettore
Le funzionalità di trace di OCI Functions sono fornite dal servizio Oracle Cloud Infrastructure Application Performance Monitoring. Le funzioni di Application Performance Monitoring (APM) consentono di identificare e risolvere gli errori e i problemi di latenza nelle funzioni create e distribuite.
Nel servizio Application Performance Monitoring:
- Un dominio APM contiene i sistemi monitorati da Application Performance Monitoring. Un dominio APM è un'istanza di un collector di dati di trace e di intervallo che memorizza, aggrega, visualizza e visualizza i dati.
- Una traccia è il flusso completo di una richiesta che attraversa tutti i componenti di un sistema distribuito in un determinato periodo di tempo. È costituito da un intero albero di intervalli tutti correlati allo stesso singolo flusso di richieste complessivo.
- Un intervallo è un'operazione o un'unità logica di lavoro con un nome, un'ora di inizio e una durata all'interno di una traccia. Un intervallo è un segmento temporale associato alla durata di un'unità di lavoro all'interno del flusso di richieste complessivo.
Application Performance Monitoring Trace Explorer consente di visualizzare l'intero flusso di richieste ed esplorare i dettagli di trace e intervallo per la diagnostica. È possibile visualizzare e monitorare tracce lente con errori. Per isolare e identificare i problemi di trace, è possibile eseguire il drill-down in intervalli specifici, ad esempio caricamenti di pagine, chiamate AJAX e richieste di servizio. Per ulteriori informazioni sul servizio Application Performance Monitoring, vedere Application Performance Monitoring.
Per abilitare il trace per una funzione, è necessario effettuare le operazioni riportate di seguito.
- Impostare un criterio per concedere al servizio OCI Functions l'autorizzazione per accedere ai domini APM, se il criterio non esiste già (vedere Istruzioni dei criteri per concedere all'OCI Functions Service e agli utenti OCI Functions Access to Tracing Resources).
- Impostare un dominio APM.
- Abilitare il trace per l'applicazione Functions e selezionare il dominio APM creato.
- Abilita il trace per una o più funzioni.
Quando si abilita il trace per una funzione, OCI Functions genera automaticamente un "intervallo di richiamo della funzione predefinito". L'intervallo predefinito acquisisce informazioni sul contesto di esecuzione della funzione, incluso il tempo complessivo impiegato per elaborare la richiesta e restituire una risposta al chiamante. Oltre all'intervallo di richiamo della funzione predefinito, è possibile aggiungere codice alle funzioni per definire intervalli personalizzati. Utilizza gli intervalli personalizzati per acquisire più informazioni specifiche sulla funzione e facilitare il debug. Ad esempio, è possibile definire intervalli personalizzati per acquisire l'inizio e la fine di unità di lavoro specifiche. Ad esempio, le unità di lavoro potrebbero includere il recupero della password del database dal Vault, l'apertura di una connessione al database e il recupero dei record dal database.
Al contesto OCI Functions sono state aggiunte quattro variabili che forniscono utili informazioni di trace. Di seguito sono riportate le variabili indicate di seguito.
FN_APP_NAME:
Il nome dell'applicazione della funzione.FN_FN_NAME:
Il nome della funzione.OCI_TRACE_COLLECTOR_URL
: URL del dominio APM con chiave dati.OCI_TRACING_ENABLED:
Il trace è abilitato?- Quando viene recuperato dalle variabili di ambiente, restituisce 0 o 1.
- Quando viene recuperato dal contesto della funzione, restituisce
true
ofalse
in base alla lingua utilizzata.
Criteri IAM necessari per abilitare il trace
Per poter abilitare il trace, è necessario che il gruppo a cui si appartiene disponga dell'autorizzazione per accedere ai domini APM esistenti o per creare domini APM. Inoltre, OCI Functions deve disporre dell'autorizzazione per accedere ai domini APM. Vedere Istruzioni dei criteri per concedere agli utenti di OCI Functions Service e OCI Functions l'accesso alle risorse di trace.
Utilizzo della console per abilitare il trace e visualizzare i trace delle funzioni
Sono necessari alcuni passi per abilitare il trace e visualizzare i trace delle funzioni per il servizio APM (Oracle Cloud Infrastructure Application Performance Monitoring). In primo luogo, abilitare il trace per l'applicazione che contiene la funzione. Abilitare quindi il trace per una o più funzioni. È quindi possibile visualizzare i trace delle funzioni in Trace Explorer APM.
Utilizzo della console per abilitare il trace
Per abilitare il trace, seguire questa procedura.
- Nella pagina di elenco Applicazioni, selezionare l'applicazione con le funzioni per le quali si desidera abilitare il trace. Per informazioni su come trovare la pagina di elenco o l'applicazione, vedere Elenco di applicazioni.
- Selezionare la scheda Monitoraggio e andare alla sezione Tracce.
- Per abilitare il trace per l'applicazione:
- Dal menu
- Compartimento: il compartimento in cui creare il trace. Per impostazione predefinita, il compartimento corrente.
- Dominio APM: il dominio APM (definito nel servizio Application Performance Monitoring) in cui creare il trace. Per utilizzare un dominio APM esistente, selezionare un dominio APM esistente dalla lista. In alternativa, per creare un nuovo dominio APM, selezionare Dominio APM. Per ulteriori informazioni sui domini APM, vedere Introduzione a Application Performance Monitoring. Nota
Per garantire il corretto funzionamento del trace delle funzioni, il dominio APM deve disporre sia di chiavi dati pubbliche che di chiavi dati private. Se le chiavi non esistono, è possibile crearle tramite l'interfaccia della console.
, selezionare Configura e specificare: - Selezionare Abilita trace per abilitare il trace per l'applicazione.
Dopo aver abilitato il trace per l'applicazione Functions, è possibile abilitare il trace per una o più funzioni nell'applicazione.
- Dal menu
- Per abilitare il trace per funzioni specifiche nell'applicazione:
- Selezionare la scheda Funzioni.
- Selezionare l'opzione Abilita trace dal menu Azioni (tre punti) per la funzione o le funzioni per le quali si desidera abilitare il trace.
L'opzione Abilita trace viene visualizzata solo se in precedenza è stato abilitato il trace per l'applicazione. Tenere presente quanto riportato di seguito.
- Se l'opzione Abilita trace non è visualizzata, è necessario abilitare il trace per l'applicazione. Se non è stato ancora abilitato il trace per l'applicazione, vedere il passo precedente.
- Se in precedenza è stato abilitato il trace per l'applicazione ma successivamente è stato disabilitato, viene visualizzato un collegamento Abilita trace applicazione. Selezionare il collegamento Abilita trace applicazione per riabilitare il trace per l'applicazione (vedere il passo precedente). Dopo aver riabilitato il trace per l'applicazione, è possibile abilitare il trace per funzioni specifiche.
Dopo aver abilitato il trace per l'applicazione e una o più funzioni, è possibile visualizzare i trace delle funzioni.
Utilizzo della console per visualizzare i trace delle funzioni
Per visualizzare i trace per le funzioni per le quali è abilitato il trace, effettuare le operazioni riportate di seguito.
- Nella pagina di elenco Applicazioni selezionare l'applicazione contenente le funzioni per le quali si desidera visualizzare i trace. Per informazioni su come trovare la pagina di elenco o l'applicazione, vedere Elenco di applicazioni.
- Per visualizzare le tracce delle funzioni:
- Per visualizzare i trace per tutte le funzioni per le quali è abilitato il trace nell'applicazione, effettuare le operazioni riportate di seguito.
- Selezionare la scheda Monitoraggio e andare alla sezione Tracce.
- Selezionare il nome del trace. Nota
Un nome traccia viene visualizzato solo se è già stato abilitato il trace per l'applicazione.
- Per visualizzare il trace per una funzione specifica per cui è abilitato il trace, effettuare le operazioni riportate di seguito.
- Selezionare la scheda Funzioni.
- Selezionare l'opzione Visualizza trace dal menu Azioni (tre punti) per la funzione per la quale si desidera visualizzare il trace.Nota
L'opzione Visualizza traccia viene visualizzata solo se è già stato abilitato il trace per la funzione.
I trace per le funzioni selezionate vengono visualizzati in Trace Explorer APM. Per impostazione predefinita, viene visualizzata una traccia per l'intervallo di richiamo della funzione predefinito e per qualsiasi intervallo personalizzato definito per la funzione.
- Per visualizzare i trace per tutte le funzioni per le quali è abilitato il trace nell'applicazione, effettuare le operazioni riportate di seguito.
- In Trace Explorer APM:
- Selezionare un trace per visualizzare gli intervalli per tale trace.
- Selezionare un intervallo per visualizzare i dettagli acquisiti per tale intervallo.
Per ulteriori informazioni sull'uso di Trace Explorer APM, vedere Usa Trace Explorer.
Tracciamento di una catena di funzioni
Per impostazione predefinita, il trace delle funzioni fornisce una traccia per un intero richiamo delle funzioni. Tuttavia, spesso con le moderne applicazioni cloud, è necessario concatenare i richiami delle funzioni. Il trace di OCI Functions offre la possibilità di tracciare l'esecuzione di una funzione richiamata da un'altra funzione. Questa possibilità consente di esaminare l'esecuzione di ciascuna funzione in una catena di chiamate in un singolo albero di intervalli in APM Trace Explorer.
Per tracciare una catena di funzioni, è necessario propagare le intestazioni X-B3 X-B3-TraceId
, X-B3-SpanId
, X-B3-ParentSpanId
e X-B3-Sampled
nella richiesta di richiamo della funzione dal codice funzione.
Dopo l'esecuzione della funzione, i dati di trace delle funzioni vengono raccolti e disponibili in APM Trace Explorer. Per ulteriori informazioni sull'uso di Trace Explorer APM, vedere Usa Trace Explorer.
Ecco un esempio di come è possibile tracciare una catena di funzioni. Per provare questo esempio, è necessario creare due funzioni di esempio. Per impostare le funzioni, attenersi alla procedura riportata di seguito.
- Creare la funzione Python di trace:
fn init --runtime python <your-function-name-1>
- Crea il tuo "Ciao Mondo!" Funzione Python:
fn init --runtime python <your-function-name-2>
- Distribuire entrambe le funzioni:
fn -v deploy --app <app-name>
- Recupera l'OCID della seconda funzione e richiama l'endpoint:
fn inspect function your-app-name your-function-name-2
- Creare un file JSON per passare le informazioni richieste alla prima funzione. Ad esempio, il file
test.json
potrebbe avere l'aspetto seguente:{ "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\" }" }
- Quando viene richiamata la prima funzione, è possibile passare le informazioni sulla seconda funzione utilizzando
test.json
:fn invoke <app-name> <your-function-name-1> < test.json
Ora è possibile aggiornare la prima funzione con gli aggiornamenti di codice richiesti.
Configura package
Aggiornare il file requirements.txt
in modo che includa i seguenti pacchetti:
fdk
oci
Salvare il file.
Aggiornare il codice funzione per propagare le intestazioni X-B3
La funzione Python chiama la funzione handler
e passa le informazioni JSON dal comando di richiamo. La funzione handler
è suddivisa in diversi piccoli blocchi per semplicità. Il file di origine completo viene fornito nella parte inferiore di questa sezione.
Caricare i dati JSON
In questa prima parte, i dati JSON vengono caricati dal richiamo della funzione.
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
Crea client di richiamo e raccoglie informazioni intestazione
Creare il client di richiamo Functions utilizzando i principal delle risorse SDK e Functions Python OCI. Quindi, recuperare il file tracing_context
ed estrarre le informazioni necessarie per creare le intestazioni HTTP.
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()
Propaga le intestazioni X-B3
L'SDK Python OCI ti consente di impostare intestazioni personalizzate. Utilizzare questa tecnica per passare le intestazioni X-B3 al secondo richiamo della funzione. Le informazioni sull'intestazione vengono passate per trace_id
, span_id
, parent_span_id
e is_sampled
. Infine, la seconda funzione viene richiamata con client
e la risposta viene passata alla risposta di questa funzione.
# 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"}
)
Ecco il codice sorgente completo per la funzione Python di esempio.
#
# 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"}
)
Aggiunta di intervalli personalizzati alle funzioni
Con la funzione di trace abilitata, l'intervallo di richiamo della funzione predefinito fornisce una traccia per l'intero richiamo della funzione. L'intervallo predefinito può fornire buone informazioni, ma durante l'analisi del codice potresti voler scavare più a fondo. Gli intervalli personalizzati vengono aggiunti direttamente al codice e consentono di definire gli intervalli per un metodo o un blocco di codice. I dati risultanti forniscono un quadro migliore della funzione in esecuzione.
Prima di poter utilizzare gli intervalli personalizzati, è necessario abilitare il trace per l'applicazione e le funzioni utilizzando il servizio APM (Oracle Cloud Infrastructure Application Performance Monitoring). Per impostare il trace, è necessario:
- Impostare un criterio per concedere al servizio OCI Functions l'autorizzazione per accedere ai domini APM, se il criterio non esiste già (vedere Istruzioni dei criteri per concedere all'OCI Functions Service e agli utenti OCI Functions Access to Tracing Resources).
- Impostare un dominio APM.
- Abilitare il trace per l'applicazione Functions e selezionare il dominio APM creato.
- Abilita il trace per una o più funzioni.
Questi passi sono già stati affrontati. Tuttavia, sono necessarie un paio di altre cose per gli intervalli personalizzati:
- Selezionare una libreria client di trace distribuita, ad esempio Zipkin.
- Aggiungere librerie client alle dipendenze delle funzioni.
- Nel codice funzione, utilizzare la variabile di contesto della funzione
OCI_TRACING_ENABLED
per verificare se il trace è abilitato. - Nel codice funzione utilizzare la variabile di contesto della funzione
OCI_TRACE_COLLECTOR_URL
per inviare gli intervalli personalizzati al dominio APM. - Aggiungere la strumentazione al codice funzione.
Per utilizzare gli intervalli personalizzati, è necessario disporre delle seguenti versioni minime degli FDK del progetto Fn:
- Java FDK: 1.0.129
- FDK Python: 0.1.22
- FDK nodo: 0.0.20
Ecco un esempio di come utilizzare Zipkin per aggiungere intervalli personalizzati alla funzione Java. Se si desidera provare questo esempio, è possibile creare una funzione Java "Hello World!" e aggiungere codice di intervallo personalizzato. Per creare una funzione di esempio:
- Creare una funzione Java:
fn init --runtime java apm-fn-java
- Per semplicità, rimuovere la directory
src/test
.
Configura Maven
Aggiungere le seguenti dipendenze alla sezione <dependencies> del file pom.xml
Maven.
<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>
Salvare il file.
Il metodo HandleRequest
Le osservazioni sul metodo seguono il codice sorgente 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();
}
- L'oggetto
TracingContext tracingConext
viene trasmesso in tutte le informazioni correlate ad APM necessarie per effettuare connessioni al servizio APM. - Viene chiamato il metodo
intializeZipkin
che aggiornatracingContext
e crea un oggettotracer
utilizzato per impostare gli intervalli personalizzati. - Viene creato un valore
span
per l'intervallo personalizzato padre. Quindi tre metodi vengono chiamati nell'ambito dell'intervallo padre. - Si noti che nel blocco
finally
tutti gli oggetti di trace vengono chiusi.
Il metodo initializeZipkin
Le osservazioni sul metodo intializeZipkin
seguono il codice sorgente.
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();
}
- Il comando
traceContext
viene passato per creare tutti gli oggetti utilizzati per creare intervalli personalizzati. - Il valore
apmURL
viene recuperato dal metodogetTraceCollectorURL()
. L'URL è l'endpoint del dominio APM e viene utilizzato per creare l'oggettotracer
che crea gli intervalli personalizzati. - Un builder utilizza
zipkinSpanHandler
e il nome del servizio per creare un oggettotracer
. Questo oggettotracer
viene utilizzato per creare intervalli personalizzati.
Creazione di intervalli personalizzati
Con l'oggetto tracer
inizializzato, è possibile creare intervalli personalizzati.
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();
}
}
- Il metodo
method1
crea un intervallo personalizzato denominato "Method1".
Di seguito è riportato il codice sorgente completo per la funzione di trace Java di esempio.
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();
}
}
}
Ecco un esempio di come utilizzare Zipkin per aggiungere intervalli personalizzati alla funzione Python. Se si desidera provare questo esempio, è possibile creare una funzione Python "Hello World!" e aggiungere codice di intervallo personalizzato. Per creare una funzione di esempio:
- Creare una funzione Python:
fn init --runtime python apm-fn-python
Configura package
Aggiornare il file requirements.txt
in modo che includa i seguenti pacchetti:
fdk
requests
py_zipkin
Salvare il file.
Creazione della classe handler e dell'intervallo personalizzato padre
La funzione Python chiama la funzione handler
e passa nel contesto della funzione per creare intervalli personalizzati.
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"}
)
- Il file
tracing_context
viene passato dal contesto della funzione e contiene tutte le informazioni necessarie per creare e configurare gli intervalli personalizzati.Nota
Se il trace non è abilitato, il contesto di trace è un oggetto vuoto. Con un contesto di trace vuoto, il flagis_sampled
è impostato suNone
epy_zipkin
non emette intervalli. - L'istruzione
with zipkin_span
viene utilizzata per creare gli intervalli.- Le informazioni in
tracing_context
vengono utilizzate per ottenereservice_name
, chiamare iltransport_handler
e impostare ilzipking_attrs
. - Un nome di intervallo personalizzato viene specificato solo impostando
span_name
. - Gli attributi di trace richiesti per Zipkin vengono recuperati dal contesto di trace:
tracing_context.zipkin_attrs()
.
- Le informazioni in
- Con l'impostazione dell'intervallo personalizzato, il blocco principale esegue il codice "Hello World!" Con l'unica eccezione, una chiamata alla funzione
example
.
La funzione transport_handler
La funzione transport_handler
comunica con il dominio APM con i messaggi relativi all'esecuzione dell'intervallo.
# 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"},
)
- Il valore
trace_collector_url
viene restituito dal contesto della funzione. Questo URL fornisce l'endpoint di comunicazione per gli intervalli personalizzati al dominio APM.
Creazione di un intervallo personalizzato nella funzione di esempio
La funzione di esempio illustra la creazione di un intervallo personalizzato.
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
}
)
- L'istruzione
with zipkin_span
viene utilizzata per identificare l'intervallo personalizzato e assegnargli un nome. - Il blocco
example_span_context
genera un'eccezione e restituisce un messaggio di errore.
Ecco il codice sorgente completo per la funzione di trace Python di esempio.
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
}
)
Ecco un esempio di come utilizzare Zipkin per aggiungere intervalli personalizzati alla funzione Node.js. Se si desidera provare questo esempio, è possibile creare una funzione Node "Hello World!" e aggiungere codice di intervallo personalizzato. Per creare una funzione di esempio:
- Creare una funzione Node:
fn init --runtime node apm-fn-node
Configura dipendenze nodo
Aggiornare il file package.json
in modo che includa i seguenti pacchetti:
{
"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"
}
}
Salvare il file.
Aggiorna metodo gestione
Le osservazioni chiave sul metodo fdk.handle
seguono il codice sorgente.
// 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;
})
- Il file
tracer
viene creato e quindi utilizzato per creare un intervallo personalizzato padre. Vengono quindi creati intervalli figlio per le funzionifetchResource
,processResource
eupdateResource
.
Analisi della funzione createOCITracer
Le osservazioni chiave sulla funzione seguono il codice sorgente.
/**
* 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
}
- Il contesto della funzione (
ctx
) viene passato a questa funzione che fornisce le informazioni necessarie per connettersi al dominio APM. Se si seguono le chiamate di funzione, è possibile vedere come vengono creati gli ID e i campi di trace.
Di seguito è riportato il codice sorgente completo per la funzione di trace del nodo di esempio.
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)
}
Uso dell'API
Per informazioni sull'uso dell'API e delle richieste di firma, consulta la documentazione dell'API REST e le credenziali di sicurezza. Per informazioni sugli SDK, vedere SDK e l'interfaccia CLI.
Utilizzare le operazioni API riportate di seguito per abilitare e disabilitare il trace per le applicazioni e le funzioni in esse contenute.