Traçage réparti pour les fonctions

Découvrez comment activer le traçage et voir les traces de fonction lors du débogage avec le service des fonctions pour OCI.

Lorsqu'une fonction est appelée mais 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 à travers 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 le traçage de fonctions pour déboguer des problèmes avec 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 de stockage d'objets
  • une fonction qui sert de dorsale pour une passerelle d'API déployée dans le service de passerelle d'API
  • une fonction déclenchée en réponse à un événement par le service d'événements, le service d'avis ou le centre de connecteurs

Les fonctions de traçage du service des fonctions pour OCI sont fournies par le service Application Performance Monitoring pour Oracle Cloud Infrastructure. Les fonctions 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 traverse tous les composants d'un système réparti pendant une période donnée. Il se compose d'un arbre complet d'intervalles tous liés au même flux de demandes global unique.
  • Un intervalle 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. Un intervalle est un segment de temps associé à la durée d'une unité de travail dans le flux de demande global.

L'explorateur de trace d'Application Performance Monitoring vous permet de visualiser l'ensemble du flux de demandes et d'explorer les détails de trace et d'intervalle pour les diagnostics. Vous pouvez afficher et surveiller les traces lentes et les traces comportant des erreurs. Pour isoler et identifier les problèmes de trace, vous pouvez forer jusqu'à des intervalles spécifiques, tels que les chargements de page, les appels AJAX et les demandes de service. Pour plus d'informations sur le service Application Performance Monitoring, voir Application Performance Monitoring.

Pour activer le traçage pour une fonction, vous devez :

  1. Configurez une politique pour accorder au service des fonctions pour OCI l'autorisation d'accéder aux domaines APM, si la politique n'existe pas déjà (voir Énoncés de politique pour accorder aux utilisateurs du service des fonctions pour OCI et du service des fonctions pour OCI l'accès aux ressources de traçage).
  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. Activer le traçage pour une ou plusieurs fonctions.

Lorsque vous activez la fonction de trace pour une fonction, le service des fonctions pour OCI génère automatiquement un "intervalle d'appel de fonction par défaut". L'intervalle par défaut capture des informations sur le contexte d'exécution de la fonction, y compris le temps total nécessaire pour traiter la demande et retourner une réponse à l'appelant. En plus de l'intervalle d'appel de fonction par défaut, vous pouvez ajouter du code aux fonctions pour définir des intervalles personnalisés. Utilisez des intervalles personnalisés pour saisir davantage d'informations propres à une fonction afin de faciliter le débogage. Par exemple, vous pouvez définir des intervalles personnalisés pour saisir 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 la base de données à partir de la chambre forte, l'ouverture d'une connexion à la base de données et l'extraction d'enregistrements à partir de la base de données.

Quatre variables ont été ajoutées au contexte du service des fonctions pour OCI qui fournissent des informations de trace utiles. Ces variables sont les suivantes :

  • FN_APP_NAME: Nom de l'application de la fonction.
  • FN_FN_NAME: Nom de la fonction.
  • OCI_TRACE_COLLECTOR_URL : URL du domaine APM avec clé de données.
  • OCI_TRACING_ENABLED: Le traçage est-il activé?
    • Lors de l'extraction à partir des variables d'environnement, retourne 0 ou 1.
    • Lors de l'extraction à partir du contexte de fonction, retourne true ou false, selon la langue utilisée.

Politique IAM requise pour activer le traçage

Pour que vous puissiez activer le traçage, le groupe auquel vous appartenez doit être autorisé à accéder aux domaines APM existants ou à créer des domaines APM. En outre, le service des fonctions pour OCI doit avoir l'autorisation d'accéder aux domaines APM. Voir Énoncés de politique permettant aux utilisateurs du service des fonctions pour OCI et du service des fonctions pour OCI d'accéder aux ressources de traçage.

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

