Rastreamento Distribuído para o Serviço Functions

Descubra como ativar o rastreamento e exibir rastreamentos de funções ao depurar com o OCI Functions.

Quando uma função é chamada, mas não é executada ou executada conforme esperado, você precisa investigar o problema em um nível detalhado. O recurso de rastreamento distribuído observa a execução da função à medida que ela se move pelos diferentes componentes do sistema. Você pode rastrear e instrumentar funções independentes para depurar problemas de execução e desempenho. Você também pode usar o rastreamento de funções para depurar problemas com aplicativos sem servidor completos que compreendem várias funções e serviços, como:

  • uma função chamando outra função
  • uma função que chama outros serviços, como o serviço Object Storage
  • uma função que serve como backend para um gateway de API implantado no serviço API Gateway
  • uma função acionada em resposta a um evento pelo serviço Events, serviço Notifications ou Connector Hub

Os recursos de rastreamento do OCI Functions são fornecidos pelo serviço Oracle Cloud Infrastructure Application Performance Monitoring. Os recursos do Application Performance Monitoring (APM) permitem identificar e solucionar problemas de falhas e latência nas funções que você cria e implanta.

No serviço Application Performance Monitoring:

  • Um domínio APM contém os sistemas monitorados pelo serviço Application Performance Monitoring. Um domínio do APM é uma instância de um coletor de dados de rastreamento e intervalo que armazena, agrega, exibe e visualiza os dados.
  • Um rastreamento é o fluxo completo de uma solicitação à medida que ela passa por todos os componentes de um sistema distribuído em um determinado período. Ele consiste em uma árvore inteira de intervalos, todos relacionados ao mesmo fluxo de solicitação geral único.
  • Um intervalo é uma operação ou uma unidade lógica de trabalho com um nome, hora inicial e duração, dentro de um rastreamento. Um intervalo é um segmento de tempo associado à duração de uma unidade de trabalho dentro do fluxo de solicitação geral.

O Trace Explorer do serviço Application Performance Monitoring permite visualizar todo o fluxo de solicitações e explorar detalhes de rastreamento e intervalo para diagnósticos. Você pode exibir e monitorar rastreamentos lentos e rastreamentos com erros. Para isolar e identificar problemas de rastreamento, você pode fazer drill-down em intervalos específicos, como carregamentos de página, chamadas AJAX e solicitações de serviço. Para obter mais informações sobre o serviço Application Performance Monitoring, consulte Application Performance Monitoring.

Para ativar o rastreamento de uma função, você deve:

  1. Configure uma política para conceder ao serviço OCI Functions permissão para acessar domínios do APM, se a política ainda não existir (consulte Instruções de Política para Conceder ao Serviço OCI Functions e aos Usuários do OCI Functions Acesso aos Recursos de Rastreamento).
  2. Configure um domínio do APM.
  3. Ative o rastreamento para o aplicativo Functions e selecione o domínio do APM que você criou.
  4. Ative o rastreamento para uma ou mais funções.

Quando você ativa o rastreamento de uma função, o OCI Functions gera automaticamente um "intervalo de chamada de função padrão". O intervalo padrão captura informações sobre o contexto de execução da função, incluindo o tempo geral necessário para processar a solicitação e retornar uma resposta ao chamador. Além do intervalo de chamada de função padrão, você pode adicionar código a funções para definir intervalos personalizados. Use intervalos personalizados para capturar mais informações específicas da função para ajudar na depuração. Por exemplo, você pode definir intervalos personalizados para capturar o início e o fim de unidades de trabalho específicas. Por exemplo, as unidades de trabalho podem incluir obter a senha do banco de dados do Vault, abrir uma conexão de banco de dados e recuperar registros do banco de dados.

Quatro variáveis foram adicionadas ao contexto do OCI Functions que fornecem informações úteis de rastreamento. Essas variáveis incluem:

  • FN_APP_NAME: O nome do aplicativo da função.
  • FN_FN_NAME: O nome da função.
  • OCI_TRACE_COLLECTOR_URL: O URL do domínio do APM com chave de dados.
  • OCI_TRACING_ENABLED: O rastreamento está ativado?
    • Quando recuperado das variáveis de ambiente, retorna 0 ou 1.
    • Quando recuperado do contexto da função, retorna true ou false conforme apropriado para o idioma usado.

