Skip to content

Commit 1dbef67

Browse files
committed
feat: refactor logging system with Loguru integration and centralized configuration
1 parent 6877a55 commit 1dbef67

File tree

10 files changed

+276
-48
lines changed

10 files changed

+276
-48
lines changed

docs/examples/cuboids_demagnetization.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import magpylib as magpy
3636
import numpy as np
3737
import pandas as pd
3838
import plotly.express as px
39-
from loguru import logger
4039
from magpylib_material_response import get_dataset
4140
from magpylib_material_response.demag import apply_demag
4241
from magpylib_material_response.meshing import mesh_all

docs/examples/soft_magnets.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import magpylib as magpy
3535
import numpy as np
3636
import pandas as pd
3737
import plotly.express as px
38-
from loguru import logger
3938
from magpylib_material_response.demag import apply_demag
4039
from magpylib_material_response.meshing import mesh_all
4140

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
:glob: true
1010
:maxdepth: 2
1111
12+
logging
1213
examples/index
1314
```
1415

docs/logging.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Logging Configuration
2+
3+
The magpylib-material-response package uses structured logging with [Loguru](https://loguru.readthedocs.io/) to provide informative messages about computation progress and debugging information.
4+
5+
## Default Behavior
6+
7+
By default, the library **does not output any log messages**. This follows best practices for Python libraries to avoid cluttering user output unless explicitly requested.
8+
9+
## Enabling Logging
10+
11+
To see log messages from the library, you need to configure logging:
12+
13+
```python
14+
from magpylib_material_response import configure_logging
15+
from magpylib_material_response.demag import apply_demag
16+
17+
# Enable logging with default settings (INFO level, colored output to stderr)
18+
configure_logging()
19+
20+
# Now use the library - you'll see progress messages
21+
# ... your code here
22+
```
23+
24+
## Configuration Options
25+
26+
### Log Level
27+
```python
28+
from magpylib_material_response import configure_logging
29+
30+
# Set to DEBUG for detailed internal operations
31+
configure_logging(level="DEBUG")
32+
33+
# Set to WARNING to only see important warnings and errors
34+
configure_logging(level="WARNING")
35+
36+
# Available levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
37+
```
38+
39+
### Output Destination
40+
```python
41+
import sys
42+
from magpylib_material_response import configure_logging
43+
44+
# Output to stdout instead of stderr
45+
configure_logging(sink=sys.stdout)
46+
47+
# Output to a file
48+
configure_logging(sink="/path/to/logfile.log")
49+
```
50+
51+
### Disable Colors and Time
52+
```python
53+
from magpylib_material_response import configure_logging
54+
55+
# Disable colored output (useful for log files)
56+
configure_logging(enable_colors=False)
57+
58+
# Disable timestamps
59+
configure_logging(show_time=False)
60+
```
61+
62+
## Environment Variables
63+
64+
You can also configure logging using environment variables:
65+
66+
```bash
67+
# Set log level
68+
export MAGPYLIB_LOG_LEVEL=DEBUG
69+
70+
# Disable colors
71+
export MAGPYLIB_LOG_COLORS=false
72+
73+
# Disable timestamps
74+
export MAGPYLIB_LOG_TIME=false
75+
```
76+
77+
## Disabling Logging
78+
79+
To completely disable logging output:
80+
81+
```python
82+
from magpylib_material_response import disable_logging
83+
84+
disable_logging()
85+
```
86+
87+
## Example Usage
88+
89+
```python
90+
import magpylib as magpy
91+
from magpylib_material_response import configure_logging
92+
from magpylib_material_response.demag import apply_demag
93+
from magpylib_material_response.meshing import mesh_Cuboid
94+
95+
# Enable logging to see progress
96+
configure_logging(level="INFO")
97+
98+
# Create a magnet
99+
magnet = magpy.magnet.Cuboid(
100+
dimension=(0.01, 0.01, 0.02),
101+
polarization=(0, 0, 1)
102+
)
103+
magnet.susceptibility = 0.1
104+
105+
# Mesh the magnet - you'll see meshing progress
106+
meshed = mesh_Cuboid(magnet, target_elems=1000, verbose=True)
107+
108+
# Apply demagnetization - you'll see computation progress
109+
apply_demag(meshed, inplace=True)
110+
```
111+
112+
This will output structured log messages showing the progress of operations, timing information, and any warnings or errors.
113+
114+
## See Also
115+
116+
- {doc}`examples/index` - Working examples that demonstrate logging output
117+
- [Loguru Documentation](https://loguru.readthedocs.io/) - Complete reference for the underlying logging library

src/magpylib_material_response/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from __future__ import annotations
88

99
from magpylib_material_response._data import get_dataset
10+
from magpylib_material_response.logging_config import configure_logging, disable_logging
1011

1112
from ._version import version as __version__
1213

13-
__all__ = ["__version__", "get_dataset"]
14+
__all__ = ["__version__", "configure_logging", "disable_logging", "get_dataset"]

src/magpylib_material_response/demag.py

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,18 @@
22

33
from __future__ import annotations
44

5-
import sys
65
from collections import Counter
76

87
import magpylib as magpy
98
import numpy as np
10-
from loguru import logger
119
from magpylib._src.obj_classes.class_BaseExcitations import BaseCurrent, BaseMagnet
1210
from magpylib.magnet import Cuboid
1311
from scipy.spatial.transform import Rotation as R
1412

13+
from magpylib_material_response.logging_config import get_logger
1514
from magpylib_material_response.utils import timelog
1615

17-
config = {
18-
"handlers": [
19-
{
20-
"sink": sys.stdout,
21-
"colorize": True,
22-
"format": (
23-
"<magenta>{time:YYYY-MM-DD at HH:mm:ss}</magenta>"
24-
" | <level>{level:^8}</level>"
25-
" | <cyan>{function}</cyan>"
26-
" | <yellow>{extra}</yellow> {level.icon:<2} {message}"
27-
),
28-
},
29-
],
30-
}
31-
logger.configure(**config)
16+
logger = get_logger("magpylib_material_response.demag")
3217

3318

3419
def get_susceptibilities(sources, susceptibility=None):
@@ -272,14 +257,16 @@ def filter_distance(
272257
"dimension": np.repeat(dim0, len(src_list), axis=0)[mask],
273258
}
274259
dsf = sum(mask) / len(mask) * 100
275-
log_msg = (
276-
"Interaction pairs left after distance factor filtering: "
277-
f"<blue>{dsf:.2f}%</blue>"
278-
)
279260
if dsf == 0:
280-
logger.opt(colors=True).warning(log_msg)
261+
logger.warning(
262+
"No interaction pairs left after distance factor filtering",
263+
percentage=f"{dsf:.2f}%"
264+
)
281265
else:
282-
logger.opt(colors=True).success(log_msg)
266+
logger.info(
267+
"Interaction pairs left after distance factor filtering",
268+
percentage=f"{dsf:.2f}%"
269+
)
283270
out = [mask]
284271
if return_params:
285272
out.append(params)
@@ -304,25 +291,25 @@ def match_pairs(src_list, min_log_time=1):
304291
len_src = len(src_list)
305292
num_of_pairs = len_src**2
306293
with logger.contextualize(task="Match interactions pairs"):
307-
logger.info("position")
294+
logger.debug("Computing position differences")
308295
pos2 = np.tile(pos0, (len_src, 1)) - np.repeat(pos0, len_src, axis=0)
309-
logger.info("orientation")
296+
logger.debug("Computing orientation differences")
310297
rotQ2a = np.tile(rotQ0, (len_src, 1)).reshape((num_of_pairs, -1))
311298
rotQ2b = np.repeat(rotQ0, len_src, axis=0).reshape((num_of_pairs, -1))
312-
logger.info("dimension")
299+
logger.debug("Computing dimension differences")
313300
dim2 = np.tile(dim0, (len_src, 1)) - np.repeat(dim0, len_src, axis=0)
314-
logger.info("concatenate properties")
301+
logger.debug("Concatenating properties for comparison")
315302
prop = (np.concatenate([pos2, rotQ2a, rotQ2b, dim2], axis=1) + 1e-9).round(
316303
8
317304
)
318-
logger.info("find unique indices")
305+
logger.debug("Finding unique interaction pairs")
319306
_, unique_inds, unique_inv_inds = np.unique(
320307
prop, return_index=True, return_inverse=True, axis=0
321308
)
322309
perc = len(unique_inds) / len(unique_inv_inds) * 100
323-
logger.opt(colors=True).info(
324-
"Interaction pairs left after pair matching filtering: "
325-
f"<blue>{perc:.2f}%</blue>"
310+
logger.info(
311+
"Interaction pairs left after pair matching filtering",
312+
percentage=f"{perc:.2f}%"
326313
)
327314

328315
params = {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""
2+
Centralized logging configuration for magpylib-material-response.
3+
4+
This module provides a proper logging setup that:
5+
- Uses named loggers with package hierarchy
6+
- Allows user configuration through environment variables
7+
- Provides sensible defaults for library usage
8+
- Avoids forcing output to stdout unless explicitly requested
9+
"""
10+
11+
from __future__ import annotations
12+
13+
import os
14+
import sys
15+
16+
from loguru import logger
17+
18+
19+
def get_logger(name: str | None = None):
20+
"""
21+
Get a named logger for the package.
22+
23+
Parameters
24+
----------
25+
name : str, optional
26+
Logger name. If None, uses the package root logger.
27+
28+
Returns
29+
-------
30+
loguru.Logger
31+
Configured logger instance
32+
"""
33+
if name is None:
34+
name = "magpylib_material_response"
35+
return logger.bind(module=name)
36+
37+
38+
def configure_logging(
39+
level: str | None = None,
40+
enable_colors: bool | None = None,
41+
show_time: bool | None = None,
42+
sink=None,
43+
) -> None:
44+
"""
45+
Configure logging for the package.
46+
47+
This function should be called by users who want to see logging output
48+
from the library. By default, the library doesn't output logs unless
49+
explicitly configured.
50+
51+
Parameters
52+
----------
53+
level : str, optional
54+
Log level. Defaults to INFO. Can be overridden with MAGPYLIB_LOG_LEVEL env var.
55+
enable_colors : bool, optional
56+
Enable colored output. Defaults to True for interactive environments.
57+
Can be overridden with MAGPYLIB_LOG_COLORS env var.
58+
show_time : bool, optional
59+
Show timestamps in log messages. Defaults to True.
60+
Can be overridden with MAGPYLIB_LOG_TIME env var.
61+
sink : optional
62+
Log sink. Defaults to sys.stderr. Use sys.stdout for stdout output.
63+
"""
64+
# Remove existing handlers to avoid duplicates
65+
logger.remove()
66+
67+
# Get configuration from environment or use defaults
68+
if level is None:
69+
level = os.getenv("MAGPYLIB_LOG_LEVEL", "INFO")
70+
71+
if enable_colors is None:
72+
enable_colors = os.getenv("MAGPYLIB_LOG_COLORS", "true").lower() in ("true", "1", "yes")
73+
74+
if show_time is None:
75+
show_time = os.getenv("MAGPYLIB_LOG_TIME", "true").lower() in ("true", "1", "yes")
76+
77+
if sink is None:
78+
sink = sys.stderr
79+
80+
# Build format string
81+
time_part = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | " if show_time else ""
82+
format_str = (
83+
f"{time_part}"
84+
"<level>{level:^8}</level> | "
85+
"<cyan>{extra[module]}</cyan> | "
86+
"{level.icon:<2} {message}"
87+
)
88+
89+
# Configure the logger
90+
logger.add(
91+
sink,
92+
level=level,
93+
format=format_str,
94+
colorize=enable_colors,
95+
filter=lambda record: (
96+
record["extra"].get("module", "").startswith("magpylib_material_response") or
97+
record.get("name", "").startswith("magpylib_material_response")
98+
)
99+
)
100+
101+
102+
def disable_logging() -> None:
103+
"""Disable all logging output from the package."""
104+
logger.remove()
105+
logger.add(sink=lambda _: None, level="CRITICAL") # Sink that does nothing
106+
107+
108+
# Set up a default minimal configuration that doesn't output anything
109+
# Users need to call configure_logging() to see logs
110+
disable_logging()

0 commit comments

Comments
 (0)