Carregar ficheiros para "/"

This commit is contained in:
2024-12-05 01:24:00 -08:00
commit 003bf5481e
5 changed files with 1914 additions and 0 deletions

492
binary_graph.py Normal file
View File

@ -0,0 +1,492 @@
import tkinter as tk
from tkinter import ttk, messagebox
import numpy as np
from logger import logger
class BinaryGraphWindow:
def __init__(self, parent, binary_data):
try:
logger.info("Initializing Binary Graph Window")
# Validate input
if not isinstance(binary_data, (list, np.ndarray)) or len(binary_data) == 0:
raise ValueError("Invalid binary data: must be a non-empty list or numpy array")
# Store data as instance attribute
self.binary_data = list(binary_data)
self.parent = parent
# Criar janela
self.window = tk.Toplevel(parent)
self.window.title("2D Binary Graph")
self.window.geometry("1000x600")
# Frame principal
self.main_frame = ttk.Frame(self.window)
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Frame para os botões de bits e configurações
self.top_frame = ttk.Frame(self.main_frame)
self.top_frame.pack(fill=tk.X, padx=5, pady=5)
# Frame para os botões de bits
self.bits_frame = ttk.Frame(self.top_frame)
self.bits_frame.pack(side=tk.LEFT, padx=(0, 10))
# Frame para seleção de pontos
self.points_frame = ttk.Frame(self.top_frame)
self.points_frame.pack(side=tk.LEFT)
# Variável para controlar o modo de bits
self.bits_mode = tk.StringVar(value="8b")
# Variável para controlar o número de pontos
self.points_var = tk.IntVar(value=512)
# Estilo para botões pequenos
style = ttk.Style()
style.configure("Small.TButton", padding=1)
# Botões para seleção de bits
self.btn_8b = ttk.Button(self.bits_frame, text="8b", width=3,
style="Small.TButton",
command=lambda: self.change_bits_mode("8b"))
self.btn_8b.pack(side=tk.LEFT, padx=2)
self.btn_16b = ttk.Button(self.bits_frame, text="16b", width=3,
style="Small.TButton",
command=lambda: self.change_bits_mode("16b"))
self.btn_16b.pack(side=tk.LEFT, padx=2)
self.btn_32b = ttk.Button(self.bits_frame, text="32b", width=3,
style="Small.TButton",
command=lambda: self.change_bits_mode("32b"))
self.btn_32b.pack(side=tk.LEFT, padx=2)
# Label e Spinbox para seleção de pontos
ttk.Label(self.points_frame, text="Points:").pack(side=tk.LEFT, padx=(0, 5))
self.points_spinbox = ttk.Spinbox(
self.points_frame,
from_=5,
to=1500,
textvariable=self.points_var,
width=5,
command=self.update_points_display
)
self.points_spinbox.pack(side=tk.LEFT, padx=(0, 5))
# Frame para canvas e scrollbar
self.graph_frame = ttk.Frame(self.main_frame)
self.graph_frame.pack(fill=tk.BOTH, expand=True)
# Criar label para hover
self.hover_label = tk.Label(self.window, text="", bg='lightyellow', relief=tk.SOLID, borderwidth=1)
# Canvas para o gráfico
self.canvas = tk.Canvas(self.graph_frame, bg='white')
self.canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
# Scrollbar horizontal
self.scrollbar = ttk.Scrollbar(self.graph_frame,
orient=tk.HORIZONTAL,
command=self.on_scroll)
self.scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
# Configurar canvas para usar scrollbar
self.canvas.configure(xscrollcommand=self.scrollbar.set)
# Configurar eventos de mouse
self.canvas.bind('<Motion>', self.on_hover)
self.canvas.bind('<Leave>', self.hide_hover_label)
# Configurações iniciais
self.max_display = 512 # Definir 512 pontos como padrão
self.current_start = 0
self.points_var.set(512) # Definir valor inicial do spinbox
# Processar dados iniciais
self.process_data()
# Plotar dados iniciais
self.plot_data()
# Destacar botão 8b inicialmente
self.update_bits_buttons()
# Vincular evento de redimensionamento
self.canvas.bind('<Configure>', self.on_resize)
# Configurar scrolling com o mouse
self.canvas.bind('<MouseWheel>', self.on_mousewheel)
logger.info(f"Binary Graph initialized with {len(binary_data)} data points")
except Exception as e:
logger.error(f"Error initializing Binary Graph: {e}", exc_info=True)
messagebox.showerror("Initialization Error", str(e))
if hasattr(self, 'window'):
self.window.destroy()
def on_scroll(self, *args):
"""Manipula a rolagem do scrollbar"""
try:
# Obter a posição atual do scrollbar
if len(args) > 1: # Scroll através da scrollbar
fraction = float(args[1])
total_points = len(self.processed_data)
# Calcular novo índice inicial baseado na fração da scrollbar
max_start = max(0, total_points - self.max_display)
self.current_start = int(fraction * max_start)
# Garantir que current_start não ultrapasse o limite
self.current_start = max(0, min(self.current_start, max_start))
# Replottar com a nova posição
self.plot_data()
logger.info(f"Scrolled to position {self.current_start}")
except Exception as e:
logger.error(f"Error in scroll handling: {e}", exc_info=True)
def update_scrollbar(self):
"""Atualiza a posição e o tamanho do scrollbar"""
try:
total_points = len(self.processed_data)
if total_points <= self.max_display:
# Se todos os pontos estão visíveis, desabilitar scrollbar
self.scrollbar.set(0, 1)
return
# Calcular a fração visível
visible_fraction = self.max_display / total_points
# Calcular a posição inicial como fração
max_start = total_points - self.max_display
start_fraction = self.current_start / max_start if max_start > 0 else 0
# Definir posição e tamanho do scrollbar
self.scrollbar.set(start_fraction, start_fraction + visible_fraction)
logger.info(f"Updated scrollbar: position {start_fraction:.2f}, size {visible_fraction:.2f}")
except Exception as e:
logger.error(f"Error updating scrollbar: {e}", exc_info=True)
def change_bits_mode(self, mode):
# Definir o modo de bits
self.bits_mode.set(mode)
# Atualizar estado dos botões
self.update_bits_buttons()
# Processar dados com o novo modo de bits
self.process_data()
# Resetar para 512 pontos ao mudar o modo de bits
self.points_var.set(512)
self.max_display = 512
self.current_start = 0
# Replottar dados
self.plot_data()
logger.info(f"Changed graph mode to {mode}")
def update_bits_buttons(self):
# Atualizar aparência dos botões
current_mode = self.bits_mode.get()
for btn, mode in [(self.btn_8b, "8b"),
(self.btn_16b, "16b"),
(self.btn_32b, "32b")]:
if mode == current_mode:
btn.state(['pressed'])
else:
btn.state(['!pressed'])
def process_data(self):
# Processar dados de acordo com o modo selecionado
mode = self.bits_mode.get()
raw_data = self.binary_data
if mode == "8b":
self.processed_data = raw_data
elif mode == "16b":
# Converter para 16 bits (combinar bytes adjacentes)
self.processed_data = []
for i in range(0, len(raw_data)-1, 2):
value = (raw_data[i+1] << 8) | raw_data[i]
self.processed_data.append(value)
elif mode == "32b":
# Converter para 32 bits (combinar 4 bytes adjacentes)
self.processed_data = []
for i in range(0, len(raw_data)-3, 4):
value = (raw_data[i+3] << 24) | (raw_data[i+2] << 16) | \
(raw_data[i+1] << 8) | raw_data[i]
self.processed_data.append(value)
def get_max_value_for_mode(self):
"""Retorna o valor máximo para o modo de bits atual"""
mode = self.bits_mode.get()
if mode == "8b":
return 255
elif mode == "16b":
return 65535
else: # 32b
return 4294967295
def format_hex(self, value):
"""Formata um número para hexadecimal sem o prefixo 0x"""
return format(value, 'X')
def plot_data(self):
"""Plota os dados no canvas"""
try:
# Limpar canvas
self.canvas.delete("all")
# Obter dimensões do canvas
plot_width = self.canvas.winfo_width() - 100 # Margem para eixos
plot_height = self.canvas.winfo_height() - 100 # Margem para eixos
margin_left = 50
margin_bottom = 50
if not self.processed_data or len(self.processed_data) == 0:
return
# Calcular índices de início e fim para exibição
end_idx = min(self.current_start + self.max_display, len(self.processed_data))
visible_data = self.processed_data[self.current_start:end_idx]
if not visible_data:
return
# Usar valor máximo fixo baseado no modo de bits
max_val = self.get_max_value_for_mode()
min_val = 0 # Sempre começar do zero
# Calcular escalas
x_scale = plot_width / (len(visible_data) - 1) if len(visible_data) > 1 else plot_width
y_scale = plot_height / max_val if max_val != 0 else 1
# Preparar pontos para plotagem
points = []
for i, value in enumerate(visible_data):
x = margin_left + (i * x_scale)
y = self.canvas.winfo_height() - margin_bottom - (value * y_scale)
points.extend([x, y])
# Plotar linha
if len(points) >= 4:
self.canvas.create_line(points, fill='blue', width=1)
# Desenhar eixo X
self.canvas.create_line(
margin_left, self.canvas.winfo_height() - margin_bottom,
margin_left + plot_width, self.canvas.winfo_height() - margin_bottom,
fill='black'
)
# Desenhar eixo Y
self.canvas.create_line(
margin_left, self.canvas.winfo_height() - margin_bottom,
margin_left, margin_bottom,
fill='black'
)
# Desenhar marcações no eixo X
num_x_ticks = 10
for i in range(num_x_ticks + 1):
x_pos = margin_left + (i * plot_width / num_x_ticks)
self.canvas.create_line(
x_pos, self.canvas.winfo_height() - margin_bottom,
x_pos, self.canvas.winfo_height() - margin_bottom + 5,
fill='black'
)
x_val = self.current_start + int((i / num_x_ticks) * (end_idx - self.current_start))
# Multiplicar por 2 e converter para hex apenas no modo 16 bits
if self.bits_mode.get() == "16b":
display_val = x_val * 2
hex_x_val = self.format_hex(display_val)
else:
hex_x_val = self.format_hex(x_val)
self.canvas.create_text(
x_pos, self.canvas.winfo_height() - margin_bottom + 20,
text=hex_x_val
)
# Desenhar marcações no eixo Y
num_y_ticks = 5
for i in range(num_y_ticks + 1):
y_val = int((i / num_y_ticks) * max_val)
y_pos = self.canvas.winfo_height() - margin_bottom - (y_val * y_scale)
self.canvas.create_line(
margin_left - 5, y_pos,
margin_left, y_pos,
fill='black'
)
# Manter valor do eixo Y em decimal
self.canvas.create_text(
margin_left - 25, y_pos,
text=str(y_val)
)
# Atualizar scrollbar
self.update_scrollbar()
# Atualizar título do gráfico
title = f"{self.bits_mode.get()}-bit Binary Data"
self.canvas.create_text(
self.canvas.winfo_width() // 2,
20,
text=title,
font=('Arial', 12, 'bold')
)
logger.info(f"Plotted {len(visible_data)} points from index {self.current_start} to {end_idx}")
except Exception as e:
logger.error(f"Error plotting data: {e}", exc_info=True)
messagebox.showerror("Plotting Error", str(e))
def on_resize(self, event):
# Replottar quando o canvas for redimensionado
self.plot_data()
def update_points_display(self):
"""Atualiza o número de pontos exibidos no gráfico"""
try:
# Obter o novo número de pontos do spinbox
new_points = self.points_var.get()
# Validar entrada
if new_points < 5 or new_points > 1500:
messagebox.showerror("Invalid Input", "Points must be between 5 and 1500")
# Restaurar o valor anterior
self.points_var.set(self.max_display)
return
# Limpar canvas antes de replottar
self.canvas.delete("all")
# Atualizar número máximo de pontos
self.max_display = new_points
# Garantir que o início atual não ultrapasse o total de pontos
total_points = len(self.processed_data)
self.current_start = min(self.current_start, max(0, total_points - new_points))
# Replottar dados
self.plot_data()
# Atualizar scrollbar
self.update_scrollbar()
logger.info(f"Updated graph display to {new_points} points")
except Exception as e:
logger.error(f"Error updating points display: {e}", exc_info=True)
messagebox.showerror("Update Error", str(e))
def on_mousewheel(self, event):
# Manipula a rolagem com o mouse
try:
# Obter a posição atual do scrollbar
total_points = len(self.processed_data)
# Calcular novo índice inicial baseado na fração da scrollbar
max_start = max(0, total_points - self.max_display)
if event.delta > 0:
self.current_start = max(0, self.current_start - 10)
else:
self.current_start = min(max_start, self.current_start + 10)
# Replottar com a nova posição
self.plot_data()
logger.info(f"Scrolled to position {self.current_start}")
except Exception as e:
logger.error(f"Error in mousewheel handling: {e}", exc_info=True)
def on_hover(self, event):
try:
# Parâmetros de plotagem
width = self.canvas.winfo_width()
height = self.canvas.winfo_height()
margin_left = 50
margin_bottom = 50
plot_width = width - 100
plot_height = height - 100
# Remover linhas de hover anteriores
self.canvas.delete('hover_line', 'hover_marker')
# Verificar se o mouse está dentro da área do gráfico
if (event.x < margin_left or event.x > width - 50 or
event.y < 50 or event.y > height - margin_bottom):
self.hide_hover_label(event)
return
# Dados processados
data = self.processed_data
end_idx = min(self.current_start + self.max_display, len(data))
visible_data = data[self.current_start:end_idx]
if not visible_data:
return
# Calcular índice X baseado na posição relativa do mouse
mouse_x_rel = event.x - margin_left
x_scale = plot_width / (len(visible_data) - 1) if len(visible_data) > 1 else plot_width
x_index = int(mouse_x_rel / x_scale)
if x_index < 0 or x_index >= len(visible_data):
return
# Calcular valor Y
y_value = visible_data[x_index]
x_pos = self.current_start + x_index
# Calcular coordenadas para linha vertical
line_x = margin_left + (x_index * x_scale)
# Desenhar linha vertical
self.canvas.create_line(
line_x, 50,
line_x, height - margin_bottom,
fill='red',
dash=(4, 4),
tags='hover_line'
)
# Formatar valores
hex_x = format(x_pos * 2, 'X') if self.bits_mode.get() == "16b" else format(x_pos, 'X')
hex_y = format(y_value, 'X')
# Atualizar label de hover
hover_text = (
f"X: 0x{hex_x}\n"
f"Y: {y_value} (0x{hex_y})"
)
self.hover_label.config(text=hover_text)
# Posicionar label próximo ao cursor
self.hover_label.place(
x=event.x_root - self.window.winfo_rootx() + 10,
y=event.y_root - self.window.winfo_rooty() + 10
)
self.hover_label.lift()
except Exception as e:
logger.error(f"Erro no hover: {e}")
def hide_hover_label(self, event):
# Esconder label quando mouse sai do gráfico
self.hover_label.place_forget()
# Remover linhas de hover
self.canvas.delete('hover_line', 'hover_marker')

