Skip to content

Commit a3c047b

Browse files
authored
Categorize SDK logs as system logs (#850)
* Add testing for cases * Amend expected behavior
1 parent 473cd1e commit a3c047b

File tree

8 files changed

+249
-4
lines changed

8 files changed

+249
-4
lines changed

azure_functions_worker/logging.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# Logging Prefixes
1010
CONSOLE_LOG_PREFIX = "LanguageWorkerConsoleLog"
1111
SYSTEM_LOG_PREFIX = "azure_functions_worker"
12+
SDK_LOG_PREFIX = "azure.functions"
1213

1314

1415
logger: logging.Logger = logging.getLogger('azure_functions_worker')
@@ -76,6 +77,15 @@ def enable_console_logging() -> None:
7677

7778

7879
def is_system_log_category(ctg: str) -> bool:
79-
# Category starts with 'azure_functions_worker' or
80-
# 'azure_functions_worker_errors' will be treated as system logs
81-
return ctg.lower().startswith(SYSTEM_LOG_PREFIX)
80+
"""Check if the logging namespace belongs to system logs. Category starts
81+
with the following name will be treated as system logs.
82+
1. 'azure_functions_worker' (Worker Info)
83+
2. 'azure_functions_worker_errors' (Worker Error)
84+
3. 'azure.functions' (SDK)
85+
86+
Expected behaviors for sytem logs and customer logs are listed below:
87+
local_console customer_app_insight functions_kusto_table
88+
system_log false false true
89+
customer_log true true false
90+
"""
91+
return ctg.startswith(SYSTEM_LOG_PREFIX) or ctg.startswith(SDK_LOG_PREFIX)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
import logging
4+
5+
import azure.functions as func
6+
7+
8+
sdk_logger = logging.getLogger('azure.functions')
9+
10+
11+
def main(req: func.HttpRequest):
12+
sdk_logger.info('sdk_logger info')
13+
sdk_logger.warning('sdk_logger warning')
14+
sdk_logger.debug('sdk_logger debug')
15+
sdk_logger.error('sdk_logger error', exc_info=True)
16+
return 'OK-sdk-logger'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"scriptFile": "__init__.py",
3+
"bindings": [
4+
{
5+
"type": "httpTrigger",
6+
"direction": "in",
7+
"name": "req"
8+
},
9+
{
10+
"type": "http",
11+
"direction": "out",
12+
"name": "$return"
13+
}
14+
],
15+
"entryPoint": "main"
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
import logging
4+
5+
import azure.functions as func
6+
7+
8+
sdk_submodule_logger = logging.getLogger('azure.functions.submodule')
9+
10+
11+
def main(req: func.HttpRequest):
12+
sdk_submodule_logger.info('sdk_submodule_logger info')
13+
sdk_submodule_logger.warning('sdk_submodule_logger warning')
14+
sdk_submodule_logger.debug('sdk_submodule_logger debug')
15+
sdk_submodule_logger.error('sdk_submodule_logger error', exc_info=True)
16+
return 'OK-sdk-submodule-logging'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"scriptFile": "__init__.py",
3+
"bindings": [
4+
{
5+
"type": "httpTrigger",
6+
"direction": "in",
7+
"name": "req"
8+
},
9+
{
10+
"type": "http",
11+
"direction": "out",
12+
"name": "$return"
13+
}
14+
],
15+
"entryPoint": "main"
16+
}

tests/unittests/test_log_filtering_functions.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3-
43
import typing
54

65
from azure_functions_worker import testutils
@@ -20,6 +19,15 @@
2019

2120

2221
class TestLogFilteringFunctions(testutils.WebHostTestCase):
22+
"""This class is for testing the logger behavior in Python Worker when
23+
dealing with customer's log and system's log. Here's a list of expected
24+
behaviors:
25+
local_console customer_app_insight functions_kusto_table
26+
system_log false false true
27+
customer_log true true false
28+
29+
Please ensure the following unit test cases align with the expectations
30+
"""
2331

