From cb65a328d581b31d335707733e3aa6849f62e6f5 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 5 Nov 2026 14:39:22 +0100 Subject: [PATCH 1/3] feat: Collect the containerdebug logs --- CHANGELOG.md | 7 + .../superset/pages/usage-guide/logging.adoc | 8 + .../product_logging/vector-test.yaml | 279 ++++++++++++++++++ .../properties/product_logging/vector.yaml | 87 ++++++ .../controller/build/resource/deployment.rs | 1 + .../src/controller/build/resource/mod.rs | 32 +- .../controller/build/resource/statefulset.rs | 7 +- .../superset-vector-aggregator-values.yaml.j2 | 54 +++- 8 files changed, 456 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d356bced..63f0a925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - Support floating tags for product images via the new `spec.image.stackableVersionPolicy` field ([#790]). - Add `/ready` endpoint to the operator Deployment, which reports the CRD installation status ([#795]). +- The logs of the `containerdebug` process are now collected by the Vector agent and forwarded to + the Vector aggregator ([#812]). - Nodes now have a default affinity to the OPA Pods when the role mapping from OPA is configured ([#796]). ### Changed @@ -54,6 +56,10 @@ See [our internal issue](https://github.com/stackabletech/hdfs-operator/issues/626) and [the fix](https://github.com/kube-rs/kube/pull/2042) for details ([#773]). - The operator now watches all resources that it creates and early-exits the reconcile action when the cluster is marked for deletion ([#781]). +- The `containerdebug` process writes its log file again. The operator set + `CONTAINERDEBUG_LOG_DIRECTORY`, which containerdebug stopped reading in its version 0.2.0 when it + moved to `stackable-telemetry`; the directory is now set via `FILE_LOG_DIRECTORY`. The file is + rotated hourly and at most two files are kept, because rotation is opt-in ([#812]). [#756]: https://github.com/stackabletech/superset-operator/pull/756 [#761]: https://github.com/stackabletech/superset-operator/pull/761 @@ -75,6 +81,7 @@ [#805]: https://github.com/stackabletech/superset-operator/pull/805 [#807]: https://github.com/stackabletech/superset-operator/pull/807 [#809]: https://github.com/stackabletech/superset-operator/pull/809 +[#812]: https://github.com/stackabletech/superset-operator/pull/812 ## [26.7.0] - 2026-07-21 diff --git a/docs/modules/superset/pages/usage-guide/logging.adoc b/docs/modules/superset/pages/usage-guide/logging.adoc index bffb7001..1d113c3a 100644 --- a/docs/modules/superset/pages/usage-guide/logging.adoc +++ b/docs/modules/superset/pages/usage-guide/logging.adoc @@ -14,4 +14,12 @@ spec: enableVectorAgent: true ---- +NOTE: The logs of the xref:concepts:observability/containerdebug.adoc[`containerdebug` process] are collected as well. +They are labelled `container: containerdebug`, although no such Kubernetes container exists. +The process runs in the background of the `superset` container. +The label comes from `/stackable/log/containerdebug`, the directory which the operator sets with the `FILE_LOG_DIRECTORY` environment variable and from which the Vector agent derives the container name. +Overriding `FILE_LOG_DIRECTORY` with `envOverrides` therefore changes how these logs are aggregated. +The agent only collects files one level below `/stackable/log`, so a sibling directory such as `/stackable/log/superset` relabels the events instead of dropping them, while any deeper or shallower path stops the aggregation. +A path outside `/stackable/log` also leaves the size-capped log volume. + Further information on how to configure logging, can be found in xref:concepts:logging.adoc[]. diff --git a/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml b/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml index 1dc567c1..43eef403 100644 --- a/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml +++ b/rust/operator-binary/src/controller/build/properties/product_logging/vector-test.yaml @@ -97,6 +97,285 @@ tests: "timestamp": t'2025-10-02T09:27:28.582Z' } + assert_eq!(expected_log_event, .) + - name: Test tracing-rs log + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"timestamp":"2026-11-02T14:49:28.028922Z","level":"INFO","fields":{"message":"found disk","disk.name":"overlay"},"target":"containerdebug::system_information::disk"}' + pod: superset-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "containerdebug", + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "containerdebug::system_information::disk", + "message": "found disk\n\ndisk.name=overlay", + "timestamp": t'2026-11-02T14:49:28.028922Z', + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + - name: Test unparsable tracing-rs log + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{ unparsable' + pod: superset-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "containerdebug", + "errors": [ + .errors[0] + ], + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "", + "message": "{ unparsable", + "timestamp": .timestamp, + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + + # The remainder of the message contains character offsets into + # the VRL source, so only the stable prefix is asserted. + assert!(starts_with!(.errors[0], "JSON not parsable: ")) + - name: Test tracing-rs log which is not a JSON object + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: 'false' + pod: superset-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "containerdebug", + "errors": [ + "Parsed event is not a JSON object." + ], + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "", + "message": "false", + "timestamp": .timestamp, + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + - name: Test tracing-rs log with invalid fields + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"timestamp":"unparsable","level":"INVALID","fields":"none","target":null}' + pod: superset-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "containerdebug", + "errors": [ + .errors[0], + "Logger/target not found.", + "Level \"INVALID\" unknown, using \"INFO\" instead.", + "Fields are not an object." + ], + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "", + "message": "{\"timestamp\":\"unparsable\",\"level\":\"INVALID\",\"fields\":\"none\",\"target\":null}", + "timestamp": .timestamp, + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + + # The remainder of the message contains character offsets into + # the VRL source, so only the stable prefix is asserted. + assert!(starts_with!(.errors[0], "Timestamp not parsable, using current time instead: ")) + - name: Test tracing-rs log without a timestamp + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"level":"INFO","fields":{"message":"found disk"},"target":"containerdebug::system_information::disk"}' + pod: superset-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "containerdebug", + "errors": [ + "Timestamp not found, using current time instead." + ], + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "containerdebug::system_information::disk", + "message": "found disk", + "timestamp": .timestamp, + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + + # The current time is used and not the timestamp which the file + # source attached to the event. The latter reaches the transform + # as a string, whereas now() yields a timestamp. + assert!(is_timestamp(.timestamp)) + - name: Test tracing-rs log without a message but with other fields + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"timestamp":"2026-11-02T14:49:28.028922Z","level":"INFO","fields":{"disk.name":"overlay"},"target":"containerdebug::system_information::disk"}' + pod: superset-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "containerdebug", + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "containerdebug::system_information::disk", + "message": "disk.name=overlay", + "timestamp": t'2026-11-02T14:49:28.028922Z', + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + - name: Test tracing-rs log with a message which is not a string + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"timestamp":"2026-11-02T14:49:28.028922Z","level":"INFO","fields":{"message":42,"disk.name":"overlay"},"target":"containerdebug::system_information::disk"}' + pod: superset-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "containerdebug", + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "containerdebug::system_information::disk", + "message": "disk.name=overlay\nmessage=42", + "timestamp": t'2026-11-02T14:49:28.028922Z', + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default" + } + + assert_eq!(expected_log_event, .) + - name: Test tracing-rs log without any fields + inputs: + - type: log + insert_at: processed_files_tracing_rs + log_fields: + file: /stackable/log/containerdebug/containerdebug.2026-11-02-14.tracing-rs.json + message: '{"timestamp":"2026-11-02T14:49:28.028922Z","level":"INFO","fields":{},"target":"containerdebug::system_information::disk"}' + pod: superset-node-default-0 + source_type: file + timestamp: 2026-11-02T14:49:29.473487331Z + outputs: + - extract_from: extended_logs + expected_event_count: 1 + conditions: + - type: vrl + source: | + expected_log_event = { + "cluster": "superset", + "container": "containerdebug", + "errors": [ + "Message not found." + ], + "file": "containerdebug.2026-11-02-14.tracing-rs.json", + "level": "INFO", + "logger": "containerdebug::system_information::disk", + "message": "", + "timestamp": t'2026-11-02T14:49:28.028922Z', + "namespace": "default", + "pod": "superset-node-default-0", + "role": "node", + "roleGroup": "default" + } + assert_eq!(expected_log_event, .) - name: Test Vector internal logs inputs: diff --git a/rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml b/rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml index 09143d4b..82d0be3f 100644 --- a/rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml +++ b/rust/operator-binary/src/controller/build/properties/product_logging/vector.yaml @@ -24,6 +24,12 @@ sources: include: - ${LOG_DIR}/*/*.py.json + # Reads the logs of the containerdebug process + files_tracing_rs: + type: file + include: + - ${LOG_DIR}/*/*.tracing-rs.json + transforms: processed_files_stdout: inputs: @@ -109,11 +115,92 @@ transforms: } # Extends the processed files with the fields "container" and "file" + # Transforms the tracing-rs JSON logs written by the containerdebug process + processed_files_tracing_rs: + inputs: + - files_tracing_rs + type: remap + source: | + raw_message = string!(.message) + + .timestamp = now() + .logger = "" + .level = "INFO" + .message = "" + .errors = [] + + parsed_event, err = parse_json(raw_message) + if err != null { + error = "JSON not parsable: " + err + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else if !is_object(parsed_event) { + error = "Parsed event is not a JSON object." + .errors = push(.errors, error) + log(error, level: "warn") + .message = raw_message + } else { + event = object!(parsed_event) + + timestamp_string, err = string(event.timestamp) + if err == null { + parsed_timestamp, err = parse_timestamp(timestamp_string, "%+") + if err == null { + .timestamp = parsed_timestamp + } else { + .errors = push(.errors, "Timestamp not parsable, using current time instead: " + err) + } + } else { + .errors = push(.errors, "Timestamp not found, using current time instead.") + } + + .logger, err = string(event.target) + if err != null || is_empty(.logger) { + .errors = push(.errors, "Logger/target not found.") + } + + level, err = string(event.level) + if err != null { + .errors = push(.errors, "Level not found, using \"" + .level + "\" instead.") + } else if !includes(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"], upcase(level)) { + .errors = push(.errors, "Level \"" + level + "\" unknown, using \"" + .level + "\" instead.") + } else { + .level = upcase(level) + } + + fields, err = object(event.fields) + if err == null { + message, err = string(fields.message) + if err == null { + # The message is prepended to the other fields, so remove it from + # them. + del(fields.message) + } else { + # Keep an unusable message among the other fields instead of + # discarding its content. + message = "" + } + + other_fields = encode_key_value(fields, field_delimiter: "\n") + .message = join!(compact([message, other_fields]), "\n\n") + } else { + .errors = push(.errors, "Fields are not an object.") + # Fall back to the raw line, so that its content is not lost. + .message = raw_message + } + + if is_empty(.message) { + .errors = push(.errors, "Message not found.") + } + } + extended_logs_files: inputs: - processed_files_stdout - processed_files_stderr - processed_files_py + - processed_files_tracing_rs type: remap source: | del(.source_type) diff --git a/rust/operator-binary/src/controller/build/resource/deployment.rs b/rust/operator-binary/src/controller/build/resource/deployment.rs index 2b0bbdbb..b31a2444 100644 --- a/rust/operator-binary/src/controller/build/resource/deployment.rs +++ b/rust/operator-binary/src/controller/build/resource/deployment.rs @@ -125,6 +125,7 @@ pub fn build_rolegroup_deployment( {remove_vector_shutdown_file_command} prepare_signal_handlers + mkdir --parents \"$FILE_LOG_DIRECTORY\" containerdebug --output={STACKABLE_LOG_DIR}/containerdebug-state.json --loop & {celery_command} & diff --git a/rust/operator-binary/src/controller/build/resource/mod.rs b/rust/operator-binary/src/controller/build/resource/mod.rs index c07af455..7cc3a64a 100644 --- a/rust/operator-binary/src/controller/build/resource/mod.rs +++ b/rust/operator-binary/src/controller/build/resource/mod.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use const_format::concatcp; use indoc::formatdoc; use stackable_operator::{ builder::pod::{ @@ -70,8 +71,13 @@ constant!(ADMIN_LASTNAME_SECRET_KEY: SecretKey = "adminUser.lastname"); constant!(ADMIN_EMAIL_SECRET_KEY: SecretKey = "adminUser.email"); constant!(ADMIN_PASSWORD_SECRET_KEY: SecretKey = "adminUser.password"); -// Env var the `containerdebug` process logs its tracing information to. -constant!(CONTAINERDEBUG_LOG_DIRECTORY: EnvVarName = "CONTAINERDEBUG_LOG_DIRECTORY"); +// Let the `containerdebug` process write its tracing information to a file. These are the generic +// `stackable-telemetry` variables and apply to the whole Superset container, so any other +// telemetry-based process in it would log into the same directory and the Vector agent would label +// its events with `container: containerdebug`. +constant!(FILE_LOG_DIRECTORY: EnvVarName = "FILE_LOG_DIRECTORY"); +constant!(FILE_LOG_MAX_FILES: EnvVarName = "FILE_LOG_MAX_FILES"); +constant!(FILE_LOG_ROTATION_PERIOD: EnvVarName = "FILE_LOG_ROTATION_PERIOD"); // Env var pointing the Superset container at the directory holding trusted CA certs. constant!(SSL_CERT_DIR: EnvVarName = "SSL_CERT_DIR"); @@ -121,6 +127,9 @@ pub(crate) fn create_volumes( name: LOG_VOLUME_NAME.to_string(), empty_dir: Some(EmptyDirVolumeSource { medium: None, + // Only the Superset log files are budgeted. The `containerdebug` files in this + // volume are rotated by time, not by size, so they have no budget; raising + // `FILE_LOG_LEVEL` via `envOverrides` can therefore evict the Pod. size_limit: Some(product_logging::framework::calculate_log_volume_size_limit( &[MAX_LOG_FILES_SIZE], )), @@ -226,10 +235,15 @@ fn build_env_vars( credentials_secret, &ADMIN_PASSWORD_SECRET_KEY, ) + // The directory must be exactly one level below the log directory, because the Vector + // agent derives the container name from that path segment. .with_value( - &CONTAINERDEBUG_LOG_DIRECTORY, - format!("{STACKABLE_LOG_DIR}/containerdebug"), + &FILE_LOG_DIRECTORY, + concatcp!(STACKABLE_LOG_DIR, "/containerdebug"), ) + // Rotation is opt-in, so without these the file would grow unbounded. + .with_value(&FILE_LOG_ROTATION_PERIOD, "hourly") + .with_value(&FILE_LOG_MAX_FILES, "2") .with_value(&SSL_CERT_DIR, STACKABLE_CERTS_DIR); // Environment variable overrides (highest precedence), merged from role and role group. @@ -370,9 +384,9 @@ mod tests { use super::{ ADMIN_EMAIL, ADMIN_EMAIL_SECRET_KEY, ADMIN_FIRSTNAME, ADMIN_FIRSTNAME_SECRET_KEY, ADMIN_LASTNAME, ADMIN_LASTNAME_SECRET_KEY, ADMIN_PASSWORD, ADMIN_PASSWORD_SECRET_KEY, - ADMIN_USERNAME, ADMIN_USERNAME_SECRET_KEY, CONFIG_VOLUME_NAME, - CONTAINERDEBUG_LOG_DIRECTORY, LISTENER_VOLUME_NAME_PVC, LOG_CONFIG_VOLUME_NAME, - LOG_VOLUME_NAME, METRICS_CONTAINER_NAME, SSL_CERT_DIR, + ADMIN_USERNAME, ADMIN_USERNAME_SECRET_KEY, CONFIG_VOLUME_NAME, FILE_LOG_DIRECTORY, + FILE_LOG_MAX_FILES, FILE_LOG_ROTATION_PERIOD, LISTENER_VOLUME_NAME_PVC, + LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME, METRICS_CONTAINER_NAME, SSL_CERT_DIR, }; #[test] @@ -391,7 +405,9 @@ mod tests { let _ = *ADMIN_LASTNAME_SECRET_KEY; let _ = *ADMIN_EMAIL_SECRET_KEY; let _ = *ADMIN_PASSWORD_SECRET_KEY; - let _ = *CONTAINERDEBUG_LOG_DIRECTORY; + let _ = *FILE_LOG_DIRECTORY; + let _ = *FILE_LOG_MAX_FILES; + let _ = *FILE_LOG_ROTATION_PERIOD; let _ = *SSL_CERT_DIR; let _ = *METRICS_CONTAINER_NAME; let _ = *LISTENER_VOLUME_NAME_PVC; diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 1a7c3310..5e005372 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -190,6 +190,7 @@ pub fn build_node_rolegroup_statefulset( {remove_vector_shutdown_file_command} prepare_signal_handlers + mkdir --parents \"$FILE_LOG_DIRECTORY\" containerdebug --output={STACKABLE_LOG_DIR}/containerdebug-state.json --loop & gunicorn --bind 0.0.0.0:${{SUPERSET_PORT}} --worker-class gthread --threads 20 --timeout {webserver_timeout} --limit-request-line 0 --limit-request-field_size 0 'superset.app:create_app()' & wait_for_termination $! @@ -411,7 +412,7 @@ mod tests { use crate::controller::build::test_support::validated_cluster; /// The user-supplied `envOverrides` must be merged in after all operator-set environment - /// variables, so that they can override any of them. `CONTAINERDEBUG_LOG_DIRECTORY` is used + /// variables, so that they can override any of them. `FILE_LOG_DIRECTORY` is used /// as the example here because it is set unconditionally by the operator. #[test] fn env_overrides_override_operator_set_env_vars() { @@ -423,7 +424,7 @@ mod tests { .expect("node default role group") .clone(); rg.env_overrides = EnvVarSet::new().with_value( - &EnvVarName::from_str("CONTAINERDEBUG_LOG_DIRECTORY").expect("valid env var name"), + &EnvVarName::from_str("FILE_LOG_DIRECTORY").expect("valid env var name"), "/stackable/log/user-override", ); @@ -445,7 +446,7 @@ mod tests { .env .iter() .flatten() - .filter(|env_var| env_var.name == "CONTAINERDEBUG_LOG_DIRECTORY") + .filter(|env_var| env_var.name == "FILE_LOG_DIRECTORY") .collect(); assert_eq!( matching_env_vars.len(), diff --git a/tests/templates/kuttl/logging/superset-vector-aggregator-values.yaml.j2 b/tests/templates/kuttl/logging/superset-vector-aggregator-values.yaml.j2 index 6e8c871c..2ee9f073 100644 --- a/tests/templates/kuttl/logging/superset-vector-aggregator-values.yaml.j2 +++ b/tests/templates/kuttl/logging/superset-vector-aggregator-values.yaml.j2 @@ -22,35 +22,55 @@ customConfig: transforms: validEvents: type: filter - inputs: [vector] + inputs: + - vector condition: is_null(.errors) filteredAutomaticLogConfigSuperset: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .pod == "superset-node-automatic-log-config-0" && .container == "superset" + filteredAutomaticLogConfigContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .pod == "superset-node-automatic-log-config-0" && + .container == "containerdebug" filteredAutomaticLogConfigVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .pod == "superset-node-automatic-log-config-0" && .container == "vector" filteredCustomLogConfigSuperset: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .pod == "superset-node-custom-log-config-0" && .container == "superset" + filteredCustomLogConfigContainerdebug: + type: filter + inputs: + - validEvents + condition: >- + .pod == "superset-node-custom-log-config-0" && + .container == "containerdebug" filteredCustomLogConfigVector: type: filter - inputs: [validEvents] + inputs: + - validEvents condition: >- .pod == "superset-node-custom-log-config-0" && .container == "vector" filteredInvalidEvents: type: filter - inputs: [vector] + inputs: + - vector condition: |- .timestamp == from_unix_timestamp!(0) || is_null(.level) || @@ -58,11 +78,29 @@ customConfig: is_null(.message) sinks: test: - inputs: [filtered*] + inputs: + - filtered* type: blackhole + # Shows all events received from the agents. This sink is intentional and + # not a debugging leftover: if a "filtered*" transform gathers no event, + # then the assertion only reports a count, whereas this log shows which + # pod and container names actually arrived. A failure in CI cannot be + # reproduced by enabling this afterwards, so it is kept enabled at the + # price of the log volume. + stdout: + inputs: + - vector + type: console + encoding: + codec: json + buffer: + # Avoid back pressure from a stalling stdout. The test should not + # fail because of a sink which only aids debugging. + when_full: drop_newest {% if lookup('env', 'VECTOR_AGGREGATOR') %} aggregator: - inputs: [vector] + inputs: + - vector type: vector routing: endpoints: From 6065aed1d5b0af4ce5f3e32619bff7856013ace8 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 5 Nov 2026 17:31:28 +0100 Subject: [PATCH 2/3] chore: Improve code style --- rust/operator-binary/Cargo.toml | 1 + .../src/controller/build/resource/statefulset.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index ddcbe753..8fc20183 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -28,6 +28,7 @@ tracing.workspace = true [dev-dependencies] indoc.workspace = true rstest.workspace = true +stackable-operator = { workspace = true, features = ["test-support"] } tokio.workspace = true yaml_serde.workspace = true diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 5e005372..69287b81 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -424,7 +424,7 @@ mod tests { .expect("node default role group") .clone(); rg.env_overrides = EnvVarSet::new().with_value( - &EnvVarName::from_str("FILE_LOG_DIRECTORY").expect("valid env var name"), + &EnvVarName::from_str_unsafe("FILE_LOG_DIRECTORY"), "/stackable/log/user-override", ); From 739689156c0cb650f58ecb5577720ab02a826133 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 6 Nov 2026 09:20:51 +0100 Subject: [PATCH 3/3] chore: Upgrade serde-saphyr --- Cargo.lock | 6 +++--- Cargo.nix | 13 +++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0338167..94fa6744 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2868,12 +2868,12 @@ dependencies = [ [[package]] name = "serde-saphyr" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4dc2714fc5632417a6e979602d7044bad8f2880c63fc7217087180acec31b6" +checksum = "d43fb82d550b07e6f929a58d7d0935182602f48ddead875f5ee313c41c8f07fe" dependencies = [ "annotate-snippets", - "base64 0.23.1", + "base64 0.22.1", "encoding_rs_io", "granit-parser", "nohash-hasher", diff --git a/Cargo.nix b/Cargo.nix index 3e131163..a0e48307 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -9824,10 +9824,10 @@ rec { }; "serde-saphyr" = rec { crateName = "serde-saphyr"; - version = "2.1.0"; + version = "2.1.1"; edition = "2024"; crateBin = []; - sha256 = "1diixjn80w882xrgqqqci3rdifj4f0nn0yg9lqbj8qy59xqw4k9z"; + sha256 = "1zh7iwfc84z3brgqgbfyips049hq6l4pv3d557wyc1qbalnvhgyl"; libName = "serde_saphyr"; license = "MIT OR Apache-2.0"; dependencies = [ @@ -9838,7 +9838,7 @@ rec { } { name = "base64"; - packageId = "base64 0.23.1"; + packageId = "base64 0.22.1"; optional = true; } { @@ -10931,7 +10931,7 @@ rec { "time" = [ "stackable-shared/time" ]; "webhook" = [ "dep:stackable-webhook" ]; }; - resolvedDefaultFeatures = [ "crds" "default" "webhook" ]; + resolvedDefaultFeatures = [ "crds" "default" "test-support" "webhook" ]; }; "stackable-operator-derive" = rec { crateName = "stackable-operator-derive"; @@ -11146,6 +11146,11 @@ rec { name = "rstest"; packageId = "rstest"; } + { + name = "stackable-operator"; + packageId = "stackable-operator"; + features = [ "webhook" "test-support" ]; + } { name = "tokio"; packageId = "tokio";