Deux étapes sont requises pour activer le suivi et voir les traces de fonction pour le service Application Performance Monitoring (APM) d'Oracle Cloud Infrastructure. Tout d'abord, activez le traçage pour l'application contenant la fonction. Activez ensuite le traçage pour une ou plusieurs fonctions. Vous pouvez ensuite afficher les traces de fonction dans l'explorateur de trace APM.

Utilisation de la console pour activer le traçage

Pour activer le traçage, suivez ces étapes.

  1. Dans la page de liste Applications, sélectionnez l'application avec les fonctions pour lesquelles vous voulez activer le traçage. Si vous avez besoin d'aide pour trouver la page de liste ou l'application, voir Liste des applications.
  2. Sélectionnez l'onglet Surveillance et allez à la section Traces.
  3. Pour activer le traçage pour l'application :
    1. Dans le menu Actions (trois points), sélectionnez Configurer et spécifiez :
      • compartiment : compartiment dans lequel créer la trace. Par défaut, le compartiment courant.
      • 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 nouveau domaine APM, sélectionnez Domaine APM. Pour plus d'informations sur les domaines APM, voir Introduction à Application Performance Monitoring.
        Note

        Le domaine APM doit avoir à la fois des clés de données publiques et des clés de données privées pour que le suivi des fonctions fonctionne. Si les clés n'existent pas, vous pouvez les créer au moyen de l'interface de console.
    2. Sélectionnez Activer le traçage pour activer le traçage pour l'application.

    Après avoir activé le traçage pour l'application Functions, vous pouvez maintenant activer le traçage pour une ou plusieurs fonctions de l'application.

  4. Pour activer le traçage pour des fonctions spécifiques dans l'application :
    1. Sélectionnez l'onglet Fonctions.
    2. Sélectionnez l'option Activer le traçage dans le menu Actions (trois points) pour les fonctions pour lesquelles vous voulez activer le traçage.

      L'option Activer le suivi n'est affichée que si vous avez déjà activé le suivi pour l'application. Notez ce qui suit :

      • Si l'option Activer le suivi n'est pas affichée, vous devez activer le suivi pour l'application. Si vous n'avez pas encore activé le traçage pour l'application, consultez l'étape précédente.
      • Si vous avez précédemment activé la fonction de traçage pour l'application, mais que vous l'avez désactivée ultérieurement, un lien Activer la fonction de traçage d'application s'affiche. Sélectionnez le lien Activer le traçage d'application pour réactiver le traçage pour l'application (voir 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é le traçage pour l'application et une ou plusieurs fonctions, vous pouvez voir les traces de fonction.

Utilisation de la console pour voir les traces de fonction

Pour voir les traces des fonctions pour lesquelles le traçage est activé :

  1. Dans la page de liste Applications, sélectionnez l'application contenant les fonctions pour lesquelles vous voulez voir les traces. Si vous avez besoin d'aide pour trouver la page de liste ou l'application, voir Liste des applications.
  2. Pour voir les traces des fonctions :
    1. Pour voir les traces de toutes les fonctions pour lesquelles le traçage est activé dans l'application :
      1. Sélectionnez l'onglet Surveillance et allez à la section Traces.
      2. Sélectionnez le nom de la trace.
        Note

        Un nom de trace n'est affiché que si vous avez déjà activé le traçage pour l'application.
    2. Pour voir la trace d'une fonction spécifique pour laquelle le traçage est activé :
      1. Sélectionnez l'onglet Fonctions.
      2. Sélectionnez l'option Voir la trace dans le menu Actions (trois points) pour la fonction pour laquelle vous voulez voir la trace.
        Note

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

    Les traces des fonctions que vous avez sélectionnées sont affichées dans l'explorateur de trace APM. Par défaut, une trace est affichée pour l'intervalle d'appel de fonction par défaut et tous les intervalles personnalisés définis pour la fonction.

  3. Dans l'explorateur de traces d'APM :
    1. Sélectionnez une trace pour voir les intervalles associés à cette trace.
    2. Sélectionnez un intervalle pour voir les détails saisis pour cet intervalle.

    Pour plus d'informations sur l'utilisation de l'explorateur de trace APM, voir Utiliser l'explorateur de trace.

Trace d'une chaîne de fonctions

Par défaut, le traçage de fonction fournit une trace pour l'ensemble d'un appel de fonction. Toutefois, souvent avec des applications en nuage modernes, vous devez chaîner les appels de fonction. La fonction de trace du service des fonctions pour OCI 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 un arbre unique d'intervalles dans l'explorateur de trace 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 trace APM. Pour plus d'informations sur l'utilisation de l'explorateur de trace APM, voir Utiliser l'explorateur de trace.

Trace d'une chaîne de fonctions avec Python

Voici un exemple de la façon dont vous pouvez tracer 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 votre fonction Python de traçage : 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 le point d'extrémité : 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 ressembler à ceci :
    
    {
        "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 deuxième 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 les paquetages

Mettez à jour votre fichier requirements.txt pour inclure les ensembles 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. 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 du service des fonctions à l'aide des principaux de ressource de la trousse SDK et du service des fonctions pour OCI Python. Ensuite, extrayez 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

La trousse SDK Python pour 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"}
    )
            
Réviser le code source de la fonction Terminé

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'intervalles personnalisés aux fonctions

Lorsque le traçage de fonction est activé, l'intervalle d'appel de fonction par défaut fournit une trace pour l'ensemble de l'appel de fonction. L'intervalle par défaut peut fournir de bonnes informations, mais lors de l'examen de votre code, vous voudrez peut-être approfondir. Les intervalles personnalisés sont ajoutés directement à votre code et vous permettent de définir des intervalles pour une méthode ou un bloc de code. Les données résultantes fournissent une meilleure image de votre fonction lors de son exécution.

Pour pouvoir utiliser des intervalles personnalisés, vous devez activer le suivi pour votre application et vos fonctions à l'aide du service Application Performance Monitoring (APM) d'Oracle Cloud Infrastructure. Pour configurer le traçage, vous devez :

  1. Configurez une politique pour accorder au service des fonctions pour OCI l'autorisation d'accéder aux domaines APM, si la politique n'existe pas déjà (voir Énoncés de politique pour accorder aux utilisateurs du service des fonctions pour OCI et du service des fonctions pour OCI l'accès aux ressources de traçage).
  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. Activer le traçage pour une ou plusieurs fonctions.

