Skip to content

Commit a515b66

Browse files
committed
otelconf: add unmarshaling / validation for batch processors
Updates otelconf v1.0.0 to include unmarshaling that validates the fields for batch log processor and batch span processor configuration. Part of splitting open-telemetry#8026 Signed-off-by: alex boten <[email protected]>
1 parent 1f0bb77 commit a515b66

File tree

5 files changed

+320
-2
lines changed

5 files changed

+320
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1313
- `ParseYAML` in `go.opentelemetry.io/contrib/otelconf` now supports environment variables substitution in the format `${[env:]VAR_NAME[:-defaultvalue]}`. (#6215)
1414
- Introduce v1.0.0-rc.2 model in `go.opentelemetry.io/contrib/otelconf`. (#8031)
1515
- Add unmarshaling and validation for `CardinalityLimits` and `SpanLimits` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8043)
16+
- Add unmarshaling and validation for `BatchLogRecordProcessor` and `BatchSpanProcessor` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8049)
1617

1718
### Removed
1819

otelconf/config_common.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import (
99
)
1010

1111
var (
12-
errUnmarshalingCardinalityLimits = errors.New("unmarshaling cardinality_limit")
13-
errUnmarshalingSpanLimits = errors.New("unmarshaling span_limit")
12+
errUnmarshalingCardinalityLimits = errors.New("unmarshaling cardinality_limit")
13+
errUnmarshalingSpanLimits = errors.New("unmarshaling span_limit")
14+
errUnmarshalingBatchLogRecordProcessor = errors.New("unmarshaling BatchLogRecordProcessor")
15+
errUnmarshalingBatchSpanProcessor = errors.New("unmarshaling BatchSpanProcessor")
1416
)
1517

