diff --git a/data/config.json b/data/config.json index 2ed9177..6a181ea 100644 --- a/data/config.json +++ b/data/config.json @@ -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" } \ No newline at end of file diff --git a/data/config_backups/config_backup_20251025_171511.json b/data/config_backups/config_backup_20251025_171511.json new file mode 100644 index 0000000..cc5be5f --- /dev/null +++ b/data/config_backups/config_backup_20251025_171511.json @@ -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" +} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251025_171530.json b/data/config_backups/config_backup_20251025_171530.json new file mode 100644 index 0000000..54deccb --- /dev/null +++ b/data/config_backups/config_backup_20251025_171530.json @@ -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" +} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251025_171647.json b/data/config_backups/config_backup_20251025_171647.json new file mode 100644 index 0000000..fd37f90 --- /dev/null +++ b/data/config_backups/config_backup_20251025_171647.json @@ -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" +} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251025_171912.json b/data/config_backups/config_backup_20251025_171912.json new file mode 100644 index 0000000..a070eb1 --- /dev/null +++ b/data/config_backups/config_backup_20251025_171912.json @@ -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" +} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251025_171937.json b/data/config_backups/config_backup_20251025_171937.json new file mode 100644 index 0000000..648b6ff --- /dev/null +++ b/data/config_backups/config_backup_20251025_171937.json @@ -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" +} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251025_172020.json b/data/config_backups/config_backup_20251025_172020.json new file mode 100644 index 0000000..8310997 --- /dev/null +++ b/data/config_backups/config_backup_20251025_172020.json @@ -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" +} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251025_172138.json b/data/config_backups/config_backup_20251025_172138.json new file mode 100644 index 0000000..65e68a5 --- /dev/null +++ b/data/config_backups/config_backup_20251025_172138.json @@ -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" +} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251025_172359.json b/data/config_backups/config_backup_20251025_172359.json new file mode 100644 index 0000000..232edb6 --- /dev/null +++ b/data/config_backups/config_backup_20251025_172359.json @@ -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" +} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251025_172550.json b/data/config_backups/config_backup_20251025_172550.json new file mode 100644 index 0000000..5f0d8c8 --- /dev/null +++ b/data/config_backups/config_backup_20251025_172550.json @@ -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" +} \ No newline at end of file diff --git a/data/download_queue.json b/data/download_queue.json index dd8bf82..1f9c3b0 100644 --- a/data/download_queue.json +++ b/data/download_queue.json @@ -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" } \ No newline at end of file diff --git a/logs/no_key_found.log b/logs/no_key_found.log deleted file mode 100644 index e69de29..0000000 diff --git a/src/core/SerieScanner.py b/src/core/SerieScanner.py index b48f152..3355f22 100644 --- a/src/core/SerieScanner.py +++ b/src/core/SerieScanner.py @@ -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( diff --git a/src/core/providers/aniworld_provider.py b/src/core/providers/aniworld_provider.py index 67f08a2..cf1ac07 100644 --- a/src/core/providers/aniworld_provider.py +++ b/src/core/providers/aniworld_provider.py @@ -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): diff --git a/src/infrastructure/logging/GlobalLogger.py b/src/infrastructure/logging/GlobalLogger.py deleted file mode 100644 index 553be95..0000000 --- a/src/infrastructure/logging/GlobalLogger.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/src/server/config/logging_config.py b/src/server/config/logging_config.py new file mode 100644 index 0000000..5642667 --- /dev/null +++ b/src/server/config/logging_config.py @@ -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) diff --git a/tests/unit/test_dependencies.py b/tests/unit/test_dependencies.py index 65459c3..1680cab 100644 --- a/tests/unit/test_dependencies.py +++ b/tests/unit/test_dependencies.py @@ -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()