Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Continuous log writing and toggle #38

Merged
merged 7 commits into from
Dec 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 86 additions & 27 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import subprocess
import os
import tkinter as tk
from tkinter import messagebox, ttk
from tkinter import messagebox, ttk, filedialog
import datetime
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
Expand All @@ -28,22 +28,37 @@ def __init__(self, text_widget, log_level=LogLevel.INFO, log_to_file=False):
self.setup_log_file()

def setup_log_file(self):
log_dir = os.path.join(os.path.dirname(sys.executable), 'logs')
os.makedirs(log_dir, exist_ok=True)
log_file_name = f"ebeam_log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
self.log_file = open(os.path.join(log_dir, log_file_name), 'w')

"""Setup a new log file in the 'EBEAM_dashboard/EBEAM-Dashboard-Logs/' directory."""
try:
# Use the EBEAM_dashboard directory
base_path = os.path.abspath(os.path.join(os.path.expanduser("~"), "EBEAM_dashboard"))
log_dir = os.path.join(base_path, "EBEAM-Dashboard-Logs")
os.makedirs(log_dir, exist_ok=True)

# Create the log file with the old naming pattern
log_file_name = f"log_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.txt"
self.log_file = open(os.path.join(log_dir, log_file_name), 'w')
print(f"Log file created at {os.path.join(log_dir, log_file_name)}")
except Exception as e:
print(f"Error creating log file: {str(e)}")

def log(self, msg, level=LogLevel.INFO):
""" Log a message to the text widget and optionally to local file """
if level >= self.log_level:
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
formatted_message = f"[{timestamp}] - {level.name}: {msg}\n"

# Write to text widget
self.text_widget.insert(tk.END, formatted_message)
self.text_widget.see(tk.END)

# write to log flie if enabled
if self.log_to_file and self.log_file:
self.log_file.write(formatted_message)
self.log_file.flush()
try:
self.log_file.write(formatted_message)
self.log_file.flush()
except Exception as e:
print(f"Error writing to log file: {str(e)}")

def debug(self, message):
self.log(message, LogLevel.DEBUG)
Expand All @@ -65,39 +80,56 @@ def set_log_level(self, level):

def close(self):
if self.log_file:
self.log_file.close()
try:
self.log_file.close()
self.log_file = None
except Exception as e:
print(f"Error closing log file {str(e)}")

class MessagesFrame:
MAX_LINES = 100 # Maximum number of lines to keep in the widget at a time

def __init__(self, parent):
self.frame = tk.Frame(parent, borderwidth=2, relief="solid")
self.frame.pack(fill=tk.BOTH, expand=True) # Make sure it expands and fills space
self.frame.pack(fill=tk.BOTH, expand=True)

# Add a title to the Messages & Errors frame
label = tk.Label(self.frame, text="Messages & Errors", font=("Helvetica", 10, "bold"))
label.grid(row=0, column=0, sticky="ew", padx=10, pady=10)
label.grid(row=0, column=0, columnspan=4, sticky="ew", padx=10, pady=10)

# Configure the grid layout to allow the text widget to expand
self.frame.columnconfigure(0, weight=1)
self.frame.columnconfigure(1, weight=1)
self.frame.columnconfigure(2, weight=1)
self.frame.columnconfigure(3, weight=0)
self.frame.rowconfigure(1, weight=1)

# Create a Text widget for logs
self.text_widget = tk.Text(self.frame, wrap=tk.WORD, font=("Helvetica", 8))
self.text_widget.grid(row=1, column=0, columnspan=2, sticky="nsew", padx=10, pady=0)
self.text_widget.grid(row=1, column=0, columnspan=4, sticky="nsew", padx=10, pady=0)

# Create a button to clear the text widget
self.clear_button = tk.Button(self.frame, text="Clear Messages", command=self.confirm_clear)
self.clear_button.grid(row=2, column=0, sticky="ew", padx=10, pady=10)
self.clear_button.grid(row=2, column=0, sticky="ew", padx=5, pady=10)

self.export_button = tk.Button(self.frame, text="Export", command=self.export_log)
self.export_button.grid(row=2, column=1, sticky="ew", padx=5, pady=10)

