Trace data class for enchanced Solum logging
* Adds easy structured logging capability * Uses thread local storage to hold trace data needed in future logging * Hooks into Oslo Log's context adapter to look via to_dict() * Utilizes Oslo Config to format the trace logging output * Includes ability to delete old trace data automatically * Will accept a RequestContext as input Partially implements blueprint logging https://blueprints.launchpad.net/solum/+spec/logging Change-Id: I9522c5eb1e2c56b1d48e444025d47d45bf0e264d
This commit is contained in:
parent
ca61f2844c
commit
f4a02a714b
|
@ -0,0 +1,19 @@
|
||||||
|
# Copyright 2014 - Rackspace
|
||||||
|
#
|
||||||
|
# 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 threading
|
||||||
|
|
||||||
|
|
||||||
|
# Make a project global TLS trace storage repository
|
||||||
|
TLS = threading.local()
|
|
@ -0,0 +1,106 @@
|
||||||
|
# Copyright 2014 - Rackspace
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# The list below holds the keys corresponding to Oslo RequestContext keys
|
||||||
|
# which are acceptable for an authenticated user to view. Everything else
|
||||||
|
# will be considered support-only data. This list may be too restrictive and
|
||||||
|
# can be relaxed more as needed. This is only used if import_context() is
|
||||||
|
# called.
|
||||||
|
_TRACE_USER_KEYS = [six.u("user"), six.u("tenant")]
|
||||||
|
|
||||||
|
|
||||||
|
class TraceData(object):
|
||||||
|
"""This class holds trace data needed for logging.
|
||||||
|
|
||||||
|
This class also provides a way to indicate the confidentiality of stored
|
||||||
|
data. This is useful for properly logging support or operator-only data
|
||||||
|
in such a way that the backend log/notification filters have a single key
|
||||||
|
to filter on. It also provides a centralized location to add secure
|
||||||
|
storage/cleanup algorithms.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize class storage."""
|
||||||
|
self.request_id = six.u("<not set>")
|
||||||
|
self._auto_clear = False
|
||||||
|
self._user_data = {}
|
||||||
|
self._support_data = {}
|
||||||
|
|
||||||
|
def import_context(self, context):
|
||||||
|
"""Accept an Oslo RequestContext and fill in data automatically."""
|
||||||
|
context_dict = context.to_dict()
|
||||||
|
for key, val in six.iteritems(context_dict):
|
||||||
|
if key == "request_id":
|
||||||
|
self.request_id = val
|
||||||
|
elif key in _TRACE_USER_KEYS:
|
||||||
|
self._user_data[key] = val
|
||||||
|
else:
|
||||||
|
self._support_data[key] = val
|
||||||
|
|
||||||
|
def user_info(self, **kwargs):
|
||||||
|
"""Add data to user-visible storage."""
|
||||||
|
self._user_data.update(kwargs)
|
||||||
|
|
||||||
|
def support_info(self, **kwargs):
|
||||||
|
"""Add confidential data to support/operator-visible only storage."""
|
||||||
|
self._support_data.update(kwargs)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""Clear data regardless of confidential status.
|
||||||
|
|
||||||
|
Note: The lightweight memory clearing below is certainly not what we
|
||||||
|
want in the end. It is just an initial/simple/portable way to do this
|
||||||
|
for now. It is only somewhat useful before garbage collection.
|
||||||
|
"""
|
||||||
|
self.request_id = six.u("<not set>")
|
||||||
|
self._user_data = {}
|
||||||
|
for key in six.iterkeys(self._support_data):
|
||||||
|
self._support_data[key] = "0" * len(self._support_data[key])
|
||||||
|
self._support_data = {}
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""Generate a dictionary for Oslo Log (requires 'request_id').
|
||||||
|
|
||||||
|
Because this class initializes request_id to <not set> the log
|
||||||
|
operation will succeed even if a UUID was not stored.
|
||||||
|
"""
|
||||||
|
if self._auto_clear is not True:
|
||||||
|
return ({"request_id": self.request_id,
|
||||||
|
"user_trace": self._user_data,
|
||||||
|
"support_trace": self._support_data})
|
||||||
|
|
||||||
|
user_data = self._user_data.copy()
|
||||||
|
support_data = self._support_data.copy()
|
||||||
|
self.clear()
|
||||||
|
return ({"request_id": self.request_id,
|
||||||
|
"user_trace": user_data,
|
||||||
|
"support_trace": support_data})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auto_clear(self):
|
||||||
|
"""_auto_clear getter."""
|
||||||
|
return self._auto_clear
|
||||||
|
|
||||||
|
@auto_clear.setter
|
||||||
|
def auto_clear(self, value):
|
||||||
|
"""_auto_clear setter to enable assertion checks.
|
||||||
|
|
||||||
|
When set to True, this class will automatically delete all trace data
|
||||||
|
after the next Oslo log call (or a to_dict() call). If the Oslo log
|
||||||
|
call does not log any data (log levels preventing it for example) then
|
||||||
|
the data is still deleted so care must be taken when using this.
|
||||||
|
"""
|
||||||
|
assert isinstance(value, bool)
|
||||||
|
self._auto_clear = value
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Copyright 2014 - Rackspace
|
||||||
|
#
|
||||||
|
# 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 solum
|
||||||
|
from solum.common import trace_data
|
||||||
|
from solum.openstack.common import context
|
||||||
|
from solum.tests import base
|
||||||
|
|
||||||
|
solum.TLS.trace = trace_data.TraceData()
|
||||||
|
|
||||||
|
# Just putting highly recognizable values in context
|
||||||
|
CONTEXT = context.RequestContext(
|
||||||
|
'_auth_token_', '_user_', '_tenant_', '_domain_', '_user_domain_',
|
||||||
|
'_project_domain_', '_is_admin_', '_read_only_', '_show_deleted_',
|
||||||
|
'_request_id_', '_instance_uuid_')
|
||||||
|
|
||||||
|
|
||||||
|
class TestTraceData(base.BaseTestCase):
|
||||||
|
"""Tests the TraceData class."""
|
||||||
|
def test_auto_clear(self):
|
||||||
|
"""auto_clear success and then a failure case."""
|
||||||
|
solum.TLS.trace.auto_clear = True
|
||||||
|
solum.TLS.trace.auto_clear = False
|
||||||
|
try:
|
||||||
|
solum.TLS.trace.auto_clear = 'fail'
|
||||||
|
except AssertionError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.assertTrue(False)
|
||||||
|
|
||||||
|
def test_import_context(self):
|
||||||
|
"""Test importing Oslo RequestContext."""
|
||||||
|
solum.TLS.trace.clear()
|
||||||
|
solum.TLS.trace.import_context(CONTEXT)
|
||||||
|
self.assertEqual(
|
||||||
|
solum.TLS.trace._user_data,
|
||||||
|
{'user': '_user_', 'tenant': '_tenant_'})
|
||||||
|
self.assertEqual(
|
||||||
|
solum.TLS.trace._support_data, (
|
||||||
|
{
|
||||||
|
'instance_uuid': '_instance_uuid_',
|
||||||
|
'read_only': '_read_only_',
|
||||||
|
'domain': '_domain_',
|
||||||
|
'show_deleted': '_show_deleted_',
|
||||||
|
'user_identity':
|
||||||
|
'_user_ _tenant_ _domain_ _user_domain_ _project_domain_',
|
||||||
|
'project_domain': '_project_domain_',
|
||||||
|
'auth_token': '_auth_token_',
|
||||||
|
'is_admin': '_is_admin_',
|
||||||
|
'user_domain': '_user_domain_'
|
||||||
|
}))
|
||||||
|
|
||||||
|
def test_info_commands(self):
|
||||||
|
"""Test trace setting functions."""
|
||||||
|
solum.TLS.trace.clear()
|
||||||
|
solum.TLS.trace.request_id = '98765'
|
||||||
|
solum.TLS.trace.user_info(ip_addr="1.2.3.4", user_id=12345)
|
||||||
|
solum.TLS.trace.support_info(confidential_data={"a": "b", "c": "d"})
|
||||||
|
self.assertEqual(
|
||||||
|
solum.TLS.trace._user_data,
|
||||||
|
{'ip_addr': '1.2.3.4', 'user_id': 12345})
|
||||||
|
self.assertEqual(
|
||||||
|
solum.TLS.trace._support_data,
|
||||||
|
{'confidential_data': {'a': 'b', 'c': 'd'}})
|
||||||
|
self.assertEqual(
|
||||||
|
solum.TLS.trace.request_id,
|
||||||
|
'98765')
|
Loading…
Reference in New Issue