Repository: GoogleCloudPlatform/functions-framework-python
Pull request #131 (2021-05-27 23:27:10)

Update GCF Python 3.7 backwards-compatible logging
Fixes issues in backwards compatibility for GCF Python 3.7.


Patch:
diff --git a/src/functions_framework/__init__.py b/src/functions_framework/__init__.py
index bc19b18..36cde34 100644
--- a/src/functions_framework/__init__.py
+++ b/src/functions_framework/__init__.py
@@ -16,6 +16,7 @@ import functools
 import importlib.util
 import io
 import json
+import logging
 import os.path
 import pathlib
 import sys
@@ -62,6 +63,18 @@ class _LoggingHandler(io.TextIOWrapper):
         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 create_app(target=None, source=None, signature_type=None):
         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):


Test patch:
diff --git a/tests/test_functions.py b/tests/test_functions.py
index 9fcee69..ad16c15 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 0000000..50becd1
--- /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