Мониторинг Go приложений с помощью Proto Observability
На этой странице:
- Введение
- Установка Go трейсера
- Конфигурация трейсера
- Метрики Go runtime
- Трейсер ProtoOBP – дополнительная документация
Введение
Общий процесс подключения Go приложения на мониторинг в Proto Observability Platform :
- Установка ProtoOBP Агента.
- Установка трейсера.
- Конфигурация трейсера (опционально).
После подключения будут доступны все возможности модуля Application Performance Monitoring Proto Observability Platform.
Установка Go трейсера
Существует два способа инструментации вашего Go приложения:
-
Инструментация на этапе компиляции:
- Обеспечивает максимальную полноту трассировки.
- Не требует изменения исходного кода, что делает этот способ идеальным для интеграции на уровне CI/CD.
-
Инструментация вручную:
Используются модули трейсера в сочетании с пакетами интеграции для автоматической генерации спанов для выбранных вами библиотек. Этот вариант:
- Дает вам полный контроль над тем, какие части вашего приложения будут отслеживаться.
- Требует изменения исходного кода приложения.
Инструментация на этапе компиляции (рекомендуется)
Пакет Orchestrion автоматически добавляет инструментацию в приложения Go во время компиляции, устраняя необходимость в модификации кода. Пакет обеспечивает всестороннее покрытие трассировкой:
- Инструментирует ваш код и все зависимости, включая стандартную библиотеку Go.
- Инструментирует ваш код во время компиляции, предотвращая пробелы в покрытии трассировки из-за упущенной ручной инструментации.
Требования для инструментации на этапе компиляции:
- Поддерживает две последние версии среды выполнения Go (в соответствии с официальной политикой релизов Go).
- Приложения должны управляться с помощью модулей go.
Для добавления в Go приложение инструментации на этапе компиляции выполните:
-
Установите пакет
Orchestrion:go install github.com/DataDog/orchestrion@latest -
Выполните команду:
orchestrion pin -
Добавьте префикс
orchestrionк обычным командамgo:orchestrion go build . orchestrion go run . orchestrion go test ./...
Пример Dockerfile:
FROM golang:1.24
WORKDIR /home/apm-tutorial-golang
COPY go.mod ./
COPY go.sum ./
RUN go mod download
#Copy notes application
COPY notes notes/
COPY cmd/notes cmd/notes/
# добавление инструментации
RUN go install github.com/DataDog/orchestrion@latest
RUN orchestrion pin
RUN orchestrion go build -o cmd/notes/notes ./cmd/notes
#Set sample rate
ENV DD_TRACE_SAMPLE_RATE 1
#Run application
ENTRYPOINT ["./cmd/notes/notes"]
Инструментация вручную
Добавьте библиотеку трейсера в свое приложение Сначала импортируйте и запустите трейсер в своем коде, следуя инструкциям в документации по настройке библиотеки. Инструкции по настройке и подробную информацию об использовании API см. ниже.
Добавление модуля pobptrace:
go get -u git.proto.group/protoobp/pobp-trace-go/pobptrace
или добавьте в main.go файл:
import(
"git.proto.group/protoobp/pobp-trace-go/pobptrace/tracer"
)
Добавление модуля ddtrace:
go get -u github.com/DataDog/dd-trace-go/v2
или добавьте в main.go файл:
import(
"github.com/DataDog/dd-trace-go/v2/ddtrace/ext"
"github.com/DataDog/dd-trace-go/v2/ddtrace/tracer"
)
Используйте документацию трейсера:
https://pkg.go.dev/github.com/DataDog/dd-trace-go/v2/ddtrace
Скоро
-
Выполните шаги, указанные на странице подключения инструметации OpenTelemetry для Go:
-
Выполните шаги, указанные на странице настройки OpenTelemetry в Proto Observability Platform.
Активируйте интеграции Go для создания спанов Активируйте интеграции Go для генерации спанов. Предлагаются ряд подключаемых пакетов, которые обеспечивают готовую поддержку для инструментации ряда библиотек и фреймворков. Список этих пакетов можно найти на странице «Требования к совместимости». Импортируйте эти пакеты в свое приложение и следуйте инструкциям по настройке, указанным рядом с каждой интеграцией.
Дополнительные модули интеграции – пример добавления:
go get -u git.proto.group/protoobp/pobp-trace-go/contrib/net/http
Для автоматической инструментации вызововов поддерживаются следующие библиотеки и фреймворки (примеры доступны в документации по ссылкам):
Пакеты для интеграции должны быть импортированы следующим образом:
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("protoobp-agent:8126"),
tracer.WithDogstatsdAddress("protoobp-agent"),
)
mux := httptrace.NewServeMux()
defer tracer.Stop()
mux.HandleFunc("/hello", hello)
mux.HandleFunc("/headers", headers)
http.ListenAndServe(":8090", mux)
}
Пакеты интеграции импортируются следующим образом:
import "github.com/DataDog/dd-trace-go/contrib/<PACKAGE_DIR>/<PACKAGE_NAME>/v2"
Трейсер поддерживает следующие библиотеки и хранилища данных:
Скоро
-
Выполните шаги, указанные на странице подключения инструметации OpenTelemetry для Go:
-
Выполните шаги, указанные на странице настройки OpenTelemetry в Proto Observability Platform.
Конфигурация трейсера
Трейсер поддерживает конфигурацию через переменные окружения или через параметры инициализации в коде вашего приложения.
Конфигурация трейсера через переменные окружения (рекомендуется)
POBP_AGENT_HOST – адрес Агента, на который будет отсылать данные трейсер:
- если Агент запущен в Docker контейнере, укажите имя Docker контейнера Агента:
Убедитесь, что контейнер Агента и контейнер приложения находятся в одной Docker сети. Адрес агента должен быть доступен из Docker контейнера приложения.
ENV POBP_AGENT_HOST="protoobp-agent" - если Агент запущен на хосте как сервис (не в контейнере):
Убедитесь что в конфигурации Агента разрешен прием APM данных от контейнеров:
ENV POBP_AGENT_HOST="host.docker.internal"apm_config: apm_non_local_traffic: true
POBP_SERVICE – имя сервиса, которое будет отображаться в интерфейсе Proto OBP:
ENV POBP_SERVICE="my_service_name"
POBP_ENV – окружение сервиса, которое будет отображаться в интерфейсе Proto OBP:
ENV POBP_ENV="prod" # prod/test/stage/dev и тд
POBP_RUNTIME_METRICS_ENABLED – обязательно установите в true для получения runtime метрик
ENV POBP_RUNTIME_METRICS_ENABLED="true"
POBP_TRACE_TELEMETRY_ENABLED – установите в false
ENV POBP_TRACE_TELEMETRY_ENABLED="false"
DD_AGENT_HOST – адрес Агента, на который будет отсылать данные трейсер:
- если Агент запущен в Docker контейнере, укажите имя Docker контейнера Агента:
Убедитесь, что контейнер Агента и контейнер приложения находятся в одной Docker сети. Адрес агента должен быть доступен из Docker контейнера приложения.
ENV DD_AGENT_HOST="protoobp-agent" - если Агент запущен на хосте как сервис (не в контейнере):
Убедитесь что в конфигурации Агента разрешен прием APM данных от контейнеров:
ENV DD_AGENT_HOST="host.docker.internal"apm_config: apm_non_local_traffic: true
DD_SERVICE – имя сервиса, которое будет отображаться в интерфейсе Proto OBP:
ENV DD_SERVICE="my_service_name"
DD_ENV – окружение сервиса, которое будет отображаться в интерфейсе Proto OBP:
ENV DD_ENV="prod" # prod/test/stage/dev и тд
DD_RUNTIME_METRICS_ENABLED – обязательно установите в true для получения runtime метрик
ENV DD_RUNTIME_METRICS_ENABLED="true"
DD_TRACE_TELEMETRY_ENABLED – установите в false
ENV DD_TRACE_TELEMETRY_ENABLED="false"
Скоро
Если приложение запускается в Docker контейнере
Пример готового Dockerfile:
ENV POBP_SERVICE=dispatch
ENV POBP_DOGSTATSD_NON_LOCAL_TRAFFIC=true
# следующие переменные лучше задавать в среде выполнения, а не в Dockerfile
ENV POBP_ENV=prod
ENV POBP_AGENT_HOST=protoobp-agent
ENV DD_SERVICE=dispatch
ENV DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true
# следующие переменные лучше задавать в среде выполнения, а не в Dockerfile
ENV DD_ENV=prod
ENV DD_AGENT_HOST=protoobp-agent
Скоро
-
Выполните шаги, указанные на странице подключения инструметации OpenTelemetry для Go:
-
Выполните шаги, указанные на странице настройки OpenTelemetry в Proto Observability Platform.
Если приложение работает в 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>"
Дополнительно необходимо передать поду переменную окружения DD_AGENT_HOST со значением IP адреса воркер-ноды, а также переменные окружения для связи трейсов с инфраструктурой (имя k8s кластера нужно задать вручную).
apiVersion: apps/v1
kind: Deployment
#(...)
spec:
containers:
- name: "<CONTAINER_NAME>"
image: "<CONTAINER_IMAGE>/<TAG>"
env:
- name: DD_SERVICE
value: dispatch
- name: DD_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: DD_TAGS
value: "pod_name:$(POD_NAME),node:$(NODE_NAME),kube_namespace:$(POD_NAMESPACE),kube_cluster_name:<my_cluster_name>"
Скоро
-
Выполните шаги, указанные на странице подключения инструметации OpenTelemetry для Go:
-
Выполните шаги, указанные на странице настройки OpenTelemetry в Proto Observability Platform.
Метрики Go runtime
Помимо трейсов, ошибок и метрик по трейсам, Proto OBP также собирает runtime метрики Go приложений. Runtime метрики отображаются на дашборде конкретного инстанса сервиса.
Включение метрик Go runtime
Убедитесь, что у Агента установлена переменная окружения:
POBP_DOGSTATSD_NON_LOCAL_TRAFFIC=true
Добавьте переменную окружения к приложению:
POBP_RUNTIME_METRICS_ENABLED=true
Добавьте переменную окружения к приложению:
DD_RUNTIME_METRICS_ENABLED=true
Список метрик Go runtime, собираемых Proto OBP
runtime_go_num_cpu (gauge) |
Процессоры, обнаруженные средой выполнения. |
runtime_go_num_goroutine (gauge) |
Созданные goroutines. |
runtime_go_num_cgo_call (gauge) |
Выполненные вызовы CGO. |
runtime_go_mem_stats_alloc (gauge) |
Alloc — это байты выделенных объектов кучи. |
runtime_go_mem_stats_total_alloc (gauge) |
TotalAlloc — это совокупный объем выделенной памяти для объектов кучи. |
runtime_go_mem_stats_sys (gauge) |
Sys — это общий объем памяти, полученной от ОС. Отображается в байтах |
runtime_go_mem_stats_lookups (gauge) |
Lookups — количество поисков указателей, выполненных. |
runtime_go_mem_stats_mallocs (gauge) |
Mallocs — совокупное количество выделенных объектов кучи. |
runtime_go_mem_stats_frees (gauge) |
Frees — это совокупное количество освобожденных объектов кучи. |
runtime_go_mem_stats_heap_alloc (gauge) |
HeapAlloc — это байты выделенных объектов кучи. |
runtime_go_mem_stats_heap_sys (gauge) |
HeapSys — это количество байтов кучи памяти, полученных от ОС. |
runtime_go_mem_stats_heap_idle (gauge) |
HeapIdle — это количество байтов в неиспользуемых (незанятых) интервалах. |
runtime_go_mem_stats_heap_inuse (gauge) |
HeapInuse — это байты в используемых диапазонах. |
runtime_go_mem_stats_heap_released (gauge) |
HeapReleased — это количество байтов физической памяти, возвращенных ОС. |
runtime_go_mem_stats_heap_objects (gauge) |
HeapObjects — это количество выделенных объектов кучи. |
runtime_go_mem_stats_stack_inuse (gauge) |
StackInuse — это количество байтов в стеке. |
runtime_go_mem_stats_stack_sys (gauge) |
StackSys — это количество байтов памяти стека, полученных от ОС. |
runtime_go_mem_stats_m_span_inuse (gauge) |
MSpanInuse — это количество байтов выделенных структур mspan. |
runtime_go_mem_stats_m_span_sys (gauge) |
MSpanSys — это количество байтов памяти, полученных от ОС для структур mspan. |
runtime_go_mem_stats_m_cache_inuse (gauge) |
MCacheInuse — это количество байтов выделенных структур mcache. |
runtime_go_mem_stats_m_cache_sys (gauge) |
MCacheSys — это количество байтов памяти, полученных от ОС |
runtime_go_mem_stats_buck_hash_sys (gauge) |
BuckHashSys — это байты памяти в хэш-таблицах профилирования. |
runtime_go_mem_stats_gc_sys (gauge) |
GCSys — это байты памяти в метаданных сборки мусора. |
runtime_go_mem_stats_other_sys (gauge) |
OtherSys — это байты памяти в различных внекупочных областях. |
runtime_go_mem_stats_next_gc (gauge) |
NextGC — это целевой размер кучи для следующего цикла GC. |
runtime_go_mem_stats_last_gc (gauge) |
LastGC — время завершения последней сборки мусора в наносекундах с 1970 года (эпоха UNIX). |
runtime_go_mem_stats_pause_total_ns (gauge) |
PauseTotalNs — это совокупное количество наносекунд в GC. |
runtime_go_mem_stats_num_gc (gauge) |
NumGC — количество завершенных циклов GC. |
runtime_go_mem_stats_num_forced_gc (gauge) |
NumForcedGC — количество циклов GC, которые были принудительно запущены приложением, вызвавшим функцию GC. |
runtime_go_mem_stats_gc_cpu_fraction (gauge) |
GCCPUFraction — доля доступного времени ЦП данной программы, использованного GC с момента запуска программы. |
runtime_go_gc_stats_pause_quantiles_min (gauge) |
Распределение времени пауз GC: минимальные значения. Отображается в наносекундах |
runtime_go_gc_stats_pause_quantiles_25p (gauge) |
Распределение времени пауз GC: 25-й процентиль. Отображается в наносекундах |
runtime_go_gc_stats_pause_quantiles_75p (gauge) |
Распределение времени пауз GC: 50-й процентиль. Отображается в наносекундах |
runtime_go_gc_stats_pause_quantiles_95p (gauge) |
Распределение времени паузы GC: 75-й процентиль. Отображается в наносекундах |
runtime_go_gc_stats_pause_quantiles_max (gauge) |
Распределение времени паузы GC: максимальные значения. Отображается в наносекундах |
Отображение метрик Go runtime
Для отображения метрик Go runtime выберите сервис, перейдите на вкладки Инстансы и откройте дашборд интересующего инстанса:

Трейсер ProtoOBP – дополнительная документация
Конфигурация трейсера при инициализации в коде
Параметры можно задать явным образом при инициализаци трейсера. В случае добавления ранее переменных окружения, достаточно указать только 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)
}