Skip to content

Commit 679c69b

Browse files
committed
Add logging.
1 parent 4365a1b commit 679c69b

File tree

2 files changed

+90
-2
lines changed

2 files changed

+90
-2
lines changed

src/judge0/__init__.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import logging
2+
import logging.handlers
23
import os
4+
import sys
35

6+
from pathlib import Path
47
from typing import Union
58

69
from .api import (
@@ -66,10 +69,79 @@
6669
JUDGE0_IMPLICIT_CE_CLIENT = None
6770
JUDGE0_IMPLICIT_EXTRA_CE_CLIENT = None
6871

72+
try:
73+
from dotenv import load_dotenv
74+
75+
load_dotenv()
76+
except: # noqa: E722
77+
pass
78+
79+
80+
def setup_logging(
81+
log_file: Path | None = None,
82+
file_level: int | None = None,
83+
console_level: int | None = None,
84+
) -> None:
85+
"""Set up logging for the package."""
86+
if log_file is None:
87+
try:
88+
log_dir = Path.home() / ".judge0" / "logs"
89+
log_dir.mkdir(parents=True, exist_ok=True)
90+
log_file = log_dir / "judge0.log"
91+
except (OSError, PermissionError):
92+
# Fallback to user's current working directory
93+
log_file = Path("judge0.log")
94+
95+
logger = logging.getLogger("judge0")
96+
logger.setLevel(logging.DEBUG)
97+
98+
# Check if handlers are already added to avoid duplication.
99+
if logger.hasHandlers():
100+
return
101+
102+
# Determine log levels: parameter > env var > default
103+
if file_level is None:
104+
file_level = getattr(
105+
logging, os.getenv("JUDGE0_FILE_LOG_LEVEL", "DEBUG").upper(), logging.DEBUG
106+
)
107+
if console_level is None:
108+
console_level = getattr(
109+
logging, os.getenv("JUDGE0_CONSOLE_LOG_LEVEL", "INFO").upper(), logging.INFO
110+
)
111+
112+
formatter = logging.Formatter(
113+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
114+
)
115+
116+
# File handler with weekly rotation.
117+
file_handler = logging.handlers.TimedRotatingFileHandler(
118+
filename=log_file,
119+
when="W0", # Rotate every Monday
120+
interval=1,
121+
backupCount=4, # Keep 4 weeks of logs
122+
)
123+
file_handler.setLevel(file_level)
124+
file_handler.setFormatter(formatter)
125+
126+
# Console handler
127+
console_handler = logging.StreamHandler(sys.stdout)
128+
console_handler.setLevel(console_level)
129+
console_handler.setFormatter(formatter)
130+
131+
# Add handlers
132+
logger.addHandler(file_handler)
133+
logger.addHandler(console_handler)
134+
135+
136+
# Call setup logging before intializing the logger below.
137+
setup_logging()
138+
69139
logger = logging.getLogger(__name__)
70140
suppress_preview_warning = os.getenv("JUDGE0_SUPPRESS_PREVIEW_WARNING") is not None
71141

72142

143+
# TODO: I belive that the whole logic for importing implicit client can be moved
144+
# to a separate module.
73145
def _get_implicit_client(flavor: Flavor) -> Client:
74146
global JUDGE0_IMPLICIT_CE_CLIENT, JUDGE0_IMPLICIT_EXTRA_CE_CLIENT
75147

src/judge0/api.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from typing import Optional, Union
23

34
from .base_types import Flavor, Iterable, TestCase, TestCases, TestCaseType
@@ -7,6 +8,8 @@
78
from .retry import RegularPeriodRetry, RetryStrategy
89
from .submission import Submission, Submissions
910

11+
logger = logging.getLogger(__name__)
12+
1013

1114
def get_client(flavor: Flavor = Flavor.CE) -> Client:
1215
"""Resolve client from API keys from environment or default to preview client.
@@ -24,7 +27,9 @@ def get_client(flavor: Flavor = Flavor.CE) -> Client:
2427
from . import _get_implicit_client
2528

2629
if isinstance(flavor, Flavor):
27-
return _get_implicit_client(flavor=flavor)
30+
client = _get_implicit_client(flavor=flavor)
31+
logger.debug(f"Resolved implicit client for flavor {flavor}: {client}")
32+
return client
2833
else:
2934
raise ValueError(
3035
"Expected argument flavor to be of of type enum Flavor, "
@@ -58,13 +63,15 @@ def _resolve_client(
5863
"""
5964
# User explicitly passed a client.
6065
if isinstance(client, Client):
66+
logger.debug(f"Using explicitly provided client: {client}")
6167
return client
6268

6369
# NOTE: At the moment, we do not support the option to check if explicit
6470
# flavor of a client supports the submissions, i.e. submissions argument is
6571
# ignored if flavor argument is provided.
6672

6773
if isinstance(client, Flavor):
74+
logger.debug(f"Resolving client from flavor: {client}")
6875
return get_client(client)
6976

7077
if client is None:
@@ -80,12 +87,14 @@ def _resolve_client(
8087

8188
# Check which client supports all languages from the provided submissions.
8289
languages = [submission.language for submission in submissions]
90+
logger.debug(f"Attempting to resolve client for languages: {languages}")
8391

8492
for flavor in Flavor:
8593
client = get_client(flavor)
8694
if client is not None and all(
8795
(client.is_language_supported(lang) for lang in languages)
8896
):
97+
logger.debug(f"Resolved client {client} for languages {languages}")
8998
return client
9099

91100
raise ClientResolutionError(
@@ -117,8 +126,10 @@ def create_submissions(
117126
client = _resolve_client(client=client, submissions=submissions)
118127

119128
if isinstance(submissions, Submission):
129+
logger.info("Creating a single submission.")
120130
return client.create_submission(submissions)
121131

132+
logger.info(f"Creating {len(submissions)} submissions.")
122133
result_submissions = []
123134
for submission_batch in batched(
124135
submissions, client.config.max_submission_batch_size
@@ -156,8 +167,10 @@ def get_submissions(
156167
client = _resolve_client(client=client, submissions=submissions)
157168

158169
if isinstance(submissions, Submission):
170+
logger.debug("Getting status for a single submission.")
159171
return client.get_submission(submissions, fields=fields)
160172

173+
logger.debug(f"Getting status for {len(submissions)} submissions.")
161174
result_submissions = []
162175
for submission_batch in batched(
163176
submissions, client.config.max_submission_batch_size
@@ -218,13 +231,16 @@ def wait(
218231
submission.token: submission for submission in submissions_list
219232
}
220233

234+
logger.info(f"Waiting for {len(submissions_to_check)} submissions to finish.")
221235
while len(submissions_to_check) > 0 and not retry_strategy.is_done():
236+
logger.debug(f"Checking {len(submissions_to_check)} submissions...")
222237
get_submissions(client=client, submissions=list(submissions_to_check.values()))
223238
finished_submissions = [
224239
token
225240
for token, submission in submissions_to_check.items()
226241
if submission.is_done()
227242
]
243+
logger.debug(f"{len(finished_submissions)} submissions finished in this step.")
228244
for token in finished_submissions:
229245
submissions_to_check.pop(token)
230246

@@ -314,7 +330,6 @@ def _execute(
314330
wait_for_result: bool = False,
315331
**kwargs,
316332
) -> Union[Submission, Submissions]:
317-
318333
if submissions is not None and source_code is not None:
319334
raise ValueError(
320335
"Both submissions and source_code arguments are provided. "
@@ -327,6 +342,7 @@ def _execute(
327342
if source_code is not None:
328343
submissions = Submission(source_code=source_code, **kwargs)
329344

345+
logger.info("Starting execution process.")
330346
client = _resolve_client(client=client, submissions=submissions)
331347
all_submissions = create_submissions_from_test_cases(submissions, test_cases)
332348
all_submissions = create_submissions(client=client, submissions=all_submissions)

0 commit comments

Comments
 (0)