Trace distribuée pour les fonctions

Découvrez comment activer la fonction de trace et visualiser les traces de fonction lors du débogage avec OCI Functions.

Lorsqu'une fonction est appelée mais qu'elle ne s'exécute pas ou ne fonctionne pas comme prévu, vous devez examiner le problème à un niveau détaillé. La fonction de trace distribuée observe l'exécution de la fonction lorsqu'elle se déplace dans les différents composants du système. Vous pouvez tracer et instrumenter des fonctions autonomes pour déboguer les problèmes d'exécution et de performances. Vous pouvez également utiliser la fonction de trace pour résoudre les problèmes liés à des applications sans serveur complètes comprenant plusieurs fonctions et services, tels que :

  • une fonction appelant une autre fonction
  • une fonction appelant d'autres services tels que le service Object Storage
  • Fonction qui sert de back-end pour une passerelle d'API déployée dans le service API Gateway
  • une fonction déclenchée en réponse à un événement par le service Events, le service Notifications ou Connector Hub

Les fonctionnalités de trace OCI Functions sont fournies par le service Oracle Cloud Infrastructure Application Performance Monitoring. Les fonctionnalités d'Application Performance Monitoring (APM) vous permettent d'identifier et de résoudre les échecs et les problèmes de latence dans les fonctions que vous créez et déployez.

Dans le service Application Performance Monitoring :

  • Un domaine APM contient les systèmes surveillés par Application Performance Monitoring. Un domaine APM est une instance d'un collecteur de données de trace et d'étendue qui stocke, agrège, affiche et visualise les données.
  • Une trace est le flux complet d'une demande lorsqu'elle passe à travers tous les composants d'un système distribué au cours d'une période donnée. Il se compose d'une arborescence complète d'étendues toutes liées au même flux de demandes global unique.
  • Une étendue est une opération ou une unité de travail logique avec un nom, une heure de début et une durée, dans une trace. Une étendue est un segment de temps associé à la durée d'une unité de travail dans le flux de demandes global.

L'explorateur de traces Application Performance Monitoring vous permet de visualiser l'ensemble du flux de demandes et d'explorer les détails de trace et d'étendue à des fins de diagnostic. Vous pouvez visualiser et surveiller les traces lentes et les traces comportant des erreurs. Pour isoler et identifier les problèmes de trace, vous pouvez explorer des étendues spécifiques, telles que les chargements de page, les appels AJAX et les demandes de service. Pour plus d'informations sur le service Application Performance Monitoring, reportez-vous à Application Performance Monitoring.

Pour activer la fonction de trace pour une fonction, vous devez :

  1. Configurez une stratégie pour autoriser le service OCI Functions à accéder aux domaines APM, si la stratégie n'existe pas déjà (reportez-vous à Instructions de stratégie permettant aux utilisateurs d'OCI Functions et d'OCI Functions d'accéder aux ressources de trace).
  2. Configurez un domaine APM.
  3. Activez la fonction de trace pour l'application Functions et sélectionnez le domaine APM que vous avez créé.
  4. Activez la fonction de trace pour une ou plusieurs fonctions.

Lorsque vous activez la fonction de trace, OCI Functions génère automatiquement une "étendue d'appel de fonction par défaut". L'étendue par défaut capture des informations sur le contexte d'exécution de la fonction, y compris le temps global nécessaire au traitement de la demande et au renvoi d'une réponse à l'appelant. En plus de l'étendue d'appel de fonction par défaut, vous pouvez ajouter du code aux fonctions pour définir des étendues personnalisées. Utilisez des étendues personnalisées pour capturer davantage d'informations spécifiques aux fonctions afin de faciliter le débogage. Par exemple, vous pouvez définir des étendues personnalisées pour capturer le début et la fin d'unités de travail spécifiques. Par exemple, les unités de travail peuvent inclure l'obtention du mot de passe de base de données à partir du coffre, l'ouverture d'une connexion de base de données et l'extraction d'enregistrements à partir de la base de données.

Quatre variables ont été ajoutées au contexte OCI Functions qui fournissent des informations de trace utiles. Ces variables incluent :

  • FN_APP_NAME: Nom de l'application de fonction.
  • FN_FN_NAME: Nom de la fonction.
  • OCI_TRACE_COLLECTOR_URL : URL de domaine APM avec clé de données.
  • OCI_TRACING_ENABLED: La fonction de trace est-elle activée ?
    • Lorsqu'elle est extraite de variables d'environnement, renvoie 0 ou 1.
    • Lorsqu'elle est extraite du contexte de fonction, renvoie true ou false selon la langue utilisée.

