Skip to content

Commit

Permalink
config: add yaml/json struct tags (#5433)
Browse files Browse the repository at this point in the history
This will make parsing the configuration easier

---------

Signed-off-by: Alex Boten <[email protected]>
  • Loading branch information
codeboten authored Oct 3, 2024
1 parent 793b970 commit 722a536
Show file tree
Hide file tree
Showing 12 changed files with 1,126 additions and 132 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- The `Severitier` and `SeverityVar` types are added to `go.opentelemetry.io/contrib/processors/minsev` allowing dynamic configuration of the severity used by the `LogProcessor`. (#6116)
- Move examples from `go.opentelemetry.io/otel` to this repository under `examples` directory. (#6158)
- Support yaml/json struct tags for generated code in `go.opentelemetry.io/contrib/config`. (#5433)
- Add support for parsing YAML configuration via `ParseYAML` in `go.opentelemetry.io/contrib/config`. (#5433)

### Changed

Expand Down
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,6 @@ genjsonschema: genjsonschema-cleanup $(GOJSONSCHEMA)
--capitalization OTLP \
--struct-name-from-title \
--package config \
--tags mapstructure \
--output ${GENERATED_CONFIG} \
${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR}/schema/opentelemetry_configuration.json
@echo Modify jsonschema generated files.
Expand Down
14 changes: 13 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"context"
"errors"

"gopkg.in/yaml.v3"

"go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
Expand Down Expand Up @@ -128,5 +130,15 @@ func WithOpenTelemetryConfiguration(cfg OpenTelemetryConfiguration) Configuratio
}

// TODO: implement parsing functionality:
// - https://github.com/open-telemetry/opentelemetry-go-contrib/issues/4373
// - https://github.com/open-telemetry/opentelemetry-go-contrib/issues/4412

// ParseYAML parses a YAML configuration file into an OpenTelemetryConfiguration.
func ParseYAML(file []byte) (*OpenTelemetryConfiguration, error) {
var cfg OpenTelemetryConfiguration
err := yaml.Unmarshal(file, &cfg)
if err != nil {
return nil, err
}

return &cfg, nil
}
304 changes: 304 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ package config

import (
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -59,6 +63,306 @@ func TestNewSDK(t *testing.T) {
}
}

var v02OpenTelemetryConfig = OpenTelemetryConfiguration{
Disabled: ptr(false),
FileFormat: "0.2",
AttributeLimits: &AttributeLimits{
AttributeCountLimit: ptr(128),
AttributeValueLengthLimit: ptr(4096),
},
LoggerProvider: &LoggerProvider{
Limits: &LogRecordLimits{
AttributeCountLimit: ptr(128),
AttributeValueLengthLimit: ptr(4096),
},
Processors: []LogRecordProcessor{
{
Batch: &BatchLogRecordProcessor{
ExportTimeout: ptr(30000),
Exporter: LogRecordExporter{
OTLP: &OTLP{
Certificate: ptr("/app/cert.pem"),
ClientCertificate: ptr("/app/cert.pem"),
ClientKey: ptr("/app/cert.pem"),
Compression: ptr("gzip"),
Endpoint: "http://localhost:4318",
Headers: Headers{
"api-key": "1234",
},
Insecure: ptr(false),
Protocol: "http/protobuf",
Timeout: ptr(10000),
},
},
MaxExportBatchSize: ptr(512),
MaxQueueSize: ptr(2048),
ScheduleDelay: ptr(5000),
},
},
{
Simple: &SimpleLogRecordProcessor{
Exporter: LogRecordExporter{
Console: Console{},
},
},
},
},
},
MeterProvider: &MeterProvider{
Readers: []MetricReader{
{
Pull: &PullMetricReader{
Exporter: MetricExporter{
Prometheus: &Prometheus{
Host: ptr("localhost"),
Port: ptr(9464),
WithResourceConstantLabels: &IncludeExclude{
Excluded: []string{"service.attr1"},
Included: []string{"service*"},
},
WithoutScopeInfo: ptr(false),
WithoutTypeSuffix: ptr(false),
WithoutUnits: ptr(false),
},
},
},
},
{
Periodic: &PeriodicMetricReader{
Exporter: MetricExporter{
OTLP: &OTLPMetric{
Certificate: ptr("/app/cert.pem"),
ClientCertificate: ptr("/app/cert.pem"),
ClientKey: ptr("/app/cert.pem"),
Compression: ptr("gzip"),
DefaultHistogramAggregation: ptr(OTLPMetricDefaultHistogramAggregationBase2ExponentialBucketHistogram),
Endpoint: "http://localhost:4318",
Headers: Headers{
"api-key": "1234",
},
Insecure: ptr(false),
Protocol: "http/protobuf",
TemporalityPreference: ptr("delta"),
Timeout: ptr(10000),
},
},
Interval: ptr(5000),
Timeout: ptr(30000),
},
},
{
Periodic: &PeriodicMetricReader{
Exporter: MetricExporter{
Console: Console{},
},
},
},
},
Views: []View{
{
Selector: &ViewSelector{
InstrumentName: ptr("my-instrument"),
InstrumentType: ptr(ViewSelectorInstrumentTypeHistogram),
MeterName: ptr("my-meter"),
MeterSchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"),
MeterVersion: ptr("1.0.0"),
Unit: ptr("ms"),
},
Stream: &ViewStream{
Aggregation: &ViewStreamAggregation{
ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{
Boundaries: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000},
RecordMinMax: ptr(true),
},
},
AttributeKeys: []string{"key1", "key2"},
Description: ptr("new_description"),
Name: ptr("new_instrument_name"),
},
},
},
},
Propagator: &Propagator{
Composite: []string{"tracecontext", "baggage", "b3", "b3multi", "jaeger", "xray", "ottrace"},
},
Resource: &Resource{
Attributes: Attributes{
"service.name": "unknown_service",
},
Detectors: &Detectors{
Attributes: &DetectorsAttributes{
Excluded: []string{"process.command_args"},
Included: []string{"process.*"},
},
},
SchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"),
},
TracerProvider: &TracerProvider{
Limits: &SpanLimits{
AttributeCountLimit: ptr(128),
AttributeValueLengthLimit: ptr(4096),
EventCountLimit: ptr(128),
EventAttributeCountLimit: ptr(128),
LinkCountLimit: ptr(128),
LinkAttributeCountLimit: ptr(128),
},
Processors: []SpanProcessor{
{
Batch: &BatchSpanProcessor{
ExportTimeout: ptr(30000),
Exporter: SpanExporter{
OTLP: &OTLP{
Certificate: ptr("/app/cert.pem"),
ClientCertificate: ptr("/app/cert.pem"),
ClientKey: ptr("/app/cert.pem"),
Compression: ptr("gzip"),
Endpoint: "http://localhost:4318",
Headers: Headers{
"api-key": "1234",
},
Insecure: ptr(false),
Protocol: "http/protobuf",
Timeout: ptr(10000),
},
},
MaxExportBatchSize: ptr(512),
MaxQueueSize: ptr(2048),
ScheduleDelay: ptr(5000),
},
},
{
Batch: &BatchSpanProcessor{
Exporter: SpanExporter{
Zipkin: &Zipkin{
Endpoint: "http://localhost:9411/api/v2/spans",
Timeout: ptr(10000),
},
},
},
},
{
Simple: &SimpleSpanProcessor{
Exporter: SpanExporter{
Console: Console{},
},
},
},
},
Sampler: &Sampler{
ParentBased: &SamplerParentBased{
LocalParentNotSampled: &Sampler{
AlwaysOff: SamplerAlwaysOff{},
},
LocalParentSampled: &Sampler{
AlwaysOn: SamplerAlwaysOn{},
},
RemoteParentNotSampled: &Sampler{
AlwaysOff: SamplerAlwaysOff{},
},
RemoteParentSampled: &Sampler{
AlwaysOn: SamplerAlwaysOn{},
},
Root: &Sampler{
TraceIDRatioBased: &SamplerTraceIDRatioBased{
Ratio: ptr(0.0001),
},
},
},
},
},
}

func TestParseYAML(t *testing.T) {
tests := []struct {
name string
input string
wantErr error
wantType interface{}
}{
{
name: "valid YAML config",
input: `valid_empty.yaml`,
wantErr: nil,
wantType: &OpenTelemetryConfiguration{
Disabled: ptr(false),
FileFormat: "0.1",
},
},
{
name: "invalid config",
input: "invalid_bool.yaml",
wantErr: errors.New(`yaml: unmarshal errors:
line 2: cannot unmarshal !!str ` + "`notabool`" + ` into bool`),
},
{
name: "valid v0.2 config",
input: "v0.2.yaml",
wantType: &v02OpenTelemetryConfig,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := os.ReadFile(filepath.Join("testdata", tt.input))
require.NoError(t, err)

got, err := ParseYAML(b)
if tt.wantErr != nil {
require.Equal(t, tt.wantErr.Error(), err.Error())
} else {
require.NoError(t, err)
assert.Equal(t, tt.wantType, got)
}
})
}
}

func TestSerializeJSON(t *testing.T) {
tests := []struct {
name string
input string
wantErr error
wantType interface{}
}{
{
name: "valid JSON config",
input: `valid_empty.json`,
wantErr: nil,
wantType: OpenTelemetryConfiguration{
Disabled: ptr(false),
FileFormat: "0.1",
},
},
{
name: "invalid config",
input: "invalid_bool.json",
wantErr: errors.New(`json: cannot unmarshal string into Go struct field Plain.disabled of type bool`),
},
{
name: "valid v0.2 config",
input: "v0.2.json",
wantType: v02OpenTelemetryConfig,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := os.ReadFile(filepath.Join("testdata", tt.input))
require.NoError(t, err)

var got OpenTelemetryConfiguration
err = json.Unmarshal(b, &got)

if tt.wantErr != nil {
require.Equal(t, tt.wantErr.Error(), err.Error())
} else {
require.NoError(t, err)
assert.Equal(t, tt.wantType, got)
}
})
}
}

func ptr[T any](v T) *T {
return &v
}
Loading

0 comments on commit 722a536

Please sign in to comment.