diff --git a/processor/geoipprocessor/README.md b/processor/geoipprocessor/README.md index 73bb1984526a..ccf54378872f 100644 --- a/processor/geoipprocessor/README.md +++ b/processor/geoipprocessor/README.md @@ -15,21 +15,4 @@ ## Description -The geoIP processor `geoipprocessor` enhances resource attributes by appending information about the geographical location of an IP address. The IP address must be included in the resource attributes for the geographical information to be added accordingly. - -## Configuration - -The following settings must configured: - -- `fields`: A list of resource attribute keys where the IP address will be searched for. The first matched key will be used to append the geographical location. - -#### Examples - -```yaml -processors: - # processor name: geoip - geoip: - fields: - - host.ip - - ip -``` +The geoIP processor `geoipprocessor` enhances resource attributes by appending information about the geographical location of an IP address. To add geographical information, the IP address must be included in the resource attributes using the [`source.address` semantic conventions key attribute](https://github.com/open-telemetry/semantic-conventions/blob/v1.26.0/docs/general/attributes.md#source). diff --git a/processor/geoipprocessor/config.go b/processor/geoipprocessor/config.go index f0294906bb25..318739c3b360 100644 --- a/processor/geoipprocessor/config.go +++ b/processor/geoipprocessor/config.go @@ -3,21 +3,9 @@ package geoipprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/geoipprocessor" -import "errors" - // Config holds the configuration for the GeoIP processor. -type Config struct { - // Fields defines a list of resource attributes keys to look for the IP value. - // The first matching attribute will be used to gather the IP geographical metadata. - // Example: - // fields: - // - client.ip - Fields []string `mapstructure:"fields"` -} +type Config struct{} func (cfg *Config) Validate() error { - if len(cfg.Fields) < 1 { - return errors.New("must specify at least a field to look for the IP when using the geoip processor") - } return nil } diff --git a/processor/geoipprocessor/config_test.go b/processor/geoipprocessor/config_test.go index af7a03fdeb8d..7714a7587d6e 100644 --- a/processor/geoipprocessor/config_test.go +++ b/processor/geoipprocessor/config_test.go @@ -25,12 +25,7 @@ func TestLoadConfig(t *testing.T) { }{ { id: component.NewID(metadata.Type), - expected: &Config{Fields: []string{"host.ip"}}, - }, - { - id: component.NewIDWithName(metadata.Type, "no_fields"), - expected: &Config{Fields: []string{"host.ip"}}, - errorMessage: "must specify at least a field to look for the IP when using the geoip processor", + expected: &Config{}, }, } diff --git a/processor/geoipprocessor/factory.go b/processor/geoipprocessor/factory.go index 763c72405d99..aa46cfe67dc4 100644 --- a/processor/geoipprocessor/factory.go +++ b/processor/geoipprocessor/factory.go @@ -10,11 +10,20 @@ import ( "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/processor" "go.opentelemetry.io/collector/processor/processorhelper" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.25.0" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/geoipprocessor/internal/metadata" ) -var processorCapabilities = consumer.Capabilities{MutatesData: true} +var ( + processorCapabilities = consumer.Capabilities{MutatesData: true} + // defaultResourceAttributes holds a list of default resource attribute keys. + // These keys are used to identify an IP address attribute associated with the resource. + defaultResourceAttributes = []attribute.Key{ + semconv.SourceAddressKey, // This key represents the standard source address attribute as defined in the OpenTelemetry semantic conventions. + } +) // NewFactory creates a new processor factory with default configuration, // and registers the processors for metrics, traces, and logs. @@ -28,16 +37,13 @@ func createDefaultConfig() component.Config { } func createMetricsProcessor(ctx context.Context, set processor.CreateSettings, cfg component.Config, nextConsumer consumer.Metrics) (processor.Metrics, error) { - geoCfg := cfg.(*Config) - return processorhelper.NewMetricsProcessor(ctx, set, cfg, nextConsumer, newGeoIPProcessor(geoCfg.Fields).processMetrics, processorhelper.WithCapabilities(processorCapabilities)) + return processorhelper.NewMetricsProcessor(ctx, set, cfg, nextConsumer, newGeoIPProcessor(defaultResourceAttributes).processMetrics, processorhelper.WithCapabilities(processorCapabilities)) } func createTracesProcessor(ctx context.Context, set processor.CreateSettings, cfg component.Config, nextConsumer consumer.Traces) (processor.Traces, error) { - geoCfg := cfg.(*Config) - return processorhelper.NewTracesProcessor(ctx, set, cfg, nextConsumer, newGeoIPProcessor(geoCfg.Fields).processTraces, processorhelper.WithCapabilities(processorCapabilities)) + return processorhelper.NewTracesProcessor(ctx, set, cfg, nextConsumer, newGeoIPProcessor(defaultResourceAttributes).processTraces, processorhelper.WithCapabilities(processorCapabilities)) } func createLogsProcessor(ctx context.Context, set processor.CreateSettings, cfg component.Config, nextConsumer consumer.Logs) (processor.Logs, error) { - geoCfg := cfg.(*Config) - return processorhelper.NewLogsProcessor(ctx, set, cfg, nextConsumer, newGeoIPProcessor(geoCfg.Fields).processLogs, processorhelper.WithCapabilities(processorCapabilities)) + return processorhelper.NewLogsProcessor(ctx, set, cfg, nextConsumer, newGeoIPProcessor(defaultResourceAttributes).processLogs, processorhelper.WithCapabilities(processorCapabilities)) } diff --git a/processor/geoipprocessor/geoip_processor.go b/processor/geoipprocessor/geoip_processor.go index b4d2f88b39bd..f44c1b600470 100644 --- a/processor/geoipprocessor/geoip_processor.go +++ b/processor/geoipprocessor/geoip_processor.go @@ -22,21 +22,21 @@ var errIPNotFound = errors.New("no IP address found in the resource attributes") // newGeoIPProcessor creates a new instance of geoIPProcessor with the specified fields. type geoIPProcessor struct { - providers []provider.GeoIPProvider - fields []string + providers []provider.GeoIPProvider + resourceAttributes []attribute.Key } -func newGeoIPProcessor(fields []string) *geoIPProcessor { +func newGeoIPProcessor(resourceAttributes []attribute.Key) *geoIPProcessor { return &geoIPProcessor{ - fields: fields, + resourceAttributes: resourceAttributes, } } // ipFromResourceAttributes extracts an IP address from the given resource's attributes based on the specified fields. // It returns the first IP address if found, or an error if no valid IP address is found. -func ipFromResourceAttributes(fields []string, resource pcommon.Resource) (net.IP, error) { - for _, field := range fields { - if ipField, found := resource.Attributes().Get(field); found { +func ipFromResourceAttributes(attributes []attribute.Key, resource pcommon.Resource) (net.IP, error) { + for _, attr := range attributes { + if ipField, found := resource.Attributes().Get(string(attr)); found { ipAttribute := net.ParseIP(ipField.AsString()) if ipAttribute == nil { return nil, fmt.Errorf("could not parse ip address %s", ipField.AsString()) @@ -65,7 +65,7 @@ func (g *geoIPProcessor) geoLocation(ctx context.Context, ip net.IP) (attribute. // processResource processes a single resource by adding geolocation attributes based on the found IP address. func (g *geoIPProcessor) processResource(ctx context.Context, resource pcommon.Resource) error { - ipAddr, err := ipFromResourceAttributes(g.fields, resource) + ipAddr, err := ipFromResourceAttributes(g.resourceAttributes, resource) if err != nil { // TODO: log IP error not found if errors.Is(err, errIPNotFound) { diff --git a/processor/geoipprocessor/geoip_processor_test.go b/processor/geoipprocessor/geoip_processor_test.go index 3ef23a4fe349..c18e9df66b8b 100644 --- a/processor/geoipprocessor/geoip_processor_test.go +++ b/processor/geoipprocessor/geoip_processor_test.go @@ -14,6 +14,7 @@ import ( "go.opentelemetry.io/collector/pdata/pmetric" "go.opentelemetry.io/collector/pdata/ptrace" "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.25.0" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/plogtest" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest" @@ -88,20 +89,23 @@ func TestProcessPdata(t *testing.T) { tests := []struct { name string - fields []string + resourceAttributes []attribute.Key initResourceAttributes []generateResourceFunc geoLocationMock func(context.Context, net.IP) (attribute.Set, error) expectedResourceAttributes []generateResourceFunc errorMessage string }{ { - name: "no fields", - fields: []string{}, + name: "default source.ip attribute, not found", + resourceAttributes: defaultResourceAttributes, initResourceAttributes: []generateResourceFunc{ withAttributes([]attribute.KeyValue{ attribute.String("ip", "1.2.3.4"), }), }, + geoLocationMock: func(context.Context, net.IP) (attribute.Set, error) { + return attribute.NewSet([]attribute.KeyValue{attribute.String("geo.city_name", "barcelona")}...), nil + }, expectedResourceAttributes: []generateResourceFunc{ withAttributes([]attribute.KeyValue{ attribute.String("ip", "1.2.3.4"), @@ -109,8 +113,28 @@ func TestProcessPdata(t *testing.T) { }, }, { - name: "single ip field", - fields: []string{"ip"}, + name: "default source.ip attribute", + resourceAttributes: defaultResourceAttributes, + initResourceAttributes: []generateResourceFunc{ + withAttributes([]attribute.KeyValue{ + attribute.String("ip", "1.2.3.4"), + attribute.String(string(semconv.SourceAddressKey), "1.2.3.4"), + }), + }, + geoLocationMock: func(context.Context, net.IP) (attribute.Set, error) { + return attribute.NewSet([]attribute.KeyValue{attribute.String("geo.city_name", "barcelona")}...), nil + }, + expectedResourceAttributes: []generateResourceFunc{ + withAttributes([]attribute.KeyValue{ + attribute.String("ip", "1.2.3.4"), + attribute.String("geo.city_name", "barcelona"), + attribute.String(string(semconv.SourceAddressKey), "1.2.3.4"), + }), + }, + }, + { + name: "custom resource attribute", + resourceAttributes: []attribute.Key{"ip"}, initResourceAttributes: []generateResourceFunc{ withAttributes([]attribute.KeyValue{ attribute.String("ip", "1.2.3.4"), @@ -128,8 +152,8 @@ func TestProcessPdata(t *testing.T) { }, }, { - name: "multiple fields, match second one", - fields: []string{"ip", "host.ip"}, + name: "custom resource attributes, match second one", + resourceAttributes: []attribute.Key{"ip", "host.ip"}, initResourceAttributes: []generateResourceFunc{ withAttributes([]attribute.KeyValue{ attribute.String("host.ip", "1.2.3.4"), @@ -149,11 +173,11 @@ func TestProcessPdata(t *testing.T) { }, }, { - name: "invalid ip address in resource attributes", - fields: []string{"ip", "host.ip"}, + name: "invalid source.address in resource attributes", + resourceAttributes: defaultResourceAttributes, initResourceAttributes: []generateResourceFunc{ withAttributes([]attribute.KeyValue{ - attribute.String("host.ip", "%"), + attribute.String(string(semconv.SourceAddressKey), "%"), }), }, errorMessage: "could not parse ip address %", @@ -164,7 +188,7 @@ func TestProcessPdata(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // prepare processor baseProviderMock.LocationF = tt.geoLocationMock - processor := newGeoIPProcessor(tt.fields) + processor := newGeoIPProcessor(tt.resourceAttributes) processor.providers = []provider.GeoIPProvider{&baseProviderMock} // assert metrics diff --git a/processor/geoipprocessor/testdata/config.yaml b/processor/geoipprocessor/testdata/config.yaml index 182c7e13b00e..870494e36a73 100644 --- a/processor/geoipprocessor/testdata/config.yaml +++ b/processor/geoipprocessor/testdata/config.yaml @@ -1,4 +1 @@ geoip: - fields: - - host.ip -geoip/no_fields: