Skip to content

Commit

Permalink
refactor: Improve the OtelConfig handling
Browse files Browse the repository at this point in the history
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
  • Loading branch information
joonas committed Sep 27, 2024
1 parent f8ed71d commit 20fe690
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 29 deletions.
118 changes: 117 additions & 1 deletion host.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package provider

import "encoding/json"
import (
"encoding/json"
"fmt"
"strings"
)

type RedactedString string

Expand Down Expand Up @@ -31,6 +35,118 @@ type OtelConfig struct {
Protocol string `json:"protocol,omitempty"`
}

type otelSignal int

const (
traces otelSignal = iota
metrics
logs

// https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/#otel_exporter_otlp_endpoint
OtelExporterGrpcEndpoint = "http://localhost:4317"
OtelExporterHttpEndpoint = "http://localhost:4318"

// https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/#otel_exporter_otlp_traces_endpoint
OtelExporterHttpTracesPath = "/v1/traces"
// https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/#otel_exporter_otlp_metrics_endpoint
OtelExporterHttpMetricsPath = "/v1/metrics"
// https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/#otel_exporter_otlp_logs_endpoint
OtelExporterHttpLogsPath = "/v1/logs"
)

// OtelProtocol returns the configured OpenTelemetry protocol if one is provided,
// otherwise defaulting to http.
func (config *OtelConfig) OtelProtocol() string {
protocol := OtelProtocolHTTP
if config.Protocol != "" {
protocol = strings.ToLower(config.Protocol)
}
return protocol
}

// TracesURL returns the configured TracesEndpoint as-is if one is provided,
// otherwise it resolves the URL based on ObservabilityEndpoint value and the
// Protocol appropriate path.
func (config *OtelConfig) TracesURL() string {
if config.TracesEndpoint != "" {
return config.TracesEndpoint
}

return config.resolveSignalUrl(traces)
}

// MetricsURL returns the configured MetricsEndpoint as-is if one is provided,
// otherwise it resolves the URL based on ObservabilityEndpoint value and the
// Protocol appropriate path.
func (config *OtelConfig) MetricsURL() string {
if config.MetricsEndpoint != "" {
return config.MetricsEndpoint
}

return config.resolveSignalUrl(metrics)
}

// LogsURL returns the configured LogsEndpoint as-is if one is provided,
// otherwise it resolves the URL based on ObservabilityEndpoint value and the
// Protocol appropriate path.
func (config *OtelConfig) LogsURL() string {
if config.LogsEndpoint != "" {
return config.LogsEndpoint
}

return config.resolveSignalUrl(logs)
}

// TracesEnabled returns whether emitting traces has been enabled.
func (config *OtelConfig) TracesEnabled() bool {
return config.EnableObservability || config.EnableTraces
}

// MetricsEnabled returns whether emitting metrics has been enabled.
func (config *OtelConfig) MetricsEnabled() bool {
return config.EnableObservability || config.EnableMetrics
}

// LogsEnabled returns whether emitting logs has been enabled.
func (config *OtelConfig) LogsEnabled() bool {
return config.EnableObservability || config.EnableLogs
}

func (config *OtelConfig) resolveSignalUrl(signal otelSignal) string {
endpoint := config.defaultEndpoint()
if config.ObservabilityEndpoint != "" {
endpoint = config.ObservabilityEndpoint
}
endpoint = strings.TrimRight(endpoint, "/")

return fmt.Sprintf("%s%s", endpoint, config.defaultSignalPath(signal))
}

func (config *OtelConfig) defaultEndpoint() string {
endpoint := OtelExporterHttpEndpoint
if config.OtelProtocol() == OtelProtocolGRPC {
endpoint = OtelExporterGrpcEndpoint
}
return endpoint
}

func (config *OtelConfig) defaultSignalPath(signal otelSignal) string {
// In case of gRPC, we return empty string gRPC doesn't need a path to be set for it.
if config.OtelProtocol() == OtelProtocolGRPC {
return ""
}

switch signal {
case traces:
return OtelExporterHttpTracesPath
case metrics:
return OtelExporterHttpMetricsPath
case logs:
return OtelExporterHttpLogsPath
}
return ""
}

type HostData struct {
HostID string `json:"host_id,omitempty"`
LatticeRPCPrefix string `json:"lattice_rpc_prefix,omitempty"`
Expand Down
165 changes: 165 additions & 0 deletions host_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,168 @@ func TestRedactedStringLogging(t *testing.T) {
t.Error("text slog handler should not have contained the secret string")
}
}

