""" Cordon - Semantic Anomaly Detection for Log Files HuggingFace Space Demo """ import tempfile from pathlib import Path import gradio as gr import torch from cordon import AnalysisConfig, SemanticLogAnalyzer # ZeroGPU support with fallback for local development try: import spaces ZEROGPU_AVAILABLE = True except ImportError: ZEROGPU_AVAILABLE = False # Detect available device def get_device() -> str: """Detect best available device.""" if torch.cuda.is_available(): return "cuda" elif torch.backends.mps.is_available(): return "mps" return "cpu" # Global model cache (keyed by model_name:device) _analyzer_cache: dict[str, SemanticLogAnalyzer] = {} def get_cached_analyzer(config: AnalysisConfig) -> SemanticLogAnalyzer: """Get or create a cached analyzer instance. Caches the analyzer to avoid repeated model loading. The config is updated on the cached instance for each call. """ cache_key = f"{config.model_name}:{config.device}" if cache_key not in _analyzer_cache: _analyzer_cache[cache_key] = SemanticLogAnalyzer(config) else: # Update config params (window_size, k_neighbors, etc.) _analyzer_cache[cache_key].config = config return _analyzer_cache[cache_key] # Load sample log file SAMPLE_LOG_PATH = Path(__file__).parent / "sample_apache.log" FALLBACK_LOG_PATH = Path(__file__).parent.parent / "examples" / "apache_sample.log" def load_sample_log() -> str: """Load the sample Apache log file.""" if SAMPLE_LOG_PATH.exists(): return SAMPLE_LOG_PATH.read_text() if FALLBACK_LOG_PATH.exists(): return FALLBACK_LOG_PATH.read_text() return "Sample log file not found. Please paste your log content here." def estimate_tokens(text: str) -> int: """Rough token estimate (chars / 4).""" return len(text) // 4 if text else 0 def _analyze_logs_impl( log_content: str, window_size: int, k_neighbors: int, mode: str, anomaly_percentile: float, range_min: float, range_max: float, progress: gr.Progress = gr.Progress(), ) -> tuple[str, str]: """Analyze log content and return results.""" if not log_content.strip(): return "", "Anomalous Sections" progress(0.05, desc="Preparing log file...") # Write content to temp file with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f: f.write(log_content) temp_path = Path(f.name) try: progress(0.1, desc="Configuring analyzer...") device = get_device() # Use larger batch size on GPU for better throughput batch_size = 128 if device == "cuda" else 32 if mode == "Percentile": config = AnalysisConfig( window_size=window_size, k_neighbors=k_neighbors, anomaly_percentile=anomaly_percentile, device=device, batch_size=batch_size, ) else: config = AnalysisConfig( window_size=window_size, k_neighbors=k_neighbors, anomaly_range_min=range_min, anomaly_range_max=range_max, device=device, batch_size=batch_size, ) progress(0.15, desc="Loading embedding model...") analyzer = get_cached_analyzer(config) progress(0.25, desc="Segmenting log into windows...") # Small delay to show progress import time time.sleep(0.1) progress(0.4, desc="Generating embeddings...") time.sleep(0.1) progress(0.6, desc="Scoring anomalies...") time.sleep(0.1) progress(0.75, desc="Analyzing patterns...") result = analyzer.analyze_file_detailed(temp_path) progress(0.9, desc="Formatting output...") time.sleep(0.05) progress(1.0, desc="Complete!") # Calculate token counts input_tokens = estimate_tokens(log_content) output_tokens = estimate_tokens(result.output) reduction = ((input_tokens - output_tokens) / input_tokens * 100) if input_tokens > 0 else 0 label = f"Anomalous Sections — {input_tokens:,} → {output_tokens:,} tokens ({reduction:.0f}% reduction)" return result.output, label finally: temp_path.unlink(missing_ok=True) # Apply ZeroGPU decorator if available, otherwise use the function directly if ZEROGPU_AVAILABLE: analyze_logs = spaces.GPU(duration=120)(_analyze_logs_impl) else: analyze_logs = _analyze_logs_impl CUSTOM_CSS = """ .main, .wrap, .contain, .gradio-container, .app { max-width: 100% !important; width: 100% !important; padding-left: 2rem !important; padding-right: 2rem !important; } /* Monospace font for log input/output */ textarea { font-family: "JetBrains Mono", "Fira Code", "SF Mono", "Cascadia Code", Menlo, Monaco, Consolas, "Liberation Mono", monospace !important; font-size: 13px !important; line-height: 1.5 !important; } """ THEME = gr.themes.Base( primary_hue="teal", secondary_hue="cyan", neutral_hue="slate", ).set( body_background_fill="#0f172a", body_text_color="#e2e8f0", block_background_fill="#1e293b", block_border_color="#334155", block_title_text_color="#5eead4", input_background_fill="#334155", input_border_color="#475569", button_primary_background_fill="#14b8a6", button_primary_background_fill_hover="#2dd4bf", button_primary_text_color="#ffffff", ) def create_interface(): """Create the Gradio interface.""" with gr.Blocks(title="Cordon", fill_width=True, theme=THEME, css=CUSTOM_CSS) as demo: # Header gr.HTML("""
Reduce logs to their semantically anomalous parts.