File size: 11,542 Bytes
6e44555 b8a7a67 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 f9b4d61 6e44555 f9b4d61 6e44555 d03dff9 6e44555 d03dff9 f9b4d61 d03dff9 f9b4d61 d03dff9 f9b4d61 d03dff9 f9b4d61 d03dff9 f9b4d61 d03dff9 f9b4d61 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 f9b4d61 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 f9b4d61 d03dff9 f9b4d61 d03dff9 f9b4d61 d03dff9 f9b4d61 d03dff9 f9b4d61 d03dff9 f9b4d61 d03dff9 f9b4d61 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 6e44555 d03dff9 | 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 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | import gradio as gr
import json
import os
import pandas as pd
from filter_utils import filter_lines
from timestamp_utils import parse_timestamp
def add_file(files, state):
if files:
for file in files:
filename = os.path.basename(file.name)
if filename not in state:
with open(file.name) as f:
lines = f.readlines()
parsed_lines = []
for line in lines:
parsed_lines.append({
"timestamp": parse_timestamp(line),
"content": line,
"source": filename
})
state[filename] = {"lines": parsed_lines, "filters": []}
file_selector_update = gr.update(choices=list(state.keys()), value=list(state.keys())[0] if state else None)
selected_file = list(state.keys())[0] if state else None
table_output_update = generate_merged_view(state)
filter_list_update = update_filter_list(selected_file, state)
return state, file_selector_update, table_output_update, filter_list_update
def select_file(selected_file, state):
if selected_file and selected_file in state:
filter_list_update = update_filter_list(selected_file, state)
return filter_list_update
return gr.update(choices=[])
def add_filter(state, selected_file, filter_type, filter_value, case_sensitive):
if selected_file and filter_value:
state[selected_file]["filters"].append({"type": filter_type, "value": filter_value, "case_sensitive": case_sensitive})
return state, ""
def remove_filter(state, selected_file, selected_filter):
if selected_file and selected_filter:
parts = selected_filter.split(" - ")
if len(parts) == 3:
filter_type, filter_value, case_str = parts
case_sensitive = case_str == "Case Sensitive: True"
filters = state[selected_file]["filters"]
for i, f in enumerate(filters):
if f["type"] == filter_type and f["value"] == filter_value and f["case_sensitive"] == case_sensitive:
filters.pop(i)
break
return state, None
def move_filter_up(state, selected_file, selected_filter):
if selected_file and selected_filter:
parts = selected_filter.split(" - ")
if len(parts) == 3:
filter_type, filter_value, case_str = parts
case_sensitive = case_str == "Case Sensitive: True"
filters = state[selected_file]["filters"]
for i, f in enumerate(filters):
if f["type"] == filter_type and f["value"] == filter_value and f["case_sensitive"] == case_sensitive:
if i > 0:
filters[i], filters[i-1] = filters[i-1], filters[i]
break
return state, selected_filter
def move_filter_down(state, selected_file, selected_filter):
if selected_file and selected_filter:
parts = selected_filter.split(" - ")
if len(parts) == 3:
filter_type, filter_value, case_str = parts
case_sensitive = case_str == "Case Sensitive: True"
filters = state[selected_file]["filters"]
for i, f in enumerate(filters):
if f["type"] == filter_type and f["value"] == filter_value and f["case_sensitive"] == case_sensitive:
if i < len(filters) - 1:
filters[i], filters[i+1] = filters[i+1], filters[i]
break
return state, selected_filter
def generate_merged_view(state):
all_lines = []
for filename, data in state.items():
lines = data["lines"]
filters = data["filters"]
processed_lines = lines
for f in filters:
filter_type = f["type"]
value = f["value"]
case = f["case_sensitive"]
content_lines = [line["content"] for line in processed_lines]
if filter_type == "Include Text":
filtered_content = filter_lines(content_lines, include_text=value, case_sensitive=case)
elif filter_type == "Exclude Text":
filtered_content = filter_lines(content_lines, exclude_text=value, case_sensitive=case)
elif filter_type == "Include Regex":
filtered_content = filter_lines(content_lines, include_regex=value, case_sensitive=case)
elif filter_type == "Exclude Regex":
filtered_content = filter_lines(content_lines, exclude_regex=value, case_sensitive=case)
processed_lines = [line for line in processed_lines if line["content"] in filtered_content]
all_lines.extend(processed_lines)
if not all_lines:
return pd.DataFrame(columns=["File", "Timestamp", "Log Entry"])
# Sort lines by timestamp
all_lines.sort(key=lambda x: x["timestamp"] if x["timestamp"] is not None else pd.Timestamp.min)
df = pd.DataFrame(all_lines)
df["Timestamp"] = df["timestamp"].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S') if pd.notnull(x) else "")
df = df.rename(columns={"source": "File", "content": "Log Entry"})
return df[["File", "Timestamp", "Log Entry"]]
def update_filter_list(selected_file, state):
if selected_file and selected_file in state:
filters = state[selected_file]["filters"]
filter_strings = [f'{f["type"]} - {f["value"]} - Case Sensitive: {f["case_sensitive"]}' for f in filters]
return gr.update(choices=filter_strings)
return gr.update(choices=[])
def save_filters(selected_file, state):
if selected_file and selected_file in state:
filters = state[selected_file]["filters"]
with open(f"{selected_file}_filters.json", "w") as f:
json.dump(filters, f)
return f"{selected_file}_filters.json"
return None
def load_filters(selected_file, state, filter_file):
if selected_file and filter_file is not None:
with open(filter_file.name) as f:
state[selected_file]["filters"] = json.load(f)
return state
def save_filtered_log(log_data):
if not log_data.empty:
log_content = ""
for index, row in log_data.iterrows():
log_content += f"[{row['File']}] [{row['Timestamp']}] {row['Log Entry']}"
with open("filtered_log.txt", "w") as f:
f.write(log_content)
return "filtered_log.txt"
return None
def show_regex_help():
gr.Info("""
**Regular Expression Quick Guide**
- `.` : Matches any single character.
- `*` : Matches the preceding character zero or more times.
- `+` : Matches the preceding character one or more times.
- `^` : Matches the start of a line.
: Matches the end of a line.
- `|` : Acts as an OR operator (e.g., `error|warn`).
- `[...]`: Matches any single character within the brackets (e.g., `[aeiou]`).
- `(...)`: Groups expressions.
**Example:** `^ERROR.*database` will find lines starting with "ERROR" that also contain "database".
""")
with gr.Blocks(theme=gr.themes.Soft(), css="#log_content .gr-dataframe { font-family: monospace; } .gradio-toast { max-width: 500px !important; }") as demo:
files_state = gr.State({})
gr.Markdown("## Log File Viewer")
with gr.Row():
file_input = gr.File(label="Upload Log File(s)", file_count="multiple")
file_selector = gr.Dropdown(label="Select Log File")
gr.Markdown("### Filters")
with gr.Row(elem_id="filter_row"):
filter_type = gr.Dropdown([
"Include Text", "Exclude Text", "Include Regex", "Exclude Regex"
], label="Filter Type")
filter_value = gr.Textbox(label="Filter Value", scale=4)
with gr.Column(scale=0, min_width=50):
help_button = gr.Button("?", scale=0)
case_sensitive_checkbox = gr.Checkbox(label="Case Sensitive", value=True)
with gr.Column(scale=0):
add_filter_button = gr.Button("Add Filter")
save_filters_button = gr.Button("Save Filters")
load_filters_file = gr.UploadButton("Load Filters (.json)", file_types=[".json"])
with gr.Row():
applied_filters_list = gr.Radio(label="Applied Filters", interactive=True)
remove_filter_button = gr.Button("Remove Selected Filter")
move_up_button = gr.Button("Move Up")
move_down_button = gr.Button("Move Down")
with gr.Row():
save_filtered_log_button = gr.Button("Save Filtered Log")
log_table = gr.DataFrame(headers=["File", "Timestamp", "Log Entry"], interactive=False, elem_id="log_content")
# Event Handlers
help_button.click(show_regex_help, inputs=None, outputs=None)
file_input.upload(
add_file,
inputs=[file_input, files_state],
outputs=[files_state, file_selector, log_table, applied_filters_list]
)
file_selector.change(
select_file,
inputs=[file_selector, files_state],
outputs=[applied_filters_list]
).then(
generate_merged_view,
inputs=files_state,
outputs=log_table
)
add_filter_button.click(
add_filter,
inputs=[files_state, file_selector, filter_type, filter_value, case_sensitive_checkbox],
outputs=[files_state, filter_value]
).then(
update_filter_list,
inputs=[file_selector, files_state],
outputs=applied_filters_list
).then(
generate_merged_view,
inputs=files_state,
outputs=log_table
)
remove_filter_button.click(
remove_filter,
inputs=[files_state, file_selector, applied_filters_list],
outputs=[files_state, applied_filters_list]
).then(
update_filter_list,
inputs=[file_selector, files_state],
outputs=applied_filters_list
).then(
generate_merged_view,
inputs=files_state,
outputs=log_table
)
move_up_button.click(
move_filter_up,
inputs=[files_state, file_selector, applied_filters_list],
outputs=[files_state, applied_filters_list]
).then(
update_filter_list,
inputs=[file_selector, files_state],
outputs=applied_filters_list
).then(
generate_merged_view,
inputs=files_state,
outputs=log_table
)
move_down_button.click(
move_filter_down,
inputs=[files_state, file_selector, applied_filters_list],
outputs=[files_state, applied_filters_list]
).then(
update_filter_list,
inputs=[file_selector, files_state],
outputs=applied_filters_list
).then(
generate_merged_view,
inputs=files_state,
outputs=log_table
)
save_filters_button.click(
save_filters,
inputs=[file_selector, files_state],
outputs=gr.File(label="Download Filter File")
)
load_filters_file.upload(
load_filters,
inputs=[file_selector, files_state, load_filters_file],
outputs=files_state
).then(
update_filter_list,
inputs=[file_selector, files_state],
outputs=applied_filters_list
).then(
generate_merged_view,
inputs=files_state,
outputs=log_table
)
save_filtered_log_button.click(
save_filtered_log,
inputs=log_table,
outputs=gr.File(label="Download Filtered Log")
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860) |