kindly_guard_server/
logging.rs

1// Copyright 2025 Kindly Software Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Enhanced logging with semantic fields for stealth operation
15//! This module provides structured logging that hides implementation details
16
17use serde::Serialize;
18use tracing_subscriber::{
19    filter::EnvFilter,
20    fmt::{self, format::FmtSpan},
21    layer::SubscriberExt,
22    util::SubscriberInitExt,
23};
24
25/// Logging configuration
26#[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/// Log output format
50#[derive(Debug, Clone, Copy)]
51pub enum LogFormat {
52    Compact,
53    Pretty,
54    Json,
55}
56
57/// Initialize the logging system with stealth configuration
58pub 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        // JSON structured logging
64        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        // Human-readable logging
80        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)) // Disable ANSI on Windows
85            .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
100/// Initialize with default configuration for backwards compatibility
101pub 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/// Log security events with semantic fields
109#[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/// Log performance metrics without revealing internals
148#[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
160/// Semantic event types for consistent logging
161pub 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
174/// Semantic field names for structured logging
175pub 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
186/// Helper to log with semantic fields
187pub struct SemanticLogger;
188
189impl SemanticLogger {
190    /// Log an authentication event
191    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    /// Log a threat detection
215    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    /// Log rate limiting
226    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    /// Log performance metrics
247    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    /// Log circuit breaker events
259    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
275/// Sanitize sensitive data from logs
276pub fn sanitize_for_log(input: &str) -> String {
277    // Normalize technical terms for consistency
278    let sanitized = input
279        .replace("enhanced", "optimized")
280        .replace("standard", "normal");
281
282    // Truncate if too long
283    if sanitized.len() > 200 {
284        format!("{}...", &sanitized[..200])
285    } else {
286        sanitized
287    }
288}
289
290/// Create a request span with tracking information
291#[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/// Log request completion with metrics
305#[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/// Structured error logging
319#[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}