Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to log to file #814

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ tower = "0.4.13"
tower-http = { version = "0.5.2", features = ["trace"] }
tracing = "0.1.40"
tracing-opentelemetry = "0.23.0"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] }
url = { version = "2.5.0", features = ["serde"] }
x509-cert = { version = "0.2.5", features = ["builder"] }
# NOTE (@Techassi): This was previously bumped to 1.8.0 in 6d6615b, but that
Expand Down
6 changes: 6 additions & 0 deletions crates/stackable-operator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- Added support for logging to files ([#814]).

### Changed

- BREAKING: Bump `kube` to 0.92.0. This required changes in a unit test, because
Expand All @@ -12,13 +16,15 @@ All notable changes to this project will be documented in this file.
- [Blog Post - Breaking Change](https://kube.rs/blog/2024/06/11/watcher-memory-improvements/#breaking-change)
- [kube#1494](https://github.com/kube-rs/kube/pull/1494)
- [kube#1504](https://github.com/kube-rs/kube/pull/1504)
- Changed OPA Bundle Builder Vector config to read from the new log-to-file setup ([#814]).

### Fixed

- Product image selection pull request version override now only applies to pull requests ([#812]).

[#804]: https://github.com/stackabletech/operator-rs/pull/804
[#812]: https://github.com/stackabletech/operator-rs/pull/812
[#814]: https://github.com/stackabletech/operator-rs/pull/814

## [0.69.3] - 2024-06-12

Expand Down
1 change: 1 addition & 0 deletions crates/stackable-operator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ tracing-opentelemetry.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
url.workspace = true
tracing-appender = "0.2.3"

[dev-dependencies]
rstest.workspace = true
Expand Down
40 changes: 35 additions & 5 deletions crates/stackable-operator/src/logging/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::path::PathBuf;

use tracing;
use tracing_appender::rolling::RollingFileAppender;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};

pub mod controller;
Expand All @@ -22,18 +25,31 @@ impl Default for TracingTarget {
/// We force users to provide a variable name so it can be different per product.
/// We encourage it to be the product name plus `_LOG`, e.g. `FOOBAR_OPERATOR_LOG`.
/// If no environment variable is provided, the maximum log level is set to INFO.
///
/// Log output can be copied to a file by setting `{env}_DIRECTORY` (e.g. `FOOBAR_OPERATOR_DIRECTORY`)
/// to a directory path. This file will be rotated regularly.
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
pub fn initialize_logging(env: &str, app_name: &str, tracing_target: TracingTarget) {
let filter = match EnvFilter::try_from_env(env) {
Ok(env_filter) => env_filter,
_ => EnvFilter::try_new(tracing::Level::INFO.to_string())
.expect("Failed to initialize default tracing level to INFO"),
};

let fmt = tracing_subscriber::fmt::layer();
let registry = Registry::default().with(filter).with(fmt);
let terminal_fmt = tracing_subscriber::fmt::layer();

let file_appender_directory = std::env::var_os(format!("{env}_DIRECTORY")).map(PathBuf::from);
let file_fmt = file_appender_directory.as_deref().map(|log_dir| {
let file_appender = RollingFileAppender::builder()
.filename_suffix(format!("{app_name}.log.json"))
.max_log_files(6)
.build(log_dir)
.expect("failed to initialize rolling file appender");
tracing_subscriber::fmt::layer()
.json()
.with_writer(file_appender)
});

match tracing_target {
TracingTarget::None => registry.init(),
let jaeger = match tracing_target {
TracingTarget::Jaeger => {
// FIXME (@Techassi): Replace with opentelemetry_otlp
#[allow(deprecated)]
Expand All @@ -42,8 +58,22 @@ pub fn initialize_logging(env: &str, app_name: &str, tracing_target: TracingTarg
.install_batch(opentelemetry_sdk::runtime::Tokio)
.expect("Failed to initialize Jaeger pipeline");
let opentelemetry = tracing_opentelemetry::layer().with_tracer(jaeger);
registry.with(opentelemetry).init();
Some(opentelemetry)
}
TracingTarget::None => None,
};

Registry::default()
.with(filter)
.with(terminal_fmt)
.with(file_fmt)
.with(jaeger)
.init();

// need to delay logging until after tracing is initialized
match file_appender_directory {
Some(dir) => tracing::info!(directory = %dir.display(), "file logging enabled"),
None => tracing::debug!("file logging disabled, because no log directory set"),
}
}

Expand Down
54 changes: 39 additions & 15 deletions crates/stackable-operator/src/product_logging/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,8 +755,7 @@ sources:
files_opa_bundle_builder:
type: file
include:
- {STACKABLE_LOG_DIR}/bundle-builder/current
- {STACKABLE_LOG_DIR}/bundle-builder/test
- {STACKABLE_LOG_DIR}/bundle-builder/opa-bundle-builder.log.json
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


files_opa_json:
type: file
Expand All @@ -778,33 +777,58 @@ transforms:
.message = ""
.errors = []

event, err = parse_regex(strip_whitespace(strip_ansi_escape_codes(raw_message)), r'(?P<timestamp>[0-9-:.TZ]+)[ ]+(?P<level>\w+)[ ]+(?P<logger>.+):[ ]+(?P<message>.*)')
parsed_event, err = parse_json(raw_message)
if err != null {{
error = "Log event not parsable: " + err
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 {{
parsed_timestamp, err = parse_timestamp(event.timestamp, "%Y-%m-%dT%H:%M:%S.%6fZ")
event = object!(parsed_event)

timestamp_string, err = string(event.timestamp)
if err == null {{
.timestamp = parsed_timestamp
}} else {{
.errors = push(.errors, "Timestamp not parsable, using current time instead: " + err)
parsed_timestamp, err = parse_timestamp(timestamp_string, "%+")
if err == null {{
.timestamp = parsed_timestamp
}} else {{
.errors = push(.errors, "Timestamp not parsable, trying current time instead: " + err)
}}
}}

level = string!(event.level)
if includes(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"], level) {{
.level = level
}} else {{
.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)
}}

.logger = string!(event.logger)
fields, err = object(event.fields)
if err != null {{
.errors = push(.errors, "Fields are not an object.")
}}

.message = string!(event.message)
if is_empty(.message) {{
.message, err = string(fields.message)
if err != null || is_empty(.message) {{
.errors = push(.errors, "Message not found.")
}}

del(fields.message)

other_fields = encode_key_value(fields, field_delimiter: "\n")
.message = join!(compact([.message, other_fields]), "\n\n")
}}

processed_files_opa_json:
Expand Down