func TestOtelConfigProtocol(t *testing.T) {
type test struct {
name string
config OtelConfig
protocol string
}

tests := []test{
{
name: "Defaults to http",
config: OtelConfig{},
protocol: "http",
},
{
name: "Explicit Grpc Rust enum variant",
config: OtelConfig{Protocol: "Grpc"},
protocol: "grpc",
},
{
name: "Explicit Http Rust enum variant",
config: OtelConfig{Protocol: "Http"},
protocol: "http",
},
}

for _, tc := range tests {
if tc.config.OtelProtocol() != tc.protocol {
t.Fatalf("%s / OtelProtocol: expected %q, got: %q", tc.name, tc.protocol, tc.config.OtelProtocol())
}
}
}

func TestOtelConfigURLs(t *testing.T) {
type test struct {
name string
config OtelConfig
tracesURL string
metricsURL string
logsURL string
}

tests := []test{
{
name: "Defaults with HTTP",
config: OtelConfig{},
tracesURL: "http://localhost:4318/v1/traces",
metricsURL: "http://localhost:4318/v1/metrics",
logsURL: "http://localhost:4318/v1/logs",
},
{
name: "Defaults with gRPC",
config: OtelConfig{Protocol: "Grpc"},
tracesURL: "http://localhost:4317",
metricsURL: "http://localhost:4317",
logsURL: "http://localhost:4317",
},
{
name: "Custom ObservabilityEndpoint",
config: OtelConfig{ObservabilityEndpoint: "https://api.opentelemetry.com"},
tracesURL: "https://api.opentelemetry.com/v1/traces",
metricsURL: "https://api.opentelemetry.com/v1/metrics",
logsURL: "https://api.opentelemetry.com/v1/logs",
},
{
name: "Custom ObservabilityEndpoint with gRPC",
config: OtelConfig{Protocol: "grpc", ObservabilityEndpoint: "https://api.opentelemetry.com"},
tracesURL: "https://api.opentelemetry.com",
metricsURL: "https://api.opentelemetry.com",
logsURL: "https://api.opentelemetry.com",
},
{
name: "Custom TracesEndpoint",
config: OtelConfig{TracesEndpoint: "https://api.opentelemetry.com/v1/traces"},
tracesURL: "https://api.opentelemetry.com/v1/traces",
metricsURL: "http://localhost:4318/v1/metrics",
logsURL: "http://localhost:4318/v1/logs",
},
{
name: "Custom MetricsEndpoint",
config: OtelConfig{MetricsEndpoint: "https://api.opentelemetry.com/v1/metrics"},
tracesURL: "http://localhost:4318/v1/traces",
metricsURL: "https://api.opentelemetry.com/v1/metrics",
logsURL: "http://localhost:4318/v1/logs",
},
{
name: "Custom LogsEndpoint",
config: OtelConfig{LogsEndpoint: "https://api.opentelemetry.com/v1/logs"},
tracesURL: "http://localhost:4318/v1/traces",
metricsURL: "http://localhost:4318/v1/metrics",
logsURL: "https://api.opentelemetry.com/v1/logs",
},
}

for _, tc := range tests {
if tc.config.TracesURL() != tc.tracesURL {
t.Fatalf("%s / TracesURL: expected %s, got: %s", tc.name, tc.tracesURL, tc.config.TracesURL())
}
if tc.config.MetricsURL() != tc.metricsURL {
t.Fatalf("%s / MetricsURL: expected %s, got: %s", tc.name, tc.metricsURL, tc.config.MetricsURL())
}
if tc.config.LogsURL() != tc.logsURL {
t.Fatalf("%s / LogsURL: expected %s, got: %s", tc.name, tc.logsURL, tc.config.LogsURL())
}
}
}

func TestOtelConfigBooleans(t *testing.T) {
type test struct {
name string
config OtelConfig
tracesEnabled bool
metricsEnabled bool
logsEnabled bool
}

tests := []test{
{
name: "Defaults",
config: OtelConfig{},
tracesEnabled: false,
metricsEnabled: false,
logsEnabled: false,
},
{
name: "Enable all with EnableObservability",
config: OtelConfig{EnableObservability: true},
tracesEnabled: true,
metricsEnabled: true,
logsEnabled: true,
},
{
name: "Enable just traces",
config: OtelConfig{EnableTraces: true},
tracesEnabled: true,
metricsEnabled: false,
logsEnabled: false,
},
{
name: "Enable just metrics",
config: OtelConfig{EnableMetrics: true},
tracesEnabled: false,
metricsEnabled: true,
logsEnabled: false,
},
{
name: "Enable just logs",
config: OtelConfig{EnableLogs: true},
tracesEnabled: false,
metricsEnabled: false,
logsEnabled: true,
},
}
for _, tc := range tests {
if tc.config.TracesEnabled() != tc.tracesEnabled {
t.Fatalf("%s / TracesEnabled: expected %t, got: %t", tc.name, tc.tracesEnabled, tc.config.TracesEnabled())
}
if tc.config.MetricsEnabled() != tc.metricsEnabled {
t.Fatalf("%s / MetricsEnabled: expected %t, got: %t", tc.name, tc.metricsEnabled, tc.config.MetricsEnabled())
}
if tc.config.LogsEnabled() != tc.logsEnabled {
t.Fatalf("%s / LogsEnabled: expected %t, got: %t", tc.name, tc.logsEnabled, tc.config.LogsEnabled())
}
}
}
34 changes: 9 additions & 25 deletions observability.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"time"

