Skip to content

Commit 465349b

Browse files
authored
refactor: logging configuration to require Settings instance (#1031)
1 parent 7663e2c commit 465349b

File tree

6 files changed

+87
-59
lines changed

6 files changed

+87
-59
lines changed

app/infrastructure/logging/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ def process_item(item_id: str):
5151
# Core logging setup
5252
from infrastructure.logging.setup import (
5353
configure_logging,
54-
logger, # Module-level logger for backward compat
5554
# Deprecated - kept for backward compatibility
5655
get_module_logger,
5756
)
@@ -76,7 +75,6 @@ def process_item(item_id: str):
7675
__all__ = [
7776
# Setup
7877
"configure_logging",
79-
"logger",
8078
# Deprecated - backward compatibility only
8179
"get_module_logger",
8280
# Context

app/infrastructure/logging/setup.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
import sys
2424
import structlog
2525
from structlog.stdlib import BoundLogger
26-
from typing import Optional
27-
from infrastructure.services.providers import get_settings
26+
from typing import TYPE_CHECKING
2827

29-
settings = get_settings()
28+
if TYPE_CHECKING:
29+
from infrastructure.configuration import Settings
3030

3131

3232
def _is_test_environment() -> bool:
@@ -39,8 +39,9 @@ def _is_test_environment() -> bool:
3939

4040

4141
def configure_logging(
42-
log_level: Optional[str] = None,
43-
is_production: Optional[bool] = None,
42+
settings: "Settings",
43+
log_level: str | None = None,
44+
is_production: bool | None = None,
4445
) -> BoundLogger:
4546
"""Configure structured logging with enhanced processors.
4647
@@ -51,6 +52,7 @@ def configure_logging(
5152
- Test environment detection for log suppression
5253
5354
Args:
55+
settings: Settings instance (REQUIRED). Must be passed explicitly.
5456
log_level: Optional override for log level (DEBUG, INFO, WARNING, etc).
5557
Defaults to settings.LOG_LEVEL if not provided.
5658
is_production: Optional override for production mode. Defaults to
@@ -61,11 +63,14 @@ def configure_logging(
6163
6264
Example:
6365
# At application startup
64-
logger = configure_logging()
66+
from infrastructure.services import get_settings
67+
settings = get_settings()
68+
logger = configure_logging(settings=settings)
6569
6670
# With overrides for testing
67-
logger = configure_logging(log_level="DEBUG", is_production=False)
71+
logger = configure_logging(settings=settings, log_level="DEBUG", is_production=False)
6872
"""
73+
6974
# Suppress all logging during tests
7075
if _is_test_environment():
7176
# Set root logger to suppress all output during tests
@@ -139,11 +144,6 @@ def configure_logging(
139144
return structlog.stdlib.get_logger()
140145

141146

142-
# Module-level logger - used for backward compatibility with legacy code
143-
# New code should use: structlog.get_logger()
144-
logger: BoundLogger = configure_logging()
145-
146-
147147
# =============================================================================
148148
# DEPRECATED FUNCTIONS - For backward compatibility only
149149
# =============================================================================
@@ -182,11 +182,11 @@ def get_module_logger() -> BoundLogger:
182182
# Auto-detect calling module for backward compatibility
183183
current_frame = inspect.currentframe()
184184
if current_frame is None:
185-
return logger
185+
return structlog.get_logger()
186186

187187
frame = current_frame.f_back
188188
if frame is None:
189-
return logger
189+
return structlog.get_logger()
190190

191191
module = inspect.getmodule(frame)
192192
if module:
@@ -196,6 +196,6 @@ def get_module_logger() -> BoundLogger:
196196
"component": parts[-1],
197197
"module_path": module_name,
198198
}
199-
return logger.bind(**context)
199+
return structlog.get_logger().bind(**context)
200200

201-
return logger.bind(component="unknown")
201+
return structlog.get_logger().bind(component="unknown")
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
"""Module-level fixtures for infrastructure tests (Level 2)."""
22

3+
import pytest
4+
from unittest.mock import Mock
5+
6+
from infrastructure.configuration import Settings
7+
8+
9+
@pytest.fixture
10+
def mock_settings():
11+
"""Mock Settings instance for testing."""
12+
settings = Mock(spec=Settings)
13+
settings.LOG_LEVEL = "INFO"
14+
settings.is_production = False
15+
return settings
16+
17+
318
# Note: Command-framework-specific fixtures are in Level 3 conftest
419
# (tests/unit/infrastructure/commands/conftest.py)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Fixtures for infrastructure.logging tests."""
2+
3+
import pytest
4+
from unittest.mock import Mock
5+
6+
from infrastructure.configuration import Settings
7+
8+
9+
@pytest.fixture
10+
def mock_settings():
11+
"""Mock Settings instance for testing."""
12+
settings = Mock(spec=Settings)
13+
settings.LOG_LEVEL = "INFO"
14+
settings.is_production = False
15+
return settings

app/tests/unit/infrastructure/logging/test_setup.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,54 +38,54 @@ def test_returns_true_during_test_run(self):
3838
class TestConfigureLogging:
3939
"""Test suite for configure_logging function."""
4040

41-
def test_configure_logging_returns_bound_logger(self):
41+
def test_configure_logging_returns_bound_logger(self, mock_settings):
4242
"""configure_logging returns a BoundLogger instance."""
43-
result = configure_logging()
43+
result = configure_logging(settings=mock_settings)
4444

4545
assert result is not None
4646
assert hasattr(result, "info")
4747
assert hasattr(result, "debug")
4848
assert hasattr(result, "warning")
4949
assert hasattr(result, "error")
5050

51-
def test_configure_logging_default_parameters(self):
51+
def test_configure_logging_default_parameters(self, mock_settings):
5252
"""configure_logging works with default parameters."""
53-
logger = configure_logging()
53+
logger = configure_logging(settings=mock_settings)
5454

5555
assert logger is not None
5656

57-
def test_configure_logging_with_log_level(self):
57+
def test_configure_logging_with_log_level(self, mock_settings):
5858
"""configure_logging accepts log_level parameter."""
5959
# In test environment, logging is suppressed, but function should still work
60-
logger = configure_logging(log_level="DEBUG")
60+
logger = configure_logging(settings=mock_settings, log_level="DEBUG")
6161
assert logger is not None
6262

63-
logger = configure_logging(log_level="INFO")
63+
logger = configure_logging(settings=mock_settings, log_level="INFO")
6464
assert logger is not None
6565

66-
logger = configure_logging(log_level="WARNING")
66+
logger = configure_logging(settings=mock_settings, log_level="WARNING")
6767
assert logger is not None
6868

69-
def test_configure_logging_with_is_production(self):
69+
def test_configure_logging_with_is_production(self, mock_settings):
7070
"""configure_logging accepts is_production parameter."""
7171
# In test environment, logging is suppressed, but parameters should be accepted
72-
logger = configure_logging(is_production=True)
72+
logger = configure_logging(settings=mock_settings, is_production=True)
7373
assert logger is not None
7474

75-
logger = configure_logging(is_production=False)
75+
logger = configure_logging(settings=mock_settings, is_production=False)
7676
assert logger is not None
7777

78-
def test_configure_logging_idempotent(self):
78+
def test_configure_logging_idempotent(self, mock_settings):
7979
"""Multiple configure_logging calls are safe."""
80-
logger1 = configure_logging()
81-
logger2 = configure_logging()
80+
logger1 = configure_logging(settings=mock_settings)
81+
logger2 = configure_logging(settings=mock_settings)
8282

8383
assert logger1 is not None
8484
assert logger2 is not None
8585

86-
def test_configure_logging_suppresses_in_test_env(self):
86+
def test_configure_logging_suppresses_in_test_env(self, mock_settings):
8787
"""In test environment, root logger level is set high to suppress output."""
88-
configure_logging()
88+
configure_logging(settings=mock_settings)
8989

9090
# In test environment, root logger should be set to suppress output
9191
root_logger = logging.getLogger()
@@ -97,9 +97,9 @@ def test_configure_logging_suppresses_in_test_env(self):
9797
class TestGetModuleLogger:
9898
"""Test suite for get_module_logger function (deprecated)."""
9999

100-
def test_get_module_logger_returns_bound_logger(self):
100+
def test_get_module_logger_returns_bound_logger(self, mock_settings):
101101
"""get_module_logger returns a BoundLogger with expected methods."""
102-
configure_logging()
102+
configure_logging(settings=mock_settings)
103103

104104
with warnings.catch_warnings(record=True) as w:
105105
warnings.simplefilter("always")
@@ -114,16 +114,16 @@ def test_get_module_logger_returns_bound_logger(self):
114114
assert hasattr(logger, "info")
115115
assert hasattr(logger, "bind")
116116

117-
def test_get_module_logger_emits_deprecation_warning(self):
117+
def test_get_module_logger_emits_deprecation_warning(self, mock_settings):
118118
"""get_module_logger emits DeprecationWarning."""
119-
configure_logging()
119+
configure_logging(settings=mock_settings)
120120

121121
with pytest.warns(DeprecationWarning, match="deprecated"):
122122
get_module_logger()
123123

124-
def test_get_module_logger_still_works(self):
124+
def test_get_module_logger_still_works(self, mock_settings):
125125
"""get_module_logger still returns functional logger for backward compat."""
126-
configure_logging()
126+
configure_logging(settings=mock_settings)
127127

128128
with warnings.catch_warnings():
129129
warnings.simplefilter("ignore", DeprecationWarning)
@@ -140,9 +140,9 @@ def test_get_module_logger_still_works(self):
140140
class TestLoggingBestPractices:
141141
"""Tests demonstrating structlog best practices."""
142142

143-
def test_standard_structlog_pattern(self):
143+
def test_standard_structlog_pattern(self, mock_settings):
144144
"""Standard structlog.get_logger() pattern works."""
145-
configure_logging()
145+
configure_logging(settings=mock_settings)
146146

147147
# This is the recommended pattern
148148
logger = structlog.get_logger()
@@ -151,29 +151,29 @@ def test_standard_structlog_pattern(self):
151151
assert hasattr(logger, "info")
152152
assert hasattr(logger, "bind")
153153

154-
def test_bind_for_context(self):
154+
def test_bind_for_context(self, mock_settings):
155155
"""Use .bind() for adding context."""
156-
configure_logging()
156+
configure_logging(settings=mock_settings)
157157

158158
logger = structlog.get_logger()
159159
log = logger.bind(user_id="123", operation="test")
160160

161161
assert log is not None
162162
assert hasattr(log, "info")
163163

164-
def test_chained_binds(self):
164+
def test_chained_binds(self, mock_settings):
165165
"""Multiple .bind() calls can be chained."""
166-
configure_logging()
166+
configure_logging(settings=mock_settings)
167167

168168
logger = structlog.get_logger()
169169
log = logger.bind(request_id="req-123")
170170
log = log.bind(user_id="user-456")
171171

172172
assert log is not None
173173

174-
def test_logging_methods_dont_raise(self):
174+
def test_logging_methods_dont_raise(self, mock_settings):
175175
"""Logging methods execute without raising exceptions."""
176-
configure_logging()
176+
configure_logging(settings=mock_settings)
177177

178178
logger = structlog.get_logger()
179179
log = logger.bind(component="test")
@@ -184,9 +184,9 @@ def test_logging_methods_dont_raise(self):
184184
log.warning("warning message")
185185
log.error("error message", error_code="E001")
186186

187-
def test_exception_logging(self):
187+
def test_exception_logging(self, mock_settings):
188188
"""Exception logging works correctly."""
189-
configure_logging()
189+
configure_logging(settings=mock_settings)
190190

191191
logger = structlog.get_logger()
192192

app/tests/unit/infrastructure/test_logging.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,33 +48,33 @@ def test_is_test_environment_without_pytest(self):
4848
# Re-import pytest since we removed it
4949
import pytest as _ # noqa: F401
5050

51-
def test_configure_logging_returns_bound_logger(self):
51+
def test_configure_logging_returns_bound_logger(self, mock_settings):
5252
"""configure_logging returns a logger instance."""
53-
logger = configure_logging()
53+
logger = configure_logging(settings=mock_settings)
5454
# Should return a structlog logger (may be BoundLoggerLazyProxy)
5555
assert logger is not None
5656
assert hasattr(logger, "bind")
5757

58-
def test_configure_logging_in_test_environment(self):
58+
def test_configure_logging_in_test_environment(self, mock_settings):
5959
"""configure_logging suppresses logs in test environment."""
6060
# We're in a test environment, so logging should be at CRITICAL+1
61-
configure_logging()
61+
configure_logging(settings=mock_settings)
6262
root_level = logging.root.level
6363
assert root_level == logging.CRITICAL + 1
6464

65-
def test_configure_logging_with_custom_log_level(self):
65+
def test_configure_logging_with_custom_log_level(self, mock_settings):
6666
"""configure_logging accepts custom log level."""
67-
logger = configure_logging(log_level="DEBUG")
67+
logger = configure_logging(settings=mock_settings, log_level="DEBUG")
6868
# Should return a logger instance
6969
assert logger is not None
7070
assert hasattr(logger, "bind")
7171

72-
def test_configure_logging_with_custom_production_flag(self):
72+
def test_configure_logging_with_custom_production_flag(self, mock_settings):
7373
"""configure_logging accepts custom production flag."""
7474
# Note: We can't easily verify the processor setup, but we can verify
7575
# it doesn't raise an error with different production flags
76-
logger1 = configure_logging(is_production=True)
77-
logger2 = configure_logging(is_production=False)
76+
logger1 = configure_logging(settings=mock_settings, is_production=True)
77+
logger2 = configure_logging(settings=mock_settings, is_production=False)
7878

7979
assert logger1 is not None
8080
assert logger2 is not None

0 commit comments

Comments
 (0)