@@ -33,7 +33,7 @@ async def test_file_handler_creation_with_rotation():
33
33
with tempfile .TemporaryDirectory () as tmpdir :
34
34
log_file = "test.log"
35
35
log_folder = tmpdir
36
-
36
+
37
37
with patch ("mcpgateway.services.logging_service.settings" ) as mock_settings :
38
38
mock_settings .log_to_file = True
39
39
mock_settings .log_file = log_file
@@ -42,7 +42,7 @@ async def test_file_handler_creation_with_rotation():
42
42
mock_settings .log_max_size_mb = 1
43
43
mock_settings .log_backup_count = 3
44
44
mock_settings .log_filemode = "a"
45
-
45
+
46
46
handler = _get_file_handler ()
47
47
assert handler is not None
48
48
assert handler .maxBytes == 1 * 1024 * 1024 # 1MB
@@ -55,18 +55,18 @@ async def test_file_handler_creation_without_rotation():
55
55
with tempfile .TemporaryDirectory () as tmpdir :
56
56
log_file = "test.log"
57
57
log_folder = tmpdir
58
-
58
+
59
59
with patch ("mcpgateway.services.logging_service.settings" ) as mock_settings :
60
60
mock_settings .log_to_file = True
61
61
mock_settings .log_file = log_file
62
62
mock_settings .log_folder = log_folder
63
63
mock_settings .log_rotation_enabled = False
64
64
mock_settings .log_filemode = "a"
65
-
65
+
66
66
# Reset global handler
67
67
import mcpgateway .services .logging_service as ls
68
68
ls ._file_handler = None
69
-
69
+
70
70
handler = _get_file_handler ()
71
71
assert handler is not None
72
72
assert not hasattr (handler , 'maxBytes' ) # Regular FileHandler doesn't have this
@@ -77,11 +77,11 @@ async def test_file_handler_raises_when_disabled():
77
77
"""Test that file handler raises ValueError when file logging is disabled."""
78
78
with patch ("mcpgateway.services.logging_service.settings" ) as mock_settings :
79
79
mock_settings .log_to_file = False
80
-
80
+
81
81
# Reset global handler
82
82
import mcpgateway .services .logging_service as ls
83
83
ls ._file_handler = None
84
-
84
+
85
85
with pytest .raises (ValueError , match = "File logging is disabled" ):
86
86
_get_file_handler ()
87
87
@@ -112,16 +112,16 @@ async def test_initialize_with_file_logging_enabled():
112
112
mock_settings .log_max_size_mb = 2
113
113
mock_settings .log_backup_count = 3
114
114
mock_settings .log_filemode = "a"
115
-
115
+
116
116
service = LoggingService ()
117
117
await service .initialize ()
118
-
118
+
119
119
root_logger = logging .getLogger ()
120
120
# Should have both text and file handlers
121
121
handler_types = [type (h ).__name__ for h in root_logger .handlers ]
122
122
assert 'StreamHandler' in handler_types
123
123
assert 'RotatingFileHandler' in handler_types
124
-
124
+
125
125
await service .shutdown ()
126
126
127
127
@@ -131,15 +131,15 @@ async def test_initialize_with_file_logging_disabled():
131
131
with patch ("mcpgateway.services.logging_service.settings" ) as mock_settings :
132
132
mock_settings .log_to_file = False
133
133
mock_settings .log_file = None
134
-
134
+
135
135
service = LoggingService ()
136
136
await service .initialize ()
137
-
137
+
138
138
root_logger = logging .getLogger ()
139
139
# Should only have text handler
140
140
handler_types = [type (h ).__name__ for h in root_logger .handlers ]
141
141
assert 'StreamHandler' in handler_types
142
-
142
+
143
143
await service .shutdown ()
144
144
145
145
@@ -152,12 +152,12 @@ async def test_initialize_with_file_logging_error():
152
152
mock_settings .log_folder = "/invalid/path"
153
153
mock_settings .log_rotation_enabled = False
154
154
mock_settings .log_filemode = "a"
155
-
155
+
156
156
# Mock the file handler to raise an exception
157
157
with patch ("mcpgateway.services.logging_service._get_file_handler" , side_effect = Exception ("Cannot create file" )):
158
158
service = LoggingService ()
159
159
await service .initialize () # Should not raise, just log warning
160
-
160
+
161
161
await service .shutdown ()
162
162
163
163
@@ -171,7 +171,7 @@ async def test_configure_uvicorn_loggers():
171
171
"""Test that uvicorn loggers are configured properly."""
172
172
service = LoggingService ()
173
173
service ._configure_uvicorn_loggers ()
174
-
174
+
175
175
uvicorn_loggers = ['uvicorn' , 'uvicorn.access' , 'uvicorn.error' , 'uvicorn.asgi' ]
176
176
for logger_name in uvicorn_loggers :
177
177
logger = logging .getLogger (logger_name )
@@ -184,7 +184,7 @@ async def test_configure_uvicorn_loggers():
184
184
async def test_configure_uvicorn_after_startup ():
185
185
"""Test public method to reconfigure uvicorn loggers after startup."""
186
186
service = LoggingService ()
187
-
187
+
188
188
with patch .object (service , '_configure_uvicorn_loggers' ) as mock_config :
189
189
service .configure_uvicorn_after_startup ()
190
190
mock_config .assert_called_once ()
@@ -199,14 +199,14 @@ async def test_configure_uvicorn_after_startup():
199
199
async def test_set_level_updates_all_loggers ():
200
200
"""Test that set_level updates all registered loggers."""
201
201
service = LoggingService ()
202
-
202
+
203
203
# Create some loggers
204
204
logger1 = service .get_logger ("test1" )
205
205
logger2 = service .get_logger ("test2" )
206
-
206
+
207
207
# Change level to ERROR
208
208
await service .set_level (LogLevel .ERROR )
209
-
209
+
210
210
# All loggers should be updated
211
211
assert logger1 .level == logging .ERROR
212
212
assert logger2 .level == logging .ERROR
@@ -217,7 +217,7 @@ async def test_set_level_updates_all_loggers():
217
217
async def test_should_log_all_levels ():
218
218
"""Test _should_log for all log levels."""
219
219
service = LoggingService ()
220
-
220
+
221
221
# Test each level (NOTICE, ALERT, EMERGENCY are also valid levels)
222
222
test_cases = [
223
223
(LogLevel .DEBUG , [LogLevel .DEBUG , LogLevel .INFO , LogLevel .NOTICE , LogLevel .WARNING , LogLevel .ERROR , LogLevel .CRITICAL , LogLevel .ALERT , LogLevel .EMERGENCY ]),
@@ -227,10 +227,10 @@ async def test_should_log_all_levels():
227
227
(LogLevel .ERROR , [LogLevel .ERROR , LogLevel .CRITICAL , LogLevel .ALERT , LogLevel .EMERGENCY ]),
228
228
(LogLevel .CRITICAL , [LogLevel .CRITICAL , LogLevel .ALERT , LogLevel .EMERGENCY ]),
229
229
]
230
-
230
+
231
231
for min_level , should_pass in test_cases :
232
232
service ._level = min_level
233
- for level in [LogLevel .DEBUG , LogLevel .INFO , LogLevel .NOTICE , LogLevel .WARNING ,
233
+ for level in [LogLevel .DEBUG , LogLevel .INFO , LogLevel .NOTICE , LogLevel .WARNING ,
234
234
LogLevel .ERROR , LogLevel .CRITICAL , LogLevel .ALERT , LogLevel .EMERGENCY ]:
235
235
if level in should_pass :
236
236
assert service ._should_log (level ), f"{ level } should log at { min_level } "
@@ -247,13 +247,13 @@ async def test_should_log_all_levels():
247
247
async def test_notify_with_logger_name ():
248
248
"""Test notify with a specific logger name."""
249
249
service = LoggingService ()
250
-
250
+
251
251
with patch .object (service , 'get_logger' ) as mock_get_logger :
252
252
mock_logger = MagicMock ()
253
253
mock_get_logger .return_value = mock_logger
254
-
254
+
255
255
await service .notify ("test message" , LogLevel .INFO , logger_name = "custom.logger" )
256
-
256
+
257
257
mock_get_logger .assert_called_with ("custom.logger" )
258
258
mock_logger .info .assert_called_with ("test message" )
259
259
@@ -262,13 +262,13 @@ async def test_notify_with_logger_name():
262
262
async def test_notify_without_logger_name ():
263
263
"""Test notify without a specific logger name uses root logger."""
264
264
service = LoggingService ()
265
-
265
+
266
266
with patch .object (service , 'get_logger' ) as mock_get_logger :
267
267
mock_logger = MagicMock ()
268
268
mock_get_logger .return_value = mock_logger
269
-
269
+
270
270
await service .notify ("test message" , LogLevel .WARNING )
271
-
271
+
272
272
mock_get_logger .assert_called_with ("" )
273
273
mock_logger .warning .assert_called_with ("test message" )
274
274
@@ -277,12 +277,12 @@ async def test_notify_without_logger_name():
277
277
async def test_notify_with_failed_subscriber ():
278
278
"""Test notify handles failed subscriber gracefully."""
279
279
service = LoggingService ()
280
-
280
+
281
281
# Create a mock queue that raises an exception
282
282
mock_queue = MagicMock ()
283
283
mock_queue .put = MagicMock (side_effect = Exception ("Queue error" ))
284
284
service ._subscribers .append (mock_queue )
285
-
285
+
286
286
# Should not raise, just log the error
287
287
await service .notify ("test message" , LogLevel .ERROR )
288
288
@@ -300,13 +300,13 @@ async def test_get_logger_with_file_handler_error():
300
300
mock_settings .log_to_file = True
301
301
mock_settings .log_file = "test.log"
302
302
mock_settings .log_folder = tmpdir
303
-
303
+
304
304
service = LoggingService ()
305
-
305
+
306
306
# Mock file handler to raise exception
307
307
with patch ("mcpgateway.services.logging_service._get_file_handler" , side_effect = Exception ("File error" )):
308
308
logger = service .get_logger ("test.logger" )
309
-
309
+
310
310
# Logger should still be created despite file handler error
311
311
assert logger is not None
312
312
assert logger .name == "test.logger"
@@ -316,10 +316,10 @@ async def test_get_logger_with_file_handler_error():
316
316
async def test_get_logger_reuses_existing ():
317
317
"""Test get_logger returns existing logger instance."""
318
318
service = LoggingService ()
319
-
319
+
320
320
logger1 = service .get_logger ("test.app" )
321
321
logger2 = service .get_logger ("test.app" )
322
-
322
+
323
323
assert logger1 is logger2
324
324
assert len (service ._loggers ) == 1
325
325
@@ -333,15 +333,15 @@ async def test_get_logger_reuses_existing():
333
333
async def test_shutdown_clears_subscribers ():
334
334
"""Test shutdown clears all subscribers."""
335
335
service = LoggingService ()
336
-
336
+
337
337
# Add some mock subscribers
338
338
service ._subscribers .append (MagicMock ())
339
339
service ._subscribers .append (MagicMock ())
340
-
340
+
341
341
assert len (service ._subscribers ) == 2
342
-
342
+
343
343
await service .shutdown ()
344
-
344
+
345
345
assert len (service ._subscribers ) == 0
346
346
347
347
@@ -355,34 +355,34 @@ async def test_dual_logging_integration():
355
355
"""Integration test for dual logging to console and file."""
356
356
with tempfile .TemporaryDirectory () as tmpdir :
357
357
log_file = os .path .join (tmpdir , "integration.log" )
358
-
358
+
359
359
with patch ("mcpgateway.services.logging_service.settings" ) as mock_settings :
360
360
mock_settings .log_to_file = True
361
361
mock_settings .log_file = "integration.log"
362
362
mock_settings .log_folder = tmpdir
363
363
mock_settings .log_rotation_enabled = False
364
364
mock_settings .log_filemode = "w"
365
-
365
+
366
366
# Reset global handlers
367
367
import mcpgateway .services .logging_service as ls
368
368
ls ._file_handler = None
369
369
ls ._text_handler = None
370
-
370
+
371
371
service = LoggingService ()
372
372
await service .initialize ()
373
-
373
+
374
374
# Log some messages
375
375
logger = service .get_logger ("integration.test" )
376
376
logger .info ("Integration test message" )
377
377
logger .error ("Integration error message" )
378
-
378
+
379
379
# Configure uvicorn loggers
380
380
service .configure_uvicorn_after_startup ()
381
381
uvicorn_logger = logging .getLogger ("uvicorn.access" )
382
382
uvicorn_logger .info ("127.0.0.1:8000 - \" GET /test HTTP/1.1\" 200" )
383
-
383
+
384
384
await service .shutdown ()
385
-
385
+
386
386
# Check file was created and contains expected content
387
387
assert os .path .exists (log_file )
388
388
with open (log_file , 'r' ) as f :
@@ -402,7 +402,7 @@ async def test_dual_logging_integration():
402
402
async def test_get_logger_with_empty_name ():
403
403
"""Test get_logger with empty name returns root logger."""
404
404
service = LoggingService ()
405
-
405
+
406
406
logger = service .get_logger ("" )
407
407
assert logger .name == "root"
408
408
@@ -411,7 +411,7 @@ async def test_get_logger_with_empty_name():
411
411
async def test_notify_with_all_log_levels ():
412
412
"""Test notify works with all log level values including special ones."""
413
413
service = LoggingService ()
414
-
414
+
415
415
# Test all levels including NOTICE, ALERT, EMERGENCY
416
416
# which are now mapped to appropriate Python levels
417
417
for level in [LogLevel .DEBUG , LogLevel .INFO , LogLevel .NOTICE , LogLevel .WARNING ,
@@ -425,18 +425,18 @@ async def test_file_handler_creates_directory():
425
425
"""Test that file handler creates log directory if it doesn't exist."""
426
426
with tempfile .TemporaryDirectory () as tmpdir :
427
427
log_folder = os .path .join (tmpdir , "new_logs" )
428
-
428
+
429
429
with patch ("mcpgateway.services.logging_service.settings" ) as mock_settings :
430
430
mock_settings .log_to_file = True
431
431
mock_settings .log_file = "test.log"
432
432
mock_settings .log_folder = log_folder
433
433
mock_settings .log_rotation_enabled = False
434
434
mock_settings .log_filemode = "a"
435
-
435
+
436
436
# Reset global handler
437
437
import mcpgateway .services .logging_service as ls
438
438
ls ._file_handler = None
439
-
439
+
440
440
handler = _get_file_handler ()
441
441
assert handler is not None
442
- assert os .path .exists (log_folder )
442
+ assert os .path .exists (log_folder )
0 commit comments