openstack-doc-test is used for building of DocBook XML files. For index page generation openstack-indexpage can be used. Remove openstack-doc-test together with all support files. Change-Id: I5589bc634e1f630f79b3d6e8bffee939c5300bf2changes/30/311730/4
@ -1,17 +0,0 @@ | |||
# | |||
# 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. | |||
import six | |||
six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox')) |
@ -1,479 +0,0 @@ | |||
# Copyright 2012 Red Hat, Inc. | |||
# Copyright 2013 IBM Corp. | |||
# All 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. | |||
""" | |||
gettext for openstack-common modules. | |||
Usual usage in an openstack.common module: | |||
from os_doc_tools.openstack.common.gettextutils import _ | |||
""" | |||
import copy | |||
import gettext | |||
import locale | |||
from logging import handlers | |||
import os | |||
from babel import localedata | |||
import six | |||
_AVAILABLE_LANGUAGES = {} | |||
# FIXME(dhellmann): Remove this when moving to oslo.i18n. | |||
USE_LAZY = False | |||
class TranslatorFactory(object): | |||
"""Create translator functions | |||
""" | |||
def __init__(self, domain, localedir=None): | |||
"""Establish a set of translation functions for the domain. | |||
:param domain: Name of translation domain, | |||
specifying a message catalog. | |||
:type domain: str | |||
:param lazy: Delays translation until a message is emitted. | |||
Defaults to False. | |||
:type lazy: Boolean | |||
:param localedir: Directory with translation catalogs. | |||
:type localedir: str | |||
""" | |||
self.domain = domain | |||
if localedir is None: | |||
localedir = os.environ.get(domain.upper() + '_LOCALEDIR') | |||
self.localedir = localedir | |||
def _make_translation_func(self, domain=None): | |||
"""Return a new translation function ready for use. | |||
Takes into account whether or not lazy translation is being | |||
done. | |||
The domain can be specified to override the default from the | |||
factory, but the localedir from the factory is always used | |||
because we assume the log-level translation catalogs are | |||
installed in the same directory as the main application | |||
catalog. | |||
""" | |||
if domain is None: | |||
domain = self.domain | |||
t = gettext.translation(domain, | |||
localedir=self.localedir, | |||
fallback=True) | |||
# Use the appropriate method of the translation object based | |||
# on the python version. | |||
m = t.gettext if six.PY3 else t.ugettext | |||
def f(msg): | |||
"""oslo.i18n.gettextutils translation function.""" | |||
if USE_LAZY: | |||
return Message(msg, domain=domain) | |||
return m(msg) | |||
return f | |||
@property | |||
def primary(self): | |||
"The default translation function." | |||
return self._make_translation_func() | |||
def _make_log_translation_func(self, level): | |||
return self._make_translation_func(self.domain + '-log-' + level) | |||
@property | |||
def log_info(self): | |||
"Translate info-level log messages." | |||
return self._make_log_translation_func('info') | |||
@property | |||
def log_warning(self): | |||
"Translate warning-level log messages." | |||
return self._make_log_translation_func('warning') | |||
@property | |||
def log_error(self): | |||
"Translate error-level log messages." | |||
return self._make_log_translation_func('error') | |||
@property | |||
def log_critical(self): | |||
"Translate critical-level log messages." | |||
return self._make_log_translation_func('critical') | |||
# NOTE(dhellmann): When this module moves out of the incubator into | |||
# oslo.i18n, these global variables can be moved to an integration | |||
# module within each application. | |||
# Create the global translation functions. | |||
_translators = TranslatorFactory('os_doc_tools') | |||
# The primary translation function using the well-known name "_" | |||
_ = _translators.primary | |||
# Translators for log levels. | |||
# | |||
# The abbreviated names are meant to reflect the usual use of a short | |||
# name like '_'. The "L" is for "log" and the other letter comes from | |||
# the level. | |||
_LI = _translators.log_info | |||
_LW = _translators.log_warning | |||
_LE = _translators.log_error | |||
_LC = _translators.log_critical | |||
# NOTE(dhellmann): End of globals that will move to the application's | |||
# integration module. | |||
def enable_lazy(): | |||
"""Convenience function for configuring _() to use lazy gettext | |||
Call this at the start of execution to enable the gettextutils._ | |||
function to use lazy gettext functionality. This is useful if | |||
your project is importing _ directly instead of using the | |||
gettextutils.install() way of importing the _ function. | |||
""" | |||
global USE_LAZY | |||
USE_LAZY = True | |||
def install(domain): | |||
"""Install a _() function using the given translation domain. | |||
Given a translation domain, install a _() function using gettext's | |||
install() function. | |||
The main difference from gettext.install() is that we allow | |||
overriding the default localedir (e.g. /usr/share/locale) using | |||
a translation-domain-specific environment variable (e.g. | |||
NOVA_LOCALEDIR). | |||
Note that to enable lazy translation, enable_lazy must be | |||
called. | |||
:param domain: the translation domain | |||
""" | |||
from six import moves | |||
tf = TranslatorFactory(domain) | |||
moves.builtins.__dict__['_'] = tf.primary | |||
class Message(six.text_type): | |||
"""A Message object is a unicode object that can be translated. | |||
Translation of Message is done explicitly using the translate() method. | |||
For all non-translation intents and purposes, a Message is simply unicode, | |||
and can be treated as such. | |||
""" | |||
def __new__(cls, msgid, msgtext=None, params=None, | |||
domain='os_doc_tools', *args): | |||
"""Create a new Message object. | |||
In order for translation to work gettext requires a message ID, this | |||
msgid will be used as the base unicode text. It is also possible | |||
for the msgid and the base unicode text to be different by passing | |||
the msgtext parameter. | |||
""" | |||
# If the base msgtext is not given, we use the default translation | |||
# of the msgid (which is in English) just in case the system locale is | |||
# not English, so that the base text will be in that locale by default. | |||
if not msgtext: | |||
msgtext = Message._translate_msgid(msgid, domain) | |||
# We want to initialize the parent unicode with the actual object that | |||
# would have been plain unicode if 'Message' was not enabled. | |||
msg = super(Message, cls).__new__(cls, msgtext) | |||
msg.msgid = msgid | |||
msg.domain = domain | |||
msg.params = params | |||
return msg | |||
def translate(self, desired_locale=None): | |||
"""Translate this message to the desired locale. | |||
:param desired_locale: The desired locale to translate the message to, | |||
if no locale is provided the message will be | |||
translated to the system's default locale. | |||
:returns: the translated message in unicode | |||
""" | |||
translated_message = Message._translate_msgid(self.msgid, | |||
self.domain, | |||
desired_locale) | |||
if self.params is None: | |||
# No need for more translation | |||
return translated_message | |||
# This Message object may have been formatted with one or more | |||
# Message objects as substitution arguments, given either as a single | |||
# argument, part of a tuple, or as one or more values in a dictionary. | |||
# When translating this Message we need to translate those Messages too | |||
translated_params = _translate_args(self.params, desired_locale) | |||
translated_message = translated_message % translated_params | |||
return translated_message | |||
@staticmethod | |||
def _translate_msgid(msgid, domain, desired_locale=None): | |||
if not desired_locale: | |||
system_locale = locale.getdefaultlocale() | |||
# If the system locale is not available to the runtime use English | |||
if not system_locale[0]: | |||
desired_locale = 'en_US' | |||
else: | |||
desired_locale = system_locale[0] | |||
locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR') | |||
lang = gettext.translation(domain, | |||
localedir=locale_dir, | |||
languages=[desired_locale], | |||
fallback=True) | |||
if six.PY3: | |||
translator = lang.gettext | |||
else: | |||
translator = lang.ugettext | |||
translated_message = translator(msgid) | |||
return translated_message | |||
def __mod__(self, other): | |||
# When we mod a Message we want the actual operation to be performed | |||
# by the parent class (i.e. unicode()), the only thing we do here is | |||
# save the original msgid and the parameters in case of a translation | |||
params = self._sanitize_mod_params(other) | |||
unicode_mod = super(Message, self).__mod__(params) | |||
modded = Message(self.msgid, | |||
msgtext=unicode_mod, | |||
params=params, | |||
domain=self.domain) | |||
return modded | |||
def _sanitize_mod_params(self, other): | |||
"""Sanitize the object being modded with this Message. | |||
- Add support for modding 'None' so translation supports it | |||
- Trim the modded object, which can be a large dictionary, to only | |||
those keys that would actually be used in a translation | |||
- Snapshot the object being modded, in case the message is | |||
translated, it will be used as it was when the Message was created | |||
""" | |||
if other is None: | |||
params = (other,) | |||
elif isinstance(other, dict): | |||
# Merge the dictionaries | |||
# Copy each item in case one does not support deep copy. | |||
params = {} | |||
if isinstance(self.params, dict): | |||
for key, val in self.params.items(): | |||
params[key] = self._copy_param(val) | |||
for key, val in other.items(): | |||
params[key] = self._copy_param(val) | |||
else: | |||
params = self._copy_param(other) | |||
return params | |||
def _copy_param(self, param): | |||
try: | |||
return copy.deepcopy(param) | |||
except Exception: | |||
# Fallback to casting to unicode this will handle the | |||
# python code-like objects that can't be deep-copied | |||
return six.text_type(param) | |||
def __add__(self, other): | |||
msg = _('Message objects do not support addition.') | |||
raise TypeError(msg) | |||
def __radd__(self, other): | |||
return self.__add__(other) | |||
if six.PY2: | |||
def __str__(self): | |||
# NOTE(luisg): Logging in python 2.6 tries to str() log records, | |||
# and it expects specifically a UnicodeError in order to proceed. | |||
msg = _('Message objects do not support str() because they may ' | |||
'contain non-ascii characters. ' | |||
'Please use unicode() or translate() instead.') | |||
raise UnicodeError(msg) | |||
def get_available_languages(domain): | |||
"""Lists the available languages for the given translation domain. | |||
:param domain: the domain to get languages for | |||
""" | |||
if domain in _AVAILABLE_LANGUAGES: | |||
return copy.copy(_AVAILABLE_LANGUAGES[domain]) | |||
localedir = '%s_LOCALEDIR' % domain.upper() | |||
find = lambda x: gettext.find(domain, | |||
localedir=os.environ.get(localedir), | |||
languages=[x]) | |||
# NOTE(mrodden): en_US should always be available (and first in case | |||
# order matters) since our in-line message strings are en_US | |||
language_list = ['en_US'] | |||
# NOTE(luisg): Babel <1.0 used a function called list(), which was | |||
# renamed to locale_identifiers() in >=1.0, the requirements master list | |||
# requires >=0.9.6, uncapped, so defensively work with both. We can remove | |||
# this check when the master list updates to >=1.0, and update all projects | |||
list_identifiers = (getattr(localedata, 'list', None) or | |||
getattr(localedata, 'locale_identifiers')) | |||
locale_identifiers = list_identifiers() | |||
for i in locale_identifiers: | |||
if find(i) is not None: | |||
language_list.append(i) | |||
# NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported | |||
# locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they | |||
# are perfectly legitimate locales: | |||
# https://github.com/mitsuhiko/babel/issues/37 | |||
# In Babel 1.3 they fixed the bug and they support these locales, but | |||
# they are still not explicitly "listed" by locale_identifiers(). | |||
# That is why we add the locales here explicitly if necessary so that | |||
# they are listed as supported. | |||
aliases = {'zh': 'zh_CN', | |||
'zh_Hant_HK': 'zh_HK', | |||
'zh_Hant': 'zh_TW', | |||
'fil': 'tl_PH'} | |||
for (locale_, alias) in six.iteritems(aliases): | |||
if locale_ in language_list and alias not in language_list: | |||
language_list.append(alias) | |||
_AVAILABLE_LANGUAGES[domain] = language_list | |||
return copy.copy(language_list) | |||
def translate(obj, desired_locale=None): | |||
"""Gets the translated unicode representation of the given object. | |||
If the object is not translatable it is returned as-is. | |||
If the locale is None the object is translated to the system locale. | |||
:param obj: the object to translate | |||
:param desired_locale: the locale to translate the message to, if None the | |||
default system locale will be used | |||
:returns: the translated object in unicode, or the original object if | |||
it could not be translated | |||
""" | |||
message = obj | |||
if not isinstance(message, Message): | |||
# If the object to translate is not already translatable, | |||
# let's first get its unicode representation | |||
message = six.text_type(obj) | |||
if isinstance(message, Message): | |||
# Even after unicoding() we still need to check if we are | |||
# running with translatable unicode before translating | |||
return message.translate(desired_locale) | |||
return obj | |||
def _translate_args(args, desired_locale=None): | |||
"""Translates all the translatable elements of the given arguments object. | |||
This method is used for translating the translatable values in method | |||
arguments which include values of tuples or dictionaries. | |||
If the object is not a tuple or a dictionary the object itself is | |||
translated if it is translatable. | |||
If the locale is None the object is translated to the system locale. | |||
:param args: the args to translate | |||
:param desired_locale: the locale to translate the args to, if None the | |||
default system locale will be used | |||
:returns: a new args object with the translated contents of the original | |||
""" | |||
if isinstance(args, tuple): | |||
return tuple(translate(v, desired_locale) for v in args) | |||
if isinstance(args, dict): | |||
translated_dict = {} | |||
for (k, v) in six.iteritems(args): | |||
translated_v = translate(v, desired_locale) | |||
translated_dict[k] = translated_v | |||
return translated_dict | |||
return translate(args, desired_locale) | |||
class TranslationHandler(handlers.MemoryHandler): | |||
"""Handler that translates records before logging them. | |||
The TranslationHandler takes a locale and a target logging.Handler object | |||
to forward LogRecord objects to after translating them. This handler | |||
depends on Message objects being logged, instead of regular strings. | |||
The handler can be configured declaratively in the logging.conf as follows: | |||
[handlers] | |||
keys = translatedlog, translator | |||
[handler_translatedlog] | |||
class = handlers.WatchedFileHandler | |||
args = ('/var/log/api-localized.log',) | |||
formatter = context | |||
[handler_translator] | |||
class = openstack.common.log.TranslationHandler | |||
target = translatedlog | |||
args = ('zh_CN',) | |||
If the specified locale is not available in the system, the handler will | |||
log in the default locale. | |||
""" | |||
def __init__(self, locale=None, target=None): | |||
"""Initialize a TranslationHandler | |||
:param locale: locale to use for translating messages | |||
:param target: logging.Handler object to forward | |||
LogRecord objects to after translation | |||
""" | |||
# NOTE(luisg): In order to allow this handler to be a wrapper for | |||
# other handlers, such as a FileHandler, and still be able to | |||
# configure it using logging.conf, this handler has to extend | |||
# MemoryHandler because only the MemoryHandlers' logging.conf | |||
# parsing is implemented such that it accepts a target handler. | |||
handlers.MemoryHandler.__init__(self, capacity=0, target=target) | |||
self.locale = locale | |||
def setFormatter(self, fmt): | |||
self.target.setFormatter(fmt) | |||
def emit(self, record): | |||
# We save the message from the original record to restore it | |||
# after translation, so other handlers are not affected by this | |||
original_msg = record.msg | |||
original_args = record.args | |||
try: | |||
self._translate_and_log_record(record) | |||
finally: | |||
record.msg = original_msg | |||
record.args = original_args | |||
def _translate_and_log_record(self, record): | |||
record.msg = translate(record.msg, self.locale) | |||
# In addition to translating the message, we also need to translate | |||
# arguments that were passed to the log method that were not part | |||
# of the main message e.g., log.info(_('Some message %s'), this_one)) | |||
record.args = _translate_args(record.args, self.locale) | |||
self.target.emit(record) |
@ -1,73 +0,0 @@ | |||
# Copyright 2011 OpenStack Foundation. | |||
# All 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. | |||
""" | |||
Import related utilities and helper functions. | |||
""" | |||
import sys | |||
import traceback | |||
def import_class(import_str): | |||
"""Returns a class from a string including module and class.""" | |||
mod_str, _sep, class_str = import_str.rpartition('.') | |||
__import__(mod_str) | |||
try: | |||
return getattr(sys.modules[mod_str], class_str) | |||
except AttributeError: | |||
raise ImportError('Class %s cannot be found (%s)' % | |||
(class_str, | |||
traceback.format_exception(*sys.exc_info()))) | |||
def import_object(import_str, *args, **kwargs): | |||
"""Import a class and return an instance of it.""" | |||
return import_class(import_str)(*args, **kwargs) | |||
def import_object_ns(name_space, import_str, *args, **kwargs): | |||
"""Tries to import object from default namespace. | |||
Imports a class and return an instance of it, first by trying | |||
to find the class in a default namespace, then failing back to | |||
a full path if not found in the default namespace. | |||
""" | |||
import_value = "%s.%s" % (name_space, import_str) | |||
try: | |||
return import_class(import_value)(*args, **kwargs) | |||
except ImportError: | |||
return import_class(import_str)(*args, **kwargs) | |||
def import_module(import_str): | |||
"""Import a module.""" | |||
__import__(import_str) | |||
return sys.modules[import_str] | |||
def import_versioned_module(version, submodule=None): | |||
module = 'os_doc_tools.v%s' % version | |||
if submodule: | |||
module = '.'.join((module, submodule)) | |||
return import_module(module) | |||
def try_import(import_str, default=None): | |||
"""Try to import a module and if it fails return default.""" | |||
try: | |||
return import_module(import_str) | |||
except ImportError: | |||
return default |
@ -1,190 +0,0 @@ | |||
# Copyright 2010 United States Government as represented by the | |||
# Administrator of the National Aeronautics and Space Administration. | |||
# Copyright 2011 Justin Santa Barbara | |||
# All 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. | |||
''' | |||
JSON related utilities. | |||
This module provides a few things: | |||
1) A handy function for getting an object down to something that can be | |||
JSON serialized. See to_primitive(). | |||
2) Wrappers around loads() and dumps(). The dumps() wrapper will | |||
automatically use to_primitive() for you if needed. | |||
3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson | |||
is available. | |||
''' | |||
import codecs | |||
import datetime | |||
import functools | |||
import inspect | |||
import itertools | |||
import sys | |||
if sys.version_info < (2, 7): | |||
# On Python <= 2.6, json module is not C boosted, so try to use | |||
# simplejson module if available | |||
try: | |||
import simplejson as json | |||
except ImportError: | |||
import json | |||
else: | |||
import json | |||
import six | |||
import six.moves.xmlrpc_client as xmlrpclib | |||
from os_doc_tools.openstack.common import gettextutils | |||
from os_doc_tools.openstack.common import importutils | |||
from os_doc_tools.openstack.common import strutils | |||
from os_doc_tools.openstack.common import timeutils | |||
netaddr = importutils.try_import("netaddr") | |||
_nasty_type_tests = [inspect.ismodule, inspect.isclass, inspect.ismethod, | |||
inspect.isfunction, inspect.isgeneratorfunction, | |||
inspect.isgenerator, inspect.istraceback, inspect.isframe, | |||
inspect.iscode, inspect.isbuiltin, inspect.isroutine, | |||
inspect.isabstract] | |||
_simple_types = (six.string_types + six.integer_types | |||
+ (type(None), bool, float)) | |||
def to_primitive(value, convert_instances=False, convert_datetime=True, | |||
level=0, max_depth=3): | |||
"""Convert a complex object into primitives. | |||
Handy for JSON serialization. We can optionally handle instances, | |||
but since this is a recursive function, we could have cyclical | |||
data structures. | |||
To handle cyclical data structures we could track the actual objects | |||
visited in a set, but not all objects are hashable. Instead we just | |||
track the depth of the object inspections and don't go too deep. | |||
Therefore, convert_instances=True is lossy ... be aware. | |||
""" | |||
# handle obvious types first - order of basic types determined by running | |||
# full tests on nova project, resulting in the following counts: | |||
# 572754 <type 'NoneType'> | |||
# 460353 <type 'int'> | |||
# 379632 <type 'unicode'> | |||
# 274610 <type 'str'> | |||
# 199918 <type 'dict'> | |||
# 114200 <type 'datetime.datetime'> | |||
# 51817 <type 'bool'> | |||
# 26164 <type 'list'> | |||
# 6491 <type 'float'> | |||
# 283 <type 'tuple'> | |||
# 19 <type 'long'> | |||
if isinstance(value, _simple_types): | |||
return value | |||
if isinstance(value, datetime.datetime): | |||
if convert_datetime: | |||
return timeutils.strtime(value) | |||
else: | |||
return value | |||
# value of itertools.count doesn't get caught by nasty_type_tests | |||
# and results in infinite loop when list(value) is called. | |||
if type(value) == itertools.count: | |||
return six.text_type(value) | |||
# FIXME(vish): Workaround for LP bug 852095. Without this workaround, | |||
# tests that raise an exception in a mocked method that | |||
# has a @wrap_exception with a notifier will fail. If | |||
# we up the dependency to 0.5.4 (when it is released) we | |||
# can remove this workaround. | |||
if getattr(value, '__module__', None) == 'mox': | |||
return 'mock' | |||
if level > max_depth: | |||
return '?' | |||
# The try block may not be necessary after the class check above, | |||
# but just in case ... | |||
try: | |||
recursive = functools.partial(to_primitive, | |||
convert_instances=convert_instances, | |||
convert_datetime=convert_datetime, | |||
level=level, | |||
max_depth=max_depth) | |||
if isinstance(value, dict): | |||
return dict((k, recursive(v)) for k, v in six.iteritems(value)) | |||
elif isinstance(value, (list, tuple)): | |||
return [recursive(lv) for lv in value] | |||
# It's not clear why xmlrpclib created their own DateTime type, but | |||
# for our purposes, make it a datetime type which is explicitly | |||
# handled | |||
if isinstance(value, xmlrpclib.DateTime): | |||
value = datetime.datetime(*tuple(value.timetuple())[:6]) | |||
if convert_datetime and isinstance(value, datetime.datetime): | |||
return timeutils.strtime(value) | |||
elif isinstance(value, gettextutils.Message): | |||
return value.data | |||
elif hasattr(value, 'iteritems'): | |||
return recursive(dict(value.iteritems()), level=level + 1) | |||
elif hasattr(value, '__iter__'): | |||
return recursive(list(value)) | |||
elif convert_instances and hasattr(value, '__dict__'): | |||
# Likely an instance of something. Watch for cycles. | |||
# Ignore class member vars. | |||
return recursive(value.__dict__, level=level + 1) | |||
elif netaddr and isinstance(value, netaddr.IPAddress): | |||
return six.text_type(value) | |||
else: | |||
if any(test(value) for test in _nasty_type_tests): | |||
return six.text_type(value) | |||
return value | |||
except TypeError: | |||
# Class objects are tricky since they may define something like | |||
# __iter__ defined but it isn't callable as list(). | |||
return six.text_type(value) | |||
def dumps(value, default=to_primitive, **kwargs): | |||
return json.dumps(value, default=default, **kwargs) | |||
def dump(obj, fp, *args, **kwargs): | |||
return json.dump(obj, fp, *args, **kwargs) | |||
def loads(s, encoding='utf-8', **kwargs): | |||
return json.loads(strutils.safe_decode(s, encoding), **kwargs) | |||
def load(fp, encoding='utf-8', **kwargs): | |||
return json.load(codecs.getreader(encoding)(fp), **kwargs) | |||
try: | |||
import anyjson | |||
except ImportError: | |||
pass | |||
else: | |||
anyjson._modules.append((__name__, 'dumps', TypeError, | |||
'loads', ValueError, 'load')) | |||
anyjson.force_implementation(__name__) |
@ -1,45 +0,0 @@ | |||
# Copyright 2011 OpenStack Foundation. | |||
# All 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. | |||
"""Local storage of variables using weak references""" | |||
import threading | |||
import weakref | |||
class WeakLocal(threading.local): | |||
def __getattribute__(self, attr): | |||
rval = super(WeakLocal, self).__getattribute__(attr) | |||
if rval: | |||
# NOTE(mikal): this bit is confusing. What is stored is a weak | |||
# reference, not the value itself. We therefore need to lookup | |||
# the weak reference and return the inner value here. | |||
rval = rval() | |||
return rval | |||
def __setattr__(self, attr, value): | |||
value = weakref.ref(value) | |||
return super(WeakLocal, self).__setattr__(attr, value) | |||
# NOTE(mikal): the name "store" should be deprecated in the future | |||
store = WeakLocal() | |||
# A "weak" store uses weak references and allows an object to fall out of scope | |||
# when it falls out of scope in the code that uses the thread local storage. A | |||
# "strong" store will hold a reference to the object so that it never falls out | |||
# of scope. | |||
weak_store = WeakLocal() | |||
strong_store = threading.local() |
@ -1,703 +0,0 @@ | |||
# Copyright 2011 OpenStack Foundation. | |||
# Copyright 2010 United States Government as represented by the | |||
# Administrator of the National Aeronautics and Space Administration. | |||
# All 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. | |||
"""OpenStack logging handler. | |||
This module adds to logging functionality by adding the option to specify | |||
a context object when calling the various log methods. If the context object | |||
is not specified, default formatting is used. Additionally, an instance uuid | |||
may be passed as part of the log message, which is intended to make it easier | |||
for admins to find messages related to a specific instance. | |||
It also allows setting of formatting information through conf. | |||
""" | |||
import inspect | |||
import itertools | |||
import logging | |||
import logging.config | |||
import logging.handlers | |||
import os | |||
import sys | |||
import traceback | |||
from oslo_config import cfg | |||
import six | |||
from six import moves | |||
_PY26 = sys.version_info[0:2] == (2, 6) | |||
from os_doc_tools.openstack.common.gettextutils import _ | |||
from os_doc_tools.openstack.common import importutils | |||
from os_doc_tools.openstack.common import jsonutils | |||
from os_doc_tools.openstack.common import local | |||
# NOTE(flaper87): Pls, remove when graduating this module | |||
# from the incubator. | |||
from os_doc_tools.openstack.common.strutils import mask_password # noqa | |||
_DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" | |||
common_cli_opts = [ | |||
cfg.BoolOpt('debug', | |||
short='d', | |||
default=False, | |||
help='Print debugging output (set logging level to ' | |||
'DEBUG instead of default WARNING level).'), | |||
cfg.BoolOpt('verbose', | |||
short='v', | |||
default=False, | |||
help='Print more verbose output (set logging level to ' | |||
'INFO instead of default WARNING level).'), | |||
] | |||
logging_cli_opts = [ | |||
cfg.StrOpt('log-config-append', | |||
metavar='PATH', | |||
deprecated_name='log-config', | |||
help='The name of a logging configuration file. This file ' | |||
'is appended to any existing logging configuration ' | |||
'files. For details about logging configuration files, ' | |||
'see the Python logging module documentation.'), | |||
cfg.StrOpt('log-format', | |||
metavar='FORMAT', | |||
help='DEPRECATED. ' | |||
'A logging.Formatter log message format string which may ' | |||
'use any of the available logging.LogRecord attributes. ' | |||
'This option is deprecated. Please use ' | |||
'logging_context_format_string and ' | |||
'logging_default_format_string instead.'), | |||
cfg.StrOpt('log-date-format', | |||
default=_DEFAULT_LOG_DATE_FORMAT, | |||
metavar='DATE_FORMAT', | |||
help='Format string for %%(asctime)s in log records. ' | |||
'Default: %(default)s .'), | |||
cfg.StrOpt('log-file', | |||
metavar='PATH', | |||
deprecated_name='logfile', | |||
help='(Optional) Name of log file to output to. ' | |||
'If no default is set, logging will go to stdout.'), | |||
cfg.StrOpt('log-dir', | |||
deprecated_name='logdir', | |||
help='(Optional) The base directory used for relative ' | |||
'--log-file paths.'), | |||
cfg.BoolOpt('use-syslog', | |||
default=False, | |||
help='Use syslog for logging. ' | |||
'Existing syslog format is DEPRECATED during I, ' | |||
'and will change in J to honor RFC5424.'), | |||
cfg.BoolOpt('use-syslog-rfc-format', | |||
# TODO(bogdando) remove or use True after existing | |||
# syslog format deprecation in J | |||
default=False, | |||
help='(Optional) Enables or disables syslog rfc5424 format ' | |||
'for logging. If enabled, prefixes the MSG part of the ' | |||
'syslog message with APP-NAME (RFC5424). The ' | |||
'format without the APP-NAME is deprecated in I, ' | |||
'and will be removed in J.'), | |||
cfg.StrOpt('syslog-log-facility', | |||
default='LOG_USER', | |||
help='Syslog facility to receive log lines.') | |||
] | |||
generic_log_opts = [ | |||
cfg.BoolOpt('use_stderr', | |||
default=True, | |||
help='Log output to standard error.') | |||
] | |||
DEFAULT_LOG_LEVELS = ['amqp=WARN', 'amqplib=WARN', 'boto=WARN', | |||
'qpid=WARN', 'sqlalchemy=WARN', 'suds=INFO', | |||
'oslo.messaging=INFO', 'iso8601=WARN', | |||
'requests.packages.urllib3.connectionpool=WARN', | |||
'urllib3.connectionpool=WARN', 'websocket=WARN'] | |||
log_opts = [ | |||
cfg.StrOpt('logging_context_format_string', | |||
default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' | |||
'%(name)s [%(request_id)s %(user_identity)s] ' | |||
'%(instance)s%(message)s', | |||
help='Format string to use for log messages with context.'), | |||
cfg.StrOpt('logging_default_format_string', | |||
default='%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' | |||
'%(name)s [-] %(instance)s%(message)s', | |||
help='Format string to use for log messages without context.'), | |||
cfg.StrOpt('logging_debug_format_suffix', | |||
default='%(funcName)s %(pathname)s:%(lineno)d', | |||
help='Data to append to log format when level is DEBUG.'), | |||
cfg.StrOpt('logging_exception_prefix', | |||
default='%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s ' | |||
'%(instance)s', | |||
help='Prefix each line of exception output with this format.'), | |||
cfg.ListOpt('default_log_levels', | |||
default=DEFAULT_LOG_LEVELS, | |||
help='List of logger=LEVEL pairs.'), | |||
cfg.BoolOpt('publish_errors', | |||
default=False, | |||
help='Enables or disables publication of error events.'), | |||
cfg.BoolOpt('fatal_deprecations', | |||
default=False, | |||
help='Enables or disables fatal status of deprecations.'), | |||
# NOTE(mikal): there are two options here because sometimes we are handed | |||
# a full instance (and could include more information), and other times we | |||
# are just handed a UUID for the instance. | |||
cfg.StrOpt('instance_format', | |||
default='[instance: %(uuid)s] ', | |||
help='The format for an instance that is passed with the log ' | |||
'message.'), | |||
cfg.StrOpt('instance_uuid_format', | |||
default='[instance: %(uuid)s] ', | |||
help='The format for an instance UUID that is passed with the ' | |||
'log message.'), | |||
] | |||
CONF = cfg.CONF | |||
CONF.register_cli_opts(common_cli_opts) | |||
CONF.register_cli_opts(logging_cli_opts) | |||
CONF.register_opts(generic_log_opts) | |||
CONF.register_opts(log_opts) | |||
# our new audit level | |||
# NOTE(jkoelker) Since we synthesized an audit level, make the logging | |||
# module aware of it so it acts like other levels. | |||
logging.AUDIT = logging.INFO + 1 | |||
logging.addLevelName(logging.AUDIT, 'AUDIT') | |||
try: | |||
NullHandler = logging.NullHandler | |||
except AttributeError: # NOTE(jkoelker) NullHandler added in Python 2.7 | |||
class NullHandler(logging.Handler): | |||
def handle(self, record): | |||
pass | |||
def emit(self, record): | |||
pass | |||
def createLock(self): | |||
self.lock = None | |||
def _dictify_context(context): | |||
if context is None: | |||
return None | |||
if not isinstance(context, dict) and getattr(context, 'to_dict', None): | |||
context = context.to_dict() | |||
return context | |||
def _get_binary_name(): | |||
return os.path.basename(inspect.stack()[-1][1]) | |||
def _get_log_file_path(binary=None): | |||
logfile = CONF.log_file | |||
logdir = CONF.log_dir | |||
if logfile and not logdir: | |||
return logfile | |||
if logfile and logdir: | |||
return os.path.join(logdir, logfile) | |||
if logdir: | |||
binary = binary or _get_binary_name() | |||
return '%s.log' % (os.path.join(logdir, binary),) | |||
return None | |||
class BaseLoggerAdapter(logging.LoggerAdapter): | |||
def audit(self, msg, *args, **kwargs): | |||
self.log(logging.AUDIT, msg, *args, **kwargs) | |||
def isEnabledFor(self, level): | |||
if _PY26: | |||
# This method was added in python 2.7 (and it does the exact | |||
# same logic, so we need to do the exact same logic so that | |||
# python 2.6 has this capability as well). | |||
return self.logger.isEnabledFor(level) | |||
else: | |||
return super(BaseLoggerAdapter, self).isEnabledFor(level) | |||
class LazyAdapter(BaseLoggerAdapter): | |||
def __init__(self, name='unknown', version='unknown'): | |||
self._logger = None | |||
self.extra = {} | |||
self.name = name | |||
self.version = version | |||
@property | |||
def logger(self): | |||
if not self._logger: | |||
self._logger = getLogger(self.name, self.version) | |||
if six.PY3: | |||
# In Python 3, the code fails because the 'manager' attribute | |||
# cannot be found when using a LoggerAdapter as the | |||
# underlying logger. Work around this issue. | |||
self._logger.manager = self._logger.logger.manager | |||
return self._logger | |||
class ContextAdapter(BaseLoggerAdapter): | |||
warn = logging.LoggerAdapter.warning | |||
def __init__(self, logger, project_name, version_string): | |||
self.logger = logger | |||
self.project = project_name | |||
self.version = version_string | |||
self._deprecated_messages_sent = dict() | |||
@property | |||
def handlers(self): | |||
return self.logger.handlers | |||
def deprecated(self, msg, *args, **kwargs): | |||
"""Call this method when a deprecated feature is used. | |||
If the system is configured for fatal deprecations then the message | |||
is logged at the 'critical' level and :class:`DeprecatedConfig` will | |||
be raised. | |||
Otherwise, the message will be logged (once) at the 'warn' level. | |||
:raises: :class:`DeprecatedConfig` if the system is configured for | |||
fatal deprecations. | |||
""" | |||
stdmsg = _("Deprecated: %s") % msg | |||
if CONF.fatal_deprecations: | |||
self.critical(stdmsg, *args, **kwargs) | |||
raise DeprecatedConfig(msg=stdmsg) | |||
# Using a list because a tuple with dict can't be stored in a set. | |||
sent_args = self._deprecated_messages_sent.setdefault(msg, list()) | |||
if args in sent_args: | |||
# Already logged this message, so don't log it again. | |||
return | |||
sent_args.append(args) | |||
self.warn(stdmsg, *args, **kwargs) | |||
def process(self, msg, kwargs): | |||
# NOTE(mrodden): catch any Message/other object and | |||
# coerce to unicode before they can get | |||
# to the python logging and possibly | |||
# cause string encoding trouble | |||
if not isinstance(msg, six.string_types): | |||
msg = six.text_type(msg) | |||
if 'extra' not in kwargs: | |||
kwargs['extra'] = {} | |||
extra = kwargs['extra'] | |||
context = kwargs.pop('context', None) | |||
if not context: | |||
context = getattr(local.store, 'context', None) | |||
if context: | |||
extra.update(_dictify_context(context)) | |||
instance = kwargs.pop('instance', None) | |||
instance_uuid = (extra.get('instance_uuid') or | |||
kwargs.pop('instance_uuid', None)) | |||
instance_extra = '' | |||
if instance: | |||
instance_extra = CONF.instance_format % instance | |||
elif instance_uuid: | |||
instance_extra = (CONF.instance_uuid_format | |||
% {'uuid': instance_uuid}) | |||
extra['instance'] = instance_extra | |||
extra.setdefault('user_identity', kwargs.pop('user_identity', None)) | |||
extra['project'] = self.project | |||
extra['version'] = self.version | |||
extra['extra'] = extra.copy() | |||
return msg, kwargs | |||
class JSONFormatter(logging.Formatter): | |||
def __init__(self, fmt=None, datefmt=None): | |||
# NOTE(jkoelker) we ignore the fmt argument, but its still there | |||
# since logging.config.fileConfig passes it. | |||
self.datefmt = datefmt | |||
def formatException(self, ei, strip_newlines=True): | |||
lines = traceback.format_exception(*ei) | |||
if strip_newlines: | |||
lines = [moves.filter( | |||
lambda x: x, | |||
line.rstrip().splitlines()) for line in lines] | |||
lines = list(itertools.chain(*lines)) | |||
return lines | |||
def format(self, record): | |||
message = {'message': record.getMessage(), | |||
'asctime': self.formatTime(record, self.datefmt), | |||
'name': record.name, | |||
'msg': record.msg, | |||
'args': record.args, | |||
'levelname': record.levelname, | |||
'levelno': record.levelno, | |||
'pathname': record.pathname, | |||
'filename': record.filename, | |||
'module': record.module, | |||
'lineno': record.lineno, | |||
'funcname': record.funcName, | |||
'created': record.created, | |||
'msecs': record.msecs, | |||
'relative_created': record.relativeCreated, | |||
'thread': record.thread, | |||
'thread_name': record.threadName, | |||
'process_name': record.processName, | |||
'process': record.process, | |||
'traceback': None} | |||
if hasattr(record, 'extra'): | |||
message['extra'] = record.extra | |||
if record.exc_info: | |||
message['traceback'] = self.formatException(record.exc_info) | |||
return jsonutils.dumps(message) | |||
def _create_logging_excepthook(product_name): | |||
def logging_excepthook(exc_type, value, tb): | |||
extra = {'exc_info': (exc_type, value, tb)} | |||
getLogger(product_name).critical( | |||
"".join(traceback.format_exception_only(exc_type, value)), | |||
**extra) | |||
return logging_excepthook | |||
class LogConfigError(Exception): | |||
message = _('Error loading logging config %(log_config)s: %(err_msg)s') | |||
def __init__(self, log_config, err_msg): | |||
self.log_config = log_config | |||
self.err_msg = err_msg | |||
def __str__(self): | |||
return self.message % dict(log_config=self.log_config, | |||
err_msg=self.err_msg) | |||
def _load_log_config(log_config_append): | |||
try: | |||
logging.config.fileConfig(log_config_append, | |||
disable_existing_loggers=False) | |||
except (moves.configparser.Error, KeyError) as exc: | |||
raise LogConfigError(log_config_append, six.text_type(exc)) | |||
def setup(product_name, version='unknown'): | |||
"""Setup logging.""" | |||
if CONF.log_config_append: | |||
_load_log_config(CONF.log_config_append) | |||
else: | |||
_setup_logging_from_conf(product_name, version) | |||
sys.excepthook = _create_logging_excepthook(product_name) | |||
def set_defaults(logging_context_format_string=None, | |||
default_log_levels=None): | |||
# Just in case the caller is not setting the | |||
# default_log_level. This is insurance because | |||
# we introduced the default_log_level parameter | |||
# later in a backwards in-compatible change | |||
if default_log_levels is not None: | |||
cfg.set_defaults( | |||
log_opts, | |||
default_log_levels=default_log_levels) | |||
if logging_context_format_string is not None: | |||
cfg.set_defaults( | |||
log_opts, | |||
logging_context_format_string=logging_context_format_string) | |||
def _find_facility_from_conf(): | |||
facility_names = logging.handlers.SysLogHandler.facility_names | |||
facility = getattr(logging.handlers.SysLogHandler, | |||
CONF.syslog_log_facility, | |||
None) | |||
if facility is None and CONF.syslog_log_facility in facility_names: | |||
facility = facility_names.get(CONF.syslog_log_facility) | |||
if facility is None: | |||
valid_facilities = facility_names.keys() | |||
consts = ['LOG_AUTH', 'LOG_AUTHPRIV', 'LOG_CRON', 'LOG_DAEMON', | |||
'LOG_FTP', 'LOG_KERN', 'LOG_LPR', 'LOG_MAIL', 'LOG_NEWS', | |||
'LOG_AUTH', 'LOG_SYSLOG', 'LOG_USER', 'LOG_UUCP', | |||
'LOG_LOCAL0', 'LOG_LOCAL1', 'LOG_LOCAL2', 'LOG_LOCAL3', | |||
'LOG_LOCAL4', 'LOG_LOCAL5', 'LOG_LOCAL6', 'LOG_LOCAL7'] | |||
valid_facilities.extend(consts) | |||
raise TypeError(_('syslog facility must be one of: %s') % | |||
', '.join("'%s'" % fac | |||
for fac in valid_facilities)) | |||
return facility | |||
class RFCSysLogHandler(logging.handlers.SysLogHandler): | |||
def __init__(self, *args, **kwargs): | |||
self.binary_name = _get_binary_name() | |||
# Do not use super() unless type(logging.handlers.SysLogHandler) | |||
# is 'type' (Python 2.7). | |||
# Use old style calls, if the type is 'classobj' (Python 2.6) | |||
logging.handlers.SysLogHandler.__init__(self, *args, **kwargs) | |||
def format(self, record): | |||
# Do not use super() unless type(logging.handlers.SysLogHandler) | |||
# is 'type' (Python 2.7). | |||
# Use old style calls, if the type is 'classobj' (Python 2.6) | |||
msg = logging.handlers.SysLogHandler.format(self, record) | |||
msg = self.binary_name + ' ' + msg | |||
return msg | |||
def _setup_logging_from_conf(project, version): | |||
log_root = getLogger(None).logger | |||
for handler in log_root.handlers: | |||
log_root.removeHandler(handler) | |||
if CONF.use_syslog: | |||
facility = _find_facility_from_conf() | |||
# TODO(bogdando) use the format provided by RFCSysLogHandler | |||
# after existing syslog format deprecation in J | |||
if CONF.use_syslog_rfc_format: | |||
syslog = RFCSysLogHandler(address='/dev/log', | |||
facility=facility) | |||
else: | |||
syslog = logging.handlers.SysLogHandler(address='/dev/log', | |||
facility=facility) | |||
log_root.addHandler(syslog) | |||
logpath = _get_log_file_path() | |||
if logpath: | |||
filelog = logging.handlers.WatchedFileHandler(logpath) | |||
log_root.addHandler(filelog) | |||
if CONF.use_stderr: | |||
streamlog = ColorHandler() | |||
log_root.addHandler(streamlog) | |||
elif not logpath: | |||
# pass sys.stdout as a positional argument | |||
# python2.6 calls the argument strm, in 2.7 it's stream | |||
streamlog = logging.StreamHandler(sys.stdout) | |||
log_root.addHandler(streamlog) | |||
if CONF.publish_errors: | |||
try: | |||
handler = importutils.import_object( | |||
"os_doc_tools.openstack.common.log_handler.PublishErrorsHandler", | |||
logging.ERROR) | |||
except ImportError: | |||
handler = importutils.import_object( | |||
"oslo.messaging.notify.log_handler.PublishErrorsHandler", | |||
logging.ERROR) | |||
log_root.addHandler(handler) | |||
datefmt = CONF.log_date_format | |||
for handler in log_root.handlers: | |||
# NOTE(alaski): CONF.log_format overrides everything currently. This | |||
# should be deprecated in favor of context aware formatting. | |||
if CONF.log_format: | |||
handler.setFormatter(logging.Formatter(fmt=CONF.log_format, | |||
datefmt=datefmt)) | |||
log_root.info('Deprecated: log_format is now deprecated and will ' | |||
'be removed in the next release') | |||
else: | |||
handler.setFormatter(ContextFormatter(project=project, | |||
version=version, | |||
datefmt=datefmt)) | |||
if CONF.debug: | |||
log_root.setLevel(logging.DEBUG) | |||
elif CONF.verbose: | |||
log_root.setLevel(logging.INFO) | |||
else: | |||
log_root.setLevel(logging.WARNING) | |||
for pair in CONF.default_log_levels: | |||
mod, _sep, level_name = pair.partition('=') | |||
logger = logging.getLogger(mod) | |||
# NOTE(AAzza) in python2.6 Logger.setLevel doesn't convert string name | |||
# to integer code. | |||
if sys.version_info < (2, 7): | |||
level = logging.getLevelName(level_name) | |||
logger.setLevel(level) | |||
else: | |||
logger.setLevel(level_name) | |||
_loggers = {} | |||
def getLogger(name='unknown', version='unknown'): | |||
if name not in _loggers: | |||
_loggers[name] = ContextAdapter(logging.getLogger(name), | |||
name, | |||
version) | |||
return _loggers[name] | |||
def getLazyLogger(name='unknown', version='unknown'): | |||
"""Returns lazy logger. | |||
Creates a pass-through logger that does not create the real logger | |||
until it is really needed and delegates all calls to the real logger | |||
once it is created. | |||
""" | |||
return LazyAdapter(name, version) | |||
class WritableLogger(object): | |||
"""A thin wrapper that responds to `write` and logs.""" | |||
def __init__(self, logger, level=logging.INFO): | |||
self.logger = logger | |||
self.level = level | |||
def write(self, msg): | |||
self.logger.log(self.level, msg.rstrip()) | |||
class ContextFormatter(logging.Formatter): | |||
"""A context.RequestContext aware formatter configured through flags. | |||
The flags used to set format strings are: logging_context_format_string | |||
and logging_default_format_string. You can also specify | |||
logging_debug_format_suffix to append extra formatting if the log level is | |||
debug. | |||
For information about what variables are available for the formatter see: | |||
http://docs.python.org/library/logging.html#formatter | |||
If available, uses the context value stored in TLS - local.store.context | |||
""" | |||
def __init__(self, *args, **kwargs): | |||
"""Initialize ContextFormatter instance | |||
Takes additional keyword arguments which can be used in the message | |||
format string. | |||
:keyword project: project name | |||
:type project: string | |||
:keyword version: project version | |||
:type version: string | |||
""" | |||
self.project = kwargs.pop('project', 'unknown') | |||
self.version = kwargs.pop('version', 'unknown') | |||
logging.Formatter.__init__(self, *args, **kwargs) | |||
def format(self, record): | |||
"""Uses contextstring if request_id is set, otherwise default.""" | |||
# store project info | |||
record.project = self.project | |||
record.version = self.version | |||
# store request info | |||
context = getattr(local.store, 'context', None) | |||
if context: | |||
d = _dictify_context(context) | |||
for k, v in d.items(): | |||
setattr(record, k, v) | |||
# NOTE(sdague): default the fancier formatting params | |||
# to an empty string so we don't throw an exception if | |||
# they get used | |||
for key in ('instance', 'color', 'user_identity'): | |||
if key not in record.__dict__: | |||
record.__dict__[key] = '' | |||
if record.__dict__.get('request_id'): | |||
fmt = CONF.logging_context_format_string | |||
else: | |||
fmt = CONF.logging_default_format_string | |||
if (record.levelno == logging.DEBUG and | |||
CONF.logging_debug_format_suffix): | |||
fmt += " " + CONF.logging_debug_format_suffix | |||
if sys.version_info < (3, 2): | |||
self._fmt = fmt | |||
else: | |||
self._style = logging.PercentStyle(fmt) | |||
self._fmt = self._style._fmt | |||
# Cache this on the record, Logger will respect our formatted copy | |||
if record.exc_info: | |||
record.exc_text = self.formatException(record.exc_info, record) | |||
return logging.Formatter.format(self, record) | |||
def formatException(self, exc_info, record=None): | |||
"""Format exception output with CONF.logging_exception_prefix.""" | |||
if not record: | |||
return logging.Formatter.formatException(self, exc_info) | |||
stringbuffer = moves.StringIO() | |||
traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], | |||
None, stringbuffer) | |||
lines = stringbuffer.getvalue().split('\n') | |||
stringbuffer.close() | |||
if CONF.logging_exception_prefix.find('%(asctime)') != -1: | |||
record.asctime = self.formatTime(record, self.datefmt) | |||
formatted_lines = [] | |||
for line in lines: | |||
pl = CONF.logging_exception_prefix % record.__dict__ | |||
fl = '%s%s' % (pl, line) | |||
formatted_lines.append(fl) | |||
return '\n'.join(formatted_lines) | |||
class ColorHandler(logging.StreamHandler): | |||
LEVEL_COLORS = { | |||
logging.DEBUG: '\033[00;32m', # GREEN | |||
logging.INFO: '\033[00;36m', # CYAN | |||
logging.AUDIT: '\033[01;36m', # BOLD CYAN | |||
logging.WARN: '\033[01;33m', # BOLD YELLOW | |||
logging.ERROR: '\033[01;31m', # BOLD RED | |||
logging.CRITICAL: '\033[01;31m', # BOLD RED | |||
} | |||
def format(self, record): | |||
record.color = self.LEVEL_COLORS[record.levelno] | |||
return logging.StreamHandler.format(self, record) | |||
class DeprecatedConfig(Exception): | |||
message = _("Fatal call to deprecated config: %(msg)s") | |||
def __init__(self, msg): | |||
super(Exception, self).__init__(self.message % dict(msg=msg)) |
@ -1,295 +0,0 @@ | |||
# Copyright 2011 OpenStack Foundation. | |||
# All 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 | |||