From 1f19c31dfb8ea40476c835c807d6479cbc7e7d69 Mon Sep 17 00:00:00 2001 From: Raissa Ferreira Date: Sun, 4 Nov 2018 20:42:38 -0300 Subject: [PATCH] feature: Update to google_cloud_logger v0.1.0 (#1) * feature: Update to google_cloud_logger v0.1.0 * chore: Bump lib version to 0.1.0 --- Pipfile | 2 + Pipfile.lock | 29 ++++---- README.md | 82 ++++++++++++++++++++++- examples/test_app.py | 52 ++++++++++++++ flask_google_cloud_logger/__init__.py | 6 +- flask_google_cloud_logger/formatter.py | 13 ++-- setup.py | 6 +- test/test_flask_google_cloud_formatter.py | 54 +++++---------- 8 files changed, 182 insertions(+), 62 deletions(-) create mode 100644 examples/test_app.py diff --git a/Pipfile b/Pipfile index 46123b8..68030ae 100644 --- a/Pipfile +++ b/Pipfile @@ -5,6 +5,7 @@ name = "pypi" [packages] flask_google_cloud_logger = {editable = true, path = "."} +flask = "*" [dev-packages] pytest = "~=3.9.3" @@ -14,5 +15,6 @@ flake8 = "~=3.6.0" yapf = "~=0.24.0" ipdb = "~=0.11" safety = "~=1.8.4" + [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 3369ec5..d922508 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e1379a7ea0bffa7097d1dc2b8fc1ab3b43f3e7e0d3c3666ae2860655e762abf7" + "sha256": "ce5b4b8a2381c05b4b2d867e0ca9fd3896720a9e3b8c0b4586958a71878872d6" }, "pipfile-spec": 6, "requires": { @@ -28,6 +28,7 @@ "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" ], + "index": "pypi", "version": "==1.0.2" }, "flask-google-cloud-logger": { @@ -36,9 +37,10 @@ }, "google-cloud-logger": { "hashes": [ - "sha256:ce88aa9d42c00a5b75ef1a402ce320db9a1e8d314eea4a0030198502b8fec629" + "sha256:07d8a657db8c98f1c97f11cdbe7666cc352e5d9ce1e686325bee399d7c3c39d7", + "sha256:9f92a6de22a54d621ac0a531d586419c8f930663873e92b697550a964ca6af83" ], - "version": "==0.0.2" + "version": "==0.1.0" }, "itsdangerous": { "hashes": [ @@ -197,6 +199,7 @@ "sha256:a5781d6934a3341a1f9acb4ea5acdc7ea0a0855e689dbe755d070ca51e995435", "sha256:b10a7ddd03657c761fc503495bc36471c8158e3fc948573fb9fe82a7029d8efd" ], + "markers": "python_version >= '3.3'", "version": "==7.1.1" }, "ipython-genutils": { @@ -266,11 +269,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:646b3401b3b0bb7752100bc9b7aeecb36cb09cdfc63652b5856708b5ba8db7da", - "sha256:82766ffd7397e6661465e20bd1390db0781ca4fbbab4cf6c2578cacdd8b09754", - "sha256:ccad8461b5d912782726af17122113e196085e7e11d57cf0c9b982bf1ab2c7be" + "sha256:c1d6aff5252ab2ef391c2fe498ed8c088066f66bc64a8d5c095bbf795d9fec34", + "sha256:d4c47f79b635a0e70b84fdb97ebd9a274203706b1ee5ed44c10da62755cf3ec9", + "sha256:fd17048d8335c1e6d5ee403c3569953ba3eb8555d710bfc548faf0712666ea39" ], - "version": "==2.0.6" + "version": "==2.0.7" }, "ptyprocess": { "hashes": [ @@ -309,10 +312,10 @@ }, "pyparsing": { "hashes": [ - "sha256:bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a", - "sha256:d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401" + "sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b", + "sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592" ], - "version": "==2.2.2" + "version": "==2.3.0" }, "pytest": { "hashes": [ @@ -385,10 +388,10 @@ }, "urllib3": { "hashes": [ - "sha256:41c3db2fc01e5b907288010dec72f9d0a74e37d6994e6eb56849f59fea2265ae", - "sha256:8819bba37a02d143296a4d032373c4dd4aca11f6d4c9973335ca75f9c8475f59" + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" ], - "version": "==1.24" + "version": "==1.24.1" }, "wcwidth": { "hashes": [ diff --git a/README.md b/README.md index adf06b7..a846693 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,86 @@ Flask extension to format logs according to Google Cloud v2 Specification +Python log formatter for Google Cloud according to [v2 specification](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry) using [python-json-logger](https://github.com/madzak/python-json-logger) formatter + +Inspired by Elixir's [logger_json](https://github.com/Nebo15/logger_json) + +## Instalation + +### Pipenv + +``` + pipenv install flask_google_cloud_logger +``` + +### Pip + +``` + pip install flask_google_cloud_logger +``` + +## Usage + +```python +import logging +from logging import config + +from flask import Flask, request, g +from flask_google_cloud_logger import FlaskGoogleCloudLogger + + +LOG_CONFIG = { + "version": 1, + "formatters": { + "json": { + "()": "flask_google_cloud_logger.FlaskGoogleCloudFormatter", + "application_info": { + "type": "python-application", + "name": "Example Application" + }, + "format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s" + } + }, + "handlers": { + "json": { + "class": "logging.StreamHandler", + "formatter": "json" + } + }, + "loggers": { + "root": { + "level": "INFO", + "handlers": ["json"] + }, + "werkzeug": { + "level": "WARN", # Disable werkzeug hardcoded logger + "handlers": ["json"] + } + } +} + +config.dictConfig(LOG_CONFIG) # load log config from dict +logger = logging.getLogger("root") # get root logger instance +app = Flask("test_app") +FlaskGoogleCloudLogger(app) + + +@app.route("/") +def hello_world(): + return "Hello, World!" + + +@app.teardown_request +def log_request_time(_exception): + logger.info(f"{request.method} {request.path} - Sent {g.response.status_code} in {g.request_time:.5f}ms") +``` + +Example output: + +```json +{"timestamp": "2018-11-04T21:34:04.002000Z", "severity": "INFO", "message": "GET / - Sent 200 in 0.13828ms", "labels": {"client": {"user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36", "ip": "127.0.0.1", "version": null}, "connection": {"method": "GET", "path": "/", "request_id": "9135a0fc-8398-40a7-b830-47f6265672a2", "status": 200}, "latency": 0.13828277587890625}, "metadata": {"userLabels": {}}, "sourceLocation": {"file": "test_app.py", "line": 51, "function": "log_request_time"}} +``` + ## Credits -Thanks [@thulio](https://github.com/thulio), [@robsonpeixoto](https://github.com/robsonpeixoto), [@ramondelemos](https://github.com/ramondelemos) \ No newline at end of file +Thanks [@thulio](https://github.com/thulio), [@robsonpeixoto](https://github.com/robsonpeixoto), [@ramondelemos](https://github.com/ramondelemos) diff --git a/examples/test_app.py b/examples/test_app.py new file mode 100644 index 0000000..c81671a --- /dev/null +++ b/examples/test_app.py @@ -0,0 +1,52 @@ +import logging +from logging import config + +from flask import Flask, request, g +from flask_google_cloud_logger import FlaskGoogleCloudLogger + +LOG_CONFIG = { + "version": 1, + "formatters": { + "json": { + "()": "flask_google_cloud_logger.FlaskGoogleCloudFormatter", + "application_info": { + "type": "python-application", + "name": "Example Application" + }, + "format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s" + } + }, + "handlers": { + "json": { + "class": "logging.StreamHandler", + "formatter": "json" + } + }, + "loggers": { + "root": { + "level": "INFO", + "handlers": ["json"] + }, + "werkzeug": { + "level": "WARN", # Disable werkzeug hardcoded logger + "handlers": ["json"] + } + } +} + +config.dictConfig(LOG_CONFIG) # load log config from dict +logger = logging.getLogger("root") # get root logger instance +app = Flask("test_app") +FlaskGoogleCloudLogger(app) + + +@app.route("/") +def hello_world(): + return "Hello, World!" + + +@app.teardown_request +def log_request_time(_exception): + logger.info( + f"{request.method} {request.path} - Sent {g.response.status_code}" + + " in {g.request_time:.5f}ms") diff --git a/flask_google_cloud_logger/__init__.py b/flask_google_cloud_logger/__init__.py index 718ea01..39964c5 100644 --- a/flask_google_cloud_logger/__init__.py +++ b/flask_google_cloud_logger/__init__.py @@ -1,5 +1,5 @@ from . import hooks -from . import formatter # noqa +from .formatter import FlaskGoogleCloudFormatter # noqa class FlaskGoogleCloudLogger(object): @@ -18,5 +18,5 @@ def teardown(self, exception): "flask_google_cloud_logger_after_request", None) def init_app(self, app): - app.before_request(hooks.flask_google_cloud_logger_before_request) - app.after_request(hooks.flask_google_cloud_logger_after_request) + self.app.before_request(hooks.flask_google_cloud_logger_before_request) + self.app.after_request(hooks.flask_google_cloud_logger_after_request) diff --git a/flask_google_cloud_logger/formatter.py b/flask_google_cloud_logger/formatter.py index 4d6f490..adda99d 100644 --- a/flask_google_cloud_logger/formatter.py +++ b/flask_google_cloud_logger/formatter.py @@ -6,14 +6,15 @@ class FlaskGoogleCloudFormatter(GoogleCloudFormatter): - def make_metadata(self, _record): + def __init__(self, *args, **kwargs): + super(FlaskGoogleCloudFormatter, self).__init__(*args, **kwargs) + + def make_labels(self): if has_request_context(): return { - "userLabels": { - "client": self._make_client_info(request), - "connection": self._make_connection_info(request, g), - "latency": getattr(g, "request_time", None) - } + "client": self._make_client_info(request), + "connection": self._make_connection_info(request, g), + "latency": getattr(g, "request_time", None) } return {} diff --git a/setup.py b/setup.py index 629b560..e1258f5 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,10 @@ from setuptools import setup +__VERSION__ = "0.1.0" + setup( name="flask_google_cloud_logger", - version="0.0.2", + version=__VERSION__, description="Google Cloud Log Formatter for Flask", url="http://github.com/rai200890/flask_google_cloud_logger", author="Raissa Ferreira", @@ -10,7 +12,7 @@ license="MIT", packages=["flask_google_cloud_logger"], install_requires=[ - "google_cloud_logger>=0.0.2", + "google_cloud_logger>=0.1.0", "flask>=1.0", ], classifiers=[ diff --git a/test/test_flask_google_cloud_formatter.py b/test/test_flask_google_cloud_formatter.py index 526d126..26e8f10 100644 --- a/test/test_flask_google_cloud_formatter.py +++ b/test/test_flask_google_cloud_formatter.py @@ -1,6 +1,6 @@ import pytest -from flask_google_cloud_logger.formatter import FlaskGoogleCloudFormatter +from flask_google_cloud_logger import FlaskGoogleCloudFormatter @pytest.fixture @@ -8,29 +8,11 @@ def formatter(): return FlaskGoogleCloudFormatter("") -@pytest.fixture -def record(mocker): - return mocker.Mock( - asctime="2018-08-30 20:40:57,245", - filename="_internal.py", - funcName="_log", - lineno="88", - levelname="WARNING", - getMessage=lambda: "farofa") - - -@pytest.fixture -def message_dict(): - return {"extra": "extra_args", "extra_2": 1} - - -def test_make_metadata_when_request_context_isnt_available( - formatter, record, mocker): - assert formatter.make_metadata(record) == {} +def test_make_labels_when_request_context_isnt_available(formatter, mocker): + assert formatter.make_labels() == {} -def test_make_metadata_when_request_context_is_available( - formatter, record, mocker): +def test_make_metadata_when_request_context_is_available(formatter, mocker): mocker.patch( "flask_google_cloud_logger.formatter.has_request_context", return_value=True) @@ -50,21 +32,19 @@ def test_make_metadata_when_request_context_is_available( g_mock.status_code = 200 g_mock.request_time = 0.01 g_mock.response = mocker.Mock(status_code=200) - metadata = formatter.make_metadata(record) + metadata = formatter.make_labels() assert metadata == { - "userLabels": { - "client": { - "ip": "0.0.0.0", - "user_agent": "Browser", - "version": "1" - }, - "connection": { - "method": "GET", - "path": "/", - "request_id": "4353658", - "status": 200 - }, - "latency": 0.01 - } + "client": { + "ip": "0.0.0.0", + "user_agent": "Browser", + "version": "1" + }, + "connection": { + "method": "GET", + "path": "/", + "request_id": "4353658", + "status": 200 + }, + "latency": 0.01 }