feat: add structured logging with tracing
parent
f10c2a8afb
commit
e5821f5b4c
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -6,3 +6,4 @@ timeout_sec = 20
|
|||
concurrency = 30
|
||||
proxy = "http://username:password@host:port"
|
||||
enable_multiplexing = true
|
||||
log_level = "info"
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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<KeyCheckerConfig> = 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
|
||||
|
|
10
src/error.rs
10
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<T> = std::result::Result<T, ValidatorError>;
|
||||
|
|
16
src/main.rs
16
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()?;
|
||||
|
||||
|
|
|
@ -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<str> for GeminiKey {
|
|||
}
|
||||
|
||||
impl FromStr for GeminiKey {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
type Err = ValidatorError;
|
||||
fn from_str(s: &str) -> Result<Self, ValidatorError> {
|
||||
static RE: LazyLock<Regex> =
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue