berangerthomas commited on
Commit
d1fd9e1
·
1 Parent(s): f972805

Add multiple views and graphs

Browse files
app.py CHANGED
@@ -1,114 +1,39 @@
1
- #####################################################
2
- #### Imports ####
3
- #####################################################
4
- import os
5
- import tempfile
6
- from datetime import datetime
7
 
8
  import streamlit as st
9
 
10
- from config.log_definitions import log_definitions
11
- from utils.log2pandas import LogParser
12
- from utils.pandas2sql import Pandas2SQL
13
 
14
- #####################################################
15
- #### Interface Setup ####
16
- #####################################################
17
 
18
- st.title("ShadowLog - Log File Analyzer")
19
- st.write("Upload a log file to analyze and/or convert it to SQLite")
 
 
20
 
21
- # File upload widget
22
- uploaded_file = st.file_uploader("Choose a log file")
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
- # Get available log types from log_definitions
25
- log_types = list(log_definitions.keys())
26
- # Set default log type if not already in session state
27
- if "log_type" not in st.session_state:
28
- st.session_state.log_type = log_types[0] # Default to first log type
29
 
30
- st.session_state.log_type = st.selectbox(
31
- "Select log type", log_types, index=log_types.index(st.session_state.log_type)
32
- )
33
 
34
- # Store the parsed dataframe in the session state
35
- if "parsed_df" not in st.session_state:
36
- st.session_state.parsed_df = None
37
 
38
- if uploaded_file is not None:
39
- # Create two columns for the buttons
40
- col1, col2 = st.columns(2)
 
 
41
 
42
- with col1:
43
- # Button to parse the log file
44
- if st.button("Parse the log file"):
45
- with st.spinner("Analyzing the file..."):
46
- # Create a temporary file
47
- with tempfile.NamedTemporaryFile(
48
- delete=False, suffix=".log"
49
- ) as tmp_file:
50
- tmp_file.write(uploaded_file.getvalue())
51
- tmp_path = tmp_file.name
52
-
53
- try:
54
- # Parse the log file
55
- parser = LogParser(tmp_path, st.session_state.log_type)
56
- st.session_state.parsed_df = parser.parse_file()
57
-
58
- # Display a success message and the dataframe
59
- st.success("Log file successfully analyzed!")
60
- # st.dataframe(st.session_state.parsed_df)
61
- except Exception as e:
62
- st.error(f"Error analyzing the file: {e}")
63
- finally:
64
- # Clean up the temporary file
65
- os.unlink(tmp_path)
66
-
67
- with col2:
68
- # Button to convert to SQLite and download
69
- if st.button("Convert to SQLite"):
70
- if st.session_state.parsed_df is not None:
71
- with st.spinner("Converting to SQLite..."):
72
- try:
73
- # Create a temporary SQLite file
74
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
75
- sqlite_path = os.path.join(
76
- tempfile.gettempdir(), f"log_data_{timestamp}.sqlite"
77
- )
78
-
79
- # Create the SQL converter
80
- sql_converter = Pandas2SQL(sqlite_path)
81
-
82
- # Convert the dataframe to SQLite
83
- sql_converter.create_table(
84
- st.session_state.parsed_df, st.session_state.log_type
85
- )
86
-
87
- # Read the SQLite file for download
88
- with open(sqlite_path, "rb") as file:
89
- sqlite_data = file.read()
90
-
91
- # Success message and immediate download
92
- st.success("SQLite file created successfully!")
93
-
94
- # Download button
95
- st.download_button(
96
- label="Download SQLite file",
97
- data=sqlite_data,
98
- file_name=f"log_file_{st.session_state.log_type}_{timestamp}.sqlite",
99
- mime="application/octet-stream",
100
- key="auto_download",
101
- )
102
- except Exception as e:
103
- st.error(f"Error converting to SQLite: {e}")
104
- finally:
105
- # Clean up the temporary file
106
- if os.path.exists(sqlite_path):
107
- os.unlink(sqlite_path)
108
- else:
109
- st.warning("Please parse the log file first.")
110
-
111
- # Display the dataframe if available
112
- if st.session_state.parsed_df is not None:
113
- st.subheader("Analyzed log data")
114
- st.dataframe(st.session_state.parsed_df)
 
