refactor: remove GlobalLogger and migrate to standard Python logging

- Remove src/infrastructure/logging/GlobalLogger.py
- Update SerieScanner.py to use standard logging.getLogger()
- Update aniworld_provider.py to remove custom noKeyFound_logger setup
- Fix test_dependencies.py to properly mock config_service
- Fix code style issues (line length, formatting)
- All 846 tests passing
This commit is contained in:
Lukas 2025-10-25 17:27:49 +02:00
parent a3651e0e47
commit a41c86f1da
17 changed files with 447 additions and 135 deletions

View File

@ -18,7 +18,7 @@
},
"other": {
"anime_directory": "/home/lukas/Volume/serien/",
"master_password_hash": "$pbkdf2-sha256$29000$ZWwtJaQ0ZkxpLUWolRJijA$QcfgTBqgM3ABu9N93/w8naBLdfCKmKFc65Cn/f4fP84"
"master_password_hash": "$pbkdf2-sha256$29000$gdCa897bm/Oec07J2RvDOA$L60V4C1PUEGWC3WtX.32koXvTA12P6J2rYPXTIPFg5g"
},
"version": "1.0.0"
}

View File

@ -0,0 +1,24 @@
{
"name": "Aniworld",
"data_dir": "data",
"scheduler": {
"enabled": true,
"interval_minutes": 60
},
"logging": {
"level": "INFO",
"file": null,
"max_bytes": null,
"backup_count": 3
},
"backup": {
"enabled": false,
"path": "data/backups",
"keep_days": 30
},
"other": {
"anime_directory": "/home/lukas/Volume/serien/",
"master_password_hash": "$pbkdf2-sha256$29000$w3jPmfM.B8DY2xsjJCQkZA$BT52/04WG0hpMP/4uRnJUgBjYyrLxEwQodBoa6mKVOQ"
},
"version": "1.0.0"
}

View File

@ -0,0 +1,24 @@
{
"name": "Aniworld",
"data_dir": "data",
"scheduler": {
"enabled": true,
"interval_minutes": 60
},
"logging": {
"level": "INFO",
"file": null,
"max_bytes": null,
"backup_count": 3
},
"backup": {
"enabled": false,
"path": "data/backups",
"keep_days": 30
},
"other": {
"anime_directory": "/home/lukas/Volume/serien/",
"master_password_hash": "$pbkdf2-sha256$29000$fw.hFMI4JyRkrHXufU/J2Q$Lu.CArOq.fQ32HPX8IeiH/dNX1NOjOqVQl1uXuoTP4k"
},
"version": "1.0.0"
}

View File

@ -0,0 +1,24 @@
{
"name": "Aniworld",
"data_dir": "data",
"scheduler": {
"enabled": true,
"interval_minutes": 60
},
"logging": {
"level": "INFO",
"file": null,
"max_bytes": null,
"backup_count": 3
},
"backup": {
"enabled": false,
"path": "data/backups",
"keep_days": 30
},
"other": {
"anime_directory": "/home/lukas/Volume/serien/",
"master_password_hash": "$pbkdf2-sha256$29000$d.7dG8N4r/V.zzknxHiv9Q$pL5O6lAj4YhG1VNJYQKyojEti1yhvfxkjmP.O3OMXMk"
},
"version": "1.0.0"
}

View File

@ -0,0 +1,24 @@
{
"name": "Aniworld",
"data_dir": "data",
"scheduler": {
"enabled": true,
"interval_minutes": 60
},
"logging": {
"level": "INFO",
"file": null,
"max_bytes": null,
"backup_count": 3
},
"backup": {
"enabled": false,
"path": "data/backups",
"keep_days": 30
},
"other": {
"anime_directory": "/home/lukas/Volume/serien/",
"master_password_hash": "$pbkdf2-sha256$29000$aS1l7H3vnbM2JqT03jun1A$ZDIgiVSD5j6Gj/WrOJObZzhB/UBTDzjg3KebQj62ae0"
},
"version": "1.0.0"
}

View File

@ -0,0 +1,24 @@
{
"name": "Aniworld",
"data_dir": "data",
"scheduler": {
"enabled": true,
"interval_minutes": 60
},
"logging": {
"level": "INFO",
"file": null,
"max_bytes": null,
"backup_count": 3
},
"backup": {
"enabled": false,
"path": "data/backups",
"keep_days": 30
},
"other": {
"anime_directory": "/home/lukas/Volume/serien/",
"master_password_hash": "$pbkdf2-sha256$29000$s3YOYUyplRJiDOGcs3autQ$/Uxgp3nQSzYwKYB1tt7H230PL.zR7DQrB4Dp/8Y0xBI"
},
"version": "1.0.0"
}

