Skip to content

Commit c16c2b2

Browse files
Feature/v3/feature/386 use better default logging colors and dont log to file by default (#394)
* Fix `charge_state` Constraint in `Storage` leading to incorrect losses in discharge and therefore incorrect charge states and discharge values (#347) * Fix equation in Storage * Fix test for equation in Storage * Update CHANGELOG.md * Improve Changelog Message * Fix CHANGELOG.md * Simplify changes from next release * Update CHANGELOG.md * Fix CHANGELOG.md * chore(deps): update dependency mkdocs-material to v9.6.20 (#369) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Improve renovate.json to automerge ruff despite 0.x version * chore(deps): update dependency tsam to v2.3.9 (#379) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update dependency ruff to v0.13.2 (#378) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Feature/Improve Configuration options and handling (#385) * Refactor configuration management: remove dataclass-based schema and simplify CONFIG structure. * Refactor configuration loading: switch from `os` to `pathlib`, streamline YAML loading logic. * Refactor logging setup: split handler creation into dedicated functions, simplify configuration logic. * Improve logging configurability and safety - Add support for `RotatingFileHandler` to prevent large log files. - Introduce `console` flag for optional console logging. - Default to `NullHandler` when no handlers are configured for better library behavior. * Temp * Temp * Temp * Temp * Temp * Temp * Refactor configuration and logging: remove unused `merge_configs` function, streamline logging setup, and encapsulate `_setup_logging` as an internal function. * Remove unused `change_logging_level` import and export. * Add tests for config.py * Expand `config.py` test coverage: add tests for custom config loading, logging setup, dict roundtrip, and attribute modification. * Expand `test_config.py` coverage: add modeling config persistence test, refine logging reset, and improve partial config load assertions. * Expand `test_config.py` coverage: add teardown for state cleanup and reset modeling config in setup. * Add `CONFIG.reset()` method and expand test coverage to verify default restoration * Refactor `CONFIG` to centralize defaults in `_DEFAULTS` and ensure `reset()` aligns with them; add test to verify consistency. * Refactor `_DEFAULTS` to use `MappingProxyType` for immutability, restructure config hierarchy, and simplify `reset()` implementation for maintainability; update tests accordingly. * Mark `TestConfigModule` tests to run in a single worker with `@pytest.mark.xdist_group` to prevent global config interference. * Add default log file * Update CHANGELOG.md * Readd change_logging_level() for backwards compatability * Add more options to config.py * Add a docstring to config.y * Add a docstring to config.y * rename parameter message_format * Improve color config * Improve color config * Update CHANGELOG.md * Improve color handling * Improve color handling * Remove console Logging explicityl from examples * Make log to console the default * Make log to console the default * Add individual level parameters for console and file * Add extra Handler section * Use dedicated levels for both handlers * Switch back to not use Handlers * Revert "Switch back to not use Handlers" This reverts commit 05bbccb. * Revert "Use dedicated levels for both handlers" This reverts commit ed0542b. * Revert "Add extra Handler section" This reverts commit a133cc8. * Revert "Add individual level parameters for console and file" This reverts commit 19f81c9. * Fix CHANGELOG.md * Update CHANGELOG.md * Fix CHANGELOG.md * Allow blank issues * Change default logging behaviour to other colors and no file logging * Use white for INFO * Use terminal default for INFO * Explicitly use stdout for StreamHandler * Use terminal default for Logging color * Add option for loggger name * Update CHANGELOG.md * Ensure custom formats are being applied * Catch empty config files * Update test to match new defaults --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
1 parent 84d4bba commit c16c2b2

8 files changed

Lines changed: 100 additions & 35 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,14 @@ Until here -->
181181
- Added configurable log format settings: `CONFIG.Logging.date_format` and `CONFIG.Logging.format`
182182
- Added configurable console settings: `CONFIG.Logging.console_width` and `CONFIG.Logging.show_path`
183183
- Added `CONFIG.Logging.Colors` nested class for customizable log level colors using ANSI escape codes (works with both standard and Rich handlers)
184+
- All examples now enable console logging to demonstrate proper logging usage
185+
- Console logging now outputs to `sys.stdout` instead of `sys.stderr` for better compatibility with output redirection
184186

185187
### ♻️ Changed
186188
- Logging and Configuration management changed
189+
- **Breaking**: Console logging is now disabled by default (`CONFIG.Logging.console = False`). Enable it explicitly in your scripts with `CONFIG.Logging.console = True` and `CONFIG.apply()`
190+
- **Breaking**: File logging is now disabled by default (`CONFIG.Logging.file = None`). Set a file path to enable file logging
191+
- Improved default logging colors: DEBUG is now gray (`\033[90m`) for de-emphasized messages, INFO uses terminal default color (`\033[0m`) for clean output
187192

188193
### 🗑️ Deprecated
189194
- `change_logging_level()` function is now deprecated in favor of `CONFIG.Logging.level` and `CONFIG.apply()`. Will be removed in version 3.0.0.

examples/00_Minmal/minimal_example.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import flixopt as fx
1010

1111
if __name__ == '__main__':
12+
# Enable console logging
13+
fx.CONFIG.Logging.console = True
14+
fx.CONFIG.apply()
1215
# --- Define the Flow System, that will hold all elements, and the time steps you want to model ---
1316
timesteps = pd.date_range('2020-01-01', periods=3, freq='h')
1417
flow_system = fx.FlowSystem(timesteps)

examples/01_Simple/simple_example.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import flixopt as fx
99

1010
if __name__ == '__main__':
11+
# Enable console logging
12+
fx.CONFIG.Logging.console = True
13+
fx.CONFIG.apply()
1114
# --- Create Time Series Data ---
1215
# Heat demand profile (e.g., kW) over time and corresponding power prices
1316
heat_demand_per_h = np.array([30, 0, 90, 110, 110, 20, 20, 20, 20])

examples/02_Complex/complex_example.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import flixopt as fx
1010

1111
if __name__ == '__main__':
12+
# Enable console logging
13+
fx.CONFIG.Logging.console = True
14+
fx.CONFIG.apply()
1215
# --- Experiment Options ---
1316
# Configure options for testing various parameters and behaviors
1417
check_penalty = False

examples/02_Complex/complex_example_results.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import flixopt as fx
66

77
if __name__ == '__main__':
8+
# Enable console logging
9+
fx.CONFIG.Logging.console = True
10+
fx.CONFIG.apply()
811
# --- Load Results ---
912
try:
1013
results = fx.results.CalculationResults.from_file('results', 'complex example')

examples/03_Calculation_types/example_calculation_types.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import flixopt as fx
1212

1313
if __name__ == '__main__':
14+
# Enable console logging
15+
fx.CONFIG.Logging.console = True
16+
fx.CONFIG.apply()
1417
# Calculation Types
1518
full, segmented, aggregated = True, True, True
1619

flixopt/config.py

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import logging
4+
import sys
45
import warnings
56
from logging.handlers import RotatingFileHandler
67
from pathlib import Path
@@ -25,19 +26,20 @@
2526
'logging': MappingProxyType(
2627
{
2728
'level': 'INFO',
28-
'file': 'flixopt.log',
29+
'file': None,
2930
'rich': False,
30-
'console': True,
31+
'console': False,
3132
'max_file_size': 10_485_760, # 10MB
3233
'backup_count': 5,
3334
'date_format': '%Y-%m-%d %H:%M:%S',
3435
'format': '%(message)s',
3536
'console_width': 120,
3637
'show_path': False,
38+
'show_logger_name': False,
3739
'colors': MappingProxyType(
3840
{
39-
'DEBUG': '\033[32m', # Green
40-
'INFO': '\033[34m', # Blue
41+
'DEBUG': '\033[90m', # Bright Black/Gray
42+
'INFO': '\033[0m', # Default/White
4143
'WARNING': '\033[33m', # Yellow
4244
'ERROR': '\033[31m', # Red
4345
'CRITICAL': '\033[1m\033[31m', # Bold Red
@@ -62,7 +64,7 @@ class CONFIG:
6264
The CONFIG class provides centralized configuration for logging and modeling parameters.
6365
All changes require calling ``CONFIG.apply()`` to take effect.
6466
65-
By default, logging outputs to both console and file ('flixopt.log').
67+
By default, logging is disabled (no console or file output). Enable logging in your scripts as needed.
6668
6769
Attributes:
6870
Logging: Nested class containing all logging configuration options.
@@ -73,9 +75,9 @@ class CONFIG:
7375
Logging Attributes:
7476
level (str): Logging level: 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'.
7577
Default: 'INFO'
76-
file (str | None): Log file path. Default: 'flixopt.log'.
77-
Set to None to disable file logging.
78-
console (bool): Enable console (stdout) logging. Default: True
78+
file (str | None): Log file path. Default: None (file logging disabled).
79+
Set to a file path to enable file logging.
80+
console (bool): Enable console (stdout) logging. Default: False
7981
rich (bool): Use Rich library for enhanced console output. Default: False
8082
max_file_size (int): Maximum log file size in bytes before rotation.
8183
Default: 10485760 (10MB)
@@ -85,10 +87,11 @@ class CONFIG:
8587
format (str): Log message format string. Default: '%(message)s'
8688
console_width (int): Console width for Rich handler. Default: 120
8789
show_path (bool): Show file paths in log messages. Default: False
90+
show_logger_name (bool): Show logger name in log messages. Default: False
8891
8992
Colors Attributes:
90-
DEBUG (str): ANSI color code for DEBUG level. Default: '\\033[32m' (green)
91-
INFO (str): ANSI color code for INFO level. Default: '\\033[34m' (blue)
93+
DEBUG (str): ANSI color code for DEBUG level. Default: '\\033[90m' (bright black/gray)
94+
INFO (str): ANSI color code for INFO level. Default: '\\033[0m' (terminal default)
9295
WARNING (str): ANSI color code for WARNING level. Default: '\\033[33m' (yellow)
9396
ERROR (str): ANSI color code for ERROR level. Default: '\\033[31m' (red)
9497
CRITICAL (str): ANSI color code for CRITICAL level. Default: '\\033[1m\\033[31m' (bold red)
@@ -106,6 +109,8 @@ class CONFIG:
106109
- '\\033[35m' - Magenta
107110
- '\\033[36m' - Cyan
108111
- '\\033[37m' - White
112+
- '\\033[90m' - Bright Black/Gray
113+
- '\\033[0m' - Reset to default
109114
- '\\033[1m\\033[3Xm' - Bold color (replace X with color code 0-7)
110115
- '\\033[2m\\033[3Xm' - Dim color (replace X with color code 0-7)
111116
@@ -130,6 +135,12 @@ class CONFIG:
130135
CONFIG.Logging.level = 'DEBUG'
131136
CONFIG.apply()
132137
138+
Show logger names to see which module is logging::
139+
140+
CONFIG.Logging.console = True
141+
CONFIG.Logging.show_logger_name = True
142+
CONFIG.apply()
143+
133144
Configure log file rotation::
134145
135146
CONFIG.Logging.file = 'myapp.log'
@@ -139,7 +150,7 @@ class CONFIG:
139150
140151
Customize log colors::
141152
142-
CONFIG.Logging.Colors.INFO = '\\033[35m' # Magenta
153+
CONFIG.Logging.Colors.INFO = '\\033[32m' # Green
143154
CONFIG.Logging.Colors.DEBUG = '\\033[36m' # Cyan
144155
CONFIG.Logging.Colors.ERROR = '\\033[1m\\033[31m' # Bold red
145156
CONFIG.apply()
@@ -150,7 +161,7 @@ class CONFIG:
150161
CONFIG.Logging.rich = True
151162
CONFIG.Logging.console_width = 100
152163
CONFIG.Logging.show_path = True
153-
CONFIG.Logging.Colors.INFO = '\\033[36m' # Cyan
164+
CONFIG.Logging.Colors.INFO = '\\033[32m' # Green
154165
CONFIG.apply()
155166
156167
Load from YAML file::
@@ -165,15 +176,16 @@ class CONFIG:
165176
level: DEBUG
166177
console: true
167178
file: app.log
168-
rich: true
179+
rich: false
180+
show_logger_name: true
169181
max_file_size: 5242880 # 5MB
170182
backup_count: 3
171183
date_format: '%H:%M:%S'
172184
console_width: 100
173-
show_path: true
185+
show_path: false
174186
colors:
175-
DEBUG: "\\033[36m" # Cyan
176-
INFO: "\\033[32m" # Green
187+
DEBUG: "\\033[90m" # Bright Black/Gray
188+
INFO: "\\033[0m" # Default
177189
WARNING: "\\033[33m" # Yellow
178190
ERROR: "\\033[31m" # Red
179191
CRITICAL: "\\033[1m\\033[31m" # Bold red
@@ -207,6 +219,7 @@ class Logging:
207219
format: str = _DEFAULTS['logging']['format']
208220
console_width: int = _DEFAULTS['logging']['console_width']
209221
show_path: bool = _DEFAULTS['logging']['show_path']
222+
show_logger_name: bool = _DEFAULTS['logging']['show_logger_name']
210223

211224
class Colors:
212225
DEBUG: str = _DEFAULTS['logging']['colors']['DEBUG']
@@ -262,6 +275,7 @@ def apply(cls):
262275
format=cls.Logging.format,
263276
console_width=cls.Logging.console_width,
264277
show_path=cls.Logging.show_path,
278+
show_logger_name=cls.Logging.show_logger_name,
265279
colors=colors_dict,
266280
)
267281

@@ -273,7 +287,7 @@ def load_from_file(cls, config_file: str | Path):
273287
raise FileNotFoundError(f'Config file not found: {config_file}')
274288

275289
with config_path.open() as file:
276-
config_dict = yaml.safe_load(file)
290+
config_dict = yaml.safe_load(file) or {}
277291
cls._apply_config_dict(config_dict)
278292

279293
cls.apply()
@@ -312,6 +326,7 @@ def to_dict(cls):
312326
'format': cls.Logging.format,
313327
'console_width': cls.Logging.console_width,
314328
'show_path': cls.Logging.show_path,
329+
'show_logger_name': cls.Logging.show_logger_name,
315330
'colors': {
316331
'DEBUG': cls.Logging.Colors.DEBUG,
317332
'INFO': cls.Logging.Colors.INFO,
@@ -331,14 +346,22 @@ def to_dict(cls):
331346
class MultilineFormater(logging.Formatter):
332347
"""Formatter that handles multi-line messages with consistent prefixes."""
333348

334-
def __init__(self, fmt=None, datefmt=None):
349+
def __init__(self, fmt: str = '%(message)s', datefmt=None, show_logger_name=False):
335350
super().__init__(fmt=fmt, datefmt=datefmt)
351+
self.show_logger_name = show_logger_name
336352

337353
def format(self, record):
338-
message_lines = record.getMessage().split('\n')
354+
record.message = record.getMessage()
355+
message_lines = self._style.format(record).split('\n')
339356
timestamp = self.formatTime(record, self.datefmt)
340357
log_level = record.levelname.ljust(8)
341-
log_prefix = f'{timestamp} | {log_level} |'
358+
359+
if self.show_logger_name:
360+
# Truncate long logger names for readability
361+
logger_name = record.name if len(record.name) <= 20 else f'...{record.name[-17:]}'
362+
log_prefix = f'{timestamp} | {log_level} | {logger_name.ljust(20)} |'
363+
else:
364+
log_prefix = f'{timestamp} | {log_level} |'
342365

343366
first_line = [f'{log_prefix} {message_lines[0]}']
344367
if len(message_lines) > 1:
@@ -354,14 +377,14 @@ class ColoredMultilineFormater(MultilineFormater):
354377

355378
RESET = '\033[0m'
356379

357-
def __init__(self, fmt=None, datefmt=None, colors=None):
358-
super().__init__(fmt=fmt, datefmt=datefmt)
380+
def __init__(self, fmt=None, datefmt=None, colors=None, show_logger_name=False):
381+
super().__init__(fmt=fmt, datefmt=datefmt, show_logger_name=show_logger_name)
359382
self.COLORS = (
360383
colors
361384
if colors is not None
362385
else {
363-
'DEBUG': '\033[32m',
364-
'INFO': '\033[34m',
386+
'DEBUG': '\033[90m',
387+
'INFO': '\033[0m',
365388
'WARNING': '\033[33m',
366389
'ERROR': '\033[31m',
367390
'CRITICAL': '\033[1m\033[31m',
@@ -379,6 +402,7 @@ def _create_console_handler(
379402
use_rich: bool = False,
380403
console_width: int = 120,
381404
show_path: bool = False,
405+
show_logger_name: bool = False,
382406
date_format: str = '%Y-%m-%d %H:%M:%S',
383407
format: str = '%(message)s',
384408
colors: dict[str, str] | None = None,
@@ -389,6 +413,7 @@ def _create_console_handler(
389413
use_rich: If True, use RichHandler with color support.
390414
console_width: Width of the console for Rich handler.
391415
show_path: Show file paths in log messages (Rich only).
416+
show_logger_name: Show logger name in log messages.
392417
date_format: Date/time format string.
393418
format: Log message format string.
394419
colors: Dictionary of ANSI color codes for each log level.
@@ -423,8 +448,16 @@ def _create_console_handler(
423448
)
424449
handler.setFormatter(logging.Formatter(format))
425450
else:
426-
handler = logging.StreamHandler()
427-
handler.setFormatter(ColoredMultilineFormater(fmt=format, datefmt=date_format, colors=colors))
451+
# Explicitly use sys.stdout instead of default sys.stderr
452+
handler = logging.StreamHandler(stream=sys.stdout)
453+
handler.setFormatter(
454+
ColoredMultilineFormater(
455+
fmt=format,
456+
datefmt=date_format,
457+
colors=colors,
458+
show_logger_name=show_logger_name,
459+
)
460+
)
428461

429462
return handler
430463

@@ -433,6 +466,7 @@ def _create_file_handler(
433466
log_file: str,
434467
max_file_size: int = 10_485_760,
435468
backup_count: int = 5,
469+
show_logger_name: bool = False,
436470
date_format: str = '%Y-%m-%d %H:%M:%S',
437471
format: str = '%(message)s',
438472
) -> RotatingFileHandler:
@@ -442,6 +476,7 @@ def _create_file_handler(
442476
log_file: Path to the log file.
443477
max_file_size: Maximum size in bytes before rotation.
444478
backup_count: Number of backup files to keep.
479+
show_logger_name: Show logger name in log messages.
445480
date_format: Date/time format string.
446481
format: Log message format string.
447482
@@ -454,7 +489,13 @@ def _create_file_handler(
454489
backupCount=backup_count,
455490
encoding='utf-8',
456491
)
457-
handler.setFormatter(MultilineFormater(fmt=format, datefmt=date_format))
492+
handler.setFormatter(
493+
MultilineFormater(
494+
fmt=format,
495+
datefmt=date_format,
496+
show_logger_name=show_logger_name,
497+
)
498+
)
458499
return handler
459500

460501

@@ -469,6 +510,7 @@ def _setup_logging(
469510
format: str = '%(message)s',
470511
console_width: int = 120,
471512
show_path: bool = False,
513+
show_logger_name: bool = False,
472514
colors: dict[str, str] | None = None,
473515
):
474516
"""Internal function to setup logging - use CONFIG.apply() instead.
@@ -487,6 +529,7 @@ def _setup_logging(
487529
format: Log message format string.
488530
console_width: Console width for Rich handler.
489531
show_path: Show file paths in log messages (Rich only).
532+
show_logger_name: Show logger name in log messages.
490533
colors: ANSI color codes for each log level.
491534
"""
492535
logger = logging.getLogger('flixopt')
@@ -500,6 +543,7 @@ def _setup_logging(
500543
use_rich=use_rich_handler,
501544
console_width=console_width,
502545
show_path=show_path,
546+
show_logger_name=show_logger_name,
503547
date_format=date_format,
504548
format=format,
505549
colors=colors,
@@ -512,6 +556,7 @@ def _setup_logging(
512556
log_file=log_file,
513557
max_file_size=max_file_size,
514558
backup_count=backup_count,
559+
show_logger_name=show_logger_name,
515560
date_format=date_format,
516561
format=format,
517562
)

0 commit comments

Comments
 (0)