diff --git a/src/functions_framework/__init__.py b/src/functions_framework/__init__.py index bc19b185..36cde34b 100644 --- a/src/functions_framework/__init__.py +++ b/src/functions_framework/__init__.py @@ -16,6 +16,7 @@ import importlib.util import io import json +import logging import os.path import pathlib import sys @@ -62,6 +63,18 @@ def write(self, out): return self.stderr.write(json.dumps(payload) + "\n") +def setup_logging(): + logging.getLogger().setLevel(logging.INFO) + info_handler = logging.StreamHandler(sys.stdout) + info_handler.setLevel(logging.NOTSET) + info_handler.addFilter(lambda record: record.levelno <= logging.INFO) + logging.getLogger().addHandler(info_handler) + + warn_handler = logging.StreamHandler(sys.stderr) + warn_handler.setLevel(logging.WARNING) + logging.getLogger().addHandler(warn_handler) + + def _http_view_func_wrapper(function, request): def view_func(path): return function(request._get_current_object()) @@ -237,15 +250,9 @@ def handle_none(rv): app.make_response = handle_none # Handle log severity backwards compatibility - import logging # isort:skip - - logging.info = _LoggingHandler("INFO", sys.stderr).write - logging.warn = _LoggingHandler("ERROR", sys.stderr).write - logging.warning = _LoggingHandler("ERROR", sys.stderr).write - logging.error = _LoggingHandler("ERROR", sys.stderr).write - logging.critical = _LoggingHandler("ERROR", sys.stderr).write sys.stdout = _LoggingHandler("INFO", sys.stderr) sys.stderr = _LoggingHandler("ERROR", sys.stderr) + setup_logging() # Extract the target function from the source file if not hasattr(source_module, target): diff --git a/tests/test_functions.py b/tests/test_functions.py index 9fcee69f..ad16c15b 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -524,6 +524,22 @@ def test_legacy_function_log_severity(monkeypatch, capfd, mode, expected): assert expected in captured +def test_legacy_function_log_exception(monkeypatch, capfd): + source = TEST_FUNCTIONS_DIR / "http_log_exception" / "main.py" + target = "function" + severity = '"severity": "ERROR"' + traceback = "Traceback (most recent call last)" + + monkeypatch.setenv("ENTRY_POINT", target) + + client = create_app(target, source).test_client() + resp = client.post("/") + captured = capfd.readouterr().err + assert resp.status_code == 200 + assert severity in captured + assert traceback in captured + + def test_legacy_function_returns_none(monkeypatch): source = TEST_FUNCTIONS_DIR / "returns_none" / "main.py" target = "function" diff --git a/tests/test_functions/http_log_exception/main.py b/tests/test_functions/http_log_exception/main.py new file mode 100644 index 00000000..50becd1a --- /dev/null +++ b/tests/test_functions/http_log_exception/main.py @@ -0,0 +1,33 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Function used in Worker tests of legacy GCF Python 3.7 logging.""" +import logging + +X_GOOGLE_FUNCTION_NAME = "gcf-function" +X_GOOGLE_ENTRY_POINT = "function" +HOME = "/tmp" + + +def function(request): + """Test function which logs exceptions. + + Args: + request: The HTTP request which triggered this function. + """ + try: + raise Exception + except: + logging.exception("log") + return None