increase logging verbosity around s3 access errors
Docker server image / build-and-push (push) Successful in 3m14s

This commit is contained in:
2026-03-27 18:10:18 -07:00
parent 3e8e6bd543
commit 78272ad0d2
5 changed files with 70 additions and 2 deletions
+4
View File
@@ -5,7 +5,11 @@ S3_USE_SSL=false
S3_PUBLIC_USE_SSL=false
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
# Optional SigV4 region for strict S3 gateways (e.g. some SeaweedFS / proxy setups).
# S3_REGION=us-east-1
S3_SESSION_TTL_SECONDS=3600
# Set to true to dump raw S3 HTTP on stderr (debugging AccessDenied / proxies).
# OFFICECONVERT_S3_TRACE=true
CONVERSION_PPTX_TO_PDF_TIMEOUT_SECONDS=180
CONVERSION_PDF_TO_IMAGES_TIMEOUT_SECONDS=1800
CONVERSION_PPTX_TO_PDF_BASE_TIMEOUT_SECONDS=45
@@ -4,6 +4,7 @@ from __future__ import annotations
import logging
import os
import sys
from officeconvertapi.v1.conversion_connect import ConversionServiceASGIApplication
@@ -47,9 +48,13 @@ def create_app() -> ConversionServiceASGIApplication:
access_key=config.s3_access_key,
secret_key=config.s3_secret_key,
secure=config.s3_secure,
region=config.s3_region,
public_endpoint=config.s3_public_endpoint,
public_secure=config.s3_public_secure,
)
if os.getenv("OFFICECONVERT_S3_TRACE", "").lower() in ("1", "true", "yes"):
store.enable_http_trace(sys.stderr)
logger.warning("OFFICECONVERT_S3_TRACE enabled: S3 HTTP dumps on stderr")
service = ConversionServiceImpl(config=config, store=store)
return ConversionServiceASGIApplication(service)
@@ -14,6 +14,7 @@ class ServerConfig:
s3_access_key: str
s3_secret_key: str
s3_secure: bool
s3_region: str | None
s3_public_endpoint: str
s3_public_secure: bool
s3_session_ttl_seconds: int
@@ -35,11 +36,13 @@ def load_server_config() -> ServerConfig:
if public_ssl_env is not None
else s3_secure
)
region_env = os.getenv("S3_REGION", "").strip()
return ServerConfig(
s3_endpoint=os.getenv("S3_ENDPOINT", "localhost:8333"),
s3_access_key=os.getenv("S3_ACCESS_KEY", "minioadmin"),
s3_secret_key=os.getenv("S3_SECRET_KEY", "minioadmin"),
s3_secure=s3_secure,
s3_region=region_env or None,
s3_public_endpoint=os.getenv("S3_PUBLIC_ENDPOINT", "localhost:8333"),
s3_public_secure=s3_public_secure,
s3_session_ttl_seconds=int(os.getenv("S3_SESSION_TTL_SECONDS", "3600")),
@@ -30,9 +30,11 @@ from officeconvert.conversion import (
)
from officeconvertapi.v1 import conversion_connect, conversion_pb2
from minio.error import S3Error
from officeconvert_server.config import ServerConfig
from officeconvert_server.models import ConversionSession, utc_now
from officeconvert_server.storage import S3Store
from officeconvert_server.storage import S3Store, log_s3_error
logger = logging.getLogger("uvicorn.error")
@@ -78,7 +80,16 @@ class ConversionServiceImpl(conversion_connect.ConversionService):
upload_key = "input/source.pptx"
expires_at = utc_now() + timedelta(seconds=self._config.s3_session_ttl_seconds)
self._store.ensure_bucket(bucket_name)
try:
self._store.ensure_bucket(bucket_name)
except S3Error as exc:
log_s3_error(
"ensure_bucket",
endpoint=self._config.s3_endpoint,
secure=self._config.s3_secure,
exc=exc,
)
raise
upload_url = self._store.presigned_put_url(
bucket_name,
upload_key,
@@ -2,14 +2,51 @@
from __future__ import annotations
import logging
from datetime import timedelta
from pathlib import Path
from typing import TextIO
from urllib.parse import urlparse
from minio import Minio
from minio.deleteobjects import DeleteObject
from minio.error import S3Error
_log = logging.getLogger(__name__)
def log_s3_error(
operation: str,
*,
endpoint: str,
secure: bool,
exc: S3Error,
) -> None:
"""Emit HTTP details for S3 failures (MinIO often maps HTTP 403 to AccessDenied)."""
status = getattr(exc.response, "status", None)
ctype = (
exc.response.headers.get("content-type")
if exc.response is not None
else None
)
body = ""
if exc.response is not None and exc.response.data:
body = exc.response.data.decode(errors="replace")[:4000]
_log.error(
"S3 %s failed: code=%r message=%r http_status=%s content_type=%r "
"endpoint=%s secure=%s resource=%r request_id=%r body=%r",
operation,
exc.code,
exc.message,
status,
ctype,
endpoint,
secure,
exc.resource,
exc.request_id,
body,
)
class S3Store:
"""Provides typed helper methods around S3-compatible object storage operations."""
@@ -21,6 +58,7 @@ class S3Store:
access_key: str,
secret_key: str,
secure: bool,
region: str | None,
public_endpoint: str,
public_secure: bool,
) -> None:
@@ -30,14 +68,21 @@ class S3Store:
access_key=access_key,
secret_key=secret_key,
secure=secure,
region=region,
)
self._public_client = Minio(
public_endpoint,
access_key=access_key,
secret_key=secret_key,
secure=public_secure,
region=region,
)
def enable_http_trace(self, stream: TextIO) -> None:
"""Write raw HTTP request/response traces for both clients (debugging)."""
self._client.trace_on(stream)
self._public_client.trace_on(stream)
def ensure_bucket(self, bucket_name: str) -> None:
"""Create a bucket if it does not already exist.