diff --git a/Cargo.lock b/Cargo.lock index 1465837..5f3d28e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,6 +394,8 @@ dependencies = [ "thiserror", "tokio", "toml 0.9.2", + "tracing", + "tracing-subscriber", "url", ] @@ -746,6 +748,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.174" @@ -780,6 +788,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.5" @@ -815,6 +832,16 @@ dependencies = [ "windows-sys 0.59.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 = "object" version = "0.36.7" @@ -836,6 +863,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pear" version = "0.2.9" @@ -1024,8 +1057,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -1036,9 +1078,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -1219,6 +1267,15 @@ dependencies = [ "serde", ] +[[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" @@ -1326,6 +1383,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tinystr" version = "0.8.1" @@ -1535,9 +1601,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.34" @@ -1545,6 +1623,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 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-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -1598,6 +1706,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" @@ -1728,6 +1842,28 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 10333c1..65f0224 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,5 @@ figment = { version = "0.10", features = ["env", "toml"] } serde = { version = "1.0", features = ["derive"] } toml = "0.9" thiserror = "2.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } diff --git a/Config.toml.example b/Config.toml.example index 1132f14..805ef00 100644 --- a/Config.toml.example +++ b/Config.toml.example @@ -6,3 +6,4 @@ timeout_sec = 20 concurrency = 30 proxy = "http://username:password@host:port" enable_multiplexing = true +log_level = "info" diff --git a/src/adapters/output/local.rs b/src/adapters/output/local.rs index 937fa2d..8a26c48 100644 --- a/src/adapters/output/local.rs +++ b/src/adapters/output/local.rs @@ -3,6 +3,7 @@ use crate::error::Result; use std::{fs, io::Write}; use tokio::io::{AsyncWriteExt, BufWriter}; use toml::Value; +use tracing::info; // Write valid key to output file pub async fn write_keys_txt_file( @@ -31,6 +32,6 @@ pub fn write_keys_clewdr_format(file: &mut fs::File, key: &GeminiKey) -> Result< pub fn write_keys_to_file(keys: &[String], filename: &str) -> Result<()> { let content = keys.join("\n"); fs::write(filename, content)?; - println!("File '{}' created with {} keys", filename, keys.len()); + info!("File '{}' created with {} keys", filename, keys.len()); Ok(()) } diff --git a/src/config/config.rs b/src/config/config.rs index 88488d0..1d11f55 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -77,6 +77,9 @@ pub struct KeyCheckerConfig { // Whether to enable HTTP/2 multiplexing for requests. #[serde(default)] pub enable_multiplexing: bool, + + #[serde(default)] + pub log_level: String, } impl Default for KeyCheckerConfig { @@ -110,33 +113,36 @@ impl KeyCheckerConfig { /// Returns the complete Gemini API URL for generateContent endpoint pub fn gemini_api_url(&self) -> Url { self.api_host - .join("v1beta/models/gemini-2.0-flash-exp:generateContent") + .join("v1beta/models/gemini-2.5-flash-lite:generateContent") .expect("Failed to join API URL") } } impl Display for KeyCheckerConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "API Host: {}", self.api_host)?; - let proxy_status = match &self.proxy { Some(proxy) => proxy.to_string(), None => "Disabled".to_string(), }; - writeln!(f, "Proxy: {}", proxy_status)?; let protocol_status = if self.enable_multiplexing { "HTTP/2 (Multiplexing Enabled)" } else { "HTTP/1.1 (Multiplexing Disabled)" }; - writeln!(f, "Protocol: {}", protocol_status)?; - writeln!(f, "Timeout: {}s", self.timeout_sec)?; - writeln!(f, "Concurrency: {}", self.concurrency)?; - writeln!(f, "Input: {}", self.input_path.display())?; - writeln!(f, "Output: {}", self.output_path.display())?; - write!(f, "Backup: {}", self.backup_path.display()) + write!( + f, + "Host={}, Proxy={}, Protocol={}, Timeout={}s, Concurrency={}, Input={}, Output={}, Backup={}", + self.api_host, + proxy_status, + protocol_status, + self.timeout_sec, + self.concurrency, + self.input_path.display(), + self.output_path.display(), + self.backup_path.display() + ) } } @@ -150,6 +156,7 @@ static DEFAULT_CONFIG: LazyLock = LazyLock::new(|| KeyCheckerC concurrency: 50, proxy: None, enable_multiplexing: true, + log_level: "info".to_string(), }); // LazyLock for the test message body used in API key validation diff --git a/src/error.rs b/src/error.rs index 5919182..927cb88 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,9 +5,6 @@ pub enum ValidatorError { #[error("HTTP error: {0}")] ReqwestError(#[from] reqwest::Error), - #[error("Key is unavailable or invalid")] - KeyInvalid, - #[error("IO error: {0}")] Io(#[from] std::io::Error), @@ -26,8 +23,11 @@ pub enum ValidatorError { #[error("URL parse error: {0}")] UrlParse(#[from] url::ParseError), - #[error("Key validation failed: {0}")] - Validation(String), + #[error("Key is unavailable or invalid")] + KeyInvalid, + + #[error("Invalid Google API key format: {0}")] + KeyFormatInvalid(String), } pub type Result = std::result::Result; diff --git a/src/main.rs b/src/main.rs index bf7cb6b..372bc6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use gemini_keychecker::error::ValidatorError; use gemini_keychecker::{BANNER, config::KeyCheckerConfig, service::start_validation}; - use mimalloc::MiMalloc; +use tracing::info; #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; @@ -9,11 +9,19 @@ static GLOBAL: MiMalloc = MiMalloc; /// Main function - displays banner and starts validation service #[tokio::main] async fn main() -> Result<(), ValidatorError> { + let config = KeyCheckerConfig::load_config()?; + // Initialize tracing with professional format using configured log level + tracing_subscriber::fmt() + .with_target(false) + .with_level(true) + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(&config.log_level)), + ) + .init(); // Display banner and configuration status at startup println!("{BANNER}"); - - let config = KeyCheckerConfig::load_config()?; - println!("{config}"); + info!("Configuration loaded: {}", config); // Start validation service start_validation().await diff --git a/src/service/key_tester.rs b/src/service/key_tester.rs index 7bbc568..984c54b 100644 --- a/src/service/key_tester.rs +++ b/src/service/key_tester.rs @@ -1,6 +1,7 @@ use backon::{ExponentialBuilder, Retryable}; use reqwest::{Client, IntoUrl, StatusCode}; use tokio::time::Duration; +use tracing::{debug, error, info, warn}; use url::Url; use crate::config::TEST_MESSAGE_BODY; @@ -15,19 +16,23 @@ pub async fn validate_key( let api_endpoint = api_endpoint.into_url()?; match send_test_request(client, &api_endpoint, api_key.clone()).await { - Ok(response) => { - let status = response.status(); - match status { - StatusCode::OK => Ok(api_key), - StatusCode::UNAUTHORIZED - | StatusCode::FORBIDDEN - | StatusCode::TOO_MANY_REQUESTS => Err(ValidatorError::KeyInvalid), - _ => Err(ValidatorError::ReqwestError( - response.error_for_status().unwrap_err(), - )), + Ok(_) => { + info!("SUCCESS - {}... - Valid key found", &api_key.as_ref()[..10]); + Ok(api_key) + } + Err(e) => { + match e.status() { + Some(StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN | StatusCode::TOO_MANY_REQUESTS) => { + warn!("INVALID - {}... - {}", &api_key.as_ref()[..10], ValidatorError::KeyInvalid); + Err(ValidatorError::KeyInvalid) + } + _ => { + let req_error = ValidatorError::ReqwestError(e); + error!("ERROR- {}... - {}", &api_key.as_ref()[..10], req_error); + Err(req_error) + } } } - Err(e) => Err(ValidatorError::ReqwestError(e)), } } @@ -49,7 +54,7 @@ async fn send_test_request( .json(&*TEST_MESSAGE_BODY) .send() .await?; - + debug!("Response for key {}: {:?}", key.as_ref(), response.status()); response.error_for_status() }) .retry(&retry_policy) diff --git a/src/service/validation.rs b/src/service/validation.rs index c3b0073..7fb8132 100644 --- a/src/service/validation.rs +++ b/src/service/validation.rs @@ -59,7 +59,6 @@ impl ValidationService { // Process validated keys and write to output file while let Some(valid_key) = valid_keys_stream.next().await { - println!("Valid key found: {}", valid_key.as_ref()); if let Err(e) = write_keys_txt_file(&mut buffer_writer, &valid_key).await { eprintln!("Failed to write key to output file: {}", e); } @@ -73,7 +72,7 @@ impl ValidationService { } } -/// 启动验证服务 - 封装了所有启动逻辑 + pub async fn start_validation() -> Result<(), ValidatorError> { let config = KeyCheckerConfig::load_config()?; diff --git a/src/types.rs b/src/types.rs index ffb9f7d..e81cf4b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,4 @@ +use crate::error::ValidatorError; use regex::Regex; use std::str::FromStr; use std::sync::LazyLock; @@ -14,9 +15,8 @@ impl AsRef for GeminiKey { } impl FromStr for GeminiKey { - type Err = &'static str; - - fn from_str(s: &str) -> Result { + type Err = ValidatorError; + fn from_str(s: &str) -> Result { static RE: LazyLock = LazyLock::new(|| Regex::new(r"^AIzaSy[A-Za-z0-9_-]{33}$").unwrap()); @@ -27,7 +27,7 @@ impl FromStr for GeminiKey { inner: cleaned.to_string(), }) } else { - Err("Invalid Google API key format") + Err(ValidatorError::KeyFormatInvalid(cleaned.to_string())) } } }