Ces étapes ont déjà été couvertes. Cependant, quelques éléments supplémentaires sont requis pour les intervalles personnalisés :

  • Sélectionnez une bibliothèque client de traçage distribué, par exemple Zipkin.
  • Ajoutez des bibliothèques client à vos dépendances de fonction.
  • Dans votre code de fonction, utilisez la variable de contexte de fonction OCI_TRACING_ENABLED pour vérifier si le traçage est activé.
  • Dans votre code de fonction, utilisez la variable de contexte de fonction OCI_TRACE_COLLECTOR_URL pour envoyer vos intervalles personnalisés à votre domaine APM.
  • Ajoutez l'instrumentation à votre code de fonction.
Note

Pour utiliser des intervalles personnalisés, vous devez disposer des versions minimales suivantes des trousses FDK Fn Project :

  • TROUSSE SDK Java : 1.0.129
  • Python FDK : 0.1.22
  • FDK de noeud : 0.0.20
Ajout d'intervalles personnalisés aux fonctions Java

Voici un exemple d'utilisation de Zipkin pour ajouter des intervalles personnalisés à votre fonction Java. Si vous voulez essayer cet exemple, vous pouvez créer une fonction Java "Hello World!" et ajouter un code d'intervalle personnalisé. Pour créer un exemple de fonction :

  • Créez une fonction Java : fn init --runtime java apm-fn-java
  • Pour simplifier, supprimez 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, qui met à jour tracingContext et crée un objet tracer utilisé pour configurer des intervalles personnalisés.
  • Une valeur span est créée pour l'intervalle personnalisé parent. Trois méthodes sont ensuite appelées dans la portée de l'intervalle parent.
  • Notez que dans le bloc finally, tous les objets de traçage 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();
    }   
            
  • traceContext est transmis pour créer tous les objets utilisés pour créer des intervalles personnalisés.
  • apmURL est extrait de la méthode getTraceCollectorURL(). L'URL est le point d'extrémité du domaine APM et est utilisée pour créer l'objet tracer qui crée les intervalles personnalisés.
  • Un générateur utilise zipkinSpanHandler et le nom du service pour créer un objet tracer. Cet objet tracer est utilisé pour créer des intervalles personnalisés.

