#!/bin/bash # This file is part of the rsyslog project, released under ASL 2.0 ## omotel-compression.sh -- compression and resource config test for omotel module ## ## Tests that omotel correctly sends gzip-compressed payloads ## and that OTEL Collector can decode them. Also tests resource parameter ## configuration with string, integer, double, and boolean attributes. . ${srcdir:=.}/diag.sh init export NUMMESSAGES=2000 export EXTRA_EXIT=otel # Download and prepare OTEL Collector download_otel_collector prepare_otel_collector start_otel_collector # Read the port from the port file if [ ! -f ${RSYSLOG_DYNNAME}.otel_port.file ]; then echo "ERROR: OTEL Collector port file not found: ${RSYSLOG_DYNNAME}.otel_port.file" error_exit 1 fi otel_port=$(cat ${RSYSLOG_DYNNAME}.otel_port.file) if [ -z "$otel_port" ]; then echo "ERROR: OTEL Collector port is empty" error_exit 1 fi echo "Using OTEL Collector port: $otel_port" generate_conf add_conf ' template(name="otlpBody" type="string" string="msgnum:%msg:F,58:2%") module(load="../plugins/omotel/.libs/omotel") if $msg contains "msgnum:" then action( name="omotel-http" type="omotel" template="otlpBody" endpoint="http://127.0.0.1:'$otel_port'" path="/v1/logs" batch.max_items="10" batch.timeout.ms="1000" compression="gzip" resource="{ \"service.name\": \"test-service\", \"service.instance.id\": \"test-instance-123\", \"service.version\": \"1.2.3\", \"deployment.environment\": \"test\", \"custom.string.attr\": \"test-value\", \"custom.int.attr\": 42, \"custom.double.attr\": 3.14159, \"custom.bool.attr\": true }" ) ' startup injectmsg shutdown_when_empty wait_shutdown # Stop OTEL Collector to ensure file exporter flushes data # The file exporter may buffer data until collector shuts down stop_otel_collector # Give OTEL Collector a moment to flush the output file after shutdown if [ -n "$TESTTOOL_DIR" ] && [ -f "$TESTTOOL_DIR/msleep" ]; then $TESTTOOL_DIR/msleep 1000 else sleep 1 fi # Extract data from OTEL Collector output otel_collector_get_data # Verify resource attributes python3 - "$RSYSLOG_DYNNAME.otel-output.json" <<'PY' import json import sys path = sys.argv[1] try: # OTEL Collector file exporter writes newline-delimited JSON (one JSON object per line) payloads = [] with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue payload = json.loads(line) if "resourceLogs" in payload: payloads.append(payload) except Exception as exc: sys.stderr.write(f"omotel-compression: failed to parse OTLP output: {exc}\n") sys.exit(1) if not payloads: sys.stderr.write("omotel-compression: OTLP output did not contain any resourceLogs\n") sys.exit(1) sys.stdout.write(f"omotel-compression: found {len(payloads)} payload(s)\n") # Extract resource attributes from the first payload resource_attrs = {} found_resource = False for payload in payloads: if "resourceLogs" in payload: for resource_log in payload["resourceLogs"]: if "resource" in resource_log and "attributes" in resource_log["resource"]: found_resource = True attrs = resource_log["resource"]["attributes"] sys.stdout.write(f"omotel-compression: found {len(attrs)} resource attributes\n") # Parse attributes into a dictionary for attr_entry in attrs: key = attr_entry.get("key") value_obj = attr_entry.get("value", {}) # Extract value based on type if "stringValue" in value_obj: resource_attrs[key] = ("string", value_obj["stringValue"]) elif "intValue" in value_obj: resource_attrs[key] = ("int", value_obj["intValue"]) elif "doubleValue" in value_obj: resource_attrs[key] = ("double", value_obj["doubleValue"]) elif "boolValue" in value_obj: resource_attrs[key] = ("bool", value_obj["boolValue"]) else: resource_attrs[key] = ("unknown", value_obj) # Only check the first resourceLogs entry break if found_resource: break if not found_resource: sys.stderr.write("omotel-compression: no resource attributes found in OTLP output\n") sys.exit(1) sys.stdout.write(f"omotel-compression: extracted {len(resource_attrs)} resource attributes\n") # Expected attributes (automatic + custom) expected_attrs = { # Automatic attributes (always present) "service.name": ("string", "test-service"), # Overridden by custom config "telemetry.sdk.name": ("string", "rsyslog-omotel"), "telemetry.sdk.language": ("string", "C"), # Custom attributes from resource parameter "service.instance.id": ("string", "test-instance-123"), "service.version": ("string", "1.2.3"), "deployment.environment": ("string", "test"), "custom.string.attr": ("string", "test-value"), "custom.int.attr": ("int", 42), "custom.double.attr": ("double", 3.14159), "custom.bool.attr": ("bool", True), } # Verify expected attributes errors = [] for key, (expected_type, expected_value) in expected_attrs.items(): if key not in resource_attrs: errors.append(f"missing attribute: {key}") continue actual_type, actual_value = resource_attrs[key] if actual_type != expected_type: errors.append(f"attribute {key}: type mismatch (expected {expected_type}, got {actual_type})") continue if actual_value != expected_value: errors.append(f"attribute {key}: value mismatch (expected {expected_value}, got {actual_value})") continue sys.stdout.write(f"omotel-compression: verified {key} = {actual_value} ({actual_type})\n") # Check for telemetry.sdk.version (automatic, but version may vary) if "telemetry.sdk.version" not in resource_attrs: errors.append("missing automatic attribute: telemetry.sdk.version") else: sdk_type, sdk_version = resource_attrs["telemetry.sdk.version"] if sdk_type != "string" or not sdk_version: errors.append(f"telemetry.sdk.version has invalid value: {sdk_version}") else: sys.stdout.write(f"omotel-compression: verified telemetry.sdk.version = {sdk_version}\n") if errors: sys.stderr.write("omotel-compression: resource attribute verification errors:\n") for error in errors: sys.stderr.write(f" - {error}\n") sys.exit(1) sys.stdout.write(f"omotel-compression: successfully verified all resource attributes\n") PY seq_check exit_test