Merge pull request #440 from rustfs/feature/obs-config

Enhance flexi_logger Integration with Log Rotation by Time and Size
This commit is contained in:
houseme
2025-05-29 23:09:47 +08:00
committed by GitHub
13 changed files with 916 additions and 289 deletions
@@ -0,0 +1,73 @@
services:
openobserve:
image: public.ecr.aws/zinclabs/openobserve:latest
restart: unless-stopped
environment:
ZO_ROOT_USER_EMAIL: "root@rustfs.com"
ZO_ROOT_USER_PASSWORD: "rustfs123"
ZO_TRACING_HEADER_KEY: "Authorization"
ZO_TRACING_HEADER_VALUE: "Basic cm9vdEBydXN0ZnMuY29tOmQ4SXlCSEJTUkk3RGVlcEQ="
ZO_DATA_DIR: "/data"
ZO_MEMORY_CACHE_ENABLED: "true"
ZO_MEMORY_CACHE_MAX_SIZE: "256"
RUST_LOG: "info"
TZ: Asia/Shanghai
ports:
- "5080:5080"
- "5081:5081"
volumes:
- ./data:/data
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:5080/health" ]
start_period: 60s
interval: 10s
timeout: 5s
retries: 6
networks:
- otel-network
deploy:
resources:
limits:
memory: 1024M
reservations:
memory: 512M
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
restart: unless-stopped
environment:
- TZ=Asia/Shanghai
volumes:
- ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml
ports:
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
- "13133:13133" # Health check
- "1777:1777" # pprof
- "55679:55679" # zpages
- "1888:1888" # Metrics
- "8888:8888" # Prometheus metrics
- "8889:8889" # Additional metrics endpoint
depends_on:
- openobserve
networks:
- otel-network
deploy:
resources:
limits:
memory: 10240M
reservations:
memory: 512M
networks:
otel-network:
driver: bridge
name: otel-network
ipam:
config:
- subnet: 172.28.0.0/16
gateway: 172.28.0.1
labels:
com.example.description: "Network for OpenObserve and OpenTelemetry Collector"
volumes:
data:
@@ -0,0 +1,78 @@
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
filelog:
include: [ "/var/log/app/*.log" ]
start_at: end
processors:
batch:
timeout: 1s
send_batch_size: 1024
memory_limiter:
check_interval: 1s
limit_mib: 400
spike_limit_mib: 100
exporters:
otlphttp/openobserve:
endpoint: http://openobserve:5080/api/default # http://127.0.0.1:5080/api/default
headers:
Authorization: "Basic cm9vdEBydXN0ZnMuY29tOmQ4SXlCSEJTUkk3RGVlcEQ="
stream-name: default
organization: default
compression: gzip
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 300s
timeout: 10s
otlp/openobserve:
endpoint: openobserve:5081 # http://127.0.0.1:5080/api/default
headers:
Authorization: "Basic cm9vdEBydXN0ZnMuY29tOmQ4SXlCSEJTUkk3RGVlcEQ="
stream-name: default
organization: default
compression: gzip
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 300s
timeout: 10s
tls:
insecure: true
extensions:
health_check:
endpoint: 0.0.0.0:13133
pprof:
endpoint: 0.0.0.0:1777
zpages:
endpoint: 0.0.0.0:55679
service:
extensions: [ health_check, pprof, zpages ]
pipelines:
traces:
receivers: [ otlp ]
processors: [ memory_limiter, batch ]
exporters: [ otlp/openobserve ]
metrics:
receivers: [ otlp ]
processors: [ memory_limiter, batch ]
exporters: [ otlp/openobserve ]
logs:
receivers: [ otlp, filelog ]
processors: [ memory_limiter, batch ]
exporters: [ otlp/openobserve ]
telemetry:
logs:
level: "info" # Collector 日志级别
metrics:
address: "0.0.0.0:8888" # Collector 自身指标暴露
+2 -1
View File
@@ -16,4 +16,5 @@ deploy/certs/*
.env .env
.rustfs.sys .rustfs.sys
.cargo .cargo
profile.json profile.json
.docker/openobserve-otel/data
Generated
+272 -3
View File
@@ -1745,6 +1745,15 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.21"
@@ -3250,6 +3259,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
dependencies = [
"serde",
"typeid",
]
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.11" version = "0.3.11"
@@ -3364,6 +3383,27 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "flexi_logger"
version = "0.30.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb03342077df16d5b1400d7bed00156882846d7a479ff61a6f10594bcc3423d8"
dependencies = [
"chrono",
"crossbeam-channel",
"crossbeam-queue",
"log",
"notify-debouncer-mini",
"nu-ansi-term 0.50.1",
"regex",
"serde",
"serde_derive",
"thiserror 2.0.12",
"toml",
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "flume" name = "flume"
version = "0.11.1" version = "0.11.1"
@@ -3439,6 +3479,15 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "futf" name = "futf"
version = "0.1.5" version = "0.1.5"
@@ -4466,6 +4515,26 @@ dependencies = [
"cfb", "cfb",
] ]
[[package]]
name = "inotify"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.9.0",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "inout" name = "inout"
version = "0.1.4" version = "0.1.4"
@@ -4682,6 +4751,26 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "kqueue"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
]
[[package]] [[package]]
name = "kuchikiki" name = "kuchikiki"
version = "0.8.2" version = "0.8.2"
@@ -4970,6 +5059,10 @@ name = "log"
version = "0.4.27" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
dependencies = [
"serde",
"value-bag",
]
[[package]] [[package]]
name = "longest-increasing-subsequence" name = "longest-increasing-subsequence"
@@ -5181,6 +5274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [ dependencies = [
"libc", "libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@@ -5350,6 +5444,43 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "notify"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
dependencies = [
"bitflags 2.9.0",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"notify-types",
"walkdir",
"windows-sys 0.59.0",
]
[[package]]
name = "notify-debouncer-mini"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a689eb4262184d9a1727f9087cd03883ea716682ab03ed24efec57d7716dccb8"
dependencies = [
"log",
"notify",
"notify-types",
"tempfile",
]
[[package]]
name = "notify-types"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
[[package]] [[package]]
name = "ntapi" name = "ntapi"
version = "0.4.1" version = "0.4.1"
@@ -5369,6 +5500,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.50.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
dependencies = [
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "nugine-rust-utils" name = "nugine-rust-utils"
version = "0.3.1" version = "0.3.1"
@@ -7464,8 +7604,8 @@ version = "0.0.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"chrono", "chrono",
"config", "flexi_logger",
"local-ip-address", "nu-ansi-term 0.50.1",
"nvml-wrapper", "nvml-wrapper",
"opentelemetry", "opentelemetry",
"opentelemetry-appender-tracing", "opentelemetry-appender-tracing",
@@ -7834,6 +7974,15 @@ dependencies = [
"syn 2.0.100", "syn 2.0.100",
] ]
[[package]]
name = "serde_fmt"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.140" version = "1.0.140"
@@ -8395,6 +8544,84 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "sval"
version = "2.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc9739f56c5d0c44a5ed45473ec868af02eb896af8c05f616673a31e1d1bb09"
[[package]]
name = "sval_buffer"
version = "2.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f39b07436a8c271b34dad5070c634d1d3d76d6776e938ee97b4a66a5e8003d0b"
dependencies = [
"sval",
"sval_ref",
]
[[package]]
name = "sval_dynamic"
version = "2.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffcb072d857431bf885580dacecf05ed987bac931230736739a79051dbf3499b"
dependencies = [
"sval",
]
[[package]]
name = "sval_fmt"
version = "2.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f214f427ad94a553e5ca5514c95c6be84667cbc5568cce957f03f3477d03d5c"
dependencies = [
"itoa 1.0.15",
"ryu",
"sval",
]
[[package]]
name = "sval_json"
version = "2.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ed34b32e638dec9a99c8ac92d0aa1220d40041026b625474c2b6a4d6f4feb"
dependencies = [
"itoa 1.0.15",
"ryu",
"sval",
]
[[package]]
name = "sval_nested"
version = "2.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14bae8fcb2f24fee2c42c1f19037707f7c9a29a0cda936d2188d48a961c4bb2a"
dependencies = [
"sval",
"sval_buffer",
"sval_ref",
]
[[package]]
name = "sval_ref"
version = "2.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4eaea3821d3046dcba81d4b8489421da42961889902342691fb7eab491d79e"
dependencies = [
"sval",
]
[[package]]
name = "sval_serde"
version = "2.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "172dd4aa8cb3b45c8ac8f3b4111d644cd26938b0643ede8f93070812b87fb339"
dependencies = [
"serde",
"sval",
"sval_nested",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@@ -9146,7 +9373,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [ dependencies = [
"matchers", "matchers",
"nu-ansi-term", "nu-ansi-term 0.46.0",
"once_cell", "once_cell",
"regex", "regex",
"serde", "serde",
@@ -9242,6 +9469,12 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.18.0" version = "1.18.0"
@@ -9410,6 +9643,42 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "value-bag"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
dependencies = [
"value-bag-serde1",
"value-bag-sval2",
]
[[package]]
name = "value-bag-serde1"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35540706617d373b118d550d41f5dfe0b78a0c195dc13c6815e92e2638432306"
dependencies = [
"erased-serde",
"serde",
"serde_fmt",
]
[[package]]
name = "value-bag-sval2"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fe7e140a2658cc16f7ee7a86e413e803fc8f9b5127adc8755c19f9fefa63a52"
dependencies = [
"sval",
"sval_buffer",
"sval_dynamic",
"sval_fmt",
"sval_json",
"sval_ref",
"sval_serde",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
+2
View File
@@ -75,6 +75,7 @@ derive_builder = "0.20.2"
dioxus = { version = "0.6.3", features = ["router"] } dioxus = { version = "0.6.3", features = ["router"] }
dirs = "6.0.0" dirs = "6.0.0"
flatbuffers = "25.2.10" flatbuffers = "25.2.10"
flexi_logger = { version = "0.30.2", features = ["trc"] }
futures = "0.3.31" futures = "0.3.31"
futures-core = "0.3.31" futures-core = "0.3.31"
futures-util = "0.3.31" futures-util = "0.3.31"
@@ -105,6 +106,7 @@ mime = "0.3.17"
mime_guess = "2.0.5" mime_guess = "2.0.5"
netif = "0.1.6" netif = "0.1.6"
nix = { version = "0.30.1", features = ["fs"] } nix = { version = "0.30.1", features = ["fs"] }
nu-ansi-term = "0.50.1"
num_cpus = { version = "1.16.0" } num_cpus = { version = "1.16.0" }
nvml-wrapper = "0.10.0" nvml-wrapper = "0.10.0"
object_store = "0.11.2" object_store = "0.11.2"
+42 -13
View File
@@ -56,14 +56,13 @@ pub const DEFAULT_ACCESS_KEY: &str = "rustfsadmin";
/// Example: RUSTFS_SECRET_KEY=rustfsadmin /// Example: RUSTFS_SECRET_KEY=rustfsadmin
/// Example: --secret-key rustfsadmin /// Example: --secret-key rustfsadmin
pub const DEFAULT_SECRET_KEY: &str = "rustfsadmin"; pub const DEFAULT_SECRET_KEY: &str = "rustfsadmin";
/// Default configuration file for observability
/// Default value: config/obs.toml /// Default OBS configuration endpoint
/// Environment variable: RUSTFS_OBS_CONFIG /// Environment variable: DEFAULT_OBS_ENDPOINT
/// Command line argument: --obs-config /// Command line argument: --obs-endpoint
/// Example: RUSTFS_OBS_CONFIG=config/obs.toml /// Example: DEFAULT_OBS_ENDPOINT="http://localost:4317"
/// Example: --obs-config config/obs.toml /// Example: --obs-endpoint http://localost:4317
/// Example: --obs-config /etc/rustfs/obs.toml pub const DEFAULT_OBS_ENDPOINT: &str = "";
pub const DEFAULT_OBS_CONFIG: &str = "./deploy/config/obs.toml";
/// Default TLS key for rustfs /// Default TLS key for rustfs
/// This is the default key for TLS. /// This is the default key for TLS.
@@ -90,6 +89,41 @@ pub const DEFAULT_CONSOLE_PORT: u16 = 9001;
/// This is the default address for rustfs console. /// This is the default address for rustfs console.
pub const DEFAULT_CONSOLE_ADDRESS: &str = concat!(":", DEFAULT_CONSOLE_PORT); pub const DEFAULT_CONSOLE_ADDRESS: &str = concat!(":", DEFAULT_CONSOLE_PORT);
/// Default log filename for rustfs
/// This is the default log filename for rustfs.
/// It is used to store the logs of the application.
/// Default value: rustfs.log
/// Environment variable: RUSTFS_OBSERVABILITY_LOG_FILENAME
pub const DEFAULT_LOG_FILENAME: &str = "rustfs.log";
/// Default log directory for rustfs
/// This is the default log directory for rustfs.
/// It is used to store the logs of the application.
/// Default value: logs
/// Environment variable: RUSTFS_OBSERVABILITY_LOG_DIRECTORY
pub const DEFAULT_LOG_DIR: &str = "logs";
/// Default log rotation size mb for rustfs
/// This is the default log rotation size for rustfs.
/// It is used to rotate the logs of the application.
/// Default value: 100 MB
/// Environment variable: RUSTFS_OBSERVABILITY_LOG_ROTATION_SIZE_MB
pub const DEFAULT_LOG_ROTATION_SIZE_MB: u64 = 100;
/// Default log rotation time for rustfs
/// This is the default log rotation time for rustfs.
/// It is used to rotate the logs of the application.
/// Default value: hour, eg: day,hour,minute,second
/// Environment variable: RUSTFS_OBSERVABILITY_LOG_ROTATION_TIME
pub const DEFAULT_LOG_ROTATION_TIME: &str = "day";
/// Default log keep files for rustfs
/// This is the default log keep files for rustfs.
/// It is used to keep the logs of the application.
/// Default value: 30
/// Environment variable: RUSTFS_OBSERVABILITY_LOG_KEEP_FILES
pub const DEFAULT_LOG_KEEP_FILES: u16 = 30;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -154,10 +188,6 @@ mod tests {
#[test] #[test]
fn test_file_path_constants() { fn test_file_path_constants() {
// Test file path related constants
assert_eq!(DEFAULT_OBS_CONFIG, "./deploy/config/obs.toml");
assert!(DEFAULT_OBS_CONFIG.ends_with(".toml"), "Config file should be TOML format");
assert_eq!(RUSTFS_TLS_KEY, "rustfs_key.pem"); assert_eq!(RUSTFS_TLS_KEY, "rustfs_key.pem");
assert!(RUSTFS_TLS_KEY.ends_with(".pem"), "TLS key should be PEM format"); assert!(RUSTFS_TLS_KEY.ends_with(".pem"), "TLS key should be PEM format");
@@ -219,7 +249,6 @@ mod tests {
ENVIRONMENT, ENVIRONMENT,
DEFAULT_ACCESS_KEY, DEFAULT_ACCESS_KEY,
DEFAULT_SECRET_KEY, DEFAULT_SECRET_KEY,
DEFAULT_OBS_CONFIG,
RUSTFS_TLS_KEY, RUSTFS_TLS_KEY,
RUSTFS_TLS_CERT, RUSTFS_TLS_CERT,
DEFAULT_ADDRESS, DEFAULT_ADDRESS,
+2 -2
View File
@@ -20,8 +20,8 @@ kafka = ["dep:rdkafka"]
rustfs-config = { workspace = true } rustfs-config = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
chrono = { workspace = true } chrono = { workspace = true }
config = { workspace = true } flexi_logger = { workspace = true, features = ["trc", "kv"] }
local-ip-address = { workspace = true } nu-ansi-term = { workspace = true }
nvml-wrapper = { workspace = true, optional = true } nvml-wrapper = { workspace = true, optional = true }
opentelemetry = { workspace = true } opentelemetry = { workspace = true }
opentelemetry-appender-tracing = { workspace = true, features = ["experimental_use_tracing_span_context", "experimental_metadata_attributes"] } opentelemetry-appender-tracing = { workspace = true, features = ["experimental_use_tracing_span_context", "experimental_metadata_attributes"] }
+112 -102
View File
@@ -1,5 +1,7 @@
use config::{Config, File, FileFormat}; use rustfs_config::{
use rustfs_config::{APP_NAME, DEFAULT_LOG_LEVEL, ENVIRONMENT, METER_INTERVAL, SAMPLE_RATIO, SERVICE_VERSION, USE_STDOUT}; APP_NAME, DEFAULT_LOG_DIR, DEFAULT_LOG_FILENAME, DEFAULT_LOG_KEEP_FILES, DEFAULT_LOG_LEVEL, DEFAULT_LOG_ROTATION_SIZE_MB,
DEFAULT_LOG_ROTATION_TIME, ENVIRONMENT, METER_INTERVAL, SAMPLE_RATIO, SERVICE_VERSION, USE_STDOUT,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::env; use std::env;
@@ -22,54 +24,94 @@ pub struct OtelConfig {
pub environment: Option<String>, // Environment pub environment: Option<String>, // Environment
pub logger_level: Option<String>, // Logger level pub logger_level: Option<String>, // Logger level
pub local_logging_enabled: Option<bool>, // Local logging enabled pub local_logging_enabled: Option<bool>, // Local logging enabled
} // 新增 flexi_logger 相关配置
pub log_directory: Option<String>, // 日志文件目录
/// Helper function: Extract observable configuration from environment variables pub log_filename: Option<String>, // 日志文件名称
fn extract_otel_config_from_env() -> OtelConfig { pub log_rotation_size_mb: Option<u64>, // 日志文件大小切割阈值 (MB)
OtelConfig { pub log_rotation_time: Option<String>, // 日志按时间切割 (Hour, Day)
endpoint: env::var("RUSTFS_OBSERVABILITY_ENDPOINT").unwrap_or_else(|_| "".to_string()), pub log_keep_files: Option<u16>, // 保留日志文件数量
use_stdout: env::var("RUSTFS_OBSERVABILITY_USE_STDOUT")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(USE_STDOUT)),
sample_ratio: env::var("RUSTFS_OBSERVABILITY_SAMPLE_RATIO")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(SAMPLE_RATIO)),
meter_interval: env::var("RUSTFS_OBSERVABILITY_METER_INTERVAL")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(METER_INTERVAL)),
service_name: env::var("RUSTFS_OBSERVABILITY_SERVICE_NAME")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(APP_NAME.to_string())),
service_version: env::var("RUSTFS_OBSERVABILITY_SERVICE_VERSION")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(SERVICE_VERSION.to_string())),
environment: env::var("RUSTFS_OBSERVABILITY_ENVIRONMENT")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(ENVIRONMENT.to_string())),
logger_level: env::var("RUSTFS_OBSERVABILITY_LOGGER_LEVEL")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(DEFAULT_LOG_LEVEL.to_string())),
local_logging_enabled: env::var("RUSTFS_OBSERVABILITY_LOCAL_LOGGING_ENABLED")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(false)),
}
} }
impl OtelConfig { impl OtelConfig {
/// Helper function: Extract observable configuration from environment variables
pub fn extract_otel_config_from_env(endpoint: Option<String>) -> OtelConfig {
let endpoint = if let Some(endpoint) = endpoint {
if endpoint.is_empty() {
env::var("RUSTFS_OBSERVABILITY_ENDPOINT").unwrap_or_else(|_| "".to_string())
} else {
endpoint
}
} else {
env::var("RUSTFS_OBSERVABILITY_ENDPOINT").unwrap_or_else(|_| "".to_string())
};
let mut use_stdout = env::var("RUSTFS_OBSERVABILITY_USE_STDOUT")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(USE_STDOUT));
if endpoint.is_empty() {
use_stdout = Some(true);
}
OtelConfig {
endpoint,
use_stdout,
sample_ratio: env::var("RUSTFS_OBSERVABILITY_SAMPLE_RATIO")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(SAMPLE_RATIO)),
meter_interval: env::var("RUSTFS_OBSERVABILITY_METER_INTERVAL")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(METER_INTERVAL)),
service_name: env::var("RUSTFS_OBSERVABILITY_SERVICE_NAME")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(APP_NAME.to_string())),
service_version: env::var("RUSTFS_OBSERVABILITY_SERVICE_VERSION")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(SERVICE_VERSION.to_string())),
environment: env::var("RUSTFS_OBSERVABILITY_ENVIRONMENT")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(ENVIRONMENT.to_string())),
logger_level: env::var("RUSTFS_OBSERVABILITY_LOGGER_LEVEL")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(DEFAULT_LOG_LEVEL.to_string())),
local_logging_enabled: env::var("RUSTFS_OBSERVABILITY_LOCAL_LOGGING_ENABLED")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(false)),
log_directory: env::var("RUSTFS_OBSERVABILITY_LOG_DIRECTORY")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(DEFAULT_LOG_DIR.to_string())),
log_filename: env::var("RUSTFS_OBSERVABILITY_LOG_FILENAME")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(DEFAULT_LOG_FILENAME.to_string())),
log_rotation_size_mb: env::var("RUSTFS_OBSERVABILITY_LOG_ROTATION_SIZE_MB")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(DEFAULT_LOG_ROTATION_SIZE_MB)), // Default to 100 MB
log_rotation_time: env::var("RUSTFS_OBSERVABILITY_LOG_ROTATION_TIME")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(DEFAULT_LOG_ROTATION_TIME.to_string())), // Default to "Day"
log_keep_files: env::var("RUSTFS_OBSERVABILITY_LOG_KEEP_FILES")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(DEFAULT_LOG_KEEP_FILES)), // Default to keeping 30 log files
}
}
/// Create a new instance of OtelConfig with default values /// Create a new instance of OtelConfig with default values
/// ///
/// # Returns /// # Returns
/// A new instance of OtelConfig /// A new instance of OtelConfig
pub fn new() -> Self { pub fn new() -> Self {
extract_otel_config_from_env() Self::extract_otel_config_from_env(None)
} }
} }
@@ -122,6 +164,12 @@ pub struct WebhookSinkConfig {
impl WebhookSinkConfig { impl WebhookSinkConfig {
pub fn new() -> Self { pub fn new() -> Self {
Self::default()
}
}
impl Default for WebhookSinkConfig {
fn default() -> Self {
Self { Self {
endpoint: env::var("RUSTFS_SINKS_WEBHOOK_ENDPOINT") endpoint: env::var("RUSTFS_SINKS_WEBHOOK_ENDPOINT")
.ok() .ok()
@@ -137,12 +185,6 @@ impl WebhookSinkConfig {
} }
} }
impl Default for WebhookSinkConfig {
fn default() -> Self {
Self::new()
}
}
/// File Sink Configuration - Add buffering parameters /// File Sink Configuration - Add buffering parameters
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct FileSinkConfig { pub struct FileSinkConfig {
@@ -160,7 +202,6 @@ impl FileSinkConfig {
eprintln!("Failed to create log directory: {}", e); eprintln!("Failed to create log directory: {}", e);
return "rustfs/rustfs.log".to_string(); return "rustfs/rustfs.log".to_string();
} }
println!("Using log directory: {:?}", temp_dir);
temp_dir temp_dir
.join("rustfs.log") .join("rustfs.log")
.to_str() .to_str()
@@ -173,9 +214,18 @@ impl FileSinkConfig {
.ok() .ok()
.filter(|s| !s.trim().is_empty()) .filter(|s| !s.trim().is_empty())
.unwrap_or_else(Self::get_default_log_path), .unwrap_or_else(Self::get_default_log_path),
buffer_size: Some(8192), buffer_size: env::var("RUSTFS_SINKS_FILE_BUFFER_SIZE")
flush_interval_ms: Some(1000), .ok()
flush_threshold: Some(100), .and_then(|v| v.parse().ok())
.or(Some(8192)),
flush_interval_ms: env::var("RUSTFS_SINKS_FILE_FLUSH_INTERVAL_MS")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(1000)),
flush_threshold: env::var("RUSTFS_SINKS_FILE_FLUSH_THRESHOLD")
.ok()
.and_then(|v| v.parse().ok())
.or(Some(100)),
} }
} }
} }
@@ -260,6 +310,14 @@ impl AppConfig {
logger: Some(LoggerConfig::default()), logger: Some(LoggerConfig::default()),
} }
} }
pub fn new_with_endpoint(endpoint: Option<String>) -> Self {
Self {
observability: OtelConfig::extract_otel_config_from_env(endpoint),
sinks: vec![SinkConfig::new()],
logger: Some(LoggerConfig::new()),
}
}
} }
// implement default for AppConfig // implement default for AppConfig
@@ -269,9 +327,6 @@ impl Default for AppConfig {
} }
} }
/// Default configuration file name
const DEFAULT_CONFIG_FILE: &str = "obs";
/// Loading the configuration file /// Loading the configuration file
/// Supports TOML, YAML and .env formats, read in order by priority /// Supports TOML, YAML and .env formats, read in order by priority
/// ///
@@ -288,51 +343,6 @@ const DEFAULT_CONFIG_FILE: &str = "obs";
/// ///
/// let config = load_config(None); /// let config = load_config(None);
/// ``` /// ```
pub fn load_config(config_dir: Option<String>) -> AppConfig { pub fn load_config(config: Option<String>) -> AppConfig {
let config_dir = if let Some(path) = config_dir { AppConfig::new_with_endpoint(config)
// If a path is provided, check if it's empty
if path.is_empty() {
// If empty, use the default config file name
DEFAULT_CONFIG_FILE.to_string()
} else {
// Use the provided path
let path = std::path::Path::new(&path);
if path.extension().is_some() {
// If path has extension, use it as is (extension will be added by Config::builder)
path.with_extension("").to_string_lossy().into_owned()
} else {
// If path is a directory, append the default config file name
path.to_string_lossy().into_owned()
}
}
} else {
// If no path provided, use current directory + default config file
match env::current_dir() {
Ok(dir) => dir.join(DEFAULT_CONFIG_FILE).to_string_lossy().into_owned(),
Err(_) => {
eprintln!("Warning: Failed to get current directory, using default config file");
DEFAULT_CONFIG_FILE.to_string()
}
}
};
// Log using proper logging instead of println when possible
println!("Using config file base: {}", config_dir);
let app_config = Config::builder()
.add_source(File::with_name(config_dir.as_str()).format(FileFormat::Toml).required(false))
.add_source(File::with_name(config_dir.as_str()).format(FileFormat::Yaml).required(false))
.build()
.unwrap_or_default();
match app_config.try_deserialize::<AppConfig>() {
Ok(app_config) => {
println!("Parsed AppConfig: {:?}", app_config);
app_config
}
Err(e) => {
println!("Failed to deserialize config: {}", e);
AppConfig::default()
}
}
} }
+2 -2
View File
@@ -23,7 +23,7 @@ pub struct Collector {
impl Collector { impl Collector {
pub fn new(pid: Pid, meter: opentelemetry::metrics::Meter, interval_ms: u64) -> Result<Self, GlobalError> { pub fn new(pid: Pid, meter: opentelemetry::metrics::Meter, interval_ms: u64) -> Result<Self, GlobalError> {
let mut system = System::new_all(); let mut system = System::new();
let attributes = ProcessAttributes::new(pid, &mut system)?; let attributes = ProcessAttributes::new(pid, &mut system)?;
let core_count = System::physical_core_count().ok_or(GlobalError::CoreCountError)?; let core_count = System::physical_core_count().ok_or(GlobalError::CoreCountError)?;
let metrics = Metrics::new(&meter); let metrics = Metrics::new(&meter);
@@ -52,7 +52,7 @@ impl Collector {
fn collect(&mut self) -> Result<(), GlobalError> { fn collect(&mut self) -> Result<(), GlobalError> {
self.system self.system
.refresh_processes(sysinfo::ProcessesToUpdate::Some(&[self.pid]), true); .refresh_processes(sysinfo::ProcessesToUpdate::Some(&[self.pid]), false);
// refresh the network interface list and statistics // refresh the network interface list and statistics
self.networks.refresh(false); self.networks.refresh(false);
+300 -136
View File
@@ -1,4 +1,7 @@
// Added flexi_logger related dependencies
use crate::OtelConfig; use crate::OtelConfig;
use flexi_logger::{style, Age, Cleanup, Criterion, DeferredNow, FileSpec, LogSpecification, Naming, Record, WriteMode};
use nu_ansi_term::Color;
use opentelemetry::trace::TracerProvider; use opentelemetry::trace::TracerProvider;
use opentelemetry::{global, KeyValue}; use opentelemetry::{global, KeyValue};
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
@@ -13,7 +16,9 @@ use opentelemetry_semantic_conventions::{
attribute::{DEPLOYMENT_ENVIRONMENT_NAME, NETWORK_LOCAL_ADDRESS, SERVICE_VERSION as OTEL_SERVICE_VERSION}, attribute::{DEPLOYMENT_ENVIRONMENT_NAME, NETWORK_LOCAL_ADDRESS, SERVICE_VERSION as OTEL_SERVICE_VERSION},
SCHEMA_URL, SCHEMA_URL,
}; };
use rustfs_config::{APP_NAME, DEFAULT_LOG_LEVEL, ENVIRONMENT, METER_INTERVAL, SAMPLE_RATIO, SERVICE_VERSION, USE_STDOUT}; use rustfs_config::{
APP_NAME, DEFAULT_LOG_KEEP_FILES, DEFAULT_LOG_LEVEL, ENVIRONMENT, METER_INTERVAL, SAMPLE_RATIO, SERVICE_VERSION, USE_STDOUT,
};
use rustfs_utils::get_local_ip_with_default; use rustfs_utils::get_local_ip_with_default;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::borrow::Cow; use std::borrow::Cow;
@@ -47,23 +52,44 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilte
/// // When it's dropped, all telemetry components are properly shut down /// // When it's dropped, all telemetry components are properly shut down
/// drop(otel_guard); /// drop(otel_guard);
/// ``` /// ```
#[derive(Debug)] // Implement Debug trait correctly, rather than using derive, as some fields may not have implemented Debug
pub struct OtelGuard { pub struct OtelGuard {
tracer_provider: SdkTracerProvider, tracer_provider: Option<SdkTracerProvider>,
meter_provider: SdkMeterProvider, meter_provider: Option<SdkMeterProvider>,
logger_provider: SdkLoggerProvider, logger_provider: Option<SdkLoggerProvider>,
// Add a flexi_logger handle to keep the logging alive
_flexi_logger_handles: Option<flexi_logger::LoggerHandle>,
}
// Implement debug manually and avoid relying on all fields to implement debug
impl std::fmt::Debug for OtelGuard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OtelGuard")
.field("tracer_provider", &self.tracer_provider.is_some())
.field("meter_provider", &self.meter_provider.is_some())
.field("logger_provider", &self.logger_provider.is_some())
.field("_flexi_logger_handles", &self._flexi_logger_handles.is_some())
.finish()
}
} }
impl Drop for OtelGuard { impl Drop for OtelGuard {
fn drop(&mut self) { fn drop(&mut self) {
if let Err(err) = self.tracer_provider.shutdown() { if let Some(provider) = self.tracer_provider.take() {
eprintln!("Tracer shutdown error: {:?}", err); if let Err(err) = provider.shutdown() {
eprintln!("Tracer shutdown error: {:?}", err);
}
} }
if let Err(err) = self.meter_provider.shutdown() {
eprintln!("Meter shutdown error: {:?}", err); if let Some(provider) = self.meter_provider.take() {
if let Err(err) = provider.shutdown() {
eprintln!("Meter shutdown error: {:?}", err);
}
} }
if let Err(err) = self.logger_provider.shutdown() { if let Some(provider) = self.logger_provider.take() {
eprintln!("Logger shutdown error: {:?}", err); if let Err(err) = provider.shutdown() {
eprintln!("Logger shutdown error: {:?}", err);
}
} }
} }
} }
@@ -106,156 +132,247 @@ pub fn init_telemetry(config: &OtelConfig) -> OtelGuard {
let service_name = config.service_name.as_deref().unwrap_or(APP_NAME); let service_name = config.service_name.as_deref().unwrap_or(APP_NAME);
let environment = config.environment.as_deref().unwrap_or(ENVIRONMENT); let environment = config.environment.as_deref().unwrap_or(ENVIRONMENT);
// Pre-create resource objects to avoid repeated construction // Configure flexi_logger to cut by time and size
let res = resource(config); let mut flexi_logger_handle = None;
if !endpoint.is_empty() {
// Pre-create resource objects to avoid repeated construction
let res = resource(config);
// initialize tracer provider // initialize tracer provider
let tracer_provider = { let tracer_provider = {
let sample_ratio = config.sample_ratio.unwrap_or(SAMPLE_RATIO); let sample_ratio = config.sample_ratio.unwrap_or(SAMPLE_RATIO);
let sampler = if sample_ratio > 0.0 && sample_ratio < 1.0 { let sampler = if sample_ratio > 0.0 && sample_ratio < 1.0 {
Sampler::TraceIdRatioBased(sample_ratio) Sampler::TraceIdRatioBased(sample_ratio)
} else { } else {
Sampler::AlwaysOn Sampler::AlwaysOn
};
let builder = SdkTracerProvider::builder()
.with_sampler(sampler)
.with_id_generator(RandomIdGenerator::default())
.with_resource(res.clone());
let tracer_provider = if endpoint.is_empty() {
builder
.with_batch_exporter(opentelemetry_stdout::SpanExporter::default())
.build()
} else {
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.unwrap();
let builder = if use_stdout {
builder
.with_batch_exporter(exporter)
.with_batch_exporter(opentelemetry_stdout::SpanExporter::default())
} else {
builder.with_batch_exporter(exporter)
};
builder.build()
};
global::set_tracer_provider(tracer_provider.clone());
tracer_provider
}; };
let builder = SdkTracerProvider::builder() // initialize meter provider
.with_sampler(sampler) let meter_provider = {
.with_id_generator(RandomIdGenerator::default()) let mut builder = MeterProviderBuilder::default().with_resource(res.clone());
.with_resource(res.clone());
let tracer_provider = if endpoint.is_empty() { if endpoint.is_empty() {
builder builder = builder.with_reader(create_periodic_reader(meter_interval));
.with_batch_exporter(opentelemetry_stdout::SpanExporter::default())
.build()
} else {
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.unwrap();
let builder = if use_stdout {
builder
.with_batch_exporter(exporter)
.with_batch_exporter(opentelemetry_stdout::SpanExporter::default())
} else { } else {
builder.with_batch_exporter(exporter) let exporter = opentelemetry_otlp::MetricExporter::builder()
}; .with_tonic()
.with_endpoint(endpoint)
.with_temporality(opentelemetry_sdk::metrics::Temporality::default())
.build()
.unwrap();
builder = builder.with_reader(
PeriodicReader::builder(exporter)
.with_interval(std::time::Duration::from_secs(meter_interval))
.build(),
);
if use_stdout {
builder = builder.with_reader(create_periodic_reader(meter_interval));
}
}
let meter_provider = builder.build();
global::set_meter_provider(meter_provider.clone());
meter_provider
};
// initialize logger provider
let logger_provider = {
let mut builder = SdkLoggerProvider::builder().with_resource(res);
if endpoint.is_empty() {
builder = builder.with_batch_exporter(opentelemetry_stdout::LogExporter::default());
} else {
let exporter = opentelemetry_otlp::LogExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.unwrap();
builder = builder.with_batch_exporter(exporter);
if use_stdout {
builder = builder.with_batch_exporter(opentelemetry_stdout::LogExporter::default());
}
}
builder.build() builder.build()
}; };
global::set_tracer_provider(tracer_provider.clone()); // configuring tracing
tracer_provider {
}; // configure the formatting layer
let fmt_layer = {
let enable_color = std::io::stdout().is_terminal();
let mut layer = tracing_subscriber::fmt::layer()
.with_target(true)
.with_ansi(enable_color)
.with_thread_names(true)
.with_thread_ids(true)
.with_file(true)
.with_line_number(true);
// initialize meter provider // Only add full span events tracking in the development environment
let meter_provider = { if environment != ENVIRONMENT {
let mut builder = MeterProviderBuilder::default().with_resource(res.clone()); layer = layer.with_span_events(FmtSpan::FULL);
}
if endpoint.is_empty() { layer.with_filter(build_env_filter(logger_level, None))
builder = builder.with_reader(create_periodic_reader(meter_interval)); };
} else {
let exporter = opentelemetry_otlp::MetricExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.with_temporality(opentelemetry_sdk::metrics::Temporality::default())
.build()
.unwrap();
builder = builder.with_reader( let filter = build_env_filter(logger_level, None);
PeriodicReader::builder(exporter) let otel_filter = build_env_filter(logger_level, None);
.with_interval(std::time::Duration::from_secs(meter_interval)) let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider).with_filter(otel_filter);
.build(), let tracer = tracer_provider.tracer(Cow::Borrowed(service_name).to_string());
);
if use_stdout { // Configure registry to avoid repeated calls to filter methods
builder = builder.with_reader(create_periodic_reader(meter_interval)); tracing_subscriber::registry()
.with(filter)
.with(ErrorLayer::default())
.with(if config.local_logging_enabled.unwrap_or(false) {
Some(fmt_layer)
} else {
None
})
.with(OpenTelemetryLayer::new(tracer))
.with(otel_layer)
.with(MetricsLayer::new(meter_provider.clone()))
.init();
if !endpoint.is_empty() {
info!(
"OpenTelemetry telemetry initialized with OTLP endpoint: {}, logger_level: {},RUST_LOG env: {}",
endpoint,
logger_level,
std::env::var("RUST_LOG").unwrap_or_else(|_| "Not set".to_string())
);
} }
} }
let meter_provider = builder.build(); OtelGuard {
global::set_meter_provider(meter_provider.clone()); tracer_provider: Some(tracer_provider),
meter_provider meter_provider: Some(meter_provider),
}; logger_provider: Some(logger_provider),
_flexi_logger_handles: flexi_logger_handle,
// initialize logger provider
let logger_provider = {
let mut builder = SdkLoggerProvider::builder().with_resource(res);
if endpoint.is_empty() {
builder = builder.with_batch_exporter(opentelemetry_stdout::LogExporter::default());
} else {
let exporter = opentelemetry_otlp::LogExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()
.unwrap();
builder = builder.with_batch_exporter(exporter);
if use_stdout {
builder = builder.with_batch_exporter(opentelemetry_stdout::LogExporter::default());
}
} }
} else {
// Obtain the log directory and file name configuration
let log_directory = config.log_directory.as_deref().unwrap_or("logs");
let log_filename = config.log_filename.as_deref().unwrap_or(service_name);
builder.build() // Build log cutting conditions
}; let rotation_criterion = match (config.log_rotation_time.as_deref(), config.log_rotation_size_mb) {
// Cut by time and size at the same time
// configuring tracing (Some(time), Some(size)) => {
{ let age = match time.to_lowercase().as_str() {
// configure the formatting layer "hour" => Age::Hour,
let fmt_layer = { "day" => Age::Day,
let enable_color = std::io::stdout().is_terminal(); "minute" => Age::Minute,
let mut layer = tracing_subscriber::fmt::layer() "second" => Age::Second,
.with_target(true) _ => Age::Day, // The default is by day
.with_ansi(enable_color) };
.with_thread_names(true) Criterion::AgeOrSize(age, size * 1024 * 1024) // Convert to bytes
.with_thread_ids(true)
.with_file(true)
.with_line_number(true);
// Only add full span events tracking in the development environment
if environment != ENVIRONMENT {
layer = layer.with_span_events(FmtSpan::FULL);
} }
// Cut by time only
layer.with_filter(build_env_filter(logger_level, None)) (Some(time), None) => {
let age = match time.to_lowercase().as_str() {
"hour" => Age::Hour,
"day" => Age::Day,
"minute" => Age::Minute,
"second" => Age::Second,
_ => Age::Day, // The default is by day
};
Criterion::Age(age)
}
// Cut by size only
(None, Some(size)) => {
Criterion::Size(size * 1024 * 1024) // Convert to bytes
}
// By default, it is cut by the day
_ => Criterion::Age(Age::Day),
}; };
let filter = build_env_filter(logger_level, None); // The number of log files retained
let otel_filter = build_env_filter(logger_level, None); let keep_files = config.log_keep_files.unwrap_or(DEFAULT_LOG_KEEP_FILES);
let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider).with_filter(otel_filter);
let tracer = tracer_provider.tracer(Cow::Borrowed(service_name).to_string());
// Configure registry to avoid repeated calls to filter methods // Parsing the log level
tracing_subscriber::registry() let log_spec = LogSpecification::parse(logger_level).unwrap_or(LogSpecification::info());
.with(filter)
.with(ErrorLayer::default())
.with(if config.local_logging_enabled.unwrap_or(false) {
Some(fmt_layer)
} else {
None
})
.with(OpenTelemetryLayer::new(tracer))
.with(otel_layer)
.with(MetricsLayer::new(meter_provider.clone()))
.init();
if !endpoint.is_empty() { // Configure the flexi_logger
info!( let flexi_logger_result = flexi_logger::Logger::with(log_spec)
"OpenTelemetry telemetry initialized with OTLP endpoint: {}, logger_level: {},RUST_LOG env: {}", .log_to_file(
endpoint, FileSpec::default()
logger_level, .directory(log_directory)
std::env::var("RUST_LOG").unwrap_or_else(|_| "Not set".to_string()) .basename(log_filename)
); .suffix("log"),
)
.rotate(rotation_criterion, Naming::Timestamps, Cleanup::KeepLogFiles(keep_files.into()))
.format_for_files(format_for_file) // Add a custom formatting function for file output
.duplicate_to_stdout(flexi_logger::Duplicate::Info)
.format_for_stdout(format_with_color) // Add a custom formatting function for terminal output
.write_mode(WriteMode::Async)
.start();
if let Ok(logger) = flexi_logger_result {
// Save the logger handle to keep the logging
flexi_logger_handle = Some(logger);
info!("Flexi logger initialized with file logging to {}/{}.log", log_directory, log_filename);
// Log logging of log cutting conditions
match (config.log_rotation_time.as_deref(), config.log_rotation_size_mb) {
(Some(time), Some(size)) => info!(
"Log rotation configured for: every {} or when size exceeds {}MB, keeping {} files",
time, size, keep_files
),
(Some(time), None) => info!("Log rotation configured for: every {}, keeping {} files", time, keep_files),
(None, Some(size)) => {
info!("Log rotation configured for: when size exceeds {}MB, keeping {} files", size, keep_files)
}
_ => info!("Log rotation configured for: daily, keeping {} files", keep_files),
}
} else {
eprintln!("Failed to initialize flexi_logger: {:?}", flexi_logger_result.err());
} }
}
OtelGuard { OtelGuard {
tracer_provider, tracer_provider: None,
meter_provider, meter_provider: None,
logger_provider, logger_provider: None,
_flexi_logger_handles: flexi_logger_handle,
}
} }
} }
@@ -271,3 +388,50 @@ fn build_env_filter(logger_level: &str, default_level: Option<&str>) -> EnvFilte
filter filter
} }
/// Custom Log Formatter Function - Terminal Output (with Color)
fn format_with_color(w: &mut dyn std::io::Write, now: &mut DeferredNow, record: &Record) -> Result<(), std::io::Error> {
let level = record.level();
let level_style = style(level);
// Get the current thread information
let binding = std::thread::current();
let thread_name = binding.name().unwrap_or("unnamed");
let thread_id = format!("{:?}", std::thread::current().id());
writeln!(
w,
"{} {} [{}] [{}:{}] [{}:{}] {}",
now.now().format("%Y-%m-%d %H:%M:%S%.6f"),
level_style.paint(level.to_string()),
Color::Magenta.paint(record.target()),
Color::Blue.paint(record.file().unwrap_or("unknown")),
Color::Blue.paint(record.line().unwrap_or(0).to_string()),
Color::Green.paint(thread_name),
Color::Green.paint(thread_id),
record.args()
)
}
/// Custom Log Formatter - File Output (No Color)
fn format_for_file(w: &mut dyn std::io::Write, now: &mut DeferredNow, record: &Record) -> Result<(), std::io::Error> {
let level = record.level();
// Get the current thread information
let binding = std::thread::current();
let thread_name = binding.name().unwrap_or("unnamed");
let thread_id = format!("{:?}", std::thread::current().id());
writeln!(
w,
"{} {} [{}] [{}:{}] [{}:{}] {}",
now.now().format("%Y-%m-%d %H:%M:%S%.6f"),
level,
record.target(),
record.file().unwrap_or("unknown"),
record.line().unwrap_or(0),
thread_name,
thread_id,
record.args()
)
}
+2 -2
View File
@@ -64,8 +64,8 @@ pub struct Opt {
/// Observability configuration file /// Observability configuration file
/// Default value: config/obs.toml /// Default value: config/obs.toml
#[arg(long, default_value_t = rustfs_config::DEFAULT_OBS_CONFIG.to_string(), env = "RUSTFS_OBS_CONFIG")] #[arg(long, default_value_t = rustfs_config::DEFAULT_OBS_ENDPOINT.to_string(), env = "RUSTFS_OBS_ENDPOINT")]
pub obs_config: String, pub obs_endpoint: String,
/// tls path for rustfs api and console. /// tls path for rustfs api and console.
#[arg(long, env = "RUSTFS_TLS_PATH")] #[arg(long, env = "RUSTFS_TLS_PATH")]
+1 -1
View File
@@ -103,7 +103,7 @@ async fn main() -> Result<()> {
init_license(opt.license.clone()); init_license(opt.license.clone());
// Load the configuration file // Load the configuration file
let config = load_config(Some(opt.clone().obs_config)); let config = load_config(Some(opt.clone().obs_endpoint));
// Initialize Observability // Initialize Observability
let (_logger, guard) = init_obs(config.clone()).await; let (_logger, guard) = init_obs(config.clone()).await;
+28 -27
View File
@@ -31,44 +31,45 @@ export RUSTFS_VOLUMES="./target/volume/test{0...4}"
# export RUSTFS_VOLUMES="./target/volume/test" # export RUSTFS_VOLUMES="./target/volume/test"
export RUSTFS_ADDRESS=":9000" export RUSTFS_ADDRESS=":9000"
export RUSTFS_CONSOLE_ENABLE=true export RUSTFS_CONSOLE_ENABLE=true
export RUSTFS_CONSOLE_ADDRESS=":9002" export RUSTFS_CONSOLE_ADDRESS=":9001"
# export RUSTFS_SERVER_DOMAINS="localhost:9000" # export RUSTFS_SERVER_DOMAINS="localhost:9000"
# HTTPS 证书目录 # HTTPS 证书目录
# export RUSTFS_TLS_PATH="./deploy/certs" # export RUSTFS_TLS_PATH="./deploy/certs"
# 具体路径修改为配置文件真实路径,obs.example.toml 仅供参考 其中 `RUSTFS_OBS_CONFIG` 和下面变量二选一 # 具体路径修改为配置文件真实路径,obs.example.toml 仅供参考 其中 `RUSTFS_OBS_CONFIG` 和下面变量二选一
export RUSTFS_OBS_CONFIG="./deploy/config/obs.example.toml" # export RUSTFS_OBS_ENDPOINT=http://localhost:4317
# 如下变量需要必须参数都有值才可以,以及会覆盖配置文件中的值 # 如下变量需要必须参数都有值才可以,以及会覆盖配置文件中的值
#export RUSTFS__OBSERVABILITY__ENDPOINT=http://localhost:4317 #export RUSTFS_OBSERVABILITY_ENDPOINT=http://localhost:4317 # OpenTelemetry Collector 的地址
#export RUSTFS__OBSERVABILITY__USE_STDOUT=false #export RUSTFS_OBSERVABILITY_USE_STDOUT=false # 是否使用标准输出
#export RUSTFS__OBSERVABILITY__SAMPLE_RATIO=2.0 #export RUSTFS_OBSERVABILITY_SAMPLE_RATIO=2.0 # 采样率,0.0-1.0之间,0.0表示不采样,1.0表示全部采样
#export RUSTFS__OBSERVABILITY__METER_INTERVAL=31 #export RUSTFS_OBSERVABILITY_METER_INTERVAL=1 # 采样间隔,单位为秒
#export RUSTFS__OBSERVABILITY__SERVICE_NAME=rustfs #export RUSTFS_OBSERVABILITY_SERVICE_NAME=rustfs # 服务名称
#export RUSTFS__OBSERVABILITY__SERVICE_VERSION=0.1.0 #export RUSTFS_OBSERVABILITY_SERVICE_VERSION=0.1.0 # 服务版本
#export RUSTFS__OBSERVABILITY__ENVIRONMENT=develop #export RUSTFS_OBSERVABILITY_ENVIRONMENT=develop # 环境名称
#export RUSTFS__OBSERVABILITY__LOGGER_LEVEL=debug #export RUSTFS_OBSERVABILITY_LOGGER_LEVEL=debug # 日志级别,支持 trace, debug, info, warn, error
#export RUSTFS__OBSERVABILITY__LOCAL_LOGGING_ENABLED=true export RUSTFS_OBSERVABILITY_LOCAL_LOGGING_ENABLED=true # 是否启用本地日志记录
export RUSTFS_OBSERVABILITY_LOG_DIRECTORY="./deploy/logs" # Log directory
export RUSTFS_OBSERVABILITY_LOG_ROTATION_TIME="minute" # Log rotation time unit, can be "second", "minute", "hour", "day"
export RUSTFS_OBSERVABILITY_LOG_ROTATION_SIZE_MB=1 # Log rotation size in MB
# #
#export RUSTFS__SINKS_0__type=File #export RUSTFS_SINKS_FILE_PATH=./deploy/logs/rustfs.log
#export RUSTFS__SINKS_0__path=./deploy/logs/rustfs.log #export RUSTFS_SINKS_FILE_BUFFER_SIZE=12
#export RUSTFS__SINKS_0__buffer_size=12 #export RUSTFS_SINKS_FILE_FLUSH_INTERVAL_MS=1000
#export RUSTFS__SINKS_0__flush_interval_ms=1000 #export RUSTFS_SINKS_FILE_FLUSH_THRESHOLD=100
#export RUSTFS__SINKS_0__flush_threshold=100
# #
#export RUSTFS__SINKS_1__type=Kakfa #export RUSTFS_SINKS_KAFKA_BROKERS=localhost:9092
#export RUSTFS__SINKS_1__brokers=localhost:9092 #export RUSTFS_SINKS_KAFKA_TOPIC=logs
#export RUSTFS__SINKS_1__topic=logs #export RUSTFS_SINKS_KAFKA_BATCH_SIZE=100
#export RUSTFS__SINKS_1__batch_size=100 #export RUSTFS_SINKS_KAFKA_BATCH_TIMEOUT_MS=1000
#export RUSTFS__SINKS_1__batch_timeout_ms=1000
# #
#export RUSTFS__SINKS_2__type=Webhook #export RUSTFS_SINKS_WEBHOOK_ENDPOINT=http://localhost:8080/webhook
#export RUSTFS__SINKS_2__endpoint=http://localhost:8080/webhook #export RUSTFS_SINKS_WEBHOOK_AUTH_TOKEN=you-auth-token
#export RUSTFS__SINKS_2__auth_token=you-auth-token #export RUSTFS_SINKS_WEBHOOK_BATCH_SIZE=100
#export RUSTFS__SINKS_2__batch_size=100 #export RUSTFS_SINKS_WEBHOOK_BATCH_TIMEOUT_MS=1000
#export RUSTFS__SINKS_2__batch_timeout_ms=1000
# #
#export RUSTFS__LOGGER__QUEUE_CAPACITY=10 #export RUSTFS_LOGGER_QUEUE_CAPACITY=10
export OTEL_INSTRUMENTATION_NAME="rustfs" export OTEL_INSTRUMENTATION_NAME="rustfs"
export OTEL_INSTRUMENTATION_VERSION="0.1.1" export OTEL_INSTRUMENTATION_VERSION="0.1.1"