"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
Expand Down Expand Up @@ -43,16 +42,11 @@ func newTracerProvider(ctx context.Context, config OtelConfig, serviceResource *
var exporter trace.SpanExporter
var err error

endpoint := config.TracesEndpoint
if endpoint == "" {
endpoint = config.ObservabilityEndpoint
}

switch strings.ToLower(config.Protocol) {
switch config.OtelProtocol() {
case OtelProtocolGRPC:
exporter, err = otlptracegrpc.New(ctx, otlptracegrpc.WithEndpointURL(endpoint))
exporter, err = otlptracegrpc.New(ctx, otlptracegrpc.WithEndpointURL(config.TracesURL()))
case OtelProtocolHTTP:
exporter, err = otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(endpoint))
exporter, err = otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(config.TracesURL()))
default:
return nil, fmt.Errorf("unknown observability protocol %q", config.Protocol)
}
Expand Down Expand Up @@ -82,16 +76,11 @@ func newMeterProvider(ctx context.Context, config OtelConfig, serviceResource *r
var exporter metric.Exporter
var err error

endpoint := config.MetricsEndpoint
if endpoint == "" {
endpoint = config.ObservabilityEndpoint
}

switch strings.ToLower(config.Protocol) {
switch config.OtelProtocol() {
case OtelProtocolGRPC:
exporter, err = otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithEndpointURL(endpoint))
exporter, err = otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithEndpointURL(config.MetricsURL()))
case OtelProtocolHTTP:
exporter, err = otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpointURL(endpoint))
exporter, err = otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpointURL(config.MetricsURL()))
default:
return nil, fmt.Errorf("unknown observability protocol %q", config.Protocol)
}
Expand All @@ -113,16 +102,11 @@ func newLoggerProvider(ctx context.Context, config OtelConfig, serviceResource *
var exporter log.Exporter
var err error

endpoint := config.LogsEndpoint
if endpoint == "" {
endpoint = config.ObservabilityEndpoint
}

switch strings.ToLower(config.Protocol) {
switch config.OtelProtocol() {
case OtelProtocolGRPC:
exporter, err = otlploggrpc.New(ctx, otlploggrpc.WithEndpointURL(endpoint))
exporter, err = otlploggrpc.New(ctx, otlploggrpc.WithEndpointURL(config.LogsURL()))
case OtelProtocolHTTP:
exporter, err = otlploghttp.New(ctx, otlploghttp.WithEndpointURL(endpoint))
exporter, err = otlploghttp.New(ctx, otlploghttp.WithEndpointURL(config.LogsURL()))
default:
return nil, fmt.Errorf("unknown observability protocol %q", config.Protocol)
}
Expand Down
6 changes: 3 additions & 3 deletions provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func NewWithHostDataSource(source io.Reader, options ...ProviderHandler) (*Wasmc
return nil, err
}

if hostData.OtelConfig.EnableObservability || hostData.OtelConfig.EnableMetrics {
if hostData.OtelConfig.MetricsEnabled() {
meterProvider, err := newMeterProvider(context.Background(), hostData.OtelConfig, serviceResource)
if err != nil {
return nil, err
Expand All @@ -134,7 +134,7 @@ func NewWithHostDataSource(source io.Reader, options ...ProviderHandler) (*Wasmc
internalShutdownFuncs = append(internalShutdownFuncs, func(c context.Context) error { return meterProvider.Shutdown(c) })
}

if hostData.OtelConfig.EnableObservability || hostData.OtelConfig.EnableTraces {
if hostData.OtelConfig.TracesEnabled() {
tracerProvider, err := newTracerProvider(context.Background(), hostData.OtelConfig, serviceResource)
if err != nil {
return nil, err
Expand All @@ -143,7 +143,7 @@ func NewWithHostDataSource(source io.Reader, options ...ProviderHandler) (*Wasmc
internalShutdownFuncs = append(internalShutdownFuncs, func(c context.Context) error { return tracerProvider.Shutdown(c) })
}

if hostData.OtelConfig.EnableObservability || hostData.OtelConfig.EnableLogs {
if hostData.OtelConfig.LogsEnabled() {
loggerProvider, err := newLoggerProvider(context.Background(), hostData.OtelConfig, serviceResource)
if err != nil {
return nil, err
Expand Down

0 comments on commit 20fe690

Please sign in to comment.