From 7c12e92cd69a71717f621f0c2cc65593c57d98ec Mon Sep 17 00:00:00 2001 From: zzzhang6 Date: Sat, 3 Oct 2020 15:37:42 +0800 Subject: [PATCH 1/2] Support tornado web framework --- examples/images/tornado.png | Bin 0 -> 56637 bytes examples/tornado/README.md | 17 ++++ examples/tornado/pyproject.toml | 18 ++++ examples/tornado/tornado_example_app.py | 37 ++++++++ pyctuator/impl/tornado_pyctuator.py | 113 ++++++++++++++++++++++++ pyctuator/pyctuator.py | 16 ++++ 6 files changed, 201 insertions(+) create mode 100644 examples/images/tornado.png create mode 100644 examples/tornado/README.md create mode 100644 examples/tornado/pyproject.toml create mode 100644 examples/tornado/tornado_example_app.py create mode 100644 pyctuator/impl/tornado_pyctuator.py diff --git a/examples/images/tornado.png b/examples/images/tornado.png new file mode 100644 index 0000000000000000000000000000000000000000..8f6fc056a500601db37eb98c3387fc2085d68189 GIT binary patch literal 56637 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 >MrV20Wh3r+)D*w>&Ggta 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 O!~2T&awQ)>|33gY<}}d& literal 0 HcmV?d00001 diff --git a/examples/tornado/README.md b/examples/tornado/README.md new file mode 100644 index 0000000..e8e799b --- /dev/null +++ b/examples/tornado/README.md @@ -0,0 +1,17 @@ +# Tornado example +This example demonstrates the integration with the [Tornado](https://www.tornadoweb.org/). + +## Running the example +1. Start an instance of SBA (Spring Boot Admin): + ```sh + docker run --rm -p 8080:8080 michayaak/spring-boot-admin:2.2.3-1 + ``` +2. Once Spring Boot Admin is running, you can run the examples as follow: + ```sh + cd examples/tornado + poetry install + poetry run python -m tornado_example_app + ``` + +![tornado Example](../images/tornado.png) + diff --git a/examples/tornado/pyproject.toml b/examples/tornado/pyproject.toml new file mode 100644 index 0000000..9e3acb6 --- /dev/null +++ b/examples/tornado/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "tornado-pyctuator-example" +version = "1.0.0" +description = "Example of using Pyctuator" +authors = [ + "Desmond Stonie ", +] + +[tool.poetry.dependencies] +python = "^3.7" +psutil = { version = "^5.6" } +tornado = { version = "^6.0.4" } +pyctuator = { version = "^0.13" } + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" + diff --git a/examples/tornado/tornado_example_app.py b/examples/tornado/tornado_example_app.py new file mode 100644 index 0000000..5a4c2d8 --- /dev/null +++ b/examples/tornado/tornado_example_app.py @@ -0,0 +1,37 @@ +import datetime +import logging +import random + +from tornado import ioloop +from tornado.web import Application, RequestHandler +from tornado.httpserver import HTTPServer + +from pyctuator.pyctuator import Pyctuator + +my_logger = logging.getLogger("example") + +class HomeHandler(RequestHandler): + def get(self): + my_logger.debug(f"{datetime.datetime.now()} - {str(random.randint(0, 100))}") + print("Printing to STDOUT") + self.write("Hello World!") + +app = Application([ + (r"/", HomeHandler) +], debug=False) + +example_app_address = "host.docker.internal" +example_sba_address = "localhost" + +Pyctuator( + app, + "Tornado Pyctuator", + app_url=f"http://{example_app_address}:5000", + pyctuator_endpoint_url=f"http://{example_app_address}:5000/pyctuator", + registration_url=f"http://{example_sba_address}:8080/instances", + app_description="Demonstrate Spring Boot Admin Integration with Tornado", +) + +http_server = HTTPServer(app, decompress_request=True) +http_server.listen(5000) +ioloop.IOLoop.current().start() diff --git a/pyctuator/impl/tornado_pyctuator.py b/pyctuator/impl/tornado_pyctuator.py new file mode 100644 index 0000000..08625ce --- /dev/null +++ b/pyctuator/impl/tornado_pyctuator.py @@ -0,0 +1,113 @@ +import dataclasses +import json +from collections import defaultdict +from datetime import datetime +from functools import partial +from typing import Any, Callable, List, Mapping + +from tornado.web import Application, RequestHandler + +from pyctuator.httptrace import TraceRecord, TraceRequest, TraceResponse +from pyctuator.impl import SBA_V2_CONTENT_TYPE +from pyctuator.impl.pyctuator_impl import PyctuatorImpl +from pyctuator.impl.pyctuator_router import PyctuatorRouter + +class AbstractPyctuatorHandler(RequestHandler): + pyctuator = None + dumps = None + def initialize(self): + self.pyctuator = self.application.settings.get('pyctuator') + self.dumps = self.application.settings.get('custom_dumps') + +class PyctuatorHandler(AbstractPyctuatorHandler): + def get(self): + resp = self.pyctuator.get_endpoints_data() + self.write(self.dumps(resp)) + +# GET /env +class EnvHandler(AbstractPyctuatorHandler): + def options(self): + self.write('') + def get(self): + resp = self.pyctuator.pyctuator_impl.get_environment() + self.write(self.dumps(resp)) + +# GET /info +class InfoHandler(AbstractPyctuatorHandler): + def options(self): + self.write('') + def get(self): + resp = self.pyctuator.pyctuator_impl.app_info + self.write(self.dumps(resp)) + +# GET /health +class HealthHandler(AbstractPyctuatorHandler): + def options(self): + self.write('') + def get(self): + resp = self.pyctuator.pyctuator_impl.get_health() + self.write(self.dumps(resp)) + +# GET /metrics +class MetricsHandler(AbstractPyctuatorHandler): + def options(self): + self.write('') + def get(self): + resp = self.pyctuator.pyctuator_impl.get_metric_names() + self.write(self.dumps(resp)) + +# GET "/metrics/{metric_name}" +class MetricsNameHandler(AbstractPyctuatorHandler): + def get(self, metric_name): + resp = self.pyctuator.pyctuator_impl.get_metric_measurement(metric_name) + self.write(self.dumps(resp)) + +# GET /loggers +class LoggersHandler(AbstractPyctuatorHandler): + def options(self): + self.write('') + def get(self): + resp = self.pyctuator.pyctuator_impl.logging.get_loggers() + self.write(self.dumps(resp)) + +# GET /loggers/{logger_name} +# POST /loggers/{logger_name} +class LoggersNameHandler(AbstractPyctuatorHandler): + def get(self, logger_name): + resp = self.pyctuator.pyctuator_impl.logging.get_logger(logger_name) + self.write(self.dumps(resp)) + def post(self, logger_name): + body = self.request.body.decode('utf-8') + body = json.loads(body) + self.pyctuator.pyctuator_impl.logging.set_logger_level(logger_name, body.get('configuredLevel', None)) + self.write('') + +# pylint: disable=too-many-locals,unused-argument +class TornadoHttpPyctuator(PyctuatorRouter): + def __init__(self, app: Application, pyctuator_impl: PyctuatorImpl) -> None: + super().__init__(app, pyctuator_impl) + + custom_dumps = partial( + json.dumps, default=self._custom_json_serializer + ) + + app.settings.setdefault("pyctuator", self) + app.settings.setdefault("custom_dumps", custom_dumps) + app.add_handlers(".*$", [ + (r"/pyctuator", PyctuatorHandler), + (r"/pyctuator/env", EnvHandler), + (r"/pyctuator/info", InfoHandler), + (r"/pyctuator/health", HealthHandler), + (r"/pyctuator/metrics", MetricsHandler), + (r"/pyctuator/metrics/(?P.*$)", MetricsNameHandler), + (r"/pyctuator/loggers", LoggersHandler), + (r"/pyctuator/loggers/(?P.*$)", LoggersNameHandler), + ]) + + def _custom_json_serializer(self, value: Any) -> Any: + if dataclasses.is_dataclass(value): + return dataclasses.asdict(value) + + if isinstance(value, datetime): + return str(value) + return None diff --git a/pyctuator/pyctuator.py b/pyctuator/pyctuator.py index c692d2a..47de625 100644 --- a/pyctuator/pyctuator.py +++ b/pyctuator/pyctuator.py @@ -53,6 +53,8 @@ def __init__( * aiohttp - `app` is an instance of `aiohttp.web.Application` + * Tornado - `app` is an instance of `tornado.web.Application` + :param app: an instance of a supported web-framework with which the pyctuator endpoints will be registered :param app_name: the application's name that will be presented in the "Info" section in boot-admin :param app_description: a description that will be presented in the "Info" section in boot-admin @@ -101,6 +103,7 @@ def __init__( "flask": self._integrate_flask, "fastapi": self._integrate_fastapi, "aiohttp": self._integrate_aiohttp, + "tornado": self._integrate_tornado } for framework_name, framework_integration_function in framework_integrations.items(): if self._is_framework_installed(framework_name): @@ -195,3 +198,16 @@ def _integrate_aiohttp(self, app: Any, pyctuator_impl: PyctuatorImpl) -> bool: AioHttpPyctuator(app, pyctuator_impl) return True return False + + def _integrate_tornado(self, app: Any, pyctuator_impl: PyctuatorImpl) -> bool: + """ + This method should only be called if we detected that tornado is installed. + It will then check whether the given app is a tornado app, and if so - it will add the Pyctuator + endpoints to it. + """ + from tornado.web import Application + if isinstance(app, Application): + from pyctuator.impl.tornado_pyctuator import TornadoHttpPyctuator + TornadoHttpPyctuator(app, pyctuator_impl) + return True + return False From 874165d375bd4a22d8301331f2016347942b2eb4 Mon Sep 17 00:00:00 2001 From: zzzhang6 Date: Sat, 3 Oct 2020 15:48:29 +0800 Subject: [PATCH 2/2] Fixed missing dependency --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4dca14b..e7dba9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ PyMySQL = {version = "^0.9.3", optional = true} cryptography = {version = "^2.8", optional = true} redis = {version = "^3.3", optional = true} aiohttp = {version = "^3.6.2", optional = true} +tornado = {version = "^6.0.4", optional = true} [tool.poetry.dev-dependencies] requests = "^2.22" @@ -56,6 +57,7 @@ psutil = ["psutil"] fastapi = ["fastapi", "uvicorn"] flask = ["flask"] aiohttp = ["aiohttp"] +tornado = ["tornado"] db = ["sqlalchemy", "PyMySQL", "cryptography"] redis = ["redis"]