Exemplo de instrumentação do Go

Este documento descreve como modificar uma app Go para recolher dados de rastreio e métricas através da framework OpenTelemetry de código aberto, e como escrever registos JSON estruturados para a saída padrão. Este documento também fornece informações sobre uma app de exemplo que pode instalar e executar. A app está configurada para gerar métricas, rastreios e registos.

Para saber mais sobre a instrumentação, consulte os seguintes documentos:

Acerca do contexto

O Context do OpenTelemetry é um mecanismo para transportar valores com âmbito de execução em APIs num processo. Uma utilização importante do contexto é transportar o intervalo ativo atual para que possa ser modificado ou referenciado como o principal de quaisquer novos intervalos quando são criados. Para resumir:

  • Contexto refere-se ao mecanismo para propagar valores com âmbito de execução, incluindo o intervalo ativo atual, nas APIs num processo.

  • O Span Context é um objeto imutável em cada intervalo que inclui o ID do rastreio, o ID do intervalo e as flags e o estado do rastreio.

  • A propagação é o mecanismo que move o contexto entre serviços e processos.

A biblioteca padrão do Go context.Context também transporta valores com âmbito através dos limites da API. Normalmente, as funções do controlador num servidor recebem um Context de entrada e transmitem-no através da cadeia de chamadas a quaisquer clientes que façam pedidos de saída.

A biblioteca padrão do Go context.Context é usada como a implementação do contexto do OpenTelemetry no Go.

