Logging System#

PlestyLib uses Python built-in logging throughout device, traffic, service, and utility layers.

Central setup helper: setup_logging in plestylib.utils.logger.

Purpose#

The logging system is used to:

  1. Track device lifecycle events (connect, disconnect, query, write).

  2. Record communication events in traffic managers.

  3. Capture warnings and errors from validation and exception handling.

  4. Provide file-based logs for troubleshooting and reproducibility.

Setup API#

from plestylib.utils.logger import setup_logging

setup_logging(app_name="powermeter", level="INFO", console_print=True)

Function signature:

setup_logging(app_name, level: str = "INFO", console_print: bool = True)

Parameters:

  1. app_name: Prefix used in log filename.

  2. level: Log level string (DEBUG, INFO, WARNING, ERROR, CRITICAL).

  3. console_print: If true, logs are sent to console in addition to file.

Output Behavior#

When setup_logging runs:

  1. Creates logs/ in current working directory if missing.

  2. Creates a timestamped log file: logs/<app_name>_YYYY-MM-DD_HH-MM-SS.log

  3. Configures logging format: %(asctime)s | %(levelname)s | %(name)s | %(message)s

Minimal Example#

from plestylib.utils.logger import setup_logging

setup_logging(app_name="pm100d", level="DEBUG", console_print=True)

# Then run your normal device logic.

Typical Integration Pattern#

Call setup once at process startup, before creating device instances:

import logging
from plestylib.utils.logger import setup_logging
from powermeter_device import PowermeterDevice

setup_logging(app_name="powermeter_service", level="INFO", console_print=True)

with PowermeterDevice("USB0::0x1313::0x8078::P0000001::INSTR", sensor_type="S155C") as dev:
	print(dev.identity())
	logging.info("Connected to PM100D")
	logging.debug("Debug trace for identity query complete")

Developer Example#

Use this pattern when building or debugging a new device API implementation.

import logging
from plestylib.device.base_tcp_scpi_device import BaseTCPScpiDevice


class DemoScpiDevice(BaseTCPScpiDevice):
	def __init__(self, host: str, port: int):
		super().__init__(host=host, port=port)
		self.register_config("POWER", dtype=float, read_only=True, command="MEAS:POW")
        logging.info("Initialization finished.")
    
    def some_utility_function():
        try:
            # do something
            logging.debug("xxx done")
        except Exception as e:
            logging.error(f"Error: {e}")

Why this helps developers:

  1. DEBUG level captures fine-grained query/write traces.

  2. File logs in logs/ preserve run history for troubleshooting.

  3. Console output helps while iterating quickly.

End User Example#

Use this pattern in production-like scripts where operators mainly need status and errors.

import logging

from plestylib.utils.logger import setup_logging
from powermeter_device import PowermeterDevice


setup_logging(app_name="powermeter_run", level="INFO", console_print=True)

address = "USB0::0x1313::0x8078::P0000001::INSTR"

try:
	with PowermeterDevice(address, sensor_type="S155C") as dev:
		value = dev.query("POWER")
		print(f"Power: {value}")
		logging.info(f"Measured power: {value}")
except Exception as e:
	logging.error(f"Measurement failed: {e}")

Why this fits end users:

  1. INFO level keeps output readable.

  2. Important status/error events are visible without debug noise.

  3. File logs can be attached when reporting issues.

Where Logs Come From#

You will see logs from multiple layers, for example:

  1. Base device classes: query and write operations.

  2. Traffic managers: open/close/send/receive events.

  3. Error handling utility: warnings and exceptions.

  4. TCP server/client service components.

Notes and Caveats#

  1. logging.basicConfig is process-global and typically only applies once.

  2. If logging was configured earlier (by your app or tests), calling setup_logging later may not replace existing handlers.

  3. Log file path is relative to the process current working directory.