1use serde::Serialize;
18use tracing_subscriber::{
19 filter::EnvFilter,
20 fmt::{self, format::FmtSpan},
21 layer::SubscriberExt,
22 util::SubscriberInitExt,
23};
24
25#[derive(Debug, Clone)]
27pub struct LogConfig {
28 pub format: LogFormat,
29 pub level: String,
30 pub detailed: bool,
31 pub json_output: bool,
32 pub include_timestamp: bool,
33 pub include_target: bool,
34}
35
36impl Default for LogConfig {
37 fn default() -> Self {
38 Self {
39 format: LogFormat::Compact,
40 level: "info".to_string(),
41 detailed: false,
42 json_output: false,
43 include_timestamp: true,
44 include_target: false,
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy)]
51pub enum LogFormat {
52 Compact,
53 Pretty,
54 Json,
55}
56
57pub fn init_logging(config: LogConfig) -> Result<(), Box<dyn std::error::Error>> {
59 let env_filter = EnvFilter::try_from_default_env()
60 .unwrap_or_else(|_| EnvFilter::new(format!("kindly_guard={},warn", config.level)));
61
62 if config.json_output || matches!(config.format, LogFormat::Json) {
63 let fmt_layer = fmt::layer()
65 .with_target(config.include_target)
66 .with_thread_ids(false)
67 .with_thread_names(false)
68 .with_span_events(if config.detailed {
69 FmtSpan::FULL
70 } else {
71 FmtSpan::NONE
72 });
73
74 tracing_subscriber::registry()
75 .with(env_filter)
76 .with(fmt_layer)
77 .init();
78 } else {
79 let fmt_layer = fmt::layer()
81 .with_target(config.include_target)
82 .with_thread_ids(false)
83 .with_thread_names(false)
84 .with_ansi(!cfg!(windows)) .with_span_events(if config.detailed {
86 FmtSpan::FULL
87 } else {
88 FmtSpan::NONE
89 });
90
91 tracing_subscriber::registry()
92 .with(env_filter)
93 .with(fmt_layer)
94 .init();
95 }
96
97 Ok(())
98}
99
100pub fn init_logging_simple(detailed: bool) -> Result<(), Box<dyn std::error::Error>> {
102 init_logging(LogConfig {
103 detailed,
104 ..Default::default()
105 })
106}
107
108#[macro_export]
110macro_rules! log_security_event {
111 ($level:expr, event_type:$event_type:expr, client:$client:expr, $($field:tt)*) => {
112 match $level {
113 Level::ERROR => tracing::error!(
114 event_type = $event_type,
115 client = $client,
116 category = "security",
117 $($field)*
118 ),
119 Level::WARN => tracing::warn!(
120 event_type = $event_type,
121 client = $client,
122 category = "security",
123 $($field)*
124 ),
125 Level::INFO => tracing::info!(
126 event_type = $event_type,
127 client = $client,
128 category = "security",
129 $($field)*
130 ),
131 Level::DEBUG => tracing::debug!(
132 event_type = $event_type,
133 client = $client,
134 category = "security",
135 $($field)*
136 ),
137 _ => tracing::trace!(
138 event_type = $event_type,
139 client = $client,
140 category = "security",
141 $($field)*
142 ),
143 }
144 };
145}
146
147#[macro_export]
149macro_rules! log_performance {
150 ($operation:expr, $duration_ms:expr, $($field:tt)*) => {
151 tracing::debug!(
152 operation = $operation,
153 duration_ms = $duration_ms,
154 category = "performance",
155 $($field)*
156 );
157 };
158}
159
160pub mod event_types {
162 pub const AUTH_SUCCESS: &str = "authentication.success";
163 pub const AUTH_FAILURE: &str = "authentication.failure";
164 pub const RATE_LIMIT_EXCEEDED: &str = "rate_limit.exceeded";
165 pub const THREAT_DETECTED: &str = "threat.detected";
166 pub const THREAT_BLOCKED: &str = "threat.blocked";
167 pub const REQUEST_PROCESSED: &str = "request.processed";
168 pub const CIRCUIT_BREAKER_OPEN: &str = "protection.activated";
169 pub const CIRCUIT_BREAKER_CLOSE: &str = "protection.restored";
170 pub const PATTERN_DETECTED: &str = "pattern.detected";
171 pub const PERFORMANCE_OPTIMIZED: &str = "performance.optimized";
172}
173
174pub mod field_names {
176 pub const CLIENT_ID: &str = "client.id";
177 pub const METHOD: &str = "request.method";
178 pub const THREAT_TYPE: &str = "threat.type";
179 pub const THREAT_SEVERITY: &str = "threat.severity";
180 pub const RESPONSE_TIME_MS: &str = "response.time_ms";
181 pub const TOKENS_REMAINING: &str = "rate_limit.tokens_remaining";
182 pub const PATTERN_CONFIDENCE: &str = "pattern.confidence";
183 pub const OPTIMIZATION_LEVEL: &str = "performance.level";
184}
185
186pub struct SemanticLogger;
188
189impl SemanticLogger {
190 pub fn auth_event(success: bool, client_id: &str, method: Option<&str>) {
192 let event_type = if success {
193 event_types::AUTH_SUCCESS
194 } else {
195 event_types::AUTH_FAILURE
196 };
197
198 if let Some(method) = method {
199 tracing::info!(
200 event_type = event_type,
201 client.id = client_id,
202 auth.method = method,
203 category = "security"
204 );
205 } else {
206 tracing::info!(
207 event_type = event_type,
208 client.id = client_id,
209 category = "security"
210 );
211 }
212 }
213
214 pub fn threat_detected(client_id: &str, threat_type: &str, severity: &str) {
216 tracing::warn!(
217 event_type = event_types::THREAT_DETECTED,
218 client.id = client_id,
219 threat.type = threat_type,
220 threat.severity = severity,
221 category = "security"
222 );
223 }
224
225 pub fn rate_limit_event(client_id: &str, allowed: bool, tokens: f64) {
227 if allowed {
228 tracing::debug!(
229 event_type = "rate_limit.check",
230 client.id = client_id,
231 rate_limit.allowed = allowed,
232 rate_limit.tokens_remaining = tokens,
233 category = "security"
234 );
235 } else {
236 tracing::warn!(
237 event_type = event_types::RATE_LIMIT_EXCEEDED,
238 client.id = client_id,
239 rate_limit.allowed = allowed,
240 rate_limit.tokens_remaining = tokens,
241 category = "security"
242 );
243 }
244 }
245
246 pub fn performance_metric(operation: &str, duration_ms: u64, enhanced: bool) {
248 let level = if enhanced { "optimized" } else { "standard" };
249 tracing::debug!(
250 event_type = "performance.metric",
251 operation = operation,
252 duration_ms = duration_ms,
253 performance.mode = level,
254 category = "performance"
255 );
256 }
257
258 pub fn circuit_breaker_event(endpoint: &str, open: bool) {
260 let event_type = if open {
261 event_types::CIRCUIT_BREAKER_OPEN
262 } else {
263 event_types::CIRCUIT_BREAKER_CLOSE
264 };
265
266 tracing::info!(
267 event_type = event_type,
268 endpoint = endpoint,
269 protection.active = open,
270 category = "security"
271 );
272 }
273}
274
275pub fn sanitize_for_log(input: &str) -> String {
277 let sanitized = input
279 .replace("enhanced", "optimized")
280 .replace("standard", "normal");
281
282 if sanitized.len() > 200 {
284 format!("{}...", &sanitized[..200])
285 } else {
286 sanitized
287 }
288}
289
290#[macro_export]
292macro_rules! request_span {
293 ($method:expr, $request_id:expr, $client_id:expr) => {
294 tracing::info_span!(
295 "request",
296 request.id = $request_id,
297 request.method = $method,
298 client.id = $client_id,
299 request.start_time = %chrono::Utc::now(),
300 )
301 };
302}
303
304#[macro_export]
306macro_rules! log_request_complete {
307 ($span:expr, $success:expr, $duration_ms:expr) => {
308 tracing::info!(
309 parent: &$span,
310 event_type = "request.complete",
311 request.success = $success,
312 request.duration_ms = $duration_ms,
313 category = "performance"
314 );
315 };
316}
317
318#[derive(Debug, Serialize)]
320pub struct ErrorLog {
321 pub error_type: String,
322 pub error_code: i32,
323 pub message: String,
324 pub severity: String,
325 pub retryable: bool,
326 pub context: Option<serde_json::Value>,
327}
328
329impl ErrorLog {
330 pub fn from_kindly_error(error: &crate::error::KindlyError) -> Self {
331 Self {
332 error_type: format!("{error:?}"),
333 error_code: error.to_protocol_code(),
334 message: error.to_string(),
335 severity: format!("{:?}", error.severity()),
336 retryable: error.is_retryable(),
337 context: None,
338 }
339 }
340
341 pub fn log(&self) {
342 match self.severity.as_str() {
343 "Critical" => tracing::error!(
344 error = serde_json::to_string(self).unwrap_or_default(),
345 category = "error"
346 ),
347 "High" => tracing::warn!(
348 error = serde_json::to_string(self).unwrap_or_default(),
349 category = "error"
350 ),
351 _ => tracing::info!(
352 error = serde_json::to_string(self).unwrap_or_default(),
353 category = "error"
354 ),
355 }
356 }
357}