unrealon 1.0.9__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unrealon/__init__.py +23 -21
- unrealon-1.1.0.dist-info/METADATA +164 -0
- unrealon-1.1.0.dist-info/RECORD +82 -0
- {unrealon-1.0.9.dist-info → unrealon-1.1.0.dist-info}/WHEEL +1 -1
- unrealon-1.1.0.dist-info/entry_points.txt +9 -0
- {unrealon-1.0.9.dist-info → unrealon-1.1.0.dist-info/licenses}/LICENSE +1 -1
- unrealon_bridge/__init__.py +114 -0
- unrealon_bridge/cli.py +316 -0
- unrealon_bridge/client/__init__.py +93 -0
- unrealon_bridge/client/base.py +78 -0
- unrealon_bridge/client/commands.py +89 -0
- unrealon_bridge/client/connection.py +90 -0
- unrealon_bridge/client/events.py +65 -0
- unrealon_bridge/client/health.py +38 -0
- unrealon_bridge/client/html_parser.py +146 -0
- unrealon_bridge/client/logging.py +139 -0
- unrealon_bridge/client/proxy.py +70 -0
- unrealon_bridge/client/scheduler.py +450 -0
- unrealon_bridge/client/session.py +70 -0
- unrealon_bridge/configs/__init__.py +14 -0
- unrealon_bridge/configs/bridge_config.py +212 -0
- unrealon_bridge/configs/bridge_config.yaml +39 -0
- unrealon_bridge/models/__init__.py +138 -0
- unrealon_bridge/models/base.py +28 -0
- unrealon_bridge/models/command.py +41 -0
- unrealon_bridge/models/events.py +40 -0
- unrealon_bridge/models/html_parser.py +79 -0
- unrealon_bridge/models/logging.py +55 -0
- unrealon_bridge/models/parser.py +63 -0
- unrealon_bridge/models/proxy.py +41 -0
- unrealon_bridge/models/requests.py +95 -0
- unrealon_bridge/models/responses.py +88 -0
- unrealon_bridge/models/scheduler.py +592 -0
- unrealon_bridge/models/session.py +28 -0
- unrealon_bridge/server/__init__.py +91 -0
- unrealon_bridge/server/base.py +171 -0
- unrealon_bridge/server/handlers/__init__.py +23 -0
- unrealon_bridge/server/handlers/command.py +110 -0
- unrealon_bridge/server/handlers/html_parser.py +139 -0
- unrealon_bridge/server/handlers/logging.py +95 -0
- unrealon_bridge/server/handlers/parser.py +95 -0
- unrealon_bridge/server/handlers/proxy.py +75 -0
- unrealon_bridge/server/handlers/scheduler.py +545 -0
- unrealon_bridge/server/handlers/session.py +66 -0
- unrealon_browser/__init__.py +61 -18
- unrealon_browser/{src/cli → cli}/browser_cli.py +6 -13
- unrealon_browser/{src/cli → cli}/cookies_cli.py +5 -1
- unrealon_browser/{src/core → core}/browser_manager.py +2 -2
- unrealon_browser/{src/managers → managers}/captcha.py +1 -1
- unrealon_browser/{src/managers → managers}/cookies.py +1 -1
- unrealon_browser/managers/logger_bridge.py +231 -0
- unrealon_browser/{src/managers → managers}/profile.py +1 -1
- unrealon_driver/__init__.py +73 -19
- unrealon_driver/browser/__init__.py +8 -0
- unrealon_driver/browser/config.py +74 -0
- unrealon_driver/browser/manager.py +416 -0
- unrealon_driver/exceptions.py +28 -0
- unrealon_driver/parser/__init__.py +55 -0
- unrealon_driver/parser/cli_manager.py +141 -0
- unrealon_driver/parser/daemon_manager.py +227 -0
- unrealon_driver/parser/managers/__init__.py +46 -0
- unrealon_driver/parser/managers/browser.py +51 -0
- unrealon_driver/parser/managers/config.py +281 -0
- unrealon_driver/parser/managers/error.py +412 -0
- unrealon_driver/parser/managers/html.py +732 -0
- unrealon_driver/parser/managers/logging.py +609 -0
- unrealon_driver/parser/managers/result.py +321 -0
- unrealon_driver/parser/parser_manager.py +628 -0
- unrealon/sdk_config.py +0 -88
- unrealon-1.0.9.dist-info/METADATA +0 -810
- unrealon-1.0.9.dist-info/RECORD +0 -246
- unrealon_browser/pyproject.toml +0 -182
- unrealon_browser/src/__init__.py +0 -62
- unrealon_browser/src/managers/logger_bridge.py +0 -395
- unrealon_driver/README.md +0 -204
- unrealon_driver/pyproject.toml +0 -187
- unrealon_driver/src/__init__.py +0 -90
- unrealon_driver/src/cli/__init__.py +0 -10
- unrealon_driver/src/cli/main.py +0 -66
- unrealon_driver/src/cli/simple.py +0 -510
- unrealon_driver/src/config/__init__.py +0 -11
- unrealon_driver/src/config/auto_config.py +0 -478
- unrealon_driver/src/core/__init__.py +0 -18
- unrealon_driver/src/core/exceptions.py +0 -289
- unrealon_driver/src/core/parser.py +0 -638
- unrealon_driver/src/dto/__init__.py +0 -66
- unrealon_driver/src/dto/cli.py +0 -119
- unrealon_driver/src/dto/config.py +0 -18
- unrealon_driver/src/dto/events.py +0 -237
- unrealon_driver/src/dto/execution.py +0 -313
- unrealon_driver/src/dto/services.py +0 -311
- unrealon_driver/src/execution/__init__.py +0 -23
- unrealon_driver/src/execution/daemon_mode.py +0 -317
- unrealon_driver/src/execution/interactive_mode.py +0 -88
- unrealon_driver/src/execution/modes.py +0 -45
- unrealon_driver/src/execution/scheduled_mode.py +0 -209
- unrealon_driver/src/execution/test_mode.py +0 -250
- unrealon_driver/src/logging/__init__.py +0 -24
- unrealon_driver/src/logging/driver_logger.py +0 -512
- unrealon_driver/src/services/__init__.py +0 -24
- unrealon_driver/src/services/browser_service.py +0 -726
- unrealon_driver/src/services/llm/__init__.py +0 -15
- unrealon_driver/src/services/llm/browser_llm_service.py +0 -363
- unrealon_driver/src/services/llm/llm.py +0 -195
- unrealon_driver/src/services/logger_service.py +0 -232
- unrealon_driver/src/services/metrics_service.py +0 -185
- unrealon_driver/src/services/scheduler_service.py +0 -489
- unrealon_driver/src/services/websocket_service.py +0 -362
- unrealon_driver/src/utils/__init__.py +0 -16
- unrealon_driver/src/utils/service_factory.py +0 -317
- unrealon_driver/src/utils/time_formatter.py +0 -338
- unrealon_llm/README.md +0 -44
- unrealon_llm/__init__.py +0 -26
- unrealon_llm/pyproject.toml +0 -154
- unrealon_llm/src/__init__.py +0 -228
- unrealon_llm/src/cli/__init__.py +0 -0
- unrealon_llm/src/core/__init__.py +0 -11
- unrealon_llm/src/core/smart_client.py +0 -438
- unrealon_llm/src/dto/__init__.py +0 -155
- unrealon_llm/src/dto/models/__init__.py +0 -0
- unrealon_llm/src/dto/models/config.py +0 -343
- unrealon_llm/src/dto/models/core.py +0 -328
- unrealon_llm/src/dto/models/enums.py +0 -123
- unrealon_llm/src/dto/models/html_analysis.py +0 -345
- unrealon_llm/src/dto/models/statistics.py +0 -473
- unrealon_llm/src/dto/models/translation.py +0 -383
- unrealon_llm/src/dto/models/type_conversion.py +0 -462
- unrealon_llm/src/dto/schemas/__init__.py +0 -0
- unrealon_llm/src/exceptions.py +0 -392
- unrealon_llm/src/llm_config/__init__.py +0 -20
- unrealon_llm/src/llm_config/logging_config.py +0 -178
- unrealon_llm/src/llm_logging/__init__.py +0 -42
- unrealon_llm/src/llm_logging/llm_events.py +0 -107
- unrealon_llm/src/llm_logging/llm_logger.py +0 -466
- unrealon_llm/src/managers/__init__.py +0 -15
- unrealon_llm/src/managers/cache_manager.py +0 -67
- unrealon_llm/src/managers/cost_manager.py +0 -107
- unrealon_llm/src/managers/request_manager.py +0 -298
- unrealon_llm/src/modules/__init__.py +0 -0
- unrealon_llm/src/modules/html_processor/__init__.py +0 -25
- unrealon_llm/src/modules/html_processor/base_processor.py +0 -415
- unrealon_llm/src/modules/html_processor/details_processor.py +0 -85
- unrealon_llm/src/modules/html_processor/listing_processor.py +0 -91
- unrealon_llm/src/modules/html_processor/models/__init__.py +0 -20
- unrealon_llm/src/modules/html_processor/models/processing_models.py +0 -40
- unrealon_llm/src/modules/html_processor/models/universal_model.py +0 -56
- unrealon_llm/src/modules/html_processor/processor.py +0 -102
- unrealon_llm/src/modules/llm/__init__.py +0 -0
- unrealon_llm/src/modules/translator/__init__.py +0 -0
- unrealon_llm/src/provider.py +0 -116
- unrealon_llm/src/utils/__init__.py +0 -95
- unrealon_llm/src/utils/common.py +0 -64
- unrealon_llm/src/utils/data_extractor.py +0 -188
- unrealon_llm/src/utils/html_cleaner.py +0 -767
- unrealon_llm/src/utils/language_detector.py +0 -308
- unrealon_llm/src/utils/models_cache.py +0 -592
- unrealon_llm/src/utils/smart_counter.py +0 -229
- unrealon_llm/src/utils/token_counter.py +0 -189
- unrealon_sdk/README.md +0 -25
- unrealon_sdk/__init__.py +0 -30
- unrealon_sdk/pyproject.toml +0 -231
- unrealon_sdk/src/__init__.py +0 -150
- unrealon_sdk/src/cli/__init__.py +0 -12
- unrealon_sdk/src/cli/commands/__init__.py +0 -22
- unrealon_sdk/src/cli/commands/benchmark.py +0 -42
- unrealon_sdk/src/cli/commands/diagnostics.py +0 -573
- unrealon_sdk/src/cli/commands/health.py +0 -46
- unrealon_sdk/src/cli/commands/integration.py +0 -498
- unrealon_sdk/src/cli/commands/reports.py +0 -43
- unrealon_sdk/src/cli/commands/security.py +0 -36
- unrealon_sdk/src/cli/commands/server.py +0 -483
- unrealon_sdk/src/cli/commands/servers.py +0 -56
- unrealon_sdk/src/cli/commands/tests.py +0 -55
- unrealon_sdk/src/cli/main.py +0 -126
- unrealon_sdk/src/cli/utils/reporter.py +0 -519
- unrealon_sdk/src/clients/openapi.yaml +0 -3347
- unrealon_sdk/src/clients/python_http/__init__.py +0 -3
- unrealon_sdk/src/clients/python_http/api_config.py +0 -228
- unrealon_sdk/src/clients/python_http/models/BaseModel.py +0 -12
- unrealon_sdk/src/clients/python_http/models/BroadcastDeliveryStats.py +0 -33
- unrealon_sdk/src/clients/python_http/models/BroadcastMessage.py +0 -17
- unrealon_sdk/src/clients/python_http/models/BroadcastMessageRequest.py +0 -35
- unrealon_sdk/src/clients/python_http/models/BroadcastPriority.py +0 -10
- unrealon_sdk/src/clients/python_http/models/BroadcastResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/BroadcastResultResponse.py +0 -33
- unrealon_sdk/src/clients/python_http/models/BroadcastTarget.py +0 -11
- unrealon_sdk/src/clients/python_http/models/ConnectionStats.py +0 -27
- unrealon_sdk/src/clients/python_http/models/ConnectionsResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/DeveloperMessageResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ErrorResponse.py +0 -25
- unrealon_sdk/src/clients/python_http/models/HTTPValidationError.py +0 -16
- unrealon_sdk/src/clients/python_http/models/HealthResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/HealthStatus.py +0 -33
- unrealon_sdk/src/clients/python_http/models/LogLevel.py +0 -10
- unrealon_sdk/src/clients/python_http/models/LoggingRequest.py +0 -27
- unrealon_sdk/src/clients/python_http/models/LoggingResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/MaintenanceMode.py +0 -9
- unrealon_sdk/src/clients/python_http/models/MaintenanceModeRequest.py +0 -33
- unrealon_sdk/src/clients/python_http/models/MaintenanceStatusResponse.py +0 -39
- unrealon_sdk/src/clients/python_http/models/ParserCommandRequest.py +0 -25
- unrealon_sdk/src/clients/python_http/models/ParserMessageResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/ParserRegistrationRequest.py +0 -28
- unrealon_sdk/src/clients/python_http/models/ParserRegistrationResponse.py +0 -25
- unrealon_sdk/src/clients/python_http/models/ParserType.py +0 -10
- unrealon_sdk/src/clients/python_http/models/ProxyBlockRequest.py +0 -19
- unrealon_sdk/src/clients/python_http/models/ProxyEndpointResponse.py +0 -20
- unrealon_sdk/src/clients/python_http/models/ProxyListResponse.py +0 -19
- unrealon_sdk/src/clients/python_http/models/ProxyProvider.py +0 -10
- unrealon_sdk/src/clients/python_http/models/ProxyPurchaseRequest.py +0 -25
- unrealon_sdk/src/clients/python_http/models/ProxyResponse.py +0 -47
- unrealon_sdk/src/clients/python_http/models/ProxyRotationRequest.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ProxyStatus.py +0 -10
- unrealon_sdk/src/clients/python_http/models/ProxyUsageRequest.py +0 -19
- unrealon_sdk/src/clients/python_http/models/ProxyUsageStatsResponse.py +0 -26
- unrealon_sdk/src/clients/python_http/models/ServiceRegistrationDto.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ServiceStatsResponse.py +0 -31
- unrealon_sdk/src/clients/python_http/models/SessionStartRequest.py +0 -23
- unrealon_sdk/src/clients/python_http/models/SuccessResponse.py +0 -25
- unrealon_sdk/src/clients/python_http/models/SystemNotificationResponse.py +0 -23
- unrealon_sdk/src/clients/python_http/models/ValidationError.py +0 -18
- unrealon_sdk/src/clients/python_http/models/ValidationErrorResponse.py +0 -21
- unrealon_sdk/src/clients/python_http/models/WebSocketMetrics.py +0 -21
- unrealon_sdk/src/clients/python_http/models/__init__.py +0 -44
- unrealon_sdk/src/clients/python_http/services/None_service.py +0 -35
- unrealon_sdk/src/clients/python_http/services/ParserManagement_service.py +0 -190
- unrealon_sdk/src/clients/python_http/services/ProxyManagement_service.py +0 -289
- unrealon_sdk/src/clients/python_http/services/SocketLogging_service.py +0 -187
- unrealon_sdk/src/clients/python_http/services/SystemHealth_service.py +0 -119
- unrealon_sdk/src/clients/python_http/services/WebSocketAPI_service.py +0 -198
- unrealon_sdk/src/clients/python_http/services/__init__.py +0 -0
- unrealon_sdk/src/clients/python_http/services/admin_service.py +0 -125
- unrealon_sdk/src/clients/python_http/services/async_None_service.py +0 -35
- unrealon_sdk/src/clients/python_http/services/async_ParserManagement_service.py +0 -190
- unrealon_sdk/src/clients/python_http/services/async_ProxyManagement_service.py +0 -289
- unrealon_sdk/src/clients/python_http/services/async_SocketLogging_service.py +0 -189
- unrealon_sdk/src/clients/python_http/services/async_SystemHealth_service.py +0 -123
- unrealon_sdk/src/clients/python_http/services/async_WebSocketAPI_service.py +0 -200
- unrealon_sdk/src/clients/python_http/services/async_admin_service.py +0 -125
- unrealon_sdk/src/clients/python_websocket/__init__.py +0 -28
- unrealon_sdk/src/clients/python_websocket/client.py +0 -490
- unrealon_sdk/src/clients/python_websocket/events.py +0 -732
- unrealon_sdk/src/clients/python_websocket/example.py +0 -136
- unrealon_sdk/src/clients/python_websocket/types.py +0 -871
- unrealon_sdk/src/core/__init__.py +0 -64
- unrealon_sdk/src/core/client.py +0 -556
- unrealon_sdk/src/core/config.py +0 -465
- unrealon_sdk/src/core/exceptions.py +0 -239
- unrealon_sdk/src/core/metadata.py +0 -191
- unrealon_sdk/src/core/models.py +0 -142
- unrealon_sdk/src/core/types.py +0 -68
- unrealon_sdk/src/dto/__init__.py +0 -268
- unrealon_sdk/src/dto/authentication.py +0 -108
- unrealon_sdk/src/dto/cache.py +0 -208
- unrealon_sdk/src/dto/common.py +0 -19
- unrealon_sdk/src/dto/concurrency.py +0 -393
- unrealon_sdk/src/dto/events.py +0 -108
- unrealon_sdk/src/dto/health.py +0 -339
- unrealon_sdk/src/dto/load_balancing.py +0 -336
- unrealon_sdk/src/dto/logging.py +0 -230
- unrealon_sdk/src/dto/performance.py +0 -165
- unrealon_sdk/src/dto/rate_limiting.py +0 -295
- unrealon_sdk/src/dto/resource_pooling.py +0 -128
- unrealon_sdk/src/dto/structured_logging.py +0 -112
- unrealon_sdk/src/dto/task_scheduling.py +0 -121
- unrealon_sdk/src/dto/websocket.py +0 -55
- unrealon_sdk/src/enterprise/__init__.py +0 -59
- unrealon_sdk/src/enterprise/authentication.py +0 -401
- unrealon_sdk/src/enterprise/cache_manager.py +0 -578
- unrealon_sdk/src/enterprise/error_recovery.py +0 -494
- unrealon_sdk/src/enterprise/event_system.py +0 -549
- unrealon_sdk/src/enterprise/health_monitor.py +0 -747
- unrealon_sdk/src/enterprise/load_balancer.py +0 -964
- unrealon_sdk/src/enterprise/logging/__init__.py +0 -68
- unrealon_sdk/src/enterprise/logging/cleanup.py +0 -156
- unrealon_sdk/src/enterprise/logging/development.py +0 -744
- unrealon_sdk/src/enterprise/logging/service.py +0 -410
- unrealon_sdk/src/enterprise/multithreading_manager.py +0 -853
- unrealon_sdk/src/enterprise/performance_monitor.py +0 -539
- unrealon_sdk/src/enterprise/proxy_manager.py +0 -696
- unrealon_sdk/src/enterprise/rate_limiter.py +0 -652
- unrealon_sdk/src/enterprise/resource_pool.py +0 -763
- unrealon_sdk/src/enterprise/task_scheduler.py +0 -709
- unrealon_sdk/src/internal/__init__.py +0 -10
- unrealon_sdk/src/internal/command_router.py +0 -497
- unrealon_sdk/src/internal/connection_manager.py +0 -397
- unrealon_sdk/src/internal/http_client.py +0 -446
- unrealon_sdk/src/internal/websocket_client.py +0 -420
- unrealon_sdk/src/provider.py +0 -471
- unrealon_sdk/src/utils.py +0 -234
- /unrealon_browser/{src/cli → cli}/__init__.py +0 -0
- /unrealon_browser/{src/cli → cli}/interactive_mode.py +0 -0
- /unrealon_browser/{src/cli → cli}/main.py +0 -0
- /unrealon_browser/{src/core → core}/__init__.py +0 -0
- /unrealon_browser/{src/dto → dto}/__init__.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/config.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/core.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/dataclasses.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/detection.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/enums.py +0 -0
- /unrealon_browser/{src/dto → dto}/models/statistics.py +0 -0
- /unrealon_browser/{src/managers → managers}/__init__.py +0 -0
- /unrealon_browser/{src/managers → managers}/stealth.py +0 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging Manager - Universal logging for parser developers with Pydantic v2
|
|
3
|
+
|
|
4
|
+
Strict compliance with CRITICAL_REQUIREMENTS.md:
|
|
5
|
+
- No Dict[str, Any] usage
|
|
6
|
+
- Complete type annotations
|
|
7
|
+
- Pydantic v2 models everywhere
|
|
8
|
+
- Custom exception hierarchy
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import logging
|
|
13
|
+
import sys
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Optional, Union, Any
|
|
17
|
+
from pydantic import BaseModel, Field, ConfigDict, field_validator
|
|
18
|
+
from enum import Enum
|
|
19
|
+
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
from rich.logging import RichHandler
|
|
22
|
+
from rich.text import Text
|
|
23
|
+
|
|
24
|
+
from unrealon_rpc.logging import get_logger
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LogLevel(str, Enum):
|
|
28
|
+
"""Log levels for driver logger"""
|
|
29
|
+
DEBUG = "DEBUG"
|
|
30
|
+
INFO = "INFO"
|
|
31
|
+
WARNING = "WARNING"
|
|
32
|
+
ERROR = "ERROR"
|
|
33
|
+
CRITICAL = "CRITICAL"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LoggingConfig(BaseModel):
|
|
37
|
+
"""Logging configuration with strict typing"""
|
|
38
|
+
model_config = ConfigDict(
|
|
39
|
+
validate_assignment=True,
|
|
40
|
+
extra="forbid"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
parser_name: str = Field(
|
|
44
|
+
...,
|
|
45
|
+
min_length=1,
|
|
46
|
+
description="Name of the parser"
|
|
47
|
+
)
|
|
48
|
+
log_dir: Path = Field(
|
|
49
|
+
default_factory=lambda: Path.cwd() / "logs",
|
|
50
|
+
description="Directory for log files"
|
|
51
|
+
)
|
|
52
|
+
console_enabled: bool = Field(
|
|
53
|
+
default=True,
|
|
54
|
+
description="Enable console output"
|
|
55
|
+
)
|
|
56
|
+
file_enabled: bool = Field(
|
|
57
|
+
default=True,
|
|
58
|
+
description="Enable file logging"
|
|
59
|
+
)
|
|
60
|
+
bridge_enabled: bool = Field(
|
|
61
|
+
default=True,
|
|
62
|
+
description="Enable bridge logging"
|
|
63
|
+
)
|
|
64
|
+
console_level: LogLevel = Field(
|
|
65
|
+
default=LogLevel.DEBUG,
|
|
66
|
+
description="Minimum level for console output"
|
|
67
|
+
)
|
|
68
|
+
file_level: LogLevel = Field(
|
|
69
|
+
default=LogLevel.DEBUG,
|
|
70
|
+
description="Minimum level for file logging"
|
|
71
|
+
)
|
|
72
|
+
bridge_level: LogLevel = Field(
|
|
73
|
+
default=LogLevel.DEBUG,
|
|
74
|
+
description="Minimum level for bridge logging"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@field_validator('parser_name')
|
|
78
|
+
@classmethod
|
|
79
|
+
def validate_parser_name(cls, v: str) -> str:
|
|
80
|
+
"""Validate parser name is not empty"""
|
|
81
|
+
if not v.strip():
|
|
82
|
+
raise ValueError("Parser name cannot be empty")
|
|
83
|
+
return v.strip()
|
|
84
|
+
|
|
85
|
+
def model_post_init(self, __context) -> None:
|
|
86
|
+
"""Create log directory if it doesn't exist"""
|
|
87
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class LogContext(BaseModel):
|
|
91
|
+
"""Log context information"""
|
|
92
|
+
model_config = ConfigDict(
|
|
93
|
+
validate_assignment=True,
|
|
94
|
+
extra="forbid"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
session_id: Optional[str] = Field(default=None)
|
|
98
|
+
command_id: Optional[str] = Field(default=None)
|
|
99
|
+
operation: Optional[str] = Field(default=None)
|
|
100
|
+
url: Optional[str] = Field(default=None)
|
|
101
|
+
additional_data: dict[str, str] = Field(default_factory=dict)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class LogEntry(BaseModel):
|
|
105
|
+
"""Log entry with structured data"""
|
|
106
|
+
model_config = ConfigDict(
|
|
107
|
+
validate_assignment=True,
|
|
108
|
+
extra="forbid"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
level: LogLevel = Field(...)
|
|
112
|
+
message: str = Field(..., min_length=1)
|
|
113
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
114
|
+
parser_name: str = Field(..., min_length=1)
|
|
115
|
+
context: LogContext = Field(default_factory=LogContext)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class LoggingManagerError(Exception):
|
|
119
|
+
"""Base exception for logging manager"""
|
|
120
|
+
def __init__(self, message: str, operation: str, details: Optional[dict[str, str]] = None):
|
|
121
|
+
self.message = message
|
|
122
|
+
self.operation = operation
|
|
123
|
+
self.details = details or {}
|
|
124
|
+
super().__init__(message)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class BridgeLoggingError(LoggingManagerError):
|
|
128
|
+
"""Raised when bridge logging fails"""
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class FileLoggingError(LoggingManagerError):
|
|
133
|
+
"""Raised when file logging fails"""
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class LoggingManager:
|
|
138
|
+
"""
|
|
139
|
+
📝 Logging Manager - Universal logging for parser developers
|
|
140
|
+
|
|
141
|
+
Features:
|
|
142
|
+
- Rich console output with colors and formatting
|
|
143
|
+
- File logging to developer-specified directory
|
|
144
|
+
- Bridge logging (sends all logs to Django via bridge)
|
|
145
|
+
- Easy-to-use API with multiple log levels
|
|
146
|
+
- Configurable output formats and destinations
|
|
147
|
+
- Type-safe logging with Pydantic v2
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def __init__(
|
|
151
|
+
self,
|
|
152
|
+
config: LoggingConfig,
|
|
153
|
+
bridge_client: Optional[Any] = None
|
|
154
|
+
):
|
|
155
|
+
self.config = config
|
|
156
|
+
self.bridge_client = bridge_client
|
|
157
|
+
self._context = LogContext()
|
|
158
|
+
|
|
159
|
+
# Setup console
|
|
160
|
+
if self.config.console_enabled:
|
|
161
|
+
self.console = Console()
|
|
162
|
+
else:
|
|
163
|
+
self.console = None
|
|
164
|
+
|
|
165
|
+
# Setup file logger
|
|
166
|
+
self.file_logger: Optional[logging.Logger] = None
|
|
167
|
+
if self.config.file_enabled:
|
|
168
|
+
self._setup_file_logger()
|
|
169
|
+
|
|
170
|
+
# Fallback logger for internal logging
|
|
171
|
+
self.internal_logger = get_logger()
|
|
172
|
+
|
|
173
|
+
def _setup_file_logger(self) -> None:
|
|
174
|
+
"""Setup file logger"""
|
|
175
|
+
log_file = self._get_log_file_path()
|
|
176
|
+
|
|
177
|
+
# Ensure log directory exists
|
|
178
|
+
log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
179
|
+
|
|
180
|
+
# Create file logger
|
|
181
|
+
logger_name = f"driver_{self.config.parser_name}"
|
|
182
|
+
self.file_logger = logging.getLogger(logger_name)
|
|
183
|
+
self.file_logger.setLevel(getattr(logging, self.config.file_level.value))
|
|
184
|
+
|
|
185
|
+
# Remove existing handlers
|
|
186
|
+
for handler in self.file_logger.handlers[:]:
|
|
187
|
+
self.file_logger.removeHandler(handler)
|
|
188
|
+
|
|
189
|
+
# Add file handler
|
|
190
|
+
file_handler = logging.FileHandler(log_file, encoding='utf-8')
|
|
191
|
+
file_handler.setLevel(getattr(logging, self.config.file_level.value))
|
|
192
|
+
|
|
193
|
+
# Format for file logging
|
|
194
|
+
formatter = logging.Formatter(
|
|
195
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
196
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
197
|
+
)
|
|
198
|
+
file_handler.setFormatter(formatter)
|
|
199
|
+
|
|
200
|
+
self.file_logger.addHandler(file_handler)
|
|
201
|
+
self.file_logger.propagate = False
|
|
202
|
+
|
|
203
|
+
# ==========================================
|
|
204
|
+
# CONTEXT MANAGEMENT
|
|
205
|
+
# ==========================================
|
|
206
|
+
|
|
207
|
+
def set_session(self, session_id: str) -> None:
|
|
208
|
+
"""Set current session ID for context"""
|
|
209
|
+
if not session_id.strip():
|
|
210
|
+
raise ValueError("Session ID cannot be empty")
|
|
211
|
+
self._context.session_id = session_id
|
|
212
|
+
|
|
213
|
+
def set_command(self, command_id: str) -> None:
|
|
214
|
+
"""Set current command ID for context"""
|
|
215
|
+
if not command_id.strip():
|
|
216
|
+
raise ValueError("Command ID cannot be empty")
|
|
217
|
+
self._context.command_id = command_id
|
|
218
|
+
|
|
219
|
+
def set_operation(self, operation: str) -> None:
|
|
220
|
+
"""Set current operation for context"""
|
|
221
|
+
if not operation.strip():
|
|
222
|
+
raise ValueError("Operation cannot be empty")
|
|
223
|
+
self._context.operation = operation
|
|
224
|
+
|
|
225
|
+
def set_url(self, url: str) -> None:
|
|
226
|
+
"""Set current URL for context"""
|
|
227
|
+
if not url.strip():
|
|
228
|
+
raise ValueError("URL cannot be empty")
|
|
229
|
+
self._context.url = url
|
|
230
|
+
|
|
231
|
+
def add_context_data(self, key: str, value: str) -> None:
|
|
232
|
+
"""Add additional context data"""
|
|
233
|
+
if not key.strip():
|
|
234
|
+
raise ValueError("Context key cannot be empty")
|
|
235
|
+
self._context.additional_data[key] = str(value)
|
|
236
|
+
|
|
237
|
+
def clear_context(self) -> None:
|
|
238
|
+
"""Clear all context"""
|
|
239
|
+
self._context = LogContext()
|
|
240
|
+
|
|
241
|
+
# ==========================================
|
|
242
|
+
# SYNCHRONOUS LOGGING METHODS
|
|
243
|
+
# ==========================================
|
|
244
|
+
|
|
245
|
+
def debug(self, message: str, **kwargs) -> None:
|
|
246
|
+
"""Log debug message"""
|
|
247
|
+
self._log(LogLevel.DEBUG, message, **kwargs)
|
|
248
|
+
|
|
249
|
+
def info(self, message: str, **kwargs) -> None:
|
|
250
|
+
"""Log info message"""
|
|
251
|
+
self._log(LogLevel.INFO, message, **kwargs)
|
|
252
|
+
|
|
253
|
+
def warning(self, message: str, **kwargs) -> None:
|
|
254
|
+
"""Log warning message"""
|
|
255
|
+
self._log(LogLevel.WARNING, message, **kwargs)
|
|
256
|
+
|
|
257
|
+
def error(self, message: str, **kwargs) -> None:
|
|
258
|
+
"""Log error message"""
|
|
259
|
+
self._log(LogLevel.ERROR, message, **kwargs)
|
|
260
|
+
|
|
261
|
+
def critical(self, message: str, **kwargs) -> None:
|
|
262
|
+
"""Log critical message"""
|
|
263
|
+
self._log(LogLevel.CRITICAL, message, **kwargs)
|
|
264
|
+
|
|
265
|
+
# Aliases
|
|
266
|
+
warn = warning
|
|
267
|
+
|
|
268
|
+
# ==========================================
|
|
269
|
+
# ASYNCHRONOUS LOGGING METHODS
|
|
270
|
+
# ==========================================
|
|
271
|
+
|
|
272
|
+
async def debug_async(self, message: str, **kwargs) -> None:
|
|
273
|
+
"""Log debug message (async)"""
|
|
274
|
+
await self._log_async(LogLevel.DEBUG, message, **kwargs)
|
|
275
|
+
|
|
276
|
+
async def info_async(self, message: str, **kwargs) -> None:
|
|
277
|
+
"""Log info message (async)"""
|
|
278
|
+
await self._log_async(LogLevel.INFO, message, **kwargs)
|
|
279
|
+
|
|
280
|
+
async def warning_async(self, message: str, **kwargs) -> None:
|
|
281
|
+
"""Log warning message (async)"""
|
|
282
|
+
await self._log_async(LogLevel.WARNING, message, **kwargs)
|
|
283
|
+
|
|
284
|
+
async def error_async(self, message: str, **kwargs) -> None:
|
|
285
|
+
"""Log error message (async)"""
|
|
286
|
+
await self._log_async(LogLevel.ERROR, message, **kwargs)
|
|
287
|
+
|
|
288
|
+
async def critical_async(self, message: str, **kwargs) -> None:
|
|
289
|
+
"""Log critical message (async)"""
|
|
290
|
+
await self._log_async(LogLevel.CRITICAL, message, **kwargs)
|
|
291
|
+
|
|
292
|
+
# Aliases
|
|
293
|
+
warn_async = warning_async
|
|
294
|
+
|
|
295
|
+
# ==========================================
|
|
296
|
+
# SPECIALIZED LOGGING METHODS
|
|
297
|
+
# ==========================================
|
|
298
|
+
|
|
299
|
+
def start_operation(self, operation: str, **kwargs) -> None:
|
|
300
|
+
"""Log start of operation"""
|
|
301
|
+
self.set_operation(operation)
|
|
302
|
+
self.info(f"🚀 Starting {operation}", **kwargs)
|
|
303
|
+
|
|
304
|
+
def end_operation(self, operation: str, duration: Optional[float] = None, **kwargs) -> None:
|
|
305
|
+
"""Log end of operation"""
|
|
306
|
+
if duration is not None:
|
|
307
|
+
self.info(f"✅ Completed {operation} in {duration:.2f}s", duration=str(duration), **kwargs)
|
|
308
|
+
else:
|
|
309
|
+
self.info(f"✅ Completed {operation}", **kwargs)
|
|
310
|
+
|
|
311
|
+
def fail_operation(self, operation: str, error: str, **kwargs) -> None:
|
|
312
|
+
"""Log failed operation"""
|
|
313
|
+
self.error(f"❌ Failed {operation}: {error}", error=error, **kwargs)
|
|
314
|
+
|
|
315
|
+
def progress(self, message: str, current: int, total: int, **kwargs) -> None:
|
|
316
|
+
"""Log progress information"""
|
|
317
|
+
if total <= 0:
|
|
318
|
+
raise ValueError("Total must be positive")
|
|
319
|
+
if current < 0:
|
|
320
|
+
raise ValueError("Current must be non-negative")
|
|
321
|
+
|
|
322
|
+
percentage = (current / total * 100)
|
|
323
|
+
self.info(
|
|
324
|
+
f"📊 {message} ({current}/{total} - {percentage:.1f}%)",
|
|
325
|
+
current=str(current),
|
|
326
|
+
total=str(total),
|
|
327
|
+
percentage=f"{percentage:.1f}",
|
|
328
|
+
**kwargs
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def url_access(self, url: str, status: str = "accessing", **kwargs) -> None:
|
|
332
|
+
"""Log URL access"""
|
|
333
|
+
self.set_url(url)
|
|
334
|
+
self.info(f"🌐 {status.title()} URL: {url}", status=status, **kwargs)
|
|
335
|
+
|
|
336
|
+
def data_extracted(self, data_type: str, count: int, **kwargs) -> None:
|
|
337
|
+
"""Log data extraction"""
|
|
338
|
+
if count < 0:
|
|
339
|
+
raise ValueError("Count must be non-negative")
|
|
340
|
+
|
|
341
|
+
self.info(
|
|
342
|
+
f"📦 Extracted {count} {data_type}",
|
|
343
|
+
data_type=data_type,
|
|
344
|
+
count=str(count),
|
|
345
|
+
**kwargs
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# ==========================================
|
|
349
|
+
# INTERNAL LOGGING IMPLEMENTATION
|
|
350
|
+
# ==========================================
|
|
351
|
+
|
|
352
|
+
def _log(self, level: LogLevel, message: str, **kwargs) -> None:
|
|
353
|
+
"""Internal synchronous logging"""
|
|
354
|
+
# Create log entry
|
|
355
|
+
context = self._create_context(**kwargs)
|
|
356
|
+
log_entry = LogEntry(
|
|
357
|
+
level=level,
|
|
358
|
+
message=message,
|
|
359
|
+
parser_name=self.config.parser_name,
|
|
360
|
+
context=context
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# Console output
|
|
364
|
+
if self.config.console_enabled and self._should_log_to_console(level):
|
|
365
|
+
self._log_to_console(log_entry)
|
|
366
|
+
|
|
367
|
+
# File output
|
|
368
|
+
if self.config.file_enabled and self._should_log_to_file(level):
|
|
369
|
+
self._log_to_file(log_entry)
|
|
370
|
+
|
|
371
|
+
# Bridge output (async in background)
|
|
372
|
+
if self.config.bridge_enabled and self.bridge_client and self._should_log_to_bridge(level):
|
|
373
|
+
try:
|
|
374
|
+
loop = asyncio.get_event_loop()
|
|
375
|
+
if loop.is_running():
|
|
376
|
+
loop.create_task(self._log_to_bridge_async(log_entry))
|
|
377
|
+
except RuntimeError:
|
|
378
|
+
# No event loop, skip bridge logging
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
async def _log_async(self, level: LogLevel, message: str, **kwargs) -> None:
|
|
382
|
+
"""Internal asynchronous logging"""
|
|
383
|
+
# Create log entry
|
|
384
|
+
context = self._create_context(**kwargs)
|
|
385
|
+
log_entry = LogEntry(
|
|
386
|
+
level=level,
|
|
387
|
+
message=message,
|
|
388
|
+
parser_name=self.config.parser_name,
|
|
389
|
+
context=context
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# Console output
|
|
393
|
+
if self.config.console_enabled and self._should_log_to_console(level):
|
|
394
|
+
self._log_to_console(log_entry)
|
|
395
|
+
|
|
396
|
+
# File output
|
|
397
|
+
if self.config.file_enabled and self._should_log_to_file(level):
|
|
398
|
+
self._log_to_file(log_entry)
|
|
399
|
+
|
|
400
|
+
# Bridge output
|
|
401
|
+
if self.config.bridge_enabled and self.bridge_client and self._should_log_to_bridge(level):
|
|
402
|
+
await self._log_to_bridge_async(log_entry)
|
|
403
|
+
|
|
404
|
+
def _create_context(self, **kwargs) -> LogContext:
|
|
405
|
+
"""Create log context from current context and kwargs"""
|
|
406
|
+
context_data = self._context.additional_data.copy()
|
|
407
|
+
|
|
408
|
+
# Add kwargs as string values
|
|
409
|
+
for key, value in kwargs.items():
|
|
410
|
+
if key not in ['session_id', 'command_id', 'operation', 'url']:
|
|
411
|
+
context_data[key] = str(value)
|
|
412
|
+
|
|
413
|
+
return LogContext(
|
|
414
|
+
session_id=kwargs.get('session_id') or self._context.session_id,
|
|
415
|
+
command_id=kwargs.get('command_id') or self._context.command_id,
|
|
416
|
+
operation=kwargs.get('operation') or self._context.operation,
|
|
417
|
+
url=kwargs.get('url') or self._context.url,
|
|
418
|
+
additional_data=context_data
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
def _should_log_to_console(self, level: LogLevel) -> bool:
|
|
422
|
+
"""Check if should log to console"""
|
|
423
|
+
level_value = getattr(logging, level.value)
|
|
424
|
+
console_level_value = getattr(logging, self.config.console_level.value)
|
|
425
|
+
return level_value >= console_level_value
|
|
426
|
+
|
|
427
|
+
def _should_log_to_file(self, level: LogLevel) -> bool:
|
|
428
|
+
"""Check if should log to file"""
|
|
429
|
+
level_value = getattr(logging, level.value)
|
|
430
|
+
file_level_value = getattr(logging, self.config.file_level.value)
|
|
431
|
+
return level_value >= file_level_value
|
|
432
|
+
|
|
433
|
+
def _should_log_to_bridge(self, level: LogLevel) -> bool:
|
|
434
|
+
"""Check if should log to bridge"""
|
|
435
|
+
level_value = getattr(logging, level.value)
|
|
436
|
+
bridge_level_value = getattr(logging, self.config.bridge_level.value)
|
|
437
|
+
return level_value >= bridge_level_value
|
|
438
|
+
|
|
439
|
+
def _log_to_console(self, log_entry: LogEntry) -> None:
|
|
440
|
+
"""Log to console with rich formatting"""
|
|
441
|
+
if not self.console:
|
|
442
|
+
# Fallback to print if rich not available
|
|
443
|
+
time_str = log_entry.timestamp.strftime('%H:%M:%S')
|
|
444
|
+
print(f"[{time_str}] {log_entry.level.value} - {log_entry.parser_name} - {log_entry.message}")
|
|
445
|
+
return
|
|
446
|
+
|
|
447
|
+
# Rich console output
|
|
448
|
+
time_str = log_entry.timestamp.strftime('%H:%M:%S')
|
|
449
|
+
|
|
450
|
+
# Color based on level
|
|
451
|
+
level_colors = {
|
|
452
|
+
LogLevel.DEBUG: "dim white",
|
|
453
|
+
LogLevel.INFO: "bright_blue",
|
|
454
|
+
LogLevel.WARNING: "yellow",
|
|
455
|
+
LogLevel.ERROR: "red",
|
|
456
|
+
LogLevel.CRITICAL: "bold red"
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
level_color = level_colors.get(log_entry.level, "white")
|
|
460
|
+
|
|
461
|
+
# Format message
|
|
462
|
+
formatted_message = Text()
|
|
463
|
+
formatted_message.append(f"[{time_str}] ", style="dim")
|
|
464
|
+
formatted_message.append(f"{log_entry.level.value}", style=level_color)
|
|
465
|
+
formatted_message.append(f" - {log_entry.parser_name} - ", style="dim")
|
|
466
|
+
formatted_message.append(log_entry.message, style="white")
|
|
467
|
+
|
|
468
|
+
# Add context if available
|
|
469
|
+
context_parts = []
|
|
470
|
+
if log_entry.context.additional_data:
|
|
471
|
+
for key, value in log_entry.context.additional_data.items():
|
|
472
|
+
context_parts.append(f"{key}={value}")
|
|
473
|
+
|
|
474
|
+
if context_parts:
|
|
475
|
+
formatted_message.append(f" ({', '.join(context_parts)})", style="dim")
|
|
476
|
+
|
|
477
|
+
self.console.print(formatted_message)
|
|
478
|
+
|
|
479
|
+
def _log_to_file(self, log_entry: LogEntry) -> None:
|
|
480
|
+
"""Log to file"""
|
|
481
|
+
if not self.file_logger:
|
|
482
|
+
return
|
|
483
|
+
|
|
484
|
+
try:
|
|
485
|
+
# Create log message with context
|
|
486
|
+
extra_info = ""
|
|
487
|
+
if log_entry.context.additional_data:
|
|
488
|
+
context_parts = [f"{k}={v}" for k, v in log_entry.context.additional_data.items()]
|
|
489
|
+
extra_info = f" - {', '.join(context_parts)}"
|
|
490
|
+
|
|
491
|
+
full_message = f"{log_entry.message}{extra_info}"
|
|
492
|
+
|
|
493
|
+
# Log to file
|
|
494
|
+
log_level = getattr(logging, log_entry.level.value)
|
|
495
|
+
self.file_logger.log(log_level, full_message)
|
|
496
|
+
|
|
497
|
+
except Exception as e:
|
|
498
|
+
raise FileLoggingError(
|
|
499
|
+
message=f"Failed to write to log file: {e}",
|
|
500
|
+
operation="file_logging",
|
|
501
|
+
details={"log_file": str(self._get_log_file_path())}
|
|
502
|
+
) from e
|
|
503
|
+
|
|
504
|
+
async def _log_to_bridge_async(self, log_entry: LogEntry) -> None:
|
|
505
|
+
"""Log to bridge asynchronously"""
|
|
506
|
+
if not self.bridge_client:
|
|
507
|
+
return
|
|
508
|
+
|
|
509
|
+
try:
|
|
510
|
+
# Send to bridge
|
|
511
|
+
await self.bridge_client.send_log(
|
|
512
|
+
level=log_entry.level.value,
|
|
513
|
+
message=log_entry.message,
|
|
514
|
+
session_id=log_entry.context.session_id,
|
|
515
|
+
command_id=log_entry.context.command_id,
|
|
516
|
+
operation=log_entry.context.operation,
|
|
517
|
+
url=log_entry.context.url,
|
|
518
|
+
data=log_entry.context.additional_data if log_entry.context.additional_data else None,
|
|
519
|
+
error_details=log_entry.context.additional_data.get('error') if log_entry.level in [LogLevel.ERROR, LogLevel.CRITICAL] else None
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
except Exception as e:
|
|
523
|
+
# Log bridge error to internal logger but don't raise
|
|
524
|
+
self.internal_logger.warning(f"Failed to send log to bridge: {e}")
|
|
525
|
+
|
|
526
|
+
# ==========================================
|
|
527
|
+
# UTILITY METHODS
|
|
528
|
+
# ==========================================
|
|
529
|
+
|
|
530
|
+
def _get_log_file_path(self) -> Path:
|
|
531
|
+
"""Get path to log file"""
|
|
532
|
+
safe_name = self.config.parser_name.lower().replace(' ', '_')
|
|
533
|
+
return self.config.log_dir / f"{safe_name}.log"
|
|
534
|
+
|
|
535
|
+
def get_log_file_path(self) -> Path:
|
|
536
|
+
"""Get path to log file (public method)"""
|
|
537
|
+
return self._get_log_file_path()
|
|
538
|
+
|
|
539
|
+
def clear_log_file(self) -> None:
|
|
540
|
+
"""Clear log file"""
|
|
541
|
+
log_file = self._get_log_file_path()
|
|
542
|
+
try:
|
|
543
|
+
if log_file.exists():
|
|
544
|
+
log_file.write_text("")
|
|
545
|
+
self.info("Log file cleared")
|
|
546
|
+
except Exception as e:
|
|
547
|
+
raise FileLoggingError(
|
|
548
|
+
message=f"Failed to clear log file: {e}",
|
|
549
|
+
operation="clear_log_file",
|
|
550
|
+
details={"log_file": str(log_file)}
|
|
551
|
+
) from e
|
|
552
|
+
|
|
553
|
+
def get_log_stats(self) -> dict[str, str]:
|
|
554
|
+
"""Get logging statistics"""
|
|
555
|
+
log_file = self._get_log_file_path()
|
|
556
|
+
|
|
557
|
+
return {
|
|
558
|
+
"parser_name": self.config.parser_name,
|
|
559
|
+
"log_dir": str(self.config.log_dir),
|
|
560
|
+
"log_file": str(log_file),
|
|
561
|
+
"log_file_exists": str(log_file.exists()),
|
|
562
|
+
"log_file_size": str(log_file.stat().st_size if log_file.exists() else 0),
|
|
563
|
+
"console_enabled": str(self.config.console_enabled),
|
|
564
|
+
"file_enabled": str(self.config.file_enabled),
|
|
565
|
+
"bridge_enabled": str(self.config.bridge_enabled),
|
|
566
|
+
"session_id": self._context.session_id or "",
|
|
567
|
+
"command_id": self._context.command_id or ""
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
def update_bridge_client(self, bridge_client: Any) -> None:
|
|
571
|
+
"""Update bridge client"""
|
|
572
|
+
self.bridge_client = bridge_client
|
|
573
|
+
|
|
574
|
+
def __repr__(self) -> str:
|
|
575
|
+
return f"<LoggingManager(parser='{self.config.parser_name}', log_dir='{self.config.log_dir}')>"
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
# ==========================================
|
|
579
|
+
# CONVENIENCE FUNCTIONS
|
|
580
|
+
# ==========================================
|
|
581
|
+
|
|
582
|
+
def get_logging_manager(
|
|
583
|
+
parser_name: str,
|
|
584
|
+
log_dir: Optional[Union[str, Path]] = None,
|
|
585
|
+
bridge_client: Optional[Any] = None,
|
|
586
|
+
**kwargs
|
|
587
|
+
) -> LoggingManager:
|
|
588
|
+
"""
|
|
589
|
+
Get a logging manager instance
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
parser_name: Name of the parser
|
|
593
|
+
log_dir: Directory for log files
|
|
594
|
+
bridge_client: Bridge client for sending logs
|
|
595
|
+
**kwargs: Additional logger configuration
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
Configured LoggingManager instance
|
|
599
|
+
"""
|
|
600
|
+
config_data = {
|
|
601
|
+
"parser_name": parser_name,
|
|
602
|
+
**kwargs
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if log_dir is not None:
|
|
606
|
+
config_data["log_dir"] = Path(log_dir)
|
|
607
|
+
|
|
608
|
+
config = LoggingConfig.model_validate(config_data)
|
|
609
|
+
return LoggingManager(config=config, bridge_client=bridge_client)
|