Membuat trace dan metrik dengan Go

Dokumen ini menjelaskan cara mengubah aplikasi Go untuk mengumpulkan data trace dan metrik menggunakan framework OpenTelemetry open source, dan cara menulis log JSON terstruktur ke output standar. Dokumen ini juga memberikan informasi tentang contoh aplikasi yang dapat Anda instal dan jalankan. Aplikasi dikonfigurasi untuk menghasilkan metrik, trace, dan log.

Untuk mempelajari instrumentasi lebih lanjut, lihat dokumen berikut:

Tentang konteks

Konteks OpenTelemetry adalah mekanisme untuk membawa nilai cakupan eksekusi di seluruh API dalam suatu proses. Penggunaan konteks yang penting adalah untuk membawa span aktif saat ini sehingga dapat diubah, atau direferensikan sebagai induk dari span baru saat dibuat. Ringkasnya:

  • Konteks mengacu pada mekanisme untuk menyebarkan nilai cakupan eksekusi, termasuk span aktif saat ini, di seluruh API dalam proses.

  • Konteks Rentang adalah objek yang tidak dapat diubah di setiap rentang yang menyertakan ID rekaman aktivitas, ID rentang, serta tanda dan status untuk rekaman aktivitas.

  • Penerapan adalah mekanisme yang memindahkan konteks antarlayanan dan proses.

context.Context library standar Go juga membawa nilai cakupan di seluruh batas API. Biasanya, fungsi pengendali di server menerima Context masuk dan meneruskannya melalui rantai panggilan ke klien yang membuat permintaan keluar.

Library standar Go context.Context digunakan sebagai implementasi Konteks OpenTelemetry di Go.

Sebelum memulai

Enable the Cloud Logging, Cloud Monitoring, and Cloud Trace APIs.

Enable the APIs

Menginstrumentasikan aplikasi untuk mengumpulkan trace, metrik, dan log

Untuk melengkapi aplikasi Anda guna mengumpulkan data rekaman aktivitas dan metrik, serta menulis JSON terstruktur ke output standar, lakukan langkah-langkah berikut seperti yang dijelaskan di bagian berikutnya dalam dokumen ini:

  1. Mengonfigurasi fungsi utama
  2. Mengonfigurasi OpenTelemetry
  3. Mengonfigurasi logging terstruktur
  4. Menambahkan instrumentasi ke server HTTP
  5. Menautkan span rekaman aktivitas dengan log dan metrik
  6. Menambahkan instrumentasi ke klien HTTP
  7. Menulis log terstruktur

Mengonfigurasi fungsi utama

Untuk mengonfigurasi aplikasi agar dapat menulis log terstruktur dan mengumpulkan metrik serta data rekaman aktivitas menggunakan OpenTelemetry, perbarui fungsi main untuk mengonfigurasi paket logging terstruktur Go, slog, dan untuk mengonfigurasi OpenTelemetry.

Contoh kode berikut mengilustrasikan fungsi main yang memanggil dua fungsi bantuan, setupLogging() dan setupOpenTelemetry(). Fungsi bantuan ini mengonfigurasi paket logging dan OpenTelemetry.

Untuk melihat contoh lengkapnya, klik Lainnya, lalu pilih Lihat di 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)
	}
}

Setelah mengonfigurasi paket logging, untuk menautkan log ke data rekaman aktivitas, Anda harus meneruskan Go Context ke logger. Untuk informasi selengkapnya, lihat bagian Menulis log terstruktur dalam dokumen ini.

Mengonfigurasi OpenTelemetry

Untuk mengumpulkan dan mengekspor trace dan metrik menggunakan protokol OTLP, konfigurasikan instance TracerProvider dan MeterProvider global. Contoh kode berikut mengilustrasikan fungsi setupOpenTelemetry, yang dipanggil dari fungsi 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
}

Contoh kode sebelumnya mengonfigurasi TextMapPropagator global untuk menggunakan format Konteks Trace W3C untuk mem-propagasi konteks trace. Konfigurasi ini memastikan bahwa span memiliki hubungan induk-turunan yang benar dalam rekaman aktivitas.

Untuk memastikan semua telemetri yang tertunda dihapus dan koneksi ditutup dengan baik, fungsi setupOpenTelemetry akan menampilkan fungsi bernama shutdown, yang melakukan tindakan tersebut.

Mengonfigurasi logging terstruktur

