calebdevans commited on
Commit
ff37a98
Β·
unverified Β·
1 Parent(s): c3e8b58

add new cordon space

Browse files
Files changed (4) hide show
  1. README.md +34 -6
  2. app.py +293 -0
  3. requirements.txt +5 -0
  4. sample_apache.log +0 -0
README.md CHANGED
@@ -1,14 +1,42 @@
1
  ---
2
  title: Cordon
3
- emoji: πŸ’»
4
- colorFrom: green
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.2.0
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
- short_description: Reduce logs to their anomalous parts
 
 
 
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Cordon
3
+ emoji: 🧬
4
+ colorFrom: teal
5
+ colorTo: cyan
6
  sdk: gradio
7
+ sdk_version: "4.44.0"
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
+ short_description: Reduce logs to their semantically anomalous parts
12
+ tags:
13
+ - log-analysis
14
+ - anomaly-detection
15
+ - embeddings
16
+ - nlp
17
+ - transformers
18
  ---
19
 
20
+ # Cordon
21
+
22
+ **Reduce logs to their semantically anomalous parts.**
23
+
24
+ Cordon uses AI to understand the *meaning* of your logs and find the parts that are semantically unusualβ€”reducing massive log files to just the anomalous sections worth investigating.
25
+
26
+ ## How It Works
27
+
28
+ 1. **Segmentation** β€” Logs are split into non-overlapping windows
29
+ 2. **Embedding** β€” Each window is vectorized using transformers
30
+ 3. **Scoring** β€” Distance to nearest neighbors identifies unusual patterns
31
+ 4. **Thresholding** β€” Top anomalies are selected
32
+ 5. **Output** β€” XML-tagged blocks ready for LLM analysis
33
+
34
+ ## Key Insight
35
+
36
+ > Repetitive patterns (even errors) are filtered out. Cordon surfaces the *rare* and *unusual* events that stand out from the bulk of your logs.
37
+
38
+ ## Links
39
+
40
+ - [GitHub Repository](https://github.com/calebevans/cordon)
41
+ - [PyPI Package](https://pypi.org/project/cordon/)
42
+ - [Red Hat Developer Article](https://developers.redhat.com/articles/2025/12/09/semantic-anomaly-detection-log-files-cordon)
app.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Cordon - Semantic Anomaly Detection for Log Files
3
+ HuggingFace Space Demo
4
+ """
5
+
6
+ import tempfile
7
+ from pathlib import Path
8
+
9
+ import gradio as gr
10
+
11
+ from cordon import AnalysisConfig, SemanticLogAnalyzer
12
+
13
+ # Load sample log file
14
+ SAMPLE_LOG_PATH = Path(__file__).parent / "sample_apache.log"
15
+ FALLBACK_LOG_PATH = Path(__file__).parent.parent / "examples" / "apache_sample.log"
16
+
17
+
18
+ def load_sample_log() -> str:
19
+ """Load the sample Apache log file."""
20
+ if SAMPLE_LOG_PATH.exists():
21
+ return SAMPLE_LOG_PATH.read_text()
22
+ if FALLBACK_LOG_PATH.exists():
23
+ return FALLBACK_LOG_PATH.read_text()
24
+ return "Sample log file not found. Please paste your log content here."
25
+
26
+
27
+ def estimate_tokens(text: str) -> int:
28
+ """Rough token estimate (chars / 4)."""
29
+ return len(text) // 4 if text else 0
30
+
31
+
32
+ def analyze_logs(
33
+ log_content: str,
34
+ window_size: int,
35
+ k_neighbors: int,
36
+ mode: str,
37
+ anomaly_percentile: float,
38
+ range_min: float,
39
+ range_max: float,
40
+ progress: gr.Progress = gr.Progress(),
41
+ ) -> tuple[str, str]:
42
+ """Analyze log content and return results."""
43
+
44
+ if not log_content.strip():
45
+ return "", "Anomalous Sections"
46
+
47
+ progress(0.05, desc="Preparing log file...")
48
+
49
+ # Write content to temp file
50
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
51
+ f.write(log_content)
52
+ temp_path = Path(f.name)
53
+
54
+ try:
55
+ progress(0.1, desc="Configuring analyzer...")
56
+
57
+ if mode == "Percentile":
58
+ config = AnalysisConfig(
59
+ window_size=window_size,
60
+ k_neighbors=k_neighbors,
61
+ anomaly_percentile=anomaly_percentile,
62
+ device="cpu",
63
+ )
64
+ else:
65
+ config = AnalysisConfig(
66
+ window_size=window_size,
67
+ k_neighbors=k_neighbors,
68
+ anomaly_range_min=range_min,
69
+ anomaly_range_max=range_max,
70
+ device="cpu",
71
+ )
72
+
73
+ progress(0.15, desc="Loading embedding model...")
74
+ analyzer = SemanticLogAnalyzer(config)
75
+
76
+ progress(0.25, desc="Segmenting log into windows...")
77
+ # Small delay to show progress
78
+ import time
79
+ time.sleep(0.1)
80
+
81
+ progress(0.4, desc="Generating embeddings...")
82
+ time.sleep(0.1)
83
+
84
+ progress(0.6, desc="Scoring anomalies...")
85
+ time.sleep(0.1)
86
+
87
+ progress(0.75, desc="Analyzing patterns...")
88
+ result = analyzer.analyze_file_detailed(temp_path)
89
+
90
+ progress(0.9, desc="Formatting output...")
91
+ time.sleep(0.05)
92
+
93
+ progress(1.0, desc="Complete!")
94
+
95
+ # Calculate token counts
96
+ input_tokens = estimate_tokens(log_content)
97
+ output_tokens = estimate_tokens(result.output)
98
+ reduction = ((input_tokens - output_tokens) / input_tokens * 100) if input_tokens > 0 else 0
99
+
100
+ label = f"Anomalous Sections β€” {input_tokens:,} β†’ {output_tokens:,} tokens ({reduction:.0f}% reduction)"
101
+
102
+ return result.output, label
103
+
104
+ finally:
105
+ temp_path.unlink(missing_ok=True)
106
+
107
+
108
+ CUSTOM_CSS = """
109
+ .main, .wrap, .contain, .gradio-container, .app {
110
+ max-width: 100% !important;
111
+ width: 100% !important;
112
+ padding-left: 2rem !important;
113
+ padding-right: 2rem !important;
114
+ }
115
+ """
116
+
117
+ THEME = gr.themes.Base(
118
+ primary_hue="teal",
119
+ secondary_hue="cyan",
120
+ neutral_hue="slate",
121
+ ).set(
122
+ body_background_fill="#0f172a",
123
+ body_text_color="#e2e8f0",
124
+ block_background_fill="#1e293b",
125
+ block_border_color="#334155",
126
+ block_title_text_color="#5eead4",
127
+ input_background_fill="#334155",
128
+ input_border_color="#475569",
129
+ button_primary_background_fill="#14b8a6",
130
+ button_primary_background_fill_hover="#2dd4bf",
131
+ button_primary_text_color="#ffffff",
132
+ )
133
+
134
+
135
+ def create_interface():
136
+ """Create the Gradio interface."""
137
+
138
+ with gr.Blocks(title="Cordon", fill_width=True) as demo:
139
+
140
+ # Header
141
+ gr.HTML("""
142
+ <div style="text-align: center; padding: 16px 0 24px 0;">
143
+ <a href="https://github.com/calebevans/cordon" target="_blank" style="text-decoration: none;">
144
+ <h1 style="font-size: 2.5rem; letter-spacing: 0.2em; margin: 0 0 8px 0; color: #2dd4bf; cursor: pointer;">CORDON</h1>
145
+ </a>
146
+ <p style="color: #94a3b8; margin: 0;">Reduce logs to their semantically anomalous parts.</p>
147
+ </div>
148
+ """)
149
+
150
+ # Main content - two equal columns
151
+ with gr.Row():
152
+
153
+ # Left: Input
154
+ with gr.Column(scale=1):
155
+ log_input = gr.Textbox(
156
+ label="Input Log",
157
+ placeholder="Paste your log content here...",
158
+ lines=22,
159
+ )
160
+
161
+ with gr.Row():
162
+ load_sample_btn = gr.Button("πŸ“‚ Load Sample", size="sm")
163
+ clear_btn = gr.Button("Clear", size="sm")
164
+
165
+ with gr.Row():
166
+ window_size = gr.Slider(2, 20, value=4, step=1, label="Window Size")
167
+ k_neighbors = gr.Slider(2, 20, value=5, step=1, label="k Neighbors")
168
+
169
+ with gr.Row():
170
+ mode_select = gr.Dropdown(
171
+ choices=["Percentile", "Range"],
172
+ value="Percentile",
173
+ label="Mode",
174
+ scale=1
175
+ )
176
+ anomaly_percentile = gr.Slider(0.01, 0.5, value=0.1, step=0.01, label="Top N%", scale=2)
177
+ range_min = gr.Slider(0.0, 0.49, value=0.05, step=0.01, label="Min %", visible=False, scale=1)
178
+ range_max = gr.Slider(0.01, 0.5, value=0.15, step=0.01, label="Max %", visible=False, scale=1)
179
+
180
+ def toggle_mode(mode):
181
+ if mode == "Percentile":
182
+ return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
183
+ else:
184
+ return gr.update(visible=False), gr.update(visible=True), gr.update(visible=True)
185
+
186
+ mode_select.change(
187
+ fn=toggle_mode,
188
+ inputs=[mode_select],
189
+ outputs=[anomaly_percentile, range_min, range_max]
190
+ )
191
+
192
+ analyze_btn = gr.Button("πŸ” Analyze", variant="primary", size="lg")
193
+
194
+ # Right: Output
195
+ with gr.Column(scale=1):
196
+ output_text = gr.Textbox(
197
+ label="Anomalous Sections",
198
+ placeholder="Results will appear here...",
199
+ lines=37,
200
+ )
201
+
202
+
203
+ # Documentation section
204
+ gr.Markdown("""
205
+ ---
206
+
207
+ ## Documentation
208
+
209
+ **Cordon** is a semantic anomaly detection tool for system log files. It uses transformer embeddings and k-NN density scoring to identify semantically unusual patterns, reducing massive logs to their most anomalous sections.
210
+
211
+ ### Why Cordon?
212
+
213
+ Traditional log analysis tools use keyword matching or regex patterns. Cordon understands the *meaning* of log content. This means:
214
+
215
+ - **Repetitive errors are filtered out** β€” The same error 1000 times? That's "normal background noise."
216
+ - **Rare patterns surface** β€” Unusual state transitions, unexpected clusters, one-off events.
217
+ - **No rules to maintain** β€” The model learns what's normal from your logs.
218
+
219
+ ### How It Works
220
+
221
+ ```
222
+ Log File β†’ Segmentation β†’ Embedding β†’ Scoring β†’ Thresholding β†’ Output
223
+ ```
224
+
225
+ 1. **Segmentation** β€” Split logs into windows of N lines
226
+ 2. **Embedding** β€” Convert each window to a vector using transformer models
227
+ 3. **Scoring** β€” For each window, find its k nearest neighbors in the embedding space and calculate the average distance. Windows that are *far* from their neighbors are semantically unique (anomalous). Windows that are *close* to many others are repetitive (normal).
228
+ 4. **Thresholding** β€” Keep top N% highest-scoring windows
229
+ 5. **Output** β€” XML-tagged blocks with line numbers and scores
230
+
231
+ ### Installation
232
+
233
+ ```bash
234
+ # With uv (recommended)
235
+ uv pip install cordon
236
+
237
+ # With pip
238
+ pip install cordon
239
+ ```
240
+
241
+ ### Command Line Usage
242
+
243
+ ```bash
244
+ # Basic usage
245
+ cordon system.log
246
+
247
+ # Customize parameters
248
+ cordon --window-size 10 --k-neighbors 10 --anomaly-percentile 0.05 app.log
249
+
250
+ # Use range mode (exclude top 5%, keep next 10%)
251
+ cordon --anomaly-range 0.05 0.15 app.log
252
+
253
+ # Save to file
254
+ cordon --output anomalies.xml system.log
255
+ ```
256
+
257
+ ### Python API
258
+
259
+ ```python
260
+ from cordon import SemanticLogAnalyzer, AnalysisConfig
261
+
262
+ config = AnalysisConfig(window_size=4, k_neighbors=5, anomaly_percentile=0.1)
263
+ analyzer = SemanticLogAnalyzer(config)
264
+ result = analyzer.analyze_file(Path("system.log"))
265
+ ```
266
+
267
+ ### Links
268
+
269
+ - [GitHub Repository](https://github.com/calebevans/cordon)
270
+ - [PyPI Package](https://pypi.org/project/cordon/)
271
+ - [Red Hat Developer Article](https://developers.redhat.com/articles/2025/12/09/semantic-anomaly-detection-log-files-cordon)
272
+ """)
273
+
274
+ # Events
275
+ load_sample_btn.click(fn=load_sample_log, outputs=[log_input])
276
+ clear_btn.click(fn=lambda: "", outputs=[log_input])
277
+
278
+ def run_analysis(log_content, window_size, k_neighbors, mode, anomaly_percentile, range_min, range_max, progress=gr.Progress()):
279
+ output, label = analyze_logs(log_content, window_size, k_neighbors, mode, anomaly_percentile, range_min, range_max, progress)
280
+ return gr.update(value=output, label=label)
281
+
282
+ analyze_btn.click(
283
+ fn=run_analysis,
284
+ inputs=[log_input, window_size, k_neighbors, mode_select, anomaly_percentile, range_min, range_max],
285
+ outputs=[output_text],
286
+ )
287
+
288
+ return demo
289
+
290
+
291
+ if __name__ == "__main__":
292
+ demo = create_interface()
293
+ demo.launch(theme=THEME, css=CUSTOM_CSS)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Cordon package from PyPI
2
+ cordon>=0.3.0
3
+
4
+ # UI dependencies
5
+ gradio>=4.0.0
sample_apache.log ADDED
The diff for this file is too large to render. See raw diff