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