Skip to content
Draft
Show file tree
Hide file tree
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
25 changes: 25 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Build artifacts
*.egg-info/
dist/
build/
*.pyc
__pycache__/

# IDE
.vscode/
.idea/

# Virtual environments
venv/
env/
.env

# Test artifacts
.pytest_cache/
.coverage
htmlcov/
*.png

# OS files
.DS_Store
Thumbs.db
135 changes: 135 additions & 0 deletions LOGGING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Logging in SpyDE

## Overview

SpyDE uses Python's built-in `logging` module for all application output. All `print()` statements have been replaced with appropriate logging calls to provide better control and visibility into application behavior.

## Configuration

Logging is configured centrally in `spyde/logging_config.py` and initialized when the application starts in `main_window.py:main()`.

### Setting Log Level

There are two ways to set the logging level:

#### 1. Via the GUI (Recommended)

Use the menu: **View → Set Log Level** and select from:
- **DEBUG**: Show all diagnostic information
- **INFO**: Show general informational messages (default)
- **WARNING**: Show only warnings and errors
- **ERROR**: Show only errors
- **CRITICAL**: Show only critical errors

The log level can be changed at any time while the application is running, and changes take effect immediately.

#### 2. Via Environment Variable

- **LOG_LEVEL**: Set the logging level at startup (DEBUG, INFO, WARNING, ERROR, CRITICAL)
- Default: `INFO` in production
- For debugging, set `LOG_LEVEL=DEBUG`

Example:
```bash
export LOG_LEVEL=DEBUG
spyde
```

## Usage in Code

### Creating a Logger

Each module should create its own logger at the module level:

```python
import logging

logger = logging.getLogger(__name__)
```

### Logging Messages

Use appropriate logging levels:

```python
# Debug - Detailed diagnostic information
logger.debug("Processing data with shape: %s", data.shape)

# Info - General informational messages
logger.info("Signal loaded: %s", signal)

# Warning - Something unexpected but not critical
logger.warning("Could not find tool button for action %s", action_name)

# Error - An error occurred but the application can continue
logger.error("Plot update failed: %s", result)

# Critical - A serious error that may prevent the application from continuing
logger.critical("Unable to initialize Dask client")
```

### Exception Logging

For exceptions, use `logger.exception()` inside an except block:

```python
try:
process_data()
except Exception as e:
logger.exception("Failed to process data: %s", e)
raise
```

This automatically includes the traceback in the log output.

## Best Practices

1. **Use string formatting with %**: Instead of f-strings, use `%s` placeholders
```python
# Good
logger.info("Loading file: %s", filename)

# Avoid
logger.info(f"Loading file: {filename}")
```
This is more efficient because string formatting only happens if the log level is enabled.

2. **Choose appropriate log levels**:
- `DEBUG`: Detailed information for diagnosing problems
- `INFO`: Confirmation that things are working as expected
- `WARNING`: Something unexpected happened, but the software is still working
- `ERROR`: A serious problem occurred
- `CRITICAL`: The program may be unable to continue

3. **No print statements**: Use logging instead. Print statements should only be used in:
- Example scripts that demonstrate usage
- Test output that should always be visible

4. **Third-party library logging**: Overly verbose third-party loggers are suppressed in `logging_config.py` to reduce noise.

## Testing Logging

To verify logging is working in a module:

```python
import os
os.environ['LOG_LEVEL'] = 'DEBUG'

from spyde.logging_config import setup_logging
setup_logging()

# Your code with logging calls
```

## Output Format

The default log format is:
```
%(asctime)s - %(name)s - %(levelname)s - %(message)s
```

Example output:
```
2025-11-04 15:04:16 - spyde.main_window - INFO - Starting Dask LocalCluster with 4 workers
2025-11-04 15:04:16 - spyde.signal_tree - DEBUG - Created Signal Tree with root signal: <Signal>
```
5 changes: 4 additions & 1 deletion spyde/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from importlib.resources import files
import yaml
import logging

logger = logging.getLogger(__name__)

# Load the configuration .yaml files at package initialization

Expand All @@ -10,7 +13,7 @@
"r", encoding="utf-8"
) as f:
METADATA_WIDGET_CONFIG = yaml.safe_load(f)
print(METADATA_WIDGET_CONFIG)
logger.debug("Loaded metadata widget config: %s", METADATA_WIDGET_CONFIG)

__all__ = ["TOOLBAR_ACTIONS", "METADATA_WIDGET_CONFIG"]

Expand Down
7 changes: 5 additions & 2 deletions spyde/actions/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import TYPE_CHECKING
import logging

if TYPE_CHECKING:
from spyde.drawing.toolbars.rounded_toolbar import RoundedToolBar
Expand All @@ -7,6 +8,8 @@

from PySide6 import QtWidgets

logger = logging.getLogger(__name__)

ZOOM_STEP = 0.8


Expand Down Expand Up @@ -138,7 +141,7 @@ def toggle_navigation_plots(
# current_buttons[add_button_name] = add_button

if toolbar.action_widgets.get(action_name, None) is None:
print(f"Adding toolbar action widget: {action_name}")
logger.debug("Adding toolbar action widget: %s", action_name)
toolbar.add_action_widget(action_name, group, layout)

if toggle is not None:
Expand Down Expand Up @@ -233,7 +236,7 @@ def tree2buttons(tree):
layout = toolbar.action_widgets[action_name]["layout"]

if toolbar.action_widgets.get(action_name, None) is None:
print(f"Adding toolbar action widget: {action_name}")
logger.debug("Adding toolbar action widget: %s", action_name)
toolbar.add_action_widget(action_name, group, layout)

if toggle is not None:
Expand Down
Loading
Loading