Skip to content

Commit 3a0e3bc

Browse files
Add option to log to file (#814)
* Add environment variable for logging to file Fixes #795 * Actually print log directory * Reconfigure Vector to read OPA bundle builder logs without multilog * Log to file as JSON * Changelog * Move tracing-appender dependency to workspace * Reenable hourly rotation * Move changelog entry to unreleased * Generalize bundle builder Vector rule * More changelog fixup * Update crates/stackable-operator/src/logging/mod.rs Co-authored-by: Siegfried Weber <[email protected]> --------- Co-authored-by: Siegfried Weber <[email protected]>
1 parent e74b0c1 commit 3a0e3bc

File tree

6 files changed

+140
-25
lines changed

6 files changed

+140
-25
lines changed

Cargo.lock

+47
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@ tokio-test = "0.4.4"
6868
tower = "0.4.13"
6969
tower-http = { version = "0.5.2", features = ["trace"] }
7070
tracing = "0.1.40"
71+
tracing-appender = "0.2.3"
7172
tracing-opentelemetry = "0.24.0"
72-
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
73+
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] }
7374
url = { version = "2.5.2", features = ["serde"] }
7475
x509-cert = { version = "0.2.5", features = ["builder"] }
7576
zeroize = "1.8.1"

crates/stackable-operator/CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- Added support for logging to files ([#814]).
10+
11+
### Changed
12+
13+
- Changed OPA Bundle Builder Vector config to read from the new log-to-file setup ([#814]).
14+
15+
[#814]: https://github.com/stackabletech/operator-rs/pull/814
16+
717
## [0.70.0] - 2024-07-10
818

919
### Added

crates/stackable-operator/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ snafu.workspace = true
3838
strum.workspace = true
3939
time = { workspace = true, optional = true }
4040
tokio.workspace = true
41+
tracing.workspace = true
42+
tracing-appender.workspace = true
4143
tracing-opentelemetry.workspace = true
4244
tracing-subscriber.workspace = true
43-
tracing.workspace = true
4445
url.workspace = true
4546

4647
[dev-dependencies]

crates/stackable-operator/src/logging/mod.rs

+37-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use std::path::PathBuf;
2+
13
use tracing;
4+
use tracing_appender::rolling::{RollingFileAppender, Rotation};
25
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
36

47
pub mod controller;
@@ -22,18 +25,33 @@ impl Default for TracingTarget {
2225
/// We force users to provide a variable name so it can be different per product.
2326
/// We encourage it to be the product name plus `_LOG`, e.g. `FOOBAR_OPERATOR_LOG`.
2427
/// If no environment variable is provided, the maximum log level is set to INFO.
28+
///
29+
/// Log output can be copied to a file by setting `{env}_DIRECTORY` (e.g. `FOOBAR_OPERATOR_DIRECTORY`)
30+
/// to a directory path. This file will be rotated regularly.
2531
pub fn initialize_logging(env: &str, app_name: &str, tracing_target: TracingTarget) {
2632
let filter = match EnvFilter::try_from_env(env) {
2733
Ok(env_filter) => env_filter,
2834
_ => EnvFilter::try_new(tracing::Level::INFO.to_string())
2935
.expect("Failed to initialize default tracing level to INFO"),
3036
};
3137

32-
let fmt = tracing_subscriber::fmt::layer();
33-
let registry = Registry::default().with(filter).with(fmt);
38+
let terminal_fmt = tracing_subscriber::fmt::layer();
39+
40+
let file_appender_directory = std::env::var_os(format!("{env}_DIRECTORY")).map(PathBuf::from);
41+
let file_fmt = file_appender_directory.as_deref().map(|log_dir| {
42+
let file_appender = RollingFileAppender::builder()
43+
.rotation(Rotation::HOURLY)
44+
.filename_prefix(app_name.to_string())
45+
.filename_suffix("tracing-rs.json")
46+
.max_log_files(6)
47+
.build(log_dir)
48+
.expect("failed to initialize rolling file appender");
49+
tracing_subscriber::fmt::layer()
50+
.json()
51+
.with_writer(file_appender)
52+
});
3453

35-
match tracing_target {
36-
TracingTarget::None => registry.init(),
54+
let jaeger = match tracing_target {
3755
TracingTarget::Jaeger => {
3856
// FIXME (@Techassi): Replace with opentelemetry_otlp
3957
#[allow(deprecated)]
@@ -42,8 +60,22 @@ pub fn initialize_logging(env: &str, app_name: &str, tracing_target: TracingTarg
4260
.install_batch(opentelemetry_sdk::runtime::Tokio)
4361
.expect("Failed to initialize Jaeger pipeline");
4462
let opentelemetry = tracing_opentelemetry::layer().with_tracer(jaeger);
45-
registry.with(opentelemetry).init();
63+
Some(opentelemetry)
4664
}
65+
TracingTarget::None => None,
66+
};
67+
68+
Registry::default()
69+
.with(filter)
70+
.with(terminal_fmt)
71+
.with(file_fmt)
72+
.with(jaeger)
73+
.init();
74+
75+
// need to delay logging until after tracing is initialized
76+
match file_appender_directory {
77+
Some(dir) => tracing::info!(directory = %dir.display(), "file logging enabled"),
78+
None => tracing::debug!("file logging disabled, because no log directory set"),
4779
}
4880
}
4981

crates/stackable-operator/src/product_logging/framework.rs

+42-18
Original file line numberDiff line numberDiff line change
@@ -752,11 +752,10 @@ sources:
752752
include:
753753
- {STACKABLE_LOG_DIR}/*/*.airlift.json
754754
755-
files_opa_bundle_builder:
755+
files_tracing_rs:
756756
type: file
757757
include:
758-
- {STACKABLE_LOG_DIR}/bundle-builder/current
759-
- {STACKABLE_LOG_DIR}/bundle-builder/test
758+
- {STACKABLE_LOG_DIR}/*/*.tracing-rs.json
760759
761760
files_opa_json:
762761
type: file
@@ -765,9 +764,9 @@ sources:
765764
- {STACKABLE_LOG_DIR}/opa/test
766765
767766
transforms:
768-
processed_files_opa_bundle_builder:
767+
processed_files_tracing_rs:
769768
inputs:
770-
- files_opa_bundle_builder
769+
- files_tracing_rs
771770
type: remap
772771
source: |
773772
raw_message = string!(.message)
@@ -778,33 +777,58 @@ transforms:
778777
.message = ""
779778
.errors = []
780779
781-
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>.*)')
780+
parsed_event, err = parse_json(raw_message)
782781
if err != null {{
783-
error = "Log event not parsable: " + err
782+
error = "JSON not parsable: " + err
783+
.errors = push(.errors, error)
784+
log(error, level: "warn")
785+
.message = raw_message
786+
}} else if !is_object(parsed_event) {{
787+
error = "Parsed event is not a JSON object."
784788
.errors = push(.errors, error)
785789
log(error, level: "warn")
786790
.message = raw_message
787791
}} else {{
788-
parsed_timestamp, err = parse_timestamp(event.timestamp, "%Y-%m-%dT%H:%M:%S.%6fZ")
792+
event = object!(parsed_event)
793+
794+
timestamp_string, err = string(event.timestamp)
789795
if err == null {{
790-
.timestamp = parsed_timestamp
791-
}} else {{
792-
.errors = push(.errors, "Timestamp not parsable, using current time instead: " + err)
796+
parsed_timestamp, err = parse_timestamp(timestamp_string, "%+")
797+
if err == null {{
798+
.timestamp = parsed_timestamp
799+
}} else {{
800+
.errors = push(.errors, "Timestamp not parsable, trying current time instead: " + err)
801+
}}
793802
}}
794803
795-
level = string!(event.level)
796-
if includes(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"], level) {{
797-
.level = level
798-
}} else {{
804+
.logger, err = string(event.target)
805+
if err != null || is_empty(.logger) {{
806+
.errors = push(.errors, "Logger/target not found.")
807+
}}
808+
809+
level, err = string(event.level)
810+
if err != null {{
811+
.errors = push(.errors, "Level not found, using \"" + .level + "\" instead.")
812+
}} else if !includes(["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"], upcase(level)) {{
799813
.errors = push(.errors, "Level \"" + level + "\" unknown, using \"" + .level + "\" instead.")
814+
}} else {{
815+
.level = upcase(level)
800816
}}
801817
802-
.logger = string!(event.logger)
818+
fields, err = object(event.fields)
819+
if err != null {{
820+
.errors = push(.errors, "Fields are not an object.")
821+
}}
803822
804-
.message = string!(event.message)
805-
if is_empty(.message) {{
823+
.message, err = string(fields.message)
824+
if err != null || is_empty(.message) {{
806825
.errors = push(.errors, "Message not found.")
807826
}}
827+
828+
del(fields.message)
829+
830+
other_fields = encode_key_value(fields, field_delimiter: "\n")
831+
.message = join!(compact([.message, other_fields]), "\n\n")
808832
}}
809833
810834
processed_files_opa_json:

0 commit comments

Comments
 (0)