Stratégie IAM requise pour l'activation de la fonction de trace

Avant de pouvoir activer la fonction de trace, le groupe auquel vous appartenez doit être autorisé à accéder aux domaines APM existants ou à créer des domaines APM. En outre, OCI Functions doit être autorisé à accéder aux domaines APM. Reportez-vous à Instructions de stratégie permettant aux utilisateurs du service OCI Functions et d'OCI Functions d'accéder à la trace des ressources.

Utilisation de la console pour activer la fonction de trace et afficher les traces de fonction

Vous devez suivre quelques étapes pour activer la fonction de trace et afficher les traces de fonction pour le service Oracle Cloud Infrastructure Application Performance Monitoring (APM). Tout d'abord, activez la fonction de trace pour l'application contenant la fonction. Activez ensuite la fonction de trace pour une ou plusieurs fonctions. Vous pouvez ensuite afficher les traces de fonction dans l'explorateur de traces APM.

Utilisation de la console pour activer la fonction de trace

Pour activer la fonction de trace, suivez ces étapes.

  1. Sur la page de liste Applications, sélectionnez l'application avec les fonctions pour lesquelles vous voulez activer la fonction de trace. Si vous avez besoin d'aide pour rechercher la page de liste ou l'application, reportez-vous à Liste des applications.
  2. Sélectionnez l'onglet Surveillance et accédez à la section Traces.
  3. Pour activer la trace pour l'application :
    1. Dans le menu Actions (trois points), sélectionnez Configurer et indiquez les éléments suivants :
      • compartiment : compartiment dans lequel créer la trace. Par défaut, il s'agit du compartiment en cours.
      • Domaine APM : domaine APM (défini dans le service Application Performance Monitoring) dans lequel créer la trace. Pour utiliser un domaine APM existant, sélectionnez un domaine APM existant dans la liste. Ou, pour créer un domaine APM, sélectionnez Domaine APM. Pour plus d'informations sur les domaines APM, reportez-vous à Introduction à Application Performance Monitoring.
        Remarque

        Le domaine APM doit disposer à la fois de clés de données publiques et privées pour que la fonction de trace fonctionne. Si les clés n'existent pas, vous pouvez les créer via l'interface de la console.
    2. Sélectionnez Activer la trace pour activer la fonction de trace pour l'application.

    Une fois la fonction de trace activée pour l'application Functions, vous pouvez désormais activer la fonction de trace pour une ou plusieurs fonctions de l'application.

  4. Pour activer la fonction de trace pour des fonctions spécifiques dans l'application :
    1. Sélectionnez l'onglet Fonctions.
    2. Sélectionnez l'option Activer la trace dans le menu Actions (trois points) pour la ou les fonctions pour lesquelles vous souhaitez activer la trace.

      L'option Activer la trace n'est affichée que si vous avez précédemment activé la fonction de trace pour l'application. Tenez compte des éléments suivants :

      • Si l'option Activer la trace n'est pas affichée, vous devez activer la fonction de trace pour l'application. Si vous n'avez pas encore activé la fonction de trace pour l'application, reportez-vous à l'étape précédente.
      • Si vous avez précédemment activé la fonction de trace pour l'application, mais que vous l'avez désactivée ultérieurement, un lien Activer la fonction de trace de l'application apparaît. Sélectionnez le lien Activer la fonction de trace d'application pour réactiver la fonction de trace pour l'application (reportez-vous à l'étape précédente). Après avoir réactivé la fonction de trace pour l'application, vous pouvez activer la fonction de trace pour des fonctions spécifiques.

Lorsque vous avez activé la fonction de trace pour l'application et une ou plusieurs fonctions, vous pouvez afficher les traces de fonction.

Utilisation de la console pour afficher les traces de fonction

Pour afficher les traces des fonctions pour lesquelles la fonction de trace est activée, procédez comme suit :

  1. Sur la page de liste Applications, sélectionnez l'application contenant les fonctions pour lesquelles vous voulez afficher les traces. Si vous avez besoin d'aide pour rechercher la page de liste ou l'application, reportez-vous à Liste des applications.
  2. Pour afficher les traces des fonctions, procédez comme suit :
    1. Pour afficher les traces de toutes les fonctions pour lesquelles la fonction de trace est activée dans l'application, procédez comme suit :
      1. Sélectionnez l'onglet Surveillance et accédez à la section Traces.
      2. Sélectionnez le nom de la trace.
        Remarque

        Un nom de trace est affiché uniquement si vous avez déjà activé la fonction de trace pour l'application.
    2. Pour afficher la trace d'une fonction spécifique pour laquelle la fonction de trace est activée, procédez comme suit :
      1. Sélectionnez l'onglet Fonctions.
      2. Sélectionnez l'option Afficher la trace dans le menu Actions (trois points) de la fonction pour laquelle vous souhaitez afficher la trace.
        Remarque

        L'option Afficher la trace n'est affichée que si vous avez déjà activé la fonction de trace.

    Les traces des fonctions sélectionnées sont affichées dans l'explorateur de traces APM. Par défaut, une trace est affichée pour l'étendue d'appel de fonction par défaut et toutes les étendues personnalisées définies pour la fonction.

  3. Dans l'explorateur de traces APM :
    1. Sélectionnez une trace pour afficher les étendues de cette trace.
    2. Sélectionnez une étendue pour afficher les détails capturés pour cette étendue.

    Pour plus d'informations sur l'utilisation de l'explorateur de traces APM, reportez-vous à Utilisation de l'explorateur de traces.

Tracer une chaîne de fonctions

Par défaut, la fonction de trace fournit une trace pour l'ensemble d'un appel de fonction. Cependant, souvent avec les applications cloud d'aujourd'hui, vous devez enchaîner les appels de fonction. La fonction de trace OCI Functions permet de suivre l'exécution d'une fonction appelée par une autre fonction. Cela signifie que vous pouvez examiner l'exécution de chaque fonction dans une chaîne d'appels dans une seule arborescence d'étendues dans l'explorateur de traces APM.

Pour tracer une chaîne de fonctions, vous devez propager les en-têtes X-B3 X-B3-TraceId, X-B3-SpanId, X-B3-ParentSpanId et X-B3-Sampled dans la demande d'appel de fonction à partir de votre code de fonction.

Une fois la fonction exécutée, les données de trace de vos fonctions sont collectées et disponibles dans l'explorateur de traces APM. Pour plus d'informations sur l'utilisation de l'explorateur de traces APM, reportez-vous à Utilisation de l'explorateur de traces.

Tracer une chaîne de fonctions avec Python

Voici un exemple de traçage d'une chaîne de fonctions. Si vous voulez essayer cet exemple, vous devez créer deux exemples de fonctions. Suivez ces étapes pour configurer vos fonctions.

  1. Créez la fonction Python de trace : fn init --runtime python <your-function-name-1>
  2. Créez votre "Hello World !". Fonction Python : fn init --runtime python <your-function-name-2>
  3. Déployez les deux fonctions : fn -v deploy --app <app-name>
  4. Obtenez l'OCID de la deuxième fonction et appelez l'adresse : fn inspect function your-app-name your-function-name-2
  5. Créez un fichier JSON pour transmettre les informations requises à la première fonction. Par exemple, votre fichier test.json peut se présenter comme suit :
    
    {
        "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. Lorsque la première fonction est appelée, vous pouvez transmettre les informations de la seconde fonction à l'aide de test.json : fn invoke <app-name> <your-function-name-1> < test.json

Vous pouvez maintenant mettre à jour la première fonction avec les mises à jour de code requises.

Configurer des packages

Mettez à jour votre fichier requirements.txt pour inclure les packages suivants :


fdk
oci
            

Enregistrez le fichier.

Mettez à jour votre code de fonction pour propager les en-têtes X-B3

La fonction Python appelle la fonction handler et transmet les informations JSON à partir de la commande d'appel. La fonction handler est divisée en plusieurs petits blocs pour simplifier la tâche. Le fichier source complet est fourni au bas de cette section.

Charger les données JSON

Dans cette première partie, les données JSON sont chargées à partir de l'appel de fonction.


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
            

Créer un client d'appel et collecter les informations d'en-tête

Créez le client d'appel Functions à l'aide des principaux de ressource OCI Python SDK et Functions. Extrayez ensuite le fichier tracing_context et extrayez les informations requises pour créer les en-têtes 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()
            

Propager les en-têtes X-B3

Le kit SDK Python OCI vous permet de définir des en-têtes personnalisés. Utilisez cette technique pour transmettre les en-têtes X-B3 au deuxième appel de fonction. Les informations d'en-tête sont transmises pour trace_id, span_id, parent_span_id et is_sampled. Enfin, la deuxième fonction est appelée avec client et la réponse est transmise à la réponse de cette fonction.


    # 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"}
    )
            