Création d'intervalles personnalisés

Avec l'objet tracer initialisé, des intervalles personnalisés peuvent être créés.


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 un intervalle personnalisé nommé "Method1".
Réviser le code source de la fonction Terminé

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'intervalles personnalisés aux fonctions Python

Voici un exemple d'utilisation de Zipkin pour ajouter des intervalles personnalisés à votre fonction Python. Si vous voulez essayer cet exemple, vous pouvez créer une fonction Python "Hello World!" et ajouter un code d'intervalle personnalisé. Pour créer un exemple de fonction :

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

Configurer les paquetages

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


fdk
requests
py_zipkin
            

Enregistrez le fichier.

Création de la classe de programme de traitement et de l'intervalle personnalisé parent

La fonction Python appelle la fonction handler et la transmet dans le contexte de la fonction pour créer des intervalles personnalisés.

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"}
        )         
  • tracing_context est transmis à partir du contexte de fonction et contient toutes les informations nécessaires pour créer et configurer des intervalles personnalisés.
    Note

    Si le traçage n'est pas activé, le contexte de traçage est un objet vide. Avec un contexte de traçage vide, l'indicateur is_sampled est réglé à None et py_zipkin n'émet pas d'intervalles.
  • L'énoncé with zipkin_span est utilisé pour créer des intervalles.
    • Les informations dans tracing_context sont utilisées pour obtenir service_name, appeler transport_handler et définir zipking_attrs.
    • Un nom d'intervalle personnalisé est spécifié uniquement en définissant span_name.
    • Les attributs de traçage requis pour Zipkin sont extraits du contexte de traçage : tracing_context.zipkin_attrs().
  • Avec la configuration de l'intervalle personnalisé, le bloc principal exécute le code "Hello World!" standard. À la seule exception, un appel à la fonction example.

Fonction transport_handler

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

# 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"},
    )            
  • trace_collector_url est retourné à partir du contexte de fonction. Cette URL fournit le point d'extrémité de communication pour vos intervalles personnalisés vers le domaine APM.

Création d'un intervalle personnalisé dans l'exemple de fonction

L'exemple de fonction illustre la création d'un intervalle personnalisé.

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'énoncé with zipkin_span est utilisé pour identifier l'intervalle personnalisé et lui donner un nom.
  • Le bloc example_span_context génère une exception et retourne un message d'erreur.
Réviser le code source de la fonction Terminé

Voici le code source complet de l'exemple de fonction de traçage 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
                }
            )
            
Ajout d'intervalles personnalisés aux fonctions de noeud

Voici un exemple d'utilisation de Zipkin pour ajouter des intervalles personnalisés à votre fonction Node.js. Si vous voulez essayer cet exemple, vous pouvez créer une fonction Node "Hello World!" et ajouter un code d'intervalle personnalisé. Pour créer un exemple de fonction :

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

Configurer les dépendances de noeud

Mettez à jour votre fichier package.json pour inclure les ensembles 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.

Méthode de traitement de mise à jour

Les observations clés 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;

})            
            
  • tracer est créé, puis utilisé pour créer un intervalle personnalisé parent. Des intervalles enfants sont ensuite créés 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 se connecter au domaine APM. Si vous suivez les appels de fonction, vous pouvez voir comment les ID de trace et les champs sont créés.
Réviser le code source de la fonction Terminé

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