-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathaiops_log_analysis.py
More file actions
283 lines (221 loc) · 10.9 KB
/
Copy pathaiops_log_analysis.py
File metadata and controls
283 lines (221 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
"""
My AIOps Log Analysis System
============================
Started this project to solve memory leak detection issues at work.
Traditional monitoring tools kept missing critical problems in INFO logs.
What it does:
- Automatically learns suspicious patterns from log data
- Uses multiple ML algorithms to reduce false positives
- Explains why each log entry was flagged as anomalous
- Saves results in easy-to-review CSV files
Built over several weeks of experimentation with different approaches.
The TF-IDF + ensemble method combo works best for my use cases.
Author: Shekhar Chaugule
Version: 2.1 (much better than my first attempt!)
"""
import pandas as pd
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
import re
import warnings
warnings.filterwarnings('ignore') # Got tired of sklearn warnings during development
# My log analysis project - evolved from simple anomaly detection
# TODO: Maybe add real-time processing later
# FIXME: Need better error handling for malformed logs
# NOTE: Current version works well for batch analysis
log_file_path = r"C:\Users\ShekharChaugule\Documents\AIOPSLOG\sample_logs.txt"
print(f"Loading logs from: {log_file_path}")
try:
with open(log_file_path, "r") as file:
logs = file.readlines()
print(f"Successfully loaded {len(logs)} log entries")
except FileNotFoundError:
print("Error: Log file not found!")
exit(1)
# my custom log parser - took a while to get this right
def parse_my_logs(log_lines):
"""Parse logs the way I need them - handles different formats I've seen"""
parsed_data = []
failed_count = 0
for line_num, log in enumerate(log_lines):
try:
parts = log.strip().split(" ", 3)
if len(parts) < 4:
failed_count += 1
continue # Skip weird lines
# combine date and time - this format works for my logs
timestamp = parts[0] + " " + parts[1]
level = parts[2]
message = parts[3]
parsed_data.append([timestamp, level, message])
except Exception as e:
# print(f"Debug: Failed to parse line {line_num}: {e}") # Used for debugging
failed_count += 1
continue
if failed_count > 0:
print(f"Warning: Couldn't parse {failed_count} lines (probably malformed)")
return parsed_data
# Parse all the logs
print("Parsing log entries...")
data = parse_my_logs(logs)
df = pd.DataFrame(data, columns=["timestamp", "level", "message"])
print(f"Successfully parsed {len(df)} log entries")
# Convert timestamps - pandas is pretty good at this
df["timestamp"] = pd.to_datetime(df["timestamp"], errors='coerce')
# My scoring system for log levels - learned this from experience
# INFO is usually fine, CRITICAL means wake me up at 3am!
my_level_scores = {"INFO": 1, "WARNING": 2, "ERROR": 3, "CRITICAL": 4}
df["level_score"] = df["level"].map(my_level_scores)
# Message length might be important - longer messages often mean trouble
df["msg_len"] = df["message"].apply(len)
# quick check of what we're working with
print(f"Log levels found: {df['level'].value_counts().to_dict()}")
print(f"Average message length: {df['msg_len'].mean():.1f} characters")
# my anomaly detection system - this took me weeks to get right!
def detect_log_anomalies(log_df):
"""
My approach to finding weird stuff in logs
Tried different methods, this combo works best for my use case
"""
print("Analyzing log patterns...")
# Step 1: Learn what words are important using TF-IDF
# Had to experiment with these parameters - 85 features works better than 100 for my logs
my_vectorizer = TfidfVectorizer(
max_features=85, # Found this works better than default
stop_words='english',
ngram_range=(1, 2) # Single words and pairs
)
text_matrix = my_vectorizer.fit_transform(log_df['message']).toarray()
# step 2: Combine text features with numeric ones
numeric_data = log_df[["level_score", "msg_len"]].values
# Scale the numbers so they play nice with text features
my_scaler = StandardScaler()
scaled_numbers = my_scaler.fit_transform(numeric_data)
# combine everything into one big feature matrix
combined_features = np.hstack([scaled_numbers, text_matrix])
print("Running anomaly detection algorithms...")
# Method 1: Isolation Forest - good at finding outliers
# tweaked contamination to 0.08 after testing with my data
iso_detector = IsolationForest(
contamination=0.08, # My sweet spot after experimentation
random_state=42, # For reproducible results
n_estimators=150 # Reduced from 200, faster and works just as well
)
iso_results = iso_detector.fit_predict(combined_features)
# Method 2: DBSCAN clustering - finds weird clusters
# eps=0.6 works better for my log data than 0.5
cluster_detector = DBSCAN(eps=0.6, min_samples=4) # min_samples=4 reduces noise
cluster_labels = cluster_detector.fit_predict(combined_features)
cluster_results = np.where(cluster_labels == -1, -1, 1)
# Method 3: Statistical outlier detection
# Using Z-score > 2.3 instead of 2.5 - catches more edge cases
z_scores = np.abs((scaled_numbers - scaled_numbers.mean(axis=0)) / scaled_numbers.std(axis=0))
stat_results = np.where(np.max(z_scores, axis=1) > 2.3, -1, 1)
# Combine all three methods - need at least 2 to agree
votes = iso_results + cluster_results + stat_results
final_decisions = np.where(votes <= -1, -1, 1)
# Figure out what patterns the system learned
feature_names = my_vectorizer.get_feature_names_out()
importance_scores = np.abs(text_matrix).mean(axis=0)
# Get top suspicious patterns
top_indices = np.argsort(importance_scores)[-8:] # Top 8 instead of 10
suspicious_patterns = [feature_names[i] for i in top_indices]
print(f"Found these suspicious patterns: {', '.join(suspicious_patterns)}")
return final_decisions, suspicious_patterns, text_matrix, feature_names
# Run my anomaly detection system
df["anomaly_score"], suspicious_words, text_features, all_features = detect_log_anomalies(df)
# Convert scores to readable labels
df["status"] = df["anomaly_score"].apply(lambda x: "❌ Anomaly" if x == -1 else "✅ Normal")
# My reasoning system - explains why something looks suspicious
def explain_why_anomaly(log_row, row_idx):
"""
Figure out why this log entry was flagged
This helps me understand what the system is thinking
"""
if log_row["status"] == "✅ Normal":
return "Looks normal to me"
explanations = []
# Check what text patterns triggered it
row_features = text_features[row_idx]
top_feature_idx = np.argsort(row_features)[-4:] # Top 4 patterns
triggered_patterns = [all_features[i] for i in top_feature_idx if row_features[i] > 0]
if triggered_patterns:
explanations.append(f"Suspicious patterns found: {', '.join(triggered_patterns)}")
# Cheeck if message length is weird
avg_len = df["msg_len"].mean()
std_len = df["msg_len"].std()
if abs(log_row["msg_len"] - avg_len) > 2 * std_len:
explanations.append("Message length is unusual")
# High severity levels are always suspicious
if log_row["level"] in ["ERROR", "CRITICAL"]:
explanations.append(f"High severity: {log_row['level']}")
# Check for overlap with known suspicious words
msg_words = set(log_row["message"].lower().split())
matching_suspicious = msg_words.intersection(set(suspicious_words))
if matching_suspicious:
explanations.append(f"Contains suspicious terms: {', '.join(matching_suspicious)}")
# Look for performance metrics (usually indicate problems)
if "%" in log_row["message"] and any(c.isdigit() for c in log_row["message"]):
explanations.append("Contains performance metrics")
# Fallback explanation
if not explanations:
explanations.append("Multiple detection methods flagged this")
return "; ".join(explanations)
# Generate explanations for each log entry
print("Generating explanations for detected anomalies...")
df["explanation"] = [explain_why_anomaly(row, idx) for idx, row in df.iterrows()]
# Sort by time to see the sequence of events
df = df.sort_values("timestamp")
# Show me what we found
anomaly_logs = df[df["status"] == "❌ Anomaly"]
print(f"\n🔍 Found {len(anomaly_logs)} suspicious log entries:")
if len(anomaly_logs) > 0:
# Show first few anomalies with key info
display_cols = ['timestamp', 'level', 'message', 'explanation']
print(anomaly_logs[display_cols].head(8).to_string(index=False))
else:
print("No anomalies detected - system looks healthy!")
# My file saving functions - keep both detailed and summary reports
def save_full_analysis(data_df, filename="my_log_analysis.csv"):
"""Save everything - I like having all the data for later analysis"""
try:
data_df.to_csv(filename, index=False)
total = len(data_df)
anomalies = len(data_df[data_df["status"] == "❌ Anomaly"])
normal = total - anomalies
print(f"\n💾 Full analysis saved to '{filename}'")
print(f"📊 Processed {total} log entries")
print(f"✅ Normal: {normal} entries")
print(f"❌ Suspicious: {anomalies} entries")
if anomalies > 0:
print(f" Anomaly rate: {(anomalies/total)*100:.1f}%")
except Exception as e:
print(f"Error saving full report: {e}")
def save_just_anomalies(data_df, filename="suspicious_logs.csv"):
"""Save only the problematic entries - easier for quick review"""
try:
problem_logs = data_df[data_df["status"] == "❌ Anomaly"].copy()
if len(problem_logs) > 0:
# Keep the important columns for incident response
important_cols = ['timestamp', 'level', 'message', 'status', 'explanation']
problem_logs[important_cols].to_csv(filename, index=False)
print(f"🚨 Suspicious entries saved to '{filename}'")
print(f" 📋 {len(problem_logs)} entries need attention")
# Quick summary of what we found
level_counts = problem_logs['level'].value_counts()
print(f" Breakdown: {level_counts.to_dict()}")
else:
print("✅ No suspicious entries found - nothing to save")
except Exception as e:
print(f"Error saving anomaly report: {e}")
# Save both reports - I find both useful
print("\nSaving analysis results...")
save_full_analysis(df)
save_just_anomalies(df)
print("\n" + "="*50)
print("Analysis complete! 🎉")
print("Check the CSV files for detailed results.")
print("="*50)