diff --git a/Cargo.lock b/Cargo.lock index 7ab2db9..61606e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,8 +4,13 @@ version = 4 [[package]] name = "RhythmWorks" -version = "0.3.0" +version = "0.4.0" dependencies = [ + "opentelemetry 0.27.1", + "opentelemetry-otlp", + "opentelemetry-proto", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", "reqwest", "semver", "serde", @@ -14,6 +19,10 @@ dependencies = [ "songbird", "symphonia", "tokio", + "tonic", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", "yt-dlp", ] @@ -82,6 +91,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "arbitrary" version = "1.4.2" @@ -100,6 +115,28 @@ dependencies = [ "serde", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -152,6 +189,53 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "base64" version = "0.22.1" @@ -1243,6 +1327,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.8.1" @@ -1257,6 +1347,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -1275,6 +1366,7 @@ dependencies = [ "hyper", "hyper-util", "rustls 0.23.35", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", @@ -1282,6 +1374,19 @@ dependencies = [ "webpki-roots 1.0.4", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1515,6 +1620,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.16" @@ -1683,6 +1797,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "md-5" version = "0.10.6" @@ -1937,6 +2057,108 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab70038c28ed37b97d8ed414b6429d343a8bbf44c9f79ec854f3a643029ba6d7" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 1.0.69", + "tracing", +] + +[[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.17", +] + +[[package]] +name = "opentelemetry-http" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a8a7f5f6ba7c1b286c2fbca0454eaba116f63bbe69ed250b642d36fbb04d80" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry 0.27.1", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76" +dependencies = [ + "async-trait", + "futures-core", + "http", + "opentelemetry 0.27.1", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "thiserror 1.0.69", + "tokio", + "tonic", + "tracing", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e05acbfada5ec79023c85368af14abd0b307c015e9064d249b2a950ef459a6" +dependencies = [ + "hex", + "opentelemetry 0.27.1", + "opentelemetry_sdk", + "prost", + "serde", + "tonic", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc1b6902ff63b32ef6c489e8048c5e253e2e4a803ea3ea7e783914536eb15c52" + +[[package]] +name = "opentelemetry_sdk" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231e9d6ceef9b0b2546ddf52335785ce41252bc7474ee8ba05bfad277be13ab8" +dependencies = [ + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "opentelemetry 0.27.1", + "percent-encoding", + "rand 0.8.5", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "ordered-float" version = "2.10.1" @@ -2195,6 +2417,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "pulldown-cmark" version = "0.9.6" @@ -2442,6 +2687,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls 0.23.35", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -2451,7 +2697,7 @@ dependencies = [ "tokio-native-tls", "tokio-rustls 0.26.4", "tokio-util", - "tower", + "tower 0.5.2", "tower-http", "tower-service", "url", @@ -2581,6 +2827,7 @@ version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -2601,6 +2848,15 @@ dependencies = [ "security-framework 3.5.1", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.13.2" @@ -3963,6 +4219,59 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "rustls-native-certs", + "rustls-pemfile", + "socket2 0.5.10", + "tokio", + "tokio-rustls 0.26.4", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.2" @@ -3991,7 +4300,7 @@ dependencies = [ "http-body", "iri-string", "pin-project-lite", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -4062,6 +4371,22 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +dependencies = [ + "js-sys", + "opentelemetry 0.31.0", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-subscriber" version = "0.3.22" diff --git a/Cargo.toml b/Cargo.toml index 1e75e8c..dc94c5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,14 @@ [package] name = "RhythmWorks" -version = "0.3.0" +version = "0.4.0" edition = "2024" [dependencies] +opentelemetry = { version = "0.27.0", features = ["logs", "metrics", "trace"] } +opentelemetry-otlp = { version = "0.27.0", features = ["trace", "metrics", "grpc-tonic", "http-proto", "tls", "reqwest-client", "reqwest-rustls"] } +opentelemetry-proto = "0.27.0" +opentelemetry-semantic-conventions = "0.27.0" +opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio", "trace"] } reqwest = { version = "0.12.28", features = ["blocking", "json"] } semver = "1.0.27" serde = "1.0.228" @@ -12,6 +17,10 @@ serenity = { version = "0.12.5", features = ["client", "gateway", "voice"] } songbird = { version = "0.5.0", features = ["builtin-queue", "driver", "serenity"] } symphonia = { version = "0.5.5", features = ["aac", "alac", "isomp4", "mp3"] } tokio = "1.48.0" +tonic = { version = "0.12.3", features = ["tls-roots"] } +tracing = "0.1.41" +tracing-opentelemetry = "0.32.0" +tracing-subscriber = "0.3.20" yt-dlp = "1.4.7" diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..1b5246d --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +cargo clean +cargo fmt +cargo clippy --fix --all-targets --allow-dirty +cargo build --release +cargo build --release --target x86_64-pc-windows-gnu \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c1f7019..2e6ea23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,14 @@ use modules::*; +use opentelemetry::KeyValue; +use opentelemetry::global; +use opentelemetry::global::ObjectSafeSpan; +use opentelemetry::trace::SpanKind; +use opentelemetry::trace::Status; +use opentelemetry::trace::TraceContextExt; +use opentelemetry::trace::Tracer; use serenity::all::ChannelId; use serenity::async_trait; +use serenity::futures::channel::oneshot::channel; use serenity::model::channel::Message; use serenity::model::gateway::Ready; use serenity::prelude::*; @@ -32,13 +40,50 @@ impl EventHandler for Handler { println!("Error sending message: {why:?}"); } if msg.content == "!join" { + let tracer = global::tracer("RhythmWorks"); + let span = tracer.start("Join Message Received"); + let cx = opentelemetry::Context::current_with_span(span); let guild_id = match msg.guild_id { - Some(g) => g, - None => return, - }; - let channel_id = match get_user_voice_channel(&ctx, &msg) { - Some(c) => c, + Some(g) => { + cx.span().add_event( + "Received Guild ID", + vec![KeyValue::new("Server ID", g.to_string())], + ); + cx.span().set_status(opentelemetry::trace::Status::Ok); + cx.span() + .set_attribute(KeyValue::new("Guild_ID", g.to_string())); + g + } None => { + cx.span().add_event( + "Failed to Retreive Guild ID", + vec![KeyValue::new("Server ID", "None")], + ); + cx.span() + .set_status(opentelemetry::trace::Status::error("Failed")); + return; + } + }; + + let channel_id = match get_user_voice_channel(&ctx, &msg) { + Some(c) => { + cx.span().add_event( + "Received Channel ID", + vec![KeyValue::new("Channel ID", c.to_string())], + ); + cx.span().set_status(Status::Ok); + c + } + None => { + cx.span().add_event( + "Failed to Retreive Channel ID", + vec![KeyValue::new("Channel ID", "None")], + ); + cx.span() + .set_status(opentelemetry::trace::Status::error(format!( + "{} not in Voice Channel", + msg.author.to_string() + ))); let _ = msg .channel_id .say(&ctx.http, "You must be in a voice channel") @@ -46,7 +91,6 @@ impl EventHandler for Handler { return; } }; - let manager = get(&ctx).await.expect("Songbird not initialized").clone(); let _ = manager.join(guild_id, channel_id).await; @@ -55,6 +99,11 @@ impl EventHandler for Handler { .channel_id .say(&ctx.http, "Joined your voice channel!") .await; + cx.span().add_event( + "Joined Voice Channel", + vec![KeyValue::new("Voice Channel", msg.channel_id.to_string())], + ); + cx.span().set_status(Status::Ok); } if msg.content == "!leave" { let guild_id = match msg.guild_id { @@ -71,6 +120,8 @@ impl EventHandler for Handler { } } if msg.content.starts_with("!play") { + let tracer = global::tracer("RhythmWorks"); + let span = tracer.start("Play Message Received"); let mut yt_url = msg .content .strip_prefix("!play ") @@ -84,7 +135,30 @@ impl EventHandler for Handler { let _ = msg.reply(&ctx, "Please provide a YouTube URL.").await; return; } - let guild_id = msg.guild_id.unwrap(); + let cx = opentelemetry::Context::current_with_span(span); + cx.span() + .set_attribute(KeyValue::new("Youtube_URL", yt_url.to_string())); + let guild_id = match msg.guild_id { + Some(g) => { + cx.span().add_event( + "Received Guild ID", + vec![KeyValue::new("Server ID", g.to_string())], + ); + cx.span().set_status(opentelemetry::trace::Status::Ok); + cx.span() + .set_attribute(KeyValue::new("Guild_ID", g.to_string())); + g + } + None => { + cx.span().add_event( + "Failed to Retreive Guild ID", + vec![KeyValue::new("Server ID", "None")], + ); + cx.span() + .set_status(opentelemetry::trace::Status::error("Failed")); + return; + } + }; let manager = songbird::get(&ctx).await.unwrap().clone(); let handler_lock = if let Some(call) = manager.get(guild_id) { call @@ -92,21 +166,46 @@ impl EventHandler for Handler { let channel_id = match get_user_voice_channel(&ctx, &msg) { Some(c) => c, None => { - let _ = msg.reply(&ctx, "Join a voice channel first!").await; + cx.span().add_event( + "Failed to Retreive Channel ID", + vec![KeyValue::new("Channel ID", "None")], + ); + cx.span() + .set_status(opentelemetry::trace::Status::error(format!( + "{} not in Voice Channel", + msg.author.to_string() + ))); + let _ = msg + .channel_id + .say(&ctx.http, "You must be in a voice channel") + .await; return; } }; manager.join(guild_id, channel_id).await.unwrap() }; - let output = Command::new("./yt-dlp") + let output = match Command::new("./yt-dlp") .args(["-f", "bestaudio", "-g", &yt_url]) .stdout(Stdio::piped()) .output() - .expect("yt-dlp failed"); - if !output.status.success() { - let _ = msg.reply(&ctx, "Failed to fetch audio stream.").await; - return; - } + { + Ok(output) => { + cx.span().add_event( + "Retreiving Youtube Information", + vec![KeyValue::new("Success", yt_url.to_string())], + ); + output + } + Err(e) => { + cx.span().add_event( + "Retreiving Youtube Information", + vec![KeyValue::new("Failed", e.to_string())], + ); + cx.span().set_status(Status::error("Failed")); + eprintln!("{}", e); + return; + } + }; let stream_url = String::from_utf8(output.stdout) .expect("Invalid UTF-8") .trim() @@ -116,8 +215,10 @@ impl EventHandler for Handler { let _ = call.enqueue_input(input).await; let position = call.queue().len(); let response = if position == 1 { + cx.span().add_event("Starting Playback", vec![KeyValue::new("Status", "Playing".to_string())]); "🎶 Now playing!".to_string() } else { + cx.span().add_event("Starting Playback", vec![KeyValue::new("Status", "Added to Queue".to_string())]); format!("🎶 Added Song to Queue: #{}", position) }; let _ = msg.channel_id.say(&ctx.http, response).await; @@ -180,7 +281,12 @@ async fn main() { This program comes with ABSOLUTELY NO WARRANTY\n This is free software, and you are welcome to redistribute it under certain conditions." ); - updator::update().await; + let tracer_provider = telemetry::init_telemetry(); + global::set_tracer_provider(tracer_provider.clone()); + let tracer: global::BoxedTracer = global::tracer("tracer"); + tracer + .in_span("Checking for Updates", |cx| updator::update()) + .await; println!("Please paste in your bot token"); let mut input = String::new(); io::stdin() diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 34d394b..4ebc0e2 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -1 +1,2 @@ pub mod updator; +pub mod telemetry; \ No newline at end of file diff --git a/src/modules/telemetry.rs b/src/modules/telemetry.rs new file mode 100644 index 0000000..6d78b9e --- /dev/null +++ b/src/modules/telemetry.rs @@ -0,0 +1,26 @@ +use opentelemetry::KeyValue; +use opentelemetry_otlp::{WithExportConfig, WithTonicConfig}; +use opentelemetry_sdk::{Resource, runtime}; +use tonic::transport::{Channel, ClientTlsConfig}; + +pub fn init_telemetry() -> opentelemetry_sdk::trace::TracerProvider { + let endpoint = "https://signoz.racooncity.org".to_string(); + let channel = Channel::from_shared(endpoint.clone()) + .unwrap() + .tls_config(ClientTlsConfig::new().with_native_roots()) + .unwrap() + .connect_lazy(); + let exporter = opentelemetry_otlp::SpanExporter::builder() + .with_tonic() + .with_endpoint(endpoint.clone()) + .with_channel(channel) + .build() + .expect("Failed to Build Exporter"); + opentelemetry_sdk::trace::TracerProvider::builder() + .with_batch_exporter(exporter, runtime::Tokio) + .with_resource(Resource::new(vec![KeyValue::new( + "service.name", + "RhythmWorks", + )])) + .build() +}