View File

@ -0,0 +1,24 @@
{
"name": "Aniworld",
"data_dir": "data",
"scheduler": {
"enabled": true,
"interval_minutes": 60
},
"logging": {
"level": "INFO",
"file": null,
"max_bytes": null,
"backup_count": 3
},
"backup": {
"enabled": false,
"path": "data/backups",
"keep_days": 30
},
"other": {
"anime_directory": "/home/lukas/Volume/serien/",
"master_password_hash": "$pbkdf2-sha256$29000$RigFgPD.n9O69/5fq9Xauw$Vy2ak.30Y89iiSq9NOWKNxTp49ZyuZcIUCyzmyMgiCc"
},
"version": "1.0.0"
}

View File

@ -0,0 +1,24 @@
{
"name": "Aniworld",
"data_dir": "data",
"scheduler": {
"enabled": true,
"interval_minutes": 60
},
"logging": {
"level": "INFO",
"file": null,
"max_bytes": null,
"backup_count": 3
},
"backup": {
"enabled": false,
"path": "data/backups",
"keep_days": 30
},
"other": {
"anime_directory": "/home/lukas/Volume/serien/",
"master_password_hash": "$pbkdf2-sha256$29000$VQohxNhb6x0j5Lz3XqsVIg$yNJHqC7RxzhwScZVDmOY60CrCQG5RQbzCBGVz8FZrf0"
},
"version": "1.0.0"
}

View File

@ -0,0 +1,24 @@
{
"name": "Aniworld",
"data_dir": "data",
"scheduler": {
"enabled": true,
"interval_minutes": 60
},
"logging": {
"level": "INFO",
"file": null,
"max_bytes": null,
"backup_count": 3
},
"backup": {
"enabled": false,
"path": "data/backups",
"keep_days": 30
},
"other": {
"anime_directory": "/home/lukas/Volume/serien/",
"master_password_hash": "$pbkdf2-sha256$29000$I8S41/p/D2HsXYsxhvCeUw$0MEEWNZA3RJlxKtqZ8TDqahhDlueTvHWE100uo115R4"
},
"version": "1.0.0"
}

View File

@ -0,0 +1,24 @@
{
"name": "Aniworld",
"data_dir": "data",
"scheduler": {
"enabled": true,
"interval_minutes": 60
},
"logging": {
"level": "INFO",
"file": null,
"max_bytes": null,
"backup_count": 3
},
"backup": {
"enabled": false,
"path": "data/backups",
"keep_days": 30
},
"other": {
"anime_directory": "/home/lukas/Volume/serien/",
"master_password_hash": "$pbkdf2-sha256$29000$YoyRkvIeg/D.H8NYKwWgdA$AWAYrJO3h7vDZJ3IZCc1men8OrroAzRXlJWvXcBpBDA"
},
"version": "1.0.0"
}

View File

