From de1865204955c139b4a527ef1cc2b165398e2950 Mon Sep 17 00:00:00 2001 From: Paulin Todev Date: Fri, 27 Oct 2023 20:13:58 +0100 Subject: [PATCH] Use raw strings for regexes in docs --- .../processor/attributes/attributes_test.go | 4 +- .../processor/transform/transform_test.go | 110 +++++++++--------- .../otelcol.processor.attributes.md | 6 +- .../components/otelcol.processor.transform.md | 93 ++++++++------- 4 files changed, 114 insertions(+), 99 deletions(-) diff --git a/component/otelcol/processor/attributes/attributes_test.go b/component/otelcol/processor/attributes/attributes_test.go index fc860dd7ac12..174c0aedbded 100644 --- a/component/otelcol/processor/attributes/attributes_test.go +++ b/component/otelcol/processor/attributes/attributes_test.go @@ -18,6 +18,8 @@ import ( "go.opentelemetry.io/collector/client" ) +const backtick = "`" + // These are tests for SeverityLevel and not for the attributes processor as a whole. // However, because Otel's LogSeverityNumberMatchProperties structure is internal // we are not able ot test it directly. @@ -211,7 +213,7 @@ func Test_RegexExtract(t *testing.T) { cfg := ` action { key = "user_key" - pattern = "\\/api\\/v1\\/document\\/(?P.*)\\/update\\/(?P.*)$" + pattern = ` + backtick + `\/api\/v1\/document\/(?P.*)\/update\/(?P.*)$` + backtick + ` action = "extract" } diff --git a/component/otelcol/processor/transform/transform_test.go b/component/otelcol/processor/transform/transform_test.go index 9547d30b24ec..8dac32ce215e 100644 --- a/component/otelcol/processor/transform/transform_test.go +++ b/component/otelcol/processor/transform/transform_test.go @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/require" ) +const backtick = "`" + func TestArguments_UnmarshalRiver(t *testing.T) { tests := []struct { testName string @@ -44,7 +46,7 @@ func TestArguments_UnmarshalRiver(t *testing.T) { context = "span" statements = [ // Accessing a map with a key that does not exist will return nil. - "set(attributes[\"test\"], \"pass\") where attributes[\"test\"] == nil", + ` + backtick + `set(attributes["test"], "pass") where attributes["test"] == nil` + backtick + `, ] } output {} @@ -68,8 +70,8 @@ func TestArguments_UnmarshalRiver(t *testing.T) { trace_statements { context = "resource" statements = [ - "set(attributes[\"namespace\"], attributes[\"k8s.namespace.name\"])", - "delete_key(attributes, \"k8s.namespace.name\")", + ` + backtick + `set(attributes["namespace"], attributes["k8s.namespace.name"])` + backtick + `, + ` + backtick + `delete_key(attributes, "k8s.namespace.name")` + backtick + `, ] } output {} @@ -94,7 +96,7 @@ func TestArguments_UnmarshalRiver(t *testing.T) { trace_statements { context = "resource" statements = [ - "replace_all_patterns(attributes, \"key\", \"k8s\\\\.namespace\\\\.name\", \"namespace\")", + ` + backtick + `replace_all_patterns(attributes, "key", "k8s\\.namespace\\.name", "namespace")` + backtick + `, ] } output {} @@ -118,7 +120,7 @@ func TestArguments_UnmarshalRiver(t *testing.T) { log_statements { context = "log" statements = [ - "set(attributes[\"body\"], body)", + ` + backtick + `set(attributes["body"], body)` + backtick + `, ] } output {} @@ -143,7 +145,7 @@ func TestArguments_UnmarshalRiver(t *testing.T) { context = "resource" statements = [ // The Concat function combines any number of strings, separated by a delimiter. - "set(attributes[\"test\"], Concat([attributes[\"foo\"], attributes[\"bar\"]], \" \"))", + ` + backtick + `set(attributes["test"], Concat([attributes["foo"], attributes["bar"]], " "))` + backtick + `, ] } output {} @@ -167,10 +169,10 @@ func TestArguments_UnmarshalRiver(t *testing.T) { log_statements { context = "log" statements = [ - "merge_maps(cache, ParseJSON(body), \"upsert\") where IsMatch(body, \"^\\\\{\") ", - "set(attributes[\"attr1\"], cache[\"attr1\"])", - "set(attributes[\"attr2\"], cache[\"attr2\"])", - "set(attributes[\"nested.attr3\"], cache[\"nested\"][\"attr3\"])", + ` + backtick + `merge_maps(cache, ParseJSON(body), "upsert") where IsMatch(body, "^\\{")` + backtick + `, + ` + backtick + `set(attributes["attr1"], cache["attr1"])` + backtick + `, + ` + backtick + `set(attributes["attr2"], cache["attr2"])` + backtick + `, + ` + backtick + `set(attributes["nested.attr3"], cache["nested"]["attr3"])` + backtick + `, ] } output {} @@ -181,7 +183,7 @@ func TestArguments_UnmarshalRiver(t *testing.T) { map[string]interface{}{ "context": "log", "statements": []interface{}{ - `merge_maps(cache, ParseJSON(body), "upsert") where IsMatch(body, "^\\{") `, + `merge_maps(cache, ParseJSON(body), "upsert") where IsMatch(body, "^\\{")`, `set(attributes["attr1"], cache["attr1"])`, `set(attributes["attr2"], cache["attr2"])`, `set(attributes["nested.attr3"], cache["nested"]["attr3"])`, @@ -197,57 +199,57 @@ func TestArguments_UnmarshalRiver(t *testing.T) { trace_statements { context = "resource" statements = [ - "keep_keys(attributes, [\"service.name\", \"service.namespace\", \"cloud.region\", \"process.command_line\"])", - "replace_pattern(attributes[\"process.command_line\"], \"password\\\\=[^\\\\s]*(\\\\s?)\", \"password=***\")", - "limit(attributes, 100, [])", - "truncate_all(attributes, 4096)", + ` + backtick + `keep_keys(attributes, ["service.name", "service.namespace", "cloud.region", "process.command_line"])` + backtick + `, + ` + backtick + `replace_pattern(attributes["process.command_line"], "password\\=[^\\s]*(\\s?)", "password=***")` + backtick + `, + ` + backtick + `limit(attributes, 100, [])` + backtick + `, + ` + backtick + `truncate_all(attributes, 4096)` + backtick + `, ] } trace_statements { context = "span" statements = [ - "set(status.code, 1) where attributes[\"http.path\"] == \"/health\"", - "set(name, attributes[\"http.route\"])", - "replace_match(attributes[\"http.target\"], \"/user/*/list/*\", \"/user/{userId}/list/{listId}\")", - "limit(attributes, 100, [])", - "truncate_all(attributes, 4096)", + ` + backtick + `set(status.code, 1) where attributes["http.path"] == "/health"` + backtick + `, + ` + backtick + `set(name, attributes["http.route"])` + backtick + `, + ` + backtick + `replace_match(attributes["http.target"], "/user/*/list/*", "/user/{userId}/list/{listId}")` + backtick + `, + ` + backtick + `limit(attributes, 100, [])` + backtick + `, + ` + backtick + `truncate_all(attributes, 4096)` + backtick + `, ] } metric_statements { context = "resource" statements = [ - "keep_keys(attributes, [\"host.name\"])", - "truncate_all(attributes, 4096)", + ` + backtick + `keep_keys(attributes, ["host.name"])` + backtick + `, + ` + backtick + `truncate_all(attributes, 4096)` + backtick + `, ] } metric_statements { context = "metric" statements = [ - "set(description, \"Sum\") where type == \"Sum\"", + ` + backtick + `set(description, "Sum") where type == "Sum"` + backtick + `, ] } metric_statements { context = "datapoint" statements = [ - "limit(attributes, 100, [\"host.name\"])", - "truncate_all(attributes, 4096)", - "convert_sum_to_gauge() where metric.name == \"system.processes.count\"", - "convert_gauge_to_sum(\"cumulative\", false) where metric.name == \"prometheus_metric\"", + ` + backtick + `limit(attributes, 100, ["host.name"])` + backtick + `, + ` + backtick + `truncate_all(attributes, 4096)` + backtick + `, + ` + backtick + `convert_sum_to_gauge() where metric.name == "system.processes.count"` + backtick + `, + ` + backtick + `convert_gauge_to_sum("cumulative", false) where metric.name == "prometheus_metric"` + backtick + `, ] } log_statements { context = "resource" statements = [ - "keep_keys(attributes, [\"service.name\", \"service.namespace\", \"cloud.region\"])", + ` + backtick + `keep_keys(attributes, ["service.name", "service.namespace", "cloud.region"])` + backtick + `, ] } log_statements { context = "log" statements = [ - "set(severity_text, \"FAIL\") where body == \"request failed\"", - "replace_all_matches(attributes, \"/user/*/list/*\", \"/user/{userId}/list/{listId}\")", - "replace_all_patterns(attributes, \"value\", \"/account/\\\\d{4}\", \"/account/{accountId}\")", - "set(body, attributes[\"http.route\"])", + ` + backtick + `set(severity_text, "FAIL") where body == "request failed"` + backtick + `, + ` + backtick + `replace_all_matches(attributes, "/user/*/list/*", "/user/{userId}/list/{listId}")` + backtick + `, + ` + backtick + `replace_all_patterns(attributes, "value", "/account/\\d{4}", "/account/{accountId}")` + backtick + `, + ` + backtick + `set(body, attributes["http.route"])` + backtick + `, ] } output {} @@ -324,40 +326,40 @@ func TestArguments_UnmarshalRiver(t *testing.T) { trace_statements { context = "span" statements = [ - "set(name, \"bear\") where attributes[\"http.path\"] == \"/animal\"", - "keep_keys(attributes, [\"http.method\", \"http.path\"])", + ` + backtick + `set(name, "bear") where attributes["http.path"] == "/animal"` + backtick + `, + ` + backtick + `keep_keys(attributes, ["http.method", "http.path"])` + backtick + `, ] } trace_statements { context = "resource" statements = [ - "set(attributes[\"name\"], \"bear\")", + ` + backtick + `set(attributes["name"], "bear")` + backtick + `, ] } metric_statements { context = "datapoint" statements = [ - "set(metric.name, \"bear\") where attributes[\"http.path\"] == \"/animal\"", - "keep_keys(attributes, [\"http.method\", \"http.path\"])", + ` + backtick + `set(metric.name, "bear") where attributes["http.path"] == "/animal"` + backtick + `, + ` + backtick + `keep_keys(attributes, ["http.method", "http.path"])` + backtick + `, ] } metric_statements { context = "resource" statements = [ - "set(attributes[\"name\"], \"bear\")", + ` + backtick + `set(attributes["name"], "bear")` + backtick + `, ] } log_statements { context = "log" statements = [ - "set(body, \"bear\") where attributes[\"http.path\"] == \"/animal\"", - "keep_keys(attributes, [\"http.method\", \"http.path\"])", + ` + backtick + `set(body, "bear") where attributes["http.path"] == "/animal"` + backtick + `, + ` + backtick + `keep_keys(attributes, ["http.method", "http.path"])` + backtick + `, ] } log_statements { context = "resource" statements = [ - "set(attributes[\"name\"], \"bear\")", + ` + backtick + `set(attributes["name"], "bear")` + backtick + `, ] } output {} @@ -425,8 +427,8 @@ func TestArguments_UnmarshalRiver(t *testing.T) { log_statements { context = "log" statements = [ - "set(body, \"bear\" where attributes[\"http.path\"] == \"/animal\"", - "keep_keys(attributes, [\"http.method\", \"http.path\"])", + ` + backtick + `set(body, "bear" where attributes["http.path"] == "/animal"` + backtick + `, + ` + backtick + `keep_keys(attributes, ["http.method", "http.path"])` + backtick + `, ] } output {} @@ -439,8 +441,8 @@ func TestArguments_UnmarshalRiver(t *testing.T) { metric_statements { context = "datapoint" statements = [ - "set(name, \"bear\" where attributes[\"http.path\"] == \"/animal\"", - "keep_keys(attributes, [\"http.method\", \"http.path\"])", + ` + backtick + `set(name, "bear" where attributes["http.path"] == "/animal"` + backtick + `, + ` + backtick + `keep_keys(attributes, ["http.method", "http.path"])` + backtick + `, ] } output {} @@ -453,8 +455,8 @@ func TestArguments_UnmarshalRiver(t *testing.T) { trace_statements { context = "span" statements = [ - "set(name, \"bear\" where attributes[\"http.path\"] == \"/animal\"", - "keep_keys(attributes, [\"http.method\", \"http.path\"])", + ` + backtick + `set(name, "bear" where attributes["http.path"] == "/animal"` + backtick + `, + ` + backtick + `keep_keys(attributes, ["http.method", "http.path"])` + backtick + `, ] } output {} @@ -467,8 +469,8 @@ func TestArguments_UnmarshalRiver(t *testing.T) { log_statements { context = "log" statements = [ - "set(body, \"bear\") where attributes[\"http.path\"] == \"/animal\"", - "not_a_function(attributes, [\"http.method\", \"http.path\"])", + ` + backtick + `set(body, "bear") where attributes["http.path"] == "/animal"` + backtick + `, + ` + backtick + `not_a_function(attributes, ["http.method", "http.path"])` + backtick + `, ] } output {} @@ -481,8 +483,8 @@ func TestArguments_UnmarshalRiver(t *testing.T) { metric_statements { context = "datapoint" statements = [ - "set(metric.name, \"bear\") where attributes[\"http.path\"] == \"/animal\"", - "not_a_function(attributes, [\"http.method\", \"http.path\"])", + ` + backtick + `set(metric.name, "bear") where attributes["http.path"] == "/animal"` + backtick + `, + ` + backtick + `not_a_function(attributes, ["http.method", "http.path"])` + backtick + `, ] } output {} @@ -495,8 +497,8 @@ func TestArguments_UnmarshalRiver(t *testing.T) { trace_statements { context = "span" statements = [ - "set(name, \"bear\") where attributes[\"http.path\"] == \"/animal\"", - "not_a_function(attributes, [\"http.method\", \"http.path\"])", + ` + backtick + `set(name, "bear") where attributes["http.path"] == "/animal"` + backtick + `, + ` + backtick + `not_a_function(attributes, ["http.method", "http.path"])` + backtick + `, ] } output {} @@ -509,7 +511,7 @@ func TestArguments_UnmarshalRiver(t *testing.T) { trace_statements { context = "test" statements = [ - "set(name, \"bear\") where attributes[\"http.path\"] == \"/animal\"", + ` + backtick + `set(name, "bear") where attributes["http.path"] == "/animal"` + backtick + `, ] } output {} diff --git a/docs/sources/flow/reference/components/otelcol.processor.attributes.md b/docs/sources/flow/reference/components/otelcol.processor.attributes.md index 4cb61a17de9d..39af8475dd3f 100644 --- a/docs/sources/flow/reference/components/otelcol.processor.attributes.md +++ b/docs/sources/flow/reference/components/otelcol.processor.attributes.md @@ -294,11 +294,15 @@ otelcol.processor.attributes "default" { // then the following attributes will be inserted: // new_example_user_key: 12345678 // version: v1 + // // Note: Similar to the Span Processor, if a target key already exists, // it will be updated. + // + // Note: The regex pattern is enclosed in backticks instead of quotation marks. + // This constitutes a raw River string, and lets us avoid the need to escape backslash characters. action { key = "example_user_key" - pattern = "\\/api\\/v1\\/document\\/(?P.*)\\/update\\/(?P.*)$" + pattern = `\/api\/v1\/document\/(?P.*)\/update\/(?P.*)$` action = "extract" } diff --git a/docs/sources/flow/reference/components/otelcol.processor.transform.md b/docs/sources/flow/reference/components/otelcol.processor.transform.md index b54755710a04..d5754d57e22c 100644 --- a/docs/sources/flow/reference/components/otelcol.processor.transform.md +++ b/docs/sources/flow/reference/components/otelcol.processor.transform.md @@ -282,7 +282,7 @@ otelcol.processor.transform "default" { context = "span" statements = [ // Accessing a map with a key that does not exist will return nil. - "set(attributes[\"test\"], \"pass\") where attributes[\"test\"] == nil", + `set(attributes["test"], "pass") where attributes["test"] == nil`, ] } @@ -294,7 +294,9 @@ otelcol.processor.transform "default" { } ``` -Each `"` is [escaped][river-strings] with `\"` inside the River string. +Each statement is enclosed in backticks instead of quotation marks. +This constitutes a [raw string][river-raw-strings], and lets us avoid the need to escape +each `"` with a `\"` inside a [normal][river-strings] River string. ### Rename a resource attribute @@ -308,8 +310,8 @@ otelcol.processor.transform "default" { trace_statements { context = "resource" statements = [ - "set(attributes[\"namespace\"], attributes[\"k8s.namespace.name\"])", - "delete_key(attributes, \"k8s.namespace.name\")", + `set(attributes["namespace"], attributes["k8s.namespace.name"])`, + `delete_key(attributes, "k8s.namespace.name")`, ] } @@ -330,7 +332,7 @@ otelcol.processor.transform "default" { trace_statements { context = "resource" statements = [ - "replace_all_patterns(attributes, \"key\", \"k8s\\\\.namespace\\\\.name\", \"namespace\")", + `replace_all_patterns(attributes, "key", "k8s\\.namespace\\.name", "namespace")`, ] } @@ -342,9 +344,9 @@ otelcol.processor.transform "default" { } ``` -Some values in the River string are [escaped][river-strings]: -* `\` is escaped with `\\` -* `"` is escaped with `\"` +Each statement is enclosed in backticks instead of quotation marks. +This constitutes a [raw string][river-raw-strings], and lets us avoid the need to escape +each `"` with a `\"`, and each `\` with a `\\` inside a [normal][river-strings] River string. ### Create an attribute from the contents of a log body @@ -357,7 +359,7 @@ otelcol.processor.transform "default" { log_statements { context = "log" statements = [ - "set(attributes[\"body\"], body)", + `set(attributes["body"], body)`, ] } @@ -369,7 +371,9 @@ otelcol.processor.transform "default" { } ``` -Each `"` is [escaped][river-strings] with `\"` inside the River string. +Each statement is enclosed in backticks instead of quotation marks. +This constitutes a [raw string][river-raw-strings], and lets us avoid the need to escape +each `"` with a `\"` inside a [normal][river-strings] River string. ### Combine two attributes @@ -383,7 +387,7 @@ otelcol.processor.transform "default" { context = "resource" statements = [ // The Concat function combines any number of strings, separated by a delimiter. - "set(attributes[\"test\"], Concat([attributes[\"service.name\"], attributes[\"service.version\"]], \" \"))", + `set(attributes["test"], Concat([attributes["foo"], attributes["bar"]], " "))`, ] } @@ -395,7 +399,9 @@ otelcol.processor.transform "default" { } ``` -Each `"` is [escaped][river-strings] with `\"` inside the River string. +Each statement is enclosed in backticks instead of quotation marks. +This constitutes a [raw string][river-raw-strings], and lets us avoid the need to escape +each `"` with a `\"` inside a [normal][river-strings] River string. ### Parsing JSON logs @@ -424,16 +430,16 @@ otelcol.processor.transform "default" { statements = [ // Parse body as JSON and merge the resulting map with the cache map, ignoring non-json bodies. // cache is a field exposed by OTTL that is a temporary storage place for complex operations. - "merge_maps(cache, ParseJSON(body), \"upsert\") where IsMatch(body, \"^\\\\{\") ", + `merge_maps(cache, ParseJSON(body), "upsert") where IsMatch(body, "^\\{")`, // Set attributes using the values merged into cache. // If the attribute doesn't exist in cache then nothing happens. - "set(attributes[\"attr1\"], cache[\"attr1\"])", - "set(attributes[\"attr2\"], cache[\"attr2\"])", + `set(attributes["attr1"], cache["attr1"])`, + `set(attributes["attr2"], cache["attr2"])`, // To access nested maps you can chain index ([]) operations. // If nested or attr3 do no exist in cache then nothing happens. - "set(attributes[\"nested.attr3\"], cache[\"nested\"][\"attr3\"])", + `set(attributes["nested.attr3"], cache["nested"]["attr3"])`, ] } @@ -445,9 +451,9 @@ otelcol.processor.transform "default" { } ``` -Some values in the River strings are [escaped][river-strings]: -* `\` is escaped with `\\` -* `"` is escaped with `\"` +Each statement is enclosed in backticks instead of quotation marks. +This constitutes a [raw string][river-raw-strings], and lets us avoid the need to escape +each `"` with a `\"`, and each `\` with a `\\` inside a [normal][river-strings] River string. ### Various transformations of attributes and status codes @@ -472,63 +478,63 @@ otelcol.processor.transform "default" { trace_statements { context = "resource" statements = [ - "keep_keys(attributes, [\"service.name\", \"service.namespace\", \"cloud.region\", \"process.command_line\"])", - "replace_pattern(attributes[\"process.command_line\"], \"password\\\\=[^\\\\s]*(\\\\s?)\", \"password=***\")", - "limit(attributes, 100, [])", - "truncate_all(attributes, 4096)", + `keep_keys(attributes, ["service.name", "service.namespace", "cloud.region", "process.command_line"])`, + `replace_pattern(attributes["process.command_line"], "password\\=[^\\s]*(\\s?)", "password=***")`, + `limit(attributes, 100, [])`, + `truncate_all(attributes, 4096)`, ] } trace_statements { context = "span" statements = [ - "set(status.code, 1) where attributes[\"http.path\"] == \"/health\"", - "set(name, attributes[\"http.route\"])", - "replace_match(attributes[\"http.target\"], \"/user/*/list/*\", \"/user/{userId}/list/{listId}\")", - "limit(attributes, 100, [])", - "truncate_all(attributes, 4096)", + `set(status.code, 1) where attributes["http.path"] == "/health"`, + `set(name, attributes["http.route"])`, + `replace_match(attributes["http.target"], "/user/*/list/*", "/user/{userId}/list/{listId}")`, + `limit(attributes, 100, [])`, + `truncate_all(attributes, 4096)`, ] } metric_statements { context = "resource" statements = [ - "keep_keys(attributes, [\"host.name\"])", - "truncate_all(attributes, 4096)", + `keep_keys(attributes, ["host.name"])`, + `truncate_all(attributes, 4096)`, ] } metric_statements { context = "metric" statements = [ - "set(description, \"Sum\") where type == \"Sum\"", + `set(description, "Sum") where type == "Sum"`, ] } metric_statements { context = "datapoint" statements = [ - "limit(attributes, 100, [\"host.name\"])", - "truncate_all(attributes, 4096)", - "convert_sum_to_gauge() where metric.name == \"system.processes.count\"", - "convert_gauge_to_sum(\"cumulative\", false) where metric.name == \"prometheus_metric\"", + `limit(attributes, 100, ["host.name"])`, + `truncate_all(attributes, 4096)`, + `convert_sum_to_gauge() where metric.name == "system.processes.count"`, + `convert_gauge_to_sum("cumulative", false) where metric.name == "prometheus_metric"`, ] } log_statements { context = "resource" statements = [ - "keep_keys(attributes, [\"service.name\", \"service.namespace\", \"cloud.region\"])", + `keep_keys(attributes, ["service.name", "service.namespace", "cloud.region"])`, ] } log_statements { context = "log" statements = [ - "set(severity_text, \"FAIL\") where body == \"request failed\"", - "replace_all_matches(attributes, \"/user/*/list/*\", \"/user/{userId}/list/{listId}\")", - "replace_all_patterns(attributes, \"value\", \"/account/\\\\d{4}\", \"/account/{accountId}\")", - "set(body, attributes[\"http.route\"])", + `set(severity_text, "FAIL") where body == "request failed"`, + `replace_all_matches(attributes, "/user/*/list/*", "/user/{userId}/list/{listId}")`, + `replace_all_patterns(attributes, "value", "/account/\\d{4}", "/account/{accountId}")`, + `set(body, attributes["http.route"])`, ] } @@ -546,11 +552,12 @@ otelcol.exporter.otlp "default" { } ``` -Some values in the River strings are [escaped][river-strings]: -* `\` is escaped with `\\` -* `"` is escaped with `\"` +Each statement is enclosed in backticks instead of quotation marks. +This constitutes a [raw string][river-raw-strings], and lets us avoid the need to escape +each `"` with a `\"`, and each `\` with a `\\` inside a [normal][river-strings] River string. [river-strings]: {{< relref "../../config-language/expressions/types_and_values.md/#strings" >}} +[river-raw-strings]: {{< relref "../../config-language/expressions/types_and_values.md/#raw-strings" >}} [traces protobuf]: https://github.com/open-telemetry/opentelemetry-proto/blob/v1.0.0/opentelemetry/proto/trace/v1/trace.proto [metrics protobuf]: https://github.com/open-telemetry/opentelemetry-proto/blob/v1.0.0/opentelemetry/proto/metrics/v1/metrics.proto