import copy
import logging
import logging.handlers
import os
import platform
import sys
from typing import Optional
from rich.logging import LogRender as _LogRender
from rich.logging import RichHandler as _RichHandler
from jina import __resources_path__, __uptime__, __windows__
from jina.enums import LogVerbosity
from jina.jaml import JAML
from jina.logging import formatter
class _MyLogRender(_LogRender):
"""Override the original rich log record for more compact layout."""
def __call__(
self,
console,
renderables,
log_time=None,
time_format=None,
level=None,
path=None,
line_no=None,
link_path=None,
):
from rich.containers import Renderables
from rich.table import Table
from rich.text import Text
output = Table.grid(padding=(0, 1))
output.expand = True
if self.show_level:
output.add_column(style="log.level", width=5)
output.add_column(ratio=1, style='log.message', overflow='ellipsis')
if self.show_time:
output.add_column(style="log.path")
row = []
if self.show_level:
row.append(level)
row.append(Renderables(renderables))
if self.show_time:
log_time = log_time or console.get_datetime()
time_format = time_format or self.time_format
if callable(time_format):
log_time_display = time_format(log_time)
else:
log_time_display = Text(log_time.strftime(time_format))
if log_time_display == self._last_time and self.omit_repeated_times:
row.append(Text(" " * len(log_time_display)))
else:
row.append(log_time_display)
self._last_time = log_time_display
output.add_row(*row)
return output
[docs]class RichHandler(_RichHandler):
"""Override the original rich handler for more compact layout."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._log_render = _MyLogRender(
show_time=self._log_render.show_time,
show_level=self._log_render.show_level,
show_path=self._log_render.show_path,
time_format=self._log_render.time_format,
omit_repeated_times=self._log_render.omit_repeated_times,
level_width=None,
)
[docs]class SysLogHandlerWrapper(logging.handlers.SysLogHandler):
"""
Override the priority_map :class:`SysLogHandler`.
.. warning::
This messages at DEBUG and INFO are therefore not stored by ASL, (ASL = Apple System Log)
which in turn means they can't be printed by syslog after the fact. You can confirm it via :command:`syslog` or
:command:`tail -f /var/log/system.log`.
"""
priority_map = {
'DEBUG': 'debug',
'INFO': 'info',
'WARNING': 'warning',
'ERROR': 'error',
'CRITICAL': 'critical',
}
[docs]class JinaLogger:
"""
Build a logger for a context.
:param context: The context identifier of the class, module or method.
:param log_config: The configuration file for the logger.
configuration to group logs by deployment.
:return:: an executor object.
"""
supported = {'FileHandler', 'StreamHandler', 'SysLogHandler', 'RichHandler'}
def __init__(
self,
context: str,
name: Optional[str] = None,
log_config: Optional[str] = None,
quiet: bool = False,
**kwargs,
):
if not log_config:
log_config = os.getenv(
'JINA_LOG_CONFIG',
'default',
)
if quiet or os.getenv('JINA_LOG_CONFIG', None) == 'QUIET':
log_config = 'quiet'
if not name:
name = os.getenv('JINA_DEPLOYMENT_NAME', context)
# Remove all handlers associated with the root logger object.
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
self.logger = logging.getLogger(context)
self.logger.propagate = False
context_vars = {
'name': name,
'uptime': __uptime__,
'context': context,
}
self.add_handlers(log_config, **context_vars)
self.success = lambda *x: self.logger.log(LogVerbosity.SUCCESS, *x)
self.debug = self.logger.debug
self.warning = self.logger.warning
self.critical = self.logger.critical
self.error = self.logger.error
self.info = self.logger.info
self._is_closed = False
self.debug_enabled = self.logger.isEnabledFor(logging.DEBUG)
@property
def handlers(self):
"""
Get the handlers of the logger.
:return:: Handlers of logger.
"""
return self.logger.handlers
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
[docs] def close(self):
"""Close all the handlers."""
if not self._is_closed:
for handler in self.logger.handlers:
handler.close()
self._is_closed = True
[docs] def add_handlers(self, config_path: Optional[str] = None, **kwargs):
"""
Add handlers from config file.
:param config_path: Path of config file.
:param kwargs: Extra parameters.
"""
self.logger.handlers = []
if not os.path.exists(config_path):
old_config_path = config_path
if 'logging.' in config_path and '.yml' in config_path:
config_path = os.path.join(__resources_path__, config_path)
else:
config_path = os.path.join(
__resources_path__, f'logging.{config_path}.yml'
)
if not os.path.exists(config_path):
config_path = old_config_path
with open(config_path) as fp:
config = JAML.load(fp)
for h in config['handlers']:
cfg = config['configs'].get(h, None)
fmt = getattr(formatter, cfg.get('formatter', 'Formatter'))
if h not in self.supported or not cfg:
raise ValueError(
f'can not find configs for {h}, maybe it is not supported'
)
handler = None
if h == 'StreamHandler':
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(fmt(cfg['format'].format_map(kwargs)))
elif h == 'RichHandler':
kwargs_handler = copy.deepcopy(cfg)
kwargs_handler.pop('format')
handler = RichHandler(**kwargs_handler)
handler.setFormatter(fmt(cfg['format'].format_map(kwargs)))
elif h == 'SysLogHandler' and not __windows__:
if cfg['host'] and cfg['port']:
handler = SysLogHandlerWrapper(address=(cfg['host'], cfg['port']))
else:
# a UNIX socket is used
if platform.system() == 'Darwin':
handler = SysLogHandlerWrapper(address='/var/run/syslog')
else:
handler = SysLogHandlerWrapper(address='/dev/log')
if handler:
handler.ident = cfg.get('ident', '')
handler.setFormatter(fmt(cfg['format'].format_map(kwargs)))
try:
handler._connect_unixsocket(handler.address)
except OSError:
handler = None
pass
elif h == 'FileHandler':
filename = cfg['output'].format_map(kwargs)
if __windows__:
# colons are not allowed in filenames
filename = filename.replace(':', '.')
handler = logging.FileHandler(filename, delay=True)
handler.setFormatter(fmt(cfg['format'].format_map(kwargs)))
if handler:
self.logger.addHandler(handler)
verbose_level = LogVerbosity.from_string(config['level'])
if 'JINA_LOG_LEVEL' in os.environ:
verbose_level = LogVerbosity.from_string(os.environ['JINA_LOG_LEVEL'])
self.logger.setLevel(verbose_level.value)