Как настроить веб-сервер Go с помощью Fiber и экспортировать трассировки в Honeycomb.io

Введение

Это руководство о том, как оснастить код Go с помощью OpenTelemetry.
Эта конкретная статья немного более самоуверенна и посвящена тому, как инструментировать веб-сервер HTTP, написанный на Go и использующий Fiber (если вы используете Gin, есть адаптация этой статьи здесь) и запускать HTTP. и запросы gRPC оттуда.
Здесь также показано, как экспортировать трассировки в Honeycomb.io, популярное решение для наблюдения, которое предлагает бесплатный план с неограниченным временем и не требует кредитной карты для регистрации.
В сопроводительном репозитории README есть минутное видео, в котором показано, как все настроить и начать просматривать промежутки.

Я постарался собрать здесь наиболее распространенные вопросы, возникающие при запуске приложения Go с помощью OpenTelemetry.
Здесь обсуждаются только следы, я не собираюсь приводить метрики OTeL или логи. » и я, наверное, напишу о них еще одну статью.

Это определенно не руководство по OpenTelemetry или Observability, а очень практичный способ начать работу с распределенной трассировкой в ​​популярном решении для наблюдения.

Отказ от ответственности: код предназначен для демонстрации того, как использовать инструменты, а не для примера написания чистого кода. Никаких модульных тестов и абстракций.

Репозиторий доступен на GitHub:



Для запуска примера кода на вашем локальном компьютере необходимы следующие требования:

  • Go (> 1.19)
  • Docker (если вы хотите запустить Docker, создайте или создайте образы)

TL;TR

Просто создайте учетную запись в Honeycomb.io, клонируйте репозиторий emanuelef/go-fiber-honeycomb (или запустите из GitHub Codespaces, там также работает docker Compose), создайте файлы .env с ключом API (вручную или запустив set_token. sh) и из корневой папки запустите:

./set_token.sh

docker compose up

Вызовите конечные точки GET и наблюдайте, как трассировки появляются в Honeycomb.io.

Если вы используете Почтальон, в репозитории есть коллекция.
Или вы можете просто запустить скрипт, который вызывает все конечные точки:

./run_http_requests.sh

Создайте учетную запись на Honeycomb.io.

С кодом в репозитории можно использовать любое решение для наблюдения, и единственные изменения, необходимые для экспорта трассировок в службу, отличную от Honeycomb.io, будут в переменных env.

Honeycomb.io на сегодняшний день предлагает очень щедрый бесплатный план без необходимости добавления кредитной карты для создания учетной записи.
Все, что нужно, — это электронное письмо, откуда вы получите ссылку для активации.

Зарегистрируйтесь здесь, подтвердите адрес электронной почты и создайте новую команду (подойдет любое название команды, все шаги по созданию команды будут представлены автоматически после регистрации).

Домашняя страница будет содержать инструкции по настройке инструментов и ключ API, который понадобится в коде.

Вы можете получить ключ API даже позже, зайдя в «Аккаунт» (внизу слева) и «Настройки».

На домашней странице начнут отображаться трассировки после инструментирования и запуска кода в репозитории.

Инструментарий

Подготовка

Получите код в репозитории emanuelef/go-fiber-honeycomb, либо клонируйте его, либо получите zip-архив.

В качестве альтернативы вы можете использовать кодовые пространства GitHub (я попробовал это в этом примере, и это работает отлично).

Запустите скрипт set_token и вставьте ключ API с веб-сайта Honeycomb.

./set_token.sh
Honeycomb API Key: *********

Это просто создаст файлы .env, необходимые для каждого приложения (они содержат ключи API, поэтому лучше не помещать их в репозитории), если это не сработает, просто скопируйте файлы .env.example в каждую папку (возможно, вы просто захотите начать с тем, что находится в корневой папке, /вторичный и/grpc-server понадобятся позже) в .env и замените your_key_here ключом API.

На этом этапе мы можем проверить, всё ли настроено правильно и трассировки экспортированы.
В терминале из корневой папки запустите:

go run main.go

 ┌───────────────────────────────────────────────────┐ 
 │                   Fiber v2.48.0                   │ 
 │               http://127.0.0.1:8080               │ 
 │                                                   │ 
 │ Handlers ............ 16  Processes ........... 1 │ 
 │ Prefork ....... Disabled  PID ............. 84354 │ 
 └───────────────────────────────────────────────────┘ 

Из другого терминала:

curl http://127.0.0.1:8080/hello

Перейдите на сайт Honeycomb.io, и через некоторое время вы увидите экспортированную трассировку.

Нажмите кнопку слева, и вы увидите трассу с одним пролетом.

Настройка поставщика и экспортера трассировки

Первым шагом является создание поставщика трассировки с экспортером.

В этой статье и в репозитории трассировки экспортируются непосредственно в общедоступную конечную точку, предоставленную Honeycomb и указанную в переменной env OTEL_EXPORTER_OTLP_ENDPOINT (https://api.honeycomb.io:443).
Использование Коллектора, вероятно, чаще встречается в производстве.

В opentelemetry_setup.go функция InitializeGlobalTracerProvider создает поставщика трассировки и экспортера. Go OpenTelemetry SDK будет использовать переменные env, определенные в файле .env. Для этих примеров необходимы только следующие:

OTEL_SERVICE_NAME=GoFiberExample
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io:443
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your_key_here

Во время создания поставщика трассировки можно добавлять собственные ресурсы, которые будут добавляться к каждому диапазону в качестве атрибутов; ресурсы также можно добавлять с помощью переменной OTEL_RESOURCE_ATTRIBUTESenv.

Проверьте Семантические соглашения, чтобы увидеть, определены ли уже типы ресурсов, которые вы хотите добавить.

Здесь можно добавить ресурсы в код:

resource, rErr := resource.Merge(
 resource.Default(),
 resource.NewWithAttributes(
  semconv.SchemaURL,
  attribute.String("environment", "test"),
  attribute.String("stack", "001"),
 ),
)

Веб-сервер Instrument Fiber с Otelfiber

Fiber может быть оснащен промежуточным программным обеспечением Otelfiber.

app := fiber.New()

app.Use(otelfiber.Middleware())

app.Get("/hello", func(c *fiber.Ctx) error {
 return c.Send(nil)
})

При добавлении промежуточного программного обеспечения otelfiber автоматически создается диапазон для каждого полученного HTTP-запроса.

Если по какой-то причине вы хотите исключить некоторые конечные точки из создания интервалов, вы можете использовать опцию otelfiber.WithNext. Если переданная функция возвращает true, промежуточное программное обеспечение otelfiber пропустит инструментирование OTeL.

 app.Use(otelfiber.Middleware(otelfiber.WithNext(func(c *fiber.Ctx) bool {
  return c.Path() == "/health"
 })))

Как показано в разделе «Подготовка», вызов GET /hello создаст диапазон.

Добавить дочерний диапазон

Вполне вероятно, что работа, выполняемая HTTP-запросом, будет включать дополнительные диапазоны.
Чтобы создать дочерний диапазон или новый корневой диапазон, вам понадобится трассировщик, вероятно, он будет один для всего приложения или один за каждую посылку)

var tracer trace.Tracer

func init() {
 // this seems to work even if the init happens before setting up the trace provider
 tracer = otel.Tracer("github.com/emanuelef/go-fiber-honeycomb")
}

Затем в коде внутри обработчика волокна

  ctx, childSpan := tracer.Start(c.UserContext(), "custom-child-span")
  time.Sleep(10 * time.Millisecond) // simulate some work
  childSpan.End()

c.UserContext() извлекает контекст из контекста волокна с информацией об открытой телеметрии (идентификатор трассировки, идентификатор участка).

GET /hello-child сгенерирует следующие промежутки:

Если ctx, переданный в tracer.Start, содержит диапазон, то вновь созданный диапазон будет дочерним элементом этого диапазона, в противном случае это будет корневой диапазон.
tracer.Start также принимает SpanStartOption, поэтому можно добавлять атрибуты или диапазон. добрый, если нужно.

Создать новый диапазон

Если необходим новый корневой диапазон (а не дочерний по отношению к какому-либо другому), процесс аналогичен созданию дочернего диапазона, но с передачей контекста, который еще не содержит этого диапазона.

ctx, span := tracer.Start(context.Background(), "timed-operation")
// Do something
span.End()

В основном коде приложения репозитория есть отметка времени, которая создает новый корневой диапазон каждую минуту.

Инструментирование исходящих HTTP-запросов

Я представлю три способа добавления промежутков для каждого исходящего HTTP-запроса (а также распространения трассировки на вызываемый веб-сервер).
В конце концов, все они создают клиент со стандартным http.Client.

Пакет Otelhttp

Самый простой способ — использовать пакет otelhttp, определенный в go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp, он оборачивает HTTP-запросы, добавляя диапазон.

В настоящее время там реализованы следующие методы:

  • Получать
  • Голова
  • Почта
  • Постформа

Внутри обработчика Fiber необходимо передать контекст c.UserContext(), в противном случае диапазон HTTP-запроса будет корневым.

externalURL := "https://pokeapi.co/api/v2/pokemon/ditto"
resp, err := otelhttp.Get(c.UserContext(), externalURL)
_, _ = io.ReadAll(resp.Body) // This is needed to close the span

Здесь небольшая ошибка: io.ReadAll на самом деле требуется для закрытия диапазона, даже если ответ не нужен.

Otelhttp также позаботится о том, чтобы Transport внедрил контекст диапазона в заголовки исходящих запросов.

В репозитории вызов GET /hello-otelhttp создаст следующие диапазоны. Для этого потребуется запустить вторичное приложение, и он используется для демонстрации того, что traceparent (один HTTP-заголовок, содержащий Trace_id и span_id, среди версии и флагов трассировки) переносится в HTTP-заголовок, что позволяет осуществлять распределенную трассировку по нескольким приложениям.

