Commit c7661f2a authored by vertighel's avatar vertighel
Browse files

loguru again

parent 781420f4
Loading
Loading
Loading
Loading
Loading
+12 −25
Original line number Diff line number Diff line
@@ -113,13 +113,10 @@ image_number = {v: k for k, v in image_state.items()}
# Directories
############

DATA_FOLDER_NAME = "data"
LOG_DIRECTORY_NAME = "log"
fits_folder = "fits"
focus_folder = "focus"

# data_folder = "data"
# log_folder = "log"
DATA_FOLDER = "data"
LOG_FOLDER = "log"
FITS_FOLDER = "fits"
FOCUS_FOLDER = "focus"

dir_type = {
    0: "dark",
@@ -131,21 +128,6 @@ dir_type = {
# Reversing dir_type
dir_number = {v: k for k, v in dir_type.items()}

############
# Logging
############

# Log file naming
LOG_FILE_EXTENSION = ".log"
LATEST_LOG_FILENAME = "latest"

# Rotation settings (used by logger.py)
LOG_ROTATION_TIME_H = 12 # Midday (hour in UTC)
LOG_ROTATION_TIME_M = 0  # Midday (minute in UTC)
LOG_ROTATION_TIME_S = 0  # Midday (second in UTC)
LOG_ROTATION_INTERVAL_DAYS = 1 # Rotate daily
LOG_BACKUP_COUNT = 0 # 0 means keep all backup files (infinite)

############
# FITS
############
@@ -154,6 +136,11 @@ LOG_BACKUP_COUNT = 0 # 0 means keep all backup files (infinite)
imagetyp = "IMAGETYP"  # 'Light'
dateobs = "DATE-OBS"  # '2021-12-18T05:09:56.163'

FILE_PREFIX = "OARPAF."
ext = ".fits" # File extension
focus_ext = ".foc"
############
# EXTENSIONS
############

FILE_PREFIX = "OARPAF"
FITS_EXT = "fits" # File extension
FOCUS_EXT = "foc"
LOG_EXT = "log"
+2 −2
Original line number Diff line number Diff line
@@ -143,9 +143,9 @@ class Sequencer():
                full_module_name = f"noctua.templates.{template_name}"
                try:
                    tplmodule = importlib.import_module(full_module_name)
                except ModuleNotFoundError:
                except ModuleNotFoundError as e:
                    log.error(
                        f"SEQUENCER: Template module '{full_module_name}' not found.")
                        f"SEQUENCER: Template module '{full_module_name}' not found: {e}")
                    self.error.append(
                        f"Template module '{full_module_name}' not found.")
                    continue
+1 −1
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@
from time import sleep

# Third-party modules
from templates.basetemplate import BaseTemplate
from .basetemplate import BaseTemplate

# Other templates
from ..config.constants import on_off
+1 −1
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ from astropy.time import Time

# Other templates
from ..config.constants import on_off
from ..directory.structure import foc_path
from ..utils.structure import foc_path
# from devices import lamp, light
from ..utils.logger import log
from .basetemplate import BaseTemplate
+43 −155
Original line number Diff line number Diff line
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#!/usr/bin/env python

"""Custom format log"""

# System modules
import logging
import logging.handlers
import sys
from pathlib import Path
from datetime import datetime, time
import os # Will be used if log_path only returns a directory

# Third-party modules

# Custom modules
from ..config.constants import ( LOG_FILE_EXTENSION, LATEST_LOG_FILENAME, LOG_ROTATION_TIME_H,
                                 LOG_ROTATION_TIME_M, LOG_ROTATION_TIME_S,
                                 LOG_ROTATION_INTERVAL_DAYS, LOG_BACKUP_COUNT )
from .structure import get_log_dir_path, get_dated_log_filepath, get_latest_log_filepath

LOG_DIR = get_log_dir_path() # Creates directory if it doesn't exist
LATEST_LOG_FILEPATH = get_latest_log_filepath()

log = None # Global logger instance

class ColorizingFormatter(logging.Formatter):
    """
    A custom logging formatter that adds ANSI color codes to log level names
    for console output.
    """

    # Color codes
    BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)

    # Log level to color mapping Using bold for emphasis
    LEVEL_COLORS = {
        logging.DEBUG: f"\x1b[3{MAGENTA};1m",  # Bold Magenta
        logging.INFO: f"\x1b[3{GREEN};1m",     # Bold Green
        logging.WARNING: f"\x1b[3{YELLOW};1m", # Bold Yellow
        logging.ERROR: f"\x1b[3{RED};1m",      # Bold Red
        logging.CRITICAL: f"\x1b[4{RED};1m",   # Bold Red with Red Background (or just very bold red)
    }
    RESET_SEQ = "\x1b[0m" # Reset all attributes

    def __init__(self, fmt=None, datefmt=None, style='%', validate=True, *, defaults=None):
        super().__init__(fmt, datefmt, style, validate, defaults=defaults)
        # For UTC timestamps if needed by the base Formatter
        self.converter = lambda *args: datetime.utcnow().timetuple()


    def format(self, record):
        s = super().format(record)

        levelname_str = record.levelname
        color = self.LEVEL_COLORS.get(record.levelno)

        if color:
            padded_levelname = f"{levelname_str:<8}" 

            if padded_levelname in s:
                colored_levelname = f"{color}{padded_levelname}{self.RESET_SEQ}"
                s = s.replace(padded_levelname, colored_levelname, 1) # Replace only the first occurrence
            else: # Fallback if padded version not found, try raw (less likely to match in typical formats)
                if levelname_str in s:
                    colored_levelname = f"{color}{levelname_str}{self.RESET_SEQ}"
                    s = s.replace(levelname_str, colored_levelname, 1)
        return s