Untuk menyertakan informasi rekaman aktivitas sebagai bagian dari log berformat JSON yang ditulis ke output standar, konfigurasikan paket logging terstruktur Go, slog. Contoh kode berikut mengilustrasikan fungsi setupLogging, yang dipanggil dari fungsi 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))
}

Kode sebelumnya memanggil fungsi handlerWithSpanContext, yang mengekstrak informasi dari instance Context dan menambahkan informasi tersebut sebagai atribut ke log. Atribut ini kemudian dapat digunakan untuk mengaitkan log dengan rekaman aktivitas:

  • logging.googleapis.com/trace: Nama resource rekaman aktivitas yang terkait dengan entri log.
  • logging.googleapis.com/spanId: ID span dengan rekaman aktivitas yang terkait dengan entri log.
  • logging.googleapis.com/trace_sampled: Nilai kolom ini harus true atau false.

Untuk mengetahui informasi selengkapnya tentang kolom ini, lihat struktur 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
}

Menambahkan instrumentasi ke server HTTP

Untuk menambahkan instrumentasi trace dan metrik ke permintaan yang ditangani oleh server HTTP, gunakan OpenTelemetry. Contoh berikut menggunakan pengendali otelhttp untuk menyebarkan konteks, dan untuk instrumentasi trace dan metrik:

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)
}

Pada kode sebelumnya, pengendali otelhttp menggunakan instance TracerProvider, MeterProvider, dan TextMapPropagator global. Fungsi setupOpenTelemetry mengonfigurasi instance ini.

Menautkan span rekaman aktivitas dengan log dan metrik

Untuk menautkan span server dan klien, serta mengaitkan metrik dan log, teruskan instance Go Context ke permintaan HTTP dan saat Anda menulis log. Contoh berikut mengilustrasikan pengendali rute yang mengekstrak instance Context Go dan meneruskan instance tersebut ke logger dan ke fungsi callSingle, yang membuat permintaan HTTP keluar:

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")
}

Dalam kode sebelumnya, panggilan fungsi r.Context() mengambil Go Context dari permintaan HTTP.

Menambahkan instrumentasi ke klien HTTP

Untuk memasukkan konteks rekaman aktivitas ke dalam permintaan HTTP keluar dan menambahkan instrumentasi rekaman aktivitas dan metrik, panggil fungsi otelhttp.Get. Pada contoh berikut, fungsi callSingle melakukan tindakan ini:

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()
}

Pada kode sebelumnya, pengendali otelhttp menggunakan instance TracerProvider, MeterProvider, dan TextMapPropagator global. Fungsi setupOpenTelemetry mengonfigurasi instance ini.

Menulis log terstruktur

Untuk menulis log terstruktur yang ditautkan ke rekaman aktivitas, gunakan paket logging terstruktur Go, slog, dan teruskan instance Context Go ke logger. Instance Context Go diperlukan saat Anda ingin menautkan log ke span. Misalnya, pernyataan berikut menunjukkan cara memanggil metode InfoContext untuk slog, dan mengilustrasikan cara menambahkan kolom subRequests ke instance JSON:

slog.InfoContext(r.Context(), "handle /multi request", slog.Int("subRequests", subRequests))

Menjalankan aplikasi contoh yang dikonfigurasi untuk mengumpulkan telemetri

Aplikasi contoh menggunakan format yang netral vendor, termasuk JSON untuk log dan OTLP untuk metrik dan rekaman aktivitas. Untuk merutekan telemetri ke Google Cloud, contoh ini menggunakan Collector OpenTelemetry yang dikonfigurasi dengan pengekspor Google. Generator beban di aplikasi mengeluarkan permintaan ke rute aplikasi.

Mendownload dan men-deploy aplikasi

Untuk menjalankan contoh, lakukan hal berikut:

  1. In the Google Cloud console, activate Cloud Shell.

    Activate Cloud Shell

    At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

  2. Meng-cloning repository

    git clone https://github.com/GoogleCloudPlatform/golang-samples
    
  3. Buka direktori OpenTelemetry:

    cd golang-samples/opentelemetry/instrumentation
    
  4. Build dan jalankan contoh:

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

    Jika Anda tidak menjalankan di Cloud Shell, jalankan aplikasi dengan variabel lingkungan GOOGLE_APPLICATION_CREDENTIALS yang mengarah ke file kredensial. Kredensial Default Aplikasi menyediakan file kredensial di $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
    

