148 lines
5.7 KiB
Python
148 lines
5.7 KiB
Python
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
|
#
|
|
# 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.
|
|
""" A logging filter to prepend UWSGI-handled formatting to all logging
|
|
records that use this filter. Request-based values will cause the log
|
|
records to have correlation values that can be used to better trace
|
|
logs. If uwsgi is not present, does not attempt to change the logs in
|
|
any way
|
|
|
|
Threads initiated using threading.Thread can be correlated to the request
|
|
they came from by setting a kwarg of log_extra, containing a dictionary
|
|
of valeus matching the VALID_ADDL_FIELDS below and any fields that are set
|
|
as additional_fields by the setup_logging function. This mechanism assumes
|
|
that the thread will maintain the correlation values for the life
|
|
of the thread.
|
|
"""
|
|
import logging
|
|
import threading
|
|
|
|
# Import uwsgi to determine if it has been provided to the application.
|
|
try:
|
|
import uwsgi
|
|
except ImportError:
|
|
uwsgi = None
|
|
|
|
VALID_ADDL_FIELDS = ['req_id', 'external_ctx', 'user']
|
|
_DEFAULT_LOG_FORMAT = (
|
|
'%(asctime)s %(levelname)-8s %(req_id)s %(external_ctx)s %(user)s '
|
|
'%(module)s(%(lineno)d) %(funcName)s - %(message)s'
|
|
)
|
|
|
|
_LOG_FORMAT_IN_USE = None
|
|
|
|
|
|
def setup_logging(level, format_string=None, additional_fields=None):
|
|
""" Establishes the base logging using the appropriate filter
|
|
attached to the console/stream handler.
|
|
:param level: The level value to set as the threshold for
|
|
logging. Ideally a client would use logging.INFO or
|
|
the desired logging constant to set this level value
|
|
:param format_string: Optional value allowing for override of the
|
|
logging format string. If new values beyond
|
|
the default value are introduced, the
|
|
additional_fields must contain those fields
|
|
to ensure they are set upon using the logging
|
|
filter.
|
|
:param additional_fields: Optionally allows for specifying more
|
|
fields that will be set on each logging
|
|
record. If specified, the format_string
|
|
parameter should be set with matching
|
|
fields, otherwise they will not be
|
|
displayed.
|
|
"""
|
|
global _LOG_FORMAT_IN_USE
|
|
_LOG_FORMAT_IN_USE = format_string or _DEFAULT_LOG_FORMAT
|
|
|
|
console_handler = logging.StreamHandler()
|
|
if uwsgi:
|
|
console_handler.addFilter(UwsgiLogFilter(additional_fields))
|
|
logging.basicConfig(level=level,
|
|
format=_LOG_FORMAT_IN_USE,
|
|
handlers=[console_handler])
|
|
logger = logging.getLogger(__name__)
|
|
logger.info('Established logging defaults')
|
|
|
|
|
|
def get_log_format():
|
|
""" Returns the common log format being used by this application
|
|
"""
|
|
return _LOG_FORMAT_IN_USE
|
|
|
|
|
|
def set_logvar(key, value):
|
|
""" Attempts to set the logvar in the request scope , or ignores it
|
|
if not running in uwsgi
|
|
"""
|
|
if uwsgi and value:
|
|
uwsgi.set_logvar(key, value)
|
|
|
|
|
|
class UwsgiLogFilter(logging.Filter):
|
|
""" A filter that preepends log records with additional request
|
|
based information, or information provided by log_extra in the
|
|
kwargs provided to a thread
|
|
"""
|
|
def __init__(self, additional_fields=None):
|
|
super().__init__()
|
|
if additional_fields is None:
|
|
additional_fields = []
|
|
self.log_fields = [*VALID_ADDL_FIELDS, *additional_fields]
|
|
|
|
def filter(self, record):
|
|
""" Checks for thread provided values, or attempts to get values
|
|
from uwsgi
|
|
"""
|
|
if self._thread_has_log_extra():
|
|
value_setter = self._set_values_from_log_extra
|
|
else:
|
|
value_setter = self._set_value
|
|
|
|
for field_nm in self.log_fields:
|
|
value_setter(record, field_nm)
|
|
return True
|
|
|
|
def _set_value(self, record, logvar):
|
|
# handles setting the logvars from uwsgi or '' in case of none/empty
|
|
try:
|
|
logvar_value = None
|
|
if uwsgi:
|
|
logvar_value = uwsgi.get_logvar(logvar)
|
|
if logvar_value:
|
|
setattr(record, logvar, logvar_value.decode('UTF-8'))
|
|
else:
|
|
setattr(record, logvar, '')
|
|
except SystemError:
|
|
# This happens if log_extra is not on a thread that is spawned
|
|
# by a process running under uwsgi
|
|
setattr(record, logvar, '')
|
|
|
|
def _set_values_from_log_extra(self, record, logvar):
|
|
# sets the values from the log_extra on the thread
|
|
setattr(record, logvar, self._get_value_from_thread(logvar) or '')
|
|
|
|
def _thread_has_log_extra(self):
|
|
# Checks to see if log_extra is present on the current thread
|
|
if self._get_log_extra_from_thread():
|
|
return True
|
|
return False
|
|
|
|
def _get_value_from_thread(self, logvar):
|
|
# retrieve the logvar from the log_extra from kwargs for the thread
|
|
return self._get_log_extra_from_thread().get(logvar, '')
|
|
|
|
def _get_log_extra_from_thread(self):
|
|
# retrieves the log_extra value from kwargs or {} if it doesn't
|
|
# exist
|
|
return threading.current_thread()._kwargs.get('log_extra', {})
|