1
+ import base64
 
 
 
 
 
2
 
3
  import streamlit as st
4
 
5
+ st.set_page_config(page_title=" ShadowLog ", page_icon="assets/logo.png", layout="wide")
 
 
6
 
 
 
 
7
 
8
+ def add_logo():
9
+ # Lecture du fichier image local
10
+ with open("assets/small_logo_no_text.png", "rb") as f:
11
+ logo_data = base64.b64encode(f.read()).decode()
12
 
13
+ st.markdown(
14
+ f"""
15
+ <style>
16
+ [data-testid="stSidebarNav"] {{
17
+ background-image: url("data:image/png;base64,{logo_data}");
18
+ background-repeat: no-repeat;
19
+ padding-top: 225px;
20
+ background-position: center 20px;
21
+ background-size: 50%;
22
+ }}
23
+ </style>
24
+ """,
25
+ unsafe_allow_html=True,
26
+ )
27
 
 
 
 
 
 
28
 
29
+ add_logo()
 
 
30
 
 
 
 
31
 
32
+ # Pages definition
33
+ home = st.Page("pages/home.py", title="🏠 Home")
34
+ upload = st.Page("pages/upload.py", title="📥 Upload")
35
+ analyze = st.Page("pages/analyze.py", title=" 📊 Analyze")
36
+ about = st.Page("pages/about.py", title="📄 About")
37
 
