Skip to content

Commit 8279d70

Browse files
achauhan-sccl0lawrence
authored andcommitted
Shrike (Azure#38560)
1 parent 5295a0b commit 8279d70

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# ---------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# ---------------------------------------------------------
4+
5+
""" This is logger utility which will work with allowed logged filter AML policy
6+
https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Machine%20Learning/AllowedLogFilter_EnforceSetting.json
7+
You have to define the same "logFilters" while initializing the logger using "enable_compliant_logging" method
8+
e.g.
9+
log filters: ["^SystemLog:.*$"]
10+
initialize : enable_compliant_logging(format_key="prefix",
11+
format_key_value="SystemLog",
12+
format=f"%(prefix)s{logging.BASIC_FORMAT}")
13+
By default log message will not compliant e.g. not modified
14+
"""
15+
16+
import logging
17+
import sys
18+
from datetime import datetime
19+
from threading import Lock
20+
from typing import Optional
21+
22+
_LOCK = Lock()
23+
_FORMAT_KEY = None
24+
_FORMAT_VALUE = None
25+
26+
27+
# pylint: disable=global-statement
28+
def set_format(key_name: str, value: str) -> None:
29+
with _LOCK:
30+
global _FORMAT_KEY
31+
_FORMAT_KEY = key_name
32+
global _FORMAT_VALUE
33+
_FORMAT_VALUE = value
34+
35+
36+
def get_format_key() -> Optional[str]:
37+
return _FORMAT_KEY
38+
39+
40+
def get_format_value() -> Optional[str]:
41+
return _FORMAT_VALUE
42+
43+
44+
def get_default_logging_format() -> str:
45+
return f"%({get_format_key()})s{logging.BASIC_FORMAT}"
46+
47+
48+
class CompliantLogger(logging.getLoggerClass()): # type: ignore
49+
"""
50+
Subclass of the default logging class with an explicit `is_compliant` parameter
51+
on all logging methods. It will pass an `extra` param with `format` key
52+
(value depending on whether `is_compliant` is True or False) to the
53+
handlers.
54+
55+
The default value for data `is_compliant` is `False` for all methods.
56+
57+
Implementation is inspired by:
58+
https://github.com/python/cpython/blob/3.8/Lib/logging/__init__.py
59+
"""
60+
61+
def __init__(self, name: str, handlers=None):
62+
super().__init__(name) # type: ignore
63+
64+
self.format_key = get_format_key()
65+
self.format_value = get_format_value()
66+
67+
if handlers:
68+
self.handlers = handlers
69+
70+
self.start_time = datetime.now()
71+
self.metric_count = 1
72+
# number of iterable items that are logged
73+
self.max_iter_items = 10
74+
75+
def _log(
76+
self,
77+
level,
78+
msg,
79+
args=None,
80+
exc_info=None,
81+
extra=None,
82+
stack_info=False,
83+
stacklevel=1,
84+
is_compliant=False,
85+
):
86+
if is_compliant:
87+
format_value = self.format_value
88+
else:
89+
format_value = ""
90+
91+
if extra:
92+
extra.update({self.format_key: format_value})
93+
else:
94+
extra = {self.format_key: format_value}
95+
96+
if sys.version_info[1] <= 7:
97+
super(CompliantLogger, self)._log(
98+
level=level,
99+
msg=msg,
100+
args=args,
101+
exc_info=exc_info,
102+
extra=extra,
103+
stack_info=stack_info,
104+
)
105+
else:
106+
super(CompliantLogger, self)._log(
107+
level=level,
108+
msg=msg,
109+
args=args,
110+
exc_info=exc_info,
111+
extra=extra,
112+
stack_info=stack_info,
113+
stacklevel=stacklevel, # type: ignore
114+
)
115+
116+
117+
_logging_basic_config_set_warning = """
118+
********************************************************************************
119+
The root logger already has handlers set! As a result, the behavior of this
120+
library is undefined. If running in Python >= 3.8, this library will attempt to
121+
call logging.basicConfig(force=True), which will remove all existing root
122+
handlers. See https://stackoverflow.com/q/20240464 and
123+
https://github.com/Azure/confidential-ml-utils/issues/33 for more information.
124+
********************************************************************************
125+
"""
126+
127+
128+
def enable_compliant_logging(
129+
format_key: str = "prefix",
130+
format_key_value: str = "SystemLog:",
131+
**kwargs,
132+
) -> None:
133+
"""
134+
The default format is `logging.BASIC_FORMAT` (`%(levelname)s:%(name)s:%(message)s`).
135+
All other kwargs are passed to `logging.basicConfig`. Sets the default
136+
logger class and root logger to be compliant. This means the format
137+
string `%(xxxx)` will work.
138+
139+
:param format_key: key for format
140+
:type format_key: str
141+
:param format_key_value: value for format
142+
:type format_key_value: str
143+
144+
Set the format using the `format` kwarg.
145+
146+
If running in Python >= 3.8, will attempt to add `force=True` to the kwargs
147+
for logging.basicConfig.
148+
149+
The standard implementation of the logging API is a good reference:
150+
https://github.com/python/cpython/blob/3.9/Lib/logging/__init__.py
151+
"""
152+
set_format(format_key, format_key_value)
153+
154+
if "format" not in kwargs:
155+
kwargs["format"] = get_default_logging_format()
156+
157+
# Ensure that all loggers created via `logging.getLogger` are instances of
158+
# the `CompliantLogger` class.
159+
logging.setLoggerClass(CompliantLogger)
160+
161+
if len(logging.root.handlers) > 0:
162+
p = get_format_value()
163+
for line in _logging_basic_config_set_warning.splitlines():
164+
print(f"{p}{line}", file=sys.stderr)
165+
166+
if "force" not in kwargs and sys.version_info >= (3, 8):
167+
kwargs["force"] = True
168+
169+
root = CompliantLogger(logging.root.name, handlers=logging.root.handlers)
170+
171+
logging.root = root
172+
logging.Logger.root = root # type: ignore
173+
logging.Logger.manager = logging.Manager(root) # type: ignore
174+
175+
# https://github.com/kivy/kivy/issues/6733
176+
logging.basicConfig(**kwargs)

0 commit comments

Comments
 (0)