blob: 0adee8496f72095b76f8fd4feb76851a71725e0d [file] [log] [blame]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#
import logging
import sys
import traceback
from multiprocessing import current_process
from os.path import basename, splitext
from threading import current_thread
from skywalking import config
from skywalking.agent import agent
from skywalking.protocol.common.Common_pb2 import KeyStringValuePair
from skywalking.protocol.logging.Logging_pb2 import LogData, LogDataBody, TraceContext, LogTags, TextLog
from skywalking.trace.context import get_context
from skywalking.utils.exception import IllegalStateError
from skywalking.utils.filter import sw_filter
link_vector = ['https://pypi.org/project/loguru/']
support_matrix = {
'loguru': {
'>=3.7': ['0.6.0']
}
}
note = """"""
def install():
from loguru import logger
from loguru._recattrs import RecordException, RecordFile, RecordLevel, RecordProcess, RecordThread
from loguru._datetime import aware_now
from loguru._get_frame import get_frame
from loguru._logger import start_time, context as logger_context, Logger
from types import MethodType
_log = logger._log
log_reporter_level = logging.getLevelName(config.agent_log_reporter_level) # type: int
def gen_record(self, level_id, static_level_no, from_decorator, options, message, args, kwargs):
""" Generate log record as loguru.logger._log """
core = self._core
if not core.handlers:
return
(exception, depth, record, lazy, colors, raw, capture, patcher, extra) = options
frame = get_frame(depth + 2)
try:
name = frame.f_globals['__name__']
except KeyError:
name = None
try:
if not core.enabled[name]:
return
except KeyError:
enabled = core.enabled
if name is None:
status = core.activation_none
enabled[name] = status
if not status:
return
else:
dotted_name = name + '.'
for dotted_module_name, status in core.activation_list:
if dotted_name[: len(dotted_module_name)] == dotted_module_name:
if status:
break
enabled[name] = False
return
enabled[name] = True
current_datetime = aware_now()
if level_id is None:
level_icon = ' '
level_no = static_level_no
level_name = f'Level {level_no}' # not really level name, just as loguru
else:
level_name, level_no, _, level_icon = core.levels[level_id]
if level_no < core.min_level:
return
code = frame.f_code
file_path = code.co_filename
file_name = basename(file_path)
thread = current_thread()
process = current_process()
elapsed = current_datetime - start_time
if exception:
if isinstance(exception, BaseException):
type_, value, traceback = (type(exception), exception, exception.__traceback__)
elif isinstance(exception, tuple):
type_, value, traceback = exception
else:
type_, value, traceback = sys.exc_info()
exception = RecordException(type_, value, traceback)
else:
exception = None
log_record = {
'elapsed': elapsed,
'exception': exception,
'extra': {**core.extra, **logger_context.get(), **extra},
'file': RecordFile(file_name, file_path),
'function': code.co_name,
'level': RecordLevel(level_name, level_no, level_icon),
'line': frame.f_lineno,
'message': str(message),
'module': splitext(file_name)[0],
'name': name,
'process': RecordProcess(process.ident, process.name),
'thread': RecordThread(thread.ident, thread.name),
'time': current_datetime,
}
if capture and kwargs:
log_record['extra'].update(kwargs)
if record:
kwargs.update(record=log_record)
if args or kwargs:
log_record['message'] = message.format(*args, **kwargs)
if core.patcher:
core.patcher(log_record)
if patcher:
patcher(log_record)
return log_record
def _sw_log(self, level_id, static_level_no, from_decorator, options, message, args, kwargs):
_log(level_id, static_level_no, from_decorator, options, message, args, kwargs)
record = gen_record(self, level_id, static_level_no, from_decorator, options, message, args, kwargs)
if record is None:
return
core = self._core
if record['level'].no < log_reporter_level:
return
if not config.agent_log_reporter_ignore_filter and record['level'].no < core.min_level: # ignore filtered logs
return
# loguru has only one logger. Use tags referring Python-Agent doc
core_tags = [
KeyStringValuePair(key='level', value=record['level'].name),
KeyStringValuePair(key='logger', value='loguru'),
KeyStringValuePair(key='thread', value=record['thread'].name),
]
tags = LogTags()
tags.data.extend(core_tags)
exception = record['exception']
if exception:
stack_trace = ''.join(traceback.format_exception(exception.type, exception.value, exception.traceback,
limit=config.agent_cause_exception_depth))
tags.data.append(KeyStringValuePair(key='exception',
value=sw_filter(stack_trace)
)) # \n doesn't work in tags for UI
context = get_context()
active_span_id = -1
primary_endpoint_name = ''
try:
# Try to extract active span, if user code/plugin code throws uncaught
# exceptions before any span is even created, just ignore these fields and
# avoid appending 'no active span' traceback that could be confusing.
# Or simply the log is generated outside any span context.
active_span_id = context.active_span.sid
primary_endpoint_name = context.primary_endpoint.get_name()
except IllegalStateError:
pass
log_data = LogData(
timestamp=round(record['time'].timestamp() * 1000),
service=config.agent_name,
serviceInstance=config.agent_instance_name,
body=LogDataBody(
type='text',
text=TextLog(
text=sw_filter(message)
)
),
tags=tags,
)
if active_span_id != -1:
trace_context = TraceContext(
traceId=str(context.segment.related_traces[0]),
traceSegmentId=str(context.segment.segment_id),
spanId=active_span_id
)
log_data.traceContext.CopyFrom(trace_context)
if primary_endpoint_name:
log_data.endpoint = primary_endpoint_name
agent.archive_log(log_data)
# Bind _sw_log function to default logger instance.
bound_sw_log = MethodType(_sw_log, logger)
logger._log = bound_sw_log
# Bind _sw_log function to Logger class for new instance.
Logger._log = _sw_log