Categorize SDK logs as system logs (#850)
* Add testing for cases * Amend expected behavior
This commit is contained in:
Родитель
473cd1e264
Коммит
a3c047bb01
|
@ -9,6 +9,7 @@ import sys
|
||||||
# Logging Prefixes
|
# Logging Prefixes
|
||||||
CONSOLE_LOG_PREFIX = "LanguageWorkerConsoleLog"
|
CONSOLE_LOG_PREFIX = "LanguageWorkerConsoleLog"
|
||||||
SYSTEM_LOG_PREFIX = "azure_functions_worker"
|
SYSTEM_LOG_PREFIX = "azure_functions_worker"
|
||||||
|
SDK_LOG_PREFIX = "azure.functions"
|
||||||
|
|
||||||
|
|
||||||
logger: logging.Logger = logging.getLogger('azure_functions_worker')
|
logger: logging.Logger = logging.getLogger('azure_functions_worker')
|
||||||
|
@ -76,6 +77,15 @@ def enable_console_logging() -> None:
|
||||||
|
|
||||||
|
|
||||||
def is_system_log_category(ctg: str) -> bool:
|
def is_system_log_category(ctg: str) -> bool:
|
||||||
# Category starts with 'azure_functions_worker' or
|
"""Check if the logging namespace belongs to system logs. Category starts
|
||||||
# 'azure_functions_worker_errors' will be treated as system logs
|
with the following name will be treated as system logs.
|
||||||
return ctg.lower().startswith(SYSTEM_LOG_PREFIX)
|
1. 'azure_functions_worker' (Worker Info)
|
||||||
|
2. 'azure_functions_worker_errors' (Worker Error)
|
||||||
|
3. 'azure.functions' (SDK)
|
||||||
|
|
||||||
|
Expected behaviors for sytem logs and customer logs are listed below:
|
||||||
|
local_console customer_app_insight functions_kusto_table
|
||||||
|
system_log false false true
|
||||||
|
customer_log true true false
|
||||||
|
"""
|
||||||
|
return ctg.startswith(SYSTEM_LOG_PREFIX) or ctg.startswith(SDK_LOG_PREFIX)
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
# Licensed under the MIT License.
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import azure.functions as func
|
||||||
|
|
||||||
|
|
||||||
|
sdk_logger = logging.getLogger('azure.functions')
|
||||||
|
|
||||||
|
|
||||||
|
def main(req: func.HttpRequest):
|
||||||
|
sdk_logger.info('sdk_logger info')
|
||||||
|
sdk_logger.warning('sdk_logger warning')
|
||||||
|
sdk_logger.debug('sdk_logger debug')
|
||||||
|
sdk_logger.error('sdk_logger error', exc_info=True)
|
||||||
|
return 'OK-sdk-logger'
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"scriptFile": "__init__.py",
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"type": "httpTrigger",
|
||||||
|
"direction": "in",
|
||||||
|
"name": "req"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"direction": "out",
|
||||||
|
"name": "$return"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"entryPoint": "main"
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
# Licensed under the MIT License.
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import azure.functions as func
|
||||||
|
|
||||||
|
|
||||||
|
sdk_submodule_logger = logging.getLogger('azure.functions.submodule')
|
||||||
|
|
||||||
|
|
||||||
|
def main(req: func.HttpRequest):
|
||||||
|
sdk_submodule_logger.info('sdk_submodule_logger info')
|
||||||
|
sdk_submodule_logger.warning('sdk_submodule_logger warning')
|
||||||
|
sdk_submodule_logger.debug('sdk_submodule_logger debug')
|
||||||
|
sdk_submodule_logger.error('sdk_submodule_logger error', exc_info=True)
|
||||||
|
return 'OK-sdk-submodule-logging'
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"scriptFile": "__init__.py",
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"type": "httpTrigger",
|
||||||
|
"direction": "in",
|
||||||
|
"name": "req"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"direction": "out",
|
||||||
|
"name": "$return"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"entryPoint": "main"
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
# Licensed under the MIT License.
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from azure_functions_worker import testutils
|
from azure_functions_worker import testutils
|
||||||
|
@ -20,6 +19,15 @@ HOST_JSON_TEMPLATE_WITH_LOGLEVEL_INFO = """\
|
||||||
|
|
||||||
|
|
||||||
class TestLogFilteringFunctions(testutils.WebHostTestCase):
|
class TestLogFilteringFunctions(testutils.WebHostTestCase):
|
||||||
|
"""This class is for testing the logger behavior in Python Worker when
|
||||||
|
dealing with customer's log and system's log. Here's a list of expected
|
||||||
|
behaviors:
|
||||||
|
local_console customer_app_insight functions_kusto_table
|
||||||
|
system_log false false true
|
||||||
|
customer_log true true false
|
||||||
|
|
||||||
|
Please ensure the following unit test cases align with the expectations
|
||||||
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
@ -50,6 +58,7 @@ class TestLogFilteringFunctions(testutils.WebHostTestCase):
|
||||||
self.assertIn('logging info', host_out)
|
self.assertIn('logging info', host_out)
|
||||||
self.assertIn('logging warning', host_out)
|
self.assertIn('logging warning', host_out)
|
||||||
self.assertIn('logging error', host_out)
|
self.assertIn('logging error', host_out)
|
||||||
|
# See HOST_JSON_TEMPLATE_WITH_LOGLEVEL_INFO, debug log is disabled
|
||||||
self.assertNotIn('logging debug', host_out)
|
self.assertNotIn('logging debug', host_out)
|
||||||
|
|
||||||
def test_debug_with_user_logging(self):
|
def test_debug_with_user_logging(self):
|
||||||
|
@ -61,4 +70,40 @@ class TestLogFilteringFunctions(testutils.WebHostTestCase):
|
||||||
self.assertIn('logging info', host_out)
|
self.assertIn('logging info', host_out)
|
||||||
self.assertIn('logging warning', host_out)
|
self.assertIn('logging warning', host_out)
|
||||||
self.assertIn('logging error', host_out)
|
self.assertIn('logging error', host_out)
|
||||||
|
# See HOST_JSON_TEMPLATE_WITH_LOGLEVEL_INFO, debug log is disabled
|
||||||
self.assertNotIn('logging debug', host_out)
|
self.assertNotIn('logging debug', host_out)
|
||||||
|
|
||||||
|
def test_info_with_sdk_logging(self):
|
||||||
|
"""Invoke a HttpTrigger sdk_logging which contains logging invocation
|
||||||
|
via the azure.functions logger. This should be treated as system logs,
|
||||||
|
which means the log should not be displayed in local console.
|
||||||
|
"""
|
||||||
|
r = self.webhost.request('GET', 'sdk_logging')
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.text, 'OK-sdk-logger')
|
||||||
|
|
||||||
|
def check_log_info_with_sdk_logging(self, host_out: typing.List[str]):
|
||||||
|
# See TestLogFilteringFunctions docstring
|
||||||
|
# System log should not be captured in console
|
||||||
|
self.assertNotIn('sdk_logger info', host_out)
|
||||||
|
self.assertNotIn('sdk_logger warning', host_out)
|
||||||
|
self.assertNotIn('sdk_logger error', host_out)
|
||||||
|
self.assertNotIn('sdk_logger debug', host_out)
|
||||||
|
|
||||||
|
def test_info_with_sdk_submodule_logging(self):
|
||||||
|
"""Invoke a HttpTrigger sdk_submodule_logging which contains logging
|
||||||
|
invocation via the azure.functions logger. This should be treated as
|
||||||
|
system logs.
|
||||||
|
"""
|
||||||
|
r = self.webhost.request('GET', 'sdk_submodule_logging')
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertEqual(r.text, 'OK-sdk-submodule-logging')
|
||||||
|
|
||||||
|
def check_log_info_with_sdk_submodule_logging(self,
|
||||||
|
host_out: typing.List[str]):
|
||||||
|
# See TestLogFilteringFunctions docstring
|
||||||
|
# System log should not be captured in console
|
||||||
|
self.assertNotIn('sdk_submodule_logger info', host_out)
|
||||||
|
self.assertNotIn('sdk_submodule_logger warning', host_out)
|
||||||
|
self.assertNotIn('sdk_submodule_logger error', host_out)
|
||||||
|
self.assertNotIn('sdk_submodule_logger debug', host_out)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
# Licensed under the MIT License.
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from azure_functions_worker import logging as flog
|
||||||
|
|
||||||
|
|
||||||
|
class TestLogging(unittest.TestCase):
|
||||||
|
"""This class is for testing the grpc logging behavior in Python Worker.
|
||||||
|
Here's a list of expected behaviors:
|
||||||
|
local_console customer_app_insight functions_kusto_table
|
||||||
|
system_log false false true
|
||||||
|
customer_log true true false
|
||||||
|
|
||||||
|
Please ensure the following unit test cases align with the expectations
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_system_log_namespace(self):
|
||||||
|
"""Ensure the following list is part of the system's log
|
||||||
|
"""
|
||||||
|
self.assertTrue(flog.is_system_log_category('azure_functions_worker'))
|
||||||
|
self.assertTrue(
|
||||||
|
flog.is_system_log_category('azure_functions_worker_error')
|
||||||
|
)
|
||||||
|
self.assertTrue(flog.is_system_log_category('azure.functions'))
|
||||||
|
self.assertTrue(flog.is_system_log_category('azure.functions.module'))
|
||||||
|
|
||||||
|
def test_customer_log_namespace(self):
|
||||||
|
"""Ensure the following list is part of the customer's log
|
||||||
|
"""
|
||||||
|
self.assertFalse(flog.is_system_log_category('customer_logger'))
|
||||||
|
self.assertFalse(flog.is_system_log_category('azure'))
|
||||||
|
self.assertFalse(flog.is_system_log_category('protobuf'))
|
||||||
|
self.assertFalse(flog.is_system_log_category('root'))
|
||||||
|
self.assertFalse(flog.is_system_log_category(''))
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
# Licensed under the MIT License.
|
||||||
|
from unittest.mock import patch, call
|
||||||
|
|
||||||
|
from azure_functions_worker import testutils, protos
|
||||||
|
from azure_functions_worker.logging import is_system_log_category
|
||||||
|
|
||||||
|
|
||||||
|
class TestMockLogFilteringFunctions(testutils.AsyncTestCase):
|
||||||
|
dir = testutils.UNIT_TESTS_FOLDER / 'log_filtering_functions'
|
||||||
|
|
||||||
|
async def test_root_logger_should_be_customer_log(self):
|
||||||
|
"""When customer use the root logger to send logs, the 'root' namespace
|
||||||
|
should be treated as customer log, only sending to our customers.
|
||||||
|
"""
|
||||||
|
with patch(
|
||||||
|
'azure_functions_worker.dispatcher.is_system_log_category'
|
||||||
|
) as islc_mock:
|
||||||
|
async with testutils.start_mockhost(script_root=self.dir) as host:
|
||||||
|
await host.load_function('debug_logging')
|
||||||
|
await self._invoke_function(host, 'debug_logging')
|
||||||
|
|
||||||
|
self.assertIn(call('root'), islc_mock.call_args_list)
|
||||||
|
self.assertFalse(is_system_log_category('root'))
|
||||||
|
|
||||||
|
async def test_customer_logging_should_not_be_system_log(self):
|
||||||
|
"""When sdk uses the 'azure' logger to send logs
|
||||||
|
(e.g. 'azure.servicebus'), the namespace should be treated as customer
|
||||||
|
log, only sends to our customers.
|
||||||
|
"""
|
||||||
|
with patch(
|
||||||
|
'azure_functions_worker.dispatcher.is_system_log_category'
|
||||||
|
) as islc_mock:
|
||||||
|
async with testutils.start_mockhost(script_root=self.dir) as host:
|
||||||
|
await host.load_function('debug_user_logging')
|
||||||
|
await self._invoke_function(host, 'debug_user_logging')
|
||||||
|
|
||||||
|
self.assertIn(call('my function'), islc_mock.call_args_list)
|
||||||
|
self.assertFalse(is_system_log_category('my function'))
|
||||||
|
|
||||||
|
async def test_sdk_logger_should_be_system_log(self):
|
||||||
|
"""When sdk uses the 'azure.functions' logger to send logs, the
|
||||||
|
namespace should be treated as system log, sending to our customers and
|
||||||
|
our kusto table.
|
||||||
|
"""
|
||||||
|
with patch(
|
||||||
|
'azure_functions_worker.dispatcher.is_system_log_category'
|
||||||
|
) as islc_mock:
|
||||||
|
async with testutils.start_mockhost(script_root=self.dir) as host:
|
||||||
|
await host.load_function('sdk_logging')
|
||||||
|
await self._invoke_function(host, 'sdk_logging')
|
||||||
|
|
||||||
|
self.assertIn(
|
||||||
|
call('azure.functions'), islc_mock.call_args_list
|
||||||
|
)
|
||||||
|
self.assertTrue(is_system_log_category('azure.functions'))
|
||||||
|
|
||||||
|
async def test_sdk_submodule_logger_should_be_system_log(self):
|
||||||
|
"""When sdk uses the 'azure.functions.submodule' logger to send logs,
|
||||||
|
the namespace should be treated as system log, sending to our customers
|
||||||
|
and our kusto table.
|
||||||
|
"""
|
||||||
|
with patch(
|
||||||
|
'azure_functions_worker.dispatcher.is_system_log_category'
|
||||||
|
) as islc_mock:
|
||||||
|
async with testutils.start_mockhost(script_root=self.dir) as host:
|
||||||
|
await host.load_function('sdk_submodule_logging')
|
||||||
|
await self._invoke_function(host, 'sdk_submodule_logging')
|
||||||
|
|
||||||
|
self.assertIn(
|
||||||
|
call('azure.functions.submodule'), islc_mock.call_args_list
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
is_system_log_category('azure.functions.submodule')
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _invoke_function(self,
|
||||||
|
host: testutils._MockWebHost,
|
||||||
|
function_name: str):
|
||||||
|
_, r = await host.invoke_function(
|
||||||
|
function_name, [
|
||||||
|
protos.ParameterBinding(
|
||||||
|
name='req',
|
||||||
|
data=protos.TypedData(
|
||||||
|
http=protos.RpcHttp(method='GET')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(r.response.result.status, protos.StatusResult.Success)
|
Загрузка…
Ссылка в новой задаче