1618
type errBound struct {
@@ -31,6 +33,22 @@ func (e *errBound) Is(target error) bool {
3133
return e.Field == t.Field && e.Bound == t.Bound && e.Op == t.Op
3234
}
3335

36+
type errRequiredExporter struct {
37+
Object string
38+
}
39+
40+
func (e *errRequiredExporter) Error() string {
41+
return fmt.Sprintf("field exporter in %s: required", e.Object)
42+
}
43+
44+
func (e *errRequiredExporter) Is(target error) bool {
45+
t, ok := target.(*errRequiredExporter)
46+
if !ok {
47+
return false
48+
}
49+
return e.Object == t.Object
50+
}
51+
3452
// newErrGreaterOrEqualZero creates a new error indicating that the field must be greater than
3553
// or equal to zero.
3654
func newErrGreaterOrEqualZero(field string) error {
@@ -43,6 +61,45 @@ func newErrGreaterThanZero(field string) error {
4361
return &errBound{Field: field, Bound: 0, Op: ">"}
4462
}
4563

64+
// newErrRequiredExporter creates a new error indicating that the exporter field is required.
65+
func newErrRequiredExporter(object string) error {
66+
return &errRequiredExporter{Object: object}
67+
}
68+
69+
// validateBatchLogRecordProcessor handles validation for BatchLogRecordProcessor.
70+
func validateBatchLogRecordProcessor(plain *BatchLogRecordProcessor) error {
71+
if plain.ExportTimeout != nil && 0 > *plain.ExportTimeout {
72+
return newErrGreaterOrEqualZero("export_timeout")
73+
}
74+
if plain.MaxExportBatchSize != nil && 0 >= *plain.MaxExportBatchSize {
75+
return newErrGreaterThanZero("max_export_batch_size")
76+
}
77+
if plain.MaxQueueSize != nil && 0 >= *plain.MaxQueueSize {
78+
return newErrGreaterThanZero("max_queue_size")
79+
}
80+
if plain.ScheduleDelay != nil && 0 > *plain.ScheduleDelay {
81+
return newErrGreaterOrEqualZero("schedule_delay")
82+
}
83+
return nil
84+
}
85+
86+
// validateBatchSpanProcessor handles validation for BatchSpanProcessor.
87+
func validateBatchSpanProcessor(plain *BatchSpanProcessor) error {
88+
if plain.ExportTimeout != nil && 0 > *plain.ExportTimeout {
89+
return newErrGreaterOrEqualZero("export_timeout")
90+
}
91+
if plain.MaxExportBatchSize != nil && 0 >= *plain.MaxExportBatchSize {
92+
return newErrGreaterThanZero("max_export_batch_size")
93+
}
94+
if plain.MaxQueueSize != nil && 0 >= *plain.MaxQueueSize {
95+
return newErrGreaterThanZero("max_queue_size")
96+
}
97+
if plain.ScheduleDelay != nil && 0 > *plain.ScheduleDelay {
98+
return newErrGreaterOrEqualZero("schedule_delay")
99+
}
100+
return nil
101+
}
102+
46103
// validateCardinalityLimits handles validation for CardinalityLimits.
47104
func validateCardinalityLimits(plain *CardinalityLimits) error {
48105
if plain.Counter != nil && 0 >= *plain.Counter {

otelconf/config_json.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,48 @@ import (
88
"errors"
99
)
1010

11+
// UnmarshalJSON implements json.Unmarshaler.
12+
func (j *BatchLogRecordProcessor) UnmarshalJSON(b []byte) error {
13+
var raw map[string]any
14+
if err := json.Unmarshal(b, &raw); err != nil {
15+
return errors.Join(errUnmarshalingBatchLogRecordProcessor, err)
16+
}
17+
if _, ok := raw["exporter"]; raw != nil && !ok {
18+
return newErrRequiredExporter("BatchLogRecordProcessor")
19+
}
20+
type Plain BatchLogRecordProcessor
21+
var plain Plain
22+
if err := json.Unmarshal(b, &plain); err != nil {
23+
return errors.Join(errUnmarshalingBatchLogRecordProcessor, err)
24+
}
25+
if err := validateBatchLogRecordProcessor((*BatchLogRecordProcessor)(&plain)); err != nil {
26+
return err
27+
}
28+
*j = BatchLogRecordProcessor(plain)
29+
return nil
30+
}
31+
32+
// UnmarshalJSON implements json.Unmarshaler.
33+
func (j *BatchSpanProcessor) UnmarshalJSON(b []byte) error {
34+
var raw map[string]any
35+
if err := json.Unmarshal(b, &raw); err != nil {
36+
return errors.Join(errUnmarshalingBatchSpanProcessor, err)
37+
}
38+
if _, ok := raw["exporter"]; raw != nil && !ok {
39+
return newErrRequiredExporter("BatchSpanProcessor")
40+
}
41+
type Plain BatchSpanProcessor
42+
var plain Plain
43+
if err := json.Unmarshal(b, &plain); err != nil {
44+
return errors.Join(errUnmarshalingBatchSpanProcessor, err)
45+
}
46+
if err := validateBatchSpanProcessor((*BatchSpanProcessor)(&plain)); err != nil {
47+
return err
48+
}
49+
*j = BatchSpanProcessor(plain)
50+
return nil
51+
}
52+
1153
// UnmarshalJSON implements json.Unmarshaler.
1254
func (j *CardinalityLimits) UnmarshalJSON(value []byte) error {
1355
type Plain CardinalityLimits

otelconf/config_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,182 @@ import (
1010
"go.yaml.in/yaml/v3"
1111
)
1212

13+
func TestUnmarshalBatchLogRecordProcessor(t *testing.T) {
14+
for _, tt := range []struct {
15+
name string
16+
yamlConfig []byte
17+
jsonConfig []byte
18+
wantErrT error
19+
}{
20+
{
21+
name: "valid with console exporter",
22+
jsonConfig: []byte(`{"exporter":{"console":{}}}`),
23+
yamlConfig: []byte("exporter:\n console: {}"),
24+
},
25+
{
26+
name: "valid with all fields positive",
27+
jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":5000,"max_export_batch_size":512,"max_queue_size":2048,"schedule_delay":1000}`),
28+
yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: 5000\nmax_export_batch_size: 512\nmax_queue_size: 2048\nschedule_delay: 1000"),
29+
},
30+
{
31+
name: "valid with zero export_timeout",
32+
jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":0}`),
33+
yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: 0"),
34+
},
35+
{
36+
name: "valid with zero schedule_delay",
37+
jsonConfig: []byte(`{"exporter":{"console":{}},"schedule_delay":0}`),
38+
yamlConfig: []byte("exporter:\n console: {}\nschedule_delay: 0"),
39+
},
40+
{
41+
name: "missing required exporter field",
42+
jsonConfig: []byte(`{}`),
43+
yamlConfig: []byte("{}"),
44+
wantErrT: newErrRequiredExporter("BatchLogRecordProcessor"),
45+
},
46+
{
47+
name: "invalid data",
48+
jsonConfig: []byte(`{:2000}`),
49+
yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: !!str str"),
50+
wantErrT: errUnmarshalingBatchLogRecordProcessor,
51+
},
52+
{
53+
name: "invalid export_timeout negative",
54+
jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":-1}`),
55+
yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: -1"),
56+
wantErrT: newErrGreaterOrEqualZero("export_timeout"),
57+
},
58+
{
59+
name: "invalid max_export_batch_size zero",
60+
jsonConfig: []byte(`{"exporter":{"console":{}},"max_export_batch_size":0}`),
61+
yamlConfig: []byte("exporter:\n console: {}\nmax_export_batch_size: 0"),
62+
wantErrT: newErrGreaterThanZero("max_export_batch_size"),
63+
},
64+
{
65+
name: "invalid max_export_batch_size negative",
66+
jsonConfig: []byte(`{"exporter":{"console":{}},"max_export_batch_size":-1}`),
67+
yamlConfig: []byte("exporter:\n console: {}\nmax_export_batch_size: -1"),
68+
wantErrT: newErrGreaterThanZero("max_export_batch_size"),
69+
},
70+
{
71+
name: "invalid max_queue_size zero",
72+
jsonConfig: []byte(`{"exporter":{"console":{}},"max_queue_size":0}`),
73+
yamlConfig: []byte("exporter:\n console: {}\nmax_queue_size: 0"),
74+
wantErrT: newErrGreaterThanZero("max_queue_size"),
75+
},
76+
{
77+
name: "invalid max_queue_size negative",
78+
jsonConfig: []byte(`{"exporter":{"console":{}},"max_queue_size":-1}`),
79+
yamlConfig: []byte("exporter:\n console: {}\nmax_queue_size: -1"),
80+
wantErrT: newErrGreaterThanZero("max_queue_size"),
81+
},
82+
{
83+
name: "invalid schedule_delay negative",
84+
jsonConfig: []byte(`{"exporter":{"console":{}},"schedule_delay":-1}`),
85+
yamlConfig: []byte("exporter:\n console: {}\nschedule_delay: -1"),
86+
wantErrT: newErrGreaterOrEqualZero("schedule_delay"),
87+
},
88+
} {
89+
t.Run(tt.name, func(t *testing.T) {
90+
cl := BatchLogRecordProcessor{}
91+
err := cl.UnmarshalJSON(tt.jsonConfig)
92+
assert.ErrorIs(t, err, tt.wantErrT)
93+
94+
cl = BatchLogRecordProcessor{}
95+
err = yaml.Unmarshal(tt.yamlConfig, &cl)
96+
assert.ErrorIs(t, err, tt.wantErrT)
97+
})
98+
}
99+
}
100+
101+
func TestUnmarshalBatchSpanProcessor(t *testing.T) {
102+
for _, tt := range []struct {
103+
name string
104+
yamlConfig []byte
105+
jsonConfig []byte
106+
wantErrT error
107+
}{
108+
{
109+
name: "valid with console exporter",
110+
jsonConfig: []byte(`{"exporter":{"console":{}}}`),
111+
yamlConfig: []byte("exporter:\n console: {}"),
112+
},
113+
{
114+
name: "valid with all fields positive",
115+
jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":5000,"max_export_batch_size":512,"max_queue_size":2048,"schedule_delay":1000}`),
116+
yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: 5000\nmax_export_batch_size: 512\nmax_queue_size: 2048\nschedule_delay: 1000"),
117+
},
118+
{
119+
name: "valid with zero export_timeout",
120+
jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":0}`),
121+
yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: 0"),
122+
},
123+
{
124+
name: "valid with zero schedule_delay",
125+
jsonConfig: []byte(`{"exporter":{"console":{}},"schedule_delay":0}`),
126+
yamlConfig: []byte("exporter:\n console: {}\nschedule_delay: 0"),
127+
},
128+
{
129+
name: "missing required exporter field",
130+
jsonConfig: []byte(`{}`),
131+
yamlConfig: []byte("{}"),
132+
wantErrT: newErrRequiredExporter("BatchSpanProcessor"),
133+
},
134+
{
135+
name: "invalid data",
136+
jsonConfig: []byte(`{:2000}`),
137+
yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: !!str str"),
138+
wantErrT: errUnmarshalingBatchSpanProcessor,
139+
},
140+
{
141+
name: "invalid export_timeout negative",
142+
jsonConfig: []byte(`{"exporter":{"console":{}},"export_timeout":-1}`),
143+
yamlConfig: []byte("exporter:\n console: {}\nexport_timeout: -1"),
144+
wantErrT: newErrGreaterOrEqualZero("export_timeout"),
145+
},
146+
{
147+
name: "invalid max_export_batch_size zero",
148+
jsonConfig: []byte(`{"exporter":{"console":{}},"max_export_batch_size":0}`),
149+
yamlConfig: []byte("exporter:\n console: {}\nmax_export_batch_size: 0"),
150+
wantErrT: newErrGreaterThanZero("max_export_batch_size"),
151+
},
152+
{
153+
name: "invalid max_export_batch_size negative",
154+
jsonConfig: []byte(`{"exporter":{"console":{}},"max_export_batch_size":-1}`),
155+
yamlConfig: []byte("exporter:\n console: {}\nmax_export_batch_size: -1"),
156+
wantErrT: newErrGreaterThanZero("max_export_batch_size"),
157+
},
158+
{
159+
name: "invalid max_queue_size zero",
160+
jsonConfig: []byte(`{"exporter":{"console":{}},"max_queue_size":0}`),
161+
yamlConfig: []byte("exporter:\n console: {}\nmax_queue_size: 0"),
162+
wantErrT: newErrGreaterThanZero("max_queue_size"),
163+
},
164+
{
165+
name: "invalid max_queue_size negative",
166+
jsonConfig: []byte(`{"exporter":{"console":{}},"max_queue_size":-1}`),
167+
yamlConfig: []byte("exporter:\n console: {}\nmax_queue_size: -1"),
168+
wantErrT: newErrGreaterThanZero("max_queue_size"),
169+
},
170+
{
171+
name: "invalid schedule_delay negative",
172+
jsonConfig: []byte(`{"exporter":{"console":{}},"schedule_delay":-1}`),
173+
yamlConfig: []byte("exporter:\n console: {}\nschedule_delay: -1"),
174+
wantErrT: newErrGreaterOrEqualZero("schedule_delay"),
175+
},
176+
} {
177+
t.Run(tt.name, func(t *testing.T) {
178+
cl := BatchSpanProcessor{}
179+
err := cl.UnmarshalJSON(tt.jsonConfig)
180+
assert.ErrorIs(t, err, tt.wantErrT)
181+
182+
cl = BatchSpanProcessor{}
183+
err = yaml.Unmarshal(tt.yamlConfig, &cl)
184+
assert.ErrorIs(t, err, tt.wantErrT)
185+
})
186+
}
187+
}
188+
13189
func TestUnmarshalCardinalityLimits(t *testing.T) {
14190
for _, tt := range []struct {
15191
name string

otelconf/config_yaml.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,48 @@ import (
99
"go.yaml.in/yaml/v3"
1010
)
1111

12+
// UnmarshalYAML implements yaml.Unmarshaler.
13+
func (j *BatchLogRecordProcessor) UnmarshalYAML(node *yaml.Node) error {
14+
var raw map[string]any
15+
if err := node.Decode(&raw); err != nil {
16+
return errors.Join(errUnmarshalingBatchLogRecordProcessor, err)
17+
}
18+
if _, ok := raw["exporter"]; raw != nil && !ok {
19+
return newErrRequiredExporter("BatchLogRecordProcessor")
20+
}
21+
type Plain BatchLogRecordProcessor
22+
var plain Plain
23+
if err := node.Decode(&plain); err != nil {
24+
return errors.Join(errUnmarshalingBatchLogRecordProcessor, err)
25+
}
26+
if err := validateBatchLogRecordProcessor((*BatchLogRecordProcessor)(&plain)); err != nil {
27+
return err
28+
}
29+
*j = BatchLogRecordProcessor(plain)
30+
return nil
31+
}
32+
33+
// UnmarshalYAML implements yaml.Unmarshaler.
34+
func (j *BatchSpanProcessor) UnmarshalYAML(node *yaml.Node) error {
35+
var raw map[string]any
36+
if err := node.Decode(&raw); err != nil {
37+
return errors.Join(errUnmarshalingBatchSpanProcessor, err)
38+
}
39+
if _, ok := raw["exporter"]; raw != nil && !ok {
40+
return newErrRequiredExporter("BatchSpanProcessor")
41+
}
42+
type Plain BatchSpanProcessor
43+
var plain Plain
44+
if err := node.Decode(&plain); err != nil {
45+
return errors.Join(errUnmarshalingBatchSpanProcessor, err)
46+
}
47+
if err := validateBatchSpanProcessor((*BatchSpanProcessor)(&plain)); err != nil {
48+
return err
49+
}
50+
*j = BatchSpanProcessor(plain)
51+
return nil
52+
}
53+
1254
// UnmarshalYAML implements yaml.Unmarshaler.
1355
func (j *CardinalityLimits) UnmarshalYAML(node *yaml.Node) error {
1456
type Plain CardinalityLimits

0 commit comments

Comments
 (0)