aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Mark <[email protected]>2022-06-01 11:09:30 -0700
committerGravatar GitHub <[email protected]>2022-06-01 11:09:30 -0700
commitb0fb2bf8e52f9e340a8f77503c68daafeed4d41d (patch)
tree132d3b703c1a29f5cc6de484d6317220ffc94da5
parentMerge #139 - use pip-tools instead of Pipenv (diff)
parentFix typo in Dockerfile comment (diff)
Merge #140 - add pyproject.toml and versioning
-rw-r--r--.coveragerc13
-rw-r--r--.dockerignore9
-rw-r--r--.github/workflows/build.yaml24
-rw-r--r--.github/workflows/deploy.yaml15
-rw-r--r--.github/workflows/main.yaml4
-rw-r--r--.github/workflows/test.yaml9
-rw-r--r--Dockerfile21
-rw-r--r--Makefile6
-rw-r--r--config/gunicorn.conf.py3
-rw-r--r--docker-compose.yml3
-rw-r--r--pyproject.toml66
-rw-r--r--requirements/requirements.in7
-rw-r--r--requirements/requirements.pip12
-rw-r--r--scripts/version.py38
-rw-r--r--snekbox/__init__.py55
-rw-r--r--snekbox/__main__.py2
-rw-r--r--snekbox/api/app.py3
-rw-r--r--snekbox/api/resources/eval.py2
-rw-r--r--snekbox/nsjail.py2
-rw-r--r--snekbox/utils/__init__.py4
-rw-r--r--snekbox/utils/cgroup.py2
-rw-r--r--snekbox/utils/gunicorn.py33
-rw-r--r--snekbox/utils/logging.py35
-rw-r--r--snekbox/utils/swap.py2
-rw-r--r--tests/gunicorn_utils.py4
25 files changed, 255 insertions, 119 deletions
diff --git a/.coveragerc b/.coveragerc
deleted file mode 100644
index ce475d7..0000000
--- a/.coveragerc
+++ /dev/null
@@ -1,13 +0,0 @@
-[run]
-branch = true
-data_file = ${COVERAGE_DATAFILE-.coverage}
-include = snekbox/*
-omit =
- snekbox/api/app.py
- snekbox/config_pb2.py
-relative_files = true
-
-[report]
-exclude_lines =
- pragma: no cover
- if DEBUG
diff --git a/.dockerignore b/.dockerignore
index 30ecfd4..6a360ff 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -2,8 +2,13 @@
*
# Make exceptions for what's needed
-!snekbox
+!.git/
!config/
!requirements/
-!tests
+!scripts/
+!snekbox/
+!tests/
!LICENSE
+!NOTICE
+!pyproject.toml
+!README.md
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 28e5b69..e5791c9 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -4,9 +4,9 @@ on:
artifact:
description: The name of the uploaded image aretfact.
value: ${{ jobs.build.outputs.artifact }}
- tag:
- description: The tag used for the built image.
- value: ${{ jobs.build.outputs.tag }}
+ version:
+ description: The package's version.
+ value: ${{ jobs.build.outputs.version }}
jobs:
build:
@@ -14,21 +14,21 @@ jobs:
runs-on: ubuntu-latest
outputs:
artifact: ${{ env.artifact }}
- tag: ${{ steps.sha_tag.outputs.tag }}
+ version: ${{ steps.version.outputs.version }}
env:
artifact: image_artifact_snekbox-venv
steps:
- # Create a short SHA with which to tag built images.
- - name: Create SHA Container Tag
- id: sha_tag
- run: |
- tag=$(cut -c 1-7 <<< $GITHUB_SHA)
- echo "::set-output name=tag::$tag"
-
- name: Checkout code
uses: actions/checkout@v2
+ - name: Get version
+ id: version
+ run: |
+ set -eu
+ version=$(python scripts/version.py)
+ echo "::set-output name=version::version"
+
# The current version (v2) of Docker's build-push action uses buildx,
# which comes with BuildKit. It has cache features which can speed up
# the builds. See https://github.com/docker/build-push-action
@@ -83,7 +83,7 @@ jobs:
ghcr.io/python-discord/snekbox-base:latest
ghcr.io/python-discord/snekbox-venv:latest
cache-to: ${{ steps.cache_config.outputs.cache_to }}
- tags: ghcr.io/python-discord/snekbox-venv:${{ steps.sha_tag.outputs.tag }}
+ tags: ghcr.io/python-discord/snekbox-venv:${{ steps.version.outputs.version }}
# Make the image available as an artifact so other jobs will be able to
# download it.
diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
index 3b12921..9113188 100644
--- a/.github/workflows/deploy.yaml
+++ b/.github/workflows/deploy.yaml
@@ -4,7 +4,7 @@ on:
artifact:
required: true
type: string
- tag:
+ version:
required: true
type: string
secrets:
@@ -55,8 +55,7 @@ jobs:
cache-to: type=inline
tags: |
ghcr.io/python-discord/snekbox:latest
- ghcr.io/python-discord/snekbox:${{ inputs.tag }}
- build-args: git_sha=${{ github.sha }}
+ ghcr.io/python-discord/snekbox:${{ inputs.version }}
# Deploy to Kubernetes.
- name: Authenticate with Kubernetes
@@ -69,7 +68,7 @@ jobs:
uses: Azure/k8s-deploy@v1
with:
manifests: deployment.yaml
- images: 'ghcr.io/python-discord/snekbox:${{ inputs.tag }}'
+ images: 'ghcr.io/python-discord/snekbox:${{ inputs.version }}'
kubectl-version: 'latest'
# Push the base image to GHCR, with an inline cache manifest.
@@ -82,7 +81,9 @@ jobs:
push: true
cache-from: ghcr.io/python-discord/snekbox-base:latest
cache-to: type=inline
- tags: ghcr.io/python-discord/snekbox-base:latest
+ tags: |
+ ghcr.io/python-discord/snekbox-base:latest
+ ghcr.io/python-discord/snekbox-base:${{ inputs.version }}
# Push the venv image to GHCR, with an inline cache manifest.
- name: Push venv image
@@ -96,4 +97,6 @@ jobs:
ghcr.io/python-discord/snekbox-base:latest
ghcr.io/python-discord/snekbox-venv:latest
cache-to: type=inline
- tags: ghcr.io/python-discord/snekbox-venv:latest
+ tags: |
+ ghcr.io/python-discord/snekbox-venv:latest
+ ghcr.io/python-discord/snekbox-venv:${{ inputs.version }}
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index f28ab61..b581ba3 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -16,13 +16,13 @@ jobs:
needs: build
with:
artifact: ${{ needs.build.outputs.artifact }}
- tag: ${{ needs.build.outputs.tag }}
+ version: ${{ needs.build.outputs.version }}
deploy:
uses: ./.github/workflows/deploy.yaml
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
needs: [build, lint, test]
with:
artifact: ${{ needs.build.outputs.artifact }}
- tag: ${{ needs.build.outputs.tag }}
+ version: ${{ needs.build.outputs.version }}
secrets:
KUBECONFIG: ${{ secrets.KUBECONFIG }}
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index f7f3635..30e6ba3 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -4,7 +4,7 @@ on:
artifact:
required: true
type: string
- tag:
+ version:
required: true
type: string
@@ -38,11 +38,12 @@ jobs:
- name: Run tests
id: run_tests
run: |
- export IMAGE_SUFFIX='-venv:${{ inputs.tag }}'
+ export IMAGE_SUFFIX='-venv:${{ inputs.version }}'
docker-compose run \
--rm -T -e COVERAGE_DATAFILE=.coverage.${{ matrix.os }} \
+ --entrypoint coverage \
snekbox \
- coverage run -m unittest
+ run -m unittest
# Upload it so the coverage from all matrix jobs can be combined later.
- name: Upload coverage data
@@ -57,7 +58,7 @@ jobs:
- name: Docker cleanup
if: matrix.os == 'self-hosted' && always()
run: |
- export IMAGE_SUFFIX='-venv:${{ inputs.tag }}'
+ export IMAGE_SUFFIX='-venv:${{ inputs.version }}'
docker-compose down --rmi all --remove-orphans -v -t 0
report:
diff --git a/Dockerfile b/Dockerfile
index c207a2b..e6110d6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,3 +1,4 @@
+# syntax=docker/dockerfile:1
FROM python:3.10-slim-buster as builder
WORKDIR /nsjail
@@ -31,6 +32,7 @@ ENV PATH=/root/.local/bin:$PATH \
RUN apt-get -y update \
&& apt-get install -y \
gcc=4:8.3.* \
+ git=1:2.20.* \
libnl-route-3-200=3.4.* \
libprotobuf17=3.6.* \
&& rm -rf /var/lib/apt/lists/*
@@ -60,18 +62,17 @@ RUN if [ -n "${DEV}" ]; \
fi
# At the end to avoid re-installing dependencies when only a config changes.
-# It's in the venv image because the final image is not used during development.
COPY config/ /snekbox/config/
-# ------------------------------------------------------------------------------
-FROM venv
-
ENTRYPOINT ["gunicorn"]
-CMD ["-c", "config/gunicorn.conf.py", "snekbox.api.app"]
+CMD ["-c", "config/gunicorn.conf.py"]
-COPY . /snekbox
-WORKDIR /snekbox
+# ------------------------------------------------------------------------------
+FROM venv
-# At the end to prevent it from invalidating the layer cache.
-ARG git_sha="development"
-ENV GIT_SHA=$git_sha
+# Use a separate directory to avoid importing the source over the installed pkg.
+# The venv already installed dependencies, so nothing besides snekbox itself
+# will be installed. Note requirements.pip cannot be used as a constraint file
+# because it contains extras, which pip disallows.
+RUN --mount=source=.,target=/snekbox_src,rw \
+ pip install /snekbox_src[gunicorn,sentry] \
diff --git a/Makefile b/Makefile
index 8a2705f..a30579a 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,8 @@ setup: install-piptools
.PHONY: upgrade
upgrade: install-piptools
- $(PIP_COMPILE_CMD) -o requirements/requirements.pip requirements/requirements.in
+ $(PIP_COMPILE_CMD) -o requirements/requirements.pip \
+ --extra gunicorn --extra sentry pyproject.toml
$(PIP_COMPILE_CMD) -o requirements/coverage.pip requirements/coverage.in
$(PIP_COMPILE_CMD) -o requirements/coveralls.pip requirements/coveralls.in
$(PIP_COMPILE_CMD) -o requirements/lint.pip requirements/lint.in
@@ -24,12 +25,11 @@ lint: setup
pre-commit run --all-files
# Fix ownership of the coverage file even if tests fail & preserve exit code
-# Install numpy because a test checks if it's importable
.PHONY: test
test:
docker-compose build -q --force-rm
docker-compose run --entrypoint /bin/bash --rm snekbox -c \
- 'coverage run -m unittest; e=$?; chown --reference=. .coverage; exit $e'
+ 'coverage run -m unittest; e=$?; chown --reference=. .coverage; exit $e'
.PHONY: report
report: setup
diff --git a/config/gunicorn.conf.py b/config/gunicorn.conf.py
index 5ab11f4..563f8ea 100644
--- a/config/gunicorn.conf.py
+++ b/config/gunicorn.conf.py
@@ -1,5 +1,6 @@
workers = 2
bind = "0.0.0.0:8060"
-logger_class = "snekbox.GunicornLogger"
+logger_class = "snekbox.utils.gunicorn.GunicornLogger"
access_logformat = "%(m)s %(U)s%(q)s %(s)s %(b)s %(L)ss"
access_logfile = "-"
+wsgi_app = "snekbox:SnekAPI"
diff --git a/docker-compose.yml b/docker-compose.yml
index 3854825..aa1a0f5 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,7 +5,7 @@ services:
container_name: snekbox_dev
hostname: snekbox_dev
privileged: true
- image: ghcr.io/python-discord/snekbox${IMAGE_SUFFIX:-:dev}
+ image: ghcr.io/python-discord/snekbox${IMAGE_SUFFIX:--venv:dev}
ports:
- 8060:8060
init: true
@@ -17,6 +17,7 @@ services:
build:
context: .
dockerfile: Dockerfile
+ target: venv
args:
DEV: 1
cache_from:
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..08aeb8c
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,66 @@
+[build-system]
+requires = ["setuptools>=61", "setuptools-git-versioning>=1.8"]
+build-backend = "setuptools.build_meta:__legacy__"
+
+[project]
+name = "snekbox"
+description = "HTTP REST API for sandboxed execution of arbitrary Python code."
+readme = "README.md"
+license = {text = "MIT"}
+authors = [{name = "Python Discord", email = "[email protected]"}]
+keywords = ["sandbox", "nsjail", "HTTP REST API"]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Console",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: POSIX :: Linux",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.10",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ "Topic :: Security",
+ "Topic :: Software Development :: Interpreters",
+]
+dynamic = ["version"]
+
+requires-python = ">=3.10"
+dependencies = [
+ # Sentry's Falcon integration relies on api_helpers (falconry/falcon#1902).
+ "falcon>=3.0.1",
+ "jsonschema>=4.0",
+ "protobuf>=3.19",
+]
+
+[project.optional-dependencies]
+gunicorn = ["gunicorn>=20.1"] # Lowest which supports wsgi_app in config.
+sentry = ["sentry-sdk[falcon]>=1.5.4"]
+
+[project.urls]
+source = "https://github.com/python-discord/snekbox"
+tracker = "https://github.com/python-discord/snekbox/issues"
+
+[project.scripts]
+snekbox = "snekbox.__main__:main"
+
+[tool.setuptools.packages.find]
+include = ["snekbox*"]
+
+[tool.setuptools-git-versioning]
+enabled = true
+version_callback = "scripts.version:get_version"
+
+[tool.coverage.report]
+exclude_lines = [
+ "pragma: no cover",
+ "if DEBUG"
+]
+
+[tool.coverage.run]
+branch = true
+data_file = "${COVERAGE_DATAFILE-.coverage}"
+include = ["snekbox/*"]
+omit = [
+ "snekbox/api/app.py",
+ "snekbox/config_pb2.py"
+]
+relative_files = true
diff --git a/requirements/requirements.in b/requirements/requirements.in
deleted file mode 100644
index 775ad39..0000000
--- a/requirements/requirements.in
+++ /dev/null
@@ -1,7 +0,0 @@
-# Sentry's Falcon integration relies on api_helpers. See falconry/falcon#1902
-falcon>=3.0.1
-
-gunicorn>=20
-jsonschema>=4.0
-protobuf>=3.19
-sentry-sdk[falcon]>=1.5.4
diff --git a/requirements/requirements.pip b/requirements/requirements.pip
index 21b6678..034f104 100644
--- a/requirements/requirements.pip
+++ b/requirements/requirements.pip
@@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
-# pip-compile --output-file=requirements/requirements.pip requirements/requirements.in
+# pip-compile --extra=gunicorn --extra=sentry --output-file=requirements/requirements.pip pyproject.toml
#
attrs==21.4.0
# via jsonschema
@@ -10,18 +10,18 @@ certifi==2022.5.18.1
# via sentry-sdk
falcon==3.1.0
# via
- # -r requirements/requirements.in
# sentry-sdk
+ # snekbox (pyproject.toml)
gunicorn==20.1.0
- # via -r requirements/requirements.in
+ # via snekbox (pyproject.toml)
jsonschema==4.5.1
- # via -r requirements/requirements.in
+ # via snekbox (pyproject.toml)
protobuf==4.21.1
- # via -r requirements/requirements.in
+ # via snekbox (pyproject.toml)
pyrsistent==0.18.1
# via jsonschema
sentry-sdk[falcon]==1.5.12
- # via -r requirements/requirements.in
+ # via snekbox (pyproject.toml)
urllib3==1.26.9
# via sentry-sdk
diff --git a/scripts/version.py b/scripts/version.py
new file mode 100644
index 0000000..bf8d509
--- /dev/null
+++ b/scripts/version.py
@@ -0,0 +1,38 @@
+import datetime
+import subprocess
+
+__all__ = ("get_version",)
+
+
+def get_version() -> str:
+ """
+ Return a version based on the HEAD commit's date.
+
+ The format is 'year.month.day.commits' and is compliant with PEP 440. 'commits' is the amount of
+ commits made on the same date as HEAD, excluding HEAD. This ensures versions are unique if
+ multiple release occur on the same date.
+ """
+ args = ["git", "show", "-s", "--format=%ct", "HEAD"]
+ stdout = subprocess.check_output(args, text=True)
+ timestamp = float(stdout.strip())
+ date = datetime.datetime.fromtimestamp(timestamp, datetime.timezone.utc)
+
+ commits = count_commits_on_date(date) - 1 # Exclude HEAD.
+
+ # Don't use strftime because it includes leading zeros, which are against PEP 440.
+ return f"{date.year}.{date.month}.{date.day}.{commits}"
+
+
+def count_commits_on_date(dt: datetime.datetime) -> int:
+ """Return the amount of commits made on the given UTC aware datetime."""
+ dt = dt.combine(dt - datetime.timedelta(days=1), dt.max.time(), dt.tzinfo)
+
+ # git log uses the committer date for this, not the author date.
+ args = ["git", "log", "--oneline", "--after", str(dt.timestamp())]
+ stdout = subprocess.check_output(args, text=True)
+
+ return stdout.strip().count("\n")
+
+
+if __name__ == "__main__":
+ print(get_version())
diff --git a/snekbox/__init__.py b/snekbox/__init__.py
index 42628d4..657c032 100644
--- a/snekbox/__init__.py
+++ b/snekbox/__init__.py
@@ -1,51 +1,18 @@
-import logging
import os
-import sys
-
-import sentry_sdk
-from gunicorn import glogging
-from gunicorn.config import Config
-from sentry_sdk.integrations.falcon import FalconIntegration
+from importlib import metadata
DEBUG = os.environ.get("DEBUG", False)
-GIT_SHA = os.environ.get("GIT_SHA", "development")
-
-sentry_sdk.init(
- dsn=os.environ.get("SNEKBOX_SENTRY_DSN", ""),
- integrations=[FalconIntegration()],
- send_default_pii=True,
- release=f"snekbox@{GIT_SHA}"
-)
-
-
-class GunicornLogger(glogging.Logger):
- """Logger for Gunicorn with custom formatting and support for the DEBUG environment variable."""
-
- error_fmt = "%(asctime)s | %(process)5s | %(name)30s | %(levelname)8s | %(message)s"
- access_fmt = error_fmt
- datefmt = None # Use the default ISO 8601 format
-
- def setup(self, cfg: Config) -> None:
- """
- Set up loggers and set error logger's level to DEBUG if the DEBUG env var is set.
-
- Note: Access and syslog handlers would need to be recreated to use a custom date format
- because they are created with an unspecified datefmt argument by default.
- """
- super().setup(cfg)
- if DEBUG:
- self.loglevel = logging.DEBUG
- else:
- self.loglevel = self.LOG_LEVELS.get(cfg.loglevel.lower(), logging.INFO)
+try:
+ __version__ = metadata.version("snekbox")
+except metadata.PackageNotFoundError: # pragma: no cover
+ __version__ = "0.0.0.0+unknown"
- self.error_log.setLevel(self.loglevel)
+from snekbox.api import SnekAPI # noqa: E402
+from snekbox.nsjail import NsJail # noqa: E402
+from snekbox.utils.logging import init_logger, init_sentry # noqa: E402
+__all__ = ("NsJail", "SnekAPI")
-log = logging.getLogger("snekbox")
-log.setLevel(logging.DEBUG if DEBUG else logging.INFO)
-log.propagate = True
-formatter = logging.Formatter(GunicornLogger.error_fmt)
-handler = logging.StreamHandler(sys.stdout)
-handler.setFormatter(formatter)
-log.addHandler(handler)
+init_sentry(__version__)
+init_logger(DEBUG)
diff --git a/snekbox/__main__.py b/snekbox/__main__.py
index 704ec9d..7ac10e9 100644
--- a/snekbox/__main__.py
+++ b/snekbox/__main__.py
@@ -1,7 +1,7 @@
import argparse
import sys
-from snekbox.nsjail import NsJail
+from snekbox import NsJail
def parse_args() -> argparse.Namespace:
diff --git a/snekbox/api/app.py b/snekbox/api/app.py
deleted file mode 100644
index c71e246..0000000
--- a/snekbox/api/app.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from . import SnekAPI
-
-application = SnekAPI()
diff --git a/snekbox/api/resources/eval.py b/snekbox/api/resources/eval.py
index 9560d0b..0a59f2e 100644
--- a/snekbox/api/resources/eval.py
+++ b/snekbox/api/resources/eval.py
@@ -5,6 +5,8 @@ from falcon.media.validators.jsonschema import validate
from snekbox.nsjail import NsJail
+__all__ = ("EvalResource",)
+
log = logging.getLogger(__name__)
diff --git a/snekbox/nsjail.py b/snekbox/nsjail.py
index ac36551..1aca637 100644
--- a/snekbox/nsjail.py
+++ b/snekbox/nsjail.py
@@ -13,6 +13,8 @@ from google.protobuf import text_format
from snekbox import DEBUG, utils
from snekbox.config_pb2 import NsJailConfig
+__all__ = ("NsJail",)
+
log = logging.getLogger(__name__)
# [level][timestamp][PID]? function_signature:line_no? message
diff --git a/snekbox/utils/__init__.py b/snekbox/utils/__init__.py
index 5a7b632..6d6bc32 100644
--- a/snekbox/utils/__init__.py
+++ b/snekbox/utils/__init__.py
@@ -1,3 +1,3 @@
-from . import cgroup, swap
+from . import cgroup, logging, swap
-__all__ = ("cgroup", "swap")
+__all__ = ("cgroup", "logging", "swap")
diff --git a/snekbox/utils/cgroup.py b/snekbox/utils/cgroup.py
index 3e12406..cd515ab 100644
--- a/snekbox/utils/cgroup.py
+++ b/snekbox/utils/cgroup.py
@@ -5,6 +5,8 @@ from snekbox.config_pb2 import NsJailConfig
log = logging.getLogger(__name__)
+__all__ = ("get_version", "init", "init_v1", "init_v2")
+
def get_version(config: NsJailConfig) -> int:
"""
diff --git a/snekbox/utils/gunicorn.py b/snekbox/utils/gunicorn.py
new file mode 100644
index 0000000..68e3ed9
--- /dev/null
+++ b/snekbox/utils/gunicorn.py
@@ -0,0 +1,33 @@
+import logging
+
+from gunicorn import glogging
+from gunicorn.config import Config
+
+from snekbox import DEBUG
+from .logging import FORMAT
+
+__all__ = ("GunicornLogger",)
+
+
+class GunicornLogger(glogging.Logger):
+ """Logger for Gunicorn with custom formatting and support for the DEBUG environment variable."""
+
+ error_fmt = FORMAT
+ access_fmt = error_fmt
+ datefmt = None # Use the default ISO 8601 format
+
+ def setup(self, cfg: Config) -> None:
+ """
+ Set up loggers and set error logger's level to DEBUG if the DEBUG env var is set.
+
+ Note: Access and syslog handlers would need to be recreated to use a custom date format
+ because they are created with an unspecified datefmt argument by default.
+ """
+ super().setup(cfg)
+
+ if DEBUG:
+ self.loglevel = logging.DEBUG
+ else:
+ self.loglevel = self.LOG_LEVELS.get(cfg.loglevel.lower(), logging.INFO)
+
+ self.error_log.setLevel(self.loglevel)
diff --git a/snekbox/utils/logging.py b/snekbox/utils/logging.py
new file mode 100644
index 0000000..c15e3f1
--- /dev/null
+++ b/snekbox/utils/logging.py
@@ -0,0 +1,35 @@
+import logging
+import os
+import sys
+
+__all__ = ("FORMAT", "init_logger", "init_sentry")
+
+FORMAT = "%(asctime)s | %(process)5s | %(name)30s | %(levelname)8s | %(message)s"
+
+
+def init_logger(debug: bool) -> None:
+ """Initialise the root logger with a handler that outputs to stdout."""
+ log = logging.getLogger("snekbox")
+ log.setLevel(logging.DEBUG if debug else logging.INFO)
+ log.propagate = True
+
+ formatter = logging.Formatter(FORMAT)
+ handler = logging.StreamHandler(sys.stdout)
+ handler.setFormatter(formatter)
+ log.addHandler(handler)
+
+
+def init_sentry(version: str) -> None:
+ """Initialise the Sentry SDK if it's installed."""
+ try:
+ import sentry_sdk
+ from sentry_sdk.integrations.falcon import FalconIntegration
+ except ImportError:
+ return
+
+ sentry_sdk.init(
+ dsn=os.environ.get("SNEKBOX_SENTRY_DSN", ""),
+ integrations=[FalconIntegration()],
+ send_default_pii=True,
+ release=f"snekbox@{version}"
+ )
diff --git a/snekbox/utils/swap.py b/snekbox/utils/swap.py
index 3e0d0aa..6a919cb 100644
--- a/snekbox/utils/swap.py
+++ b/snekbox/utils/swap.py
@@ -4,6 +4,8 @@ from pathlib import Path
from snekbox.config_pb2 import NsJailConfig
+__all__ = ("controller_exists", "is_enabled", "should_ignore_limit")
+
log = logging.getLogger(__name__)
diff --git a/tests/gunicorn_utils.py b/tests/gunicorn_utils.py
index f2d9b6d..b417b1b 100644
--- a/tests/gunicorn_utils.py
+++ b/tests/gunicorn_utils.py
@@ -17,8 +17,8 @@ class _StandaloneApplication(Application):
pass
def load(self):
- from snekbox.api.app import application
- return application
+ from snekbox.api import SnekAPI
+ return SnekAPI()
def load_config(self):
for key, value in self.options.items():