285
flash_window.py Normal file
View File

@ -0,0 +1,285 @@
import tkinter as tk
from tkinter import filedialog, messagebox, ttk, scrolledtext
from view_parameters import ViewParametersDialog
import struct
import os
import logging
from logger import logger # Use the centralized logger
# Create a logger
# logger = logging.getLogger(__name__)
class FlashWindow:
def __init__(self, master, main_window):
try:
logger.info("Initializing FlashWindow")
# Validate input
if master is None:
raise ValueError("Master window cannot be None")
# Store references
self.parent = master
self.main_window = main_window # Referência à janela principal
# Create window
self.window = tk.Toplevel(master)
self.window.title("Flash File - EDC15")
self.window.geometry("800x600")
# Minimizar janela principal quando abrir Flash
self.main_window.iconify()
# Configurar para restaurar janela principal quando fechar
self.window.protocol("WM_DELETE_WINDOW", self.on_close)
# Add a frame to the new window
self.flash_frame = ttk.Frame(self.window)
self.flash_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
# Create buttons frame
self.buttons_frame = ttk.Frame(self.flash_frame)
self.buttons_frame.pack(fill=tk.X, pady=(0, 10))
# Add Open and Save buttons
ttk.Button(self.buttons_frame, text="Open File", command=self.open_file).pack(side=tk.LEFT, padx=5)
ttk.Button(self.buttons_frame, text="Save File", command=self.save_file).pack(side=tk.LEFT, padx=5)
ttk.Button(self.buttons_frame, text="Parâmetros", command=self.show_parameters).pack(side=tk.LEFT, padx=5)
# Botão para alternar entre Hexa e Decimal
self.btn_view_mode = ttk.Button(self.buttons_frame, text="Hexa", command=self.toggle_view_mode)
self.btn_view_mode.pack(side=tk.LEFT, padx=5)
# Botão 2D
self.btn_2d_view = ttk.Button(self.buttons_frame, text="2D", command=self.open_2d_graph)
self.btn_2d_view.pack(side=tk.LEFT, padx=5)
# File path label
self.file_path = tk.StringVar()
self.file_path_label = ttk.Label(self.flash_frame, textvariable=self.file_path, wraplength=780)
self.file_path_label.pack(fill=tk.X, pady=5)
# Create text area for binary content
self.content_frame = ttk.LabelFrame(self.flash_frame, text="Binary Content")
self.content_frame.pack(fill=tk.BOTH, expand=True, pady=5)
self.text_area = scrolledtext.ScrolledText(self.content_frame, wrap=tk.WORD, width=80, height=20)
self.text_area.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.text_area.config(font=('Courier', 10))
# Store binary data and view parameters
self.binary_data = None
self.view_params = {
'first_cell': '0000F0',
'offset': 0,
'columns': 16,
'hex_view': False,
'show_diff': True,
'value_type': '8bit',
'address_type': '8bit'
}
logger.info("FlashWindow initialized successfully")
except Exception as e:
logger.error(f"Error initializing FlashWindow: {e}", exc_info=True)
messagebox.showerror("Initialization Error", str(e))
if hasattr(self, 'window'):
self.window.destroy()
def format_value(self, data, start_idx, value_type, hex_view):
try:
# Primeiro, garantir que temos bytes suficientes
if value_type == '8bit' and len(data) < 1:
return "?" * (2 if hex_view else 3)
elif value_type == '16bit' and len(data) < 2:
return "?" * (4 if hex_view else 5)
elif (value_type == '32bit' or value_type == 'float') and len(data) < 4:
return "?" * (8 if hex_view else 10)
# Agora processar o valor
if value_type == '8bit':
value = data[0]
if hex_view:
return f"{value:02X}"
return f"{value:03d}"
elif value_type == '16bit':
value = struct.unpack('>H', data[:2])[0] # big-endian
if hex_view:
return f"{value:04X}"
return f"{value:05d}"
elif value_type == '32bit':
value = struct.unpack('>I', data[:4])[0] # big-endian
if hex_view:
return f"{value:08X}"
return f"{value:010d}"
elif value_type == 'float':
value = struct.unpack('>f', data[:4])[0] # big-endian
return f"{value:10.4f}"
except (IndexError, struct.error) as e:
return "?" * (2 if hex_view else 3)
def get_value_size(self, value_type):
if value_type == '8bit':
return 1
elif value_type == '16bit':
return 2
else: # 32bit ou float
return 4
def show_parameters(self):
dialog = ViewParametersDialog(self.window, self.view_params)
result = dialog.show()
if result:
self.view_params = result
self.display_binary_data()
def open_file(self):
file_path = filedialog.askopenfilename(
title="Select Binary File",
filetypes=[
("Binary files", "*.bin"),
("All files", "*.*")
]
)
if file_path:
self.file_path.set(f"Selected file: {file_path}")
try:
with open(file_path, 'rb') as f:
self.binary_data = f.read()
self.display_binary_data()
except Exception as e:
messagebox.showerror("Error", f"Error reading file: {str(e)}")
def display_binary_data(self):
if self.binary_data:
self.text_area.delete('1.0', tk.END)
# Get parameters
bytes_per_row = self.view_params['columns']
start_offset = self.view_params['offset']
hex_view = self.view_params['hex_view']
value_type = self.view_params['value_type']
# Update frame title
mode_text = "Hexadecimal" if hex_view else "Decimal"
value_text = f"{value_type} values"
if value_type == 'float':
value_text = "Floating point values"
self.content_frame.configure(text=f"Binary Content - {mode_text} ({value_text})")
# Calculate display range
display_data = self.binary_data[start_offset:]
# Get the size of each value based on type
value_size = self.get_value_size(value_type)
values_per_row = bytes_per_row // value_size
for i in range(0, len(display_data), bytes_per_row):
# Format offset (from the first_cell value)
base_offset = int(self.view_params['first_cell'], 16)
current_offset = base_offset + i
offset = f"{current_offset:08X}"
# Get row data
row_data = display_data[i:i + bytes_per_row]
# Format values
values = []
ascii_values = []
# Process each value in the row
for j in range(0, len(row_data), value_size):
value_data = row_data[j:j + value_size]
if len(value_data) == value_size:
# Format the value according to type
value_str = self.format_value(value_data, j, value_type, hex_view)
values.append(value_str)
# ASCII representation (only for 8-bit values)
if value_type == '8bit':
b = value_data[0]
ascii_values.append(chr(b) if 32 <= b <= 126 else '.')
else:
ascii_values.extend(['.' for _ in range(value_size)])
# Join values and ASCII
values_part = " ".join(values)
ascii_part = "".join(ascii_values)
# Add the line to display
line = f"{offset}: {values_part} | {ascii_part}\n"
self.text_area.insert(tk.END, line)
self.text_area.see('1.0') # Scroll to top
def save_file(self):
if not self.binary_data:
messagebox.showwarning("Warning", "No data to save. Please open a file first.")
return
file_path = filedialog.asksaveasfilename(
title="Save Binary File",
defaultextension=".bin",
filetypes=[
("Binary files", "*.bin"),
("All files", "*.*")
]
)
if file_path:
try:
with open(file_path, 'wb') as f:
f.write(self.binary_data)
self.file_path.set(f"File saved to: {file_path}")
except Exception as e:
messagebox.showerror("Error", f"Error saving file: {str(e)}")
def open_2d_graph(self):
try:
# Ensure binary data is available
if not hasattr(self, 'binary_data') or self.binary_data is None:
logger.warning("No binary data available to plot graph")
messagebox.showwarning("Graph Error", "No binary data available to plot.")
return
# Import BinaryGraphWindow dynamically to avoid circular imports
from binary_graph import BinaryGraphWindow
# Convert binary data to byte values if not already done
# Ensure we're passing a list of integers
byte_values = [int(byte) for byte in self.binary_data]
# Open binary graph window
graph_window = BinaryGraphWindow(self.window, byte_values)
logger.info("Binary graph window opened successfully")
except ImportError as e:
logger.error(f"Failed to import BinaryGraphWindow: {e}")
messagebox.showerror("Import Error", "Could not load graph module.")
except Exception as e:
logger.error(f"Error opening binary graph: {e}", exc_info=True)
messagebox.showerror("Graph Error", f"Could not open graph: {e}")
def toggle_view_mode(self):
# Alternar entre hexadecimal e decimal
current_mode = self.view_params.get('hex_view', False)
self.view_params['hex_view'] = not current_mode
# Atualizar texto do botão
self.btn_view_mode.config(text="Decimal" if current_mode else "Hexa")
# Redesenhar dados
self.display_binary_data()
def on_close(self):
# Restaurar janela principal
self.main_window.deiconify()
# Fechar janela Flash
self.window.destroy()

BIN
hexawork.log Normal file

Binary file not shown.

44
logger.py Normal file
View File

@ -0,0 +1,44 @@
import logging
import os
from datetime import datetime
def setup_logger(name='hexaw_logger', log_dir='logs'):
"""
Set up a logger with file and console output.
:param name: Name of the logger
:param log_dir: Directory to store log files
:return: Configured logger
"""
# Create logs directory if it doesn't exist
os.makedirs(log_dir, exist_ok=True)
# Generate a log filename with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_file = os.path.join(log_dir, f'{name}_{timestamp}.log')
# Create logger
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
# Create file handler
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
# Create console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# Create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# Add handlers to logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
# Create a global logger instance
logger = setup_logger()

1093
main.py Normal file

File diff suppressed because it is too large Load Diff