diff --git a/.env.example b/.env.example index fa5b3438..1a7e721b 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,11 @@ ADMINS=ID1,ID2 # Database DATABASE_URL=mongodb+srv://:@.mongodb.net +# OpenTelemetry +OTLP_ENDPOINT=https://openobserve.example.com/api/default +OTLP_TOKEN=ASUPERSECRETTOKEN +OTLP_STREAM=discord-analytics + #Tokens DISCORD_TOKEN=ASUPERSECRETTOKEN JWT_SECRET=ASUPERSECRETSECRET diff --git a/Cargo.lock b/Cargo.lock index feea0620..1524a4d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,9 +36,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.11.2" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" dependencies = [ "actix-codec", "actix-rt", @@ -77,9 +77,9 @@ dependencies = [ [[package]] name = "actix-router" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" dependencies = [ "bytestring", "cfg-if", @@ -138,9 +138,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.12.1" +version = "4.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" +checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" dependencies = [ "actix-codec", "actix-http", @@ -272,9 +272,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", "zeroize", @@ -382,9 +382,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5c6f81257d10a0f602a294ae4182251151ff97dbb504ef9afcdda4a64b24d9b4" [[package]] name = "bytes" @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -767,8 +767,11 @@ dependencies = [ "hex", "jsonwebtoken", "mongodb", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry_sdk", "regex", - "reqwest", + "reqwest 0.13.2", "ring", "s3", "serde", @@ -840,6 +843,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -993,6 +1002,17 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.32" @@ -1040,9 +1060,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -1299,6 +1319,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1513,6 +1534,15 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -1922,6 +1952,88 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.18", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "opentelemetry-http" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +dependencies = [ + "async-trait", + "bytes", + "http 1.4.0", + "opentelemetry", + "reqwest 0.12.28", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" +dependencies = [ + "http 1.4.0", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest 0.12.28", + "thiserror 2.0.18", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", + "tonic-prost", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.2", + "thiserror 2.0.18", +] + [[package]] name = "p256" version = "0.13.2" @@ -2110,11 +2222,34 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quick-xml" -version = "0.39.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e3bf4aa9d243beeb01a7b3bc30b77cfe2c44e24ec02d751a7104a53c2c49a1" +checksum = "bd58c6a1fc307e1092aa0bb23d204ca4d1f021764142cd0424dccc84d2d5d106" dependencies = [ "memchr", "serde", @@ -2317,6 +2452,44 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "reqwest" version = "0.13.2" @@ -3082,6 +3255,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -3096,6 +3280,38 @@ dependencies = [ "tokio", ] +[[package]] +name = "tonic" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0" +dependencies = [ + "async-trait", + "base64", + "bytes", + "http 1.4.0", + "http-body", + "http-body-util", + "percent-encoding", + "pin-project", + "sync_wrapper", + "tokio-stream", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f" +dependencies = [ + "bytes", + "prost", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -3238,9 +3454,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" diff --git a/Cargo.toml b/Cargo.toml index 153761e9..65d6245e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,9 @@ s3 = { version = "0.1.15", default-features = false, features = [ ] } serde = { version = "1.0.228", default-features = false, features = ["derive"] } serde_json = { version = "1.0.149", default-features = false } +opentelemetry-appender-tracing = { version = "0.31.1", default-features = false } +opentelemetry-otlp = { version = "0.31.0", default-features = false, features = ["logs", "http-proto", "reqwest-rustls"] } +opentelemetry_sdk = { version = "0.31.0", default-features = false, features = ["logs"] } tokio = { version = "1.49.0", default-features = false, features = [ "rt-multi-thread", "macros", diff --git a/src/config/env.rs b/src/config/env.rs index 38765cfe..dc7ba7e4 100644 --- a/src/config/env.rs +++ b/src/config/env.rs @@ -1,6 +1,6 @@ use std::{env, sync::OnceLock}; -use anyhow::{Error, Result}; +use anyhow::{Error, Result, anyhow}; use dotenvy::dotenv; #[derive(Debug)] @@ -14,6 +14,11 @@ pub struct EnvConfig { // Database pub database_url: String, + // OpenTelemetry + pub otlp_endpoint: Option, + pub otlp_token: Option, + pub otlp_stream: Option, + // Tokens pub discord_token: String, pub jwt_secret: String, @@ -70,6 +75,20 @@ pub fn init_env() -> Result<&'static EnvConfig> { let database_url = get_var("DATABASE_URL")?; + let (otlp_endpoint, otlp_token, otlp_stream) = match ( + get_var("OTLP_ENDPOINT"), + get_var("OTLP_TOKEN"), + get_var("OTLP_STREAM"), + ) { + (Ok(endpoint), Ok(token), Ok(stream)) => (Some(endpoint), Some(token), Some(stream)), + (Err(_), Err(_), Err(_)) => (None, None, None), + _ => { + return Err(anyhow!( + "One of these env vars are missing: OTLP_ENDPOINT, OTLP_TOKEN or OTLP_STREAM" + )); + } + }; + let discord_token = get_var("DISCORD_TOKEN")?; let jwt_secret = get_var("JWT_SECRET")?; @@ -97,6 +116,9 @@ pub fn init_env() -> Result<&'static EnvConfig> { client_url, admins, database_url, + otlp_endpoint, + otlp_token, + otlp_stream, discord_token, jwt_secret, client_secret, diff --git a/src/utils/logger/mod.rs b/src/utils/logger/mod.rs index 6e5d1723..5ccf53fb 100644 --- a/src/utils/logger/mod.rs +++ b/src/utils/logger/mod.rs @@ -1,11 +1,16 @@ mod codes; -use std::io; +use std::{collections::HashMap, io}; use anyhow::Result; +use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; +use opentelemetry_otlp::{Protocol, WithExportConfig, WithHttpConfig, LogExporter}; +use opentelemetry_sdk::{Resource, logs::SdkLoggerProvider}; use tracing::{Level, level_filters::LevelFilter}; use tracing_subscriber::{fmt, layer::SubscriberExt, prelude::*, registry}; +use crate::app_env; + pub struct Logger { level: Level, dev_mode: bool, @@ -32,7 +37,38 @@ impl Logger { .with_ansi(self.dev_mode) .with_filter(LevelFilter::from_level(self.level)); - registry().with(stdout_layer).init(); + if let (Some(endpoint), Some(token), Some(stream)) = ( + app_env!().otlp_endpoint.clone(), + app_env!().otlp_token.clone(), + app_env!().otlp_stream.clone(), + ) && !self.dev_mode + { + let mut headers = HashMap::new(); + headers.insert( + String::from("Authorization"), + String::from(format!("Basic {}", token)), + ); + headers.insert("stream-name".to_string(), stream); + + let exporter = LogExporter::builder() + .with_http() + .with_protocol(Protocol::HttpBinary) + .with_headers(headers) + .with_endpoint(format!("{}/v1/logs", endpoint)) + .build()?; + + let resource = Resource::builder().with_service_name("api").build(); + + let provider = SdkLoggerProvider::builder() + .with_batch_exporter(exporter) + .with_resource(resource) + .build(); + let otlp_layer = OpenTelemetryTracingBridge::new(&provider); + + registry().with(stdout_layer).with(otlp_layer).init(); + } else { + registry().with(stdout_layer).init() + } Ok(()) }