Política do Serviço IAM Obrigatória para Ativar o Rastreamento

Para poder ativar o rastreamento, o grupo ao qual você pertence deve ter permissão para acessar domínios do APM existentes ou criar domínios do APM. Além disso, o OCI Functions deve ter permissão para acessar domínios do APM. Consulte Instruções de Política para Conceder ao Serviço OCI Functions e aos Usuários do OCI Functions Acesso aos Recursos de Rastreamento.

Usando a Console para Ativar Rastreamento e Exibir Rastreamentos de Funções

São necessárias algumas etapas para ativar o rastreamento e exibir rastreamentos de funções para o serviço Oracle Cloud Infrastructure Application Performance Monitoring (APM). Primeiro, ative o rastreamento do aplicativo que contém a função. Em seguida, ative o rastreamento de uma ou mais funções. Em seguida, você pode exibir rastreamentos de função no Explorador de Rastreamento do APM.

Usando a Console para Ativar o Rastreamento

Para ativar o rastreamento, siga estas etapas.

  1. Na página da lista Aplicativos, selecione o aplicativo com funções para as quais você deseja ativar o rastreamento. Se precisar de ajuda para localizar a página de lista ou o aplicativo, consulte Listando Aplicativos.
  2. Selecione a guia Monitoramento e vá para a seção Rastreamentos.
  3. Para ativar o rastreamento do aplicativo:
    1. No menu Ações Menu Ações, selecione Configurar e especifique:
      • Compartimento: O compartimento no qual criar o rastreamento. Por padrão, o compartimento atual.
      • Domínio do APM: O domínio do APM (definido no serviço Application Performance Monitoring) no qual o rastreamento será criado. Para usar um Domínio do APM existente, selecione um domínio do APM existente na lista. Ou, para criar um novo domínio do APM, selecione Domínio do APM. Para obter mais informações sobre domínios do APM, consulte Conceitos Básicos do Serviço Application Performance Monitoring.
        Observação

        O Domínio do APM precisa ter chaves de dados públicas e privadas para que o rastreamento de funções funcione. Se as chaves não existirem, crie-as por meio da interface do console.
    2. Selecione Ativar Rastreamento para ativar o rastreamento do aplicativo.

    Com o rastreamento ativado para o aplicativo Functions, agora você pode ativar o rastreamento para uma ou mais funções no aplicativo.

  4. Para ativar o rastreamento de funções específicas no aplicativo:
    1. Selecione a guia Funções.
    2. Selecione a opção Ativar Rastreamento no menu Ações Menu Ações das funções para as quais você deseja ativar o rastreamento.

      A opção Ativar Rastreamento só será mostrada se você tiver ativado o rastreamento anteriormente para o aplicativo. Observe o seguinte:

      • Se a opção Ativar Rastreamento não for mostrada, você deverá ativar o rastreamento para o aplicativo. Se você ainda não ativou o rastreamento para o aplicativo, consulte a etapa anterior.
      • Se você tiver ativado anteriormente o rastreamento do aplicativo, mas desativado posteriormente, um link Ativar rastreamento do aplicativo será mostrado. Selecione o link Ativar rastreamento do aplicativo para reativar o rastreamento do aplicativo (consulte a etapa anterior). Depois de reativar o rastreamento do aplicativo, você poderá ativar o rastreamento de funções específicas.

Quando você tiver ativado o rastreamento para o aplicativo e uma ou mais funções, poderá exibir os rastreamentos de funções.

Usando a Console para Exibir Rastreamentos de Funções

Para exibir rastreamentos de funções que têm rastreamento ativado:

  1. Na página da lista Aplicativos, selecione o aplicativo que contém as funções para as quais você deseja exibir rastreamentos. Se precisar de ajuda para localizar a página de lista ou o aplicativo, consulte Listando Aplicativos.
  2. Para ver rastreamentos de funções:
    1. Para ver rastreamentos de todas as funções que têm rastreamento ativado no aplicativo:
      1. Selecione a guia Monitoramento e vá para a seção Rastreamentos.
      2. Selecione o nome do rastreamento.
        Observação

        Um nome de rastreamento só será mostrado se você já tiver ativado o rastreamento para o aplicativo.
    2. Para ver o rastreamento de uma função específica que tem o rastreamento ativado:
      1. Selecione a guia Funções.
      2. Selecione a opção Exibir Rastreamento no menu Ações Menu Ações da função para a qual você deseja exibir o rastreamento.
        Observação

        A opção Exibir Rastreamento só será mostrada se você já tiver ativado o rastreamento para a função.

    Os rastreamentos das funções selecionadas são mostrados no Explorador de Rastreamento do APM. Por padrão, um rastreamento é mostrado para o intervalo de chamada de função padrão e qualquer intervalo personalizado definido para a função.

  3. No Trace Explorer do APM:
    1. Selecione um rastreamento para ver os intervalos desse rastreamento.
    2. Selecione um intervalo para ver os detalhes capturados para esse intervalo.

    Para obter mais informações sobre como usar o Explorador de Rastreamento do APM, consulte Usar o Explorador de Rastreamento.

Rastreando uma Cadeia de Funções

Por padrão, o rastreamento de função fornece um rastreamento para uma chamada de função inteira. No entanto, geralmente com aplicativos de nuvem modernos, você precisa encadear chamadas de função. O rastreamento do OCI Functions fornece a capacidade de rastrear a execução de uma função chamada por outra função. Essa capacidade significa que você pode examinar a execução de cada função em uma cadeia de chamadas em uma única árvore de intervalos no explorador de rastreamento do APM.

Para rastrear uma cadeia de funções, você precisa propagar os cabeçalhos X-B3 X-B3-TraceId, X-B3-SpanId, X-B3-ParentSpanId e X-B3-Sampled na solicitação de chamada de função do seu código de função.

Após a execução da função, os dados de rastreamento de suas funções são coletados e disponíveis no Explorador de Rastreamento do APM. Para obter mais informações sobre como usar o Explorador de Rastreamento do APM, consulte Usar o Explorador de Rastreamento.

Rastreando uma Cadeia de Funções com o Python

Aqui está um exemplo de como você pode rastrear uma cadeia de funções. Se quiser experimentar este exemplo, você precisará criar duas funções de amostra. Siga estas etapas para configurar suas funções.

  1. Crie sua função Python de rastreamento: fn init --runtime python <your-function-name-1>
  2. Crie o seu "Hello World!" Função Python: fn init --runtime python <your-function-name-2>
  3. Implante as duas funções: fn -v deploy --app <app-name>
  4. Obtenha o OCID das segunda funções e chame o ponto final: fn inspect function your-app-name your-function-name-2
  5. Crie um arquivo JSON para transmitir as informações necessárias para a primeira função. Por exemplo, seu arquivo test.json pode ter esta aparência:
    
    {
        "function_ocid": "ocid1.fnfunc.oc1.iad.aaaaaaaaxxxxxxxxxxx",
        "function_endpoint": "https://xxxxxxxxx.us-ashburn-1.functions.oci.oraclecloud.com",
        "function_body": "",
        "__comment": "Alternatively, you can set function_body to { \"name\": \"Oracle\" }"
    }                    
                    
  6. Quando a primeira função é chamada, você pode especificar as informações da segunda função usando test.json: fn invoke <app-name> <your-function-name-1> < test.json

Agora você está pronto para atualizar a primeira função com as atualizações de código necessárias.

Configurar Pacotes

Atualize o arquivo requirements.txt para incluir os seguintes pacotes:


fdk
oci
            

Salvar o arquivo.

Atualize seu Código de Função para Propagar os Cabeçalhos X-B3

A função Python chama a função handler e transmite as informações JSON do comando de chamada. A função handler é dividida em vários blocos pequenos para simplificar. O arquivo de origem completo é fornecido na parte inferior desta seção.

Carregar os Dados JSON

Nesta primeira parte, os dados JSON são carregados da chamada de função.


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
            

Criar Cliente de Chamada e Coletar Informações do Cabeçalho

Crie o cliente de chamada do Functions usando os controladores de recursos do OCI Python SDK e do Functions. Em seguida, recupere o tracing_context e extraia as informações necessárias para criar os cabeçalhos 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()
            

Propagar os Cabeçalhos X-B3