self.save_button = tk.Button(self.frame, text="Save Log", command=self.save_log)
self.save_button.grid(row=2, column=1, sticky="ew", padx=10, pady=10)
self.toggle_file_logging_button = tk.Button(self.frame, text="Record Log: ON", command=self.toggle_file_logging)
self.toggle_file_logging_button.grid(row=2, column=2, sticky="ew", padx=5, pady=10)

# circular indicator for log writing state
self.logging_indicator_canvas = tk.Canvas(self.frame, width=16, height=16, highlightthickness=0)
self.logging_indicator_canvas.grid(row=2, column=3, padx=(0, 10), pady=10)
self.logging_indicator_circle = self.logging_indicator_canvas.create_oval(
2, 2, 14, 14, fill="green", outline="black", width=2
)

self.file_logging_enabled = True
self.logger = Logger(self.text_widget, log_level=LogLevel.DEBUG, log_to_file=True)

# Redirect stdout to the text widget
sys.stdout = TextRedirector(self.text_widget, "stdout")

self.logger = Logger(self.text_widget, log_level=LogLevel.DEBUG, log_to_file=True)

# Ensure that the log directory exists
self.ensure_log_directory()
Expand All @@ -107,6 +139,27 @@ def write(self, msg):
self.text_widget.insert(tk.END, msg)
self.trim_text()

def toggle_file_logging(self):
# Toggle the file_logging_enabled state
if self.file_logging_enabled:
# Currently ON, turn it OFF
self.file_logging_enabled = False
self.logger.log_to_file = False
if self.logger.log_file:
self.logger.log_file.close()
self.toggle_file_logging_button.config(text="Record Log: OFF")
self.logging_indicator_canvas.itemconfig(self.logging_indicator_circle, fill="gray")
self.logger.info("Log recording has been turned OFF.")
else:
# Currently OFF, turn it ON
self.file_logging_enabled = True
self.logger.log_to_file = True
if not self.logger.log_file: # if no file is open, set up a new one
self.logger.setup_log_file()
self.toggle_file_logging_button.config(text="Record Log: ON")
self.logging_indicator_canvas.itemconfig(self.logging_indicator_circle, fill="green")
self.logger.info("Log recording has been turned ON.")

def set_log_level(self, level):
self.logger.set_log_level(level)

Expand Down Expand Up @@ -142,23 +195,29 @@ def ensure_log_directory(self):
except Exception as e:
print(f"Failed to create log directory: {str(e)}")

def save_log(self):
""" Save the current contents of the text widget to a timestamped log file in 'logs/' directory. """
def export_log(self):
""" Export the current log contents to a user-specified file. """
try:
filename = datetime.datetime.now().strftime("log_%Y-%m-%d_%H-%M-%S.txt")
full_path = os.path.join(self.log_dir, filename)
with open(full_path, 'w') as file:
file.write(self.text_widget.get("1.0", tk.END))
messagebox.showinfo("Save Successful", f"Log saved as {filename}")
# Open a file dialog to let the user choose save location
initial_name = f"log_export_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.txt"
file_path = filedialog.asksaveasfilename(
defaultextension=".txt",
initialfile=initial_name,
title="Export Log As...",
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
)
if file_path:
with open(file_path, 'a') as file:
file.write(self.text_widget.get("1.0", tk.END))
messagebox.showinfo("Export Successful", f"Log exported to {file_path}")
except Exception as e:
messagebox.showerror("Save Error", f"Failed to save log: {str(e)}")
messagebox.showerror("Export Error", f"Failed to export log: {str(e)}")

def confirm_clear(self):
''' Show a confirmation dialog before clearing the text widget '''
if messagebox.askokcancel("Clear Messages", "Do you really want to clear all messages?"):
self.text_widget.delete('1.0', tk.END)


class TextRedirector:
def __init__(self, widget, tag="stdout"):
self.widget = widget
Expand Down Expand Up @@ -258,7 +317,7 @@ def show_tip(self):
self.tip_window.bind("<Destroy>", lambda e, fig=fig: plt.close(fig))

# Add vertical and horizontal lines if values are provided
if self.voltage_var.get() and self.current_var.get():
if self.voltage_var and self.current_var:

try:
voltage = float(self.voltage_var.get().replace(' V', ''))
Expand Down