1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements. See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 Mix-ins and functions for logging, supporting multiple backends (such as SQL) and consistent
22 from logging import handlers as logging_handlers
23 # NullHandler doesn't exist in < 27. this workaround is from
24 # http://docs.python.org/release/2.6/library/logging.html#configuring-logging-for-a-library
26 from logging import NullHandler # pylint: disable=unused-import
28 class NullHandler(logging.Handler):
29 def emit(self, record):
31 from datetime import datetime
34 TASK_LOGGER_NAME = 'aria.executions.task'
37 _base_logger = logging.getLogger('aria')
40 class LoggerMixin(object):
42 Provides logging functionality to a class.
44 :ivar logger_name: logger name; default to the class name
45 :ivar logger_level: logger level; defaults to ``logging.DEBUG``
46 :ivar base_logger: child loggers are created from this; defaults to the root logger
49 logger_level = logging.DEBUG
51 def __init__(self, *args, **kwargs):
52 self.logger_name = self.logger_name or self.__class__.__name__
53 self.logger = logging.getLogger('{0}.{1}'.format(_base_logger.name, self.logger_name))
54 # Set the logger handler of any object derived from LoggerMixing to NullHandler.
55 # This is since the absence of a handler shows up while using the CLI in the form of:
56 # `No handlers could be found for logger "..."`.
57 self.logger.addHandler(NullHandler())
58 self.logger.setLevel(self.logger_level)
59 super(LoggerMixin, self).__init__(*args, **kwargs)
65 logger_level=logging.DEBUG,
66 base_logger=logging.getLogger(),
69 Set the logger used by the consuming class.
71 cls.logger_name = logger_name
72 cls.logger_level = logger_level
73 cls.base_logger = base_logger
76 def __getstate__(self):
77 obj_dict = vars(self).copy()
78 del obj_dict['logger']
81 def __setstate__(self, obj_dict):
83 logger=logging.getLogger('{0}.{1}'.format(_base_logger.name, obj_dict['logger_name'])),
87 def create_logger(logger=_base_logger, handlers=(), **configs):
89 :param logger: logger name; defaults to ARIA logger
90 :type logger: logging.Logger
91 :param handlers: logger handlers
93 :param configs: logger configurations
98 for handler in handlers:
99 logger.addHandler(handler)
101 logger.setLevel(configs.get('level', logging.DEBUG))
102 logger.debug('Logger {0} configured'.format(logger.name))
106 def create_console_log_handler(level=logging.DEBUG, formatter=None):
111 console = logging.StreamHandler()
112 console.setLevel(level)
113 console.formatter = formatter or _DefaultConsoleFormat()
117 def create_sqla_log_handler(model, log_cls, execution_id, level=logging.DEBUG):
119 # This is needed since the engine and session are entirely new we need to reflect the db
120 # schema of the logging model into the engine and session.
121 return _SQLAlchemyHandler(model=model, log_cls=log_cls, execution_id=execution_id, level=level)
124 class _DefaultConsoleFormat(logging.Formatter):
126 Info level log format: ``%(message)s``.
128 Every other log level is formatted: ``%(levelname)s: %(message)s``.
130 def format(self, record):
132 if hasattr(record, 'prefix'):
133 self._fmt = '<%(asctime)s: [%(levelname)s] @%(prefix)s> %(message)s'
135 self._fmt = '<%(asctime)s: [%(levelname)s]> %(message)s'
137 except AttributeError:
138 return record.message
139 return logging.Formatter.format(self, record)
142 def create_file_log_handler(
145 max_bytes=5 * 1000 * 1024,
149 Create a :class:`logging.handlers.RotatingFileHandler`.
151 rotating_file = logging_handlers.RotatingFileHandler(
154 backupCount=backup_count,
157 rotating_file.setLevel(level)
158 rotating_file.formatter = formatter or _default_file_formatter
162 class _SQLAlchemyHandler(logging.Handler):
163 def __init__(self, model, log_cls, execution_id, **kwargs):
164 logging.Handler.__init__(self, **kwargs)
167 self._execution_id = execution_id
169 def emit(self, record):
170 created_at = datetime.strptime(logging.Formatter('%(asctime)s').formatTime(record),
171 '%Y-%m-%d %H:%M:%S,%f')
173 execution_fk=self._execution_id,
174 task_fk=record.task_id,
175 level=record.levelname,
177 created_at=created_at,
180 traceback=getattr(record, 'traceback', None)
182 self._model.log.put(log)
185 _default_file_formatter = logging.Formatter(
186 '%(asctime)s [%(name)s:%(levelname)s] %(message)s <%(pathname)s:%(lineno)d>')