Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion questionpy_sdk/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from questionpy_sdk.constants import DEFAULT_STATE_STORAGE_PATH
from questionpy_sdk.watcher import Watcher
from questionpy_sdk.webserver import WebServer
from questionpy_sdk.webserver.server import WebServerArgs
from questionpy_sdk.webserver.server import AccessLogMode, WebServerArgs
from questionpy_server.worker.impl.subprocess import SubprocessWorker
from questionpy_server.worker.impl.thread import ThreadWorker
from questionpy_server.worker.runtime.package_location import DirPackageLocation
Expand Down Expand Up @@ -57,6 +57,15 @@ async def async_run(webserver_args: WebServerArgs) -> None:
show_default=True,
help="The worker implementation to use. Thread workers offer no isolation but may improve debugging experience.",
)
@click.option(
"--access-log",
"-l",
"access_log_mode",
type=click.Choice(("none", "api", "all"), case_sensitive=False),
default="api",
show_default=True,
help="Access log mode to use, none disables logging, api logs only API requests, all logs everything.",
)
def run(
package: str,
state_storage_path: Path,
Expand All @@ -65,6 +74,7 @@ def run(
*,
watch: bool,
worker: Literal["subprocess", "thread"],
access_log_mode: AccessLogMode,
) -> None:
"""Run a package.

Expand All @@ -84,6 +94,7 @@ def run(
host=host,
port=port,
worker_class=ThreadWorker if worker == "thread" else SubprocessWorker,
access_log_mode=access_log_mode,
)

if watch:
Expand Down
21 changes: 19 additions & 2 deletions questionpy_sdk/webserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import logging
from pathlib import Path
from types import TracebackType
from typing import ClassVar, NotRequired, Self, TypedDict, Unpack
from typing import ClassVar, Literal, NotRequired, Self, TypedDict, Unpack

from aiohttp import web

Expand All @@ -27,13 +27,28 @@

log = logging.getLogger("questionpy-sdk:web-server")

type AccessLogMode = Literal["all", "api", "none"]


class WebServerArgs(TypedDict):
package_location: PackageLocation
state_storage_path: Path
host: NotRequired[str]
port: NotRequired[int]
worker_class: NotRequired[type[Worker]]
access_log_mode: NotRequired[AccessLogMode]


class FilteredAccessLogger(web.AccessLogger):
mode: AccessLogMode = "api"

def log(self, request: web.BaseRequest, response: web.StreamResponse, time: float) -> None:
if self.mode == "none":
return
if self.mode == "api" and not request.path.startswith(f"{API_PATH_PREFIX}/"):
return

super().log(request, response, time)


class WebServer:
Expand All @@ -45,6 +60,7 @@ def __init__(self, **kwargs: Unpack[WebServerArgs]) -> None:
self._host = kwargs.get("host", "localhost")
self._port = kwargs.get("port", 8080)
self._worker_class = kwargs.get("worker_class", self.DEFAULT_WORKER_CLASS)
self._access_log_mode = kwargs.get("access_log_mode", "api")

self._app: web.Application
self._api_app: web.Application
Expand Down Expand Up @@ -76,7 +92,8 @@ async def __aenter__(self) -> Self:

# Create web app
self._app = self._create_webapp()
self._runner = web.AppRunner(self.app)
FilteredAccessLogger.mode = self._access_log_mode
self._runner = web.AppRunner(self.app, access_log=log, access_log_class=FilteredAccessLogger)
await self._runner.setup()
await web.TCPSite(self._runner, self._host, self._port).start()
self._print_status()
Expand Down