From 03f54bf5dd225f47c31347e568321308d465f26b Mon Sep 17 00:00:00 2001 From: soneda-yuya Date: Fri, 21 Nov 2025 17:24:06 +0900 Subject: [PATCH 1/2] chore(server): refactore otel --- go.work.sum | 4 + server/go.mod | 40 +++-- server/go.sum | 60 ++++--- server/internal/app/app.go | 6 +- server/internal/app/config/config.go | 64 ++++---- server/internal/app/main.go | 27 ++- server/internal/app/otel/middleware.go | 25 +++ server/internal/app/otel/otel.go | 219 +++++++++++++++++++++++++ server/internal/app/tracer.go | 131 --------------- 9 files changed, 368 insertions(+), 208 deletions(-) create mode 100644 server/internal/app/otel/middleware.go create mode 100644 server/internal/app/otel/otel.go diff --git a/go.work.sum b/go.work.sum index fb30f14a7a..44b3fb3c2f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -518,6 +518,7 @@ github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxj github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/apache/arrow/go/v12 v12.0.0 h1:xtZE63VWl7qLdB0JObIXvvhGjoVNrQ9ciIHG2OK5cmc= github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY= @@ -828,6 +829,7 @@ github.com/reearth/reearthx v0.0.0-20240229141256-04bd46953fe6 h1:7EMP5wvzOcip/B github.com/reearth/reearthx v0.0.0-20240229141256-04bd46953fe6/go.mod h1:8DSD6e+h1KhfhO4TceYDDPpLkhZJH2CLF6nHxyV8hts= github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481 h1:jMxcLa+VjJKhpCwbLUXAD15wJ+hhvXMLujCl3MkXpfM= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= @@ -896,6 +898,7 @@ go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDI go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/metric v0.30.0/go.mod h1:/ShZ7+TS4dHzDFmfi1kSXMhMVubNoP0oIaBp70J6UXU= @@ -926,6 +929,7 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= diff --git a/server/go.mod b/server/go.mod index 603e906df4..27f4cc5554 100644 --- a/server/go.mod +++ b/server/go.mod @@ -4,7 +4,7 @@ require ( cloud.google.com/go/profiler v0.4.2 cloud.google.com/go/storage v1.56.1 github.com/99designs/gqlgen v0.17.73 - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 // indirect github.com/avast/retry-go/v4 v4.6.1 github.com/aws/aws-sdk-go-v2 v1.36.3 github.com/aws/aws-sdk-go-v2/config v1.29.14 @@ -37,9 +37,9 @@ require ( github.com/samber/lo v1.50.0 github.com/spf13/afero v1.14.0 github.com/square/mongo-lock v0.0.0-20230808145049-cfcf499f6bf0 - github.com/stretchr/testify v1.10.0 - github.com/uber/jaeger-client-go v2.30.0+incompatible - github.com/uber/jaeger-lib v2.4.1+incompatible + github.com/stretchr/testify v1.11.1 + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/vektah/dataloaden v0.3.0 github.com/vektah/gqlparser/v2 v2.5.27 github.com/zitadel/oidc v1.13.5 // indirect @@ -48,9 +48,9 @@ require ( go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.59.0 go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.59.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 - go.opentelemetry.io/otel v1.36.0 - go.opentelemetry.io/otel/sdk v1.36.0 - go.opentelemetry.io/otel/trace v1.36.0 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/mock v0.6.0 golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b golang.org/x/net v0.43.0 @@ -58,22 +58,26 @@ require ( golang.org/x/text v0.28.0 golang.org/x/tools v0.36.0 google.golang.org/api v0.247.0 - google.golang.org/grpc v1.74.2 - google.golang.org/protobuf v1.36.7 + google.golang.org/grpc v1.75.0 + google.golang.org/protobuf v1.36.8 gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/square/go-jose.v2 v2.6.0 // indirect ) +require ( + cloud.google.com/go/compute/metadata v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 +) + require ( cel.dev/expr v0.24.0 // indirect cloud.google.com/go v0.121.6 // indirect cloud.google.com/go/auth v0.16.5 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.8.0 // indirect cloud.google.com/go/iam v1.5.2 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect cloud.google.com/go/trace v1.11.6 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect @@ -99,6 +103,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect github.com/aws/smithy-go v1.22.3 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/coder/websocket v1.8.13 // indirect @@ -111,7 +116,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 // indirect - github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -131,6 +136,7 @@ require ( github.com/gorilla/schema v1.2.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/imkira/go-interpol v1.1.0 // indirect @@ -179,8 +185,10 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib v1.35.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect @@ -190,8 +198,8 @@ require ( golang.org/x/sys v0.36.0 // indirect golang.org/x/time v0.12.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/server/go.sum b/server/go.sum index ec91f01d37..1fa0d966df 100644 --- a/server/go.sum +++ b/server/go.sum @@ -28,8 +28,8 @@ github.com/99designs/gqlgen v0.17.73/go.mod h1:2RyGWjy2k7W9jxrs8MOQthXGkD3L3oGr0 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 h1:Jtr816GUk6+I2ox9L/v+VcOwN6IyGOEDTSNHfD6m9sY= @@ -107,6 +107,8 @@ github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= @@ -148,8 +150,8 @@ github.com/globusdigital/deep-copy v0.5.4 h1:W4CHb7ul8VpZN0uj529AvfFH9Rs14MjKSaJ github.com/globusdigital/deep-copy v0.5.4/go.mod h1:UDFAJxcRE6lzC16dS/njdFBd3TXL4yeYH47PWkJJgEM= github.com/go-faker/faker/v4 v4.6.0 h1:6aOPzNptRiDwD14HuAnEtlTa+D1IfFuEHO8+vEFwjTs= github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62z1YqFH0m0= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -222,6 +224,8 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -373,8 +377,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= @@ -449,18 +453,24 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6h go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/contrib/propagators/b3 v1.34.0 h1:9pQdCEvV/6RWQmag94D6rhU+A4rzUhYBEJ8bpscx5p8= go.opentelemetry.io/contrib/propagators/b3 v1.34.0/go.mod h1:FwM71WS8i1/mAK4n48t0KU6qUS/OZRBgDrHZv3RlJ+w= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -553,6 +563,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.0.0-20170921000349-586095a6e407/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc= google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM= @@ -560,15 +572,15 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/genproto v0.0.0-20170918111702-1e559d0a00ee/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.2.1-0.20170921194603-d4b75ebd4f9f/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= -google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/server/internal/app/app.go b/server/internal/app/app.go index 3b6f8bacd4..a2ebb0de02 100644 --- a/server/internal/app/app.go +++ b/server/internal/app/app.go @@ -13,11 +13,11 @@ import ( "github.com/labstack/echo/v4/middleware" "github.com/reearth/reearth/server/internal/adapter" appmiddleware "github.com/reearth/reearth/server/internal/adapter/middleware" + "github.com/reearth/reearth/server/internal/app/otel" "github.com/reearth/reearth/server/internal/usecase/interactor" "github.com/reearth/reearthx/appx" "github.com/reearth/reearthx/log" "github.com/reearth/reearthx/rerror" - "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" ) func initEcho(ctx context.Context, cfg *ServerConfig) *echo.Echo { @@ -34,9 +34,11 @@ func initEcho(ctx context.Context, cfg *ServerConfig) *echo.Echo { // basic middleware logger := log.NewEcho() e.Logger = logger + if cfg.Config.OtelEnabled { + e.Use(otel.Middleware(otel.OtelServiceName)) + } e.Use( middleware.Recover(), - otelecho.Middleware("reearth-visualizer"), appmiddleware.RestAPITracingMiddleware(), // Add detailed REST API tracing echo.WrapMiddleware(appx.RequestIDMiddleware()), logger.AccessLogger(), diff --git a/server/internal/app/config/config.go b/server/internal/app/config/config.go index 61160feb38..f6948e1658 100644 --- a/server/internal/app/config/config.go +++ b/server/internal/app/config/config.go @@ -4,6 +4,7 @@ import ( "net/url" "os" "strings" + "time" "github.com/joho/godotenv" "github.com/k0kubun/pp/v3" @@ -23,34 +24,41 @@ func init() { type Mailer mailer.Mailer type Config struct { mailer.Config - Port string `default:"8080" envconfig:"PORT"` - ServerHost string `pp:",omitempty"` - Host string `default:"http://localhost:8080"` - Host_Web string `pp:",omitempty"` - Dev bool `pp:",omitempty"` - DB string `default:"mongodb://localhost"` - DB_Account string `default:"reearth_account" pp:",omitempty"` - DB_Users []appx.NamedURI `pp:",omitempty"` - DB_Vis string `default:"reearth" pp:",omitempty"` - GraphQL GraphQLConfig `pp:",omitempty"` - Published PublishedConfig `pp:",omitempty"` - GCPProject string `envconfig:"GOOGLE_CLOUD_PROJECT" pp:",omitempty"` - Profiler string `pp:",omitempty"` - Tracer string `pp:",omitempty"` - TracerSample float64 `default:"0.01" envconfig:"REEARTH_TRACER_SAMPLE" pp:",omitempty"` - Marketplace MarketplaceConfig `pp:",omitempty"` - AssetBaseURL string `default:"http://localhost:8080/assets"` - Origins []string `pp:",omitempty"` - Policy PolicyConfig `pp:",omitempty"` - Web_Disabled bool `pp:",omitempty"` - Web_App_Disabled bool `pp:",omitempty"` - Web map[string]string `pp:",omitempty"` - Web_Config JSON `pp:",omitempty"` - Web_Title string `pp:",omitempty"` - Web_FaviconURL string `pp:",omitempty"` - SignupSecret string `pp:",omitempty"` - SignupDisabled bool `pp:",omitempty"` - HTTPSREDIRECT bool `pp:",omitempty"` + Port string `default:"8080" envconfig:"PORT"` + ServerHost string `pp:",omitempty"` + Host string `default:"http://localhost:8080"` + Host_Web string `pp:",omitempty"` + Dev bool `pp:",omitempty"` + DB string `default:"mongodb://localhost"` + DB_Account string `default:"reearth_account" pp:",omitempty"` + DB_Users []appx.NamedURI `pp:",omitempty"` + DB_Vis string `default:"reearth" pp:",omitempty"` + GraphQL GraphQLConfig `pp:",omitempty"` + Published PublishedConfig `pp:",omitempty"` + GCPProject string `envconfig:"GOOGLE_CLOUD_PROJECT" pp:",omitempty"` + OtelEnabled bool `env:"REEARTH_VISUALIZER_OTEL_ENABLED" envDefault:"false"` + OtelEndpoint string `env:"REEARTH_VISUALIZER_OTEL_ENDPOINT" envDefault:"localhost:4317"` + OtelExporterType string `env:"REEARTH_VISUALIZER_OTEL_EXPORTER_TYPE" envDefault:"otlp"` // otlp, jaeger, or gcp + OtelBatchTimeout time.Duration `env:"REEARTH_VISUALIZER_OTEL_BATCH_TIMEOUT" envDefault:"1s"` // seconds + OtelMaxExportBatchSize int `env:"REEARTH_VISUALIZER_OTEL_MAX_EXPORT_BATCH_SIZE" envDefault:"512"` + OtelMaxQueueSize int `env:"REEARTH_VISUALIZER_OTEL_MAX_QUEUE_SIZE" envDefault:"2048"` + OtelSamplingRatio float64 `env:"REEARTH_VISUALIZER_OTEL_SAMPLING_RATIO" envDefault:"1.0"` // 0.0 to 1.0 + Profiler string `pp:",omitempty"` + Tracer string `pp:",omitempty"` + TracerSample float64 `default:"0.01" envconfig:"REEARTH_TRACER_SAMPLE" pp:",omitempty"` + Marketplace MarketplaceConfig `pp:",omitempty"` + AssetBaseURL string `default:"http://localhost:8080/assets"` + Origins []string `pp:",omitempty"` + Policy PolicyConfig `pp:",omitempty"` + Web_Disabled bool `pp:",omitempty"` + Web_App_Disabled bool `pp:",omitempty"` + Web map[string]string `pp:",omitempty"` + Web_Config JSON `pp:",omitempty"` + Web_Title string `pp:",omitempty"` + Web_FaviconURL string `pp:",omitempty"` + SignupSecret string `pp:",omitempty"` + SignupDisabled bool `pp:",omitempty"` + HTTPSREDIRECT bool `pp:",omitempty"` // storage GCS GCSConfig `pp:",omitempty"` diff --git a/server/internal/app/main.go b/server/internal/app/main.go index 1b0e05936e..612264a8a6 100644 --- a/server/internal/app/main.go +++ b/server/internal/app/main.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/reearth/reearth/server/internal/app/config" + "github.com/reearth/reearth/server/internal/app/otel" mongorepo "github.com/reearth/reearth/server/internal/infrastructure/mongo" "github.com/reearth/reearth/server/internal/infrastructure/mongo/migration" "github.com/reearth/reearthx/log" @@ -32,14 +33,26 @@ func Start(debug bool, version string) { initProfiler(conf.Profiler, version) // Init tracer - closer := initTracer(ctx, conf, version) - defer func() { - if closer != nil { - if err := closer.Close(); err != nil { - log.Errorf("Failed to close tracer: %s\n", err.Error()) - } + if conf.OtelEnabled { + closer, err := otel.InitTracer(ctx, &otel.Config{ + Enabled: conf.OtelEnabled, + Endpoint: conf.OtelEndpoint, + ExporterType: otel.ExporterType(conf.OtelExporterType), + MaxExportBatchSize: conf.OtelMaxExportBatchSize, + BatchTimeout: conf.OtelBatchTimeout, + MaxQueueSize: conf.OtelMaxQueueSize, + }) + + if err != nil { + log.Fatalf("failed to init tracer: %v", err) } - }() + + defer func() { + if err := closer.Shutdown(ctx); err != nil { + log.Errorf("Failed to shutdown tracer: %s\n", err.Error()) + } + }() + } // run migration if os.Getenv("RUN_MIGRATION") == "true" { diff --git a/server/internal/app/otel/middleware.go b/server/internal/app/otel/middleware.go new file mode 100644 index 0000000000..4467882d8e --- /dev/null +++ b/server/internal/app/otel/middleware.go @@ -0,0 +1,25 @@ +package otel + +import ( + "slices" + "strings" + + "github.com/labstack/echo/v4" + "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" +) + +var skipPaths = []string{"/", "/api/ping"} + +func openTelemetrySkipper(c echo.Context) bool { + if slices.Contains(skipPaths, c.Path()) { + return true + } + + ua := c.Request().UserAgent() + return strings.Contains(ua, "GoogleStackdriverMonitoring") +} + +// Middleware returns an Echo middleware that adds OpenTelemetry tracing +func Middleware(serviceName string) echo.MiddlewareFunc { + return otelecho.Middleware(serviceName, otelecho.WithSkipper(openTelemetrySkipper)) +} diff --git a/server/internal/app/otel/otel.go b/server/internal/app/otel/otel.go new file mode 100644 index 0000000000..d81d687400 --- /dev/null +++ b/server/internal/app/otel/otel.go @@ -0,0 +1,219 @@ +package otel + +import ( + "context" + "fmt" + "time" + + "cloud.google.com/go/compute/metadata" + "github.com/reearth/reearthx/log" + "go.opentelemetry.io/contrib/detectors/gcp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" + "golang.org/x/oauth2/google" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/oauth" +) + +type ExporterType string + +const ( + ExporterTypeJaeger ExporterType = "jaeger" + ExporterTypeGCP ExporterType = "gcp" + ExporterTypeOTLP ExporterType = "otlp" +) + +const ( + gcpProjectIDAttribute = "gcp.project_id" + gcpCloudTraceEndpoint = "telemetry.googleapis.com:443" + + OtelServiceName string = "reearth-visualizer-api" +) + +type Config struct { + Enabled bool + Endpoint string + ExporterType ExporterType + MaxExportBatchSize int + BatchTimeout time.Duration + MaxQueueSize int + + SamplingRatio float64 +} + +type TracerProvider interface { + Tracer(name string, options ...trace.TracerOption) trace.Tracer + Shutdown(ctx context.Context) error +} + +type noopTracerProvider struct { + trace.TracerProvider +} + +func (n *noopTracerProvider) Shutdown(ctx context.Context) error { + return nil +} + +func InitTracer(ctx context.Context, cfg *Config) (TracerProvider, error) { + if !cfg.Enabled { + log.Infoc(ctx, "OpenTelemetry tracing is disabled") + return &noopTracerProvider{TracerProvider: noop.NewTracerProvider()}, nil + } + + if (cfg.ExporterType == ExporterTypeOTLP || cfg.ExporterType == ExporterTypeJaeger) && cfg.Endpoint == "" { + return nil, fmt.Errorf("OTLP endpoint is required for exporter type %s", cfg.ExporterType) + } + var exporter sdktrace.SpanExporter + var err error + + cfg.ExporterType = "gcp" + switch cfg.ExporterType { + case ExporterTypeGCP: + log.Infoc(ctx, "Initializing GCP Cloud Trace exporter via OTLP") + exporter, err = createGCPExporter(ctx, cfg) + case ExporterTypeJaeger: + log.Infoc(ctx, "Initializing Jaeger exporter via OTLP", "endpoint", cfg.Endpoint) + exporter, err = createOTLPExporter(ctx, cfg, false) + case ExporterTypeOTLP: + log.Infoc(ctx, "Initializing OTLP exporter", "endpoint", cfg.Endpoint) + exporter, err = createOTLPExporter(ctx, cfg, false) + default: + log.Warnc(ctx, "Unknown exporter type, defaulting to OTLP", "type", cfg.ExporterType) + exporter, err = createOTLPExporter(ctx, cfg, false) + } + + if err != nil { + return nil, fmt.Errorf("failed to create trace exporter: %w", err) + } + + res, err := createResource(ctx, cfg) + if err != nil { + return nil, fmt.Errorf("failed to create resource: %w", err) + } + + log.Infoc(ctx, fmt.Sprintf("resource attributes: %+v", res.Attributes())) + + sampler := createSampler(cfg) + + // if we want to see the traces in the console, uncomment the following line + // and comment out the line that creates the stdoutExporter + // stdoutExporter, _ := stdouttrace.New(stdouttrace.WithPrettyPrint()) + + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher( + exporter, + sdktrace.WithMaxExportBatchSize(cfg.MaxExportBatchSize), + sdktrace.WithBatchTimeout(cfg.BatchTimeout), + sdktrace.WithMaxQueueSize(cfg.MaxQueueSize), + ), + // sdktrace.WithSyncer(stdoutExporter), + sdktrace.WithResource(res), + sdktrace.WithSampler(sampler), + ) + + otel.SetTracerProvider(tp) + + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + )) + + log.Infoc(ctx, "OpenTelemetry tracing initialized successfully", + "endpoint", cfg.Endpoint, + "exporter", cfg.ExporterType, + "service", OtelServiceName, + "max_export_batch_size", cfg.MaxExportBatchSize, + "batch_timeout", cfg.BatchTimeout, + "max_queue_size", cfg.MaxQueueSize, + "sampling_ratio", cfg.SamplingRatio) + + return tp, nil +} + +func createResource(ctx context.Context, cfg *Config) (*resource.Resource, error) { + baseAttributes := []resource.Option{} + + if cfg.ExporterType == ExporterTypeGCP { + gcpDetector := gcp.NewDetector() + if gcpDetector != nil { + log.Infoc(ctx, "GCP resource detector initialized") + baseAttributes = append(baseAttributes, resource.WithDetectors(gcpDetector)) + } + + if metadata.OnGCE() { + if id, err := metadata.ProjectIDWithContext(ctx); err == nil { + baseAttributes = append(baseAttributes, resource.WithAttributes( + attribute.String(gcpProjectIDAttribute, id), + )) + } + } + } + + baseAttributes = append(baseAttributes, []resource.Option{ + resource.WithTelemetrySDK(), + resource.WithAttributes( + semconv.ServiceName(OtelServiceName), + ), + }...) + + return resource.New(ctx, baseAttributes...) +} + +func createSampler(cfg *Config) sdktrace.Sampler { + if cfg.SamplingRatio < 0 { + return sdktrace.AlwaysSample() + } + + if cfg.SamplingRatio == 0 { + return sdktrace.NeverSample() + } + + if cfg.SamplingRatio >= 1 { + return sdktrace.AlwaysSample() + } + + return sdktrace.TraceIDRatioBased(cfg.SamplingRatio) +} + +func createOTLPExporter(ctx context.Context, cfg *Config, useGCPAuth bool) (sdktrace.SpanExporter, error) { + opts := []otlptracegrpc.Option{ + otlptracegrpc.WithEndpoint(cfg.Endpoint), + } + + if useGCPAuth { + creds, err := google.FindDefaultCredentials(ctx, "https://www.googleapis.com/auth/trace.append") + if err != nil { + return nil, fmt.Errorf("failed to get GCP credentials: %w", err) + } + + perRPCCreds := oauth.TokenSource{TokenSource: creds.TokenSource} + + opts = append(opts, + otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, "")), + otlptracegrpc.WithDialOption(grpc.WithPerRPCCredentials(perRPCCreds)), + ) + } else { + opts = append(opts, otlptracegrpc.WithInsecure()) + } + + return otlptracegrpc.New(ctx, opts...) +} + +func createGCPExporter(ctx context.Context, cfg *Config) (sdktrace.SpanExporter, error) { + gcpCfg := &Config{ + Enabled: true, + Endpoint: gcpCloudTraceEndpoint, + ExporterType: ExporterTypeGCP, + SamplingRatio: cfg.SamplingRatio, + } + + return createOTLPExporter(ctx, gcpCfg, true) +} diff --git a/server/internal/app/tracer.go b/server/internal/app/tracer.go index 96aa5d597f..de4ea23c02 100644 --- a/server/internal/app/tracer.go +++ b/server/internal/app/tracer.go @@ -2,146 +2,15 @@ package app import ( "context" - "io" - "os" - "time" "github.com/99designs/gqlgen/graphql" - texporter "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace" - "github.com/reearth/reearth/server/internal/app/config" "github.com/reearth/reearthx/log" - jaeger "github.com/uber/jaeger-client-go" - jaegercfg "github.com/uber/jaeger-client-go/config" - jaegerlog "github.com/uber/jaeger-client-go/log" - "github.com/uber/jaeger-lib/metrics" - "go.opentelemetry.io/contrib/detectors/gcp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/sdk/resource" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.opentelemetry.io/otel/trace" ) -type gcpTracerCloser struct { - tp *sdktrace.TracerProvider -} - -func (c *gcpTracerCloser) Close() error { - if c.tp != nil { - ctx := context.Background() - if err := c.tp.ForceFlush(ctx); err != nil { - log.Errorf("tracer: failed to flush spans: %v", err) - } - if err := c.tp.Shutdown(ctx); err != nil { - log.Errorf("tracer: failed to shutdown: %v", err) - return err - } - } - return nil -} - -func initTracer(ctx context.Context, conf *config.Config, version string) io.Closer { - switch conf.Tracer { - case "gcp": - return initGCPTracer(ctx, conf, version) - case "jaeger": - return initJaegerTracer(conf) - default: - return nil - } -} - -func initGCPTracer(ctx context.Context, conf *config.Config, version string) io.Closer { - // Create GCP trace exporter - exporter, err := texporter.New(texporter.WithProjectID(conf.GCPProject)) - if err != nil { - log.Fatalf("failed to create GCP trace exporter: %v", err) - } - - // Detect GCP resources (project, instance, etc.) - res, err := resource.New(ctx, - resource.WithDetectors(gcp.NewDetector()), - resource.WithTelemetrySDK(), - resource.WithAttributes( - semconv.ServiceNameKey.String("reearth-visualizer"), - semconv.ServiceVersionKey.String(version), - semconv.DeploymentEnvironmentKey.String(getEnvironment()), - ), - ) - if err != nil { - log.Warnf("failed to detect GCP resources: %v, using default resource", err) - - res, _ = resource.New(ctx, - resource.WithTelemetrySDK(), - resource.WithAttributes( - semconv.ServiceNameKey.String("reearth-visualizer"), - semconv.ServiceVersionKey.String(version), - semconv.DeploymentEnvironmentKey.String(getEnvironment()), - ), - ) - } - - // Create sampler based on configuration - sampler := sdktrace.ParentBased(sdktrace.TraceIDRatioBased(conf.TracerSample)) - if conf.TracerSample >= 1.0 { - sampler = sdktrace.AlwaysSample() - } - - // Create tracer provider with batch span processor - tp := sdktrace.NewTracerProvider( - sdktrace.WithBatcher(exporter, - sdktrace.WithMaxExportBatchSize(100), // Batch up to 100 spans - sdktrace.WithBatchTimeout(time.Second), // 1 second timeout for faster export - sdktrace.WithMaxQueueSize(2048), // Queue up to 2048 spans - ), - sdktrace.WithResource(res), - sdktrace.WithSampler(sampler), - ) - - otel.SetTracerProvider(tp) - - return &gcpTracerCloser{tp: tp} -} - -// getEnvironment returns the current deployment environment -func getEnvironment() string { - env := os.Getenv("REEARTH_ENV") - if env == "" { - return "oss" - } - return env -} - -func initJaegerTracer(conf *config.Config) io.Closer { - cfg := jaegercfg.Configuration{ - Sampler: &jaegercfg.SamplerConfig{ - Type: jaeger.SamplerTypeConst, - Param: conf.TracerSample, - }, - Reporter: &jaegercfg.ReporterConfig{ - LogSpans: true, - }, - } - - jLogger := jaegerlog.StdLogger - jMetricsFactory := metrics.NullFactory - - closer, err := cfg.InitGlobalTracer( - "Re:Earth", - jaegercfg.Logger(jLogger), - jaegercfg.Metrics(jMetricsFactory), - ) - - if err != nil { - log.Fatalf("Could not initialize jaeger tracer: %s\n", err.Error()) - } - - log.Infof("tracer: initialized jaeger tracer with sample fraction: %g\n", conf.TracerSample) - return closer -} - // detailedOperationTracer creates a middleware that traces GraphQL operations with detailed attributes func detailedOperationTracer() graphql.OperationMiddleware { tracer := otel.Tracer("reearth-visualizer") From 6a8fee735774f7a5100442bd01d04fbccb8e3988 Mon Sep 17 00:00:00 2001 From: soneda-yuya Date: Tue, 25 Nov 2025 14:07:16 +0900 Subject: [PATCH 2/2] switch otel service name --- server/internal/app/app.go | 8 ++++++-- server/internal/app/otel/otel.go | 13 ++++++++++--- server/internal/app/server.go | 9 ++++++++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/server/internal/app/app.go b/server/internal/app/app.go index a2ebb0de02..6346da872e 100644 --- a/server/internal/app/app.go +++ b/server/internal/app/app.go @@ -20,7 +20,11 @@ import ( "github.com/reearth/reearthx/rerror" ) -func initEcho(ctx context.Context, cfg *ServerConfig) *echo.Echo { +func initEcho( + ctx context.Context, + cfg *ServerConfig, + otelServiceName otel.OtelServiceName, +) *echo.Echo { if cfg.Config == nil { log.Fatalf("ServerConfig.Config is nil") } @@ -35,7 +39,7 @@ func initEcho(ctx context.Context, cfg *ServerConfig) *echo.Echo { logger := log.NewEcho() e.Logger = logger if cfg.Config.OtelEnabled { - e.Use(otel.Middleware(otel.OtelServiceName)) + e.Use(otel.Middleware(string(otelServiceName))) } e.Use( middleware.Recover(), diff --git a/server/internal/app/otel/otel.go b/server/internal/app/otel/otel.go index d81d687400..2cc0a4e905 100644 --- a/server/internal/app/otel/otel.go +++ b/server/internal/app/otel/otel.go @@ -34,8 +34,13 @@ const ( const ( gcpProjectIDAttribute = "gcp.project_id" gcpCloudTraceEndpoint = "telemetry.googleapis.com:443" +) + +type OtelServiceName string - OtelServiceName string = "reearth-visualizer-api" +const ( + OtelVisualizerServiceName OtelServiceName = "reearth-visualizer-api" + OtelVisualizerInternalApiServiceName OtelServiceName = "reearth-visualizer-internal-api" ) type Config struct { @@ -47,6 +52,8 @@ type Config struct { MaxQueueSize int SamplingRatio float64 + + ServiceName OtelServiceName } type TracerProvider interface { @@ -129,7 +136,7 @@ func InitTracer(ctx context.Context, cfg *Config) (TracerProvider, error) { log.Infoc(ctx, "OpenTelemetry tracing initialized successfully", "endpoint", cfg.Endpoint, "exporter", cfg.ExporterType, - "service", OtelServiceName, + "service", string(cfg.ServiceName), "max_export_batch_size", cfg.MaxExportBatchSize, "batch_timeout", cfg.BatchTimeout, "max_queue_size", cfg.MaxQueueSize, @@ -160,7 +167,7 @@ func createResource(ctx context.Context, cfg *Config) (*resource.Resource, error baseAttributes = append(baseAttributes, []resource.Option{ resource.WithTelemetrySDK(), resource.WithAttributes( - semconv.ServiceName(OtelServiceName), + semconv.ServiceName(string(cfg.ServiceName)), ), }...) diff --git a/server/internal/app/server.go b/server/internal/app/server.go index 9744e5d1f5..a916cb38dd 100644 --- a/server/internal/app/server.go +++ b/server/internal/app/server.go @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v4" "github.com/reearth/reearth-accounts/server/pkg/gqlclient" "github.com/reearth/reearth/server/internal/app/config" + "github.com/reearth/reearth/server/internal/app/otel" "github.com/reearth/reearth/server/internal/usecase/gateway" "github.com/reearth/reearth/server/internal/usecase/repo" "github.com/reearth/reearthx/account/accountusecase/accountgateway" @@ -68,7 +69,13 @@ func NewServer(ctx context.Context, cfg *ServerConfig) *WebServer { w := &WebServer{ address: address, } - w.appServer = initEcho(ctx, cfg) + + otelServiceName := otel.OtelVisualizerServiceName + if cfg.Config.Visualizer.InternalApi.Active { + otelServiceName = otel.OtelVisualizerInternalApiServiceName + } + + w.appServer = initEcho(ctx, cfg, otelServiceName) if cfg.Config.Visualizer.InternalApi.Active { w.internalPort = ":" + cfg.Config.Visualizer.InternalApi.Port