Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd18d9b5

Browse files
ohmayrgcf-owl-bot[bot]vchudnov-gparthea
authored
feat: add automatic logging config to support debug logging (#754)
* feat: add base logger to enable debug logging* address PR feedback* address PR feedback* add tests* address PR feedback* address PR feedback* address PR feedback* address PR feedback* 🦉 Updates from OwlBot post-processorSeehttps://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md* address PR feedback* address PR feedback* address PR feedback* add coverage for already configured scope* Add partial documentation; clarify test base logger* address PR feedback* 🦉 Updates from OwlBot post-processorSeehttps://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md---------Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>Co-authored-by: Victor Chudnovsky <vchudnov@google.com>Co-authored-by: Anthonios Partheniou <partheniou@google.com>
1 parente298ebd commitd18d9b5

File tree

2 files changed

+283
-0
lines changed

2 files changed

+283
-0
lines changed

‎google/api_core/client_logging.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
importlogging
2+
importjson
3+
importos
4+
5+
fromtypingimportList,Optional
6+
7+
_LOGGING_INITIALIZED=False
8+
_BASE_LOGGER_NAME="google"
9+
10+
# Fields to be included in the StructuredLogFormatter.
11+
#
12+
# TODO(https://github.com/googleapis/python-api-core/issues/761): Update this list to support additional logging fields.
13+
_recognized_logging_fields= [
14+
"httpRequest",
15+
"rpcName",
16+
"serviceName",
17+
"credentialsType",
18+
"credentialInfo",
19+
"universeDomain",
20+
"request",
21+
"response",
22+
"metadata",
23+
"retryAttempt",
24+
]# Additional fields to be Logged.
25+
26+
27+
deflogger_configured(logger)->bool:
28+
"""Determines whether `logger` has non-default configuration
29+
30+
Args:
31+
logger: The logger to check.
32+
33+
Returns:
34+
bool: Whether the logger has any non-default configuration.
35+
"""
36+
return (
37+
logger.handlers!= []orlogger.level!=logging.NOTSETornotlogger.propagate
38+
)
39+
40+
41+
definitialize_logging():
42+
"""Initializes "google" loggers, partly based on the environment variable
43+
44+
Initializes the "google" logger and any loggers (at the "google"
45+
level or lower) specified by the environment variable
46+
GOOGLE_SDK_PYTHON_LOGGING_SCOPE, as long as none of these loggers
47+
were previously configured. If any such loggers (including the
48+
"google" logger) are initialized, they are set to NOT propagate
49+
log events up to their parent loggers.
50+
51+
This initialization is executed only once, and hence the
52+
environment variable is only processed the first time this
53+
function is called.
54+
"""
55+
global_LOGGING_INITIALIZED
56+
if_LOGGING_INITIALIZED:
57+
return
58+
scopes=os.getenv("GOOGLE_SDK_PYTHON_LOGGING_SCOPE","")
59+
setup_logging(scopes)
60+
_LOGGING_INITIALIZED=True
61+
62+
63+
defparse_logging_scopes(scopes:Optional[str]=None)->List[str]:
64+
"""Returns a list of logger names.
65+
66+
Splits the single string of comma-separated logger names into a list of individual logger name strings.
67+
68+
Args:
69+
scopes: The name of a single logger. (In the future, this will be a comma-separated list of multiple loggers.)
70+
71+
Returns:
72+
A list of all the logger names in scopes.
73+
"""
74+
ifnotscopes:
75+
return []
76+
# TODO(https://github.com/googleapis/python-api-core/issues/759): check if the namespace is a valid namespace.
77+
# TODO(b/380481951): Support logging multiple scopes.
78+
# TODO(b/380483756): Raise or log a warning for an invalid scope.
79+
namespaces= [scopes]
80+
returnnamespaces
81+
82+
83+
defconfigure_defaults(logger):
84+
"""Configures `logger` to emit structured info to stdout."""
85+
ifnotlogger_configured(logger):
86+
console_handler=logging.StreamHandler()
87+
logger.setLevel("DEBUG")
88+
logger.propagate=False
89+
formatter=StructuredLogFormatter()
90+
console_handler.setFormatter(formatter)
91+
logger.addHandler(console_handler)
92+
93+
94+
defsetup_logging(scopes:str=""):
95+
"""Sets up logging for the specified `scopes`.
96+
97+
If the loggers specified in `scopes` have not been previously
98+
configured, this will configure them to emit structured log
99+
entries to stdout, and to not propagate their log events to their
100+
parent loggers. Additionally, if the "google" logger (whether it
101+
was specified in `scopes` or not) was not previously configured,
102+
it will also configure it to not propagate log events to the root
103+
logger.
104+
105+
Args:
106+
scopes: The name of a single logger. (In the future, this will be a comma-separated list of multiple loggers.)
107+
108+
"""
109+
110+
# only returns valid logger scopes (namespaces)
111+
# this list has at most one element.
112+
logger_names=parse_logging_scopes(scopes)
113+
114+
fornamespaceinlogger_names:
115+
# This will either create a module level logger or get the reference of the base logger instantiated above.
116+
logger=logging.getLogger(namespace)
117+
118+
# Configure default settings.
119+
configure_defaults(logger)
120+
121+
# disable log propagation at base logger level to the root logger only if a base logger is not already configured via code changes.
122+
base_logger=logging.getLogger(_BASE_LOGGER_NAME)
123+
ifnotlogger_configured(base_logger):
124+
base_logger.propagate=False
125+
126+
127+
# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation.
128+
classStructuredLogFormatter(logging.Formatter):
129+
# TODO(https://github.com/googleapis/python-api-core/issues/761): ensure that additional fields such as
130+
# function name, file name, and line no. appear in a log output.
131+
defformat(self,record:logging.LogRecord):
132+
log_obj= {
133+
"timestamp":self.formatTime(record),
134+
"severity":record.levelname,
135+
"name":record.name,
136+
"message":record.getMessage(),
137+
}
138+
139+
forfield_namein_recognized_logging_fields:
140+
value=getattr(record,field_name,None)
141+
ifvalueisnotNone:
142+
log_obj[field_name]=value
143+
returnjson.dumps(log_obj)

‎tests/unit/test_client_logging.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
importjson
2+
importlogging
3+
fromunittestimportmock
4+
5+
fromgoogle.api_core.client_loggingimport (
6+
setup_logging,
7+
initialize_logging,
8+
StructuredLogFormatter,
9+
)
10+
11+
12+
defreset_logger(scope):
13+
logger=logging.getLogger(scope)
14+
logger.handlers= []
15+
logger.setLevel(logging.NOTSET)
16+
logger.propagate=True
17+
18+
19+
deftest_setup_logging_w_no_scopes():
20+
withmock.patch("google.api_core.client_logging._BASE_LOGGER_NAME","foogle"):
21+
setup_logging()
22+
base_logger=logging.getLogger("foogle")
23+
assertbase_logger.handlers== []
24+
assertnotbase_logger.propagate
25+
assertbase_logger.level==logging.NOTSET
26+
27+
reset_logger("foogle")
28+
29+
30+
deftest_setup_logging_w_base_scope():
31+
withmock.patch("google.api_core.client_logging._BASE_LOGGER_NAME","foogle"):
32+
setup_logging("foogle")
33+
base_logger=logging.getLogger("foogle")
34+
assertisinstance(base_logger.handlers[0],logging.StreamHandler)
35+
assertnotbase_logger.propagate
36+
assertbase_logger.level==logging.DEBUG
37+
38+
reset_logger("foogle")
39+
40+
41+
deftest_setup_logging_w_configured_scope():
42+
withmock.patch("google.api_core.client_logging._BASE_LOGGER_NAME","foogle"):
43+
base_logger=logging.getLogger("foogle")
44+
base_logger.propagate=False
45+
setup_logging("foogle")
46+
assertbase_logger.handlers== []
47+
assertnotbase_logger.propagate
48+
assertbase_logger.level==logging.NOTSET
49+
50+
reset_logger("foogle")
51+
52+
53+
deftest_setup_logging_w_module_scope():
54+
withmock.patch("google.api_core.client_logging._BASE_LOGGER_NAME","foogle"):
55+
setup_logging("foogle.bar")
56+
57+
base_logger=logging.getLogger("foogle")
58+
assertbase_logger.handlers== []
59+
assertnotbase_logger.propagate
60+
assertbase_logger.level==logging.NOTSET
61+
62+
module_logger=logging.getLogger("foogle.bar")
63+
assertisinstance(module_logger.handlers[0],logging.StreamHandler)
64+
assertnotmodule_logger.propagate
65+
assertmodule_logger.level==logging.DEBUG
66+
67+
reset_logger("foogle")
68+
reset_logger("foogle.bar")
69+
70+
71+
deftest_setup_logging_w_incorrect_scope():
72+
withmock.patch("google.api_core.client_logging._BASE_LOGGER_NAME","foogle"):
73+
setup_logging("abc")
74+
75+
base_logger=logging.getLogger("foogle")
76+
assertbase_logger.handlers== []
77+
assertnotbase_logger.propagate
78+
assertbase_logger.level==logging.NOTSET
79+
80+
# TODO(https://github.com/googleapis/python-api-core/issues/759): update test once we add logic to ignore an incorrect scope.
81+
logger=logging.getLogger("abc")
82+
assertisinstance(logger.handlers[0],logging.StreamHandler)
83+
assertnotlogger.propagate
84+
assertlogger.level==logging.DEBUG
85+
86+
reset_logger("foogle")
87+
reset_logger("abc")
88+
89+
90+
deftest_initialize_logging():
91+
92+
withmock.patch("os.getenv",return_value="foogle.bar"):
93+
withmock.patch("google.api_core.client_logging._BASE_LOGGER_NAME","foogle"):
94+
initialize_logging()
95+
96+
base_logger=logging.getLogger("foogle")
97+
assertbase_logger.handlers== []
98+
assertnotbase_logger.propagate
99+
assertbase_logger.level==logging.NOTSET
100+
101+
module_logger=logging.getLogger("foogle.bar")
102+
assertisinstance(module_logger.handlers[0],logging.StreamHandler)
103+
assertnotmodule_logger.propagate
104+
assertmodule_logger.level==logging.DEBUG
105+
106+
# Check that `initialize_logging()` is a no-op after the first time by verifying that user-set configs are not modified:
107+
base_logger.propagate=True
108+
module_logger.propagate=True
109+
110+
initialize_logging()
111+
112+
assertbase_logger.propagate
113+
assertmodule_logger.propagate
114+
115+
reset_logger("foogle")
116+
reset_logger("foogle.bar")
117+
118+
119+
deftest_structured_log_formatter():
120+
# TODO(https://github.com/googleapis/python-api-core/issues/761): Test additional fields when implemented.
121+
record=logging.LogRecord(
122+
name="Appelation",
123+
level=logging.DEBUG,
124+
msg="This is a test message.",
125+
pathname="some/path",
126+
lineno=25,
127+
args=None,
128+
exc_info=None,
129+
)
130+
131+
# Extra fields:
132+
record.rpcName="bar"
133+
134+
formatted_msg=StructuredLogFormatter().format(record)
135+
parsed_msg=json.loads(formatted_msg)
136+
137+
assertparsed_msg["name"]=="Appelation"
138+
assertparsed_msg["severity"]=="DEBUG"
139+
assertparsed_msg["message"]=="This is a test message."
140+
assertparsed_msg["rpcName"]=="bar"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp