Gerar traces e métricas com o Go

Neste documento, descrevemos como modificar um aplicativo Go para coletar dados de trace e métrica usando o framework de código aberto OpenTelemetry e como gravar registros JSON estruturados para saída padrão. Este documento também fornece informações sobre um app de amostra que pode ser instalado e executado. O app está configurado para gerar métricas, traces e registros.

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

Sobre o contexto

O Context do OpenTelemetry é um mecanismo para transportar valores com escopo de execução entre APIs dentro de um processo. Um uso importante do contexto é carregar o período ativo atual para que ele possa ser modificado ou referenciado como pai de qualquer novo período quando ele é criado. Para resumir:

  • Contexto refere-se ao mecanismo para propagar valores com escopo de execução, incluindo o período ativo atual, entre APIs dentro de um processo.

  • O contexto do período é um objeto imutável em todos os períodos que inclui o ID do trace, o ID do período e as sinalizações e o estado do trace.

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

O context.Context da biblioteca padrão do Go também carrega valores de escopo nos limites da API. Normalmente, as funções de gerenciador em um servidor recebem uma Context de entrada e a transmitem pela cadeia de chamadas para todos os clientes que fazem solicitações de saída.

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

Antes de começar

Ative as APIs Cloud Logging, Cloud Monitoring, and Cloud Trace.

Ative as APIs

Instrumentar o app para coletar traces, métricas e registros

Para instrumentar o app para coletar dados de rastreamento e métrica e gravar um JSON estruturado para saída padrão, siga as etapas a seguir, conforme descrito nas próximas seções deste documento:

  1. Configurar a função principal
  2. Configurar o OpenTelemetry
  3. Configurar a geração de registros estruturados
  4. Adicionar instrumentação ao servidor HTTP
  5. Vincular períodos de trace a registros e métricas
  6. Adicionar instrumentação ao cliente HTTP
  7. Gravar registros estruturados

Configurar a função principal

Para configurar o app para gravar registros estruturados e coletar métricas e dados de rastreamento usando o OpenTelemetry, atualize a função main para configurar o pacote de geração de registros estruturados do Go, slog, e para configurar o OpenTelemetry.

O exemplo de código a seguir ilustra uma função main que chama duas funções auxiliares, setupLogging() e setupOpenTelemetry(). Essas funções auxiliares configuram o pacote de geração de registros e o OpenTelemetry.

Para conferir o exemplo completo, clique em Mais e 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 geração de registros, é necessário transmitir o Context do Go para o logger para vincular seus registros aos dados de rastreamento. Para mais informações, consulte a seção Gravar registros estruturados deste documento.

Configurar o OpenTelemetry

Para coletar e exportar traces e métricas usando o protocolo OTLP (em inglês), configure as instâncias globais TracerProvider e MeterProvider. O exemplo de código a seguir ilustra a função setupOpenTelemetry, que é chamada na 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 Contexto de Trace do W3C para propagar o contexto de rastro. Essa configuração garante que os períodos tenham o relacionamento pai-filho correto em um trace.

Para garantir que toda a telemetria pendente seja limpa e que as conexões sejam fechadas corretamente, a função setupOpenTelemetry retorna uma função chamada shutdown, que executa essas ações.

Configurar a geração de registros estruturados

Para incluir as informações de trace como parte dos registros formatados em JSON gravados na saída padrão, configure o pacote de geração de registros estruturados do Go, slog. O exemplo de código a seguir ilustra a função setupLogging, que é chamada na 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 as adiciona como atributos a um registro. Esses atributos podem ser usados para correlacionar um registro com um trace:

  • logging.googleapis.com/trace: nome do recurso do trace associado à entrada de registro.
  • logging.googleapis.com/spanId: o ID do período com o trace associado à entrada de registro.
  • logging.googleapis.com/trace_sampled: o valor desse campo precisa ser true ou false.

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

func handlerWithSpanContext(handler slog.Handler) *spanContextLogHandler {
	return &spanContextLogHandler{Handler: handler}
}

// spanContextLogHandler is an 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 golang 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
}

Adicionar instrumentação ao servidor HTTP

Para adicionar instrumentação de rastreamento e métrica às solicitações processadas pelo servidor HTTP, use o OpenTelemetry. O exemplo a seguir usa o gerenciador otelhttp para propagar o contexto e para instrumentação de rastreamento e métrica:

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 gerenciador otelhttp usa as instâncias globais TracerProvider, MeterProvider e TextMapPropagator. A função setupOpenTelemetry configura essas instâncias.

Vincular períodos de trace a registros e métricas

Para vincular períodos do servidor e do cliente e associar métricas e registros, transmita a instância Context do Go para a solicitação HTTP e ao gravar registros. O exemplo a seguir ilustra um gerenciador de rota que extrai a instância Context do Go e a transmite para o registrador e a função callSingle, que faz uma solicitação 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() recupera o Context do Go da solicitação HTTP.

Adicionar instrumentação ao cliente HTTP

Para injetar o contexto de rastreamento em solicitações HTTP de saída e adicionar instrumentação de trace e métrica, chame a função otelhttp.Get. No exemplo a seguir, a função callSingle executa essa 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 gerenciador otelhttp usa as instâncias globais TracerProvider, MeterProvider e TextMapPropagator. A função setupOpenTelemetry configura essas instâncias.

Gravar registros estruturados

Para gravar registros estruturados vinculados a um trace, use o pacote de geração de registros estruturados do Go, slog, e transmita a instância Context do Go para o logger. A instância Context do Go é necessária para vincular um registro a um período. Por exemplo, a instrução a seguir 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))

Executar um app de exemplo configurado para coletar telemetria

O aplicativo de exemplo usa formatos neutros em relação a fornecedores, incluindo JSON para registros e OTLP para métricas e traces. Para rotear a telemetria para o Google Cloud, este exemplo usa o Collector do OpenTelemetry configurado com os exportadores do Google. O gerador de carga no app emite solicitações para as rotas dele.

Fazer o download e implantar o app

Para executar a amostra:

  1. No Console do Google Cloud, ative o Cloud Shell.

    Ativar o Cloud Shell

    Na parte inferior do Console do Google Cloud, uma sessão do Cloud Shell é iniciada e exibe um prompt de linha de comando. O Cloud Shell é um ambiente shell com a CLI do Google Cloud já instalada e com valores já definidos para o projeto atual. A inicialização da sessão pode levar alguns segundos.

  2. Clone o repositório:

    git clone https://github.com/GoogleCloudPlatform/golang-samples
    
  3. Acesse o diretório do OpenTelemetry:

    cd golang-samples/opentelemetry/instrumentation
    
  4. Crie e execute a amostra:

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

    Se você não estiver usando o Cloud Shell, execute o aplicativo com a variável de ambiente GOOGLE_APPLICATION_CREDENTIALS apontando para um arquivo de credenciais. O Application Default Credentials fornece um arquivo 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
    

Ver suas métricas

A instrumentação do OpenTelemetry no app de amostra gera métricas do Prometheus, que podem ser visualizadas usando o Metrics Explorer:

  • Prometheus/http_server_duration/histogram registra a duração das solicitações do servidor e armazena os resultados em um histograma.

  • Prometheus/http_server_request_content_length_total/counter registra o tamanho do conteúdo da solicitação para as rotas HTTP /multi e /single. As medidas dessa métrica são cumulativas, o que significa que cada valor representa o total desde o início da coleta de valores.

  • Prometheus/http_server_response_content_length_total/counter registra o tamanho do conteúdo da resposta para as rotas HTTP /multi e /single. As medições dessa métrica são cumulativas.

Para visualizar as métricas geradas pelo app de exemplo, faça o seguinte:
  1. No Console do Google Cloud, acesse a página do  Metrics Explorer:

    Acesse o Metrics explorer

    Se você usar a barra de pesquisa para encontrar essa página, selecione o resultado com o subtítulo Monitoramento.

  2. No elemento Metric, expanda o menu Selecionar uma métrica, digite http_server na barra de filtro e use os submenus para selecionar um tipo de recurso e métrica específicos:
    1. No menu Active resources, selecione Prometheus Target.
    2. No menu Categorias de métrica ativas, selecione Instância.
    3. No menu Métricas ativas, escolha uma opção.
    4. Clique em Aplicar.
  3. Configure a visualização dos dados.

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

    Quando valores inteiros ou duplos são medidos, como acontece com as duas métricas counter, o Metrics Explorer soma automaticamente todas as séries temporais. Para visualizar os dados das rotas HTTP /multi e /single, defina o primeiro menu da entrada Agregação como Nenhum.

    Para mais informações sobre como configurar um gráfico, consulte Selecionar métricas ao usar o Metrics Explorer.

Visualizar os rastros

Para visualizar os dados de trace, faça o seguinte:

  1. No console do Google Cloud, acesse a página Explorador de traces:

    Acessar o Explorador de traces

    Também é possível encontrar essa página usando a barra de pesquisa.

  2. No gráfico de dispersão, selecione um rastro com o URI de /multi.
  3. No diagrama de Gantt no painel Detalhes do trace, selecione o período rotulado como /multi.

    Um painel é aberto com informações sobre a solicitação HTTP. Esses detalhes incluem o método, o código de status, o número de bytes e o user agent do autor da chamada.

  4. Para visualizar os registros associados a esse trace, selecione a guia Registros e eventos.

    A guia mostra registros individuais. Para exibir os detalhes da entrada de registro, expanda a entrada de registro. Também é possível clicar em Ver registros e ver o registro usando a Análise de registros.

Para mais informações sobre como usar o explorador do Cloud Trace, consulte Encontrar e explorar traces.

Acessar os registros

Na Análise de registros, é possível inspecionar os registros e visualizar os traces associados, quando eles existirem.

  1. No console do Google Cloud, acesse a página do Análise de registros.

    Acessar a Análise de registros

    Se você usar a barra de pesquisa para encontrar essa página, selecione o resultado com o subtítulo Monitoring.

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

    Para ver os detalhes do registro, expanda a entrada. No campo jsonPayload, há uma entrada identificada como subRequests. Essa entrada foi adicionada por uma instrução na função handleMulti.

  3. Clique em Traces em uma entrada de registro com a mensagem "handle /multi request" e selecione View trace details.

    O painel Detalhes do trace é aberto e mostra o trace selecionado.

Para mais informações sobre como usar a Análise de registros, consulte Ver registros usando a Análise de registros.

A seguir