1
+ import codecs
1
2
import logging
3
+ import os
2
4
import platform
3
5
import re
4
6
import sys
@@ -10,91 +12,156 @@ def __init__(self, name: str, log_path: Path):
10
12
self .name = name
11
13
self .log_path = log_path
12
14
13
- # ANSI color codes
14
- self .CYAN = '\033 [96m'
15
- self .BLUE = '\033 [94m'
16
- self .GREEN = '\033 [92m'
17
- self .YELLOW = '\033 [93m'
18
- self .RED = '\033 [91m'
19
- self .RESET = '\033 [0m'
15
+ # 檢查是否支持顏色輸出
16
+ self .color_support = self ._check_color_support ()
17
+
18
+ # 設置顏色代碼
19
+ if self .color_support :
20
+ self .CYAN = '\033 [96m'
21
+ self .BLUE = '\033 [94m'
22
+ self .GREEN = '\033 [92m'
23
+ self .YELLOW = '\033 [93m'
24
+ self .RED = '\033 [91m'
25
+ self .RESET = '\033 [0m'
26
+ else :
27
+ self .CYAN = ''
28
+ self .BLUE = ''
29
+ self .GREEN = ''
30
+ self .YELLOW = ''
31
+ self .RED = ''
32
+ self .RESET = ''
33
+
34
+ # 檢查 Unicode 支持
35
+ self .unicode_support = self ._setup_unicode ()
36
+
37
+ # 設置框架字符
38
+ if self .unicode_support :
39
+ self .BOX_TOP_LEFT = "╔"
40
+ self .BOX_TOP_RIGHT = "╗"
41
+ self .BOX_BOTTOM_LEFT = "╚"
42
+ self .BOX_BOTTOM_RIGHT = "╝"
43
+ self .BOX_HORIZONTAL = "═"
44
+ self .BOX_VERTICAL = "║"
45
+ self .ARROW = "▶"
46
+ else :
47
+ self .BOX_TOP_LEFT = "+"
48
+ self .BOX_TOP_RIGHT = "+"
49
+ self .BOX_BOTTOM_LEFT = "+"
50
+ self .BOX_BOTTOM_RIGHT = "+"
51
+ self .BOX_HORIZONTAL = "-"
52
+ self .BOX_VERTICAL = "|"
53
+ self .ARROW = ">"
54
+
55
+ self .logger = self ._setup_logger ()
56
+
57
+ def _check_color_support (self ) -> bool :
58
+ """檢查環境是否支持顏色輸出"""
59
+ # 檢查是否在 Spyder 或其他 IDE 中運行
60
+ if any (IDE in os .environ .get ('PYTHONPATH' , '' ) for IDE in ['spyder' , 'jupyter' ]):
61
+ return False
20
62
21
- # 強制 Windows 使用 UTF-8
63
+ # 檢查是否強制啟用或禁用顏色
64
+ if 'FORCE_COLOR' in os .environ :
65
+ return os .environ ['FORCE_COLOR' ].lower () in ('1' , 'true' , 'yes' )
66
+
67
+ # Windows 檢查
22
68
if platform .system ().lower () == 'windows' :
23
- try :
24
- sys .stdout .reconfigure (encoding = 'utf-8' )
25
- self .unicode_support = True
26
- except Exception :
27
- import codecs
28
- sys .stdout = codecs .getwriter ('utf-8' )(sys .stdout .buffer )
29
- self .unicode_support = True
30
- else :
31
- self .unicode_support = True
69
+ return ('ANSICON' in os .environ or
70
+ 'WT_SESSION' in os .environ or # Windows Terminal
71
+ 'ConEmuANSI' in os .environ or
72
+ os .environ .get ('TERM_PROGRAM' , '' ).lower () == 'vscode' )
32
73
33
- # 使用 Unicode 字符
34
- self .BOX_TOP_LEFT = "╔"
35
- self .BOX_TOP_RIGHT = "╗"
36
- self .BOX_BOTTOM_LEFT = "╚"
37
- self .BOX_BOTTOM_RIGHT = "╝"
38
- self .BOX_HORIZONTAL = "═"
39
- self .BOX_VERTICAL = "║"
40
- self .ARROW = "▶"
74
+ # 其他系統檢查
75
+ return hasattr (sys .stdout , 'isatty' ) and sys .stdout .isatty ()
41
76
42
- self .logger = self ._setup_logger ()
77
+ def _setup_unicode (self ) -> bool :
78
+ """設置 Unicode 支持"""
79
+ if platform .system ().lower () == 'windows' :
80
+ try :
81
+ if hasattr (sys .stdout , 'reconfigure' ):
82
+ sys .stdout .reconfigure (encoding = 'utf-8' )
83
+ elif hasattr (sys .stdout , 'buffer' ):
84
+ sys .stdout = codecs .getwriter ('utf-8' )(sys .stdout .buffer )
85
+ else :
86
+ return False
87
+ return True
88
+ except Exception :
89
+ return False
90
+ return True
43
91
44
92
def _setup_logger (self ) -> logging .Logger :
93
+ """設置logger"""
45
94
logger = logging .getLogger (self .name )
46
95
logger .setLevel (logging .INFO )
47
96
48
- # Remove existing handlers
97
+ # 移除現有的 handlers
49
98
for handler in logger .handlers [:]:
50
99
handler .close ()
51
100
logger .removeHandler (handler )
52
101
53
- # clean ANSI formatter (for log file)
102
+ # 清理 ANSI 格式化器
54
103
class CleanFormatter (logging .Formatter ):
55
104
def format (self , record ):
56
105
formatted_msg = super ().format (record )
57
106
return re .sub (r'\033\[[0-9;]*m' , '' , formatted_msg )
58
107
59
- # Set up handlers with UTF-8 encoding
60
- file_handler = logging .FileHandler (self .log_path / f'{ self .name } .log' , encoding = 'utf-8' )
61
- file_handler .setFormatter (CleanFormatter ('%(asctime)s - %(message)s' , datefmt = '%Y-%m-%d %H:%M:%S' ))
62
-
108
+ # 設置檔案處理器
109
+ try :
110
+ log_dir = Path (self .log_path )
111
+ log_dir .mkdir (parents = True , exist_ok = True )
112
+ file_handler = logging .FileHandler (
113
+ log_dir / f'{ self .name } .log' ,
114
+ encoding = 'utf-8' ,
115
+ errors = 'replace'
116
+ )
117
+ file_handler .setFormatter (
118
+ CleanFormatter ('%(asctime)s - %(message)s' ,
119
+ datefmt = '%Y-%m-%d %H:%M:%S' )
120
+ )
121
+ logger .addHandler (file_handler )
122
+ except Exception as e :
123
+ print (f"Warning: Could not set up file logging: { e } " )
124
+
125
+ # 設置控制台處理器
63
126
console_handler = logging .StreamHandler (sys .stdout )
64
127
console_handler .setFormatter (logging .Formatter ('%(message)s' ))
65
-
66
- logger .addHandler (file_handler )
67
128
logger .addHandler (console_handler )
68
129
69
130
return logger
70
131
132
+ def _safe_print (self , text : str ) -> str :
133
+ """安全打印,處理編碼問題"""
134
+ if not self .unicode_support :
135
+ text = text .encode ('ascii' , 'replace' ).decode ('ascii' )
136
+ return text
137
+
71
138
def info (self , msg : str ):
72
- self .logger .info (msg )
139
+ self .logger .info (self . _safe_print ( msg ) )
73
140
74
141
def warning (self , msg : str ):
75
- self .logger .warning (msg )
142
+ self .logger .warning (self . _safe_print ( msg ) )
76
143
77
144
def error (self , msg : str ):
78
- self .logger .error (msg )
145
+ self .logger .error (self . _safe_print ( msg ) )
79
146
80
147
def info_box (self , text : str , color_part : str = None , width : int = 80 ):
81
- """
82
- Create a boxed message with optional colored text
83
-
84
- Args:
85
- text: Base text format (e.g., "Reading {} RAW DATA from {} to {}")
86
- color_part: Part of text to be colored (e.g., "RAW DATA")
87
- width: Box width
88
- """
148
+ """創建帶框的消息,可選擇性地為部分文本著色"""
149
+ # 處理文本
89
150
display_text = text .replace (color_part , " " * len (color_part )) if color_part else text
90
151
152
+ # 計算padding
91
153
left_padding = " " * ((width - len (display_text )) // 2 )
92
154
right_padding = " " * (width - len (display_text ) - len (left_padding ))
93
155
94
- content = text .replace (color_part , f"{ self .CYAN } { color_part } { self .RESET } " ) if color_part else text
156
+ # 處理顏色
157
+ if color_part and self .color_support :
158
+ content = text .replace (color_part , f"{ self .CYAN } { color_part } { self .RESET } " )
159
+ else :
160
+ content = text
95
161
96
162
__content__ = f"{ left_padding } { content } { right_padding } "
97
163
98
- self .info (f"╔{ '═' * width } ╗" )
99
- self .info (f"║{ __content__ } ║" )
100
- self .info (f"╚{ '═' * width } ╝" )
164
+ # 使用當前設置的框架字符
165
+ self .info (f"{ self .BOX_TOP_LEFT } { self .BOX_HORIZONTAL * width } { self .BOX_TOP_RIGHT } " )
166
+ self .info (f"{ self .BOX_VERTICAL } { __content__ } { self .BOX_VERTICAL } " )
167
+ self .info (f"{ self .BOX_BOTTOM_LEFT } { self .BOX_HORIZONTAL * width } { self .BOX_BOTTOM_RIGHT } " )
0 commit comments