Melihat metrik

Instrumentasi OpenTelemetry di aplikasi contoh menghasilkan metrik Prometheus yang dapat Anda lihat menggunakan Metrics Explorer:

  • Prometheus/http_server_duration/histogram mencatat durasi permintaan server dan menyimpan hasilnya dalam histogram.

  • Prometheus/http_server_request_content_length_total/counter mencatat panjang konten permintaan untuk rute HTTP /multi dan /single. Pengukuran untuk metrik ini bersifat kumulatif, yang berarti setiap nilai mewakili total sejak pengumpulan nilai dimulai.

  • Prometheus/http_server_response_content_length_total/counter mencatat panjang konten respons untuk rute HTTP /multi dan /single. Pengukuran untuk metrik ini bersifat kumulatif.

Untuk melihat metrik yang dihasilkan oleh aplikasi contoh, lakukan hal berikut:
  1. Di konsol Google Cloud, buka halaman  Metrics explorer:

    Buka Metrics explorer

    Jika Anda menggunakan kotak penelusuran untuk menemukan halaman ini, pilih hasil yang subjudulnya adalah Monitoring.

  2. Pada elemen Metric, luaskan menu Select a metric, masukkan http_server di panel filter, lalu gunakan submenu untuk memilih jenis dan metrik resource tertentu:
    1. Di menu Active resources, pilih Prometheus Target.
    2. Di menu Active metric categories, pilih Http.
    3. Di menu Active metrics, pilih metrik.
    4. Klik Terapkan.
  3. Konfigurasi cara data dilihat.

    Jika pengukuran untuk metrik bersifat kumulatif, Metrics Explorer akan otomatis menormalisasi data yang diukur berdasarkan periode penyelarasan, yang akan menghasilkan diagram yang menampilkan rasio. Untuk mengetahui informasi selengkapnya, lihat Jenis, tipe, dan konversi.

    Saat nilai bilangan bulat atau ganda diukur, seperti dengan dua metrik counter, Metrics Explorer akan otomatis menjumlahkan semua deret waktu. Untuk melihat data untuk rute HTTP /multi dan /single, tetapkan menu pertama entri Aggregation ke None.

    Untuk informasi selengkapnya tentang cara mengonfigurasi diagram, lihat Memilih metrik saat menggunakan Metrics Explorer.

Melihat trace Anda

Untuk melihat data rekaman aktivitas, lakukan hal berikut:

  1. Di konsol Google Cloud, buka halaman Trace Explorer:

    Buka Trace Explorer

    Anda juga dapat menemukan halaman ini menggunakan kotak penelusuran.

  2. Pada diagram pencar, pilih rekaman aktivitas dengan URI /multi.
  3. Pada diagram Gantt di panel Trace details, pilih rentang yang berlabel /multi.

    Panel akan terbuka dan menampilkan informasi tentang permintaan HTTP. Detail ini mencakup metode, kode status, jumlah byte, dan agen pengguna pemanggil.

  4. Untuk melihat log yang terkait dengan rekaman aktivitas ini, pilih tab Logs & Events.

    Tab ini menampilkan setiap log. Untuk melihat detail entri log, luaskan entri log. Anda juga dapat mengklik View Logs dan melihat log menggunakan Logs Explorer.

Untuk informasi selengkapnya tentang cara menggunakan Cloud Trace Explorer, lihat Menemukan dan menjelajahi trace.

Melihat log

Dari Logs Explorer, Anda dapat memeriksa log, dan juga dapat melihat rekaman aktivitas terkait, jika ada.

  1. Di konsol Google Cloud, buka halaman Logs Explorer:

    Buka Logs Explorer

    Jika Anda menggunakan kotak penelusuran untuk menemukan halaman ini, pilih hasil yang subjudulnya adalah Logging.

  2. Temukan log dengan deskripsi handle /multi request.

    Untuk melihat detail log, luaskan entri log. Di kolom jsonPayload, ada entri berlabel subRequests. Entri ini ditambahkan oleh pernyataan dalam fungsi handleMulti.

  3. Klik Traces pada entri log dengan pesan "handle /multi request", lalu pilih View trace details.

    Panel Trace details akan terbuka dan menampilkan rekaman aktivitas yang dipilih.

Untuk mengetahui informasi selengkapnya tentang cara menggunakan Logs Explorer, lihat Melihat log menggunakan Logs Explorer.

Langkah selanjutnya