Skip to content

Commit 6c11868

Browse files
mjcheethamclaude
andcommitted
config: replace file-path settings with inline structs
Today the pii, filter, and summary settings are specified as paths to separate YAML files: pii: /path/to/pii.yml filter: /path/to/filter.yml summary: /path/to/summary.yml This means it is not possible to have a single, combined config file for an OpenTelemetry collector that uses the trace2receiver. It also duplicates YAML loading logic that is already built-in using the ${file:PATH} syntax. Replace the path-string fields (PiiSettingsPath, FilterSettingsPath, SummaryPath) with inline struct fields (Pii, Filter, Summary) that are populated directly by mapstructure during config unmarshalling. Users who prefer separate files can use the ${file:PATH} syntax. As part of this, rename the previously private Config fields (piiSettings, filterSettings, summary) to exported names so mapstructure can populate them, and extract validate() methods on FilterSettings and SummarySettings so they can be called from Config.Validate() directly instead of at parse-from-file time. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
1 parent 2a2f265 commit 6c11868

5 files changed

Lines changed: 52 additions & 45 deletions

File tree

config.go

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import (
77
"strings"
88
)
99

10+
// Note: The `pii`, `filter`, and `summary` fields accept inline
11+
// YAML configuration. If you prefer to keep the configuration
12+
// in a separate file, use the `${file:PATH}` syntax to reference
13+
// an external YAML file.
14+
1015
// `Config` represents the complete configuration settings for
1116
// an individual receiver declaration from the `config.yaml`.
1217
//
@@ -45,17 +50,15 @@ type Config struct {
4550
// data stream.
4651
AllowCommandControlVerbs bool `mapstructure:"enable_commands"`
4752

48-
// Pathname to YML file containing PII settings.
49-
PiiSettingsPath string `mapstructure:"pii"`
50-
piiSettings *PiiSettings
53+
// PII settings control whether possibly GDPR-sensitive fields
54+
// are included in the telemetry output.
55+
Pii *PiiSettings `mapstructure:"pii"`
5156

52-
// Pathname to YML file containing our filter settings.
53-
FilterSettingsPath string `mapstructure:"filter"`
54-
filterSettings *FilterSettings
57+
// Filter settings control how the OTLP output is filtered.
58+
Filter *FilterSettings `mapstructure:"filter"`
5559

56-
// Pathname to YML file containing summary settings.
57-
SummaryPath string `mapstructure:"summary"`
58-
summary *SummarySettings
60+
// Summary settings control aggregated metrics from trace2 events.
61+
Summary *SummarySettings `mapstructure:"summary"`
5962
}
6063

6164
// `Validate()` checks if the receiver configuration is valid.
@@ -101,23 +104,14 @@ func (cfg *Config) Validate() error {
101104
cfg.UnixSocketPath = path
102105
}
103106