38
+ pg = st.navigation([home, upload, analyze, about])
39
+ pg.run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/logo.jpg ADDED
assets/logo_large.png ADDED
assets/no_text_logo.png ADDED
assets/small_logo.png ADDED
assets/small_logo_background.png ADDED
assets/small_logo_grey_background.png ADDED
assets/small_logo_no_text.png ADDED
assets/transparent_logo.png ADDED
pages/about.py ADDED
File without changes
pages/analyze.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import plotly.express as px
3
+ import streamlit as st
4
+
5
+ if "parsed_df" not in st.session_state:
6
+ st.session_state.parsed_df = None
7
+
8
+ # Page title
9
+ st.title("Data Analysis")
10
+
11
+ # Loading data
12
+ if st.session_state.parsed_df is None:
13
+ st.info("Please upload a log file on the 'Upload' page.")
14
+ st.stop()
15
+
16
+ data = st.session_state.parsed_df
17
+
18
+ # Sidebar for controls
19
+ st.sidebar.header("Visualization Options")
20
+
21
+ # Check if there are datetime columns
22
+ datetime_columns = data.select_dtypes(include=["datetime64"]).columns.tolist()
23
+ # Try to detect string columns that could be dates
24
+ if not datetime_columns:
25
+ for col in data.select_dtypes(include=["object"]).columns:
26
+ try:
27
+ pd.to_datetime(data[col], errors="raise")
28
+ datetime_columns.append(col)
29
+ except (ValueError, TypeError):
30
+ pass
31
+
32
+ # Chart type options
33
+ chart_options = ["Pie Chart", "Bar Chart", "Histogram"]
34
+ if datetime_columns:
35
+ chart_options.append("Time Series")
36
+
37
+ chart_type = st.sidebar.selectbox("Choose chart type", chart_options)
38
+
39
+ # Get categorical columns
40
+ categorical_columns = data.select_dtypes(include=["object"]).columns.tolist()
41
+
42
+ # Main area for visualization
43
+ if chart_type == "Pie Chart":
44
+ st.header("Pie Chart")
45
+
46
+ # Select variable to visualize
47
+ selected_column = st.sidebar.selectbox(
48
+ "Select a categorical variable", categorical_columns
49
+ )
50
+
51
+ # Create and display pie chart
52
+ fig = px.pie(
53
+ data,
54
+ names=selected_column,
55
+ title=f"Distribution of '{selected_column}'",
56
+ )
57
+ st.plotly_chart(fig)
58
+
59
+ # Display value table
60
+ st.write("Value distribution:")
61
+ st.write(data[selected_column].value_counts())
62
+
63
+ elif chart_type == "Bar Chart":
64
+ st.header("Bar Chart")
65
+
66
+ selected_column = st.sidebar.selectbox("Select a variable", categorical_columns)
67
+
68
+ results = data[selected_column].value_counts().reset_index()
69
+ results.columns = ["category", "count"] # Explicitly rename columns
70
+ fig = px.bar(
71
+ results,
72
+ x="category",
73
+ y="count",
74
+ labels={"category": selected_column, "count": "Count"},
75
+ )
76
+ st.plotly_chart(fig)
77
+
78
+ elif chart_type == "Histogram":
79
+ st.header("Histogram")
80
+
81
+ numerical_columns = data.select_dtypes(include=["int", "float"]).columns.tolist()
82
+ if numerical_columns:
83
+ selected_column = st.sidebar.selectbox(
84
+ "Select a numerical variable", numerical_columns
85
+ )
86
+ fig = px.histogram(data, x=selected_column)
87
+ st.plotly_chart(fig)
88
+ else:
89
+ st.write("No numerical columns available for histogram.")
90
+
91
+ elif chart_type == "Time Series":
92
+ st.header("Time Series")
93
+
94
+ # Select datetime column for x-axis
95
+ datetime_col = st.sidebar.selectbox("Select datetime column", datetime_columns)
96
+
97
+ # Convert to datetime if needed
98
+ if data[datetime_col].dtype != "datetime64[ns]":
99
+ data[datetime_col] = pd.to_datetime(data[datetime_col])
100
+
101
+ # Get numerical columns for y-axis
102
+ numerical_columns = data.select_dtypes(include=["int", "float"]).columns.tolist()
103
+
104
+ if numerical_columns:
105
+ y_column = st.sidebar.selectbox("Select y-axis variable", numerical_columns)
106
+
107
+ # Option to aggregate data
108
+ if st.sidebar.checkbox("Aggregate by time period"):
109
+ period = st.sidebar.selectbox(
110
+ "Select period", ["Day", "Week", "Month", "Year"]
111
+ )
112
+ freq_map = {"Day": "D", "Week": "W", "Month": "M", "Year": "Y"}
113
+
114
+ grouped_data = (
115
+ data.groupby(pd.Grouper(key=datetime_col, freq=freq_map[period]))[
116
+ y_column
117
+ ]
118
+ .mean()
119
+ .reset_index()
120
+ )
121
+
122
+ fig = px.line(
123
+ grouped_data,
124
+ x=datetime_col,
125
+ y=y_column,
126
+ title=f"{y_column} over time (by {period.lower()})",
127
+ )
128
+ else:
129
+ fig = px.line(
130
+ data.sort_values(by=datetime_col),
131
+ x=datetime_col,
132
+ y=y_column,
133
+ title=f"{y_column} over time",
134
+ )
135
+
136
+ st.plotly_chart(fig)
137
+ else:
138
+ st.write("No numerical columns available for y-axis.")
139
+
140
+ # Option to display raw data
141
+ if st.sidebar.checkbox("Show raw data"):
142
+ st.subheader("Data")
143
+
144
+ if chart_type in ["Pie Chart", "Bar Chart"]:
145
+ # For categorical charts, allow filtering by category
146
+ filter_option = st.selectbox(
147
+ f"Filter by {selected_column}:",
148
+ ["Show all data"] + sorted(data[selected_column].unique().tolist()),
149
+ )
150
+
151
+ if filter_option != "Show all data":
152
+ filtered_data = data[data[selected_column] == filter_option]
153
+ st.write(filtered_data)
154
+ else:
155
+ st.write(data)
156
+
157
+ elif chart_type == "Histogram" and numerical_columns:
158
+ # For histogram, allow filtering by value range
159
+ min_val = float(data[selected_column].min())
160
+ max_val = float(data[selected_column].max())
161
+
162
+ selected_range = st.slider(
163
+ f"Filter by {selected_column} range:", min_val, max_val, (min_val, max_val)
164
+ )
165
+
166
+ filtered_data = data[
167
+ (data[selected_column] >= selected_range[0])
168
+ & (data[selected_column] <= selected_range[1])
169
+ ]
170
+ st.write(filtered_data)
171
+ elif chart_type == "Time Series":
172
+ # For time series, filter by date range
173
+ min_date = data[datetime_col].min().date()
174
+ max_date = data[datetime_col].max().date()
175
+
176
+ date_range = st.date_input(
177
+ "Filter by date range",
178
+ value=[min_date, max_date],
179
+ min_value=min_date,
180
+ max_value=max_date,
181
+ )
182
+
183
+ if len(date_range) == 2:
184
+ start_date, end_date = date_range
185
+ filtered_data = data[
186
+ (data[datetime_col].dt.date >= start_date)
187
+ & (data[datetime_col].dt.date <= end_date)
188
+ ]
189
+ st.write(filtered_data)
190
+ else:
191
+ st.write(data)
192
+ else:
193
+ st.write(data)
pages/home.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from PIL import Image
3
+
4
+ # Page configuration
5
+ # st.set_page_config(page_title="ShadowLog - Home", page_icon="📊", layout="wide")
6
+
7
+ # Main page with logo
8
+ try:
9
+ logo = Image.open("assets/logo_large.png")
10
+ st.image(logo, use_container_width=True)
11
+ except FileNotFoundError:
12
+ st.error("Logo not found. Please check the path: assets/logo_large.png")
13
+
14
+ # Main content
15
+ st.title("Welcome to ShadowLog")
16
+ st.markdown("### Your Advanced Log Analysis Platform")
17
+
18
+ # Introduction
19
+ st.write("""
20
+ ShadowLog is a powerful tool designed to simplify and enhance log file analysis.
21
+ Whether you're debugging an application, monitoring system performance, or investigating security incidents,
22
+ ShadowLog provides the tools you need to efficiently process and extract insights from your log data.
23
+ """)
24
+
25
+ # Features section
26
+ st.header("Key Features")
27
+
28
+ col1, col2 = st.columns(2)
29
+
30
+ with col1:
31
+ st.subheader("📁 Log File Upload")
32
+ st.write("""
33
+ Upload log files in various formats including text, JSON, CSV, and more.
34
+ Support for compressed files (.zip, .gz) is also available.
35
+ """)
36
+
37
+ st.subheader("🔍 Advanced Parsing")
38
+ st.write("""
39
+ Automatically detect log formats or configure custom parsing rules.
40
+ Extract timestamp, log level, and message content with ease.
41
+ """)
42
+
43
+ with col2:
44
+ st.subheader("📊 Visual Analysis")
45
+ st.write("""
46
+ Generate insightful visualizations based on your log data.
47
+ Track patterns, anomalies, and trends to quickly identify issues.
48
+ """)
49
+
50
+ st.subheader("🔎 Search & Filter")
51
+ st.write("""
52
+ Powerful search functionality to find specific events or errors.
53
+ Filter logs by time, severity, source, or custom attributes.
54
+ """)
55
+
56
+ # Getting started section
57
+ st.header("Getting Started")
58
+ st.write("""
59
+ To begin analyzing your log files:
60
+ 1. Navigate to the 'Upload' page using the sidebar
61
+ 2. Upload your log file or select a sample file
62
+ 3. Configure parsing options if needed
63
+ 4. Explore the generated analysis and visualizations
64
+
65
+ Check out the documentation for more detailed instructions and advanced features.
66
+ """)
pages/upload.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #####################################################
2
+ #### Imports ####
3
+ #####################################################
4
+ import os
5
+ import tempfile
6
+ from datetime import datetime
7
+
8
+ import streamlit as st
9
+
10
+ from config.log_definitions import log_definitions
11
+ from utils.log2pandas import LogParser
12
+ from utils.pandas2sql import Pandas2SQL
13
+
14
+ #####################################################
15
+ #### Interface Setup ####
16
+ #####################################################
17
+
18
+ st.title("ShadowLog - Log File Analyzer")
19
+ st.write("Upload a log file to analyze")
20
+
21
+ # File upload widget
22
+ uploaded_file = st.file_uploader("Choose a log file")
23
+
24
+ # Get available log types from log_definitions
25
+ log_types = list(log_definitions.keys())
26
+ # Set default log type if not already in session state
27
+ if "log_type" not in st.session_state:
28
+ st.session_state.log_type = log_types[0] # Default to first log type
29
+
30
+ st.session_state.log_type = st.selectbox(
31
+ "Select log type", log_types, index=log_types.index(st.session_state.log_type)
32
+ )
33
+
34
+ # Store the parsed dataframe in the session state
35
+ if "parsed_df" not in st.session_state:
36
+ st.session_state.parsed_df = None
37
+
38
+ if uploaded_file is not None:
39
+ # Create two columns for the buttons
40
+ col1, col2 = st.columns(2)
41
+
42
+ with col1:
43
+ # Button to parse the log file
44
+ if st.button("Parse the log file"):
45
+ with st.spinner("Analyzing the file..."):
46
+ # Create a temporary file
47
+ with tempfile.NamedTemporaryFile(
48
+ delete=False, suffix=".log"
49
+ ) as tmp_file:
50
+ tmp_file.write(uploaded_file.getvalue())
51
+ tmp_path = tmp_file.name
52
+
53
+ try:
54
+ # Parse the log file
55
+ parser = LogParser(tmp_path, st.session_state.log_type)
56
+ st.session_state.parsed_df = parser.parse_file()
57
+
58
+ # Display a success message and the dataframe
59
+ st.success("Log file successfully analyzed!")
60
+ # st.dataframe(st.session_state.parsed_df)
61
+ except Exception as e:
62
+ st.error(f"Error analyzing the file: {e}")
63
+ finally:
64
+ # Clean up the temporary file
65
+ os.unlink(tmp_path)
66
+
67
+ with col2:
68
+ # Button to convert to SQLite and download
69
+ if st.button("Convert to SQLite"):
70
+ if st.session_state.parsed_df is not None:
71
+ with st.spinner("Converting to SQLite..."):
72
+ try:
73
+ # Create a temporary SQLite file
74
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
75
+ sqlite_path = os.path.join(
76
+ tempfile.gettempdir(), f"log_data_{timestamp}.sqlite"
77
+ )
78
+
79
+ # Create the SQL converter
80
+ sql_converter = Pandas2SQL(sqlite_path)
81
+
82
+ # Convert the dataframe to SQLite
83
+ sql_converter.create_table(
84
+ st.session_state.parsed_df, st.session_state.log_type
85
+ )
86
+
87
+ # Read the SQLite file for download
88
+ with open(sqlite_path, "rb") as file:
89
+ sqlite_data = file.read()
90
+
91
+ # Success message and immediate download
92
+ st.success("SQLite file created successfully!")
93
+
94
+ # Download button
95
+ st.download_button(
96
+ label="Download SQLite file",
97
+ data=sqlite_data,
98
+ file_name=f"log_file_{st.session_state.log_type}_{timestamp}.sqlite",
99
+ mime="application/octet-stream",
100
+ key="auto_download",
101
+ )
102
+ except Exception as e:
103
+ st.error(f"Error converting to SQLite: {e}")
104
+ finally:
105
+ # Clean up the temporary file
106
+ if os.path.exists(sqlite_path):
107
+ os.unlink(sqlite_path)
108
+ else:
109
+ st.warning("Please parse the log file first.")
110
+
111
+ # Display the dataframe if available
112
+ if st.session_state.parsed_df is not None:
113
+ st.subheader("Analyzed log data")
114
+ st.dataframe(st.session_state.parsed_df)
requirements.txt CHANGED
@@ -1,2 +1,3 @@
1
  pandas
2
- streamlit
 
 
1
  pandas
2
+ streamlit
3
+ plotly