diff --git a/apmpackage/apm/data_stream/app_metrics/fields/ecs.yml b/apmpackage/apm/data_stream/app_metrics/fields/ecs.yml index 092dd117ae2..02885185204 100644 --- a/apmpackage/apm/data_stream/app_metrics/fields/ecs.yml +++ b/apmpackage/apm/data_stream/app_metrics/fields/ecs.yml @@ -68,12 +68,13 @@ name: host.name - external: ecs name: host.os.platform -# TODO: change labels to be ECS compliant in 8.0 (https://github.com/elastic/apm-server/issues/3873) +# Ideally, we'd reference the ecs field "labels" and set the dynamic field to +# true to overwrite any settings that might be set at the data stream level. - name: labels type: object - description: | - A flat mapping of user-defined labels with string, boolean or number values. dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels. - external: ecs name: observer.hostname - external: ecs diff --git a/apmpackage/apm/data_stream/app_metrics/fields/fields.yml b/apmpackage/apm/data_stream/app_metrics/fields/fields.yml index 7102c79b0aa..2c7602bead0 100644 --- a/apmpackage/apm/data_stream/app_metrics/fields/fields.yml +++ b/apmpackage/apm/data_stream/app_metrics/fields/fields.yml @@ -248,3 +248,8 @@ The total virtual memory the process has. metric_type: gauge unit: byte +- name: numeric_labels + type: object + dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as scaled_float. diff --git a/apmpackage/apm/data_stream/app_metrics/manifest.yml b/apmpackage/apm/data_stream/app_metrics/manifest.yml index c625fd1b7c7..5806295b21f 100644 --- a/apmpackage/apm/data_stream/app_metrics/manifest.yml +++ b/apmpackage/apm/data_stream/app_metrics/manifest.yml @@ -16,3 +16,8 @@ elasticsearch: - histogram: mapping: type: histogram + - numeric_labels: + path_match: numeric_labels.* + mapping: + type: scaled_float + scaling_factor: 1000000 diff --git a/apmpackage/apm/data_stream/error_logs/fields/ecs.yml b/apmpackage/apm/data_stream/error_logs/fields/ecs.yml index df2acd5bb83..ca6967c08c2 100644 --- a/apmpackage/apm/data_stream/error_logs/fields/ecs.yml +++ b/apmpackage/apm/data_stream/error_logs/fields/ecs.yml @@ -76,12 +76,13 @@ name: http.response.status_code - external: ecs name: http.version -# TODO: change labels to be ECS compliant in 8.0 (https://github.com/elastic/apm-server/issues/3873) +# Ideally, we'd reference the ecs field "labels" and set the dynamic field to +# true to overwrite any settings that might be set at the data stream level. - name: labels type: object - description: | - A flat mapping of user-defined labels with string, boolean or number values. dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels. - external: ecs name: message - external: ecs diff --git a/apmpackage/apm/data_stream/error_logs/fields/fields.yml b/apmpackage/apm/data_stream/error_logs/fields/fields.yml index d06c3f823fd..4ada49126f3 100644 --- a/apmpackage/apm/data_stream/error_logs/fields/fields.yml +++ b/apmpackage/apm/data_stream/error_logs/fields/fields.yml @@ -224,3 +224,8 @@ type: keyword description: | Keyword of specific relevance in the service's domain (eg. 'request', 'backgroundjob', etc) +- name: numeric_labels + type: object + dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as scaled_float. diff --git a/apmpackage/apm/data_stream/error_logs/manifest.yml b/apmpackage/apm/data_stream/error_logs/manifest.yml index 06d22a1a4e7..6f833852811 100644 --- a/apmpackage/apm/data_stream/error_logs/manifest.yml +++ b/apmpackage/apm/data_stream/error_logs/manifest.yml @@ -10,3 +10,9 @@ elasticsearch: # ad-hoc searches on HTTP request headers without incurring storage cost # for users who do not need this capability. dynamic: false + dynamic_templates: + - numeric_labels: + path_match: numeric_labels.* + mapping: + type: scaled_float + scaling_factor: 1000000 diff --git a/apmpackage/apm/data_stream/internal_metrics/fields/ecs.yml b/apmpackage/apm/data_stream/internal_metrics/fields/ecs.yml index 092dd117ae2..02885185204 100644 --- a/apmpackage/apm/data_stream/internal_metrics/fields/ecs.yml +++ b/apmpackage/apm/data_stream/internal_metrics/fields/ecs.yml @@ -68,12 +68,13 @@ name: host.name - external: ecs name: host.os.platform -# TODO: change labels to be ECS compliant in 8.0 (https://github.com/elastic/apm-server/issues/3873) +# Ideally, we'd reference the ecs field "labels" and set the dynamic field to +# true to overwrite any settings that might be set at the data stream level. - name: labels type: object - description: | - A flat mapping of user-defined labels with string, boolean or number values. dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels. - external: ecs name: observer.hostname - external: ecs diff --git a/apmpackage/apm/data_stream/internal_metrics/fields/fields.yml b/apmpackage/apm/data_stream/internal_metrics/fields/fields.yml index 42a134cdd15..db0148f0888 100644 --- a/apmpackage/apm/data_stream/internal_metrics/fields/fields.yml +++ b/apmpackage/apm/data_stream/internal_metrics/fields/fields.yml @@ -203,3 +203,8 @@ type: keyword description: | Keyword of specific relevance in the service's domain (eg. 'request', 'backgroundjob', etc) +- name: numeric_labels + type: object + dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as scaled_float. diff --git a/apmpackage/apm/data_stream/profile_metrics/fields/ecs.yml b/apmpackage/apm/data_stream/profile_metrics/fields/ecs.yml index 87771bb2c13..102c298f919 100644 --- a/apmpackage/apm/data_stream/profile_metrics/fields/ecs.yml +++ b/apmpackage/apm/data_stream/profile_metrics/fields/ecs.yml @@ -58,12 +58,13 @@ name: host.name - external: ecs name: host.os.platform -# TODO: change labels to be ECS compliant in 8.0 (https://github.com/elastic/apm-server/issues/3873) +# Ideally, we'd reference the ecs field "labels" and set the dynamic field to +# true to overwrite any settings that might be set at the data stream level. - name: labels type: object - description: | - A flat mapping of user-defined labels with string, boolean or number values. dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels. - external: ecs name: observer.hostname - external: ecs diff --git a/apmpackage/apm/data_stream/profile_metrics/fields/fields.yml b/apmpackage/apm/data_stream/profile_metrics/fields/fields.yml index 24a54183440..65b634c4f5c 100644 --- a/apmpackage/apm/data_stream/profile_metrics/fields/fields.yml +++ b/apmpackage/apm/data_stream/profile_metrics/fields/fields.yml @@ -200,3 +200,8 @@ type: keyword description: | Version of the runtime used. +- name: numeric_labels + type: object + dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as scaled_float. diff --git a/apmpackage/apm/data_stream/profile_metrics/manifest.yml b/apmpackage/apm/data_stream/profile_metrics/manifest.yml index 121e328ca14..2893a8b62b0 100644 --- a/apmpackage/apm/data_stream/profile_metrics/manifest.yml +++ b/apmpackage/apm/data_stream/profile_metrics/manifest.yml @@ -8,3 +8,9 @@ elasticsearch: # Profile metrics currently must be dynamically # mapped, as pprof metric names may be customised. dynamic: true + dynamic_templates: + - numeric_labels: + path_match: numeric_labels.* + mapping: + type: scaled_float + scaling_factor: 1000000 diff --git a/apmpackage/apm/data_stream/rum_traces/fields/ecs.yml b/apmpackage/apm/data_stream/rum_traces/fields/ecs.yml index 6822e7056bf..3bfd1276d10 100644 --- a/apmpackage/apm/data_stream/rum_traces/fields/ecs.yml +++ b/apmpackage/apm/data_stream/rum_traces/fields/ecs.yml @@ -76,12 +76,13 @@ name: http.response.status_code - external: ecs name: http.version -# TODO: change labels to be ECS compliant in 8.0 (https://github.com/elastic/apm-server/issues/3873) +# Ideally, we'd reference the ecs field "labels" and set the dynamic field to +# true to overwrite any settings that might be set at the data stream level. - name: labels type: object - description: | - A flat mapping of user-defined labels with string, boolean or number values. dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels. - external: ecs name: observer.hostname - external: ecs diff --git a/apmpackage/apm/data_stream/rum_traces/fields/fields.yml b/apmpackage/apm/data_stream/rum_traces/fields/fields.yml index e7403cd313e..9c6e9d788d4 100644 --- a/apmpackage/apm/data_stream/rum_traces/fields/fields.yml +++ b/apmpackage/apm/data_stream/rum_traces/fields/fields.yml @@ -420,3 +420,8 @@ type: keyword description: | Keyword of specific relevance in the service's domain (eg. 'request', 'backgroundjob', etc) +- name: numeric_labels + type: object + dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as scaled_float. diff --git a/apmpackage/apm/data_stream/rum_traces/manifest.yml b/apmpackage/apm/data_stream/rum_traces/manifest.yml index 43df496a947..d0b15b6ee72 100644 --- a/apmpackage/apm/data_stream/rum_traces/manifest.yml +++ b/apmpackage/apm/data_stream/rum_traces/manifest.yml @@ -10,3 +10,9 @@ elasticsearch: # ad-hoc searches on HTTP request headers without incurring storage cost # for users who do not need this capability. dynamic: false + dynamic_templates: + - numeric_labels: + path_match: numeric_labels.* + mapping: + type: scaled_float + scaling_factor: 1000000 diff --git a/apmpackage/apm/data_stream/traces/fields/ecs.yml b/apmpackage/apm/data_stream/traces/fields/ecs.yml index 6822e7056bf..3bfd1276d10 100644 --- a/apmpackage/apm/data_stream/traces/fields/ecs.yml +++ b/apmpackage/apm/data_stream/traces/fields/ecs.yml @@ -76,12 +76,13 @@ name: http.response.status_code - external: ecs name: http.version -# TODO: change labels to be ECS compliant in 8.0 (https://github.com/elastic/apm-server/issues/3873) +# Ideally, we'd reference the ecs field "labels" and set the dynamic field to +# true to overwrite any settings that might be set at the data stream level. - name: labels type: object - description: | - A flat mapping of user-defined labels with string, boolean or number values. dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels. - external: ecs name: observer.hostname - external: ecs diff --git a/apmpackage/apm/data_stream/traces/fields/fields.yml b/apmpackage/apm/data_stream/traces/fields/fields.yml index e7403cd313e..9c6e9d788d4 100644 --- a/apmpackage/apm/data_stream/traces/fields/fields.yml +++ b/apmpackage/apm/data_stream/traces/fields/fields.yml @@ -420,3 +420,8 @@ type: keyword description: | Keyword of specific relevance in the service's domain (eg. 'request', 'backgroundjob', etc) +- name: numeric_labels + type: object + dynamic: true + description: | + Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as scaled_float. diff --git a/apmpackage/apm/data_stream/traces/manifest.yml b/apmpackage/apm/data_stream/traces/manifest.yml index 958db83fd00..bba48f0af6d 100644 --- a/apmpackage/apm/data_stream/traces/manifest.yml +++ b/apmpackage/apm/data_stream/traces/manifest.yml @@ -10,3 +10,9 @@ elasticsearch: # ad-hoc searches on HTTP request headers without incurring storage cost # for users who do not need this capability. dynamic: false + dynamic_templates: + - numeric_labels: + path_match: numeric_labels.* + mapping: + type: scaled_float + scaling_factor: 1000000 diff --git a/apmpackage/apm/docs/README.md b/apmpackage/apm/docs/README.md index 87464b266fe..4daa8a57593 100644 --- a/apmpackage/apm/docs/README.md +++ b/apmpackage/apm/docs/README.md @@ -113,13 +113,14 @@ Traces are written to `traces-apm-*` data streams, except for RUM traces, which | kubernetes.node.name | Kubernetes node name | keyword | | kubernetes.pod.name | Kubernetes pod name | keyword | | kubernetes.pod.uid | Kubernetes Pod UID | keyword | -| labels | A flat mapping of user-defined labels with string, boolean or number values. | object | +| labels | Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels. | object | | network.carrier.icc | ISO country code, eg. US | keyword | | network.carrier.mcc | Mobile country code | keyword | | network.carrier.mnc | Mobile network code | keyword | | network.carrier.name | Carrier name, eg. Vodafone, T-Mobile, etc. | keyword | | network.connection.subtype | Detailed network connection sub-type, e.g. "LTE", "CDMA" | keyword | | network.connection.type | Network connection type, eg. "wifi", "cell" | keyword | +| numeric_labels | Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as scaled_float. | object | | observer.ephemeral_id | Ephemeral identifier of the APM Server. | keyword | | observer.hostname | Hostname of the observer. | keyword | | observer.id | Unique identifier of the APM Server. | keyword | @@ -265,9 +266,10 @@ Application metrics are written to service-specific `metrics-apm.app.*-*` data s | kubernetes.node.name | Kubernetes node name | keyword | | | | kubernetes.pod.name | Kubernetes pod name | keyword | | | | kubernetes.pod.uid | Kubernetes Pod UID | keyword | | | -| labels | A flat mapping of user-defined labels with string, boolean or number values. | object | | | +| labels | Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels. | object | | | | metricset.name | Name of the set of metrics. | keyword | | | | network.connection.type | Network connection type, eg. "wifi", "cell" | keyword | | | +| numeric_labels | Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as scaled_float. | object | | | | observer.ephemeral_id | Ephemeral identifier of the APM Server. | keyword | | | | observer.hostname | Hostname of the observer. | keyword | | | | observer.id | Unique identifier of the APM Server. | keyword | | | @@ -380,9 +382,10 @@ Internal metrics are written to `metrics-apm.internal-*` data streams. | kubernetes.node.name | Kubernetes node name | keyword | | | kubernetes.pod.name | Kubernetes pod name | keyword | | | kubernetes.pod.uid | Kubernetes Pod UID | keyword | | -| labels | A flat mapping of user-defined labels with string, boolean or number values. | object | | +| labels | Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels. | object | | | metricset.name | Name of the set of metrics. | keyword | | | network.connection.type | Network connection type, eg. "wifi", "cell" | keyword | | +| numeric_labels | Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as scaled_float. | object | | | observer.ephemeral_id | Ephemeral identifier of the APM Server. | keyword | | | observer.hostname | Hostname of the observer. | keyword | | | observer.id | Unique identifier of the APM Server. | keyword | | @@ -513,7 +516,7 @@ Application errors are written to `logs-apm.error.*` data stream. | kubernetes.node.name | Kubernetes node name | keyword | | kubernetes.pod.name | Kubernetes pod name | keyword | | kubernetes.pod.uid | Kubernetes Pod UID | keyword | -| labels | A flat mapping of user-defined labels with string, boolean or number values. | object | +| labels | Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as keyword. Example: `docker` and `k8s` labels. | object | | message | For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message. | match_only_text | | network.carrier.icc | ISO country code, eg. US | keyword | | network.carrier.mcc | Mobile country code | keyword | @@ -521,6 +524,7 @@ Application errors are written to `logs-apm.error.*` data stream. | network.carrier.name | Carrier name, eg. Vodafone, T-Mobile, etc. | keyword | | network.connection.subtype | Detailed network connection sub-type, e.g. "LTE", "CDMA" | keyword | | network.connection.type | Network connection type, eg. "wifi", "cell" | keyword | +| numeric_labels | Custom key/value pairs. Can be used to add meta information to events. Should not contain nested objects. All values are stored as scaled_float. | object | | observer.ephemeral_id | Ephemeral identifier of the APM Server. | keyword | | observer.hostname | Hostname of the observer. | keyword | | observer.id | Unique identifier of the APM Server. | keyword | diff --git a/beater/beater_test.go b/beater/beater_test.go index b48667ea2b1..87f61a8285d 100644 --- a/beater/beater_test.go +++ b/beater/beater_test.go @@ -182,7 +182,7 @@ func newTestBeater( if event.Labels == nil { event.Labels = common.MapStr{} } - event.Labels["wrapped_reporter"] = true + event.Labels["wrapped_reporter"] = "true" } return nil } diff --git a/beater/test_approved_es_documents/TestPublishIntegrationEvents.approved.json b/beater/test_approved_es_documents/TestPublishIntegrationEvents.approved.json index f266254cfb9..422001af51f 100644 --- a/beater/test_approved_es_documents/TestPublishIntegrationEvents.approved.json +++ b/beater/test_approved_es_documents/TestPublishIntegrationEvents.approved.json @@ -210,12 +210,14 @@ } }, "labels": { - "ab_testing": true, + "ab_testing": "true", "group": "experimental", - "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8", - "segment": 5 + "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8" }, "message": "Request method 'POST' not supported", + "numeric_labels": { + "segment": 5 + }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", "id": "fbba762a-14dd-412c-b7e9-b79f903eb492", @@ -343,8 +345,10 @@ } }, "labels": { - "ab_testing": true, - "group": "experimental", + "ab_testing": "true", + "group": "experimental" + }, + "numeric_labels": { "segment": 5 }, "observer": { @@ -540,11 +544,13 @@ } }, "labels": { - "ab_testing": true, + "ab_testing": "true", "group": "experimental", "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8", - "segment": 5, - "wrapped_reporter": true + "wrapped_reporter": "true" + }, + "numeric_labels": { + "segment": 5 }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", @@ -677,13 +683,15 @@ } }, "labels": { - "ab_testing": true, - "code": 200, + "ab_testing": "true", "group": "experimental", - "segment": 5, - "success": true + "success": "true" }, "metricset.name": "span_breakdown", + "numeric_labels": { + "code": 200, + "segment": 5 + }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", "id": "fbba762a-14dd-412c-b7e9-b79f903eb492", diff --git a/beater/test_approved_es_documents/TestPublishIntegrationMetricsets.approved.json b/beater/test_approved_es_documents/TestPublishIntegrationMetricsets.approved.json index 53251a4b4e9..c23ba0fed57 100644 --- a/beater/test_approved_es_documents/TestPublishIntegrationMetricsets.approved.json +++ b/beater/test_approved_es_documents/TestPublishIntegrationMetricsets.approved.json @@ -16,13 +16,15 @@ "ip": "127.0.0.1" }, "labels": { - "code": 200, "some": "abc", - "success": true, - "tag1": "one", - "tag2": 2 + "success": "true", + "tag1": "one" }, "metricset.name": "span_breakdown", + "numeric_labels": { + "code": 200, + "tag2": 2 + }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", "id": "fbba762a-14dd-412c-b7e9-b79f903eb492", @@ -81,10 +83,12 @@ "ip": "127.0.0.1" }, "labels": { - "tag1": "one", - "tag2": 2 + "tag1": "one" }, "metricset.name": "app", + "numeric_labels": { + "tag2": 2 + }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", "id": "fbba762a-14dd-412c-b7e9-b79f903eb492", @@ -130,10 +134,12 @@ "ip": "127.0.0.1" }, "labels": { - "tag1": "one", - "tag2": 2 + "tag1": "one" }, "metricset.name": "app", + "numeric_labels": { + "tag2": 2 + }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", "id": "fbba762a-14dd-412c-b7e9-b79f903eb492", @@ -181,10 +187,12 @@ "ip": "127.0.0.1" }, "labels": { - "tag1": "one", - "tag2": 2 + "tag1": "one" }, "metricset.name": "app", + "numeric_labels": { + "tag2": 2 + }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", "id": "fbba762a-14dd-412c-b7e9-b79f903eb492", @@ -244,8 +252,7 @@ "ip": "127.0.0.1" }, "labels": { - "tag1": "one", - "tag2": 2 + "tag1": "one" }, "latency_distribution": { "counts": [ @@ -260,6 +267,9 @@ ] }, "metricset.name": "app", + "numeric_labels": { + "tag2": 2 + }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", "id": "fbba762a-14dd-412c-b7e9-b79f903eb492", diff --git a/beater/test_approved_es_documents/TestPublishIntegrationMinimalEvents.approved.json b/beater/test_approved_es_documents/TestPublishIntegrationMinimalEvents.approved.json index 3a0b869d3ed..38486e7de9c 100644 --- a/beater/test_approved_es_documents/TestPublishIntegrationMinimalEvents.approved.json +++ b/beater/test_approved_es_documents/TestPublishIntegrationMinimalEvents.approved.json @@ -245,7 +245,7 @@ "ip": "127.0.0.1" }, "labels": { - "wrapped_reporter": true + "wrapped_reporter": "true" }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", diff --git a/beater/test_approved_es_documents/TestPublishIntegrationSpans.approved.json b/beater/test_approved_es_documents/TestPublishIntegrationSpans.approved.json index a0abf40aa5e..010c0b71707 100644 --- a/beater/test_approved_es_documents/TestPublishIntegrationSpans.approved.json +++ b/beater/test_approved_es_documents/TestPublishIntegrationSpans.approved.json @@ -329,9 +329,11 @@ }, "labels": { "tag1": "value1", + "tag4": "true" + }, + "numeric_labels": { "tag2": 123, - "tag3": 12.34, - "tag4": true + "tag3": 12.34 }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", diff --git a/beater/test_approved_es_documents/TestPublishIntegrationTransactions.approved.json b/beater/test_approved_es_documents/TestPublishIntegrationTransactions.approved.json index 30971fe52de..61bfcf58e74 100644 --- a/beater/test_approved_es_documents/TestPublishIntegrationTransactions.approved.json +++ b/beater/test_approved_es_documents/TestPublishIntegrationTransactions.approved.json @@ -62,8 +62,10 @@ }, "labels": { "tag1": "one", - "tag2": 2, - "wrapped_reporter": true + "wrapped_reporter": "true" + }, + "numeric_labels": { + "tag2": 2 }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", @@ -254,10 +256,12 @@ "labels": { "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8", "tag1": "one", + "tag4": "false", + "wrapped_reporter": "true" + }, + "numeric_labels": { "tag2": 12, - "tag3": 12.45, - "tag4": false, - "wrapped_reporter": true + "tag3": 12.45 }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", @@ -419,8 +423,10 @@ }, "labels": { "tag1": "one", - "tag2": 2, - "wrapped_reporter": true + "wrapped_reporter": "true" + }, + "numeric_labels": { + "tag2": 2 }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", @@ -568,8 +574,10 @@ }, "labels": { "tag1": "one", - "tag2": 2, - "wrapped_reporter": true + "wrapped_reporter": "true" + }, + "numeric_labels": { + "tag2": 2 }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", @@ -735,8 +743,10 @@ }, "labels": { "tag1": "one", - "tag2": 2, - "wrapped_reporter": true + "wrapped_reporter": "true" + }, + "numeric_labels": { + "tag2": 2 }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", diff --git a/beater/test_approved_es_documents/TestPublishIntegrationTransactionsHugeTraces.approved.json b/beater/test_approved_es_documents/TestPublishIntegrationTransactionsHugeTraces.approved.json index e0a2bea4824..0a4253c306a 100644 --- a/beater/test_approved_es_documents/TestPublishIntegrationTransactionsHugeTraces.approved.json +++ b/beater/test_approved_es_documents/TestPublishIntegrationTransactionsHugeTraces.approved.json @@ -123,10 +123,12 @@ "labels": { "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8", "tag1": "one", + "tag4": "false", + "wrapped_reporter": "true" + }, + "numeric_labels": { "tag2": 12, - "tag3": 12.45, - "tag4": false, - "wrapped_reporter": true + "tag3": 12.45 }, "observer": { "ephemeral_id": "00000000-0000-0000-0000-000000000000", diff --git a/changelogs/head.asciidoc b/changelogs/head.asciidoc index e9c45f50871..2eb724ba281 100644 --- a/changelogs/head.asciidoc +++ b/changelogs/head.asciidoc @@ -72,6 +72,7 @@ https://github.com/elastic/apm-server/compare/7.15\...master[View commits] - When `auth.anonymous.enabled` isn't specified and RUM is enabled (`rum.enabled:true`), `auth.anonymous.enabled` will be set to `true` {pull}6607[6607] - Added metrics for new Elasticsearch output: `libbeat.output.events.{acked,batches,toomany}`; added tracing and log correlation {pull}6630[6630] - Run the java attacher jar when configured and not in a cloud environment {pull}6617[6617] +- The `labels` indexed field is now ECS compliant (string only) and added a new `numeric_labels` object that holds labels with numeric values {pull}6633[6633] [float] ==== Deprecated diff --git a/model/apmevent.go b/model/apmevent.go index 33987e431cf..5a115b86b1f 100644 --- a/model/apmevent.go +++ b/model/apmevent.go @@ -62,17 +62,22 @@ type APMEvent struct { // Timestamp holds the event timestamp. // - // See https://www.elastic.co/guide/en/ecs/current/ecs-base.html + // See https://www.elastic.co/guide/en/ecs/current/ecs-base.html#field-timestamp Timestamp time.Time - // Labels holds labels to apply to the event. + // Labels holds the string (keyword) labels to apply to the event, stored as + // keywords. Supports slice values. // - // See https://www.elastic.co/guide/en/ecs/current/ecs-base.html + // See https://www.elastic.co/guide/en/ecs/current/ecs-base.html#field-labels Labels common.MapStr + // NumericLabels holds the numeric (scaled_float) labels to apply to the event. + // Supports slice values. + NumericLabels common.MapStr + // Message holds the message for log events. // - // See https://www.elastic.co/guide/en/ecs/current/ecs-base.html + // See https://www.elastic.co/guide/en/ecs/current/ecs-base.html#field-message Message string Transaction *Transaction @@ -136,6 +141,7 @@ func (e *APMEvent) BeatEvent(ctx context.Context) beat.Event { fields.maybeSetMapStr("cloud", e.Cloud.fields()) fields.maybeSetMapStr("network", e.Network.fields()) fields.maybeSetMapStr("labels", sanitizeLabels(e.Labels)) + fields.maybeSetMapStr("numeric_labels", sanitizeLabels(e.NumericLabels)) fields.maybeSetMapStr("event", e.Event.fields()) fields.maybeSetMapStr("url", e.URL.fields()) fields.maybeSetMapStr("session", e.Session.fields()) diff --git a/model/apmevent_test.go b/model/apmevent_test.go index 0050ae7492d..ca668f03dad 100644 --- a/model/apmevent_test.go +++ b/model/apmevent_test.go @@ -80,7 +80,15 @@ func TestAPMEventFields(t *testing.T) { Event: Event{Outcome: outcome}, Session: Session{ID: "session_id"}, URL: URL{Original: "url"}, - Labels: common.MapStr{"a": "b", "c": 123}, + Labels: common.MapStr{ + "a": "b", + "c": "true", + "d": []string{"true", "false"}, + }, + NumericLabels: common.MapStr{ + "e": float64(1234), + "f": []float64{1234, 12311}, + }, Message: "bottle", Transaction: &Transaction{}, Timestamp: time.Date(2019, 1, 3, 15, 17, 4, 908.596*1e6, time.FixedZone("+0100", 3600)), @@ -139,7 +147,12 @@ func TestAPMEventFields(t *testing.T) { "url": common.MapStr{"original": "url"}, "labels": common.MapStr{ "a": "b", - "c": 123, + "c": "true", + "d": []string{"true", "false"}, + }, + "numeric_labels": common.MapStr{ + "e": float64(1234), + "f": []float64{1234, 12311}, }, "message": "bottle", "trace": common.MapStr{ diff --git a/model/modeldecoder/modeldecoderutil/labels.go b/model/modeldecoder/modeldecoderutil/labels.go index d9311324acf..cb9d02139c1 100644 --- a/model/modeldecoder/modeldecoderutil/labels.go +++ b/model/modeldecoder/modeldecoderutil/labels.go @@ -19,16 +19,37 @@ package modeldecoderutil import ( "encoding/json" + "strconv" "github.com/elastic/beats/v7/libbeat/common" + + "github.com/elastic/apm-server/model" ) +// LabelsFrom populates the Labels and NumericLabels. +func LabelsFrom(from common.MapStr, to *model.APMEvent) { + to.NumericLabels = filterLabels(NormalizeLabelValues(from.Clone()), + filterNumberLabels, + ) + to.Labels = filterLabels(from, filterStringLabels) +} + // MergeLabels merges eventLabels onto commonLabels. This is used for // combining event-specific labels onto (metadata) global labels. // // If commonLabels is non-nil, it is first cloned. If commonLabels // is nil, then eventLabels is cloned. -func MergeLabels(commonLabels, eventLabels common.MapStr) common.MapStr { +func MergeLabels(eventLabels common.MapStr, to *model.APMEvent) { + to.NumericLabels = mergeLabels(to.NumericLabels, filterLabels( + NormalizeLabelValues(eventLabels.Clone()), + filterNumberLabels, + )) + to.Labels = mergeLabels(to.Labels, + filterLabels(eventLabels, filterStringLabels), + ) +} + +func mergeLabels(commonLabels, eventLabels common.MapStr) common.MapStr { if commonLabels == nil { return eventLabels.Clone() } @@ -53,3 +74,50 @@ func NormalizeLabelValues(labels common.MapStr) common.MapStr { } return labels } + +func filterLabels(labels common.MapStr, fn func(interface{}) interface{}) common.MapStr { + result := common.MapStr{} + for k, v := range labels { + if typeValue := fn(v); typeValue != nil { + result[k] = typeValue + } + } + if len(result) > 0 { + return result + } + return nil +} + +func filterStringLabels(v interface{}) interface{} { + switch v := v.(type) { + case string: + return v + case bool: + return strconv.FormatBool(v) + case []interface{}: + res := make([]interface{}, 0, len(v)) + for i := range v { + if val := filterStringLabels(v[i]); val != nil { + res = append(res, val) + } + } + return res + } + return nil +} + +func filterNumberLabels(v interface{}) interface{} { + switch v := v.(type) { + case float64: + return v + case []interface{}: + res := make([]interface{}, 0, len(v)) + for i := range v { + if val := filterNumberLabels(v[i]); val != nil { + res = append(res, val) + } + } + return res + } + return nil +} diff --git a/model/modeldecoder/rumv3/decoder.go b/model/modeldecoder/rumv3/decoder.go index c732f7acc31..b604cebad47 100644 --- a/model/modeldecoder/rumv3/decoder.go +++ b/model/modeldecoder/rumv3/decoder.go @@ -171,10 +171,7 @@ func mapToErrorModel(from *errorEvent, event *model.APMEvent) { // map errorEvent specific data if from.Context.IsSet() { if len(from.Context.Tags) > 0 { - event.Labels = modeldecoderutil.MergeLabels( - event.Labels, - modeldecoderutil.NormalizeLabelValues(from.Context.Tags), - ) + modeldecoderutil.MergeLabels(from.Context.Tags, event) } if from.Context.Request.IsSet() { event.HTTP.Request = &model.HTTPRequest{} @@ -300,7 +297,7 @@ func mapToExceptionModel(from errorException, out *model.Exception) { func mapToMetadataModel(m *metadata, out *model.APMEvent) { // Labels if len(m.Labels) > 0 { - out.Labels = modeldecoderutil.NormalizeLabelValues(m.Labels.Clone()) + modeldecoderutil.LabelsFrom(m.Labels, out) } // Service @@ -543,10 +540,7 @@ func mapToSpanModel(from *span, event *model.APMEvent) { } } if len(from.Context.Tags) > 0 { - event.Labels = modeldecoderutil.MergeLabels( - event.Labels, - modeldecoderutil.NormalizeLabelValues(from.Context.Tags), - ) + modeldecoderutil.MergeLabels(from.Context.Tags, event) } if from.Duration.IsSet() { duration := time.Duration(from.Duration.Val * float64(time.Millisecond)) @@ -651,10 +645,7 @@ func mapToTransactionModel(from *transaction, event *model.APMEvent) { out.Custom = modeldecoderutil.NormalizeLabelValues(from.Context.Custom.Clone()) } if len(from.Context.Tags) > 0 { - event.Labels = modeldecoderutil.MergeLabels( - event.Labels, - modeldecoderutil.NormalizeLabelValues(from.Context.Tags), - ) + modeldecoderutil.MergeLabels(from.Context.Tags, event) } if from.Context.Request.IsSet() { event.HTTP.Request = &model.HTTPRequest{} diff --git a/model/modeldecoder/rumv3/metadata_test.go b/model/modeldecoder/rumv3/metadata_test.go index 37baf7705b4..0aa6f9e6a59 100644 --- a/model/modeldecoder/rumv3/metadata_test.go +++ b/model/modeldecoder/rumv3/metadata_test.go @@ -84,6 +84,9 @@ func metadataExceptions(keys ...string) func(key string) bool { "Trace", "URL", + // Dedicated test for it. + "NumericLabels", + // event-specific fields "Error", "Metricset", @@ -135,6 +138,24 @@ func TestDecodeNestedMetadata(t *testing.T) { assert.Contains(t, err.Error(), "validation") }) + t.Run("labels", func(t *testing.T) { + var out model.APMEvent + labelMetadata := `{"m":{"l":{"a":"b","c":true,"d":1234,"e":1234.11},"se":{"n":"name","a":{"n":"go","ve":"1.0.0"}}}}` + dec := decoder.NewJSONDecoder(strings.NewReader(labelMetadata)) + require.NoError(t, DecodeNestedMetadata(dec, &out)) + assert.Equal(t, model.APMEvent{ + Service: model.Service{Name: "name"}, + Agent: model.Agent{Name: "go", Version: "1.0.0"}, + Labels: common.MapStr{ + "a": "b", + "c": "true", + }, + NumericLabels: common.MapStr{ + "d": float64(1234), + "e": float64(1234.11), + }, + }, out) + }) } func TestDecodeMetadataMappingToModel(t *testing.T) { diff --git a/model/modeldecoder/rumv3/transaction_test.go b/model/modeldecoder/rumv3/transaction_test.go index 188be150a49..1dd7831fd52 100644 --- a/model/modeldecoder/rumv3/transaction_test.go +++ b/model/modeldecoder/rumv3/transaction_test.go @@ -378,4 +378,23 @@ func TestDecodeMapToTransactionModel(t *testing.T) { Sequence: 123, }, out.Session) }) + t.Run("labels", func(t *testing.T) { + var input transaction + input.Context.Tags = common.MapStr{ + "a": "b", + "c": float64(12315124131), + "d": 12315124131.12315124131, + "e": true, + } + var out model.APMEvent + mapToTransactionModel(&input, &out) + assert.Equal(t, common.MapStr{ + "a": "b", + "e": "true", + }, out.Labels) + assert.Equal(t, common.MapStr{ + "c": float64(12315124131), + "d": float64(12315124131.12315124131), + }, out.NumericLabels) + }) } diff --git a/model/modeldecoder/v2/decoder.go b/model/modeldecoder/v2/decoder.go index 49f2779cbae..64b6b72610c 100644 --- a/model/modeldecoder/v2/decoder.go +++ b/model/modeldecoder/v2/decoder.go @@ -316,10 +316,7 @@ func mapToErrorModel(from *errorEvent, event *model.APMEvent) { if from.Context.IsSet() { if len(from.Context.Tags) > 0 { - event.Labels = modeldecoderutil.MergeLabels( - event.Labels, - modeldecoderutil.NormalizeLabelValues(from.Context.Tags), - ) + modeldecoderutil.MergeLabels(from.Context.Tags, event) } if from.Context.Request.IsSet() { event.HTTP.Request = &model.HTTPRequest{} @@ -482,7 +479,7 @@ func mapToMetadataModel(from *metadata, out *model.APMEvent) { // Labels if len(from.Labels) > 0 { - out.Labels = modeldecoderutil.NormalizeLabelValues(from.Labels.Clone()) + modeldecoderutil.LabelsFrom(from.Labels, out) } // Process @@ -629,10 +626,7 @@ func mapToMetricsetModel(from *metricset, event *model.APMEvent) bool { } if len(from.Tags) > 0 { - event.Labels = modeldecoderutil.MergeLabels( - event.Labels, - modeldecoderutil.NormalizeLabelValues(from.Tags), - ) + modeldecoderutil.MergeLabels(from.Tags, event) } if from.Span.IsSet() { @@ -954,10 +948,7 @@ func mapToSpanModel(from *span, event *model.APMEvent) { mapToAgentModel(from.Context.Service.Agent, &event.Agent) } if len(from.Context.Tags) > 0 { - event.Labels = modeldecoderutil.MergeLabels( - event.Labels, - modeldecoderutil.NormalizeLabelValues(from.Context.Tags), - ) + modeldecoderutil.MergeLabels(from.Context.Tags, event) } if from.Duration.IsSet() { duration := time.Duration(from.Duration.Val * float64(time.Millisecond)) @@ -1088,10 +1079,7 @@ func mapToTransactionModel(from *transaction, event *model.APMEvent) { out.Custom = modeldecoderutil.NormalizeLabelValues(from.Context.Custom.Clone()) } if len(from.Context.Tags) > 0 { - event.Labels = modeldecoderutil.MergeLabels( - event.Labels, - modeldecoderutil.NormalizeLabelValues(from.Context.Tags), - ) + modeldecoderutil.MergeLabels(from.Context.Tags, event) } if from.Context.Message.IsSet() { out.Message = &model.Message{} @@ -1254,6 +1242,9 @@ func mapOTelAttributesTransaction(from otel, out *model.APMEvent) { if out.Labels == nil { out.Labels = make(common.MapStr) } + if out.NumericLabels == nil { + out.NumericLabels = make(common.MapStr) + } // TODO: Does this work? Is there a way we can infer the status code, // potentially in the actual attributes map? spanStatus := pdata.NewSpanStatus() @@ -1279,6 +1270,9 @@ func mapOTelAttributesSpan(from otel, out *model.APMEvent) { if out.Labels == nil { out.Labels = make(common.MapStr) } + if out.NumericLabels == nil { + out.NumericLabels = make(common.MapStr) + } var spanKind pdata.SpanKind if from.SpanKind.IsSet() { switch from.SpanKind.Val { diff --git a/model/modeldecoder/v2/metadata_test.go b/model/modeldecoder/v2/metadata_test.go index 542a6463392..0918057f811 100644 --- a/model/modeldecoder/v2/metadata_test.go +++ b/model/modeldecoder/v2/metadata_test.go @@ -25,6 +25,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/apm-server/decoder" "github.com/elastic/apm-server/model" "github.com/elastic/apm-server/model/modeldecoder/modeldecodertest" @@ -84,6 +86,7 @@ func isUnmappedMetadataField(key string) bool { "Observer.Type", "Observer.Version", "Observer.VersionMajor", + "NumericLabels", // Tested individually. "Parent", "Parent.ID", "Process.CommandLine", @@ -164,9 +167,9 @@ func TestDecodeMetadata(t *testing.T) { decodeFn func(decoder.Decoder, *model.APMEvent) error }{ {name: "decodeMetadata", decodeFn: DecodeMetadata, - input: `{"service":{"name":"user-service","agent":{"name":"go","version":"1.0.0"}}}`}, + input: `{"labels":{"a":"b","c":true,"d":1234,"e":1234.11},"service":{"name":"user-service","agent":{"name":"go","version":"1.0.0"}}}`}, {name: "decodeNestedMetadata", decodeFn: DecodeNestedMetadata, - input: `{"metadata":{"service":{"name":"user-service","agent":{"name":"go","version":"1.0.0"}}}}`}, + input: `{"metadata":{"labels":{"a":"b","c":true,"d":1234,"e":1234.11},"service":{"name":"user-service","agent":{"name":"go","version":"1.0.0"}}}}`}, } { t.Run("decode", func(t *testing.T) { var out model.APMEvent @@ -175,6 +178,14 @@ func TestDecodeMetadata(t *testing.T) { assert.Equal(t, model.APMEvent{ Service: model.Service{Name: "user-service"}, Agent: model.Agent{Name: "go", Version: "1.0.0"}, + Labels: common.MapStr{ + "a": "b", + "c": "true", + }, + NumericLabels: common.MapStr{ + "d": float64(1234), + "e": float64(1234.11), + }, }, out) err := tc.decodeFn(decoder.NewJSONDecoder(strings.NewReader(`malformed`)), &out) @@ -280,6 +291,5 @@ func TestDecodeMapToMetadataModel(t *testing.T) { input.System.DeprecatedHostname.Reset() assert.Empty(t, out.Host.Name) assert.Empty(t, out.Host.Hostname) - }) } diff --git a/model/modeldecoder/v2/span_test.go b/model/modeldecoder/v2/span_test.go index 0329166335a..fda993176f3 100644 --- a/model/modeldecoder/v2/span_test.go +++ b/model/modeldecoder/v2/span_test.go @@ -445,4 +445,23 @@ func TestDecodeMapToSpanModel(t *testing.T) { assert.Equal(t, "CONSUMER", event.Span.Kind) }) }) + t.Run("labels", func(t *testing.T) { + var input span + input.Context.Tags = common.MapStr{ + "a": "b", + "c": float64(12315124131), + "d": 12315124131.12315124131, + "e": true, + } + var out model.APMEvent + mapToSpanModel(&input, &out) + assert.Equal(t, common.MapStr{ + "a": "b", + "e": "true", + }, out.Labels) + assert.Equal(t, common.MapStr{ + "c": float64(12315124131), + "d": float64(12315124131.12315124131), + }, out.NumericLabels) + }) } diff --git a/model/modeldecoder/v2/transaction_test.go b/model/modeldecoder/v2/transaction_test.go index d0a782d0426..78af2fa06eb 100644 --- a/model/modeldecoder/v2/transaction_test.go +++ b/model/modeldecoder/v2/transaction_test.go @@ -547,7 +547,8 @@ func TestDecodeMapToTransactionModel(t *testing.T) { input.Type.Reset() mapToTransactionModel(&input, &event) - assert.Equal(t, common.MapStr{"double_attr": 123.456}, event.Labels) + assert.Equal(t, common.MapStr{}, event.Labels) + assert.Equal(t, common.MapStr{"double_attr": 123.456}, event.NumericLabels) }) t.Run("kind", func(t *testing.T) { @@ -560,4 +561,23 @@ func TestDecodeMapToTransactionModel(t *testing.T) { assert.Equal(t, "CLIENT", event.Span.Kind) }) }) + t.Run("labels", func(t *testing.T) { + var input span + input.Context.Tags = common.MapStr{ + "a": "b", + "c": float64(12315124131), + "d": 12315124131.12315124131, + "e": true, + } + var out model.APMEvent + mapToSpanModel(&input, &out) + assert.Equal(t, common.MapStr{ + "a": "b", + "e": "true", + }, out.Labels) + assert.Equal(t, common.MapStr{ + "c": float64(12315124131), + "d": float64(12315124131.12315124131), + }, out.NumericLabels) + }) } diff --git a/processor/otel/exceptions_test.go b/processor/otel/exceptions_test.go index 6482ad0ffb3..30e2d898ae3 100644 --- a/processor/otel/exceptions_test.go +++ b/processor/otel/exceptions_test.go @@ -114,13 +114,14 @@ Caused by: LowLevelException service, agent := languageOnlyMetadata("java") transactionEvent, errorEvents := transformTransactionSpanEvents(t, "java", exceptionEvent1, exceptionEvent2) assert.Equal(t, []model.APMEvent{{ - Service: service, - Agent: agent, - Timestamp: timestamp, - Labels: common.MapStr{}, - Processor: model.ErrorProcessor, - Trace: transactionEvent.Trace, - Parent: model.Parent{ID: transactionEvent.Transaction.ID}, + Service: service, + Agent: agent, + Timestamp: timestamp, + Labels: common.MapStr{}, + NumericLabels: common.MapStr{}, + Processor: model.ErrorProcessor, + Trace: transactionEvent.Trace, + Parent: model.Parent{ID: transactionEvent.Transaction.ID}, Transaction: &model.Transaction{ ID: transactionEvent.Transaction.ID, Type: transactionEvent.Transaction.Type, @@ -165,13 +166,14 @@ Caused by: LowLevelException }, }, }, { - Service: service, - Agent: agent, - Timestamp: timestamp, - Labels: common.MapStr{}, - Processor: model.ErrorProcessor, - Trace: transactionEvent.Trace, - Parent: model.Parent{ID: transactionEvent.Transaction.ID}, + Service: service, + Agent: agent, + Timestamp: timestamp, + Labels: common.MapStr{}, + NumericLabels: common.MapStr{}, + Processor: model.ErrorProcessor, + Trace: transactionEvent.Trace, + Parent: model.Parent{ID: transactionEvent.Transaction.ID}, Transaction: &model.Transaction{ ID: transactionEvent.Transaction.ID, Type: transactionEvent.Transaction.Type, @@ -325,13 +327,14 @@ func TestEncodeSpanEventsNonJavaExceptions(t *testing.T) { service, agent := languageOnlyMetadata("COBOL") assert.Equal(t, model.APMEvent{ - Service: service, - Agent: agent, - Timestamp: timestamp, - Labels: common.MapStr{}, - Processor: model.ErrorProcessor, - Trace: transactionEvent.Trace, - Parent: model.Parent{ID: transactionEvent.Transaction.ID}, + Service: service, + Agent: agent, + Timestamp: timestamp, + Labels: common.MapStr{}, + NumericLabels: common.MapStr{}, + Processor: model.ErrorProcessor, + Trace: transactionEvent.Trace, + Parent: model.Parent{ID: transactionEvent.Transaction.ID}, Transaction: &model.Transaction{ ID: transactionEvent.Transaction.ID, Type: transactionEvent.Transaction.Type, diff --git a/processor/otel/metadata.go b/processor/otel/metadata.go index a324a88d282..27170420211 100644 --- a/processor/otel/metadata.go +++ b/processor/otel/metadata.go @@ -21,6 +21,7 @@ import ( "fmt" "net" "regexp" + "strconv" "strings" "go.opentelemetry.io/collector/model/pdata" @@ -132,7 +133,10 @@ func translateResourceMetadata(resource pdata.Resource, out *model.APMEvent) { if out.Labels == nil { out.Labels = make(common.MapStr) } - out.Labels[replaceDots(k)] = ifaceAttributeValue(v) + if out.NumericLabels == nil { + out.NumericLabels = make(common.MapStr) + } + setLabel(replaceDots(k), out, ifaceAttributeValue(v)) } return true }) @@ -200,12 +204,12 @@ func ifaceAttributeValue(v pdata.AttributeValue) interface{} { switch v.Type() { case pdata.AttributeValueTypeString: return truncate(v.StringVal()) + case pdata.AttributeValueTypeBool: + return strconv.FormatBool(v.BoolVal()) case pdata.AttributeValueTypeInt: - return v.IntVal() + return float64(v.IntVal()) case pdata.AttributeValueTypeDouble: return v.DoubleVal() - case pdata.AttributeValueTypeBool: - return v.BoolVal() case pdata.AttributeValueTypeArray: return ifaceAnyValueArray(v.ArrayVal()) } @@ -220,8 +224,31 @@ func ifaceAnyValueArray(array pdata.AnyValueArray) []interface{} { return values } -// initEventLabels initializes an event-specific label map, either making a copy -// of commonLabels if it is non-nil, or otherwise creating a new map. -func initEventLabels(commonLabels common.MapStr) common.MapStr { - return commonLabels.Clone() +// initEventLabels initializes an event-specific labels from an event. +func initEventLabels(e *model.APMEvent) { + e.Labels = e.Labels.Clone() + e.NumericLabels = e.NumericLabels.Clone() +} + +func setLabel(key string, event *model.APMEvent, v interface{}) { + switch v := v.(type) { + case string: + event.Labels[key] = v + case bool: + event.Labels[key] = strconv.FormatBool(v) + case float64: + event.NumericLabels[key] = v + case int64: + event.NumericLabels[key] = float64(v) + case []interface{}: + if len(v) == 0 { + return + } + switch v[0].(type) { + case string: + event.Labels[key] = v + case float64: + event.NumericLabels[key] = v + } + } } diff --git a/processor/otel/metadata_test.go b/processor/otel/metadata_test.go index 2a43c9344d7..a5253c83d72 100644 --- a/processor/otel/metadata_test.go +++ b/processor/otel/metadata_test.go @@ -222,8 +222,10 @@ func TestResourceLabels(t *testing.T) { }) assert.Equal(t, common.MapStr{ "string_array": []interface{}{"abc", "def"}, - "int_array": []interface{}{int64(123), int64(456)}, }, metadata.Labels) + assert.Equal(t, common.MapStr{ + "int_array": []interface{}{float64(123), float64(456)}, + }, metadata.NumericLabels) } func transformResourceMetadata(t *testing.T, resourceAttrs map[string]pdata.AttributeValue) model.APMEvent { diff --git a/processor/otel/metrics.go b/processor/otel/metrics.go index b0be29dcbb5..7a6f5ff5c41 100644 --- a/processor/otel/metrics.go +++ b/processor/otel/metrics.go @@ -108,11 +108,17 @@ func (c *Consumer) convertInstrumentationLibraryMetrics( event.Timestamp = key.timestamp.Add(timeDelta) event.Metricset = &model.Metricset{Samples: ms.samples} if ms.attributes.Len() > 0 { - event.Labels = initEventLabels(event.Labels) + initEventLabels(&event) ms.attributes.Range(func(k string, v pdata.AttributeValue) bool { - event.Labels[k] = ifaceAttributeValue(v) + setLabel(k, &event, ifaceAttributeValue(v)) return true }) + if len(event.Labels) == 0 { + event.Labels = nil + } + if len(event.NumericLabels) == 0 { + event.NumericLabels = nil + } } *out = append(*out, event) } diff --git a/processor/otel/test_approved/jaeger_sampling_rate.approved.json b/processor/otel/test_approved/jaeger_sampling_rate.approved.json index 9c2ecfd13e5..e339d4d4e63 100644 --- a/processor/otel/test_approved/jaeger_sampling_rate.approved.json +++ b/processor/otel/test_approved/jaeger_sampling_rate.approved.json @@ -84,9 +84,11 @@ "hostname": "host-abc" }, "labels": { - "sampler_param": 2, "sampler_type": "ratelimiting" }, + "numeric_labels": { + "sampler_param": 2 + }, "processor": { "event": "transaction", "name": "transaction" diff --git a/processor/otel/test_approved/span_jaeger_http.approved.json b/processor/otel/test_approved/span_jaeger_http.approved.json index f33efad5ab3..a210bdd0eb7 100644 --- a/processor/otel/test_approved/span_jaeger_http.approved.json +++ b/processor/otel/test_approved/span_jaeger_http.approved.json @@ -26,11 +26,13 @@ }, "labels": { "component": "foo", - "double_a": 14.65, - "hasErrors": true, - "int_a": 148, + "hasErrors": "true", "string_a_b": "some note" }, + "numeric_labels": { + "double_a": 14.65, + "int_a": 148 + }, "parent": { "id": "0000000058585858" }, @@ -371,7 +373,7 @@ }, "labels": { "event": "baggage", - "isValid": false + "isValid": "false" }, "parent": { "id": "0000000058585858" diff --git a/processor/otel/test_approved/transaction_jaeger_full.approved.json b/processor/otel/test_approved/transaction_jaeger_full.approved.json index 225ad2316df..74ff9021061 100644 --- a/processor/otel/test_approved/transaction_jaeger_full.approved.json +++ b/processor/otel/test_approved/transaction_jaeger_full.approved.json @@ -22,12 +22,14 @@ "version": "1.1" }, "labels": { - "bool_a": true, + "bool_a": "true", "component": "foo", - "double_a": 14.65, - "int_a": 148, "string_a_b": "some note" }, + "numeric_labels": { + "double_a": 14.65, + "int_a": 148 + }, "processor": { "event": "transaction", "name": "transaction" @@ -431,7 +433,7 @@ }, "labels": { "event": "baggage", - "isValid": false + "isValid": "false" }, "processor": { "event": "log", diff --git a/processor/otel/traces.go b/processor/otel/traces.go index 6f515f158c2..7b8fc5c84c2 100644 --- a/processor/otel/traces.go +++ b/processor/otel/traces.go @@ -50,7 +50,6 @@ import ( semconv "go.opentelemetry.io/collector/model/semconv/v1.5.0" "google.golang.org/grpc/codes" - "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/apm-server/datastreams" @@ -203,7 +202,7 @@ func (c *Consumer) convertSpan( name := otelSpan.Name() spanID := otelSpan.SpanID().HexString() event := baseEvent - event.Labels = initEventLabels(event.Labels) + initEventLabels(&event) event.Timestamp = startTime.Add(timeDelta) event.Trace.ID = otelSpan.TraceID().HexString() event.Event.Duration = duration @@ -228,12 +227,16 @@ func (c *Consumer) convertSpan( if len(event.Labels) == 0 { event.Labels = nil } + if len(event.NumericLabels) == 0 { + event.NumericLabels = nil + } *out = append(*out, event) events := otelSpan.Events() - event.Labels = baseEvent.Labels // only copy common labels to span events - event.Event.Outcome = "" // don't set event.outcome for span events - event.Destination = model.Destination{} // don't set destination for span events + event.Labels = baseEvent.Labels // only copy common labels to span events + event.NumericLabels = baseEvent.NumericLabels // only copy common labels to span events + event.Event.Outcome = "" // don't set event.outcome for span events + event.Destination = model.Destination{} // don't set destination for span events for i := 0; i < events.Len(); i++ { *out = append(*out, convertSpanEvent(logger, events.At(i), event, timeDelta)) } @@ -284,11 +287,11 @@ func TranslateTransaction( k := replaceDots(kDots) switch v.Type() { case pdata.AttributeValueTypeArray: - event.Labels[k] = ifaceAnyValueArray(v.ArrayVal()) + setLabel(k, event, ifaceAttributeValue(v)) case pdata.AttributeValueTypeBool: - event.Labels[k] = v.BoolVal() + setLabel(k, event, ifaceAttributeValue(v)) case pdata.AttributeValueTypeDouble: - event.Labels[k] = v.DoubleVal() + setLabel(k, event, ifaceAttributeValue(v)) case pdata.AttributeValueTypeInt: switch kDots { case semconv.AttributeHTTPStatusCode: @@ -302,7 +305,7 @@ func TranslateTransaction( case "rpc.grpc.status_code": event.Transaction.Result = codes.Code(v.IntVal()).String() default: - event.Labels[k] = v.IntVal() + setLabel(k, event, ifaceAttributeValue(v)) } case pdata.AttributeValueTypeMap: case pdata.AttributeValueTypeString: @@ -455,7 +458,7 @@ func TranslateTransaction( if samplerType != (pdata.AttributeValue{}) { // The client has reported its sampling rate, so we can use it to extrapolate span metrics. - parseSamplerAttributes(samplerType, samplerParam, &event.Transaction.RepresentativeCount, event.Labels) + parseSamplerAttributes(samplerType, samplerParam, event) } else { event.Transaction.RepresentativeCount = 1 } @@ -525,11 +528,11 @@ func TranslateSpan(spanKind pdata.SpanKind, attributes pdata.AttributeMap, event k := replaceDots(kDots) switch v.Type() { case pdata.AttributeValueTypeArray: - event.Labels[k] = ifaceAnyValueArray(v.ArrayVal()) + setLabel(k, event, ifaceAnyValueArray(v.ArrayVal())) case pdata.AttributeValueTypeBool: - event.Labels[k] = v.BoolVal() + setLabel(k, event, strconv.FormatBool(v.BoolVal())) case pdata.AttributeValueTypeDouble: - event.Labels[k] = v.DoubleVal() + setLabel(k, event, v.DoubleVal()) case pdata.AttributeValueTypeInt: switch kDots { case "http.status_code": @@ -541,7 +544,7 @@ func TranslateSpan(spanKind pdata.SpanKind, attributes pdata.AttributeMap, event case "rpc.grpc.status_code": // Ignored for spans. default: - event.Labels[k] = v.IntVal() + setLabel(k, event, v.IntVal()) } case pdata.AttributeValueTypeString: stringval := truncate(v.StringVal()) @@ -782,26 +785,31 @@ func TranslateSpan(spanKind pdata.SpanKind, attributes pdata.AttributeMap, event if samplerType != (pdata.AttributeValue{}) { // The client has reported its sampling rate, so we can use it to extrapolate transaction metrics. - parseSamplerAttributes(samplerType, samplerParam, &event.Span.RepresentativeCount, event.Labels) + parseSamplerAttributes(samplerType, samplerParam, event) } else { event.Span.RepresentativeCount = 1 } } -func parseSamplerAttributes(samplerType, samplerParam pdata.AttributeValue, representativeCount *float64, labels common.MapStr) { +func parseSamplerAttributes(samplerType, samplerParam pdata.AttributeValue, event *model.APMEvent) { switch samplerType := samplerType.StringVal(); samplerType { case "probabilistic": probability := samplerParam.DoubleVal() if probability > 0 && probability <= 1 { - *representativeCount = 1 / probability + if event.Span != nil { + event.Span.RepresentativeCount = 1 / probability + } + if event.Transaction != nil { + event.Transaction.RepresentativeCount = 1 / probability + } } default: - labels["sampler_type"] = samplerType + event.Labels["sampler_type"] = samplerType switch samplerParam.Type() { case pdata.AttributeValueTypeBool: - labels["sampler_param"] = samplerParam.BoolVal() + event.Labels["sampler_param"] = strconv.FormatBool(samplerParam.BoolVal()) case pdata.AttributeValueTypeDouble: - labels["sampler_param"] = samplerParam.DoubleVal() + event.NumericLabels["sampler_param"] = samplerParam.DoubleVal() } } } @@ -813,14 +821,14 @@ func convertSpanEvent( timeDelta time.Duration, ) model.APMEvent { event := parent - event.Labels = initEventLabels(event.Labels) + initEventLabels(&event) event.Transaction = nil event.Span = nil event.Timestamp = spanEvent.Timestamp().AsTime().Add(timeDelta) isJaeger := strings.HasPrefix(parent.Agent.Name, "Jaeger") if isJaeger { - event.Error = convertJaegerErrorSpanEvent(logger, spanEvent, event.Labels) + event.Error = convertJaegerErrorSpanEvent(logger, spanEvent, &event) } else if spanEvent.Name() == "exception" { // Translate exception span events to errors. // @@ -840,7 +848,7 @@ func convertSpanEvent( case "exception.escaped": exceptionEscaped = v.BoolVal() default: - event.Labels[replaceDots(k)] = ifaceAttributeValue(v) + setLabel(replaceDots(k), &event, ifaceAttributeValue(v)) } return true }) @@ -864,14 +872,14 @@ func convertSpanEvent( event.DataStream.Type = datastreams.LogsType event.Message = spanEvent.Name() spanEvent.Attributes().Range(func(k string, v pdata.AttributeValue) bool { - event.Labels[replaceDots(k)] = ifaceAttributeValue(v) + setLabel(replaceDots(k), &event, ifaceAttributeValue(v)) return true }) } return event } -func convertJaegerErrorSpanEvent(logger *logp.Logger, event pdata.SpanEvent, labels common.MapStr) *model.Error { +func convertJaegerErrorSpanEvent(logger *logp.Logger, event pdata.SpanEvent, apmEvent *model.APMEvent) *model.Error { var isError bool var exMessage, exType string logMessage := event.Name() @@ -904,7 +912,7 @@ func convertJaegerErrorSpanEvent(logger *logp.Logger, event pdata.SpanEvent, lab case "level": isError = stringval == "error" default: - labels[replaceDots(k)] = ifaceAttributeValue(v) + setLabel(replaceDots(k), apmEvent, ifaceAttributeValue(v)) } return true }) diff --git a/processor/otel/traces_test.go b/processor/otel/traces_test.go index 11762896016..eed3e63aab9 100644 --- a/processor/otel/traces_test.go +++ b/processor/otel/traces_test.go @@ -594,23 +594,43 @@ func TestArrayLabels(t *testing.T) { boolArray.ArrayVal().AppendEmpty().SetBoolVal(false) boolArray.ArrayVal().AppendEmpty().SetBoolVal(true) + intArray := pdata.NewAttributeValueArray() + intArray.ArrayVal().AppendEmpty().SetIntVal(1234) + intArray.ArrayVal().AppendEmpty().SetIntVal(5678) + + floatArray := pdata.NewAttributeValueArray() + floatArray.ArrayVal().AppendEmpty().SetDoubleVal(1234.5678) + floatArray.ArrayVal().AppendEmpty().SetDoubleVal(9123.234123123) + txEvent := transformTransactionWithAttributes(t, map[string]pdata.AttributeValue{ "string_array": stringArray, "bool_array": boolArray, + "int_array": intArray, + "float_array": floatArray, }) assert.Equal(t, common.MapStr{ - "bool_array": []interface{}{false, true}, + "bool_array": []interface{}{"false", "true"}, "string_array": []interface{}{"string1", "string2"}, }, txEvent.Labels) + assert.Equal(t, common.MapStr{ + "int_array": []interface{}{float64(1234), float64(5678)}, + "float_array": []interface{}{1234.5678, 9123.234123123}, + }, txEvent.NumericLabels) spanEvent := transformSpanWithAttributes(t, map[string]pdata.AttributeValue{ "string_array": stringArray, "bool_array": boolArray, + "int_array": intArray, + "float_array": floatArray, }) assert.Equal(t, common.MapStr{ - "bool_array": []interface{}{false, true}, + "bool_array": []interface{}{"false", "true"}, "string_array": []interface{}{"string1", "string2"}, }, spanEvent.Labels) + assert.Equal(t, common.MapStr{ + "int_array": []interface{}{float64(1234), float64(5678)}, + "float_array": []interface{}{1234.5678, 9123.234123123}, + }, spanEvent.NumericLabels) } func TestConsumeTracesExportTimestamp(t *testing.T) { diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationEvents.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationEvents.approved.json index a96f0296fed..5402afb5c2c 100644 --- a/processor/stream/test_approved_es_documents/testIntakeIntegrationEvents.approved.json +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationEvents.approved.json @@ -203,9 +203,11 @@ } }, "labels": { - "ab_testing": true, + "ab_testing": "true", "group": "experimental", - "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8", + "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8" + }, + "numeric_labels": { "segment": 5 }, "parent": { @@ -322,8 +324,10 @@ } }, "labels": { - "ab_testing": true, - "group": "experimental", + "ab_testing": "true", + "group": "experimental" + }, + "numeric_labels": { "segment": 5 }, "parent": { @@ -506,9 +510,11 @@ } }, "labels": { - "ab_testing": true, + "ab_testing": "true", "group": "experimental", - "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8", + "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8" + }, + "numeric_labels": { "segment": 5 }, "parent": { @@ -629,11 +635,13 @@ } }, "labels": { - "ab_testing": true, - "code": 200, + "ab_testing": "true", "group": "experimental", - "segment": 5, - "success": true + "success": "true" + }, + "numeric_labels": { + "code": 200, + "segment": 5 }, "process": { "args": [ diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationMetricsets.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationMetricsets.approved.json index 61b9e9058ea..8ab112fb308 100644 --- a/processor/stream/test_approved_es_documents/testIntakeIntegrationMetricsets.approved.json +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationMetricsets.approved.json @@ -10,10 +10,12 @@ "ip": "192.0.0.1" }, "labels": { - "code": 200, "some": "abc", - "success": true, - "tag1": "one", + "success": "true", + "tag1": "one" + }, + "numeric_labels": { + "code": 200, "tag2": 2 }, "process": { @@ -61,7 +63,9 @@ "ip": "192.0.0.1" }, "labels": { - "tag1": "one", + "tag1": "one" + }, + "numeric_labels": { "tag2": 2 }, "process": { @@ -96,7 +100,9 @@ "ip": "192.0.0.1" }, "labels": { - "tag1": "one", + "tag1": "one" + }, + "numeric_labels": { "tag2": 2 }, "process": { @@ -133,7 +139,9 @@ "ip": "192.0.0.1" }, "labels": { - "tag1": "one", + "tag1": "one" + }, + "numeric_labels": { "tag2": 2 }, "process": { @@ -182,8 +190,7 @@ "ip": "192.0.0.1" }, "labels": { - "tag1": "one", - "tag2": 2 + "tag1": "one" }, "latency_distribution": { "counts": [ @@ -197,6 +204,9 @@ 3.3 ] }, + "numeric_labels": { + "tag2": 2 + }, "process": { "pid": 1234 }, diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationOpenTelemetryBridge.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationOpenTelemetryBridge.approved.json index 55603cebc62..9380219b729 100644 --- a/processor/stream/test_approved_es_documents/testIntakeIntegrationOpenTelemetryBridge.approved.json +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationOpenTelemetryBridge.approved.json @@ -54,16 +54,18 @@ } }, "labels": { + "string_array": [ + "a", + "b" + ], + "tag1": "one" + }, + "numeric_labels": { "double_value": 123.456, "int_array": [ 1, 2 ], - "string_array": [ - "a", - "b" - ], - "tag1": "one", "tag2": 2 }, "process": { @@ -195,16 +197,18 @@ } }, "labels": { + "string_array": [ + "a", + "b" + ], + "tag1": "one" + }, + "numeric_labels": { "double_value": 123.456, "int_array": [ 1, 2 ], - "string_array": [ - "a", - "b" - ], - "tag1": "one", "tag2": 2 }, "parent": { diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationSpans.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationSpans.approved.json index b5e280abce2..780e1db000d 100644 --- a/processor/stream/test_approved_es_documents/testIntakeIntegrationSpans.approved.json +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationSpans.approved.json @@ -288,9 +288,11 @@ }, "labels": { "tag1": "value1", + "tag4": "true" + }, + "numeric_labels": { "tag2": 123, - "tag3": 12.34, - "tag4": true + "tag3": 12.34 }, "parent": { "id": "abcdefabcdef7890" diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactions.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactions.approved.json index ab335139f78..21c7f2357a6 100644 --- a/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactions.approved.json +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactions.approved.json @@ -54,7 +54,9 @@ } }, "labels": { - "tag1": "one", + "tag1": "one" + }, + "numeric_labels": { "tag2": 2 }, "parent": { @@ -232,9 +234,11 @@ "labels": { "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8", "tag1": "one", + "tag4": "false" + }, + "numeric_labels": { "tag2": 12, - "tag3": 12.45, - "tag4": false + "tag3": 12.45 }, "process": { "args": [ @@ -381,7 +385,9 @@ } }, "labels": { - "tag1": "one", + "tag1": "one" + }, + "numeric_labels": { "tag2": 2 }, "process": { @@ -515,7 +521,9 @@ } }, "labels": { - "tag1": "one", + "tag1": "one" + }, + "numeric_labels": { "tag2": 2 }, "parent": { @@ -667,7 +675,9 @@ } }, "labels": { - "tag1": "one", + "tag1": "one" + }, + "numeric_labels": { "tag2": 2 }, "process": { diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactionsHugeTraces.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactionsHugeTraces.approved.json index 32d51fba806..2453bdf6352 100644 --- a/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactionsHugeTraces.approved.json +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactionsHugeTraces.approved.json @@ -116,9 +116,11 @@ "labels": { "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8", "tag1": "one", + "tag4": "false" + }, + "numeric_labels": { "tag2": 12, - "tag3": 12.45, - "tag4": false + "tag3": 12.45 }, "process": { "args": [ diff --git a/systemtest/approvals/TestApprovedMetrics.approved.json b/systemtest/approvals/TestApprovedMetrics.approved.json index 248b023a54f..87282576472 100644 --- a/systemtest/approvals/TestApprovedMetrics.approved.json +++ b/systemtest/approvals/TestApprovedMetrics.approved.json @@ -21,10 +21,12 @@ "ip": "127.0.0.1" }, "labels": { - "tag1": "one", - "tag2": 2 + "tag1": "one" }, "metricset.name": "app", + "numeric_labels": { + "tag2": 2 + }, "observer": { "ephemeral_id": "dynamic", "hostname": "dynamic", @@ -75,10 +77,12 @@ "ip": "127.0.0.1" }, "labels": { - "tag1": "one", - "tag2": 2 + "tag1": "one" }, "metricset.name": "app", + "numeric_labels": { + "tag2": 2 + }, "observer": { "ephemeral_id": "dynamic", "hostname": "dynamic", @@ -131,10 +135,12 @@ "ip": "127.0.0.1" }, "labels": { - "tag1": "one", - "tag2": 2 + "tag1": "one" }, "metricset.name": "app", + "numeric_labels": { + "tag2": 2 + }, "observer": { "ephemeral_id": "dynamic", "hostname": "dynamic", @@ -193,8 +199,7 @@ "ip": "127.0.0.1" }, "labels": { - "tag1": "one", - "tag2": 2 + "tag1": "one" }, "latency_distribution": { "counts": [ @@ -209,6 +214,9 @@ ] }, "metricset.name": "app", + "numeric_labels": { + "tag2": 2 + }, "observer": { "ephemeral_id": "dynamic", "hostname": "dynamic", @@ -259,13 +267,15 @@ "ip": "127.0.0.1" }, "labels": { - "code": 200, "some": "abc", - "success": true, - "tag1": "one", - "tag2": 2 + "success": "true", + "tag1": "one" }, "metricset.name": "span_breakdown", + "numeric_labels": { + "code": 200, + "tag2": 2 + }, "observer": { "ephemeral_id": "dynamic", "hostname": "dynamic", diff --git a/systemtest/approvals/TestOTLPGRPCTraces.approved.json b/systemtest/approvals/TestOTLPGRPCTraces.approved.json index fef45090d68..4864303f83c 100644 --- a/systemtest/approvals/TestOTLPGRPCTraces.approved.json +++ b/systemtest/approvals/TestOTLPGRPCTraces.approved.json @@ -16,9 +16,22 @@ "resource_attribute_array": [ "a", "b" + ], + "resource_attribute_bool": "true", + "resource_attribute_bool_array": [ + "true", + "false" ] }, "message": "a_span_event", + "numeric_labels": { + "resource_attribute_float": 123456.789, + "resource_attribute_float_array": [ + 123456.789, + 987654321.1234568 + ], + "resource_attribute_int": 123456 + }, "observer": { "ephemeral_id": "dynamic", "hostname": "dynamic", @@ -66,12 +79,25 @@ "a", "b" ], + "resource_attribute_bool": "true", + "resource_attribute_bool_array": [ + "true", + "false" + ], "span_attribute_array": [ "a", "b", "c" ] }, + "numeric_labels": { + "resource_attribute_float": 123456.789, + "resource_attribute_float_array": [ + 123456.789, + 987654321.1234568 + ], + "resource_attribute_int": 123456 + }, "observer": { "ephemeral_id": "dynamic", "hostname": "dynamic", diff --git a/systemtest/otlp_test.go b/systemtest/otlp_test.go index 347e0d68315..f7efc9bf465 100644 --- a/systemtest/otlp_test.go +++ b/systemtest/otlp_test.go @@ -73,6 +73,11 @@ func TestOTLPGRPCTraces(t *testing.T) { resource, err := resource.Merge(resource.Default(), sdkresource.NewSchemaless( attribute.StringSlice("resource_attribute_array", []string{"a", "b"}), + attribute.Bool("resource_attribute_bool", true), + attribute.BoolSlice("resource_attribute_bool_array", []bool{true, false}), + attribute.Float64("resource_attribute_float", 123456.789), + attribute.Float64Slice("resource_attribute_float_array", []float64{123456.789, 987654321.123456789}), + attribute.Int64("resource_attribute_int", 123456), )) require.NoError(t, err) diff --git a/testdata/jaeger/batch_0.approved.json b/testdata/jaeger/batch_0.approved.json index b102649bc54..d81feb38793 100644 --- a/testdata/jaeger/batch_0.approved.json +++ b/testdata/jaeger/batch_0.approved.json @@ -16,12 +16,14 @@ }, "labels": { "as": "thrift", - "peer_ipv4": 2130706433, - "peer_port": 50535, "peer_service": "driver-client", - "sampler_param": true, + "sampler_param": "true", "sampler_type": "const" }, + "numeric_labels": { + "peer_ipv4": 2130706433, + "peer_port": 50535 + }, "processor": { "event": "transaction", "name": "transaction" @@ -262,7 +264,9 @@ }, "labels": { "event": "Search successful", - "level": "info", + "level": "info" + }, + "numeric_labels": { "num_drivers": 10 }, "processor": {