OTel メトリクス

概要

OpenTelemetryはPush方式でアプリケーションのメトリクスを公開します。

機能

1. メトリクスエクスポート機能

メトリクスエクスポート機能はアプリケーションのメトリクスをオブザーバビリティバックエンドへ公開します。

OpenTelemetryを利用する場合は、エージェントがアプリケーションからOpenTelemtryバックエンドに向けてメトリクスを送信します。

graph LR
  Client --> Application["Application</br>(OpenTelemetry agent)"]
  Application --"Push metrics"--> Backend["OpenTelemetry</br>Backend"]

以下は、Opentelemetryよりメトリクスを公開する例です。

package main

import (
	"context"
	"log"
	"net/http"
	"time"

	"github.com/aileron-projects/aileron-observability/metrics/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
	"go.opentelemetry.io/otel/sdk/metric"
)

func main() {
	// Use gRPC exporter without TLS.
	exporter, _ := otlpmetricgrpc.New(context.Background(), otlpmetricgrpc.WithInsecure())
	_, _ = otel.New(&otel.Config{
		ProviderOpts: []metric.Option{
			metric.WithReader(metric.NewPeriodicReader(
				exporter,
				metric.WithInterval(time.Second),
				metric.WithTimeout(10*time.Second),
			)),
		},
	})

	log.Println("server listening on localhost:8080")
	svr := &http.Server{
		Addr: ":8080",
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			log.Println(r.Method, r.URL.Path) // Simple access log.
			_, _ = w.Write([]byte("Hello!!"))
		}),
		ReadTimeout: 10 * time.Second,
	}
	if err := svr.ListenAndServe(); err != nil {
		panic(err)
	}
}

2. APIリクエスト統計取得機能

APIリクエスト統計取得機能はAPIコールに関するメトリクスを取得する機能です。 この機能はサーバサイドミドルウェア、あるいはクライアントサイドミドルウェアとして機能します。

APIコールは以下の項目によりグルーピングされ、カウントされます。

  • method: HTTPメソッド
  • host: ホスト名
  • path: URLパス
  • code: ステータスコード

サーバサイドのAPIメトリクスを取得するにはサーバサイドミドルウェアを利用します。 クライアントサイドのAPIメトリクスを取得するにはクライアントサイドミドルウェアを利用します。

セキュリティに関する特記事項

メトリクスのエンドポイントに適切なアクセス制御を行ってください。

性能に関する特記事項

性能に関する特記事項は特にありません。

実装例・使い方

メトリクス公開

OpenTelemtryはPush方式でメトリクスを公開します。 以下の実装例の通り、OpenTelemtryのエージェントを作成することでメトリクスをバックエンドへ送信できます。

package main

import (
	"context"
	"log"
	"net/http"
	"time"

	"github.com/aileron-projects/aileron-observability/metrics/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
	"go.opentelemetry.io/otel/sdk/metric"
)

func main() {
	// Use gRPC exporter without TLS.
	exporter, _ := otlpmetricgrpc.New(context.Background(), otlpmetricgrpc.WithInsecure())
	_, _ = otel.New(&otel.Config{
		ProviderOpts: []metric.Option{
			metric.WithReader(metric.NewPeriodicReader(
				exporter,
				metric.WithInterval(time.Second),
				metric.WithTimeout(10*time.Second),
			)),
		},
	})

	log.Println("server listening on localhost:8080")
	svr := &http.Server{
		Addr: ":8080",
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			log.Println(r.Method, r.URL.Path) // Simple access log.
			_, _ = w.Write([]byte("Hello!!"))
		}),
		ReadTimeout: 10 * time.Second,
	}
	if err := svr.ListenAndServe(); err != nil {
		panic(err)
	}
}

以下にサンプルのdocker-composeとOpenTelemetryコレクターのconfigファイル、Prometheusのconfigファイルを示します。

{ { % code source="ex_basic_grpc/docker-compose.yaml" % } }
{ { % code source="ex_basic_grpc/prometheus.yaml" % } }

なお、HTTPによりバックエンドへメトリクスを送信する場合は以下のように利用します。

package main

import (
	"context"
	"log"
	"net/http"
	"time"

	"github.com/aileron-projects/aileron-observability/metrics/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
	"go.opentelemetry.io/otel/sdk/metric"
)

func main() {
	// Use HTTP exporter without TLS.
	exporter, _ := otlpmetrichttp.New(context.Background(), otlpmetrichttp.WithInsecure())
	_, _ = otel.New(&otel.Config{
		ProviderOpts: []metric.Option{
			metric.WithReader(metric.NewPeriodicReader(
				exporter,
				metric.WithInterval(time.Second),
				metric.WithTimeout(10*time.Second),
			)),
		},
	})

	log.Println("server listening on localhost:8080")
	svr := &http.Server{
		Addr: ":8080",
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			log.Println(r.Method, r.URL.Path) // Simple access log.
			_, _ = w.Write([]byte("Hello!!"))
		}),
		ReadTimeout: 10 * time.Second,
	}
	if err := svr.ListenAndServe(); err != nil {
		panic(err)
	}
}

また、デバッグなどで標準出力にメトリクスを出力する場合は以下の通りです。

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/aileron-projects/aileron-observability/metrics/otel"
	"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
	"go.opentelemetry.io/otel/sdk/metric"
)

func main() {
	// Use Stdout exporter.
	exporter, _ := stdoutmetric.New()
	_, _ = otel.New(&otel.Config{
		ProviderOpts: []metric.Option{
			metric.WithReader(metric.NewPeriodicReader(
				exporter,
				metric.WithInterval(time.Second),
				metric.WithTimeout(10*time.Second),
			)),
		},
	})

	log.Println("server listening on localhost:8080")
	svr := &http.Server{
		Addr: ":8080",
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			log.Println(r.Method, r.URL.Path) // Simple access log.
			_, _ = w.Write([]byte("Hello!!"))
		}),
		ReadTimeout: 10 * time.Second,
	}
	if err := svr.ListenAndServe(); err != nil {
		panic(err)
	}
}

サーバサイドAPIメトリクス

サーバサイドでAPIコールのメトリクスを取得するにはサーバサイドミドルウェアを利用します。 以下に簡単な利用例を示します。

package main

import (
	"context"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"time"

	"github.com/aileron-projects/aileron-observability/metrics/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
	"go.opentelemetry.io/otel/sdk/metric"
)

func main() {
	// Use HTTP exporter without TLS.
	exporter, _ := otlpmetrichttp.New(context.Background(), otlpmetrichttp.WithInsecure())
	m, _ := otel.New(&otel.Config{
		ProviderOpts: []metric.Option{
			metric.WithReader(metric.NewPeriodicReader(
				exporter,
				metric.WithInterval(time.Second),
				metric.WithTimeout(10*time.Second),
			)),
		},
	})

	target, _ := url.Parse("http://httpbin.org")
	proxy := httputil.NewSingleHostReverseProxy(target)

	log.Println("server listening on localhost:8080")
	svr := &http.Server{
		Addr:        ":8080",
		Handler:     m.ServerMiddleware(proxy),
		ReadTimeout: 10 * time.Second,
	}
	if err := svr.ListenAndServe(); err != nil {
		panic(err)
	}
}

クライアントサイドAPIメトリクス

クライアントサイドでAPIコールのメトリクスを取得するにはクライアントサイドミドルウェアを利用します。 以下に簡単な利用例を示します。

package main

import (
	"context"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"time"

	"github.com/aileron-projects/aileron-observability/metrics/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
	"go.opentelemetry.io/otel/sdk/metric"
)

func main() {
	// Use HTTP exporter without TLS.
	exporter, _ := otlpmetrichttp.New(context.Background(), otlpmetrichttp.WithInsecure())
	m, _ := otel.New(&otel.Config{
		ProviderOpts: []metric.Option{
			metric.WithReader(metric.NewPeriodicReader(
				exporter,
				metric.WithInterval(time.Second),
				metric.WithTimeout(10*time.Second),
			)),
		},
	})

	rt := http.DefaultTransport // A http client.
	rt = m.ClientMiddleware(rt) // Apply client-side API call counting.

	target, _ := url.Parse("http://httpbin.org")
	proxy := httputil.NewSingleHostReverseProxy(target)
	proxy.Transport = rt

	log.Println("server listening on localhost:8080")
	svr := &http.Server{
		Addr:        ":8080",
		Handler:     proxy,
		ReadTimeout: 10 * time.Second,
	}
	if err := svr.ListenAndServe(); err != nil {
		panic(err)
	}
}