2432
@classmethod
2533
def setUpClass(cls):
@@ -50,6 +58,7 @@ def check_log_debug_logging(self, host_out: typing.List[str]):
5058
self.assertIn('logging info', host_out)
5159
self.assertIn('logging warning', host_out)
5260
self.assertIn('logging error', host_out)
61+
# See HOST_JSON_TEMPLATE_WITH_LOGLEVEL_INFO, debug log is disabled
5362
self.assertNotIn('logging debug', host_out)
5463

5564
def test_debug_with_user_logging(self):
@@ -61,4 +70,40 @@ def check_log_debug_with_user_logging(self, host_out: typing.List[str]):
6170
self.assertIn('logging info', host_out)
6271
self.assertIn('logging warning', host_out)
6372
self.assertIn('logging error', host_out)
73+
# See HOST_JSON_TEMPLATE_WITH_LOGLEVEL_INFO, debug log is disabled
6474
self.assertNotIn('logging debug', host_out)
75+
76+
def test_info_with_sdk_logging(self):
77+
"""Invoke a HttpTrigger sdk_logging which contains logging invocation
78+
via the azure.functions logger. This should be treated as system logs,
79+
which means the log should not be displayed in local console.
80+
"""
81+
r = self.webhost.request('GET', 'sdk_logging')
82+
self.assertEqual(r.status_code, 200)
83+
self.assertEqual(r.text, 'OK-sdk-logger')
84+
85+
def check_log_info_with_sdk_logging(self, host_out: typing.List[str]):
86+
# See TestLogFilteringFunctions docstring
87+
# System log should not be captured in console
88+
self.assertNotIn('sdk_logger info', host_out)
89+
self.assertNotIn('sdk_logger warning', host_out)
90+
self.assertNotIn('sdk_logger error', host_out)
91+
self.assertNotIn('sdk_logger debug', host_out)
92+
93+
def test_info_with_sdk_submodule_logging(self):
94+
"""Invoke a HttpTrigger sdk_submodule_logging which contains logging
95+
invocation via the azure.functions logger. This should be treated as
96+
system logs.
97+
"""
98+
r = self.webhost.request('GET', 'sdk_submodule_logging')
99+
self.assertEqual(r.status_code, 200)
100+
self.assertEqual(r.text, 'OK-sdk-submodule-logging')
101+
102+
def check_log_info_with_sdk_submodule_logging(self,
103+
host_out: typing.List[str]):
104+
# See TestLogFilteringFunctions docstring
105+
# System log should not be captured in console
106+
self.assertNotIn('sdk_submodule_logger info', host_out)
107+
self.assertNotIn('sdk_submodule_logger warning', host_out)
108+
self.assertNotIn('sdk_submodule_logger error', host_out)
109+
self.assertNotIn('sdk_submodule_logger debug', host_out)