O OCI Python SDK permite definir cabeçalhos personalizados. Use esta técnica para passar os cabeçalhos X-B3 para a segunda chamada de função. As informações do cabeçalho são especificadas para trace_id, span_id, parent_span_id e is_sampled. Finalmente, a segunda função é chamada com client e a resposta é passada para a resposta dessa função.


    # 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"}
    )
            
Verificar Código de Origem da Função Concluída

Aqui está o código-fonte completo para a função Python de amostra.


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

Adicionando Intervalos Personalizados a Funções

Com o rastreamento de função ativado, o intervalo de chamada de função padrão fornece um rastreamento para toda a chamada de função. O intervalo padrão pode fornecer boas informações, mas ao investigar seu código, talvez você queira se aprofundar. Intervalos personalizados são adicionados diretamente ao código e permitem que você defina intervalos para um método ou um bloco de código. Os dados resultantes fornecem uma imagem melhor de sua função à medida que ela é executada.

Para poder usar intervalos personalizados, ative o rastreamento do seu aplicativo e funções usando o serviço Oracle Cloud Infrastructure Application Performance Monitoring (APM). Para configurar o rastreamento, você deve:

  1. Configure uma política para conceder ao serviço OCI Functions permissão para acessar domínios do APM, se a política ainda não existir (consulte Instruções de Política para Conceder ao Serviço OCI Functions e aos Usuários do OCI Functions Acesso aos Recursos de Rastreamento).
  2. Configure um domínio do APM.
  3. Ative o rastreamento para o aplicativo Functions e selecione o domínio do APM que você criou.
  4. Ative o rastreamento para uma ou mais funções.

Essas etapas já foram abordadas. No entanto, mais algumas coisas são necessárias para intervalos personalizados:

  • Selecione uma biblioteca de clientes de rastreamento distribuído, por exemplo, Zipkin.
  • Adicione bibliotecas do cliente às suas dependências de função.
  • Em seu código de função, use a variável de contexto da função OCI_TRACING_ENABLED para verificar se o rastreamento está ativado.
  • Em seu código de função, use a variável de contexto da função OCI_TRACE_COLLECTOR_URL para enviar seus intervalos personalizados para seu domínio do APM.
  • Adicione instrumentação ao seu código de função.
Observação

Para usar intervalos personalizados, você deve ter as seguintes versões mínimas das FDKs do Fn Project:

  • FDK DO Java: 1.0.129
  • Python FDK: 0.1.22
  • Nó FDK: 0.0.20
Adicionando Intervalos Personalizados a Funções Java

Veja um exemplo de como usar o Zipkin para adicionar intervalos personalizados à sua função Java. Se quiser testar este exemplo, você poderá criar uma função Java "Hello World!" e adicionar um código de intervalo personalizado. Para criar uma função de amostra:

  • Crie uma função Java: fn init --runtime java apm-fn-java
  • Para simplificar, remova o diretório src/test.

Configurar o Maven

Adicione as dependências a seguir à seção <dependencies> do arquivo pom.xml do Maven.


<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-sender-urlconnection</artifactId>
    <version>2.16.3</version>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
    <version>2.16.3</version>
</dependency>
<dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave</artifactId>
    <version>5.13.3</version>
</dependency>
    <dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave-core</artifactId>
    <version>4.13.6</version>
</dependency>
            

Salvar o arquivo.

O Método HandleRequest

As observações sobre o método seguem o código-fonte 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();
    }
            
  • O objeto TracingContext tracingConext transmite todas as informações relacionadas ao APM necessárias para fazer conexões com o serviço do APM.
  • O método intializeZipkin é chamado, o que atualiza o tracingContext e cria um objeto tracer que é usado para configurar intervalos personalizados.
  • Um span é criado para o intervalo personalizado pai. Em seguida, três métodos são chamados no escopo do intervalo pai.
  • Observe que no bloco finally todos os objetos de rastreamento são fechados.

O Método initializeZipkin

As observações sobre o método intializeZipkin seguem o código-fonte.

     
    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();
    }   
            
  • O traceContext é informado para criar todos os objetos usados para criar intervalos personalizados.
  • O apmURL é recuperado do método getTraceCollectorURL(). O URL é o ponto final do domínio do APM e é usado para criar o objeto tracer que cria os intervalos personalizados.
  • Um construtor usa zipkinSpanHandler e o nome do serviço para criar um objeto tracer. Este objeto tracer é usado para criar intervalos personalizados.