def setup_logger(name="noctua_logger", log_level=logging.DEBUG):
    global log
    if log:
        return log

    logger_instance = logging.getLogger(name)
    logger_instance.setLevel(log_level)

    if logger_instance.hasHandlers():
        logger_instance.handlers.clear()

    log_format_str = "%(asctime)s.%(msecs)03d | %(levelname)-8s | %(message)s (%(module)s.%(funcName)s:%(lineno)d)"
    formatter = logging.Formatter(log_format_str, datefmt="%Y-%m-%d %H:%M:%S")
    # Ensure timestamps are in UTC
    formatter.converter = lambda *args: datetime.utcnow().timetuple()

    console_handler = logging.StreamHandler(sys.stderr)
    formatter = ColorizingFormatter(log_format_str, datefmt="%Y-%m-%d %H:%M:%S")
    console_handler.setFormatter(formatter)
    logger_instance.addHandler(console_handler)

    # --- TimedRotatingFileHandler for LATEST log ---
    # Rotate at midday UTC
    rotation_time_utc = time(
        hour=LOG_ROTATION_TIME_H,
        minute=LOG_ROTATION_TIME_M,
        second=LOG_ROTATION_TIME_S,
        tzinfo=None 
from loguru import logger
import datetime
from .structure import log_path
    
def mylog():
    "logger function"

    logger.remove() # Remove default handler to prevent duplicate console logs

    time_fmt = "{time:YYYY-MM-DD HH:mm:ss.SSSSSS!UTC} "
    level_fmt = "<level>{level: <8}</level> "
    message_fmt = "| {message} "
    stack_fmt = "<bold>({module}.{function}:{line})</bold>"
    fmt = time_fmt + level_fmt + message_fmt + stack_fmt

    # On standard output
    logger.add(
        sys.stderr,
        format=fmt,
        level="DEBUG"
    )

    rotating_file_handler = logging.handlers.TimedRotatingFileHandler(
        filename=LATEST_LOG_FILEPATH,
        when="midnight", # Base rotation type (daily)
        atTime=rotation_time_utc, # Specific time for rotation
        interval=LOG_ROTATION_INTERVAL_DAYS, # Interval (1 for daily)
        backupCount=LOG_BACKUP_COUNT, # 0 means keep all backups
        encoding='utf-8',
        utc=True
    full_log_file_path = log_path()
    
    logger.add(
        full_log_file_path,
        format=fmt,
        colorize=True,  # For file logs
        rotation="16:19", # Local time
        # retention="7 days", # Old logs
        level="DEBUG" 
    )

    def custom_namer_astronomical(default_name):
        try:
            # Example: '/.../OARPAF.latest.log.2023-04-16_12-00-00' (if rotated with atTime)
            base_filepath = Path(default_name)
            suffix = base_filepath.suffix # .log or or .2023-04-16_12-00-00
            # Remove base filename and first dot
            date_str_part = base_filepath.name.replace(LATEST_LOG_FILEPATH.name + '.', '') 
            date_str_part = date_str_part.split('_')[0] # Takes YYYY-MM-DD part if HH-MM-SS is appended            
            rotation_calendar_date = datetime.strptime(date_str_part, "%Y-%m-%d").date()
            astronomical_date_of_log = rotation_calendar_date - timedelta(days=1)
            astro_date_str = astronomical_date_of_log.strftime("%Y-%m-%d")
            return str(get_dated_log_filepath(astro_date_str))
            
        except ValueError:
            # Fallback if date parsing fails
            log.warning(f"Could not parse date from default rotated log name: {default_name}. Using default.")
            return default_name

    def simpler_custom_namer(default_name):
        
        base_filepath = Path(default_name)
        
        # Extract the date part from the suffix
        # Suffix is ".YYYY-MM-DD_HH-MM-SS"
        name_parts = base_filepath.name.split(LATEST_LOG_FILEPATH.name + '.')
        if len(name_parts) > 1:
            timestamp_suffix = name_parts[1]
            date_str = timestamp_suffix.split('_')[0] # Get the YYYY-MM-DD part
            
            try:
                rotation_event_date = datetime.strptime(date_str, "%Y-%m-%d").date()
                # The log contains data for the astronomical night *before* this rotation event date.
                log_file_astro_date = rotation_event_date - timedelta(days=1)
                final_date_str = log_file_astro_date.strftime("%Y-%m-%d")
                
                # Construct the desired filename using structure.py for consistency
                return str(get_dated_log_filepath(final_date_str))
            except ValueError:
                pass # Fall through to default if parsing fails

        # Fallback to a cleaned-up version of the default name if parsing is tricky
        dir_name = base_filepath.parent
        original_plus_date = base_filepath.name.replace(LOG_FILE_EXTENSION, '') # Remove .log
        # original_plus_date is like OARPAF.latest.YYYY-MM-DD_HH-MM-SS
        cleaned_name = original_plus_date.replace(LATEST_LOG_FILENAME + '.', '') # Becomes OARPAF.YYYY-MM-DD...
        return str(dir_name / (cleaned_name + LOG_FILE_EXTENSION))

    rotating_file_handler.namer = simpler_custom_namer # Use the simpler namer
    rotating_file_handler.setFormatter(formatter)
    logger_instance.addHandler(rotating_file_handler)
    
    log = logger_instance
    log.info(f"Logger '{name}' configured. Current logs to: {LATEST_LOG_FILEPATH}")
    log.info(f"Rotated daily at {rotation_time_utc} UTC. Backup count: {'Infinite' if LOG_BACKUP_COUNT == 0 else LOG_BACKUP_COUNT}.")
    return log

# Initialize logger on module import
from datetime import timedelta # Ensure timedelta is imported for namer
log = setup_logger()
    # Custom levels
    logger.level("DEBUG", color="<magenta><bold>")
    logger.level("INFO", color="<green><bold>")

if __name__ == "__main__":
    return logger

# This ensures mylog() is called only once.
log = mylog()

def main():
    """Main function"""

    # log is already initialized globally
    log.debug("This is a debug message.")
    log.info("This is an info message.")
    log.warning("This is a warning message.")
    log.error("This is an error message.")
    log.critical("This is a critical message.")


if __name__ == "__main__":
    main()
Loading