• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# coding=utf-8
3
4#
5# Copyright (c) 2020-2022 Huawei Device Co., Ltd.
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19import logging
20import os
21import sys
22import time
23import threading
24import queue
25from logging import LogRecord
26from logging.handlers import RotatingFileHandler
27
28from _core.constants import LogType
29from _core.plugin import Plugin
30from _core.plugin import get_plugin
31from _core.exception import ParamError
32
33
34__all__ = ["Log", "platform_logger", "device_logger", "shutdown",
35           "add_task_file_handler", "remove_task_file_handler",
36           "change_logger_level",
37           "add_encrypt_file_handler", "remove_encrypt_file_handler",
38           "redirect_driver_log_begin", "redirect_driver_log_end",
39           "LogQueue"]
40
41_HANDLERS = []
42_LOGGERS = []
43MAX_LOG_LENGTH = 20 * 1024 * 1024
44MAX_ENCRYPT_LOG_LENGTH = 5 * 1024 * 1024
45MAX_LOG_NUMS = 1000
46MAX_LOG_CACHE_SIZE = 10
47
48
49def _new_file_handler(log_file, log_level, mode="a"):
50    from xdevice import Variables
51    handler = RotatingFileHandler(log_file, mode=mode, maxBytes=MAX_LOG_LENGTH,
52                                  backupCount=MAX_LOG_NUMS, encoding="utf-8")
53    handler.setFormatter(logging.Formatter(Variables.report_vars.log_format))
54    handler.setLevel(log_level)
55    return handler
56
57
58def _query_log_level():
59    log_level = getattr(sys, "log_level", logging.INFO) if hasattr(
60        sys, "log_level") else logging.DEBUG
61    return log_level
62
63
64class DriverLogFilter(logging.Filter):
65
66    def __init__(self, thread_id):
67        super().__init__()
68        self.thread_id = thread_id
69
70    def filter(self, record: LogRecord) -> bool:
71        return record.thread == self.thread_id
72
73
74class SchedulerLogFilter(logging.Filter):
75
76    def __init__(self):
77        super().__init__()
78        self.driver_thread_ids = []
79
80    def filter(self, record: LogRecord) -> bool:
81        return record.thread not in self.driver_thread_ids
82
83    def add_driver_thread_id(self, thread_id):
84        self.driver_thread_ids.append(thread_id)
85
86    def del_driver_thread_id(self, thread_id):
87        self.driver_thread_ids.remove(thread_id)
88
89
90class Log:
91    task_file_handler = None
92
93    def __init__(self):
94        self.level = logging.INFO
95        self.handlers = []
96        self.loggers = {}
97        self.task_file_filter = None
98        self.task_file_handler = None
99        self.encrypt_file_handler = None
100        self.driver_log_handler = {}
101        self._lock = threading.Lock()
102
103    def __initial__(self, log_handler_flag, log_file=None, level=None,
104                    log_format=None):
105        if _LOGGERS:
106            return
107
108        self.handlers = []
109        if log_file and "console" in log_handler_flag:
110            file_handler = RotatingFileHandler(
111                log_file, mode="a", maxBytes=MAX_LOG_LENGTH, backupCount=MAX_LOG_NUMS,
112                encoding="UTF-8")
113            file_handler.setFormatter(logging.Formatter(log_format))
114            self.handlers.append(file_handler)
115        if "console" in log_handler_flag:
116            stream_handler = logging.StreamHandler(sys.stdout)
117            stream_handler.setFormatter(logging.Formatter(log_format))
118            self.handlers.append(stream_handler)
119
120        if level:
121            self.level = level
122        self.loggers = {}
123        self.task_file_handler = None
124        _HANDLERS.extend(self.handlers)
125
126    def set_level(self, level):
127        self.level = level
128
129    def __logger__(self, name=None):
130        if not name:
131            return _init_global_logger(name)
132        elif name in self.loggers:
133            return self.loggers.get(name)
134        else:
135            log = self.loggers.setdefault(name, FrameworkLog(name))
136            _LOGGERS.append(log)
137            log.platform_log.setLevel(self.level)
138            for handler in self.handlers:
139                log.platform_log.addHandler(handler)
140            if self.task_file_handler:
141                log.add_task_log(self.task_file_handler)
142            return log
143
144    def add_driver_log_handler(self, thread_id, log_file):
145        log_dir = os.path.dirname(log_file)
146        if not os.path.exists(log_dir):
147            os.makedirs(log_dir)
148        try:
149            self._lock.acquire()
150            # 1.仅输出当前驱动执行线程的日志到文件
151            log_level = _query_log_level()
152            handler = _new_file_handler(log_file, log_level)
153            handler.addFilter(DriverLogFilter(thread_id))
154            # 2.添加驱动执行线程的日志处理器,使得新建的日志对象可用
155            self.handlers.append(handler)
156            # 3.为已有的日志对象添加驱动执行线程日志处理器
157            for _, log in self.loggers.items():
158                log.add_driver_handler(handler)
159            # 4.为调度日志对象记录器,添加当前执行线程的日志过滤标识
160            if self.task_file_filter is not None \
161                    and isinstance(self.task_file_filter, SchedulerLogFilter):
162                self.task_file_filter.add_driver_thread_id(thread_id)
163            self.driver_log_handler.setdefault(thread_id, handler)
164        finally:
165            self._lock.release()
166
167    def del_driver_log_handler(self, thread_id):
168        try:
169            self._lock.acquire()
170            # 为调度日志对象记录器,移除当前执行线程的日志过滤标识
171            if self.task_file_filter is not None \
172                    and isinstance(self.task_file_filter, SchedulerLogFilter):
173                self.task_file_filter.del_driver_thread_id(thread_id)
174            if thread_id in self.driver_log_handler.keys():
175                # 关闭驱动执行线程日志处理器
176                handler = self.driver_log_handler.pop(thread_id)
177                handler.close()
178                # 移除驱动执行线程日志处理器
179                self.handlers.remove(handler)
180                # 为已有的日志对象,移除驱动执行线程日志处理器
181                for _, log in self.loggers.items():
182                    log.remove_driver_handler(handler)
183        finally:
184            self._lock.release()
185
186    def add_task_file_handler(self, log_file):
187        self.task_file_filter = SchedulerLogFilter()
188        log_level = _query_log_level()
189        self.task_file_handler = _new_file_handler(log_file, log_level)
190        self.task_file_handler.addFilter(self.task_file_filter)
191        for _, log in self.loggers.items():
192            log.add_task_log(self.task_file_handler)
193
194    def remove_task_file_handler(self):
195        if self.task_file_handler is None:
196            return
197        for _, log in self.loggers.items():
198            log.remove_task_log(self.task_file_handler)
199        self.task_file_handler.close()
200        self.task_file_handler = None
201
202    def add_encrypt_file_handler(self, log_file):
203        from xdevice import Variables
204
205        file_handler = \
206            EncryptFileHandler(log_file, mode="ab",
207                               max_bytes=MAX_ENCRYPT_LOG_LENGTH,
208                               backup_count=MAX_LOG_NUMS, encoding="utf-8")
209        file_handler.setFormatter(logging.Formatter(
210            Variables.report_vars.log_format))
211        self.encrypt_file_handler = file_handler
212        for _, log in self.loggers.items():
213            log.add_encrypt_log(self.encrypt_file_handler)
214
215    def remove_encrypt_file_handler(self):
216        if self.encrypt_file_handler is None:
217            return
218        for _, log in self.loggers.items():
219            log.remove_encrypt_log(self.encrypt_file_handler)
220        self.encrypt_file_handler.close()
221        self.encrypt_file_handler = None
222
223
224class FrameworkLog:
225
226    def __init__(self, name):
227        self.name = name
228        self.platform_log = logging.Logger(name)
229        self.task_log = None
230        self.encrypt_log = None
231        self.driver_log = None
232
233    def set_level(self, level):
234        # apply to dynamic change logger level, and
235        # only change the level of platform log
236        cache = getattr(self.platform_log, "_cache", None)
237        if cache and isinstance(cache, dict):
238            cache.clear()
239        self.platform_log.setLevel(level)
240
241    def add_platform_handler(self, handler):
242        self.platform_log.addHandler(handler)
243
244    def del_platform_handler(self, handler):
245        self.platform_log.removeHandler(handler)
246
247    def add_task_log(self, handler):
248        if self.task_log:
249            return
250        self.task_log = logging.Logger(self.name)
251        log_level = _query_log_level()
252        self.task_log.setLevel(log_level)
253        self.task_log.addHandler(handler)
254
255    def remove_task_log(self, handler):
256        if not self.task_log:
257            return
258        self.task_log.removeHandler(handler)
259        self.task_log = None
260
261    def add_encrypt_log(self, handler):
262        if self.encrypt_log:
263            return
264        self.encrypt_log = logging.Logger(self.name)
265        log_level = getattr(sys, "log_level", logging.INFO) if hasattr(
266            sys, "log_level") else logging.DEBUG
267        self.encrypt_log.setLevel(log_level)
268        self.encrypt_log.addHandler(handler)
269
270    def remove_encrypt_log(self, handler):
271        if not self.encrypt_log:
272            return
273        self.encrypt_log.removeHandler(handler)
274        self.encrypt_log = None
275
276    def add_driver_handler(self, handler):
277        if not self.driver_log:
278            self.driver_log = logging.Logger(self.name)
279            log_level = _query_log_level()
280            self.driver_log.setLevel(log_level)
281        self.driver_log.addHandler(handler)
282
283    def remove_driver_handler(self, handler):
284        if self.driver_log:
285            self.driver_log.removeHandler(handler)
286
287    def info(self, msg, *args, **kwargs):
288        additional_output = self._get_additional_output(**kwargs)
289        updated_msg = self._update_msg(additional_output, msg)
290        self.platform_log.info(updated_msg, *args)
291        if self.task_log:
292            self.task_log.info(updated_msg, *args)
293        if self.encrypt_log:
294            self.encrypt_log.info(updated_msg, *args)
295        if self.driver_log:
296            self.driver_log.info(updated_msg, *args)
297
298    def debug(self, msg, *args, **kwargs):
299        additional_output = self._get_additional_output(**kwargs)
300        updated_msg = self._update_msg(additional_output, msg)
301        from _core.report.encrypt import check_pub_key_exist
302        if not check_pub_key_exist():
303            self.platform_log.debug(updated_msg, *args)
304            if self.task_log:
305                self.task_log.debug(updated_msg, *args)
306            if self.driver_log:
307                self.driver_log.debug(updated_msg, *args)
308        else:
309            if self.encrypt_log:
310                self.encrypt_log.debug(updated_msg, *args)
311
312    def error(self, msg, *args, **kwargs):
313        error_no = kwargs.get("error_no", "00000")
314        additional_output = self._get_additional_output(error_no, **kwargs)
315        updated_msg = self._update_msg(additional_output, msg)
316
317        self.platform_log.error(updated_msg, *args)
318        if self.task_log:
319            self.task_log.error(updated_msg, *args)
320        if self.driver_log:
321            self.driver_log.error(updated_msg, *args)
322        if self.encrypt_log:
323            self.encrypt_log.error(updated_msg, *args)
324
325    def warning(self, msg, *args, **kwargs):
326        additional_output = self._get_additional_output(**kwargs)
327        updated_msg = self._update_msg(additional_output, msg)
328
329        self.platform_log.warning(updated_msg, *args)
330        if self.task_log:
331            self.task_log.warning(updated_msg, *args)
332        if self.driver_log:
333            self.driver_log.warning(updated_msg, *args)
334        if self.encrypt_log:
335            self.encrypt_log.warning(updated_msg, *args)
336
337    def exception(self, msg, *args, **kwargs):
338        error_no = kwargs.get("error_no", "00000")
339        exc_info = kwargs.get("exc_info", True)
340        if exc_info is not True and exc_info is not False:
341            exc_info = True
342        additional_output = self._get_additional_output(error_no, **kwargs)
343        updated_msg = self._update_msg(additional_output, msg)
344
345        self.platform_log.exception(updated_msg, exc_info=exc_info, *args)
346        if self.task_log:
347            self.task_log.exception(updated_msg, exc_info=exc_info, *args)
348        if self.driver_log:
349            self.driver_log.exception(updated_msg, exc_info=exc_info, *args)
350        if self.encrypt_log:
351            self.encrypt_log.exception(updated_msg, exc_info=exc_info, *args)
352
353    @classmethod
354    def _update_msg(cls, additional_output, msg):
355        msg = "[%s]" % msg if msg else msg
356        if msg and additional_output:
357            msg = "%s [%s]" % (msg, additional_output)
358        return msg
359
360    def _get_additional_output(self, error_number=None, **kwargs):
361        dict_str = self._get_dict_str(**kwargs)
362        if error_number:
363            additional_output = "ErrorNo=%s" % error_number
364        else:
365            return dict_str
366
367        if dict_str:
368            additional_output = "%s, %s" % (additional_output, dict_str)
369        return additional_output
370
371    @classmethod
372    def _get_dict_str(cls, **kwargs):
373        dict_str = ""
374        for key, value in kwargs.items():
375            if key in ["error_no", "exc_info"]:
376                continue
377            dict_str = "%s%s=%s, " % (dict_str, key, value)
378        if dict_str:
379            dict_str = dict_str[:-2]
380        return dict_str
381
382
383def platform_logger(name=None):
384    plugins = get_plugin(Plugin.LOG, LogType.tool)
385    for log_plugin in plugins:
386        if log_plugin.get_plugin_config().enabled:
387            return log_plugin.__logger__(name)
388    return _init_global_logger(name)
389
390
391def device_logger(name=None):
392    plugins = get_plugin(Plugin.LOG, LogType.device)
393    for log_plugin in plugins:
394        if log_plugin.get_plugin_config().enabled:
395            return log_plugin.__logger__(name)
396    return _init_global_logger(name)
397
398
399def shutdown():
400    # logging will be shutdown automatically, when the program exits.
401    # This function is used by testing.
402    for log in _LOGGERS:
403        for handler in log.handlers:
404            log.removeHandler(handler)
405    for handler in _HANDLERS:
406        handler.close()
407    _HANDLERS.clear()
408    _LOGGERS.clear()
409
410
411def redirect_driver_log_begin(thread_id, log_file):
412    plugins = get_plugin(Plugin.LOG, LogType.tool)
413    for log_plugin in plugins:
414        if log_plugin.get_plugin_config().enabled:
415            log_plugin.add_driver_log_handler(thread_id, log_file)
416
417
418def redirect_driver_log_end(thread_id):
419    plugins = get_plugin(Plugin.LOG, LogType.tool)
420    for log_plugin in plugins:
421        if log_plugin.get_plugin_config().enabled:
422            log_plugin.del_driver_log_handler(thread_id)
423
424
425def add_task_file_handler(log_file=None):
426    if log_file is None:
427        return
428    plugins = get_plugin(Plugin.LOG, LogType.tool)
429    for log_plugin in plugins:
430        if log_plugin.get_plugin_config().enabled:
431            log_plugin.add_task_file_handler(log_file)
432
433
434def remove_task_file_handler():
435    plugins = get_plugin(Plugin.LOG, LogType.tool)
436    for log_plugin in plugins:
437        if log_plugin.get_plugin_config().enabled:
438            log_plugin.remove_task_file_handler()
439
440
441def add_encrypt_file_handler(log_file=None):
442    if log_file is None:
443        return
444    plugins = get_plugin(Plugin.LOG, LogType.tool)
445    for log_plugin in plugins:
446        if log_plugin.get_plugin_config().enabled:
447            log_plugin.add_encrypt_file_handler(log_file)
448
449
450def remove_encrypt_file_handler():
451    plugins = get_plugin(Plugin.LOG, LogType.tool)
452    for log_plugin in plugins:
453        if log_plugin.get_plugin_config().enabled:
454            log_plugin.remove_encrypt_file_handler()
455
456
457def _init_global_logger(name=None):
458    handler = logging.StreamHandler(sys.stdout)
459    log_format = \
460        "[%(asctime)s] [%(thread)d] [%(name)s] [%(levelname)s] [%(message)s]"
461    handler.setFormatter(logging.Formatter(log_format))
462    log = FrameworkLog(name)
463    log.platform_log.setLevel(logging.INFO)
464    log.platform_log.addHandler(handler)
465    return log
466
467
468def change_logger_level(leve_dict):
469    level_map = {"debug": logging.DEBUG, "info": logging.INFO}
470    if "console" in leve_dict.keys():
471        level = leve_dict["console"]
472        if not level:
473            return
474        if str(level).lower() in level_map.keys():
475            logger_level = level_map.get(str(level).lower(), logging.INFO)
476
477            # change level of loggers which will to be instantiated.
478            # Actually, it changes the level attribute in ToolLog,
479            # which will be used when instantiating the FrameLog object.
480            plugins = get_plugin(Plugin.LOG, LogType.tool)
481            for log_plugin in plugins:
482                log_plugin.set_level(logger_level)
483            # change level of loggers which have instantiated
484            for logger in _LOGGERS:
485                if getattr(logger, "setLevel", None):
486                    logger.setLevel(logger_level)
487                elif getattr(logger, "set_level", None):
488                    logger.set_level(logger_level)
489
490    if "file" in leve_dict.keys():
491        level = leve_dict["file"]
492        if not level:
493            return
494        if str(level).lower() in level_map.keys():
495            logger_level = level_map.get(str(level).lower(), logging.INFO)
496            setattr(sys, "log_level", logger_level)
497
498
499class EncryptFileHandler(RotatingFileHandler):
500
501    def __init__(self, filename, mode='ab', max_bytes=0, backup_count=0,
502                 encoding=None, delay=False):
503        RotatingFileHandler.__init__(self, filename, mode, max_bytes,
504                                     backup_count, encoding, delay)
505        self.mode = mode
506        self.encrypt_error = None
507
508    def _open(self):
509        if not self.mode == "ab":
510            self.mode = "ab"
511
512        # baseFilename is the attribute in FileHandler
513        base_file_name = getattr(self, "baseFilename", None)
514        with open(base_file_name, self.mode) as handler:
515            return handler
516
517    def emit(self, record):
518        try:
519            if not self._encrypt_valid():
520                return
521
522            # shouldRoller and doRoller is the method in RotatingFileHandler
523            should_rollover = getattr(self, "shouldRollover", None)
524            if callable(should_rollover) and should_rollover(record):
525                self.doRollover()
526
527            # stream is the attribute in StreamHandler
528            if not getattr(self, "stream", None):
529                setattr(self, "stream", self._open())
530            msg = self.format(record)
531            stream = getattr(self, "stream", self._open())
532            stream.write(msg)
533            self.flush()
534        except RecursionError as error:  # pylint:disable=undefined-variable
535            raise error
536
537    def _encrypt_valid(self):
538        from _core.report.encrypt import check_pub_key_exist
539        if check_pub_key_exist() and not self.encrypt_error:
540            return True
541
542    def format(self, record):
543        """
544        Customize the implementation method. If the log format of the
545        framework changes, update the return value format of the method
546        in a timely manner.
547        :param record: logging.LogRecord
548        :return: bytes
549        """
550        from _core.report.encrypt import do_rsa_encrypt
551        create_time = "{},{}".format(
552            time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(record.created)),
553            "{:0>3d}".format(int("%d" % record.msecs)))
554        name = record.name
555        level_name = record.levelname
556        msg = record.msg
557        if msg and "%s" in msg:
558            msg = msg % record.args
559        info = "[%s] [%s] [%s] [%s] %s%s" \
560               % (create_time, threading.currentThread().ident, name,
561                  level_name, msg, "\n")
562
563        try:
564            return do_rsa_encrypt(info)
565        except ParamError as error:
566            error_no_str = \
567                "ErrorNo={}".format(getattr(error, "error_no", "00113"))
568            info = "[%s] [%s] [%s] [%s] [%s] [%s]\n" % (
569                create_time, threading.currentThread().ident,
570                name, "ERROR", error, error_no_str)
571            self.encrypt_error = bytes(info, "utf-8")
572            return self.encrypt_error
573
574
575class LogQueue:
576    log = None
577    max_size = 0
578    queue_info = None
579    queue_debug = None
580    queue_error = None
581
582    def __init__(self, log, max_size=MAX_LOG_CACHE_SIZE):
583        self.log = log
584        self.max_size = max_size
585        self.queue_info = queue.Queue(maxsize=self.max_size)
586        self.queue_debug = queue.Queue(maxsize=self.max_size)
587        self.queue_error = queue.Queue(maxsize=self.max_size)
588
589    def _put(self, log_queue, log_data, clear):
590        is_print = False
591        result_data = ""
592        if log_queue.full() or clear:
593            # make sure the last one print
594            if log_queue.qsize() > 0:
595                is_print = True
596                result_data = "{}\n".format(log_queue.get())
597            else:
598                result_data = ""
599            if log_data != "":
600                log_queue.put(log_data)
601            while not log_queue.empty():
602                is_print = True
603                result_data = "{} [{}] {}\n".format(result_data, threading.currentThread().ident, log_queue.get())
604        else:
605            if log_data != "":
606                log_queue.put(log_data)
607        return is_print, result_data
608
609    def info(self, log_data, clear=False):
610        is_print, result_data = self._put(self.queue_info, log_data, clear)
611        if is_print:
612            self.log.info(result_data)
613
614    def debug(self, log_data, clear=False):
615        is_print, result_data = self._put(self.queue_debug, log_data, clear)
616        if is_print:
617            self.log.debug(result_data)
618
619    def error(self, log_data, clear=False):
620        is_print, result_data = self._put(self.queue_error, log_data, clear)
621        if is_print:
622            self.log.error(result_data)
623
624    def clear(self):
625        self.info(log_data="", clear=True)
626        self.debug(log_data="", clear=True)
627        self.error(log_data="", clear=True)
628