Початок роботи

Ця сторінка покаже вам, як почати роботу з OpenTelemetry в Go.

Ви дізнаєтеся, як можна інструментувати простий застосунок вручну, таким чином, щоб трейси, метрики, і логи виводилися в консоль.

Передумови

Переконайтеся, що у вас встановлено наступне:

  • Go 1.22 або новіше

Приклад застосунку

Наступний приклад використовує базовий застосунок net/http. Якщо ви не використовуєте net/http, це не проблема — ви можете використовувати OpenTelemetry Go з іншими веб-фреймворками, такими як Gin та Echo. Для повного списку бібліотек для підтримуваних фреймворків дивіться реєстр.

Для складніших прикладів дивіться приклади.

Налаштування

Для початку налаштуйте go.mod у новій теці:

go mod init dice

Створення та запуск HTTP сервера

У тій самій теці створіть файл з назвою main.go і додайте наступний код до файлу:

package main import ( "log" "net/http" ) func main() { http.HandleFunc("/rolldice", rolldice) log.Fatal(http.ListenAndServe(":8080", nil)) }

Створіть інший файл з назвою rolldice.go і додайте наступний код до файлу:

package main import ( "io" "log" "math/rand" "net/http" "strconv" ) func rolldice(w http.ResponseWriter, r *http.Request) { roll := 1 + rand.Intn(6) resp := strconv.Itoa(roll) + "\n" if _, err := io.WriteString(w, resp); err != nil { log.Printf("Write failed: %v\n", err) } }

Зберіть і запустіть застосунок за допомогою наступної команди:

go run .

Відкрийте http://localhost:8080/rolldice у вашому вебоглядачі, щоб переконатися, що він працює.

Додавання інструментів OpenTelemetry

Тепер ми покажемо, як додати інструменти OpenTelemetry до демонстраційного застосунку. Якщо ви використовуєте свій власний застосунок, ви можете слідувати разом, просто зверніть увагу, що ваш код може трохи відрізнятися.

Додавання залежностей

Встановіть наступні пакунки:

go get "go.opentelemetry.io/otel" \ "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" \ "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" \ "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" \ "go.opentelemetry.io/otel/sdk/log" \ "go.opentelemetry.io/otel/log/global" \ "go.opentelemetry.io/otel/propagation" \ "go.opentelemetry.io/otel/sdk/metric" \ "go.opentelemetry.io/otel/sdk/resource" \ "go.opentelemetry.io/otel/sdk/trace" \ "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"\ "go.opentelemetry.io/contrib/bridges/otelslog"

Це встановлює компоненти SDK OpenTelemetry та інструменти для net/http.

Якщо ви інструментуєте іншу бібліотеку для мережевих запитів, вам потрібно буде встановити відповідну бібліотеку інструментів. Дивіться бібліотеки для отримання додаткової інформації.

Ініціалізація SDK OpenTelemetry

Спочатку ми ініціалізуємо SDK OpenTelemetry. Це обов’язково для будь-якого застосуунку, який експортує телеметрію.

Створіть otel.go з кодом для завантаження SDK OpenTelemetry:

package main import ( "context" "errors" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/trace" ) // setupOTelSDK завантажує конвеєр OpenTelemetry. // Якщо він не повертає помилку, обов'язково викличте shutdown для правильного очищення. func setupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) { var shutdownFuncs []func(context.Context) error // shutdown викликає функції очищення, зареєстровані через shutdownFuncs. // Помилки з викликів обʼєднуються. // Кожне зареєстроване очищення буде викликано один раз. shutdown = func(ctx context.Context) error { var err error for _, fn := range shutdownFuncs { err = errors.Join(err, fn(ctx)) } shutdownFuncs = nil return err } // handleErr викликає shutdown для очищення і переконується, що всі помилки повертаються. handleErr := func(inErr error) { err = errors.Join(inErr, shutdown(ctx)) } // Налаштування поширювача. prop := newPropagator() otel.SetTextMapPropagator(prop) // Налаштування провайдера трасування. tracerProvider, err := newTraceProvider() if err != nil { handleErr(err) return } shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) otel.SetTracerProvider(tracerProvider) // Налаштування провайдера метрик. meterProvider, err := newMeterProvider() if err != nil { handleErr(err) return } shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) otel.SetMeterProvider(meterProvider) // Налаштування провайдера логів. loggerProvider, err := newLoggerProvider() if err != nil { handleErr(err) return } shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown) global.SetLoggerProvider(loggerProvider) return } func newPropagator() propagation.TextMapPropagator { return propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ) } func newTraceProvider() (*trace.TracerProvider, error) { traceExporter, err := stdouttrace.New( stdouttrace.WithPrettyPrint()) if err != nil { return nil, err } traceProvider := trace.NewTracerProvider( trace.WithBatcher(traceExporter, // Стандартно 5s. Встановлено на 1s для демонстраційних цілей. trace.WithBatchTimeout(time.Second)), ) return traceProvider, nil } func newMeterProvider() (*metric.MeterProvider, error) { metricExporter, err := stdoutmetric.New() if err != nil { return nil, err } meterProvider := metric.NewMeterProvider( metric.WithReader(metric.NewPeriodicReader(metricExporter, // Стандартно 1m. Встановлено на 3s для демонстраційних цілей. metric.WithInterval(3*time.Second))), ) return meterProvider, nil } func newLoggerProvider() (*log.LoggerProvider, error) { logExporter, err := stdoutlog.New() if err != nil { return nil, err } loggerProvider := log.NewLoggerProvider( log.WithProcessor(log.NewBatchProcessor(logExporter)), ) return loggerProvider, nil }

Якщо ви використовуєте лише трасування або метрики, ви можете пропустити відповідний код ініціалізації TracerProvider або MeterProvider.

Інструментування HTTP сервера

Тепер, коли ми ініціалізували SDK OpenTelemetry, ми можемо інструментувати HTTP сервер.

Змініть main.go, щоб включити код, який налаштовує SDK OpenTelemetry та інструментує HTTP сервер за допомогою бібліотеки інструментів otelhttp:

package main import ( "context" "errors" "log" "net" "net/http" "os" "os/signal" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func main() { if err := run(); err != nil { log.Fatalln(err) } } func run() (err error) { // Належна обробка SIGINT (CTRL+C). ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() // Налаштування OpenTelemetry. otelShutdown, err := setupOTelSDK(ctx) if err != nil { return } // Правильне завершення роботи, щоб нічого не витікало. defer func() { err = errors.Join(err, otelShutdown(context.Background())) }() // Запуск HTTP сервера. srv := &http.Server{ Addr: ":8080", BaseContext: func(_ net.Listener) context.Context { return ctx }, ReadTimeout: time.Second, WriteTimeout: 10 * time.Second, Handler: newHTTPHandler(), } srvErr := make(chan error, 1) go func() { srvErr <- srv.ListenAndServe() }() // Очікування переривання. select { case err = <-srvErr: // Помилка при запуску HTTP сервера. return case <-ctx.Done(): // Очікування першого CTRL+C. // Припинення отримання повідомлень про сигнали якомога швидше. stop() } // Коли викликається Shutdown, ListenAndServe негайно повертає ErrServerClosed. err = srv.Shutdown(context.Background()) return } func newHTTPHandler() http.Handler { mux := http.NewServeMux() // handleFunc є заміною для mux.HandleFunc // який збагачує HTTP інструментування обробника зразком як http.route. handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) { // Налаштування "http.route" для HTTP інструментування. handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc)) mux.Handle(pattern, handler) } // Реєстрація обробників. handleFunc("/rolldice/", rolldice) handleFunc("/rolldice/{player}", rolldice) // Додавання HTTP інструментування для всього сервера. handler := otelhttp.NewHandler(mux, "/") return handler }

Додавання користувацького інструментування

Бібліотеки інструментування захоплюють телеметрію на краях ваших систем, таких як вхідні та вихідні HTTP запити, але вони не захоплюють те, що відбувається у вашому застосунку. Для цього вам потрібно написати деяке власне ручне інструментування.

Змініть rolldice.go, щоб включити власне інструментування за допомогою API OpenTelemetry:

package main import ( "fmt" "io" "log" "math/rand" "net/http" "strconv" "go.opentelemetry.io/contrib/bridges/otelslog" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) const name = "go.opentelemetry.io/otel/example/dice" var ( tracer = otel.Tracer(name) meter = otel.Meter(name) logger = otelslog.NewLogger(name) rollCnt metric.Int64Counter ) func init() { var err error rollCnt, err = meter.Int64Counter("dice.rolls", metric.WithDescription("Кількість кидків за значенням кидка"), metric.WithUnit("{roll}")) if err != nil { panic(err) } } func rolldice(w http.ResponseWriter, r *http.Request) { ctx, span := tracer.Start(r.Context(), "roll") defer span.End() roll := 1 + rand.Intn(6) var msg string if player := r.PathValue("player"); player != "" { msg = fmt.Sprintf("%s кидає кістки", player) } else { msg = "Анонімний гравець кидає кістки" } logger.InfoContext(ctx, msg, "result", roll) rollValueAttr := attribute.Int("roll.value", roll) span.SetAttributes(rollValueAttr) rollCnt.Add(ctx, 1, metric.WithAttributes(rollValueAttr)) resp := strconv.Itoa(roll) + "\n" if _, err := io.WriteString(w, resp); err != nil { log.Printf("Write failed: %v\n", err) } }

Зверніть увагу, що якщо ви використовуєте лише трасування або метрики, ви можете пропустити відповідний код, який інструментує інший тип телеметрії.

Запуск застосунку

Зберіть і запустіть застосунок за допомогою наступної команди:

go mod tidy export OTEL_RESOURCE_ATTRIBUTES="service.name=dice,service.version=0.1.0" go run .

Відкрийте http://localhost:8080/rolldice/Alice у вашому вебоглядачі. Коли ви надішлете запит до сервера, ви побачите два відрізки у трейсі, що виводяться в консоль. Відрізок, створений бібліотекою інструментів, відстежує тривалість запиту до маршруту /rolldice/{player}. Відрізок з назвою roll створюється вручну і є дочірнім до попередньо згаданого відірзка.

Переглянути приклад виводу
{ "Name": "roll", "SpanContext": { "TraceID": "829fb7ceb787403c96eac3caf285c965", "SpanID": "8b6b408b6c1a35e5", "TraceFlags": "01", "TraceState": "", "Remote": false }, "Parent": { "TraceID": "829fb7ceb787403c96eac3caf285c965", "SpanID": "612be4bbdf450de6", "TraceFlags": "01", "TraceState": "", "Remote": false }, "SpanKind": 1, "StartTime": "2023-09-25T12:42:06.177119576+02:00", "EndTime": "2023-09-25T12:42:06.177136776+02:00", "Attributes": [ { "Key": "roll.value", "Value": { "Type": "INT64", "Value": 6 } } ], "Events": null, "Links": null, "Status": { "Code": "Unset", "Description": "" }, "DroppedAttributes": 0, "DroppedEvents": 0, "DroppedLinks": 0, "ChildSpanCount": 0, "Resource": [ { "Key": "service.name", "Value": { "Type": "STRING", "Value": "dice" } }, { "Key": "service.version", "Value": { "Type": "STRING", "Value": "0.1.0" } }, { "Key": "telemetry.sdk.language", "Value": { "Type": "STRING", "Value": "go" } }, { "Key": "telemetry.sdk.name", "Value": { "Type": "STRING", "Value": "opentelemetry" } }, { "Key": "telemetry.sdk.version", "Value": { "Type": "STRING", "Value": "1.19.0-rc.1" } } ], "InstrumentationLibrary": { "Name": "rolldice", "Version": "", "SchemaURL": "" } } { "Name": "/", "SpanContext": { "TraceID": "829fb7ceb787403c96eac3caf285c965", "SpanID": "612be4bbdf450de6", "TraceFlags": "01", "TraceState": "", "Remote": false }, "Parent": { "TraceID": "00000000000000000000000000000000", "SpanID": "0000000000000000", "TraceFlags": "00", "TraceState": "", "Remote": false }, "SpanKind": 2, "StartTime": "2023-09-25T12:42:06.177071077+02:00", "EndTime": "2023-09-25T12:42:06.177158076+02:00", "Attributes": [ { "Key": "http.method", "Value": { "Type": "STRING", "Value": "GET" } }, { "Key": "http.scheme", "Value": { "Type": "STRING", "Value": "http" } }, { "Key": "http.flavor", "Value": { "Type": "STRING", "Value": "1.1" } }, { "Key": "net.host.name", "Value": { "Type": "STRING", "Value": "localhost" } }, { "Key": "net.host.port", "Value": { "Type": "INT64", "Value": 8080 } }, { "Key": "net.sock.peer.addr", "Value": { "Type": "STRING", "Value": "::1" } }, { "Key": "net.sock.peer.port", "Value": { "Type": "INT64", "Value": 49046 } }, { "Key": "http.user_agent", "Value": { "Type": "STRING", "Value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" } }, { "Key": "http.route", "Value": { "Type": "STRING", "Value": "/rolldice/Alice" } }, { "Key": "http.wrote_bytes", "Value": { "Type": "INT64", "Value": 2 } }, { "Key": "http.status_code", "Value": { "Type": "INT64", "Value": 200 } } ], "Events": null, "Links": null, "Status": { "Code": "Unset", "Description": "" }, "DroppedAttributes": 0, "DroppedEvents": 0, "DroppedLinks": 0, "ChildSpanCount": 1, "Resource": [ { "Key": "service.name", "Value": { "Type": "STRING", "Value": "dice" } }, { "Key": "service.version", "Value": { "Type": "STRING", "Value": "0.1.0" } }, { "Key": "telemetry.sdk.language", "Value": { "Type": "STRING", "Value": "go" } }, { "Key": "telemetry.sdk.name", "Value": { "Type": "STRING", "Value": "opentelemetry" } }, { "Key": "telemetry.sdk.version", "Value": { "Type": "STRING", "Value": "1.19.0-rc.1" } } ], "InstrumentationLibrary": { "Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", "Version": "0.44.0", "SchemaURL": "" } }

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

Переглянути приклад виводу
{ "Timestamp": "2023-09-25T12:42:05.177136776+02:00", "ObservedTimestamp": "2023-09-25T12:42:06.809396011+02:00", "Severity": 9, "SeverityText": "", "Body": { "Type": "String", "Value": "Alice is rolling the dice" }, "Attributes": [ { "Key": "result", "Value": { "Type": "Int64", "Value": 6 } } ], "TraceID": "829fb7ceb787403c96eac3caf285c965", "SpanID": "8b6b408b6c1a35e5", "TraceFlags": "01", "Resource": [ { "Key": "service.name", "Value": { "Type": "STRING", "Value": "dice" } }, { "Key": "service.version", "Value": { "Type": "STRING", "Value": "0.1.0" } }, { "Key": "telemetry.sdk.language", "Value": { "Type": "STRING", "Value": "go" } }, { "Key": "telemetry.sdk.name", "Value": { "Type": "STRING", "Value": "opentelemetry" } }, { "Key": "telemetry.sdk.version", "Value": { "Type": "STRING", "Value": "1.19.0-rc.1" } } ], "Scope": { "Name": "rolldice", "Version": "", "SchemaURL": "" }, "DroppedAttributes": 0 }

Оновіть сторінку http://localhost:8080/rolldice/Alice кілька разів, а потім або зачекайте трохи, або завершіть роботу застосунку, і ви побачите метрики у виводі консолі. Ви побачите метрику dice.rolls, що виводиться в консоль, з окремими підрахунками для кожного значення кидка, а також HTTP метрики, створені бібліотекою інструментів.

Переглянути приклад виводу
{ "Resource": [ { "Key": "service.name", "Value": { "Type": "STRING", "Value": "dice" } }, { "Key": "service.version", "Value": { "Type": "STRING", "Value": "0.1.0" } }, { "Key": "telemetry.sdk.language", "Value": { "Type": "STRING", "Value": "go" } }, { "Key": "telemetry.sdk.name", "Value": { "Type": "STRING", "Value": "opentelemetry" } }, { "Key": "telemetry.sdk.version", "Value": { "Type": "STRING", "Value": "1.19.0-rc.1" } } ], "ScopeMetrics": [ { "Scope": { "Name": "rolldice", "Version": "", "SchemaURL": "" }, "Metrics": [ { "Name": "dice.rolls", "Description": "Кількість кидків за значенням кидка", "Unit": "{roll}", "Data": { "DataPoints": [ { "Attributes": [ { "Key": "roll.value", "Value": { "Type": "INT64", "Value": 1 } } ], "StartTime": "2023-09-25T12:42:04.279204638+02:00", "Time": "2023-09-25T12:42:15.482694258+02:00", "Value": 4 }, { "Attributes": [ { "Key": "roll.value", "Value": { "Type": "INT64", "Value": 5 } } ], "StartTime": "2023-09-25T12:42:04.279204638+02:00", "Time": "2023-09-25T12:42:15.482694258+02:00", "Value": 3 }, { "Attributes": [ { "Key": "roll.value", "Value": { "Type": "INT64", "Value": 3 } } ], "StartTime": "2023-09-25T12:42:04.279204638+02:00", "Time": "2023-09-25T12:42:15.482694258+02:00", "Value": 4 }, { "Attributes": [ { "Key": "roll.value", "Value": { "Type": "INT64", "Value": 2 } } ], "StartTime": "2023-09-25T12:42:04.279204638+02:00", "Time": "2023-09-25T12:42:15.482694258+02:00", "Value": 2 }, { "Attributes": [ { "Key": "roll.value", "Value": { "Type": "INT64", "Value": 6 } } ], "StartTime": "2023-09-25T12:42:04.279204638+02:00", "Time": "2023-09-25T12:42:15.482694258+02:00", "Value": 5 }, { "Attributes": [ { "Key": "roll.value", "Value": { "Type": "INT64", "Value": 4 } } ], "StartTime": "2023-09-25T12:42:04.279204638+02:00", "Time": "2023-09-25T12:42:15.482694258+02:00", "Value": 9 } ], "Temporality": "CumulativeTemporality", "IsMonotonic": true } } ] }, { "Scope": { "Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", "Version": "0.44.0", "SchemaURL": "" }, "Metrics": [ { "Name": "http.server.request_content_length", "Description": "", "Unit": "", "Data": { "DataPoints": [ { "Attributes": [ { "Key": "http.flavor", "Value": { "Type": "STRING", "Value": "1.1" } }, { "Key": "http.method", "Value": { "Type": "STRING", "Value": "GET" } }, { "Key": "http.route", "Value": { "Type": "STRING", "Value": "/rolldice/Alice" } }, { "Key": "http.scheme", "Value": { "Type": "STRING", "Value": "http" } }, { "Key": "http.status_code", "Value": { "Type": "INT64", "Value": 200 } }, { "Key": "net.host.name", "Value": { "Type": "STRING", "Value": "localhost" } }, { "Key": "net.host.port", "Value": { "Type": "INT64", "Value": 8080 } } ], "StartTime": "2023-09-25T12:42:04.279212238+02:00", "Time": "2023-09-25T12:42:15.482695758+02:00", "Value": 0 } ], "Temporality": "CumulativeTemporality", "IsMonotonic": true } }, { "Name": "http.server.response_content_length", "Description": "", "Unit": "", "Data": { "DataPoints": [ { "Attributes": [ { "Key": "http.flavor", "Value": { "Type": "STRING", "Value": "1.1" } }, { "Key": "http.method", "Value": { "Type": "STRING", "Value": "GET" } }, { "Key": "http.route", "Value": { "Type": "STRING", "Value": "/rolldice/Alice" } }, { "Key": "http.scheme", "Value": { "Type": "STRING", "Value": "http" } }, { "Key": "http.status_code", "Value": { "Type": "INT64", "Value": 200 } }, { "Key": "net.host.name", "Value": { "Type": "STRING", "Value": "localhost" } }, { "Key": "net.host.port", "Value": { "Type": "INT64", "Value": 8080 } } ], "StartTime": "2023-09-25T12:42:04.279214438+02:00", "Time": "2023-09-25T12:42:15.482696158+02:00", "Value": 54 } ], "Temporality": "CumulativeTemporality", "IsMonotonic": true } }, { "Name": "http.server.duration", "Description": "", "Unit": "", "Data": { "DataPoints": [ { "Attributes": [ { "Key": "http.flavor", "Value": { "Type": "STRING", "Value": "1.1" } }, { "Key": "http.method", "Value": { "Type": "STRING", "Value": "GET" } }, { "Key": "http.route", "Value": { "Type": "STRING", "Value": "/rolldice/Alice" } }, { "Key": "http.scheme", "Value": { "Type": "STRING", "Value": "http" } }, { "Key": "http.status_code", "Value": { "Type": "INT64", "Value": 200 } }, { "Key": "net.host.name", "Value": { "Type": "STRING", "Value": "localhost" } }, { "Key": "net.host.port", "Value": { "Type": "INT64", "Value": 8080 } } ], "StartTime": "2023-09-25T12:42:04.279219438+02:00", "Time": "2023-09-25T12:42:15.482697158+02:00", "Count": 27, "Bounds": [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ], "BucketCounts": [ 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], "Min": {}, "Max": {}, "Sum": 2.1752759999999993 } ], "Temporality": "CumulativeTemporality" } } ] } ] }

Наступні кроки

Для отримання додаткової інформації про інструментування вашого коду зверніться до документації ручного інструментування.

Вам також потрібно буде налаштувати відповідний експортер для експорту ваших телеметричних даних до одного або декількох бекендів телеметрії.

Якщо ви хочете дослідити складніший приклад, подивіться на Демо OpenTelemetry, яке включає заснований на Go Сервіс оформлення замовлень, Сервіс каталогу продуктів, та Сервіс бухгалтерії