Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/adal/log.py: 26%
58 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:05 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:05 +0000
1#------------------------------------------------------------------------------
2#
3# Copyright (c) Microsoft Corporation.
4# All rights reserved.
5#
6# This code is licensed under the MIT License.
7#
8# Permission is hereby granted, free of charge, to any person obtaining a copy
9# of this software and associated documentation files(the "Software"), to deal
10# in the Software without restriction, including without limitation the rights
11# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12# copies of the Software, and to permit persons to whom the Software is
13# furnished to do so, subject to the following conditions :
14#
15# The above copyright notice and this permission notice shall be included in
16# all copies or substantial portions of the Software.
17#
18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24# THE SOFTWARE.
25#
26#------------------------------------------------------------------------------
28import logging
29import uuid
30import traceback
32ADAL_LOGGER_NAME = 'adal-python'
34def create_log_context(correlation_id=None, enable_pii=False):
35 return {
36 'correlation_id' : correlation_id or str(uuid.uuid4()),
37 'enable_pii': enable_pii}
39def set_logging_options(options=None):
40 '''Configure adal logger, including level and handler spec'd by python
41 logging module.
43 Basic Usages::
44 >>>adal.set_logging_options({
45 >>> 'level': 'DEBUG',
46 >>> 'handler': logging.FileHandler('adal.log')
47 >>>})
48 '''
49 if options is None:
50 options = {}
51 logger = logging.getLogger(ADAL_LOGGER_NAME)
53 logger.setLevel(options.get('level', logging.ERROR))
55 handler = options.get('handler')
56 if handler:
57 handler.setLevel(logger.level)
58 logger.addHandler(handler)
60def get_logging_options():
61 '''Get logging options
63 :returns: a dict, with a key of 'level' for logging level.
64 '''
65 logger = logging.getLogger(ADAL_LOGGER_NAME)
66 level = logger.getEffectiveLevel()
67 return {
68 'level': logging.getLevelName(level)
69 }
71class Logger(object):
72 '''wrapper around python built-in logging to log correlation_id, and stack
73 trace through keyword argument of 'log_stack_trace'
74 '''
75 def __init__(self, component_name, log_context):
77 if not log_context:
78 raise AttributeError('Logger: log_context is a required parameter')
80 self._component_name = component_name
81 self.log_context = log_context
82 self._logging = logging.getLogger(ADAL_LOGGER_NAME)
84 def _log_message(self, msg, log_stack_trace=None):
85 correlation_id = self.log_context.get("correlation_id",
86 "<no correlation id>")
88 formatted = "{} - {}:{}".format(
89 correlation_id,
90 self._component_name,
91 msg)
92 if log_stack_trace:
93 formatted += "\nStack:\n{}".format(traceback.format_stack())
95 return formatted
97 def warn(self, msg, *args, **kwargs):
98 """
99 The recommended way to call this function with variable content,
100 is to use the `warn("hello %(name)s", {"name": "John Doe"}` form,
101 so that this method will scrub pii value when needed.
102 """
103 if len(args) == 1 and isinstance(args[0], dict) and not self.log_context.get('enable_pii'):
104 args = (scrub_pii(args[0]),)
105 log_stack_trace = kwargs.pop('log_stack_trace', None)
106 msg = self._log_message(msg, log_stack_trace)
107 self._logging.warning(msg, *args, **kwargs)
109 def info(self, msg, *args, **kwargs):
110 if len(args) == 1 and isinstance(args[0], dict) and not self.log_context.get('enable_pii'):
111 args = (scrub_pii(args[0]),)
112 log_stack_trace = kwargs.pop('log_stack_trace', None)
113 msg = self._log_message(msg, log_stack_trace)
114 self._logging.info(msg, *args, **kwargs)
116 def debug(self, msg, *args, **kwargs):
117 if len(args) == 1 and isinstance(args[0], dict) and not self.log_context.get('enable_pii'):
118 args = (scrub_pii(args[0]),)
119 log_stack_trace = kwargs.pop('log_stack_trace', None)
120 msg = self._log_message(msg, log_stack_trace)
121 self._logging.debug(msg, *args, **kwargs)
123 def exception(self, msg, *args, **kwargs):
124 if len(args) == 1 and isinstance(args[0], dict) and not self.log_context.get('enable_pii'):
125 args = (scrub_pii(args[0]),)
126 msg = self._log_message(msg)
127 self._logging.exception(msg, *args, **kwargs)
130def scrub_pii(arg_dict, padding="..."):
131 """
132 The input is a dict with semantic keys,
133 and the output will be a dict with PII values replaced by padding.
134 """
135 pii = set([ # Personally Identifiable Information
136 "subject",
137 "upn", # i.e. user name
138 "given_name", "family_name",
139 "email",
140 "oid", # Object ID
141 "userid", # Used in ADAL Python token cache
142 "login_hint",
143 "home_oid",
144 "access_token", "refresh_token", "id_token", "token_response",
146 # The following are actually Organizationally Identifiable Info
147 "tenant_id",
148 "authority", # which typically contains tenant_id
149 "client_id",
150 "_clientid", # This is the key name ADAL uses in cache query
151 "redirect_uri",
153 # Unintuitively, the following can contain PII
154 "user_realm_url", # e.g. https://login.microsoftonline.com/common/UserRealm/{username}
155 ])
156 return {k: padding if k.lower() in pii else arg_dict[k] for k in arg_dict}