Skip to content
Merged
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
19 changes: 11 additions & 8 deletions .github/workflows/code_quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v7
Comment thread
suppagoddo marked this conversation as resolved.
- name: Set up Python 3.9
uses: actions/setup-python@v2
uses: actions/setup-python@v7
with:
python-version: 3.9
- name: Install dependencies
Expand All @@ -35,16 +35,17 @@ jobs:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ]

steps:
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
- name: "Git checkout"
uses: actions/checkout@v2
uses: actions/checkout@v7
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand All @@ -58,16 +59,18 @@ jobs:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
backend: ["fastapi", "flask", "quart", "sanic"]
python-version: ["3.9", "3.14"]

steps:
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v7
with:
python-version: 3.9
python-version: ${{ matrix.python-version }}
- name: "Git checkout"
uses: actions/checkout@v2
uses: actions/checkout@v7
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ Key locations:
## Local checks

Create and activate a virtual environment before installing test requirements.
The existing CI covers Python 3.9–3.13; this is not a claim that every framework
dependency supports every version. Verify actual installation and runtime.
The CI runs unit tests on Python 3.9–3.14 and framework smoke tests on Python 3.9 and 3.14;
this is not a claim that every framework dependency supports every version. Verify actual
installation and runtime.

```bash
python -m pip install -r test-requirements.txt
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
The format is based on [Keep a Changelog](http://keepachangelog.com/).
## Unreleased

- fix: `init_sanic()` no longer raises `KeyError: 'format'` when Sanic 24.x and later omit
formatter `format` keys; older Sanic logging configuration remains supported (PR #133).

- fix: FastAPI application logs and `json_logging.get_correlation_id()` now use the request's correlation id
instead of `-` in synchronous (`def`) endpoints (fixes #124, PR #123). When request instrumentation is enabled with
`init_request_instrument(app)` (Python 3.9+):
Expand Down
2 changes: 1 addition & 1 deletion MAINTENANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
```

The GitHub Actions workflow `.github/workflows/code_quality.yml` runs
unit tests on Python 3.9–3.13, lint, and separate smoke tests for FastAPI,
unit tests on Python 3.9–3.14, lint, and separate smoke tests on Python 3.9 and 3.14 for FastAPI,
Flask, Quart, and Sanic. The shared smoke-test runner starts an example
service on localhost:5000; that port must be available for local runs.

Expand Down
4 changes: 2 additions & 2 deletions json_logging/framework/sanic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ def config(self):
LOGGING_CONFIG_DEFAULTS["formatters"]["generic"][
"class"
] = "json_logging.JSONLogFormatter"
del LOGGING_CONFIG_DEFAULTS["formatters"]["generic"]["format"]
LOGGING_CONFIG_DEFAULTS["formatters"]["generic"].pop("format", None)

LOGGING_CONFIG_DEFAULTS["formatters"]["access"][
"class"
] = "json_logging.JSONLogFormatter"
del LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"]
LOGGING_CONFIG_DEFAULTS["formatters"]["access"].pop("format", None)

# logging.config.dictConfig(LOGGING_CONFIG_DEFAULTS)

Expand Down
6 changes: 3 additions & 3 deletions tests/smoketests/quart/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import asyncio, logging, sys, json_logging, quart
import logging, sys, json_logging, quart

app = quart.Quart(__name__)
json_logging.init_quart(enable_json=True)
Expand All @@ -17,5 +17,5 @@ async def home():
return "Hello world"

if __name__ == "__main__":
loop = asyncio.get_event_loop()
app.run(host='0.0.0.0', port=int(5000), use_reloader=False, loop=loop)
# Quart manages its own event loop; get_event_loop() raises without a loop on Python 3.14.
app.run(host='0.0.0.0', port=int(5000), use_reloader=False)
5 changes: 4 additions & 1 deletion tests/smoketests/sanic/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
sanic==20.3.0
# Preserve coverage of the legacy Sanic integration on Python 3.9.
sanic==20.3.0; python_version < "3.10"
# Sanic 25.12+ supports Python 3.14; 20.3.0 pulls in httpx importing removed cgi.
sanic>=25.12,<26; python_version >= "3.10"
requests
pytest
-e .
74 changes: 74 additions & 0 deletions tests/test_sanic_logging_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Regression tests for Sanic default logging formatter compatibility.

Sanic 24+ removes the legacy 'format' keys from generic/access formatters.
Run each initialization in a fresh Python process because json_logging.init_sanic()
changes process-global state and intentionally cannot be called twice.
"""
import os
import subprocess
import sys
import textwrap
from pathlib import Path

import pytest


ROOT = Path(__file__).resolve().parents[1]


@pytest.mark.parametrize("config_shape", ["legacy", "modern", "installed"])
def test_init_sanic_handles_logging_formats_and_emits_json(config_shape):
"""Both old and new Sanic config shapes initialize and produce parseable JSON records."""
code = textwrap.dedent("""
import io
import json
import logging
import sanic.log
import json_logging

config_shape = %(config_shape)r
if config_shape != "installed":
formatters = {
"generic": {"format": "%%(message)s"} if config_shape == "legacy" else {},
"access": {"format": "%%(message)s"} if config_shape == "legacy" else {},
}
sanic.log.LOGGING_CONFIG_DEFAULTS = {
"disable_existing_loggers": True,
"formatters": formatters,
}

json_logging.init_sanic(enable_json=True)
defaults = sanic.log.LOGGING_CONFIG_DEFAULTS
assert defaults["disable_existing_loggers"] is False
logger = logging.getLogger("sanic-formatter-test")
logger.setLevel(logging.INFO)
logger.propagate = False

for name in ("generic", "access"):
formatter_config = defaults["formatters"][name]
assert formatter_config["class"] == "json_logging.JSONLogFormatter"
assert "format" not in formatter_config

output = io.StringIO()
handler = logging.StreamHandler(output)
formatter_class = getattr(json_logging, formatter_config["class"].rsplit(".", 1)[-1])
handler.setFormatter(formatter_class())
logger.addHandler(handler)
try:
logger.info("sanic json logging", extra={"service": name})
finally:
logger.removeHandler(handler)
handler.close()
record = json.loads(output.getvalue().strip())
assert record["type"] == "log"
assert record["msg"] == "sanic json logging"
assert record["service"] == name
print("PASS")
""" % {"config_shape": config_shape})
env = dict(os.environ, PYTHONWARNINGS="ignore")
result = subprocess.run(
[sys.executable, "-c", code], cwd=str(ROOT), env=env,
capture_output=True, text=True, timeout=40,
)
assert result.returncode == 0, result.stdout + result.stderr
assert "PASS" in result.stdout
Loading