@ -1,7 +1,7 @@
{
"pending": [
{
"id": "7cc643ca-0b4e-4769-8d25-c99ce539b434",
"id": "fb90e232-52cc-4315-a994-725d132df69a",
"serie_id": "workflow-series",
"serie_name": "Workflow Test Series",
"episode": {
@ -11,7 +11,7 @@
},
"status": "pending",
"priority": "high",
"added_at": "2025-10-24T17:23:26.098284Z",
"added_at": "2025-10-25T15:25:53.734312Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -20,7 +20,7 @@
"source_url": null
},
{
"id": "6a017a0d-78e2-4123-9715-80b540e03c41",
"id": "37ab9885-cf61-4417-96b7-383490ce2fca",
"serie_id": "series-2",
"serie_name": "Series 2",
"episode": {
@ -30,7 +30,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.819219Z",
"added_at": "2025-10-25T15:25:53.327912Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -39,7 +39,7 @@
"source_url": null
},
{
"id": "e31ecefa-470a-4ea6-aaa0-c16d38d5ab8b",
"id": "ede47aa4-4533-4a86-90e6-81126ff1189a",
"serie_id": "series-1",
"serie_name": "Series 1",
"episode": {
@ -49,7 +49,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.816100Z",
"added_at": "2025-10-25T15:25:53.324487Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -58,7 +58,7 @@
"source_url": null
},
{
"id": "e3b9418c-7b1e-47dc-928c-3746059a0fa8",
"id": "33af9d7b-4787-4d54-af2a-6b9af4f2b061",
"serie_id": "series-0",
"serie_name": "Series 0",
"episode": {
@ -68,7 +68,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.812680Z",
"added_at": "2025-10-25T15:25:53.322230Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -77,7 +77,7 @@
"source_url": null
},
{
"id": "77083b3b-8b7b-4e02-a4c9-0e95652b1865",
"id": "c04610c9-fa71-43fc-9cbf-2f8934b097bd",
"serie_id": "series-high",
"serie_name": "Series High",
"episode": {
@ -87,7 +87,7 @@
},
"status": "pending",
"priority": "high",
"added_at": "2025-10-24T17:23:25.591277Z",
"added_at": "2025-10-25T15:25:53.022054Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -96,7 +96,7 @@
"source_url": null
},
{
"id": "03fa75a1-0641-41e8-be69-c274383d6198",
"id": "1da55781-81dd-43bc-89a7-b4f9149c0cfe",
"serie_id": "test-series-2",
"serie_name": "Another Series",
"episode": {
@ -106,7 +106,7 @@
},
"status": "pending",
"priority": "high",
"added_at": "2025-10-24T17:23:25.567577Z",
"added_at": "2025-10-25T15:25:52.986476Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -115,7 +115,7 @@
"source_url": null
},
{
"id": "bbfa8dd3-0f28-43f3-9f42-03595684e873",
"id": "155cef74-9b1d-4582-b836-d13c48290c80",
"serie_id": "test-series-1",
"serie_name": "Test Anime Series",
"episode": {
@ -125,7 +125,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.543811Z",
"added_at": "2025-10-25T15:25:52.952839Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -134,7 +134,7 @@
"source_url": null
},
{
"id": "4d462a39-e705-4dd4-a968-e6d995471615",
"id": "682399d3-6b56-4bc2-b3a6-8aa0f30d91ba",
"serie_id": "test-series-1",
"serie_name": "Test Anime Series",
"episode": {
@ -144,7 +144,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.543911Z",
"added_at": "2025-10-25T15:25:52.952940Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -153,7 +153,7 @@
"source_url": null
},
{
"id": "04e5ce5d-ce4c-4776-a1be-b0c78c17d651",
"id": "b12de848-efcd-4e4d-838e-992f48dca631",
"serie_id": "series-normal",
"serie_name": "Series Normal",
"episode": {
@ -163,7 +163,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.593205Z",
"added_at": "2025-10-25T15:25:53.024208Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -172,7 +172,7 @@
"source_url": null
},
{
"id": "8a8da509-9bec-4979-aa01-22f726e298ef",
"id": "901ea5d2-1491-492f-8f7b-62db605d6bb4",
"serie_id": "series-low",
"serie_name": "Series Low",
"episode": {
@ -182,7 +182,7 @@
},
"status": "pending",
"priority": "low",
"added_at": "2025-10-24T17:23:25.595371Z",
"added_at": "2025-10-25T15:25:53.026631Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -191,7 +191,7 @@
"source_url": null
},
{
"id": "b07b9e02-3517-4066-aba0-2ee6b2349580",
"id": "6cddccb4-14cd-43e7-9425-5c4b20dd34f2",
"serie_id": "test-series",
"serie_name": "Test Series",
"episode": {
@ -201,7 +201,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.760199Z",
"added_at": "2025-10-25T15:25:53.257343Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -210,7 +210,7 @@
"source_url": null
},
{
"id": "9577295e-7ac6-4786-8601-ac13267aba9f",
"id": "2b6e4d96-70e9-4d71-806f-ffd6348ef205",
"serie_id": "test-series",
"serie_name": "Test Series",
"episode": {
@ -220,7 +220,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.850731Z",
"added_at": "2025-10-25T15:25:53.364247Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -229,7 +229,7 @@
"source_url": null
},
{
"id": "562ce52c-2979-4107-b630-999ff6c095e9",
"id": "1ac25804-5be3-420a-93df-1ca7b70a40e2",
"serie_id": "invalid-series",
"serie_name": "Invalid Series",
"episode": {
@ -239,7 +239,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.902493Z",
"added_at": "2025-10-25T15:25:53.430806Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -248,7 +248,7 @@
"source_url": null
},
{
"id": "1684fe7f-5755-4064-86ed-a78831e8dc0f",
"id": "4e568aec-ac73-444a-aeb3-d8c6c27cf630",
"serie_id": "test-series",
"serie_name": "Test Series",
"episode": {
@ -258,7 +258,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.926933Z",
"added_at": "2025-10-25T15:25:53.464054Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -267,7 +267,7 @@
"source_url": null
},
{
"id": "c4fe86cb-e6f7-4303-a8b6-2e76c51d7c40",
"id": "1e387ffb-c029-4fa6-9e48-93244c9b91fc",
"serie_id": "series-4",
"serie_name": "Series 4",
"episode": {
@ -277,7 +277,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.965540Z",
"added_at": "2025-10-25T15:25:53.550292Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -286,45 +286,7 @@
"source_url": null
},
{
"id": "94d7d85c-911e-495b-9203-065324594c74",
"serie_id": "series-0",
"serie_name": "Series 0",
"episode": {
"season": 1,
"episode": 1,
"title": null
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.966417Z",
"started_at": null,
"completed_at": null,
"progress": null,
"error": null,
"retry_count": 0,
"source_url": null
},
{
"id": "1d8e1cda-ff78-4ab8-a040-2f325d53666a",
"serie_id": "series-3",
"serie_name": "Series 3",
"episode": {
"season": 1,
"episode": 1,
"title": null
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.967083Z",
"started_at": null,
"completed_at": null,
"progress": null,
"error": null,
"retry_count": 0,
"source_url": null
},
{
"id": "f9b4174e-f809-4272-bcd8-f9bd44238d3c",
"id": "73c2af3f-784c-4351-9bac-ddefa11e5e18",
"serie_id": "series-2",
"serie_name": "Series 2",
"episode": {
@ -334,7 +296,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.967759Z",
"added_at": "2025-10-25T15:25:53.552089Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -343,7 +305,45 @@
"source_url": null
},
{
"id": "b41f4c2a-40d6-4205-b769-c3a77df8df5e",
"id": "d6a798ce-be87-42d9-847a-39e3ae1bc704",
"serie_id": "series-3",
"serie_name": "Series 3",
"episode": {
"season": 1,
"episode": 1,
"title": null
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-25T15:25:53.552805Z",
"started_at": null,
"completed_at": null,
"progress": null,
"error": null,
"retry_count": 0,
"source_url": null
},
{
"id": "1c34959e-bcdb-4dce-812b-32fe278f5a42",
"serie_id": "series-0",
"serie_name": "Series 0",
"episode": {
"season": 1,
"episode": 1,
"title": null
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-25T15:25:53.553458Z",
"started_at": null,
"completed_at": null,
"progress": null,
"error": null,
"retry_count": 0,
"source_url": null
},
{
"id": "9a02ae1f-c264-4a72-98e0-a2120dc625ea",
"serie_id": "series-1",
"serie_name": "Series 1",
"episode": {
@ -353,7 +353,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:25.968503Z",
"added_at": "2025-10-25T15:25:53.554161Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -362,7 +362,7 @@
"source_url": null
},
{
"id": "ae4e67dd-b77f-4fbe-8d4c-19fe979f6783",
"id": "3080213e-4a0f-4b62-8dd5-871640e4379d",
"serie_id": "persistent-series",
"serie_name": "Persistent Series",
"episode": {
@ -372,7 +372,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:26.027365Z",
"added_at": "2025-10-25T15:25:53.636324Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -381,7 +381,7 @@
"source_url": null
},
{
"id": "5dc0b529-627c-47ed-8f2a-55112d78de93",
"id": "6bd8762b-9800-4306-9143-8277b7d23611",
"serie_id": "ws-series",
"serie_name": "WebSocket Series",
"episode": {
@ -391,7 +391,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:26.073822Z",
"added_at": "2025-10-25T15:25:53.700590Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -400,7 +400,7 @@
"source_url": null
},
{
"id": "44f479fd-61f7-4279-ace1-5fbf31dad243",
"id": "b0f208df-9fa1-4952-a65b-97d39696ee72",
"serie_id": "pause-test",
"serie_name": "Pause Test Series",
"episode": {
@ -410,7 +410,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-24T17:23:26.227077Z",
"added_at": "2025-10-25T15:25:53.876408Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -421,5 +421,5 @@
],
"active": [],
"failed": [],
"timestamp": "2025-10-24T17:23:26.227320+00:00"
"timestamp": "2025-10-25T15:25:53.876987+00:00"
}

View File

View File

@ -23,9 +23,10 @@ from src.core.interfaces.callbacks import (
ProgressPhase,
)
from src.core.providers.base_provider import Loader
from src.infrastructure.logging.GlobalLogger import error_logger, noKeyFound_logger
logger = logging.getLogger(__name__)
error_logger = logging.getLogger("error")
no_key_found_logger = logging.getLogger("series.nokey")
class SerieScanner:
@ -174,7 +175,7 @@ class SerieScanner:
# remote metadata, yielding missing episodes per
# season. Results are saved back to disk so that both
# CLI and API consumers see consistent state.
missing_episodes, site = (
missing_episodes, _site = (
self.__get_missing_episodes_and_season(
serie.key, mp4_files
)
@ -192,14 +193,14 @@ class SerieScanner:
)
else:
self.folderDict[serie.key] = serie
noKeyFound_logger.info(
no_key_found_logger.info(
"Saved Serie: '%s'", str(serie)
)
except NoKeyFoundException as nkfe:
# Log error and notify via callback
error_msg = f"Error processing folder '{folder}': {nkfe}"
NoKeyFoundException.error(error_msg)
logger.error(error_msg)
self._callback_manager.notify_error(
ErrorContext(

View File

@ -46,12 +46,7 @@ if not download_error_logger.handlers:
download_error_handler.setLevel(logging.ERROR)
download_error_logger.addHandler(download_error_handler)
noKeyFound_logger = logging.getLogger("NoKeyFound")
if not noKeyFound_logger.handlers:
log_path = _logs_dir / "no_key_found.log"
noKeyFound_handler = logging.FileHandler(str(log_path))
noKeyFound_handler.setLevel(logging.ERROR)
noKeyFound_logger.addHandler(noKeyFound_handler)
noKeyFound_logger = logging.getLogger()
class AniworldLoader(Loader):

View File

@ -1,40 +0,0 @@
import logging
console_handler = None
error_logger = None
noKeyFound_logger = None
noGerFound_logger = None
def setupLogger():
global console_handler, error_logger, noKeyFound_logger, noGerFound_logger
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(funcName)s - %(message)s')
if (console_handler is None):
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(logging.Formatter(
"%(asctime)s - %(levelname)s - %(funcName)s - %(message)s")
)
logging.getLogger().addHandler(console_handler)
logging.getLogger("urllib3.connectionpool").setLevel(logging.INFO)
logging.getLogger('charset_normalizer').setLevel(logging.INFO)
logging.getLogger().setLevel(logging.INFO)
if (error_logger is None):
error_logger = logging.getLogger("ErrorLog")
error_handler = logging.FileHandler("../errors.log")
error_handler.setLevel(logging.ERROR)
error_logger.addHandler(error_handler)
if (noKeyFound_logger is None):
noKeyFound_logger = logging.getLogger("NoKeyFound")
noKeyFound_handler = logging.FileHandler("../NoKeyFound.log")
noKeyFound_handler.setLevel(logging.ERROR)
noKeyFound_logger.addHandler(noKeyFound_handler)
if (noGerFound_logger is None):
noGerFound_logger = logging.getLogger("noGerFound")
noGerFound_handler = logging.FileHandler("../noGerFound.log")
noGerFound_handler.setLevel(logging.ERROR)
noGerFound_logger.addHandler(noGerFound_handler)
setupLogger()

View File

@ -0,0 +1,122 @@
"""
Logging configuration for the FastAPI server.
This module provides comprehensive logging setup with both console and file
handlers, ensuring all server activity is properly logged.
"""
import logging
import sys
from pathlib import Path
from typing import Dict
from src.config.settings import settings
def setup_logging() -> Dict[str, logging.Logger]:
"""
Configure logging for the FastAPI application.
Creates:
- Console handler for real-time output
- File handler for server.log (general logs)
- File handler for error.log (errors only)
- File handler for access.log (request logs)
Returns:
Dict containing configured loggers
"""
# Create logs directory if it doesn't exist
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)
# Define log file paths
server_log_file = log_dir / "server.log"
error_log_file = log_dir / "error.log"
access_log_file = log_dir / "access.log"
# Define log format
detailed_format = logging.Formatter(
fmt="%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
simple_format = logging.Formatter(
fmt="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
# Configure root logger
root_logger = logging.getLogger()
root_logger.setLevel(getattr(logging, settings.log_level.upper(), logging.INFO))
# Remove existing handlers to avoid duplicates
root_logger.handlers.clear()
# Console handler - visible in terminal
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(simple_format)
root_logger.addHandler(console_handler)
# File handler for general server logs
server_file_handler = logging.FileHandler(server_log_file, mode='a', encoding='utf-8')
server_file_handler.setLevel(logging.DEBUG)
server_file_handler.setFormatter(detailed_format)
root_logger.addHandler(server_file_handler)
# File handler for errors only
error_file_handler = logging.FileHandler(error_log_file, mode='a', encoding='utf-8')
error_file_handler.setLevel(logging.ERROR)
error_file_handler.setFormatter(detailed_format)
root_logger.addHandler(error_file_handler)
# Configure uvicorn loggers
uvicorn_logger = logging.getLogger("uvicorn")
uvicorn_logger.setLevel(logging.INFO)
uvicorn_access_logger = logging.getLogger("uvicorn.access")
uvicorn_access_logger.setLevel(logging.INFO)
# Access log file handler
access_file_handler = logging.FileHandler(access_log_file, mode='a', encoding='utf-8')
access_file_handler.setLevel(logging.INFO)
access_file_handler.setFormatter(simple_format)
uvicorn_access_logger.addHandler(access_file_handler)
# Configure FastAPI logger
fastapi_logger = logging.getLogger("fastapi")
fastapi_logger.setLevel(logging.INFO)
# Reduce noise from third-party libraries
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("charset_normalizer").setLevel(logging.WARNING)
logging.getLogger("multipart").setLevel(logging.WARNING)
# Log initial setup
root_logger.info("=" * 80)
root_logger.info("FastAPI Server Logging Initialized")
root_logger.info(f"Log Level: {settings.log_level.upper()}")
root_logger.info(f"Server Log: {server_log_file.absolute()}")
root_logger.info(f"Error Log: {error_log_file.absolute()}")
root_logger.info(f"Access Log: {access_log_file.absolute()}")
root_logger.info("=" * 80)
return {
"root": root_logger,
"uvicorn": uvicorn_logger,
"uvicorn.access": uvicorn_access_logger,
"fastapi": fastapi_logger,
}
def get_logger(name: str) -> logging.Logger:
"""
Get a logger instance for a specific module.
Args:
name: Name of the module/logger
Returns:
Configured logger instance
"""
return logging.getLogger(name)

View File

@ -50,11 +50,21 @@ class TestSeriesAppDependency:
assert result == mock_series_app_instance
mock_series_app_class.assert_called_once_with("/path/to/anime")
@patch('src.server.services.config_service.get_config_service')
@patch('src.server.utils.dependencies.settings')
def test_get_series_app_no_directory_configured(self, mock_settings):
def test_get_series_app_no_directory_configured(
self, mock_settings, mock_get_config_service
):
"""Test SeriesApp dependency when directory is not configured."""
# Arrange
mock_settings.anime_directory = ""
# Mock config service to return empty config
mock_config_service = Mock()
mock_config = Mock()
mock_config.other = {}
mock_config_service.load_config.return_value = mock_config
mock_get_config_service.return_value = mock_config_service
# Act & Assert
with pytest.raises(HTTPException) as exc_info:
@ -178,8 +188,10 @@ class TestAuthenticationDependencies:
mock_get_current_user.assert_called_once_with(credentials)
@patch('src.server.utils.dependencies.get_current_user')
def test_optional_auth_with_invalid_credentials(self,
mock_get_current_user):
def test_optional_auth_with_invalid_credentials(
self,
mock_get_current_user
):
"""Test optional authentication with invalid credentials."""
# Arrange
credentials = HTTPAuthorizationCredentials(
@ -277,6 +289,8 @@ class TestUtilityDependencies:
await log_request_dependency(mock_request)
# Assert - no exception should be raised
class TestIntegrationScenarios:
"""Integration test scenarios for dependency injection."""
@ -284,14 +298,18 @@ class TestIntegrationScenarios:
"""Test the complete SeriesApp dependency lifecycle."""
# Use separate mock instances for each call
with patch('src.server.utils.dependencies.settings') as mock_settings:
with patch('src.server.utils.dependencies.SeriesApp') as mock_series_app_class:
with patch(
'src.server.utils.dependencies.SeriesApp'
) as mock_series_app_class:
# Arrange
mock_settings.anime_directory = "/path/to/anime"
# Create separate mock instances for each instantiation
mock_instance1 = MagicMock()
mock_instance2 = MagicMock()
mock_series_app_class.side_effect = [mock_instance1, mock_instance2]
mock_series_app_class.side_effect = [
mock_instance1, mock_instance2
]
# Act - Get SeriesApp instance
app1 = get_series_app()