# Copyright 2017 Mycroft AI Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """ Mycroft Logging module. This module provides the LOG pseudo function quickly creating a logger instance for use. The default log level of the logger created here can ONLY be set in /etc/mycroft/mycroft.conf or ~/.mycroft/mycroft.conf The default log level can also be programatically be changed by setting the LOG.level parameter. """ import inspect import logging import sys from os.path import isfile from mycroft.util.json_helper import load_commented_json, merge_dict from mycroft.configuration.locations import SYSTEM_CONFIG, USER_CONFIG def getLogger(name="MYCROFT"): """Depreciated. Use LOG instead""" return logging.getLogger(name) def _make_log_method(fn): @classmethod def method(cls, *args, **kwargs): cls._log(fn, *args, **kwargs) method.__func__.__doc__ = fn.__doc__ return method class LOG: """ Custom logger class that acts like logging.Logger The logger name is automatically generated by the module of the caller Usage: >>> LOG.debug('My message: %s', debug_str) 13:12:43.673 - ::1 - DEBUG - My message: hi >>> LOG('custom_name').debug('Another message') 13:13:10.462 - custom_name - DEBUG - Another message """ _custom_name = None handler = None level = None # Copy actual logging methods from logging.Logger # Usage: LOG.debug(message) debug = _make_log_method(logging.Logger.debug) info = _make_log_method(logging.Logger.info) warning = _make_log_method(logging.Logger.warning) error = _make_log_method(logging.Logger.error) exception = _make_log_method(logging.Logger.exception) @classmethod def init(cls): """ Initializes the class, sets the default log level and creates the required handlers. """ # Check configs manually, the Mycroft configuration system can't be # used since it uses the LOG system and would cause horrible cyclic # dependencies. confs = [SYSTEM_CONFIG, USER_CONFIG] config = {} for conf in confs: try: merge_dict(config, load_commented_json(conf) if isfile(conf) else {}) except Exception as e: print('couldn\'t load {}: {}'.format(conf, str(e))) cls.level = logging.getLevelName(config.get('log_level', 'INFO')) log_message_format = ( '{asctime} | {levelname:8} | {process:5} | {name} | {message}' ) formatter = logging.Formatter(log_message_format, style='{') formatter.default_msec_format = '%s.%03d' cls.handler = logging.StreamHandler(sys.stdout) cls.handler.setFormatter(formatter) # Enable logging in external modules cls.create_logger('').setLevel(cls.level) @classmethod def create_logger(cls, name): logger = logging.getLogger(name) logger.propagate = False logger.addHandler(cls.handler) return logger def __init__(self, name): LOG._custom_name = name @classmethod def _log(cls, func, *args, **kwargs): if cls._custom_name is not None: name = cls._custom_name cls._custom_name = None else: # Stack: # [0] - _log() # [1] - debug(), info(), warning(), or error() # [2] - caller stack = inspect.stack() # Record: # [0] - frame object # [1] - filename # [2] - line number # [3] - function # ... record = stack[2] mod = inspect.getmodule(record[0]) module_name = mod.__name__ if mod else '' name = module_name + ':' + record[3] + ':' + str(record[2]) func(cls.create_logger(name), *args, **kwargs) LOG.init()