diff --git a/README.md b/README.md index fc75da2..e3b03b8 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,9 @@ name: Drop checkout debug logs log: match: - resource_attribute: ["service.name"] - exact: checkout-api + equals: checkout-api - log_field: LOG_FIELD_SEVERITY_TEXT - exact: DEBUG + equals: DEBUG keep: none ``` diff --git a/examples/cost-control/README.md b/examples/cost-control/README.md index 3ef4192..48a29f3 100644 --- a/examples/cost-control/README.md +++ b/examples/cost-control/README.md @@ -78,7 +78,7 @@ name: Rate limit repeated error logs to 1 per 5 minutes per request log: match: - log_field: severity_text - exact: ERROR + equals: ERROR keep: "1/5m" sample_key: log_attribute: ["request_id"] @@ -86,10 +86,11 @@ log: #### Typed and comparison matching -The string match types (`exact`, `regex`, `contains`, ...) only see string -values. To match numbers and booleans, use the typed `equals` matcher or the -numeric comparators `gt` / `gte` / `lt` / `lte`. The value's type is inferred -from the literal — `500` is an int, `0.1` is a double, `true` is a bool. +Use `equals` for typed equality across strings, numbers, booleans, and bytes. +The pattern matchers (`regex`, `contains`, ...) only see string values. To match +numeric ranges, use the typed comparators `gt` / `gte` / `lt` / `lte`. The +value's type is inferred from the literal — `500` is an int, `0.1` is a double, +`true` is a bool. Keep only error logs by dropping the successful status-code range: @@ -134,7 +135,7 @@ name: Sample order validated logs log: match: - resource_attribute: ["service.name"] - exact: checkout-api + equals: checkout-api - log_field: body regex: "order validated" keep: 50% diff --git a/examples/cost-control/foundation/drop-cache-hit-logs.yaml b/examples/cost-control/foundation/drop-cache-hit-logs.yaml index 93941c6..1d6222b 100644 --- a/examples/cost-control/foundation/drop-cache-hit-logs.yaml +++ b/examples/cost-control/foundation/drop-cache-hit-logs.yaml @@ -3,7 +3,7 @@ name: Drop logs flagged as cache hits description: > Cache hits are high volume and low value. This matches the boolean attribute directly with equals instead of regexing a stringified flag — equals: true - matches a real boolean value, which the string match types cannot see. + matches a real boolean value, which the string pattern matchers cannot see. log: match: diff --git a/examples/cost-control/foundation/rate-limit-batch-job-logs.yaml b/examples/cost-control/foundation/rate-limit-batch-job-logs.yaml index 09c0fac..dc1c2d9 100644 --- a/examples/cost-control/foundation/rate-limit-batch-job-logs.yaml +++ b/examples/cost-control/foundation/rate-limit-batch-job-logs.yaml @@ -4,7 +4,7 @@ name: Rate limit batch job logs to 100/s per job log: match: - resource_attribute: ["service.name"] - exact: batch-processor + equals: batch-processor keep: "100/s" sample_key: log_attribute: ["job_id"] diff --git a/examples/cost-control/foundation/rate-limit-error-logs-1-per-5m.yaml b/examples/cost-control/foundation/rate-limit-error-logs-1-per-5m.yaml index deff4ea..59a4c5a 100644 --- a/examples/cost-control/foundation/rate-limit-error-logs-1-per-5m.yaml +++ b/examples/cost-control/foundation/rate-limit-error-logs-1-per-5m.yaml @@ -4,7 +4,7 @@ name: Rate limit repeated error logs to 1 per 5 minutes per request log: match: - log_field: severity_text - exact: ERROR + equals: ERROR keep: "1/5m" sample_key: log_attribute: ["request_id"] diff --git a/examples/cost-control/foundation/sample-api-logs-by-request.yaml b/examples/cost-control/foundation/sample-api-logs-by-request.yaml index c816b2f..141202e 100644 --- a/examples/cost-control/foundation/sample-api-logs-by-request.yaml +++ b/examples/cost-control/foundation/sample-api-logs-by-request.yaml @@ -4,7 +4,7 @@ name: Sample API logs at 10% by request log: match: - resource_attribute: ["service.name"] - exact: api-gateway + equals: api-gateway keep: "10%" sample_key: log_attribute: ["request_id"] diff --git a/examples/cost-control/generated/checkout-api/drop-cart-item-added.yaml b/examples/cost-control/generated/checkout-api/drop-cart-item-added.yaml index 56fdf5b..1ccdd69 100644 --- a/examples/cost-control/generated/checkout-api/drop-cart-item-added.yaml +++ b/examples/cost-control/generated/checkout-api/drop-cart-item-added.yaml @@ -4,7 +4,7 @@ name: Drop cart item added logs log: match: - resource_attribute: ["service.name"] - exact: checkout-api + equals: checkout-api - log_field: body regex: "item added to cart" keep: none diff --git a/examples/cost-control/generated/checkout-api/drop-request-received.yaml b/examples/cost-control/generated/checkout-api/drop-request-received.yaml index 2a7a060..7fc8e04 100644 --- a/examples/cost-control/generated/checkout-api/drop-request-received.yaml +++ b/examples/cost-control/generated/checkout-api/drop-request-received.yaml @@ -4,7 +4,7 @@ name: Drop request received logs log: match: - resource_attribute: ["service.name"] - exact: checkout-api + equals: checkout-api - log_field: body regex: "request received" keep: none diff --git a/examples/cost-control/generated/checkout-api/sample-order-validated.yaml b/examples/cost-control/generated/checkout-api/sample-order-validated.yaml index 7267dd8..8197109 100644 --- a/examples/cost-control/generated/checkout-api/sample-order-validated.yaml +++ b/examples/cost-control/generated/checkout-api/sample-order-validated.yaml @@ -4,7 +4,7 @@ name: Sample order validated logs log: match: - resource_attribute: ["service.name"] - exact: checkout-api + equals: checkout-api - log_field: body regex: "order validated" keep: 50% diff --git a/examples/cost-control/generated/user-service/drop-cache-hit.yaml b/examples/cost-control/generated/user-service/drop-cache-hit.yaml index 0172232..b278a6c 100644 --- a/examples/cost-control/generated/user-service/drop-cache-hit.yaml +++ b/examples/cost-control/generated/user-service/drop-cache-hit.yaml @@ -4,7 +4,7 @@ name: Drop cache hit logs log: match: - resource_attribute: ["service.name"] - exact: user-service + equals: user-service - log_field: body regex: "cache hit for user" keep: none diff --git a/examples/cost-control/generated/user-service/drop-session-heartbeat.yaml b/examples/cost-control/generated/user-service/drop-session-heartbeat.yaml index 4dbb6e2..d7f39fc 100644 --- a/examples/cost-control/generated/user-service/drop-session-heartbeat.yaml +++ b/examples/cost-control/generated/user-service/drop-session-heartbeat.yaml @@ -4,7 +4,7 @@ name: Drop session heartbeat logs log: match: - resource_attribute: ["service.name"] - exact: user-service + equals: user-service - log_field: body regex: "session heartbeat" keep: none diff --git a/examples/pci-compliance/README.md b/examples/pci-compliance/README.md index d4cb571..77c3d31 100644 --- a/examples/pci-compliance/README.md +++ b/examples/pci-compliance/README.md @@ -60,7 +60,7 @@ log: - resource_attribute: ["service.name"] regex: "^payment-" - log_field: severity_text - exact: DEBUG + equals: DEBUG keep: none ``` @@ -89,7 +89,7 @@ name: Redact transaction logged events log: match: - resource_attribute: ["service.name"] - exact: payment-api + equals: payment-api - log_field: body regex: "transaction logged" transform: diff --git a/examples/pci-compliance/foundation/drop-payment-debug.yaml b/examples/pci-compliance/foundation/drop-payment-debug.yaml index bdc6d7c..8a9348e 100644 --- a/examples/pci-compliance/foundation/drop-payment-debug.yaml +++ b/examples/pci-compliance/foundation/drop-payment-debug.yaml @@ -6,5 +6,5 @@ log: - resource_attribute: ["service.name"] regex: "^payment-" - log_field: severity_text - exact: DEBUG + equals: DEBUG keep: none diff --git a/examples/pci-compliance/generated/checkout-api/redact-order-submitted.yaml b/examples/pci-compliance/generated/checkout-api/redact-order-submitted.yaml index 1dda346..3927284 100644 --- a/examples/pci-compliance/generated/checkout-api/redact-order-submitted.yaml +++ b/examples/pci-compliance/generated/checkout-api/redact-order-submitted.yaml @@ -4,7 +4,7 @@ name: Redact order submitted events log: match: - resource_attribute: ["service.name"] - exact: checkout-api + equals: checkout-api - log_field: body regex: "order submitted" transform: diff --git a/examples/pci-compliance/generated/payment-api/redact-payment-processed.yaml b/examples/pci-compliance/generated/payment-api/redact-payment-processed.yaml index 9d4a582..4498799 100644 --- a/examples/pci-compliance/generated/payment-api/redact-payment-processed.yaml +++ b/examples/pci-compliance/generated/payment-api/redact-payment-processed.yaml @@ -4,7 +4,7 @@ name: Redact payment processed events log: match: - resource_attribute: ["service.name"] - exact: payment-api + equals: payment-api - log_field: body regex: "payment processed" transform: diff --git a/examples/pci-compliance/generated/payment-api/redact-transaction-logged.yaml b/examples/pci-compliance/generated/payment-api/redact-transaction-logged.yaml index fe306f1..024b4e1 100644 --- a/examples/pci-compliance/generated/payment-api/redact-transaction-logged.yaml +++ b/examples/pci-compliance/generated/payment-api/redact-transaction-logged.yaml @@ -4,7 +4,7 @@ name: Redact transaction logged events log: match: - resource_attribute: ["service.name"] - exact: payment-api + equals: payment-api - log_field: body regex: "transaction logged" transform: diff --git a/examples/trace-sampling/foundation/keep-spans-for-trace.yaml b/examples/trace-sampling/foundation/keep-spans-for-trace.yaml index 0c455d9..9acbbe7 100644 --- a/examples/trace-sampling/foundation/keep-spans-for-trace.yaml +++ b/examples/trace-sampling/foundation/keep-spans-for-trace.yaml @@ -4,11 +4,12 @@ description: > Retains every span belonging to one trace id while debugging an incident. trace_id is a bytes field, so the hex literal is decoded to raw bytes once at policy-load time and compared as bytes — no per-span hex encoding required. - The exact matcher is case-insensitive on the hex input. + hex_value is case-insensitive on input. trace: match: - trace_field: TRACE_FIELD_TRACE_ID - exact: "4bf92f3577b34da6a3ce929d0e0e4736" + equals: + hex_value: "4bf92f3577b34da6a3ce929d0e0e4736" keep: percentage: 100.0 diff --git a/examples/trace-sampling/foundation/sample-by-environment.yaml b/examples/trace-sampling/foundation/sample-by-environment.yaml index 688b063..23bd90f 100644 --- a/examples/trace-sampling/foundation/sample-by-environment.yaml +++ b/examples/trace-sampling/foundation/sample-by-environment.yaml @@ -7,6 +7,6 @@ description: > trace: match: - resource_attribute: ["deployment.environment"] - exact: staging + equals: staging keep: percentage: 50.0 diff --git a/examples/trace-sampling/generated/checkout-api/keep-order-submission-spans.yaml b/examples/trace-sampling/generated/checkout-api/keep-order-submission-spans.yaml index d2b1f7e..ab8147b 100644 --- a/examples/trace-sampling/generated/checkout-api/keep-order-submission-spans.yaml +++ b/examples/trace-sampling/generated/checkout-api/keep-order-submission-spans.yaml @@ -7,8 +7,8 @@ description: > trace: match: - resource_attribute: ["service.name"] - exact: checkout-api + equals: checkout-api - trace_field: name - exact: SubmitOrder + equals: SubmitOrder keep: percentage: 100.0 diff --git a/examples/trace-sampling/generated/checkout-api/sample-cart-operations.yaml b/examples/trace-sampling/generated/checkout-api/sample-cart-operations.yaml index 115ac9f..ccaad72 100644 --- a/examples/trace-sampling/generated/checkout-api/sample-cart-operations.yaml +++ b/examples/trace-sampling/generated/checkout-api/sample-cart-operations.yaml @@ -7,7 +7,7 @@ description: > trace: match: - resource_attribute: ["service.name"] - exact: checkout-api + equals: checkout-api - trace_field: name regex: "^(AddToCart|RemoveFromCart|UpdateCart)" keep: diff --git a/examples/trace-sampling/generated/checkout-api/sample-inventory-checks.yaml b/examples/trace-sampling/generated/checkout-api/sample-inventory-checks.yaml index 2b90572..78f561e 100644 --- a/examples/trace-sampling/generated/checkout-api/sample-inventory-checks.yaml +++ b/examples/trace-sampling/generated/checkout-api/sample-inventory-checks.yaml @@ -7,10 +7,10 @@ description: > trace: match: - resource_attribute: ["service.name"] - exact: checkout-api + equals: checkout-api - span_kind: CLIENT - span_attribute: ["rpc.service"] - exact: InventoryService + equals: InventoryService keep: percentage: 10.0 mode: proportional diff --git a/examples/trace-sampling/generated/payment-api/drop-heartbeat-spans.yaml b/examples/trace-sampling/generated/payment-api/drop-heartbeat-spans.yaml index bce9c20..6e7d08f 100644 --- a/examples/trace-sampling/generated/payment-api/drop-heartbeat-spans.yaml +++ b/examples/trace-sampling/generated/payment-api/drop-heartbeat-spans.yaml @@ -7,8 +7,8 @@ description: > trace: match: - resource_attribute: ["service.name"] - exact: payment-api + equals: payment-api - trace_field: name - exact: PaymentGatewayHeartbeat + equals: PaymentGatewayHeartbeat keep: percentage: 0.0 diff --git a/examples/trace-sampling/generated/payment-api/keep-payment-spans.yaml b/examples/trace-sampling/generated/payment-api/keep-payment-spans.yaml index 655878c..cb09e19 100644 --- a/examples/trace-sampling/generated/payment-api/keep-payment-spans.yaml +++ b/examples/trace-sampling/generated/payment-api/keep-payment-spans.yaml @@ -7,7 +7,7 @@ description: > trace: match: - resource_attribute: ["service.name"] - exact: payment-api + equals: payment-api - trace_field: name regex: "^(ProcessPayment|AuthorizePayment|CapturePayment|RefundPayment)" keep: diff --git a/examples/trace-sampling/generated/payment-api/sample-validation-spans.yaml b/examples/trace-sampling/generated/payment-api/sample-validation-spans.yaml index f2ee42d..ae85684 100644 --- a/examples/trace-sampling/generated/payment-api/sample-validation-spans.yaml +++ b/examples/trace-sampling/generated/payment-api/sample-validation-spans.yaml @@ -7,7 +7,7 @@ description: > trace: match: - resource_attribute: ["service.name"] - exact: payment-api + equals: payment-api - trace_field: name regex: "^Validate" - span_status: OK diff --git a/proto/tero/policy/v1/log.proto b/proto/tero/policy/v1/log.proto index 9003d3c..b464f88 100644 --- a/proto/tero/policy/v1/log.proto +++ b/proto/tero/policy/v1/log.proto @@ -84,8 +84,8 @@ enum LogField { LOG_FIELD_BODY = 1; LOG_FIELD_SEVERITY_TEXT = 2; // trace_id and span_id are bytes. They are authored as lowercase hex and - // matched as raw bytes (exact/equals), or as their hex rendering for string - // match types. See the Value message and the spec's + // matched as raw bytes with equals.hex_value, or as their hex rendering for + // string pattern match types. See the Value message and the spec's // "Bytes and Identifier Fields" section. LOG_FIELD_TRACE_ID = 3; LOG_FIELD_SPAN_ID = 4; @@ -129,12 +129,17 @@ message LogMatcher { // Match type. Exactly one must be set. // - // The string match types (exact, regex, starts_with, ends_with, contains) - // operate only on string field values. To match non-string values, use the - // typed equals matcher or the numeric comparison matchers (gt, gte, lt, lte). + // Use the typed equals matcher for equality. The exact field is deprecated and + // will move to reserved in a future version after wire-format users migrate to + // equals.string_value. + // + // The string pattern match types (regex, starts_with, ends_with, contains) + // operate only on string field values. Use the numeric comparison matchers + // (gt, gte, lt, lte) for ranges. oneof match { - // Exact string match (string field values only) - string exact = 10; + // Deprecated: use equals.string_value instead. This field will move to + // reserved in a future version. + string exact = 10 [deprecated = true]; // Regular expression match (string field values only) string regex = 11; @@ -151,7 +156,7 @@ message LogMatcher { // Literal substring match (string field values only) string contains = 15; - // Typed equality for non-string field values (bool, int, double, bytes) + // Typed equality for string, bool, int, double, or bytes field values. Value equals = 22; // Numeric greater-than comparison (int/double field values) @@ -170,7 +175,8 @@ message LogMatcher { // If true, inverts the match result bool negate = 20; - // If true, applies case-insensitive matching to all match types + // If true, applies case-insensitive matching to equals.string_value and the + // string pattern match types. bool case_insensitive = 21; } diff --git a/proto/tero/policy/v1/metric.proto b/proto/tero/policy/v1/metric.proto index 84998b7..35a8db4 100644 --- a/proto/tero/policy/v1/metric.proto +++ b/proto/tero/policy/v1/metric.proto @@ -100,12 +100,17 @@ message MetricMatcher { // Match type. Exactly one must be set. // Note: For metric_type field, only exists is valid (type equality is implicit). // - // The string match types (exact, regex, starts_with, ends_with, contains) - // operate only on string field values. To match non-string values, use the - // typed equals matcher or the numeric comparison matchers (gt, gte, lt, lte). + // Use the typed equals matcher for equality. The exact field is deprecated and + // will move to reserved in a future version after wire-format users migrate to + // equals.string_value. + // + // The string pattern match types (regex, starts_with, ends_with, contains) + // operate only on string field values. Use the numeric comparison matchers + // (gt, gte, lt, lte) for ranges. oneof match { - // Exact string match (string field values only) - string exact = 10; + // Deprecated: use equals.string_value instead. This field will move to + // reserved in a future version. + string exact = 10 [deprecated = true]; // Regular expression match (string field values only) string regex = 11; @@ -122,7 +127,7 @@ message MetricMatcher { // Literal substring match (string field values only) string contains = 15; - // Typed equality for non-string field values (bool, int, double, bytes) + // Typed equality for string, bool, int, double, or bytes field values. Value equals = 22; // Numeric greater-than comparison (int/double field values) @@ -141,6 +146,7 @@ message MetricMatcher { // If true, inverts the match result bool negate = 20; - // If true, applies case-insensitive matching to all match types + // If true, applies case-insensitive matching to equals.string_value and the + // string pattern match types. bool case_insensitive = 21; } diff --git a/proto/tero/policy/v1/shared.proto b/proto/tero/policy/v1/shared.proto index c2856e0..260cead 100644 --- a/proto/tero/policy/v1/shared.proto +++ b/proto/tero/policy/v1/shared.proto @@ -51,9 +51,7 @@ message AttributePath { repeated string path = 1; } -// Value carries a typed, non-string scalar for the `equals` matcher. String -// equality is expressed with the `exact` match type, so this message -// intentionally has no string variant. +// Value carries a typed scalar for the `equals` matcher. // // `equals` matches when the field value has the same type and value as the set // variant. Integer and floating-point values are compared in a single numeric @@ -68,19 +66,16 @@ message AttributePath { // equals: true -> bool_value // equals: 200 -> int_value // equals: 0.5 -> double_value +// equals: "foo" -> string_value // // Bytes are authored either as proto-native base64 (`bytes_value`) or, more // readably, as a lowercase-hex string (`hex_value`). The two are equivalent — // hex_value is decoded to bytes at policy-compile time and yields the same bytes // as the corresponding bytes_value — but hex_value keeps identifiers // (trace/span ids) readable in the canonical proto/JSON form instead of base64. -// A bare string literal (e.g. `equals: "foo"`) MUST be rejected — use `exact` -// for strings. // -// Future direction: the `exact` match type is expected to be deprecated. Once a -// string variant is added to this message, all equality (string and non-string) -// will be expressed through `equals`, with `exact` kept only for backward -// compatibility. +// String equality SHOULD be authored with string_value. The older matcher-level +// `exact` field is deprecated and will move to reserved in a future version. message Value { // Exactly one must be set. oneof value { @@ -93,6 +88,7 @@ message Value { // input, canonically lowercase). Decoded to bytes at policy-compile time; // an invalid hex string MUST be rejected. string hex_value = 5; + string string_value = 6; } } diff --git a/proto/tero/policy/v1/trace.proto b/proto/tero/policy/v1/trace.proto index 0d65ba3..792751b 100644 --- a/proto/tero/policy/v1/trace.proto +++ b/proto/tero/policy/v1/trace.proto @@ -32,9 +32,9 @@ enum TraceField { // Span fields TRACE_FIELD_NAME = 1; // trace_id, span_id, and parent_span_id are bytes. They are authored as - // lowercase hex and matched as raw bytes (exact/equals), or as their hex - // rendering for string match types. See the Value message and the spec's - // "Bytes and Identifier Fields" section. + // lowercase hex and matched as raw bytes with equals.hex_value, or as their + // hex rendering for string pattern match types. See the Value message and the + // spec's "Bytes and Identifier Fields" section. TRACE_FIELD_TRACE_ID = 2; TRACE_FIELD_SPAN_ID = 3; TRACE_FIELD_PARENT_SPAN_ID = 4; @@ -119,12 +119,17 @@ message TraceMatcher { // Match type. Exactly one must be set. // Note: For span_kind and span_status fields, only exists is valid (equality is implicit). // - // The string match types (exact, regex, starts_with, ends_with, contains) - // operate only on string field values. To match non-string values, use the - // typed equals matcher or the numeric comparison matchers (gt, gte, lt, lte). + // Use the typed equals matcher for equality. The exact field is deprecated and + // will move to reserved in a future version after wire-format users migrate to + // equals.string_value. + // + // The string pattern match types (regex, starts_with, ends_with, contains) + // operate only on string field values. Use the numeric comparison matchers + // (gt, gte, lt, lte) for ranges. oneof match { - // Exact string match (string field values only) - string exact = 10; + // Deprecated: use equals.string_value instead. This field will move to + // reserved in a future version. + string exact = 10 [deprecated = true]; // Regular expression match (string field values only) string regex = 11; @@ -141,7 +146,7 @@ message TraceMatcher { // Literal substring match (string field values only) string contains = 15; - // Typed equality for non-string field values (bool, int, double, bytes) + // Typed equality for string, bool, int, double, or bytes field values. Value equals = 22; // Numeric greater-than comparison (int/double field values) @@ -160,7 +165,8 @@ message TraceMatcher { // If true, inverts the match result bool negate = 20; - // If true, applies case-insensitive matching to all match types + // If true, applies case-insensitive matching to equals.string_value and the + // string pattern match types. bool case_insensitive = 21; } diff --git a/spec.md b/spec.md index 6dadfa7..ff76e20 100644 --- a/spec.md +++ b/spec.md @@ -193,13 +193,13 @@ A matcher MUST specify exactly one match type: | Type | Value Type | Description | | ------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- | -| `exact` | string | String field value MUST equal the specified string exactly. | +| `exact` | string | Deprecated string equality. Use `equals.string_value`; `exact` will move to reserved in a future version. | | `regex` | string | String field value MUST match the regular expression. | | `exists` | boolean | If `true`, field MUST exist. If `false`, field MUST NOT exist. | | `starts_with` | string | String field value MUST begin with the specified literal string. | | `ends_with` | string | String field value MUST end with the specified literal string. | | `contains` | string | String field value MUST contain the specified literal substring. | -| `equals` | Value | Non-string field value MUST equal the typed value. See [Typed and Comparison Matching](#typed-and-comparison-matching). | +| `equals` | Value | Field value MUST equal the typed value. See [Typed and Comparison Matching](#typed-and-comparison-matching). | | `gt` | NumericValue | Numeric field value MUST be greater than the value. | | `gte` | NumericValue | Numeric field value MUST be greater than or equal to the value. | | `lt` | NumericValue | Numeric field value MUST be less than the value. | @@ -212,15 +212,18 @@ consistency. #### Typed and Comparison Matching The `exact`, `regex`, `starts_with`, `ends_with`, and `contains` match types -operate on the field's **string** value. For most non-string values (bool, int, -double) they never match; use the typed `equals` matcher or the numeric -comparison matchers `gt`, `gte`, `lt`, and `lte` instead. Bytes-typed fields are -the exception and have dedicated handling — see +operate on the field's **string** value. For non-string values they never match; +use the typed `equals` matcher or the numeric comparison matchers `gt`, `gte`, +`lt`, and `lte` instead. Bytes-typed fields have dedicated handling — see [Bytes and Identifier Fields](#bytes-and-identifier-fields). -These matchers carry a typed value rather than a string. `equals` uses `Value`, -which holds any non-string scalar; the comparison matchers use `NumericValue`, -which holds only a number: +`exact` is deprecated. It remains in the wire format for progressive migration, +but new policies SHOULD use `equals` with a string value. The `exact` field will +move to reserved in a future version after existing wire-format users migrate. + +Equality and comparison matchers carry typed values. `equals` uses `Value`, +which holds any scalar; the comparison matchers use `NumericValue`, which holds +only a number: ``` Value { @@ -230,6 +233,7 @@ Value { double_value: double bytes_value: bytes // raw bytes (base64 in JSON) hex_value: string // bytes as a hex string (readable; see below) + string_value: string } NumericValue { @@ -242,29 +246,24 @@ NumericValue { Two distinct types are used deliberately: because the comparison matchers take a `NumericValue`, comparing against a non-numeric value (a bool or bytes) is unrepresentable in the schema rather than something that must be rejected during -compilation. `Value` has no string variant for the same reason — string equality -is expressed with `exact`. `int_value` preserves full 64-bit precision for large -integer fields (for example, nanosecond timestamps) that a `double` cannot -represent exactly. - -> **Future direction:** `exact` is expected to be deprecated. Once `Value` gains -> a string variant, all equality — string and non-string — will be expressed -> through a single `equals` matcher, and `exact` will be retained only for -> backward compatibility. New policies SHOULD treat `exact` and `equals` as the -> same concept differing only by the value's type. +compilation. `int_value` preserves full 64-bit precision for large integer +fields (for example, nanosecond timestamps) that a `double` cannot represent +exactly. **Semantics:** - `equals` matches if and only if the field value has the same type and value as - the supplied `Value`. Integer and floating-point values are compared in a - single numeric domain, so an `int_value` matcher MAY match a `double` field - with an equal numeric value and vice versa. All other type pairings (for - example, an `int_value` matcher against a string field) MUST NOT match. + the supplied `Value`. `exact` is equivalent to `equals.string_value` for + compatibility. Integer and floating-point values are compared in a single + numeric domain, so an `int_value` matcher MAY match a `double` field with an + equal numeric value and vice versa. All other type pairings (for example, an + `int_value` matcher against a string field) MUST NOT match. - `gt`, `gte`, `lt`, and `lte` perform numeric comparison against `int` and `double` field values. A non-numeric field value MUST NOT match. - A type mismatch is a non-match, never a runtime error (fail-open). -- `case_insensitive` has no effect on `equals` or the comparison matchers; it - applies only to the string match types. +- `case_insensitive` applies to `exact`, `equals.string_value`, and the string + pattern match types. It has no effect on other `equals` variants or the + comparison matchers. - `negate` inverts the result, as with any matcher. **Authoring:** As with [AttributePath](#attributepath), implementations MUST @@ -279,6 +278,8 @@ YAML/JSON. For shorthand, the literal's type determines the `Value` variant: lt: 0.5 # double - log_attribute: ["deprecated"] equals: true # bool +- resource_attribute: ["service.name"] + equals: checkout-api # string # Canonical proto form - log_attribute: ["http.response.status_code"] @@ -290,10 +291,9 @@ Bytes are supplied either as the proto-native base64 `bytes_value` or, more readably, as a hex string in `hex_value`; see [Bytes and Identifier Fields](#bytes-and-identifier-fields). -**Validation:** A string value supplied to `equals` (use `exact` instead), and a -bool or bytes value supplied to a comparison matcher, are not representable in -`Value`/`NumericValue` and MUST be rejected when unmarshaling. Per -[Compilation Errors](#compilation-errors), a policy is also invalid if a +**Validation:** A bool, string, or bytes value supplied to a comparison matcher +is not representable in `NumericValue` and MUST be rejected when unmarshaling. +Per [Compilation Errors](#compilation-errors), a policy is also invalid if a `hex_value` is not valid hexadecimal (non-hex characters or an odd number of digits), or if the typed value is left unset (an empty `Value`/`NumericValue`). @@ -316,10 +316,10 @@ encoding cost is paid once per policy rather than once per telemetry record. **Authoring.** A bytes-typed field is matched as follows: ```yaml -# Well-known identifier field: a plain string literal is decoded as hex, -# because the field's declared type is bytes (no decoration needed). +# Well-known identifier field: hex_value is decoded once and compared as bytes. - trace_field: TRACE_FIELD_SPAN_ID - exact: "8a3f0e1234567890" + equals: + hex_value: "8a3f0e1234567890" # Arbitrary bytes attribute: the type is not known from the field, so the bytes # value is supplied explicitly via equals. @@ -337,9 +337,11 @@ are interchangeable. **Matching semantics on a bytes-typed field:** -- `exact` and `equals` decode the hex (or base64) literal once and compare - `bytes == bytes`. Hex input is case-insensitive; the comparison is over raw - bytes, so length and value are checked exactly. +- `equals.bytes_value` and `equals.hex_value` decode the literal once and + compare `bytes == bytes`. Hex input is case-insensitive; the comparison is + over raw bytes, so length and value are checked exactly. The deprecated + `exact` matcher MAY still be accepted for well-known bytes fields during + migration, but new policies SHOULD use `equals.hex_value`. - `exists` is unchanged. - `regex`, `starts_with`, `ends_with`, and `contains` treat the field as a string, matching against its canonical lowercase-hex rendering. This keeps @@ -358,8 +360,8 @@ comparison runs. #### Case Insensitivity -If `case_insensitive` is `true`, the match is performed without regard to case. -This applies to all match types including `exact`, `regex`, `starts_with`, +If `case_insensitive` is `true`, string matching is performed without regard to +case. This applies to `exact`, `equals.string_value`, `regex`, `starts_with`, `ends_with`, and `contains`. #### Negation @@ -650,13 +652,13 @@ which performs implicit equality): | Type | Value Type | Description | | ------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- | -| `exact` | string | String field value MUST equal the specified string exactly. | +| `exact` | string | Deprecated string equality. Use `equals.string_value`; `exact` will move to reserved in a future version. | | `regex` | string | String field value MUST match the regular expression. | | `exists` | boolean | If `true`, field MUST exist. If `false`, field MUST NOT exist. | | `starts_with` | string | String field value MUST begin with the specified literal string. | | `ends_with` | string | String field value MUST end with the specified literal string. | | `contains` | string | String field value MUST contain the specified literal substring. | -| `equals` | Value | Non-string field value MUST equal the typed value. See [Typed and Comparison Matching](#typed-and-comparison-matching). | +| `equals` | Value | Field value MUST equal the typed value. See [Typed and Comparison Matching](#typed-and-comparison-matching). | | `gt` | NumericValue | Numeric field value MUST be greater than the value. | | `gte` | NumericValue | Numeric field value MUST be greater than or equal to the value. | | `lt` | NumericValue | Numeric field value MUST be less than the value. | @@ -668,8 +670,8 @@ consistency. #### Case Insensitivity -If `case_insensitive` is `true`, the match is performed without regard to case. -This applies to all match types including `exact`, `regex`, `starts_with`, +If `case_insensitive` is `true`, string matching is performed without regard to +case. This applies to `exact`, `equals.string_value`, `regex`, `starts_with`, `ends_with`, and `contains`. #### Negation @@ -778,13 +780,13 @@ A matcher MUST specify exactly one match type (except when using `span_kind` or | Type | Value Type | Description | | ------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- | -| `exact` | string | String field value MUST equal the specified string exactly. | +| `exact` | string | Deprecated string equality. Use `equals.string_value`; `exact` will move to reserved in a future version. | | `regex` | string | String field value MUST match the regular expression. | | `exists` | boolean | If `true`, field MUST exist. If `false`, field MUST NOT exist. | | `starts_with` | string | String field value MUST begin with the specified literal string. | | `ends_with` | string | String field value MUST end with the specified literal string. | | `contains` | string | String field value MUST contain the specified literal substring. | -| `equals` | Value | Non-string field value MUST equal the typed value. See [Typed and Comparison Matching](#typed-and-comparison-matching). | +| `equals` | Value | Field value MUST equal the typed value. See [Typed and Comparison Matching](#typed-and-comparison-matching). | | `gt` | NumericValue | Numeric field value MUST be greater than the value. | | `gte` | NumericValue | Numeric field value MUST be greater than or equal to the value. | | `lt` | NumericValue | Numeric field value MUST be less than the value. | @@ -796,8 +798,8 @@ consistency. #### Case Insensitivity -If `case_insensitive` is `true`, the match is performed without regard to case. -This applies to all match types including `exact`, `regex`, `starts_with`, +If `case_insensitive` is `true`, string matching is performed without regard to +case. This applies to `exact`, `equals.string_value`, `regex`, `starts_with`, `ends_with`, and `contains`. #### Negation @@ -1102,9 +1104,9 @@ enabled: true log: match: - resource_attribute: ["service.name"] - exact: checkout-api + equals: checkout-api - log_field: LOG_FIELD_SEVERITY_TEXT - exact: DEBUG + equals: DEBUG keep: none ``` @@ -1116,7 +1118,7 @@ name: Redact PII from payment service log: match: - resource_attribute: ["service.name"] - exact: payment-api + equals: payment-api transform: remove: - log_attribute: ["user.password"] @@ -1217,9 +1219,10 @@ id: keep-spans-for-trace name: Keep all spans for one trace trace: match: - # trace_id is bytes; the hex literal is decoded once and compared as bytes + # trace_id is bytes; hex_value is decoded once and compared as bytes - trace_field: TRACE_FIELD_TRACE_ID - exact: "4bf92f3577b34da6a3ce929d0e0e4736" + equals: + hex_value: "4bf92f3577b34da6a3ce929d0e0e4736" keep: percentage: 100.0 ```