Vérifier le code source de la fonction complète

Voici le code source complet de l'exemple de fonction Python.


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

Ajout d'étendues personnalisées à des fonctions

Lorsque la fonction de trace est activée, la plage d'appel de fonction par défaut fournit une trace pour l'ensemble de l'appel de fonction. L'étendue par défaut peut fournir de bonnes informations, mais lorsque vous étudiez votre code, vous voudrez peut-être approfondir vos connaissances. Les étendues personnalisées sont ajoutées directement à votre code et vous permettent de définir des étendues pour une méthode ou un bloc de code. Les données obtenues fournissent une meilleure image de votre fonction au fur et à mesure de son exécution.

Pour pouvoir utiliser des étendues personnalisées, vous devez activer la fonction de trace pour votre application et vos fonctions à l'aide du service Oracle Cloud Infrastructure Application Performance Monitoring (APM). Pour paramétrer la fonction de trace, vous devez :

  1. Configurez une stratégie pour autoriser le service OCI Functions à accéder aux domaines APM, si la stratégie n'existe pas déjà (reportez-vous à Instructions de stratégie permettant aux utilisateurs d'OCI Functions et d'OCI Functions d'accéder aux ressources de trace).
  2. Configurez un domaine APM.
  3. Activez la fonction de trace pour l'application Functions et sélectionnez le domaine APM que vous avez créé.
  4. Activez la fonction de trace pour une ou plusieurs fonctions.

Ces étapes ont déjà été couvertes. Cependant, quelques autres éléments sont nécessaires pour les étendues personnalisées :

  • Sélectionnez une bibliothèque client de trace distribuée, par exemple Zipkin.
  • Ajoutez des bibliothèques client à vos dépendances de fonction.
  • Dans le code de la fonction, utilisez la variable de contexte de la fonction OCI_TRACING_ENABLED pour vérifier si la fonction de trace est activée.
  • Dans votre code de fonction, utilisez la variable de contexte de fonction OCI_TRACE_COLLECTOR_URL pour envoyer vos étendues personnalisées à votre domaine APM.
  • Ajoutez une instrumentation à votre code de fonction.
Remarque

Pour utiliser des étendues personnalisées, vous devez disposer des versions minimales suivantes des clés génériques de projet Fn :

  • Java FDK : 1.0.129
  • Python FDK : 0.1.22
  • Clé étrangère de noeud : 0.0.20
Ajouter des étendues personnalisées à des fonctions Java

Voici un exemple d'utilisation de Zipkin pour ajouter des étendues personnalisées à votre fonction Java. Si vous voulez essayer cet exemple, vous pouvez créer une fonction Java "Hello World !" et ajouter un code d'étendue personnalisé. Pour créer un exemple de fonction, procédez comme suit :

  • Créez une fonction Java : fn init --runtime java apm-fn-java
  • Pour plus de simplicité, enlevez le répertoire src/test.

Configurer Maven

Ajoutez les dépendances suivantes à la section <dependencies> de votre fichier Maven pom.xml.


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

Enregistrez le fichier.

La méthode HandleRequest

Les observations sur la méthode suivent le code source 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'objet TracingContext tracingConext transmet toutes les informations relatives à APM nécessaires pour établir des connexions au service APM.
  • La méthode intializeZipkin est appelée, ce qui met à jour tracingContext et crée un objet tracer utilisé pour configurer des étendues personnalisées.
  • Une valeur span est créée pour l'étendue personnalisée parent. Trois méthodes sont ensuite appelées dans la portée de l'étendue parent.
  • Dans le bloc finally, tous les objets de trace sont fermés.

La méthode initializeZipkin

Les observations sur la méthode intializeZipkin suivent le code source.

     
    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();
    }   
            
  • La valeur traceContext est transmise pour créer tous les objets utilisés pour créer des étendues personnalisées.
  • L'élément apmURL est extrait de la méthode getTraceCollectorURL(). L'URL est l'adresse du domaine APM et est utilisée pour créer l'objet tracer qui crée les étendues personnalisées.
  • Un constructeur utilise zipkinSpanHandler et le nom de service pour créer un objet tracer. Cet objet tracer est utilisé pour créer des étendues personnalisées.

Créer des étendues personnalisées

L'objet tracer étant initialisé, des étendues personnalisées peuvent être créées.


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();
    }
}
            
  • La méthode method1 crée une étendue personnalisée nommée "Method1".
Vérifier le code source de la fonction complète

Voici le code source complet de l'exemple de fonction de trace Java.


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();
        }
    }
}
            
Ajout d'étendues personnalisées à des fonctions Python

Voici un exemple d'utilisation de Zipkin pour ajouter des étendues personnalisées à votre fonction Python. Si vous voulez essayer cet exemple, vous pouvez créer une fonction Python "Hello World !" et ajouter un code d'étendue personnalisé. Pour créer un exemple de fonction, procédez comme suit :

  • Créez une fonction Python : fn init --runtime python apm-fn-python

Configurer des packages

Mettez à jour votre fichier requirements.txt pour inclure les packages suivants :


fdk
requests
py_zipkin
            

Enregistrez le fichier.

Créer une classe de gestionnaire et une étendue personnalisée parent

La fonction Python appelle la fonction handler et transmet le contexte de la fonction pour créer des étendues personnalisées.

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"}
        )         
  • La valeur tracing_context est transmise à partir du contexte de fonction et contient toutes les informations nécessaires pour créer et configurer des étendues personnalisées.
    Remarque

    Si la fonction de trace n'est pas activée, le contexte de trace est un objet vide. Avec un contexte de trace vide, l'indicateur is_sampled est défini sur None et py_zipkin n'émet pas d'étendues.
  • L'instruction with zipkin_span permet de créer des étendues.
    • Les informations de tracing_context sont utilisées pour obtenir service_name, appeler transport_handler et définir zipking_attrs.
    • Un nom d'étendue personnalisé est spécifié uniquement en définissant span_name.
    • Les attributs de trace requis pour Zipkin sont extraits du contexte de trace : tracing_context.zipkin_attrs().
  • Avec la configuration d'étendue personnalisée, le bloc principal exécute le boilerplate "Hello World !" code. A la seule exception près, un appel à la fonction example.

Fonction transport_handler

La fonction transport_handler communique avec le domaine APM avec des messages sur l'exécution de l'étendue.

# 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"},
    )            
  • La valeur trace_collector_url est renvoyée à partir du contexte de fonction. Cette URL fournit l'adresse de communication de vos étendues personnalisées vers le domaine APM.

Création d'une étendue personnalisée dans l'exemple de fonction

L'exemple de fonction illustre la création d'une étendue personnalisée.

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'instruction with zipkin_span permet d'identifier l'étendue personnalisée et de lui attribuer un nom.
  • Le bloc example_span_context génère une exception et renvoie un message d'erreur.
Vérifier le code source de la fonction complète

Voici le code source complet de l'exemple de fonction de trace Python.


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
                }
            )
            
Ajouter des étendues personnalisées aux fonctions de noeud

Voici un exemple d'utilisation de Zipkin pour ajouter des étendues personnalisées à votre fonction Node.js. Si vous voulez essayer cet exemple, vous pouvez créer une fonction Node "Hello World !" et ajouter un code d'étendue personnalisé. Pour créer un exemple de fonction, procédez comme suit :

  • Créez une fonction de noeud : fn init --runtime node apm-fn-node

Configurer les dépendances de noeud

Mettez à jour votre fichier package.json pour inclure les packages suivants :


{
    "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"
    }
}
            

Enregistrez le fichier.

Mettre à jour la méthode de traitement

Les principales observations sur la méthode fdk.handle suivent le code source.


// 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;

})            
            
  • L'élément tracer est créé, puis utilisé pour créer une étendue personnalisée parent. Ensuite, des étendues enfant sont créées pour les fonctions fetchResource, processResource et updateResource.

Vérification de la fonction createOCITracer

Les principales observations sur la fonction suivent le code source.


/**
 * 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
}
            
  • Le contexte de fonction (ctx) est transmis à cette fonction qui fournit les informations requises pour la connexion au domaine APM. Si vous suivez les appels de fonction, vous pouvez voir comment les ID de suivi et les champs sont paramétrés.
Vérifier le code source de la fonction complète

Voici le code source complet de l'exemple de fonction de trace de noeud.



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

Utilisation de l'API

Pour plus d'informations sur l'utilisation de l'API et la signature des demandes, reportez-vous à la documentation relative à l'API REST et à Informations d'identification de sécurité. Pour plus d'informations sur les kits SDK, reportez-vous à Kits SDK et interface de ligne de commande.

Utilisez les opérations d'API suivantes pour activer et désactiver la fonction de trace pour les applications et les fonctions qu'elles contiennent :