Conformance test suite for Policy spec implementations. Verifies that policy evaluation engines in Go, Rust, and Zig produce identical results when given the same policies and OpenTelemetry input data.
.
├── Taskfile.yml # Test harness (build, test, bench, clean)
├── runners/
│ ├── go/ # Go conformance runner (policy-go)
│ ├── rs/ # Rust conformance runner (policy-rs)
│ └── zig/ # Zig conformance runner (policy-zig)
├── server/ # HTTP/gRPC conformance server (Go)
├── testcases/ # 184 test case directories
│ ├── logs_*/ # Log signal tests
│ ├── metrics_*/ # Metric signal tests
│ ├── traces_*/ # Trace signal tests
│ └── compound_*/ # Multi-signal / multi-batch tests
└── bin/ # Hermit-managed toolchain
Each test case is a directory under testcases/ containing:
Simple test (single input/output):
testcases/logs_severity_drop/
├── policies.json # Policy definitions
├── input.json # OTLP JSON input
├── expected.json # Expected OTLP JSON output
└── expected_stats.json # Expected match statistics
The harness runs a simple test as follows:
- Detect signal type from the directory name prefix (
logs_*→ log,metrics_*→ metric,traces_*→ trace) - Invoke the runner:
./runner-go --policies policies.json --input input.json \ --output output_go.json --stats stats_go.json --signal log - Normalize both
expected.jsonandoutput_go.jsonwith jq (strip null fields, coerce numeric strings to numbers, sort keys) - Diff the normalized output against
expected.json— any difference is a failure - Diff
stats_go.jsonagainstexpected_stats.json— any difference is a failure - Report PASS or FAIL (on failure, print the diff)
Compound test (multiple batches, stats checked once after all batches):
testcases/compound_mixed_signals/
├── policies.json # Policy definitions (may span signals)
├── input_1.json # Batch 1 (e.g., logs)
├── expected_1.json # Expected output for batch 1
├── input_2.json # Batch 2 (e.g., metrics)
├── expected_2.json # Expected output for batch 2
├── input_3.json # Batch 3 (e.g., traces)
├── expected_3.json # Expected output for batch 3
└── expected_stats.json # Merged stats across all batches
The harness runs a compound test as follows:
- Iterate over
input_N.jsonfiles sorted numerically - For each batch N:
- Detect signal type from the JSON content (
resourceLogs→ log,resourceMetrics→ metric,resourceSpans→ trace) - Invoke the runner with
input_N.json, writingoutput_N_go.jsonandstats_N_go.json - Normalize and diff
output_N_go.jsonagainstexpected_N.json
- Detect signal type from the JSON content (
- After all batches, merge per-batch stats files with jq (sum
hitsandmissesperpolicy_id) - Diff the merged stats against
expected_stats.json - Report PASS or FAIL (on failure, print diffs for each failing batch and/or stats)
All three runners implement the same CLI interface:
runner-{go,rs,zig} \
--policies policies.json \
--input input.json \
--output output.json \
--stats stats.json \
--signal {log,metric,trace}
For HTTP/gRPC mode, --policies is replaced with --server URL or
--grpc ADDR.
| Runner | Language | Policy engine | Protobuf codec |
|---|---|---|---|
runner-go |
Go | policy-go |
protojson |
runner-rs |
Rust | policy-rs |
serde + custom OTel types |
runner-zig |
Zig | policy-zig |
Native proto JSON codec |
- Task (provided via
bin/) - Go 1.26+
- Rust (stable)
- Zig 0.15+
- Hyperscan/Vectorscan (
task ci:setupinstalls it) jq(provided viabin/)
task build # Build all runners + conformance serverRuns each runner as a CLI process that reads policies from a local JSON file:
task test # Run all 3 runners
task test:go # Go only
task test:rs # Rust only
task test:zig # Zig onlyRuns the conformance server per test case, then uses the runner's --server
flag to fetch policies over HTTP:
task test:http # All 3 runners via HTTP
task test:http:go # Go only
task test:http:rs # Rust only
task test:http:zig # Zig onlyWhat happens for each http test case:
- Starts the conformance server on a random port:
./conformance-server --policies testcases/X/policies.json --http-port 0 --grpc-port 0 - Reads
HTTP_PORT=NandGRPC_PORT=Nfrom stdout (via FIFO, no sleeps) - Invokes the runner:
./runner-go --server http://localhost:PORT/v1/policy/sync \ --input testcases/X/input.json \ --output testcases/X/output_go_http.json \ --signal log - Fetches accumulated stats from
GET /stats - Shuts down the server via
GET /shutdown - Diffs output and stats (stats normalization strips
missesand zero-hit policies since the server only reports{policy_id, hits}for matched policies)
Same as HTTP but uses --grpc flag:
task test:grpc # Go + Rust via gRPC
task test:grpc:go
task test:grpc:rsNote: The Zig runner does not support gRPC.
task test:repeat TC=traces_sampling_50pct N=100 R=go # Repeat one test N times
task bench # Benchmark all runners with hyperfine
task clean # Remove build artifacts and outputstraces_event_attributeandtraces_link_trace_idare unimplemented across all runners
All 184 test cases listed below pass for all three runners (Go, Rust, Zig) in both file-based and HTTP modes unless noted otherwise.
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
logs_all_dropped |
All log records match a drop policy; output is empty | ✅ | ✅ | ✅ |
logs_attribute_match |
Match log records by log attribute exact value | ✅ | ✅ | ✅ |
logs_case_insensitive_ends_with |
Case-insensitive ends_with matcher on severity |
✅ | ✅ | ✅ |
logs_case_insensitive_exact |
Case-insensitive exact matcher on body |
✅ | ✅ | ✅ |
logs_case_insensitive_regex |
Case-insensitive regex matcher on body | ✅ | ✅ | ✅ |
logs_case_insensitive_starts_with |
Case-insensitive starts_with matcher on severity |
✅ | ✅ | ✅ |
logs_contains_ci |
Case-insensitive contains matcher on body |
✅ | ✅ | ✅ |
logs_contains_cs |
Case-sensitive contains matcher on body |
✅ | ✅ | ✅ |
logs_empty_input |
Empty input (no log records) produces empty output | ✅ | ✅ | ✅ |
logs_empty_vs_missing_field |
Distinguish between empty string and missing/null field | ✅ | ✅ | ✅ |
logs_enabled_false |
Disabled policy (enabled: false) is skipped entirely |
✅ | ✅ | ✅ |
logs_enabled_false_with_transforms |
Disabled policy with transforms; transforms must not fire | ✅ | ✅ | ✅ |
logs_ends_with |
ends_with matcher on severity text |
✅ | ✅ | ✅ |
logs_event_name_field |
Match on the event_name log field |
✅ | ✅ | ✅ |
logs_exact_drop |
Exact match on severity drops matching records | ✅ | ✅ | ✅ |
logs_exists |
exists: true matcher on log attribute |
✅ | ✅ | ✅ |
logs_exists_false |
exists: false matcher — match when attribute is absent |
✅ | ✅ | ✅ |
logs_keep_all_default |
Policy with keep: "all" passes all matched records through |
✅ | ✅ | ✅ |
logs_multiple_matchers |
Policy with multiple matchers (AND logic) | ✅ | ✅ | ✅ |
logs_multiple_policies_most_restrictive |
Most-restrictive keep wins when multiple policies match | ✅ | ✅ | ✅ |
logs_multiple_resources |
Multiple resources in input processed independently | ✅ | ✅ | ✅ |
logs_negated_match |
negate: true inverts a matcher |
✅ | ✅ | ✅ |
logs_nested_attribute |
Match on nested attribute path (e.g., ["http", "method"]) |
✅ | ✅ | ✅ |
logs_nested_attribute_deep |
Match on deeply nested attribute path (3+ levels) | ✅ | ✅ | ✅ |
logs_no_match |
No policy matches; all records pass through unmodified | ✅ | ✅ | ✅ |
logs_overlapping_policies |
Multiple policies match the same record | ✅ | ✅ | ✅ |
logs_policy_ordering_determinism |
Policies evaluated in deterministic (alphanumeric by ID) order | ✅ | ✅ | ✅ |
logs_regex_drop |
Regex matcher drops matching records | ✅ | ✅ | ✅ |
logs_resource_attr |
Match on resource attribute | ✅ | ✅ | ✅ |
logs_resource_schema_url |
Match on resource schema URL | ✅ | ✅ | ✅ |
logs_scope_attr |
Match on scope attribute | ✅ | ✅ | ✅ |
logs_scope_schema_url |
Match on scope schema URL | ✅ | ✅ | ✅ |
logs_severity_drop |
Drop by severity text exact match | ✅ | ✅ | ✅ |
logs_span_id_field |
Match on the span_id log field |
✅ | ✅ | ✅ |
logs_starts_with |
starts_with matcher on severity text |
✅ | ✅ | ✅ |
logs_three_matchers |
Three matchers combined in a single policy (AND) | ✅ | ✅ | ✅ |
logs_trace_id_field |
Match on the trace_id log field |
✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
logs_rate_limit |
Rate limit to N records per second | ✅ | ✅ | ✅ |
logs_rate_limit_drop_overlap |
keep: "none" overrides rate limit on the same record |
✅ | ✅ | ✅ |
logs_rate_limit_per_minute |
Rate limit specified as N per minute | ✅ | ✅ | ✅ |
logs_sample_key_attribute |
Sampling keyed by log attribute value | ✅ | ✅ | ✅ |
logs_sample_key_resource_attr |
Sampling keyed by resource attribute value | ✅ | ✅ | ✅ |
logs_sample_key_scope_attr |
Sampling keyed by scope attribute value | ✅ | ✅ | ✅ |
logs_sampling_10pct |
10% sampling rate | ✅ | ✅ | ✅ |
logs_sampling_25pct |
25% sampling rate | ✅ | ✅ | ✅ |
logs_sampling_50pct |
50% sampling rate | ✅ | ✅ | ✅ |
logs_sampling_75pct |
75% sampling rate | ✅ | ✅ | ✅ |
logs_sampling_drop_overlap |
keep: "none" overrides sampling on the same record |
✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
logs_transform_add_attr_upsert_absent |
Add attribute with upsert: true when field absent (insert) |
✅ | ✅ | ✅ |
logs_transform_add_attribute |
Add a new log attribute | ✅ | ✅ | ✅ |
logs_transform_add_body |
Add body to log record when body is null | ✅ | ✅ | ✅ |
logs_transform_add_body_no_upsert_exists |
Add body without upsert when body exists (no-op) | ✅ | ✅ | ✅ |
logs_transform_add_body_upsert_exists |
Add body with upsert: true when body exists (overwrite) |
✅ | ✅ | ✅ |
logs_transform_add_no_upsert |
Add attribute with upsert: false when field exists (no-op) |
✅ | ✅ | ✅ |
logs_transform_add_no_upsert_new_field |
Add attribute with upsert: false when field absent (insert) |
✅ | ✅ | ✅ |
logs_transform_add_resource_attr |
Add a new resource attribute | ✅ | ✅ | ✅ |
logs_transform_add_scope_attr |
Add a new scope attribute | ✅ | ✅ | ✅ |
logs_transform_add_upsert |
Add attribute with upsert: true overwrites existing value |
✅ | ✅ | ✅ |
logs_transform_drop_skips_transform |
Transforms are not applied to records dropped by keep: "none" |
✅ | ✅ | ✅ |
logs_transform_execution_order |
Transforms execute in spec order: remove → redact → rename → add | ✅ | ✅ | ✅ |
logs_transform_multiple_policies |
Multiple policies each apply their own transforms | ✅ | ✅ | ✅ |
logs_transform_multiple_same_field |
Multiple transforms targeting the same field in one policy | ✅ | ✅ | ✅ |
logs_transform_redact_attribute |
Redact a log attribute value with [REDACTED] |
✅ | ✅ | ✅ |
logs_transform_redact_body |
Redact the log body field | ✅ | ✅ | ✅ |
logs_transform_redact_nonexistent |
Redact a non-existent field (no-op) | ✅ | ✅ | ✅ |
logs_transform_redact_resource_attr |
Redact a resource attribute | ✅ | ✅ | ✅ |
logs_transform_redact_scope_attr |
Redact a scope attribute | ✅ | ✅ | ✅ |
logs_transform_remove_attribute |
Remove a log attribute | ✅ | ✅ | ✅ |
logs_transform_remove_body |
Remove the log body field | ✅ | ✅ | ✅ |
logs_transform_remove_nonexistent |
Remove a non-existent field (no-op) | ✅ | ✅ | ✅ |
logs_transform_remove_resource_attr |
Remove a resource attribute | ✅ | ✅ | ✅ |
logs_transform_remove_scope_attr |
Remove a scope attribute | ✅ | ✅ | ✅ |
logs_transform_rename_attribute |
Rename a log attribute | ✅ | ✅ | ✅ |
logs_transform_rename_no_upsert |
Rename with upsert: false when target exists (no-op) |
✅ | ✅ | ✅ |
logs_transform_rename_nonexistent |
Rename a non-existent source attribute (no-op) | ✅ | ✅ | ✅ |
logs_transform_rename_resource_attr |
Rename a resource attribute | ✅ | ✅ | ✅ |
logs_transform_rename_scope_attr |
Rename a scope attribute | ✅ | ✅ | ✅ |
logs_transform_rename_source_absent |
Rename when source attribute absent, upsert: false (no-op) |
✅ | ✅ | ✅ |
logs_transform_rename_target_absent |
Rename when target absent, upsert: false (normal rename) |
✅ | ✅ | ✅ |
logs_transform_rename_upsert |
Rename with upsert: true overwrites existing target |
✅ | ✅ | ✅ |
logs_transform_rename_upsert_source_absent |
Rename with upsert: true when source absent (no-op) |
✅ | ✅ | ✅ |
logs_transform_rename_upsert_target_absent |
Rename with upsert: true when target absent (rename) |
✅ | ✅ | ✅ |
logs_transform_with_rate_limit |
Transforms applied to records that survive rate limiting | ✅ | ✅ | ✅ |
logs_transform_with_sampling |
Transforms applied to records that survive sampling | ✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
metrics_aggregation_temporality |
Match by aggregation temporality (delta/cumulative) | ✅ | ✅ | ✅ |
metrics_case_insensitive |
Case-insensitive metric name matching | ✅ | ✅ | ✅ |
metrics_cumulative_temporality |
Match cumulative temporality specifically | ✅ | ✅ | ✅ |
metrics_description |
Match by metric description field | ✅ | ✅ | ✅ |
metrics_drop_by_attr |
Drop metrics by datapoint attribute | ✅ | ✅ | ✅ |
metrics_drop_by_name |
Drop metrics by name exact match | ✅ | ✅ | ✅ |
metrics_empty_input |
Empty input (no metrics) produces empty output | ✅ | ✅ | ✅ |
metrics_ends_with |
ends_with matcher on metric name |
✅ | ✅ | ✅ |
metrics_exists |
exists: true matcher on datapoint attribute |
✅ | ✅ | ✅ |
metrics_exists_false |
exists: false matcher on datapoint attribute |
✅ | ✅ | ✅ |
metrics_exponential_histogram_type |
Match exponential histogram metric type | ✅ | ✅ | ✅ |
metrics_histogram_type |
Match histogram metric type | ✅ | ✅ | ✅ |
metrics_keep |
Basic keep policy for metrics | ✅ | ✅ | ✅ |
metrics_multiple_matchers |
Multiple matchers combined (AND logic) for metrics | ✅ | ✅ | ✅ |
metrics_multiple_policies |
Multiple metric policies evaluated together | ✅ | ✅ | ✅ |
metrics_multiple_resources |
Multiple resources in metric input | ✅ | ✅ | ✅ |
metrics_negate |
Negated matcher for metrics | ✅ | ✅ | ✅ |
metrics_negate_temporality |
Negated temporality matcher | ✅ | ✅ | ✅ |
metrics_negate_type |
Negated metric type matcher | ✅ | ✅ | ✅ |
metrics_overlapping_miss |
Overlapping policies where one misses | ✅ | ✅ | ✅ |
metrics_resource_attr |
Match on resource attribute for metrics | ✅ | ✅ | ✅ |
metrics_resource_schema_url |
Match on resource schema URL for metrics | ✅ | ✅ | ✅ |
metrics_scope_attr |
Match on scope attribute for metrics | ✅ | ✅ | ✅ |
metrics_scope_name |
Match on scope name for metrics | ✅ | ✅ | ✅ |
metrics_scope_schema_url |
Match on scope schema URL for metrics | ✅ | ✅ | ✅ |
metrics_scope_version |
Match on scope version for metrics | ✅ | ✅ | ✅ |
metrics_starts_with |
starts_with matcher on metric name |
✅ | ✅ | ✅ |
metrics_sum_type |
Match sum metric type | ✅ | ✅ | ✅ |
metrics_summary_type |
Match summary metric type | ✅ | ✅ | ✅ |
metrics_three_policies |
Three metric policies evaluated together | ✅ | ✅ | ✅ |
metrics_type_filter |
Filter metrics by type (gauge/sum/histogram) | ✅ | ✅ | ✅ |
metrics_unit |
Match by metric unit field | ✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
traces_case_insensitive |
Case-insensitive span name matching | ✅ | ✅ | ✅ |
traces_drop_0pct |
Drop at 0% (all sampled out) | ✅ | ✅ | ✅ |
traces_empty_input |
Empty input (no spans) produces empty output | ✅ | ✅ | ✅ |
traces_error_vs_health |
Distinguish error spans from health check spans | ✅ | ✅ | ✅ |
traces_event_name |
Match on span event name | ✅ | ✅ | ✅ |
traces_exists |
exists: true matcher on span attribute |
✅ | ✅ | ✅ |
traces_exists_false |
exists: false matcher on span attribute |
✅ | ✅ | ✅ |
traces_multiple_matchers |
Multiple matchers combined (AND) for traces | ✅ | ✅ | ✅ |
traces_multiple_resources |
Multiple resources in trace input | ✅ | ✅ | ✅ |
traces_name_contains |
contains matcher on span name |
✅ | ✅ | ✅ |
traces_name_ends_with |
ends_with matcher on span name |
✅ | ✅ | ✅ |
traces_name_regex |
Regex matcher on span name | ✅ | ✅ | ✅ |
traces_name_starts_with |
starts_with matcher on span name |
✅ | ✅ | ✅ |
traces_negate |
Negated matcher for traces | ✅ | ✅ | ✅ |
traces_negate_span_kind |
Negated span kind matcher | ✅ | ✅ | ✅ |
traces_negate_span_status |
Negated span status matcher | ✅ | ✅ | ✅ |
traces_overlapping |
Overlapping trace policies on the same spans | ✅ | ✅ | ✅ |
traces_parent_span_id |
Match on parent span ID field | ✅ | ✅ | ✅ |
traces_resource_attr |
Match on resource attribute for traces | ✅ | ✅ | ✅ |
traces_scope_attr |
Match on scope attribute for traces | ✅ | ✅ | ✅ |
traces_scope_name |
Match on scope name for traces | ✅ | ✅ | ✅ |
traces_scope_schema_url |
Match on scope schema URL for traces | ✅ | ✅ | ✅ |
traces_scope_version |
Match on scope version for traces | ✅ | ✅ | ✅ |
traces_span_attribute |
Match on span attribute exact value | ✅ | ✅ | ✅ |
traces_span_attribute_contains |
contains matcher on span attribute |
✅ | ✅ | ✅ |
traces_span_kind |
Match on span kind (server) | ✅ | ✅ | ✅ |
traces_span_kind_client |
Match on span kind (client) | ✅ | ✅ | ✅ |
traces_span_kind_consumer |
Match on span kind (consumer) | ✅ | ✅ | ✅ |
traces_span_kind_producer |
Match on span kind (producer) | ✅ | ✅ | ✅ |
traces_span_status_error |
Match on span status = error | ✅ | ✅ | ✅ |
traces_span_status_ok |
Match on span status = ok | ✅ | ✅ | ✅ |
traces_span_status_unset |
Match on span status = unset | ✅ | ✅ | ✅ |
traces_trace_state |
Match on trace state field | ✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
traces_keep_100pct |
Keep at 100% (all sampled in, writes th:0 tracestate) |
✅ | ✅ | ✅ |
traces_sampling_10pct |
10% trace sampling with deterministic hash | ✅ | ✅ | ✅ |
traces_sampling_25pct |
25% trace sampling | ✅ | ✅ | ✅ |
traces_sampling_50pct |
50% trace sampling | ✅ | ✅ | ✅ |
traces_sampling_75pct |
75% trace sampling | ✅ | ✅ | ✅ |
traces_sampling_equalizing |
Equalizing sampling algorithm | ✅ | ✅ | ✅ |
traces_sampling_fail_closed |
fail_closed: true drops spans without valid trace ID |
✅ | ✅ | ✅ |
traces_sampling_precision |
High-precision sampling threshold encoding | ✅ | ✅ | ✅ |
traces_sampling_proportional |
Proportional sampling algorithm | ✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
traces_tracestate_equalizing_incoming_th |
Equalizing sampler with pre-existing th in tracestate |
✅ | ✅ | ✅ |
traces_tracestate_fail_closed_true |
Fail-closed behavior with tracestate present | ✅ | ✅ | ✅ |
traces_tracestate_mixed |
Mixed tracestate scenarios (some with, some without) | ✅ | ✅ | ✅ |
traces_tracestate_overwrite_ot |
Overwrite existing ot vendor key in tracestate |
✅ | ✅ | ✅ |
traces_tracestate_preserve_vendors |
Preserve non-ot vendor keys in tracestate |
✅ | ✅ | ✅ |
traces_tracestate_proportional_incoming_th |
Proportional sampler with pre-existing th in tracestate |
✅ | ✅ | ✅ |
traces_tracestate_rv_consistency_check |
Randomness value consistency check in tracestate | ✅ | ✅ | ✅ |
traces_tracestate_rv_randomness |
Random value (rv) written to tracestate |
✅ | ✅ | ✅ |
traces_tracestate_write_basic |
Basic tracestate write with sampling threshold | ✅ | ✅ | ✅ |
| Test case | Description | Go | Zig | Rust |
|---|---|---|---|---|
compound_all_keep_types |
All keep types (all/none/sample/rate_limit) in one policy set | ✅ | ✅ | ✅ |
compound_conflicting_keeps |
Conflicting keep decisions across policies; most restrictive wins | ✅ | ✅ | ✅ |
compound_datapoint_attr_types |
Datapoint attribute matching across histogram, summary, gauge | ✅ | ✅ | ✅ |
compound_disabled_mixed |
Mix of enabled and disabled policies; disabled transforms must not fire | ✅ | ✅ | ✅ |
compound_double_negation |
exists: false + negate: true semantics |
✅ | ✅ | ✅ |
compound_empty_vs_missing |
Empty string vs null/absent field behavior across signals | ✅ | ✅ | ✅ |
compound_many_policies_fanout |
50+ policies each targeting a different service; verifies no cross-contamination | ✅ | ✅ | ✅ |
compound_mixed_signals |
Policies spanning logs, metrics, and traces in one set | ✅ | ✅ | ✅ |
compound_negation_overlap |
Policies with negated matchers creating complex intersections | ✅ | ✅ | ✅ |
compound_nested_attributes |
Nested attribute paths across all signal types | ✅ | ✅ | ✅ |
compound_rate_limit_most_restrictive |
keep: "none" overrides rate limit; confirms none > rate_limit |
✅ | ✅ | ✅ |
compound_regex_edge_cases |
Character classes, anchoring, alternation, UUID patterns, escaped brackets | ✅ | ✅ | ✅ |
compound_sampling_interactions |
Trace sampling with fail_closed, 0% drop overriding sampling | ✅ | ✅ | ✅ |
compound_scope_isolation |
Resource/scope attribute transforms are isolated per scope | ✅ | ✅ | ✅ |
compound_transform_chain |
Cross-policy transform visibility (transforms applied after all matching) | ✅ | ✅ | ✅ |
compound_transform_ordering_alphanumeric |
Policies evaluated in alphanumeric ID order, not array order | ✅ | ✅ | ✅ |
compound_transforms_across_policies |
Multiple policies with add/redact/rename transforms on same records | ✅ | ✅ | ✅ |
| Signal | Tests | Go | Zig | Rust |
|---|---|---|---|---|
| Logs | 84 | 84 | 84 | 84 |
| Metrics | 32 | 32 | 32 | 32 |
| Traces | 51 | 51 | 51 | 51 |
| Compound | 17 | 16 | 17 | 16 |
| Total | 184 | 183 | 184 | 183 |