Мониторинг Go приложений с помощью Proto Observability

Подключение трейсинга и сбора метрик для Go приложений.

На этой странице:

Введение

Получение трейсов и метрик из Go приложений для Proto Observability Platform происходит посредством использования пакета pobtrace и дополнительных пакетов для инструментации библиотек и фреймворков.

Общий процесс подключения вашего приложения:

  1. Установка ProtoOBP Агента
  2. добавление модуля pobptrace и модулей для автоматической инструментации библиотек
  3. создание tracer
  4. создание спанов (минимум 1 шаг из списка ниже обязателен)
    • создание Entry спана
    • создание локального спана и/или дочерних спанов
    • создание Exit спана
  5. добавление тегов и логов к спану
  6. закрытие спанов

Добавление модулей

Добавление модуля pobptrace

go get -u git.proto.group/protoobp/pobp-trace-go/pobptrace

или добавьте в main.go файл

import(
  "git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

Дополнительные модули

go get -u git.proto.group/protoobp/pobp-trace-go/contrib/net/http

Добавление модулей для автоматической инструментации библиотек и фреймворков

Для автоматической инструментации вызововов поддерживаются следующие библиотеки и фреймворки (примеры доступны в документации по ссылкам):

Фреймворк Документация по интеграции
Gin https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/gin-gonic/gin
Gorilla Mux https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/gorilla/mux
gRPC https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/google.golang.org/grpc
gRPC v1.2 https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/google.golang.org/grpc.v12
chi https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/go-chi/chi
echo v4 https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/labstack/echo.v4
echo v3 https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go//contrib/labstack/echo
Fiber https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/gofiber/fiber.v2
Библиотека Документация по интеграции
AWS SDK https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/aws/aws-sdk-go/aws
Elasticsearch https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/olivere/elastic
Cassandra https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/gocql/gocql
GraphQL https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/graph-gophers/graphql-go
HTTP https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/net/http
HTTP router https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/julienschmidt/httprouter
Redis (go-redis) https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/go-redis/redis
Redis (go-redis-v8) https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/go-redis/redis.v8
Redis (redigo) https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/garyburd/redigo
Redis (new redigo) https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/gomodule/redigo
SQL https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/database/sql
SQLx https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/jmoiron/sqlx
MongoDB https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/go.mongodb.org/mongo-driver/mongo
MongoDB (mgo)73 https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/globalsign/mgo
BuntDB https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/tidwall/buntdb
LevelDB https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/syndtr/goleveldb/leveldb
miekg/dns https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/miekg/dns
Kafka (confluent) https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/confluentinc/confluent-kafka-go
Kafka (sarama) https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/Shopify/sarama
Google API https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/google.golang.org/api
go-restful https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/emicklei/go-restful
Twirp https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/twitchtv/twirp
Vault https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/hashicorp/vault
Consul https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/hashicorp/consul
Gorm https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/jinzhu/gorm
Gorm v2 https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/gorm.io/gorm.v1
Kubernetes https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/k8s.io/client-go/kubernetes
Memcache https://pkg.go.dev/git.proto.group/protoobp/pobp-trace-go/contrib/bradfitz/gomemcache/memcache

Пакеты для интеграции должны быть импортированы следующим образом:

import "git.proto.group/protoobp/pobp-trace-go/contrib/<PACKAGE_DIR>/<PACKAGE_NAME>"

Пример использование автоматической инструментации

Пример использования пакета для инструментации стандартных net/http:

package main

import (
	"fmt"
	"net/http"

	httptrace "git.proto.group/protoobp/pobp-trace-go/contrib/net/http" // дополнительный пакет
	"git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

func hello(w http.ResponseWriter, req *http.Request) {

	w.Write([]byte("Hello My World!\n"))

}

func headers(w http.ResponseWriter, req *http.Request) {

	for name, headers := range req.Header {
		for _, h := range headers {
			fmt.Fprintf(w, "%v: %v\n", name, h)
		}
	}
}

func main() {

	tracer.Start(
		tracer.WithService("test-server"), 
		tracer.WithRuntimeMetrics(),       
		tracer.WithAgentAddr("193.32.218.109:9080"),
		tracer.WithDogstatsdAddress("193.32.218.109"),
	)
	mux := httptrace.NewServeMux()

	defer tracer.Stop()

	mux.HandleFunc("/hello", hello)
	mux.HandleFunc("/headers", headers)

	http.ListenAndServe(":8090", mux)
}

Конфигурация трейсера

Трейсер поддерживает конфигурацию через переменные окружения или через параметры инициализации в коде вашего приложения.

Конфигурация трейсера через переменные окружения

Добавления переменных окружения упрощает конфигурацию трейсера - это рекомендуемый метод конфигурации.

Переменные Описание Значения по умолчанию
POBP_SERVICE Имя сервиса для интерфейса Не установлено
POBP_AGENT_HOST Адрес по которому доступен ProtoOBP агент Не установлено
POBP_DOGSTATSD_NON_LOCAL_TRAFFIC Включение метрик Go runtime false

Если приложение запускается в Docker контейнере

Пример готового Dockerfile

ENV POBP_SERVICE=dispatch
ENV POBP_AGENT_HOST=protoobp-agent
ENV POBP_DOGSTATSD_NON_LOCAL_TRAFFIC=true

Если приложение работает в Kubernetes

Убедитесь, что у вас успешно установлен и настроен ProtoOBP Агент для Kubernetes.

Дополнительно необходимо передать поду переменную окружения POBP_AGENT_HOST со значением IP адреса воркер-ноды, а также переменные окружения для связи трейсов с инфраструктурой (имя k8s кластера нужно задать вручную).

apiVersion: apps/v1
kind: Deployment
#(...)
    spec:
      containers:
      - name: "<CONTAINER_NAME>"
        image: "<CONTAINER_IMAGE>/<TAG>"
        env:
          - name: POBP_SERVICE
            value: dispatch			
          - name: POBP_AGENT_HOST
            valueFrom:
              fieldRef:
                fieldPath: status.hostIP
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POBP_TAGS
            value: "pod_name:$(POD_NAME),node:$(NODE_NAME),kube_namespace:$(POD_NAMESPACE),kube_cluster_name:<my_cluster_name>"            
            

Конфигурация трейсера при инициализации в коде

Параметры можно задать явным образом при инициализаци трейсера. В случае добавления ранее переменных окружения, достаточно указать только tracer.WithRuntimeMetrics() для отдачи метрик.


package main

import (
    "git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

func main() {
	tracer.Start(
		tracer.WithService("test-server"), 				// имя сервиса 
		tracer.WithRuntimeMetrics(),       				// включение передачи Go метрик
		tracer.WithAgentAddr("protoobp-agent"),	        // адрес по которому доступен ProtoOBP агент
		tracer.WithDogstatsdAddress("protoobp-agent"),	// адрес по которому доступен ProtoOBP агент
	)

    defer tracer.Stop()
}

Использование трейсера

Создание трейсера


package main

import (
    "git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

func main() {
	tracer.Start(
		tracer.WithService("test-server"), 				// имя сервиса 
		tracer.WithRuntimeMetrics(),       				// включение передачи Go метрик
	)

    defer tracer.Stop()
}

Ручное добавление спанов

Если автоматической инструментации библиотек и фреймворков недостаточно, вы можете вручную добавить спаны. Для добавления спанов доступно две функции - StartSpan и StartSpanFromContext

//Создание спана с эндпонитом /user, который дочерный к родительскому спану.
span := tracer.StartSpan("mainOp", tracer.ResourceName("/user"), tracer.ChildOf(parentSpan))

// Создание спана, который будет дочерним к спану в контексте ctx, если в контексте есть спан.
// Возвращает новый спан, и новый контекст, содержащий спан.
span, ctx := tracer.StartSpanFromContext(ctx, "mainOp", tracer.ResourceName("/user"))

Пример ручного создания спанов

package main

import (
	"io/ioutil"
	"log"

	"git.proto.group/protoobp/pobp-trace-go/pobptrace/ext"
	"git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

func main() {
	// Запускаем трейсер и не забывает про Stop.
	tracer.Start(tracer.WithAgentAddr("host:port"))
	defer tracer.Stop()

	// Начинаем корневой спан.
	span := tracer.StartSpan("get.data")
	defer span.Finish()

	// Создаем дочерный спан, вычисляем время, необходимое для открытия файла.
	child := tracer.StartSpan("read.file", tracer.ChildOf(span.Context()))
	child.SetTag(ext.ResourceName, "test.json")

	// Выполняем операцию.
	_, err := ioutil.ReadFile("~/test.json")

	// Мы можем завершить дочерный спан используя возвращаемую ошибку. Если это 
	// nil, будет отброшено.
	child.Finish(tracer.WithError(err))
	if err != nil {
		log.Fatal(err)
	}
}

Добавление тегов к спану

package main

import (
    "log"
    "net/http"

    "git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

func handler(w http.ResponseWriter, r *http.Request) {
    // Создание спана для web request для запросов к /posts.
	// ResourceName - имя эндпоинта в Proto OBP
    span := tracer.StartSpan("web.request", tracer.ResourceName("/posts"))
    defer span.Finish()

    // Добавляем тег
    span.SetTag("http.url", r.URL.Path)
    span.SetTag("<TAG_KEY>", "<TAG_VALUE>")
}

func main() {
    tracer.Start(tracer.WithService("<SERVICE_NAME>"))
    defer tracer.Stop()
    http.HandleFunc("/posts", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

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

Используйте функцию span.SetTag("<KEY>", "<VALUE>") для добавления HTTP заголовка в тэг трейса, где:

KEY - должен начинаться с http.request.headers.<ИМЯ_HTTP_ЗАГОЛОВКА>

VALUE - значение заголовка

Пример:

package main

import (
    "log"
    "net/http"

    "git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

func handler(w http.ResponseWriter, r *http.Request) {
    // Создание спана для web request для запросов к /posts.
	// ResourceName - имя эндпоинта в Proto OBP
    span := tracer.StartSpan("web.request", tracer.ResourceName("/posts"))
    defer span.Finish()

    // Добавляем тег
    span.SetTag("http.url", r.URL.Path)
	//Добавляем HTTP заголовк в тэг трейса
    span.SetTag("http.request.headers.x-request-id", "<VALUE>")
}

func main() {
    tracer.Start(tracer.WithService("<SERVICE_NAME>"))
    defer tracer.Stop()
    http.HandleFunc("/posts", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Распределенный трейсинг и обработка контекста

Мы используем тип Context для связи спанов. Если нужно добавить теги связанные с Context, вызывайте SpanFromContext:


package main

import (
    "net/http"

    "git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

func handler(w http.ResponseWriter, r *http.Request) {
    // Спан для веб запроса связанный с Go Context.
    if span, ok := tracer.SpanFromContext(r.Context()); ok {
        // добавляем тег.
        span.SetTag("http.url", r.URL.Path)
    }
}

Создание распределенного трейса путем ручного внедрения контекста:

package main

import (
    "net/http"

    "git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

func handler(w http.ResponseWriter, r *http.Request) {
    span, ctx := tracer.StartSpanFromContext(r.Context(), "post.process")
    defer span.Finish()

    req, err := http.NewRequest("GET", "http://example.com", nil)
    req = req.WithContext(ctx)
    // Внедрение Context хедеры Request
    err = tracer.Inject(span.Context(), tracer.HTTPHeadersCarrier(req.Header))
    if err != nil {
        // Обработка или логгирование ошибки внедрения контекста
    }
    http.DefaultClient.Do(req)
}

Далее на серверной стороне для продолжения трейса создайте новый спан из извлеченного контекста:


package main

import (
    "net/http"

    "git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

func handler(w http.ResponseWriter, r *http.Request) {
    // Извлекаем контекст спана и продолжаем трейс в этом сервисе
    sctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header))
    if err != nil {
        // Обработка или логгирование ошибки извлечения контекста
    }

    span := tracer.StartSpan("post.filter", tracer.ChildOf(sctx))
    defer span.Finish()
}

Ошибочный спан

Для добавления признака ошибки, используйте tracer.WithError:

err := someOperation()
span.Finish(tracer.WithError(err))

Трейсинг асинхронных запросов

func main() {
	span, ctx := tracer.StartSpanFromContext(context.Background(), "mainOp")
	defer span.Finish()

	go func() {
		asyncSpan := tracer.StartSpanFromContext(ctx, "asyncOp")
		defer asyncSpan.Finish()
		performOp()
	}()
}

Использование трейсера вместе с OpenTracing

package main

import (
	opentracing "github.com/opentracing/opentracing-go"

	"git.proto.group/protoobp/pobp-trace-go/pobptrace/opentracer"
	"git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)

func main() {
	//Запускаем POBP tracer, опционально передаем опции (лучше все определить через переменные окружения),
	// возвращем opentracing.Tracer который оборачивает его.
	t := opentracer.New(tracer.WithAgentAddr("host:port"))
	defer tracer.Stop() // не забываем останавливать, иначе трейсы не придут

	// Используем с Opentracing API. Уже запущенный POBP tracer
	// может быть использован параллельно с Opentracing API если нужно.
	opentracing.SetGlobalTracer(t)
}

Метрики Go runtime

Включение метрик Go runtime

TBD

Список метрик Go runtime, собираемых Proto OBP

TBD

Отображение метрик Go runtime

TBD

Правила алертинга, доступные при установке Proto OBP, по метрикам Go runtime

TBD