Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add backwards-compatible logging for GCF Python 3.7 #107

Merged
merged 9 commits into from
Feb 17, 2021
24 changes: 24 additions & 0 deletions src/functions_framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ def __init__(
self.data = data


class _LoggingHandler(object):
"""Logging replacement for stdout and stderr in GCF Python 3.7."""

def __init__(self, level):
self.level = level

def write(self, out):
if out.rstrip() != "":
self.level(out.rstrip())
asriniva marked this conversation as resolved.
Show resolved Hide resolved


def _http_view_func_wrapper(function, request):
def view_func(path):
return function(request._get_current_object())
Expand Down Expand Up @@ -221,6 +232,19 @@ def handle_none(rv):

app.make_response = handle_none

# Handle log severity backwards compatibility
import logging # isort:skip
from google.cloud.logging_v2.handlers.container_engine import ( # isort:skip
ContainerEngineHandler,
)

handler = ContainerEngineHandler()
cloud_logger = logging.getLogger()
cloud_logger.setLevel(logging.DEBUG)
cloud_logger.addHandler(handler)
sys.stdout = _LoggingHandler(logging.info)
sys.stderr = _LoggingHandler(logging.warning)

# Extract the target function from the source file
if not hasattr(source_module, target):
raise MissingTargetException(
Expand Down
33 changes: 33 additions & 0 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,39 @@ def test_legacy_function_check_env(monkeypatch):
assert resp.data.decode("utf-8") == target


def test_legacy_function_log_severity(monkeypatch, capsys):
asriniva marked this conversation as resolved.
Show resolved Hide resolved
source = TEST_FUNCTIONS_DIR / "http_check_severity" / "main.py"
target = "function"

monkeypatch.setenv("ENTRY_POINT", target)

client = create_app(target, source).test_client()
resp = client.post("/", json={"mode": "stdout"})
captured = capsys.readouterr().err
assert resp.status_code == 200
assert '"severity": "INFO"' in captured

resp = client.post("/", json={"mode": "stderr"})
captured = capsys.readouterr().err
assert resp.status_code == 200
assert '"severity": "WARNING"' in captured

resp = client.post("/", json={"mode": "loginfo"})
captured = capsys.readouterr().err
assert resp.status_code == 200
assert '"severity": "INFO"' in captured

resp = client.post("/", json={"mode": "logwarn"})
captured = capsys.readouterr().err
assert resp.status_code == 200
assert '"severity": "WARNING"' in captured

resp = client.post("/", json={"mode": "logerr"})
captured = capsys.readouterr().err
assert resp.status_code == 200
assert '"severity": "ERROR"' in captured


def test_legacy_function_returns_none(monkeypatch):
source = TEST_FUNCTIONS_DIR / "returns_none" / "main.py"
target = "function"
Expand Down
47 changes: 47 additions & 0 deletions tests/test_functions/http_check_severity/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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
import os
import sys

X_GOOGLE_FUNCTION_NAME = "gcf-function"
X_GOOGLE_ENTRY_POINT = "function"
HOME = "/tmp"


def function(request):
"""Test function which logs to the appropriate output.

Args:
request: The HTTP request which triggered this function. Must contain name
of the requested output in the 'mode' field in JSON document
in request body.

Returns:
Value of the mode.
"""
name = request.get_json().get("mode")
if name == "stdout":
print("log")
elif name == "stderr":
print("log", file=sys.stderr)
elif name == "loginfo":
logging.info("log")
elif name == "logwarn":
logging.warning("log")
elif name == "logerr":
logging.error("log")
return name
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ envlist = py{35,36,37,38,39}-{ubuntu-latest,macos-latest,windows-latest},lint
usedevelop = true
deps =
docker
google-cloud-logging
asriniva marked this conversation as resolved.
Show resolved Hide resolved
pytest-cov
pytest-integration
pretend
Expand Down