http.Клиент

Чтобы иметь больше контроля (и использовать методы, еще не реализованные в otelhttp, например PATCH), вы можете использовать http.Client напрямую.

externalURL := "https://pokeapi.co/api/v2/pokemon/ditto"
client := http.Client{
 Transport: otelhttp.NewTransport(
 http.DefaultTransport,
   ),
 }

req, err := http.NewRequestWithContext(c.UserContext(), "GET", externalURL, nil)
if err != nil {
 return err
}

resp, _ := client.Do(req)
_, _ = io.ReadAll(resp.Body)

Передача контекста с существующим диапазоном в http.NewRequestWithContext(, если внутри обработчика Fiber будет c.UserContext()) сделает исходящий HTTP-вызов дочерним диапазоном входящего HTTP-вызова, обслуживаемого Fiber.

При добавлении транспорта otelhttp (с использованием otelhttp.NewTransport) к http.Client исходящий запрос будет вставлять в HTTP-заголовок (traceparent) всю информацию, необходимую для включения распределенной трассировки.
Альтернативно, заголовок трассировки может быть введен вручную. в http.Request с:

otel.GetTextMapPropagator().Inject(c.UserContext(), propagation.HeaderCarrier(req.Header))

Чтобы сделать его более интересным в GET /hello-http-client, реализованном в сопроводительном репозитории, у транспорта есть возможность отображать детали HTTP-запроса (htttp.getconn, htt.headers…).

  client := http.Client{
   Transport: otelhttp.NewTransport(
    http.DefaultTransport,
    otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace {
     return otelhttptrace.NewClientTrace(ctx)
    })),
  }

Отдых

В качестве последнего примера внедрения исходящих HTTP-вызовов мы будем использовать популярную библиотеку Resty.
На сегодняшний день Resty не поддерживает инструментарий OTeL напрямую, но это возможно, передав http.Clientв resty.NewWithClient

externalURL := "https://pokeapi.co/api/v2/pokemon/ditto"

client := resty.NewWithClient(
 &http.Client{
  Transport: otelhttp.NewTransport(http.DefaultTransport,
   otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace {
    return otelhttptrace.NewClientTrace(ctx)
   })),
 },
)

restyReq := client.R()
restyReq.SetContext(c.UserContext())

resp, _ := restyReq.Get(externalURL)

Обязательно установите контекст с существующим диапазоном (если вы хотите создать дочерний диапазон).

Что касается предыдущего примера, если вы не хотите устанавливать транспорт или у вас нет возможности в другой библиотеке передать http.Client с транспортом, вы можете напрямую внедрить заголовок трассировки:

otel.GetTextMapPropagator().Inject(c.UserContext(), propagation.HeaderCarrier(restyReq.Header))

GET /hello-resty в репозитории выдаст что-то вроде:

Добавить атрибуты диапазона

Чтобы добавить дополнительные атрибуты в существующий диапазон:

span := trace.SpanFromContext(c.UserContext())
span.SetAttributes(attribute.Bool("isTrue", true), attribute.String("test.text", "Ciao"))

Добавить события диапазона

События можно добавлять в диапазон, чтобы определить что-то важное, происходящее в определенный момент времени внутри диапазона; они также могут иметь атрибуты.
События также можно использовать в качестве формы журнала, выполняя что-то вроде:

// simple event
span.AddEvent("Done second fake long running task")

// event with attributes to have a log linked to the span
span.AddEvent("log", trace.WithAttributes(
 attribute.String("log.severity", "warning"),
 attribute.String("log.message", "Example log"),
))

Инструментальные вызовы gRPC

В go-fiber-honeycomb также есть пример просмотра трассировок при использовании gRPC.

Для инструментирования вызовов gRPC.

На клиенте:

conn, err := grpc.Dial(grpcTarget,
 grpc.WithTransportCredentials(insecure.NewCredentials()),
 grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()))

defer conn.Close()
cli := protos.NewGreeterClient(conn)
r, err := cli.SayHello(c.UserContext(), &protos.HelloRequest{Greeting: "ciao"})

На сервере:

grpcServer := grpc.NewServer(grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()))

вызовите GET http://localhost:8080/hello-grpc, чтобы увидеть его в действии.

Заключение

Я надеюсь, что эта статья и связанный с ней репозиторий помогут легко начать оснастить ваш код Go с помощью OpenTelemetry.
Мы используем OTeL в производстве, и наличие распределенной трассировки (и всех других замечательных функций для анализа всех сигналов OTeL) помогло понять производительность нашей платформы и сократить время устранения неполадок.

Спасибо, что дочитали до конца. Пожалуйста, подумайте о том, чтобы подписаться на автора и эту публикацию. Посетите Stackademic, чтобы узнать больше о том, как мы демократизируем бесплатное образование в области программирования во всем мире.