tests/unittests/test_logging.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
import unittest
4+
5+
from azure_functions_worker import logging as flog
6+
7+
8+
class TestLogging(unittest.TestCase):
9+
"""This class is for testing the grpc logging behavior in Python Worker.
10+
Here's a list of expected behaviors:
11+
local_console customer_app_insight functions_kusto_table
12+
system_log false false true
13+
customer_log true true false
14+
15+
Please ensure the following unit test cases align with the expectations
16+
"""
17+
18+
def test_system_log_namespace(self):
19+
"""Ensure the following list is part of the system's log
20+
"""
21+
self.assertTrue(flog.is_system_log_category('azure_functions_worker'))
22+
self.assertTrue(
23+
flog.is_system_log_category('azure_functions_worker_error')
24+
)
25+
self.assertTrue(flog.is_system_log_category('azure.functions'))
26+
self.assertTrue(flog.is_system_log_category('azure.functions.module'))
27+
28+
def test_customer_log_namespace(self):
29+
"""Ensure the following list is part of the customer's log
30+
"""
31+
self.assertFalse(flog.is_system_log_category('customer_logger'))
32+
self.assertFalse(flog.is_system_log_category('azure'))
33+
self.assertFalse(flog.is_system_log_category('protobuf'))
34+
self.assertFalse(flog.is_system_log_category('root'))
35+
self.assertFalse(flog.is_system_log_category(''))
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
from unittest.mock import patch, call
4+
5+
from azure_functions_worker import testutils, protos
6+
from azure_functions_worker.logging import is_system_log_category
7+
8+
9+
class TestMockLogFilteringFunctions(testutils.AsyncTestCase):
10+
dir = testutils.UNIT_TESTS_FOLDER / 'log_filtering_functions'
11+
12+
async def test_root_logger_should_be_customer_log(self):
13+
"""When customer use the root logger to send logs, the 'root' namespace
14+
should be treated as customer log, only sending to our customers.
15+
"""
16+
with patch(
17+
'azure_functions_worker.dispatcher.is_system_log_category'
18+
) as islc_mock:
19+
async with testutils.start_mockhost(script_root=self.dir) as host:
20+
await host.load_function('debug_logging')
21+
await self._invoke_function(host, 'debug_logging')
22+
23+
self.assertIn(call('root'), islc_mock.call_args_list)
24+
self.assertFalse(is_system_log_category('root'))
25+
26+
async def test_customer_logging_should_not_be_system_log(self):
27+
"""When sdk uses the 'azure' logger to send logs
28+
(e.g. 'azure.servicebus'), the namespace should be treated as customer
29+
log, only sends to our customers.
30+
"""
31+
with patch(
32+
'azure_functions_worker.dispatcher.is_system_log_category'
33+
) as islc_mock:
34+
async with testutils.start_mockhost(script_root=self.dir) as host:
35+
await host.load_function('debug_user_logging')
36+
await self._invoke_function(host, 'debug_user_logging')
37+
38+
self.assertIn(call('my function'), islc_mock.call_args_list)
39+
self.assertFalse(is_system_log_category('my function'))
40+
41+
async def test_sdk_logger_should_be_system_log(self):
42+
"""When sdk uses the 'azure.functions' logger to send logs, the
43+
namespace should be treated as system log, sending to our customers and
44+
our kusto table.
45+
"""
46+
with patch(
47+
'azure_functions_worker.dispatcher.is_system_log_category'
48+
) as islc_mock:
49+
async with testutils.start_mockhost(script_root=self.dir) as host:
50+
await host.load_function('sdk_logging')
51+
await self._invoke_function(host, 'sdk_logging')
52+
53+
self.assertIn(
54+
call('azure.functions'), islc_mock.call_args_list
55+
)
56+
self.assertTrue(is_system_log_category('azure.functions'))
57+
58+
async def test_sdk_submodule_logger_should_be_system_log(self):
59+
"""When sdk uses the 'azure.functions.submodule' logger to send logs,
60+
the namespace should be treated as system log, sending to our customers
61+
and our kusto table.
62+
"""
63+
with patch(
64+
'azure_functions_worker.dispatcher.is_system_log_category'
65+
) as islc_mock:
66+
async with testutils.start_mockhost(script_root=self.dir) as host:
67+
await host.load_function('sdk_submodule_logging')
68+
await self._invoke_function(host, 'sdk_submodule_logging')
69+
70+
self.assertIn(
71+
call('azure.functions.submodule'), islc_mock.call_args_list
72+
)
73+
self.assertTrue(
74+
is_system_log_category('azure.functions.submodule')
75+
)
76+
77+
async def _invoke_function(self,
78+
host: testutils._MockWebHost,
79+
function_name: str):
80+
_, r = await host.invoke_function(
81+
function_name, [
82+
protos.ParameterBinding(
83+
name='req',
84+
data=protos.TypedData(
85+
http=protos.RpcHttp(method='GET')
86+
)
87+
)
88+
]
89+
)
90+
91+
self.assertEqual(r.response.result.status, protos.StatusResult.Success)

0 commit comments

Comments
 (0)