diff --git a/.env.example b/.env.example index b1d5862..9a91efb 100644 --- a/.env.example +++ b/.env.example @@ -26,3 +26,5 @@ IDPORTEN_ENABLED=true IDPORTEN_AUDIENCE=default IDPORTEN_ISSUER=http://localhost:8080/idporten IDPORTEN_JWKS_URI=http://localhost:8080/idporten/jwks + +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 diff --git a/Cargo.lock b/Cargo.lock index f1b650d..6f321e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" + [[package]] name = "arbitrary" version = "1.4.1" @@ -108,6 +114,28 @@ dependencies = [ "derive_arbitrary", ] +[[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", +] + [[package]] name = "async-trait" version = "0.1.83" @@ -171,7 +199,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", - "tower", + "tower 0.5.1", "tower-layer", "tower-service", "tracing", @@ -856,6 +884,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "group" version = "0.10.0" @@ -1107,11 +1141,24 @@ dependencies = [ "webpki-roots", ] +[[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-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", @@ -1236,6 +1283,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1417,6 +1473,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1478,6 +1544,83 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "opentelemetry" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "570074cc999d1a58184080966e5bd3bf3a9a4af650c3b05047c2621e7405cd17" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e1f9c8b032d4f635c730c0efcf731d5e2530ea13fa8bef7939ddc8420696bd" +dependencies = [ + "async-trait", + "futures-core", + "http", + "opentelemetry", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "thiserror", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d3968ce3aefdcca5c27e3c4ea4391b37547726a70893aab52d3de95d5f8b34" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "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.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c627d9f4c9cdc1f21a29ee4bfbd6028fcb8bcf2a857b43f3abdf72c9c862f3" +dependencies = [ + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "once_cell", + "opentelemetry", + "percent-encoding", + "rand", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "p256" version = "0.9.0" @@ -1558,6 +1701,26 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -1604,6 +1767,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -2144,6 +2330,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2332,12 +2527,19 @@ dependencies = [ "jsonwebkey", "jsonwebtoken", "log", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", "reqwest", "serde", "serde_json", "testcontainers", "thiserror", "tokio", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", "utoipa", "utoipa-axum", "utoipa-swagger-ui", @@ -2373,6 +2575,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.36" @@ -2496,6 +2708,56 @@ dependencies = [ "tokio", ] +[[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 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2", + "tokio", + "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", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.1" @@ -2554,6 +2816,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc58af5d3f6c5811462cabb3289aec0093f7338e367e5a33d28c0433b3c7360b" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -2694,6 +3000,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vec_map" version = "0.8.2" @@ -2808,6 +3120,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.26.6" diff --git a/Cargo.toml b/Cargo.toml index c590a5d..2dd1f40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,13 @@ serde_json = "1.0.132" utoipa = { version = "5.2.0", features = ["axum_extras", "openapi_extensions"] } utoipa-axum = "0.1.2" utoipa-swagger-ui = { version = "8.0.3", features = ["axum"] } +tracing = { version = "0.1.40", features = [] } +tracing-opentelemetry = "0.27.0" +opentelemetry = { version = "0.26.0", features = ["metrics", "trace"] } +opentelemetry_sdk = { version = "0.26.0", features = ["trace", "rt-tokio"] } +opentelemetry-otlp = { version = "0.26.0", features = ["metrics", "trace"] } +opentelemetry-semantic-conventions = { version = "0.27.0", features = ["semconv_experimental"] } +tracing-subscriber = "0.3.18" [dev-dependencies] testcontainers = { version = "0.23.1", features = ["http_wait", "properties-config"] } diff --git a/src/handlers.rs b/src/handlers.rs index 2565b76..d7d9eb9 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -16,6 +16,7 @@ use std::collections::HashMap; use std::sync::Arc; use thiserror::Error; use tokio::sync::RwLock; +use tracing::instrument; use crate::claims::{Assertion, ClientAssertion, JWTBearerAssertion}; use crate::grants::{ClientCredentials, JWTBearer, OnBehalfOf, TokenExchange, TokenRequestBuilder}; @@ -53,6 +54,7 @@ use crate::grants::{ClientCredentials, JWTBearer, OnBehalfOf, TokenExchange, Tok (status = INTERNAL_SERVER_ERROR, description = "Server error", body = ErrorResponse, content_type = "application/json"), ) )] +#[instrument(skip_all)] pub async fn token( State(state): State, JsonOrForm(request): JsonOrForm, @@ -103,6 +105,7 @@ pub async fn token( (status = INTERNAL_SERVER_ERROR, description = "Server error", body = ErrorResponse, content_type = "application/json"), ) )] +#[instrument(skip_all)] pub async fn token_exchange( State(state): State, JsonOrForm(request): JsonOrForm, @@ -154,6 +157,7 @@ pub async fn token_exchange( ), ) )] +#[instrument(skip_all)] pub async fn introspect( State(state): State, JsonOrForm(request): JsonOrForm, diff --git a/src/jwks.rs b/src/jwks.rs index b4b8b76..735cf49 100644 --- a/src/jwks.rs +++ b/src/jwks.rs @@ -6,6 +6,7 @@ use serde_json::Value; use std::collections::HashMap; use log::error; use thiserror::Error; +use tracing::instrument; use crate::claims::epoch_now_secs; #[derive(Clone, Debug)] @@ -37,6 +38,7 @@ pub enum Error { } impl Jwks { + #[instrument(skip_all)] pub async fn new( issuer: &str, endpoint: &str, @@ -89,6 +91,7 @@ impl Jwks { } /// Pull a new version of the JWKS from the original endpoint. + #[instrument(skip_all)] pub async fn refresh(&mut self) -> Result<(), Error> { let new_jwks = Self::new(&self.issuer, &self.endpoint, self.required_audience.clone()).await?; @@ -99,6 +102,7 @@ impl Jwks { /// Check a JWT against a JWKS. /// Returns the JWT's claims on success. /// May update the list of signing keys if the key ID is not found. + #[instrument(skip_all)] pub async fn validate(&mut self, token: &str) -> Result, Error> { let key_id = jwt::decode_header(token) .map_err(Error::InvalidTokenHeader)? diff --git a/src/main.rs b/src/main.rs index 9c6a001..cd9e62c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,23 +5,30 @@ mod grants; pub mod handlers; pub mod identity_provider; pub mod jwks; +mod tracing; use crate::app::App; use config::Config; use dotenv::dotenv; -use log::{error, info, LevelFilter}; +use log::{error, info}; +use crate::tracing::{init_tracing_subscriber, test}; #[tokio::main] async fn main() { - env_logger::builder() + let _guard = init_tracing_subscriber(); + + test().await; + + /*env_logger::builder() .filter_level(LevelFilter::Debug) .init(); - +*/ config::print_texas_logo(); info!("Starting up"); let _ = dotenv(); // load .env if present + let cfg = match Config::new_from_env() { Ok(cfg) => cfg, Err(err) => { diff --git a/src/tracing.rs b/src/tracing.rs new file mode 100644 index 0000000..1ece04f --- /dev/null +++ b/src/tracing.rs @@ -0,0 +1,118 @@ +use log::info; +use opentelemetry::trace::TracerProvider; +use opentelemetry::{global, KeyValue}; +use opentelemetry_sdk::metrics::reader::DefaultTemporalitySelector; +use opentelemetry_sdk::metrics::{MeterProviderBuilder, PeriodicReader, SdkMeterProvider}; +use opentelemetry_sdk::trace::{BatchConfig, RandomIdGenerator, Sampler, Tracer}; +use opentelemetry_sdk::{runtime, Resource}; +use opentelemetry_semantic_conventions::{ + attribute::{DEPLOYMENT_ENVIRONMENT_NAME, SERVICE_NAME, SERVICE_VERSION}, + SCHEMA_URL, +}; +use tracing::{instrument, span, Level}; +use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer}; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; + +// Initialize tracing-subscriber and return OtelGuard for opentelemetry-related termination processing +pub fn init_tracing_subscriber() -> OtelGuard { + let meter_provider = init_meter_provider(); + let tracer = init_tracer(); + tracing_subscriber::registry() + .with(tracing_subscriber::filter::LevelFilter::from_level( + Level::INFO, + )) + .with(MetricsLayer::new(meter_provider.clone())) + .with(tracing_subscriber::fmt::layer()) + .with(OpenTelemetryLayer::new(tracer)) + .init(); + + OtelGuard { meter_provider } +} + +pub struct OtelGuard { + meter_provider: SdkMeterProvider, +} + +impl Drop for OtelGuard { + fn drop(&mut self) { + if let Err(err) = self.meter_provider.shutdown() { + eprintln!("{err:?}"); + } + global::shutdown_tracer_provider(); + } +} + + +// Construct MeterProvider for MetricsLayer +fn init_meter_provider() -> SdkMeterProvider { + let exporter = opentelemetry_otlp::new_exporter() + .tonic() + .build_metrics_exporter(Box::new(DefaultTemporalitySelector::new())) + .unwrap(); + + let reader = PeriodicReader::builder(exporter, runtime::Tokio) + .with_interval(std::time::Duration::from_secs(2)) + .build(); + + let meter_provider = MeterProviderBuilder::default() + .with_resource(resource()) + .with_reader(reader) + .build(); + + global::set_meter_provider(meter_provider.clone()); + + meter_provider +} + + +fn init_tracer() -> Tracer { + let provider = opentelemetry_otlp::new_pipeline() + .tracing() + .with_trace_config( + opentelemetry_sdk::trace::Config::default() + // Customize sampling strategy + .with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased( + 1.0, + )))) + // If export trace to AWS X-Ray, you can use XrayIdGenerator + .with_id_generator(RandomIdGenerator::default()) + .with_resource(resource()), + ) + .with_batch_config(BatchConfig::default()) + .with_exporter(opentelemetry_otlp::new_exporter().tonic()) + .install_batch(runtime::Tokio) + .unwrap(); + + global::set_tracer_provider(provider.clone()); + provider.tracer("tracing-otel-subscriber") +} + +#[instrument(skip_all)] +pub async fn test() { + info!("Hello, world!"); + yolo("hello"); + tracing::info!( + monotonic_counter.foo = 1_u64, + key_1 = "bar", + key_2 = 10, + "handle foo", + ); +} + +#[instrument(fields(span.kind = "server", yoloparam=test), skip_all)] +fn yolo(test: &str) { + info!("yolo forever"); + span!(Level::INFO, "yolo using span", test = test); +} + +fn resource() -> Resource { + Resource::from_schema_url( + [ + KeyValue::new(SERVICE_NAME, env!("CARGO_PKG_NAME")), + KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")), + KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, "develop"), + ], + SCHEMA_URL, + ) +} \ No newline at end of file