py-wlcli 0.8.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.
- py_wlcli-0.8.0.dist-info/METADATA +139 -0
- py_wlcli-0.8.0.dist-info/RECORD +320 -0
- py_wlcli-0.8.0.dist-info/WHEEL +4 -0
- py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
- py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
- wl_cli/__init__.py +27 -0
- wl_cli/__main__.py +130 -0
- wl_cli/capabilities.py +79 -0
- wl_cli/cc/__init__.py +5 -0
- wl_cli/cc/checks.py +1019 -0
- wl_cli/cc/cli.py +370 -0
- wl_cli/cc/mutate.py +1350 -0
- wl_cli/cli_click.py +156 -0
- wl_cli/commands/__init__.py +84 -0
- wl_cli/commands/base.py +80 -0
- wl_cli/commands/buildcommands/__init__.py +1 -0
- wl_cli/commands/buildcommands/build.py +71 -0
- wl_cli/commands/buildcommands/build_utils.py +47 -0
- wl_cli/commands/buildcommands/builders/__init__.py +18 -0
- wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
- wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
- wl_cli/commands/buildcommands/builders/environment.py +81 -0
- wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
- wl_cli/commands/buildcommands/builders/stubs.py +58 -0
- wl_cli/commands/buildcommands/dist.py +326 -0
- wl_cli/commands/buildcommands/test.py +89 -0
- wl_cli/commands/clean/__init__.py +1 -0
- wl_cli/commands/clean/build.py +151 -0
- wl_cli/commands/clean/clean.py +294 -0
- wl_cli/commands/clean/clean_utils.py +77 -0
- wl_cli/commands/clean/core.py +236 -0
- wl_cli/commands/clean/dead_code/__init__.py +0 -0
- wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
- wl_cli/commands/clean/dead_code/detector.py +161 -0
- wl_cli/commands/clean/dead_code/fixer.py +176 -0
- wl_cli/commands/clean/dead_code/reporter.py +295 -0
- wl_cli/commands/clean/dead_code/utils.py +0 -0
- wl_cli/commands/clean/lfs.py +215 -0
- wl_cli/commands/clean/python.py +81 -0
- wl_cli/commands/clean/rust.py +91 -0
- wl_cli/commands/clean/utils/__init__.py +34 -0
- wl_cli/commands/clean/venv.py +91 -0
- wl_cli/commands/cleanup.py +73 -0
- wl_cli/commands/click_base.py +79 -0
- wl_cli/commands/config/__init__.py +1 -0
- wl_cli/commands/config/config.py +291 -0
- wl_cli/commands/debug/__init__.py +6 -0
- wl_cli/commands/debug/debug.py +309 -0
- wl_cli/commands/debug/debug_cache.py +86 -0
- wl_cli/commands/debug/debug_subagent.py +156 -0
- wl_cli/commands/debug/output_formatter.py +165 -0
- wl_cli/commands/debug/source_hasher.py +92 -0
- wl_cli/commands/format/__init__.py +13 -0
- wl_cli/commands/format/format_cache_adapter.py +125 -0
- wl_cli/commands/format/format_command.py +197 -0
- wl_cli/commands/format/format_coordinator.py +191 -0
- wl_cli/commands/format/format_executor.py +470 -0
- wl_cli/commands/format/format_outcome.py +44 -0
- wl_cli/commands/format/format_path_handler.py +1016 -0
- wl_cli/commands/format/format_progress.py +62 -0
- wl_cli/commands/format/format_reporter.py +31 -0
- wl_cli/commands/format/python_formatter.py +506 -0
- wl_cli/commands/format/rust_formatter.py +106 -0
- wl_cli/commands/gate/__init__.py +1 -0
- wl_cli/commands/gate/gate_command.py +862 -0
- wl_cli/commands/gate/preflight.py +445 -0
- wl_cli/commands/git/__init__.py +1 -0
- wl_cli/commands/git/config.py +169 -0
- wl_cli/commands/git/git_facts.py +60 -0
- wl_cli/commands/git/git_flow.py +86 -0
- wl_cli/commands/git/hook.py +112 -0
- wl_cli/commands/git/policy.py +216 -0
- wl_cli/commands/git/push_update.py +82 -0
- wl_cli/commands/initenv/__init__.py +1 -0
- wl_cli/commands/initenv/common/__init__.py +23 -0
- wl_cli/commands/initenv/common/check_git_bash.py +78 -0
- wl_cli/commands/initenv/common/error_handler.py +68 -0
- wl_cli/commands/initenv/common/exceptions.py +52 -0
- wl_cli/commands/initenv/components/__init__.py +4 -0
- wl_cli/commands/initenv/components/cargo_sync.py +240 -0
- wl_cli/commands/initenv/components/config_manager.py +106 -0
- wl_cli/commands/initenv/components/directory_utils.py +71 -0
- wl_cli/commands/initenv/components/git/__init__.py +9 -0
- wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
- wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
- wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
- wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
- wl_cli/commands/initenv/components/git/git_utility.py +254 -0
- wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
- wl_cli/commands/initenv/components/git_initializer.py +212 -0
- wl_cli/commands/initenv/components/i18n_manager.py +104 -0
- wl_cli/commands/initenv/components/log_manager.py +241 -0
- wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
- wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
- wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
- wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
- wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
- wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
- wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
- wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
- wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
- wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
- wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
- wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
- wl_cli/commands/initenv/components/rust_initializer.py +145 -0
- wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
- wl_cli/commands/initenv/components/symlink_manager.py +52 -0
- wl_cli/commands/initenv/components/venv_manager.py +118 -0
- wl_cli/commands/initenv/initenv.py +169 -0
- wl_cli/commands/initenv/services/__init__.py +11 -0
- wl_cli/commands/initenv/services/environment_initializer.py +288 -0
- wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
- wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
- wl_cli/commands/lfs/README.md +221 -0
- wl_cli/commands/lfs/__init__.py +5 -0
- wl_cli/commands/lfs/cli.py +106 -0
- wl_cli/commands/lfs/config.py +164 -0
- wl_cli/commands/lfs/hook.py +61 -0
- wl_cli/commands/lfs/initializer.py +531 -0
- wl_cli/commands/lfs/manager.py +95 -0
- wl_cli/commands/lfs/policy.py +165 -0
- wl_cli/commands/lfs/watcher.py +294 -0
- wl_cli/commands/lint/__init__.py +1 -0
- wl_cli/commands/lint/lint_command.py +297 -0
- wl_cli/commands/lint/lint_executor.py +409 -0
- wl_cli/commands/lint/lint_reporter.py +579 -0
- wl_cli/commands/lint/lint_run.py +258 -0
- wl_cli/commands/lint/mypy_config.py +90 -0
- wl_cli/commands/registry.py +103 -0
- wl_cli/commands/self/__init__.py +7 -0
- wl_cli/commands/self/common/__init__.py +8 -0
- wl_cli/commands/self/common/capabilities.py +30 -0
- wl_cli/commands/self/common/config.py +27 -0
- wl_cli/commands/self/common/version_cache.py +85 -0
- wl_cli/commands/self/handlers/__init__.py +19 -0
- wl_cli/commands/self/handlers/base_handler.py +28 -0
- wl_cli/commands/self/handlers/install_handler.py +85 -0
- wl_cli/commands/self/handlers/status_handler.py +66 -0
- wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
- wl_cli/commands/self/handlers/update_handler.py +102 -0
- wl_cli/commands/self/handlers/version_handler.py +16 -0
- wl_cli/commands/self/self_command.py +237 -0
- wl_cli/commands/self/services/__init__.py +15 -0
- wl_cli/commands/self/services/installation_manager.py +187 -0
- wl_cli/commands/self/services/local_installer.py +165 -0
- wl_cli/commands/self/services/pypi_updater.py +278 -0
- wl_cli/commands/self/services/windows_update.py +174 -0
- wl_cli/commands/testcommands/__init__.py +8 -0
- wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
- wl_cli/commands/testcommands/batch_processor.py +278 -0
- wl_cli/commands/testcommands/cache_manager.py +1156 -0
- wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
- wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
- wl_cli/commands/testcommands/progress_manager.py +79 -0
- wl_cli/commands/testcommands/python_env_detector.py +207 -0
- wl_cli/commands/testcommands/report_generator.py +101 -0
- wl_cli/commands/testcommands/single_test_executor.py +250 -0
- wl_cli/commands/testcommands/speed_checker.py +118 -0
- wl_cli/commands/testcommands/test_command_builder.py +165 -0
- wl_cli/commands/testcommands/test_commands.py +786 -0
- wl_cli/commands/testcommands/test_reporter.py +181 -0
- wl_cli/commands/testcommands/test_result_handler.py +96 -0
- wl_cli/commands/testcommands/test_runner.py +1343 -0
- wl_cli/commands/testcommands/test_utils.py +197 -0
- wl_cli/commands/testcommands/utils/__init__.py +17 -0
- wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
- wl_cli/commands/testcommands/utils/env_info.py +36 -0
- wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
- wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
- wl_cli/commands/testcommands/utils/project_root.py +61 -0
- wl_cli/commands/update/__init__.py +9 -0
- wl_cli/commands/update/services/__init__.py +21 -0
- wl_cli/commands/update/services/backup_manager.py +207 -0
- wl_cli/commands/update/services/component_manager.py +83 -0
- wl_cli/commands/update/services/conflict_resolver.py +189 -0
- wl_cli/commands/update/services/vendors_updater.py +85 -0
- wl_cli/commands/update/services/version_tracker.py +127 -0
- wl_cli/commands/update/update_command.py +270 -0
- wl_cli/commands/update/utils/__init__.py +3 -0
- wl_cli/commands/version/__init__.py +9 -0
- wl_cli/commands/version/version_command.py +155 -0
- wl_cli/commands/workflow.py +477 -0
- wl_cli/env/__init__.py +5 -0
- wl_cli/env/cli.py +167 -0
- wl_cli/env_status.py +157 -0
- wl_cli/envelope.py +91 -0
- wl_cli/exceptions.py +85 -0
- wl_cli/lib/__init__.py +3 -0
- wl_cli/lib/rust_utils.py +35 -0
- wl_cli/mcp/__init__.py +6 -0
- wl_cli/mcp/capabilities.py +19 -0
- wl_cli/mcp/cli.py +22 -0
- wl_cli/mcp/gitlab.py +553 -0
- wl_cli/mcp/k8s.py +344 -0
- wl_cli/mcp/protocol.py +159 -0
- wl_cli/platform.py +139 -0
- wl_cli/quality/__init__.py +18 -0
- wl_cli/utils/__init__.py +7 -0
- wl_cli/utils/cache.py +491 -0
- wl_cli/utils/cache_ledger.py +355 -0
- wl_cli/utils/cache_store.py +546 -0
- wl_cli/utils/config/__init__.py +27 -0
- wl_cli/utils/config/config_loader.py +163 -0
- wl_cli/utils/config/core.py +862 -0
- wl_cli/utils/config/defaults.py +142 -0
- wl_cli/utils/config/env_exporter.py +86 -0
- wl_cli/utils/config/env_parser.py +108 -0
- wl_cli/utils/config/validation.py +280 -0
- wl_cli/utils/error_handler.py +165 -0
- wl_cli/utils/file_copier.py +56 -0
- wl_cli/utils/file_operations.py +506 -0
- wl_cli/utils/language_utils.py +122 -0
- wl_cli/utils/lazy_import.py +240 -0
- wl_cli/utils/log_levels.py +35 -0
- wl_cli/utils/log_rotators.py +74 -0
- wl_cli/utils/logging.py +458 -0
- wl_cli/utils/output_context.py +142 -0
- wl_cli/utils/performance/__init__.py +1 -0
- wl_cli/utils/performance/decorators.py +111 -0
- wl_cli/utils/performance/exporter.py +38 -0
- wl_cli/utils/performance/models.py +16 -0
- wl_cli/utils/performance/monitor.py +161 -0
- wl_cli/utils/performance_monitor.py +32 -0
- wl_cli/utils/platform_adapter.py +131 -0
- wl_cli/utils/pre_commit.py +266 -0
- wl_cli/utils/pre_commit_config.py +72 -0
- wl_cli/utils/root.py +521 -0
- wl_cli/utils/root_fallback.py +256 -0
- wl_cli/utils/run_evidence.py +236 -0
- wl_cli/utils/structured_logger.py +18 -0
- wl_cli/utils/structured_logging/__init__.py +19 -0
- wl_cli/utils/structured_logging/config.py +337 -0
- wl_cli/utils/structured_logging/core.py +177 -0
- wl_cli/utils/structured_logging/env.py +56 -0
- wl_cli/utils/structured_logging/filtering.py +75 -0
- wl_cli/utils/structured_logging/filters.py +55 -0
- wl_cli/utils/structured_logging/formatters.py +251 -0
- wl_cli/utils/structured_logging/handlers.py +308 -0
- wl_cli/utils/structured_logging/logger.py +354 -0
- wl_cli/utils/subprocess.py +71 -0
- wl_cli/utils/subprocess_utils.py +260 -0
- wl_cli/utils/test_utils/__init__.py +5 -0
- wl_cli/utils/test_utils/test_filter.py +36 -0
- wl_cli/utils/uv_run.py +79 -0
- wl_cli/utils/uv_tool.py +117 -0
- wl_cli/utils/uv_tree.py +71 -0
- wl_cli/utils/uv_workspace.py +174 -0
- wl_cli/utils/version/__init__.py +15 -0
- wl_cli/utils/version/pypi_version_checker.py +95 -0
- wl_cli/utils/version/version_comparator.py +133 -0
- wl_cli/utils/version/version_detectors.py +312 -0
- wl_cli/utils/version/version_service.py +131 -0
- wl_cli/utils/version/version_updater.py +198 -0
- wl_cli/utils/wl_dir_updater.py +1440 -0
- wl_cli/utils/workspace_detector/__init__.py +28 -0
- wl_cli/utils/workspace_detector/core.py +161 -0
- wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
- wl_cli/utils/workspace_detector/exceptions.py +5 -0
- wl_cli/utils/workspace_detector/types.py +17 -0
- wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
- wl_cli/vendors/config/.codespellrc +2 -0
- wl_cli/vendors/config/dependencies.toml +10 -0
- wl_cli/vendors/config/mypy.ini +62 -0
- wl_cli/vendors/config/pyproject_defaults.toml +28 -0
- wl_cli/vendors/config/template_config.toml +24 -0
- wl_cli/vendors/config/tool_configs.toml +76 -0
- wl_cli/vendors/git/.gitignore +122 -0
- wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
- wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/post-update.sample +8 -0
- wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/pre-commit +45 -0
- wl_cli/vendors/hooks/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/pre-push.sample +60 -0
- wl_cli/vendors/hooks/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
- wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
- wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
- wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
- wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
- wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
- wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
- wl_cli/vendors/hooks/unix/post-update.sample +8 -0
- wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
- wl_cli/vendors/hooks/unix/pre-commit +190 -0
- wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
- wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
- wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
- wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
- wl_cli/vendors/hooks/unix/pre-push +417 -0
- wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
- wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
- wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
- wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
- wl_cli/vendors/hooks/update.sample +128 -0
- wl_cli/vendors/hooks/win/work.bat +63 -0
- wl_cli/vendors/project/.gitignore +90 -0
- wl_cli/vendors/project/.python-version +1 -0
- wl_cli/vendors/project/LICENSE +21 -0
- wl_cli/vendors/project/README.md +0 -0
- wl_cli/vendors/project/pyproject.toml +27 -0
- wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
- wl_cli/vendors/project/pyproject_workspace.toml +14 -0
- wl_cli/vendors/readme/README.md +120 -0
- wl_cli/vendors/rust/Cargo.toml +14 -0
- wl_cli/vendors/rust/lib/__init__.py +3 -0
- wl_cli/vendors/rust/lib/rust_utils.py +31 -0
- wl_cli/vendors/rust/src/lib.rs +13 -0
- wl_cli/vendors/rust/src/main.rs +3 -0
- wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
- wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
- wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
- wl_cli/vendors/sop_doc/python-sop.md +342 -0
- wl_cli/vendors/sop_doc/rust-readme.md +219 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Log formatters for WL Commands."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LogFormatter:
|
|
11
|
+
"""Handles formatting of log messages."""
|
|
12
|
+
|
|
13
|
+
# ANSI color codes for console output
|
|
14
|
+
_COLORS = {
|
|
15
|
+
"DEBUG": "\033[34m", # Blue
|
|
16
|
+
"INFO": "\033[32m", # Green
|
|
17
|
+
"WARNING": "\033[33m", # Yellow
|
|
18
|
+
"ERROR": "\033[31m", # Red
|
|
19
|
+
"CRITICAL": "\033[91m", # Bright Red
|
|
20
|
+
"RESET": "\033[0m", # Reset color
|
|
21
|
+
"TIMESTAMP": "\033[90m", # Gray for timestamp
|
|
22
|
+
"LOGGER": "\033[36m", # Cyan for logger name
|
|
23
|
+
"MESSAGE": "\033[37m", # White for message
|
|
24
|
+
"CONTEXT": "\033[35m", # Magenta for context
|
|
25
|
+
"MODULE": "\033[33m", # Yellow for module
|
|
26
|
+
"FUNCTION": "\033[32m", # Green for function
|
|
27
|
+
"LINE": "\033[34m", # Blue for line number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self, time_format: str = "%Y-%m-%d %H:%M:%S", enable_colors: bool = True
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Initialize log formatter.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
time_format: Time format string for timestamp formatting.
|
|
38
|
+
enable_colors: Whether to enable colors in console output.
|
|
39
|
+
"""
|
|
40
|
+
self.time_format = time_format
|
|
41
|
+
self.enable_colors = enable_colors # Always enable colors for testing
|
|
42
|
+
self.custom_formatters: dict[str, Callable[[dict[str, Any]], str]] = {}
|
|
43
|
+
|
|
44
|
+
def format_human_log(self, record: dict[str, Any]) -> str:
|
|
45
|
+
"""Format log record for human-readable file output without colors."""
|
|
46
|
+
# Extract log fields
|
|
47
|
+
level_name = record.get("level_name", "UNKNOWN")
|
|
48
|
+
logger_name = record.get("logger_name", "unknown")
|
|
49
|
+
message = record.get("message", "")
|
|
50
|
+
timestamp = record.get("timestamp", "")
|
|
51
|
+
|
|
52
|
+
# Format timestamp for better readability
|
|
53
|
+
formatted_time = self._format_timestamp(timestamp)
|
|
54
|
+
|
|
55
|
+
# Build base log message
|
|
56
|
+
log_parts = [f"[{formatted_time}] {level_name} {logger_name} - {message}"]
|
|
57
|
+
|
|
58
|
+
# Add context information if available and log level is DEBUG
|
|
59
|
+
context = record.get("context", {})
|
|
60
|
+
if context and record.get("level") == 10: # Only show context in DEBUG mode
|
|
61
|
+
log_parts.append(f" Context: {self._format_context(context)}")
|
|
62
|
+
|
|
63
|
+
# Add module information if available and log level is DEBUG
|
|
64
|
+
if (
|
|
65
|
+
"module" in record and record.get("level") == 10
|
|
66
|
+
): # Only show module info in DEBUG mode
|
|
67
|
+
module_info = f" Module: {record['module']}"
|
|
68
|
+
if "function" in record:
|
|
69
|
+
module_info += f".{record['function']}"
|
|
70
|
+
if "line" in record:
|
|
71
|
+
module_info += f":{record['line']}"
|
|
72
|
+
log_parts.append(module_info)
|
|
73
|
+
|
|
74
|
+
return "\n".join(log_parts)
|
|
75
|
+
|
|
76
|
+
def format_console_log(self, record: dict[str, Any]) -> str:
|
|
77
|
+
"""Format log record for console output with colors and readable format."""
|
|
78
|
+
# Extract log fields
|
|
79
|
+
level_name = record.get("level_name", "UNKNOWN")
|
|
80
|
+
logger_name = record.get("logger_name", "unknown")
|
|
81
|
+
message = record.get("message", "")
|
|
82
|
+
timestamp = record.get("timestamp", "")
|
|
83
|
+
|
|
84
|
+
# Format timestamp
|
|
85
|
+
formatted_time = self._format_timestamp(timestamp)
|
|
86
|
+
|
|
87
|
+
# Get color codes if enabled
|
|
88
|
+
if self.enable_colors:
|
|
89
|
+
color = self._COLORS.get(level_name, self._COLORS["RESET"])
|
|
90
|
+
reset = self._COLORS["RESET"]
|
|
91
|
+
timestamp_color = self._COLORS["TIMESTAMP"]
|
|
92
|
+
logger_color = self._COLORS["LOGGER"]
|
|
93
|
+
message_color = self._COLORS["MESSAGE"]
|
|
94
|
+
else:
|
|
95
|
+
color = reset = timestamp_color = logger_color = message_color = ""
|
|
96
|
+
|
|
97
|
+
# Build base console log message
|
|
98
|
+
console_parts = [
|
|
99
|
+
f"{timestamp_color}[{formatted_time}]{reset} {color}{level_name}{reset} {logger_color}{logger_name}{reset} - {message_color}{message}{reset}"
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
# Add context information if available and log level is DEBUG
|
|
103
|
+
context = record.get("context", {})
|
|
104
|
+
if context and record.get("level") == 10: # Only show context in DEBUG mode
|
|
105
|
+
if self.enable_colors:
|
|
106
|
+
context_color = self._COLORS["CONTEXT"]
|
|
107
|
+
console_parts.append(
|
|
108
|
+
f" {context_color}Context:{reset} {self._format_context(context)}"
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
console_parts.append(f" Context: {self._format_context(context)}")
|
|
112
|
+
|
|
113
|
+
# Add module information if available and log level is DEBUG
|
|
114
|
+
if (
|
|
115
|
+
"module" in record and record.get("level") == 10
|
|
116
|
+
): # Only show module info in DEBUG mode
|
|
117
|
+
if self.enable_colors:
|
|
118
|
+
module_color = self._COLORS["MODULE"]
|
|
119
|
+
function_color = self._COLORS["FUNCTION"]
|
|
120
|
+
line_color = self._COLORS["LINE"]
|
|
121
|
+
module_info = f" {module_color}Module:{reset} {record['module']}"
|
|
122
|
+
if "function" in record:
|
|
123
|
+
module_info += f".{function_color}{record['function']}{reset}"
|
|
124
|
+
if "line" in record:
|
|
125
|
+
module_info += f":{line_color}{record['line']}{reset}"
|
|
126
|
+
else:
|
|
127
|
+
module_info = f" Module: {record['module']}"
|
|
128
|
+
if "function" in record:
|
|
129
|
+
module_info += f".{record['function']}"
|
|
130
|
+
if "line" in record:
|
|
131
|
+
module_info += f":{record['line']}"
|
|
132
|
+
console_parts.append(module_info)
|
|
133
|
+
|
|
134
|
+
return "\n".join(console_parts)
|
|
135
|
+
|
|
136
|
+
def format_console_log_compact(self, record: dict[str, Any]) -> str:
|
|
137
|
+
"""Format log record for console output with compact format for production environments."""
|
|
138
|
+
# Extract log fields
|
|
139
|
+
level_name = record.get("level_name", "UNKNOWN")
|
|
140
|
+
message = record.get("message", "")
|
|
141
|
+
timestamp = record.get("timestamp", "")
|
|
142
|
+
|
|
143
|
+
# Format timestamp with compact format
|
|
144
|
+
formatted_time = self._format_timestamp(timestamp)
|
|
145
|
+
|
|
146
|
+
# Get color codes if enabled
|
|
147
|
+
if self.enable_colors:
|
|
148
|
+
color = self._COLORS.get(level_name, self._COLORS["RESET"])
|
|
149
|
+
reset = self._COLORS["RESET"]
|
|
150
|
+
timestamp_color = self._COLORS["TIMESTAMP"]
|
|
151
|
+
message_color = self._COLORS["MESSAGE"]
|
|
152
|
+
else:
|
|
153
|
+
color = reset = timestamp_color = message_color = ""
|
|
154
|
+
|
|
155
|
+
# Build compact console log message
|
|
156
|
+
# Only include timestamp, level, and message for production
|
|
157
|
+
return f"{timestamp_color}[{formatted_time}]{reset} {color}{level_name[:4]}{reset} - {message_color}{message}{reset}"
|
|
158
|
+
|
|
159
|
+
def format_json_log(self, record: dict[str, Any]) -> str:
|
|
160
|
+
"""Format log record as JSON."""
|
|
161
|
+
try:
|
|
162
|
+
# First try without default to detect non-serializable fields
|
|
163
|
+
return json.dumps(record)
|
|
164
|
+
except (TypeError, ValueError):
|
|
165
|
+
# Fallback if JSON serialization fails
|
|
166
|
+
simple_record = {
|
|
167
|
+
"logger_name": record.get("logger_name", "unknown"),
|
|
168
|
+
"level": record.get("level", 0),
|
|
169
|
+
"level_name": record.get("level_name", "UNKNOWN"),
|
|
170
|
+
"timestamp": record.get("timestamp", ""),
|
|
171
|
+
"message": record.get("message", ""),
|
|
172
|
+
}
|
|
173
|
+
return json.dumps(simple_record)
|
|
174
|
+
|
|
175
|
+
def _format_timestamp(self, timestamp: str) -> str:
|
|
176
|
+
"""Format timestamp for better readability."""
|
|
177
|
+
if not timestamp:
|
|
178
|
+
return ""
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
dt = datetime.datetime.fromisoformat(timestamp)
|
|
182
|
+
return dt.strftime(self.time_format)
|
|
183
|
+
except ValueError:
|
|
184
|
+
return timestamp
|
|
185
|
+
|
|
186
|
+
def _format_context(self, context: dict[str, Any]) -> str:
|
|
187
|
+
"""Format context information for human-readable output."""
|
|
188
|
+
if not context:
|
|
189
|
+
return ""
|
|
190
|
+
|
|
191
|
+
context_items = []
|
|
192
|
+
for key, value in context.items():
|
|
193
|
+
if isinstance(value, dict | list):
|
|
194
|
+
try:
|
|
195
|
+
value_str = json.dumps(value, ensure_ascii=False, default=str)
|
|
196
|
+
except Exception:
|
|
197
|
+
value_str = str(value)
|
|
198
|
+
else:
|
|
199
|
+
value_str = str(value)
|
|
200
|
+
context_items.append(f"{key}={value_str}")
|
|
201
|
+
|
|
202
|
+
return ", ".join(context_items)
|
|
203
|
+
|
|
204
|
+
def _json_default(self, obj: Any) -> Any:
|
|
205
|
+
"""Default JSON encoder for complex objects."""
|
|
206
|
+
if isinstance(obj, datetime.datetime):
|
|
207
|
+
return obj.isoformat()
|
|
208
|
+
elif isinstance(obj, set | frozenset):
|
|
209
|
+
return list(obj)
|
|
210
|
+
else:
|
|
211
|
+
try:
|
|
212
|
+
if hasattr(obj, "__dict__"):
|
|
213
|
+
return {
|
|
214
|
+
k: v for k, v in obj.__dict__.items() if not k.startswith("_")
|
|
215
|
+
}
|
|
216
|
+
else:
|
|
217
|
+
return str(obj)
|
|
218
|
+
except Exception:
|
|
219
|
+
return str(obj)
|
|
220
|
+
|
|
221
|
+
def register_formatter(
|
|
222
|
+
self, name: str, formatter: Callable[[dict[str, Any]], str]
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Register a custom formatter."""
|
|
225
|
+
self.custom_formatters[name] = formatter
|
|
226
|
+
|
|
227
|
+
def format(self, record: dict[str, Any], format_type: str) -> str:
|
|
228
|
+
"""Format log record using specified format type."""
|
|
229
|
+
if format_type in self.custom_formatters:
|
|
230
|
+
return self.custom_formatters[format_type](record)
|
|
231
|
+
elif format_type == "human":
|
|
232
|
+
return self.format_human_log(record)
|
|
233
|
+
elif format_type == "console":
|
|
234
|
+
return self.format_console_log(record)
|
|
235
|
+
elif format_type == "json":
|
|
236
|
+
return self.format_json_log(record)
|
|
237
|
+
else:
|
|
238
|
+
# Default to JSON format
|
|
239
|
+
return self.format_json_log(record)
|
|
240
|
+
|
|
241
|
+
def set_time_format(self, time_format: str) -> None:
|
|
242
|
+
"""Set custom time format."""
|
|
243
|
+
self.time_format = time_format
|
|
244
|
+
|
|
245
|
+
def set_colors(self, enable: bool) -> None:
|
|
246
|
+
"""Enable or disable colors."""
|
|
247
|
+
self.enable_colors = enable and sys.stdout.isatty()
|
|
248
|
+
|
|
249
|
+
def get_color_code(self, color_name: str) -> str:
|
|
250
|
+
"""Get color code by name."""
|
|
251
|
+
return self._COLORS.get(color_name, "")
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""Log handlers for WL Commands."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..log_rotators import LogRotator
|
|
9
|
+
from ..platform_adapter import PlatformAdapter
|
|
10
|
+
from .env import is_pytest, is_test_environment
|
|
11
|
+
from .formatters import LogFormatter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LogHandler:
|
|
15
|
+
"""Handles writing log messages to various outputs."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
log_file: str | None,
|
|
20
|
+
log_rotator: LogRotator | None,
|
|
21
|
+
enable_console: bool = True,
|
|
22
|
+
console_format: str = "colored",
|
|
23
|
+
log_file_format: str = "json",
|
|
24
|
+
buffer_size: int = 100,
|
|
25
|
+
async_write: bool = False,
|
|
26
|
+
flush_interval: float = 5.0,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Initialize log handler.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
log_file (Optional[str]): Path to log file.
|
|
33
|
+
log_rotator (Optional[LogRotator]): Log rotator instance.
|
|
34
|
+
enable_console (bool): Whether to enable console output. Defaults to True.
|
|
35
|
+
console_format (str): Console output format ('colored' or 'json'). Defaults to "colored".
|
|
36
|
+
log_file_format (str): Log file format ('human' or 'json'). Defaults to "json".
|
|
37
|
+
buffer_size (int): Log buffer size for batch writing. Defaults to 100.
|
|
38
|
+
async_write (bool): Whether to write logs asynchronously. Defaults to False.
|
|
39
|
+
flush_interval (float): Interval in seconds to flush buffer for async writing. Defaults to 5.0.
|
|
40
|
+
"""
|
|
41
|
+
self.log_file = log_file
|
|
42
|
+
self.log_rotator = log_rotator
|
|
43
|
+
self.console_format = console_format
|
|
44
|
+
self.log_file_format = log_file_format
|
|
45
|
+
self.formatter = LogFormatter()
|
|
46
|
+
self.buffer_size = buffer_size
|
|
47
|
+
|
|
48
|
+
# Disable async write in test environment to ensure immediate file writing
|
|
49
|
+
# - When running through wl test, NON_INTERACTIVE is always set
|
|
50
|
+
# - Disable for any pytest environment, including pytest-xdist workers
|
|
51
|
+
if is_test_environment():
|
|
52
|
+
self.async_write = False
|
|
53
|
+
else:
|
|
54
|
+
self.async_write = async_write
|
|
55
|
+
|
|
56
|
+
# Keep enable_console as provided, even in test environment
|
|
57
|
+
self.enable_console = enable_console
|
|
58
|
+
|
|
59
|
+
self.flush_interval = flush_interval
|
|
60
|
+
self.buffer: list[dict[str, Any]] = []
|
|
61
|
+
self.buffer_lock = threading.Lock()
|
|
62
|
+
self.last_flush_time = time.time()
|
|
63
|
+
self._shutdown = False
|
|
64
|
+
|
|
65
|
+
# Start background flush thread if async write is enabled
|
|
66
|
+
if self.async_write:
|
|
67
|
+
self.flush_thread = threading.Thread(
|
|
68
|
+
target=self._background_flush, daemon=True
|
|
69
|
+
)
|
|
70
|
+
self.flush_thread.start()
|
|
71
|
+
|
|
72
|
+
def write_log(self, record: dict[str, Any]) -> None:
|
|
73
|
+
"""Write log record to appropriate outputs."""
|
|
74
|
+
if self.async_write:
|
|
75
|
+
# Add to buffer for asynchronous processing
|
|
76
|
+
with self.buffer_lock:
|
|
77
|
+
self.buffer.append(record)
|
|
78
|
+
current_time = time.time()
|
|
79
|
+
# Flush buffer if it's full or if it's time to flush
|
|
80
|
+
if (
|
|
81
|
+
len(self.buffer) >= self.buffer_size
|
|
82
|
+
or current_time - self.last_flush_time >= self.flush_interval
|
|
83
|
+
):
|
|
84
|
+
self._flush_buffer()
|
|
85
|
+
self.last_flush_time = current_time
|
|
86
|
+
else:
|
|
87
|
+
# Process synchronously
|
|
88
|
+
self._process_log(record)
|
|
89
|
+
|
|
90
|
+
def _background_flush(self) -> None:
|
|
91
|
+
"""Background thread for flushing buffer at regular intervals."""
|
|
92
|
+
while not self._shutdown:
|
|
93
|
+
try:
|
|
94
|
+
time.sleep(self.flush_interval)
|
|
95
|
+
self._flush_buffer()
|
|
96
|
+
except Exception:
|
|
97
|
+
# Ignore exceptions to keep the thread running
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
def _process_log(self, record: dict[str, Any]) -> None:
|
|
101
|
+
"""Process log record synchronously."""
|
|
102
|
+
# Write to file if specified
|
|
103
|
+
if self.log_file:
|
|
104
|
+
self._write_to_file(record)
|
|
105
|
+
|
|
106
|
+
# Write to console only if enabled
|
|
107
|
+
# If enable_console is True, always write - respect the configured setting
|
|
108
|
+
# Non-interactive skipping is only handled when enable_console is not explicitly set
|
|
109
|
+
if self.enable_console:
|
|
110
|
+
# Skip console writing if we're in a non-interactive environment
|
|
111
|
+
# but not in test environment (tests need console output for assertions)
|
|
112
|
+
import os
|
|
113
|
+
|
|
114
|
+
if not os.environ.get("NON_INTERACTIVE") or is_pytest():
|
|
115
|
+
self._write_to_console(record)
|
|
116
|
+
|
|
117
|
+
def _write_to_file(self, record: dict[str, Any]) -> None:
|
|
118
|
+
"""Write log record to file."""
|
|
119
|
+
try:
|
|
120
|
+
import os
|
|
121
|
+
from pathlib import Path
|
|
122
|
+
|
|
123
|
+
# Format message based on file format
|
|
124
|
+
if self.log_file_format == "human":
|
|
125
|
+
# For human format, we need to handle JSON errors for tests
|
|
126
|
+
try:
|
|
127
|
+
# Try to format as human log directly
|
|
128
|
+
message = self.formatter.format_human_log(record)
|
|
129
|
+
except Exception:
|
|
130
|
+
# If formatting fails, use JSON format as fallback
|
|
131
|
+
message = self.formatter.format_json_log(record)
|
|
132
|
+
else:
|
|
133
|
+
message = self.formatter.format_json_log(record)
|
|
134
|
+
|
|
135
|
+
# Create parent directory if it doesn't exist
|
|
136
|
+
if self.log_file:
|
|
137
|
+
log_path = Path(self.log_file)
|
|
138
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
139
|
+
|
|
140
|
+
# Handle log rotation
|
|
141
|
+
if self.log_rotator and self.log_rotator.should_rotate():
|
|
142
|
+
self.log_rotator.do_rotate()
|
|
143
|
+
|
|
144
|
+
# Write to file with Windows file locking handling
|
|
145
|
+
if PlatformAdapter.is_windows() and self.log_file:
|
|
146
|
+
# Handle Windows file locking issues
|
|
147
|
+
import time
|
|
148
|
+
|
|
149
|
+
max_attempts = 3
|
|
150
|
+
for attempt in range(max_attempts):
|
|
151
|
+
try:
|
|
152
|
+
with open(
|
|
153
|
+
self.log_file, "a", encoding="utf-8", newline=""
|
|
154
|
+
) as f:
|
|
155
|
+
f.write(message + "\n")
|
|
156
|
+
f.flush() # Ensure immediate write
|
|
157
|
+
os.fsync(f.fileno()) # Ensure file is written to disk
|
|
158
|
+
break
|
|
159
|
+
except PermissionError:
|
|
160
|
+
if attempt < max_attempts - 1:
|
|
161
|
+
time.sleep(0.5)
|
|
162
|
+
continue
|
|
163
|
+
else:
|
|
164
|
+
raise
|
|
165
|
+
elif self.log_file:
|
|
166
|
+
# Write to file on non-Windows platforms
|
|
167
|
+
with open(self.log_file, "a", encoding="utf-8") as f:
|
|
168
|
+
f.write(message + "\n")
|
|
169
|
+
f.flush() # Ensure immediate write
|
|
170
|
+
os.fsync(f.fileno()) # Ensure file is written to disk
|
|
171
|
+
except Exception as e:
|
|
172
|
+
# For debugging, print the exception
|
|
173
|
+
import sys
|
|
174
|
+
|
|
175
|
+
print(f"File write error: {e}", file=sys.stderr)
|
|
176
|
+
# Ignore file write errors
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
def _write_to_console(self, record: dict[str, Any]) -> None:
|
|
180
|
+
"""Write log record to console."""
|
|
181
|
+
try:
|
|
182
|
+
import os
|
|
183
|
+
|
|
184
|
+
# Check if we're in test environment
|
|
185
|
+
is_test = is_pytest()
|
|
186
|
+
|
|
187
|
+
# In test environment with json format, still use json to preserve test behavior
|
|
188
|
+
if is_test and self.console_format == "json":
|
|
189
|
+
message = self.formatter.format_json_log(record)
|
|
190
|
+
elif is_test:
|
|
191
|
+
# In test environment with other formats, use compact format to improve performance
|
|
192
|
+
message = self.formatter.format_console_log_compact(record)
|
|
193
|
+
else:
|
|
194
|
+
# Check if we're in production environment
|
|
195
|
+
is_production = False
|
|
196
|
+
# Use environment variable as fast path
|
|
197
|
+
env = os.environ.get("WL_ENVIRONMENT", "development")
|
|
198
|
+
is_production = env == "production"
|
|
199
|
+
# Fallback to config only if environment variable not set
|
|
200
|
+
if not os.environ.get("WL_ENVIRONMENT"):
|
|
201
|
+
try:
|
|
202
|
+
from wl_cli.utils.config import get_config
|
|
203
|
+
|
|
204
|
+
env = get_config("environment", "development")
|
|
205
|
+
is_production = env == "production"
|
|
206
|
+
except Exception:
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
# Force compact format for production
|
|
210
|
+
if is_production:
|
|
211
|
+
message = self.formatter.format_console_log_compact(record)
|
|
212
|
+
else:
|
|
213
|
+
# Format message based on console format
|
|
214
|
+
if self.console_format == "colored":
|
|
215
|
+
# For colored format, we need to handle JSON errors for tests
|
|
216
|
+
try:
|
|
217
|
+
# Try to format as colored log directly
|
|
218
|
+
message = self.formatter.format_console_log(record)
|
|
219
|
+
except Exception:
|
|
220
|
+
# If formatting fails, use JSON format as fallback
|
|
221
|
+
message = self.formatter.format_json_log(record)
|
|
222
|
+
elif self.console_format == "compact":
|
|
223
|
+
message = self.formatter.format_console_log_compact(record)
|
|
224
|
+
else:
|
|
225
|
+
message = self.formatter.format_json_log(record)
|
|
226
|
+
|
|
227
|
+
# Determine output stream based on log level
|
|
228
|
+
level = record.get("level", 0)
|
|
229
|
+
level_name = record.get("level_name", "").upper()
|
|
230
|
+
# Use level_name for determining output stream if level is not available
|
|
231
|
+
if level_name in ["ERROR", "CRITICAL"]:
|
|
232
|
+
target = sys.stderr
|
|
233
|
+
else:
|
|
234
|
+
target = sys.stdout if level < 40 else sys.stderr # < ERROR level
|
|
235
|
+
|
|
236
|
+
# Write to console
|
|
237
|
+
print(message, file=target)
|
|
238
|
+
except Exception:
|
|
239
|
+
# Ignore console write errors
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
def _flush_buffer(self) -> None:
|
|
243
|
+
"""Flush log buffer to outputs."""
|
|
244
|
+
with self.buffer_lock:
|
|
245
|
+
if not self.buffer:
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
# Copy buffer and clear it
|
|
249
|
+
records = self.buffer.copy()
|
|
250
|
+
self.buffer.clear()
|
|
251
|
+
|
|
252
|
+
# Process records in a separate thread
|
|
253
|
+
def process_records():
|
|
254
|
+
for record in records:
|
|
255
|
+
self._process_log(record)
|
|
256
|
+
|
|
257
|
+
# Start processing thread
|
|
258
|
+
thread = threading.Thread(target=process_records)
|
|
259
|
+
thread.daemon = True
|
|
260
|
+
thread.start()
|
|
261
|
+
|
|
262
|
+
def flush(self) -> None:
|
|
263
|
+
"""Flush any buffered logs."""
|
|
264
|
+
if self.async_write:
|
|
265
|
+
self._flush_buffer()
|
|
266
|
+
|
|
267
|
+
def close(self) -> None:
|
|
268
|
+
"""Close log handler and flush any buffered logs."""
|
|
269
|
+
self._shutdown = True
|
|
270
|
+
# Flush any remaining logs
|
|
271
|
+
self.flush()
|
|
272
|
+
# Wait for background thread to finish if it exists
|
|
273
|
+
if hasattr(self, "flush_thread") and self.flush_thread.is_alive():
|
|
274
|
+
self.flush_thread.join(timeout=2.0)
|
|
275
|
+
|
|
276
|
+
def set_console_format(self, format_type: str) -> None:
|
|
277
|
+
"""Set console output format.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
format_type (str): Console output format ('colored', 'compact', or 'json').
|
|
281
|
+
"""
|
|
282
|
+
self.console_format = format_type
|
|
283
|
+
|
|
284
|
+
def set_file_format(self, format_type: str) -> None:
|
|
285
|
+
"""Set file output format.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
format_type (str): File output format ('human' or 'json').
|
|
289
|
+
"""
|
|
290
|
+
self.log_file_format = format_type
|
|
291
|
+
|
|
292
|
+
def set_buffer_size(self, size: int) -> None:
|
|
293
|
+
"""Set log buffer size.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
size (int): Buffer size for batch writing.
|
|
297
|
+
"""
|
|
298
|
+
self.buffer_size = size
|
|
299
|
+
|
|
300
|
+
def set_async_write(self, async_write: bool) -> None:
|
|
301
|
+
"""Set asynchronous write mode.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
async_write (bool): Whether to write logs asynchronously.
|
|
305
|
+
"""
|
|
306
|
+
self.async_write = async_write
|
|
307
|
+
if not async_write:
|
|
308
|
+
self.flush()
|