104-
if len(cfg.PiiSettingsPath) > 0 {
105-
cfg.piiSettings, err = parsePiiFile(cfg.PiiSettingsPath)
106-
if err != nil {
107-
return err
108-
}
109-
}
110-
111-
if len(cfg.FilterSettingsPath) > 0 {
112-
cfg.filterSettings, err = parseFilterSettings(cfg.FilterSettingsPath)
113-
if err != nil {
107+
if cfg.Filter != nil {
108+
if err = cfg.Filter.validate(); err != nil {
114109
return err
115110
}
116111
}
117112

118-
if len(cfg.SummaryPath) > 0 {
119-
cfg.summary, err = parseSummarySettings(cfg.SummaryPath)
120-
if err != nil {
113+
if cfg.Summary != nil {
114+
if err = cfg.Summary.validate(); err != nil {
121115
return err
122116
}
123117
}

factory.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@ func createDefaultConfig() component.Config {
1818
NamedPipePath: "",
1919
UnixSocketPath: "",
2020
AllowCommandControlVerbs: false,
21-
PiiSettingsPath: "",
22-
piiSettings: nil,
23-
FilterSettingsPath: "",
24-
filterSettings: nil,
2521
}
2622
}
2723

filter_settings.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,25 +82,32 @@ func parseFilterSettingsFromBuffer(data []byte, path string) (*FilterSettings, e
8282
return nil, err
8383
}
8484

85-
// After parsing the YML and populating the `mapstructure` fields, we need
86-
// to validate them and/or build internal structures from them.
85+
if err = fs.validate(); err != nil {
86+
return nil, err
87+
}
88+
89+
return fs, nil
90+
}
8791

88-
// For each custom ruleset [<name> -> <path>] in the table (the map[string]string),
89-
// create a peer entry in the internal [<name> -> <rsdef>] table and preload
90-
// the various `ruleset.yml` files.
92+
// validate checks the parsed filter settings and builds internal
93+
// structures. For each custom ruleset [<name> -> <path>] in the
94+
// table, create a peer entry in the internal [<name> -> <rsdef>]
95+
// table and preload the various `ruleset.yml` files.
96+
func (fs *FilterSettings) validate() error {
9197
fs.rulesetDefs = make(map[string]*RulesetDefinition)
9298
for k_rs_name, v_rs_path := range fs.Rulesets {
9399
if !strings.HasPrefix(k_rs_name, "rs:") || len(k_rs_name) < 4 || len(v_rs_path) == 0 {
94-
return nil, fmt.Errorf("ruleset has invalid name or pathname'%s':'%s'", k_rs_name, v_rs_path)
100+
return fmt.Errorf("ruleset has invalid name or pathname'%s':'%s'", k_rs_name, v_rs_path)
95101
}
96102

103+
var err error
97104
fs.rulesetDefs[k_rs_name], err = parseRulesetFile(v_rs_path)
98105
if err != nil {
99-
return nil, err
106+
return err
100107
}
101108
}
102109

103-
return fs, nil
110+
return nil
104111
}
105112

106113
// Add a ruleset to the filter settings. This is primarily for writing test code.

summary.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func apply__summary_message(tr2 *trace2Dataset, message string) {
108108
return
109109
}
110110

111-
css := tr2.rcvr_base.RcvrConfig.summary
111+
css := tr2.rcvr_base.RcvrConfig.Summary
112112
if css == nil {
113113
return
114114
}
@@ -134,7 +134,7 @@ func apply__summary_region(tr2 *trace2Dataset, region *TrRegion) {
134134
return
135135
}
136136

137-
css := tr2.rcvr_base.RcvrConfig.summary
137+
css := tr2.rcvr_base.RcvrConfig.Summary
138138
if css == nil {
139139
return
140140
}

summary_settings.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,49 +60,59 @@ func parseSummarySettingsFromBuffer(data []byte, path string) (*SummarySettings,
6060
return nil, err
6161
}
6262

63+
if err = css.validate(); err != nil {
64+
return nil, err
65+
}
66+
67+
return css, nil
68+
}
69+
70+
// validate checks the parsed summary settings for errors such as
71+
// empty required fields and duplicate field names.
72+
func (css *SummarySettings) validate() error {
6373
// Track all field names to detect duplicates
6474
fieldNames := make(map[string]bool)
6575

6676
// Validate message pattern rules
6777
for i, rule := range css.MessagePatterns {
6878
if len(rule.Prefix) == 0 {
69-
return nil, fmt.Errorf("message_patterns[%d]: prefix cannot be empty", i)
79+
return fmt.Errorf("message_patterns[%d]: prefix cannot be empty", i)
7080
}
7181
if len(rule.FieldName) == 0 {
72-
return nil, fmt.Errorf("message_patterns[%d]: field_name cannot be empty", i)
82+
return fmt.Errorf("message_patterns[%d]: field_name cannot be empty", i)
7383
}
7484
if fieldNames[rule.FieldName] {
75-
return nil, fmt.Errorf("message_patterns[%d]: duplicate field_name '%s'", i, rule.FieldName)
85+
return fmt.Errorf("message_patterns[%d]: duplicate field_name '%s'", i, rule.FieldName)
7686
}
7787
fieldNames[rule.FieldName] = true
7888
}
7989

8090
// Validate region timer rules
8191
for i, rule := range css.RegionTimers {
8292
if len(rule.Category) == 0 {
83-
return nil, fmt.Errorf("region_timers[%d]: category cannot be empty", i)
93+
return fmt.Errorf("region_timers[%d]: category cannot be empty", i)
8494
}
8595
if len(rule.Label) == 0 {
86-
return nil, fmt.Errorf("region_timers[%d]: label cannot be empty", i)
96+
return fmt.Errorf("region_timers[%d]: label cannot be empty", i)
8797
}
8898
if len(rule.CountField) == 0 && len(rule.TimeField) == 0 {
89-
return nil, fmt.Errorf("region_timers[%d]: at least one of count_field or time_field must be specified", i)
99+
return fmt.Errorf("region_timers[%d]: at least one of count_field or time_field must be specified", i)
90100
}
91101

92102
if len(rule.CountField) > 0 {
93103
if fieldNames[rule.CountField] {
94-
return nil, fmt.Errorf("region_timers[%d]: duplicate field_name '%s'", i, rule.CountField)
104+
return fmt.Errorf("region_timers[%d]: duplicate field_name '%s'", i, rule.CountField)
95105
}
96106
fieldNames[rule.CountField] = true
97107
}
98108

99109
if len(rule.TimeField) > 0 {
100110
if fieldNames[rule.TimeField] {
101-
return nil, fmt.Errorf("region_timers[%d]: duplicate field_name '%s'", i, rule.TimeField)
111+
return fmt.Errorf("region_timers[%d]: duplicate field_name '%s'", i, rule.TimeField)
102112
}
103113
fieldNames[rule.TimeField] = true
104114
}
105115
}
106116

107-
return css, nil
117+
return nil
108118
}

0 commit comments

Comments
 (0)