Antes de começar

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. Install the Google Cloud CLI.

  3. Se estiver a usar um fornecedor de identidade (IdP) externo, primeiro, tem de iniciar sessão na CLI gcloud com a sua identidade federada.

  4. Para inicializar a CLI gcloud, execute o seguinte comando:

    gcloud init
  5. Create or select a Google Cloud project.

    • Create a Google Cloud project:

      gcloud projects create PROJECT_ID

      Replace PROJECT_ID with a name for the Google Cloud project you are creating.

    • Select the Google Cloud project that you created:

      gcloud config set project PROJECT_ID

      Replace PROJECT_ID with your Google Cloud project name.

  6. Verify that billing is enabled for your Google Cloud project.

  7. Enable the Cloud Logging, Cloud Monitoring, and Cloud Trace APIs:

    gcloud services enable logging.googleapis.com monitoring.googleapis.com cloudtrace.googleapis.com
  8. Install the Google Cloud CLI.

  9. Se estiver a usar um fornecedor de identidade (IdP) externo, primeiro, tem de iniciar sessão na CLI gcloud com a sua identidade federada.

  10. Para inicializar a CLI gcloud, execute o seguinte comando:

    gcloud init
  11. Create or select a Google Cloud project.

    • Create a Google Cloud project:

      gcloud projects create PROJECT_ID

      Replace PROJECT_ID with a name for the Google Cloud project you are creating.

    • Select the Google Cloud project that you created:

      gcloud config set project PROJECT_ID

      Replace PROJECT_ID with your Google Cloud project name.

  12. Verify that billing is enabled for your Google Cloud project.

  13. Enable the Cloud Logging, Cloud Monitoring, and Cloud Trace APIs:

    gcloud services enable logging.googleapis.com monitoring.googleapis.com cloudtrace.googleapis.com
  14. Instrumente a sua app para recolher rastreios, métricas e registos

    Para instrumentar a sua app de modo a recolher dados de rastreio e métricas, e para escrever JSON estruturado para a saída padrão, siga os passos descritos nas secções subsequentes deste documento:

    1. Configure a função principal
    2. Configure o OpenTelemetry
    3. Configure o registo estruturado
    4. Adicione instrumentação ao servidor HTTP
    5. Associe intervalos de rastreio de links a registos e métricas
    6. Adicione instrumentação ao cliente HTTP
    7. Escreva registos estruturados

    Configure a função principal

    Para configurar a app para escrever registos estruturados e recolher métricas e dados de rastreio através do OpenTelemetry, atualize a função main para configurar o pacote de registo estruturado Go, slog, e para configurar o OpenTelemetry.

    O seguinte exemplo de código ilustra uma função main que chama duas funções auxiliares, setupLogging() e setupOpenTelemetry(). Estas funções auxiliares configuram o pacote de registo e o OpenTelemetry.

    Para ver o exemplo completo, clique em Mais e, de seguida, selecione Ver no GitHub.

    func main() {
    	ctx := context.Background()
    
    	// Setup logging
    	setupLogging()
    
    	// Setup metrics, tracing, and context propagation
    	shutdown, err := setupOpenTelemetry(ctx)
    	if err != nil {
    		slog.ErrorContext(ctx, "error setting up OpenTelemetry", slog.Any("error", err))
    		os.Exit(1)
    	}
    
    	// Run the http server, and shutdown and flush telemetry after it exits.
    	slog.InfoContext(ctx, "server starting...")
    	if err = errors.Join(runServer(), shutdown(ctx)); err != nil {
    		slog.ErrorContext(ctx, "server exited with error", slog.Any("error", err))
    		os.Exit(1)
    	}
    }
    

    Depois de configurar o pacote de registo, para associar os registos aos dados de rastreio, tem de transmitir o Context do Go para o registador. Para mais informações, consulte a secção Escreva registos estruturados deste documento.

    Configure o OpenTelemetry

    Para recolher e exportar rastreios e métricas através do protocolo OTLP, configure as instâncias TracerProvider globais e MeterProvider. O seguinte exemplo de código ilustra a função setupOpenTelemetry, que é chamada a partir da função main:

    func setupOpenTelemetry(ctx context.Context) (shutdown func(context.Context) error, err error) {
    	var shutdownFuncs []func(context.Context) error
    
    	// shutdown combines shutdown functions from multiple OpenTelemetry
    	// components into a single function.
    	shutdown = func(ctx context.Context) error {
    		var err error
    		for _, fn := range shutdownFuncs {
    			err = errors.Join(err, fn(ctx))
    		}
    		shutdownFuncs = nil
    		return err
    	}
    
    	// Configure Context Propagation to use the default W3C traceparent format
    	otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
    
    	// Configure Trace Export to send spans as OTLP
    	texporter, err := autoexport.NewSpanExporter(ctx)
    	if err != nil {
    		err = errors.Join(err, shutdown(ctx))
    		return
    	}
    	tp := trace.NewTracerProvider(trace.WithBatcher(texporter))
    	shutdownFuncs = append(shutdownFuncs, tp.Shutdown)
    	otel.SetTracerProvider(tp)
    
    	// Configure Metric Export to send metrics as OTLP
    	mreader, err := autoexport.NewMetricReader(ctx)
    	if err != nil {
    		err = errors.Join(err, shutdown(ctx))
    		return
    	}
    	mp := metric.NewMeterProvider(
    		metric.WithReader(mreader),
    	)
    	shutdownFuncs = append(shutdownFuncs, mp.Shutdown)
    	otel.SetMeterProvider(mp)
    
    	return shutdown, nil
    }
    

    O exemplo de código anterior configura o TextMapPropagator global para usar o formato W3C Trace Context para propagar o contexto de rastreio. Esta configuração garante que os intervalos têm a relação principal-secundário correta numa rastreabilidade.

    Para garantir que toda a telemetria pendente é descarregada e que as ligações são fechadas de forma adequada, a função setupOpenTelemetry devolve uma função denominada shutdown, que executa essas ações.

    Configure o registo estruturado

    Para incluir as informações de rastreio como parte dos registos formatados em JSON escritos na saída padrão, configure o pacote de registo estruturado do Go, slog. O seguinte exemplo de código ilustra a função setupLogging, que é chamada a partir da função main:

    func setupLogging() {
    	// Use json as our base logging format.
    	jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: replacer})
    	// Add span context attributes when Context is passed to logging calls.
    	instrumentedHandler := handlerWithSpanContext(jsonHandler)
    	// Set this handler as the global slog handler.
    	slog.SetDefault(slog.New(instrumentedHandler))
    }
    

    O código anterior chama a função handlerWithSpanContext, que extrai informações da instância Context e adiciona essas informações como atributos a um registo. Estes atributos podem ser usados para correlacionar um registo com um rastreio:

    • logging.googleapis.com/trace: nome do recurso do rastreio associado à entrada do registo.
    • logging.googleapis.com/spanId: o ID do intervalo com o rastreio associado à entrada do registo.
    • logging.googleapis.com/trace_sampled: o valor deste campo tem de ser true ou false.

    Para mais informações sobre estes campos, consulte a LogEntry estrutura.

    func handlerWithSpanContext(handler slog.Handler) *spanContextLogHandler {
    	return &spanContextLogHandler{Handler: handler}
    }
    
    // spanContextLogHandler is a slog.Handler which adds attributes from the
    // span context.
    type spanContextLogHandler struct {
    	slog.Handler
    }
    
    // Handle overrides slog.Handler's Handle method. This adds attributes from the
    // span context to the slog.Record.
    func (t *spanContextLogHandler) Handle(ctx context.Context, record slog.Record) error {
    	// Get the SpanContext from the context.
    	if s := trace.SpanContextFromContext(ctx); s.IsValid() {
    		// Add trace context attributes following Cloud Logging structured log format described
    		// in https://cloud.google.com/logging/docs/structured-logging#special-payload-fields
    		record.AddAttrs(
    			slog.Any("logging.googleapis.com/trace", s.TraceID()),
    		)
    		record.AddAttrs(
    			slog.Any("logging.googleapis.com/spanId", s.SpanID()),
    		)
    		record.AddAttrs(
    			slog.Bool("logging.googleapis.com/trace_sampled", s.TraceFlags().IsSampled()),
    		)
    	}
    	return t.Handler.Handle(ctx, record)
    }
    
    func replacer(groups []string, a slog.Attr) slog.Attr {
    	// Rename attribute keys to match Cloud Logging structured log format
    	switch a.Key {
    	case slog.LevelKey:
    		a.Key = "severity"
    		// Map slog.Level string values to Cloud Logging LogSeverity
    		// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
    		if level := a.Value.Any().(slog.Level); level == slog.LevelWarn {
    			a.Value = slog.StringValue("WARNING")
    		}
    	case slog.TimeKey:
    		a.Key = "timestamp"
    	case slog.MessageKey:
    		a.Key = "message"
    	}
    	return a
    }
    

    Adicione instrumentação ao servidor HTTP

    Para adicionar a instrumentação de rastreio e métricas aos pedidos processados pelo servidor HTTP, use o OpenTelemetry. O exemplo seguinte usa o controlador otelhttp para propagar o contexto e para a instrumentação de rastreio e métricas:

    func runServer() error {
    	handleHTTP("/single", handleSingle)
    	handleHTTP("/multi", handleMulti)
    
    	return http.ListenAndServe(":8080", nil)
    }
    
    // handleHTTP handles the http HandlerFunc on the specified route, and uses
    // otelhttp for context propagation, trace instrumentation, and metric
    // instrumentation.
    func handleHTTP(route string, handleFn http.HandlerFunc) {
    	instrumentedHandler := otelhttp.NewHandler(otelhttp.WithRouteTag(route, handleFn), route)
    
    	http.Handle(route, instrumentedHandler)
    }
    

    No código anterior, o controlador otelhttp usa as instâncias globais TracerProvider, MeterProvider e TextMapPropagator. A função setupOpenTelemetry configura estas instâncias.

    Associe intervalos de rastreio de links a registos e métricas

    Para associar intervalos do servidor e do cliente, e para associar métricas e registos, transmita a instância Context do Go ao pedido HTTP e quando escrever registos. O exemplo seguinte ilustra um controlador de rotas que extrai a instância do Go Context e passa essa instância para o registador e para a função callSingle, que faz um pedido HTTP de saída:

    func handleMulti(w http.ResponseWriter, r *http.Request) {
    	subRequests := 3 + rand.Intn(4)
    	// Write a structured log with the request context, which allows the log to
    	// be linked with the trace for this request.
    	slog.InfoContext(r.Context(), "handle /multi request", slog.Int("subRequests", subRequests))
    
    	err := computeSubrequests(r, subRequests)
    	if err != nil {
    		http.Error(w, err.Error(), http.StatusBadGateway)
    		return
    	}
    
    	fmt.Fprintln(w, "ok")
    }
    

    No código anterior, a chamada de função r.Context() obtém o Go Context do pedido HTTP.

    Adicione instrumentação ao cliente HTTP

    Para injetar o contexto de rastreio em pedidos HTTP de saída e adicionar instrumentação de rastreio e métricas, chame a função otelhttp.Get. No exemplo seguinte, a função callSingle realiza esta ação:

    func callSingle(ctx context.Context) error {
    	// otelhttp.Get makes an http GET request, just like net/http.Get.
    	// In addition, it records a span, records metrics, and propagates context.
    	res, err := otelhttp.Get(ctx, "http://localhost:8080/single")
    	if err != nil {
    		return err
    	}
    
    	return res.Body.Close()
    }
    

    No código anterior, o controlador otelhttp usa as instâncias globais TracerProvider, MeterProvider e TextMapPropagator. A função setupOpenTelemetry configura estas instâncias.

    Escreva registos estruturados

    Para escrever registos estruturados que tenham um link para um rastreio, use o pacote de registo estruturado do Go, slog, e transmita a instância do Go Context para o registador. A instância Context do Go é necessária quando quer associar um registo a um intervalo. Por exemplo, a declaração seguinte mostra como chamar o método InfoContext para slog e ilustra como adicionar o campo subRequests à instância JSON:

    slog.InfoContext(r.Context(), "handle /multi request", slog.Int("subRequests", subRequests))
    

    Execute uma app de exemplo configurada para recolher telemetria

    A app de exemplo usa formatos neutros de fornecedores, incluindo JSON para registos e OTLP para métricas e rastreios. Para encaminhar a telemetria para Google Cloud, este exemplo usa o OpenTelemetry Collector configurado com exportadores da Google. O gerador de carga na app envia pedidos para as rotas da app.

    Transfira e implemente a app

    Para executar o exemplo, faça o seguinte:

    1. In the Google Cloud console, activate Cloud Shell.

      Activate Cloud Shell

      At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

    2. Clone o repositório:

      git clone https://github.com/GoogleCloudPlatform/golang-samples
      
    3. Aceda ao diretório OpenTelemetry:

      cd golang-samples/opentelemetry/instrumentation
      
    4. Crie e execute o exemplo:

      docker compose up --abort-on-container-exit
      

      Se não estiver a usar o Cloud Shell, execute a aplicação com a variável de ambiente GOOGLE_APPLICATION_CREDENTIALS a apontar para um ficheiro de credenciais. As Credenciais padrão da aplicação fornecem um ficheiro de credenciais em $HOME/.config/gcloud/application_default_credentials.json.

      # Set environment variables
      export GOOGLE_CLOUD_PROJECT="PROJECT_ID"
      export GOOGLE_APPLICATION_CREDENTIALS="$HOME/.config/gcloud/application_default_credentials.json"
      export USERID="$(id -u)"
      
      # Run
      docker compose -f docker-compose.yaml -f docker-compose.creds.yaml up --abort-on-container-exit
      
    5. Veja as suas métricas

      A instrumentação do OpenTelemetry na app de exemplo gera métricas do Prometheus que pode ver através do Explorador de métricas:

      • Prometheus/http_server_duration/histogram registam a duração dos pedidos do servidor e armazenam os resultados num histograma.

      • Prometheus/http_server_request_content_length_total/counter registra o comprimento do conteúdo do pedido para as rotas HTTP /multi e /single. As medições desta métrica são cumulativas, o que significa que cada valor representa o total desde o início da recolha de valores.

      • Prometheus/http_server_response_content_length_total/counter regista o comprimento do conteúdo de resposta para as rotas HTTP /multi e /single. As medições desta métrica são cumulativas.

      Para ver as métricas geradas pela app de amostra, faça o seguinte:
      1. Na Google Cloud consola, aceda à página  Explorador de métricas:

        Aceda ao Metrics Explorer

        Se usar a barra de pesquisa para encontrar esta página, selecione o resultado cujo subtítulo é Monitorização.

      2. Na barra de ferramentas da Google Cloud consola, selecione o seu Google Cloud projeto. Para configurações do App Hub, selecione o projeto anfitrião do App Hub ou o projeto de gestão da pasta com apps ativadas.
      3. No elemento Métrica, expanda o menu Selecionar uma métrica, introduza http_server na barra de filtro e, de seguida, use os submenus para selecionar um tipo de recurso e uma métrica específicos:
        1. No menu Recursos ativos, selecione Alvo do Prometheus.
        2. No menu Categorias de métricas ativas, selecione Http.
        3. No menu Métricas ativas, selecione uma métrica.
        4. Clique em Aplicar.
      4. Configure a forma como os dados são vistos.

        Quando as medições de uma métrica são cumulativas, o explorador de métricas normaliza automaticamente os dados medidos pelo período de alinhamento, o que resulta na apresentação de uma taxa no gráfico. Para mais informações, consulte o artigo Tipos, tipos e conversões.

        Quando são medidos valores inteiros ou duplos, como com as duas métricas counter, o explorador de métricas soma automaticamente todas as séries cronológicas. Para ver os dados das rotas HTTP /multi e /single, defina o primeiro menu da entrada Agregação como Nenhuma.

        Para mais informações sobre como configurar um gráfico, consulte o artigo Selecione métricas quando usar o explorador de métricas.

      Veja os seus rastreios

      Pode demorar vários minutos até que os dados de rastreio estejam disponíveis. Por exemplo, quando o seu projeto recebe dados de rastreio, o Google Cloud Observability pode ter de criar uma base de dados para armazenar esses dados. A criação da base de dados pode demorar alguns minutos e, durante esse período, não estão disponíveis dados de rastreio para visualização.

      Para ver os dados de rastreio, faça o seguinte:

      1. Na Google Cloud consola, aceda à página Explorador de rastreios:

        Aceda ao Explorador de rastreios

        Também pode encontrar esta página através da barra de pesquisa.

      2. Na secção de tabelas da página, selecione uma linha com o nome do intervalo /multi.
      3. No gráfico de Gantt no painel Detalhes do rastreio, selecione o intervalo etiquetado como /multi.

        É aberto um painel que apresenta informações sobre o pedido HTTP. Estes detalhes incluem o método, o código de estado, o número de bytes e o agente do utilizador do autor da chamada.

      4. Para ver os registos associados a este rastreio, selecione o separador Registos e eventos.

        O separador mostra registos individuais. Para ver os detalhes da entrada do registo, expanda a entrada do registo. Também pode clicar em Ver registos e ver o registo através do Explorador de registos.

      Para mais informações sobre como usar o explorador do Cloud Trace, consulte o artigo Encontre e explore rastreios.

      Veja os seus registos

      No Explorador de registos, pode inspecionar os seus registos e também ver rastreios associados, quando existirem.

      1. Na Google Cloud consola, aceda à página Explorador de registos:

        Aceda ao Explorador de registos

        Se usar a barra de pesquisa para encontrar esta página, selecione o resultado cuja legenda é Registo.

      2. Localize um registo com a descrição de handle /multi request.

        Para ver os detalhes do registo, expanda a entrada do registo. No campo jsonPayload, existe uma entrada com a etiqueta subRequests. Esta entrada foi adicionada por uma declaração na função handleMulti.

      3. Clique em Rastreios numa entrada de registo com a mensagem "handle /multi request" e, de seguida, selecione Ver detalhes do rastreio.

        É aberto um painel Detalhes do rastreio que apresenta o rastreio selecionado.

        Os dados de registo podem estar disponíveis vários minutos antes dos dados de rastreio. Se encontrar um erro ao ver os dados de rastreio pesquisando um rastreio por ID ou seguindo os passos desta tarefa, aguarde um ou dois minutos e tente novamente a ação.

      Para mais informações sobre a utilização do Explorador de registos, consulte o artigo Veja registos através do Explorador de registos.

      O que se segue?