Criando Intervalos Personalizados

Com o objeto tracer inicializado, intervalos personalizados podem ser criados.


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();
    }
}
            
  • O método method1 cria um intervalo personalizado chamado "Method1".
Verificar Código de Origem da Função Concluída

Aqui está o código-fonte completo da função de rastreamento Java de amostra.


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();
        }
    }
}
            
Adicionando Intervalos Personalizados a Funções Python

Veja um exemplo de como usar o Zipkin para adicionar intervalos personalizados à sua função Python. Se você quiser experimentar este exemplo, você pode criar uma função Python "Hello World!" e adicionar código de intervalo personalizado. Para criar uma função de amostra:

  • Crie uma função Python: fn init --runtime python apm-fn-python

Configurar Pacotes

Atualize o arquivo requirements.txt para incluir os seguintes pacotes:


fdk
requests
py_zipkin
            

Salvar o arquivo.

Criando Classe de Handler e Intervalo Personalizado Pai

A função Python chama a função handler e passa no contexto da função para criar intervalos personalizados.

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"}
        )         
  • O tracing_context é informado do contexto da função e contém todas as informações necessárias para criar e configurar intervalos personalizados.
    Observação

    Se o rastreamento não estiver ativado, o contexto de rastreamento será um objeto vazio. Com um contexto de rastreamento vazio, o flag is_sampled é definido como None e py_zipkin não emite intervalos.
  • A instrução with zipkin_span é usada para criar intervalos.
    • As informações em tracing_context são usadas para obter o service_name, chamar o transport_handler e definir o zipking_attrs.
    • Um nome de intervalo personalizado é especificado apenas definindo span_name.
    • Os atributos de rastreamento necessários para o Zipkin são recuperados do contexto de rastreamento: tracing_context.zipkin_attrs().
  • Com a configuração de extensão personalizada, o bloco principal executa o código padronizado "Hello World!". Com a única exceção, uma chamada para a função example.

A Função transport_handler

A função transport_handler se comunica com o domínio do APM com mensagens sobre a execução do intervalo.

# 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"},
    )            
  • O trace_collector_url é retornado do contexto da função. Este URL fornece o ponto final de comunicação para seus intervalos personalizados para o domínio do APM.

Criando um Intervalo Personalizado na Função de Exemplo

A função de exemplo demonstra a criação de um intervalo personalizado.

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
                }
            )            
  • A instrução with zipkin_span é usada para identificar o intervalo personalizado e dar um nome a ele.
  • O bloco example_span_context gera uma exceção e retorna uma mensagem de erro.
Verificar Código de Origem da Função Concluída

Aqui está o código-fonte completo para a função de rastreamento Python de amostra.


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
                }
            )
            
Adição de Intervalos Personalizados a Funções de Nó

Veja um exemplo de como usar o Zipkin para adicionar intervalos personalizados à sua função Node.js. Se quiser testar este exemplo, você poderá criar uma função Node "Hello World!" e adicionar um código de intervalo personalizado. Para criar uma função de amostra:

  • Criar uma função de Nó: fn init --runtime node apm-fn-node

Configurar Dependências de Nó

Atualize o arquivo package.json para incluir os seguintes pacotes:


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

Salvar o arquivo.

Atualizar Método de Tratamento

As principais observações sobre o método fdk.handle seguem o código-fonte.


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

})            
            
  • O tracer é criado e, em seguida, usado para criar um intervalo personalizado pai. Em seguida, os intervalos filhos são criados para as funções fetchResource, processResource e updateResource.

Verificando a Função createOCITracer

As principais observações sobre a função seguem o código-fonte.


/**
 * 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
}
            
  • O contexto da função (ctx) é passado para essa função, que fornece as informações necessárias para estabelecer conexão com o domínio do APM. Se você seguir as chamadas de função, poderá ver como os IDs de rastreamento e os campos são criados.
Verificar Código de Origem da Função Concluída

Aqui está o código-fonte completo para a função de rastreamento de nó de amostra.



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