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.

  1. 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).
  2. Impostare un dominio APM.
  3. Abilitare il trace per l'applicazione Functions e selezionare il dominio APM creato.
  4. 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 o false 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.

  1. 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.
  2. Selezionare la scheda Monitoraggio e andare alla sezione Tracce.
  3. Per abilitare il trace per l'applicazione:
    1. Dal menu Azioni (tre punti), selezionare Configura e specificare:
      • 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.
    2. 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.

  4. Per abilitare il trace per funzioni specifiche nell'applicazione:
    1. Selezionare la scheda Funzioni.
    2. 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.

  1. 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.
  2. Per visualizzare le tracce delle funzioni:
    1. Per visualizzare i trace per tutte le funzioni per le quali è abilitato il trace nell'applicazione, effettuare le operazioni riportate di seguito.
      1. Selezionare la scheda Monitoraggio e andare alla sezione Tracce.
      2. Selezionare il nome del trace.
        Nota

        Un nome traccia viene visualizzato solo se è già stato abilitato il trace per l'applicazione.
    2. Per visualizzare il trace per una funzione specifica per cui è abilitato il trace, effettuare le operazioni riportate di seguito.
      1. Selezionare la scheda Funzioni.
      2. 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.

  3. In Trace Explorer APM:
    1. Selezionare un trace per visualizzare gli intervalli per tale trace.
    2. 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.

Tracciamento di una catena di funzioni con Python

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.

  1. Creare la funzione Python di trace: fn init --runtime python <your-function-name-1>
  2. Crea il tuo "Ciao Mondo!" Funzione Python: fn init --runtime python <your-function-name-2>
  3. Distribuire entrambe le funzioni: fn -v deploy --app <app-name>
  4. Recupera l'OCID della seconda funzione e richiama l'endpoint: fn inspect function your-app-name your-function-name-2
  5. 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\" }"
    }                    
                    
  6. 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"}
    )
            
Rivedi codice origine funzione completa

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:

  1. 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).
  2. Impostare un dominio APM.
  3. Abilitare il trace per l'applicazione Functions e selezionare il dominio APM creato.
  4. 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.
Nota

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
Aggiunta di intervalli personalizzati alle funzioni Java

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 aggiorna tracingContext e crea un oggetto tracer 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 metodo getTraceCollectorURL(). L'URL è l'endpoint del dominio APM e viene utilizzato per creare l'oggetto tracer che crea gli intervalli personalizzati.
  • Un builder utilizza zipkinSpanHandler e il nome del servizio per creare un oggetto tracer. Questo oggetto tracer 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".
Rivedi codice origine funzione completa

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();
        }
    }
}
            
Aggiunta di intervalli personalizzati alle funzioni Python

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 flag is_sampled è impostato su None e py_zipkin non emette intervalli.
  • L'istruzione with zipkin_span viene utilizzata per creare gli intervalli.
    • Le informazioni in tracing_context vengono utilizzate per ottenere service_name, chiamare il transport_handler e impostare il zipking_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().
  • 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.
Rivedi codice origine funzione completa

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
                }
            )
            
Aggiunta di intervalli personalizzati alle funzioni del nodo

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 funzioni fetchResource, processResource e updateResource.

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.
Rivedi codice origine funzione completa

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)
}