Skip to content

Commit 41dad5d

Browse files
fix: route logging to stdout, simplify format, add JOBS level
- Route all DataJoint logs to stdout instead of stderr (avoids red highlighting in terminals/IDEs that color stderr) - Simplify INFO/DEBUG format: remove level tag for cleaner output - Keep [WARNING] and [ERROR] tags for actual issues - Add JOBS log level (15, between DEBUG and INFO) for make status messages (start/error/success) so they don't flood INFO logs Log levels: DEBUG (10) < JOBS (15) < INFO (20) < WARNING (30) < ERROR (40) Example output: [2026-01-27 10:30:45] DataJoint 2.0.0 connected to postgres@localhost:5432 [2026-01-27 10:30:46][JOBS]: Making {'key': 1} -> schema.Table [2026-01-27 10:30:47][JOBS]: Success making {'key': 1} -> schema.Table [2026-01-27 10:31:02][WARNING]: Table xyz not found Users can filter job messages with DJ_LOG_LEVEL=INFO (hides JOBS) or see them with DJ_LOG_LEVEL=JOBS or DJ_LOG_LEVEL=DEBUG. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c41dfac commit 41dad5d

File tree

2 files changed

+34
-6
lines changed

2 files changed

+34
-6
lines changed

src/datajoint/autopopulate.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ def _populate1(
598598
jobs.complete(key)
599599
return False
600600

601-
logger.debug(f"Making {key} -> {self.full_table_name}")
601+
logger.jobs(f"Making {key} -> {self.full_table_name}")
602602
self.__class__._allow_insert = True
603603

604604
try:
@@ -629,7 +629,7 @@ def _populate1(
629629
exception=error.__class__.__name__,
630630
msg=": " + str(error) if str(error) else "",
631631
)
632-
logger.debug(f"Error making {key} -> {self.full_table_name} - {error_message}")
632+
logger.jobs(f"Error making {key} -> {self.full_table_name} - {error_message}")
633633
if jobs is not None:
634634
jobs.error(key, error_message=error_message, error_stack=traceback.format_exc())
635635
if not suppress_errors or isinstance(error, SystemExit):
@@ -640,7 +640,7 @@ def _populate1(
640640
else:
641641
self.connection.commit_transaction()
642642
duration = time.time() - start_time
643-
logger.debug(f"Success making {key} -> {self.full_table_name}")
643+
logger.jobs(f"Success making {key} -> {self.full_table_name}")
644644

645645
# Update hidden job metadata if table has the columns
646646
if self._has_job_metadata_attrs():

src/datajoint/logging.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,42 @@
22
import os
33
import sys
44

5+
# Custom log level for job/populate status messages
6+
# DEBUG (10) < JOBS (15) < INFO (20) < WARNING (30) < ERROR (40)
7+
JOBS = 15
8+
logging.addLevelName(JOBS, "JOBS")
9+
10+
11+
def jobs(self, message, *args, **kwargs):
12+
"""Log job status messages (make start/success/error)."""
13+
if self.isEnabledFor(JOBS):
14+
self._log(JOBS, message, args, **kwargs)
15+
16+
17+
logging.Logger.jobs = jobs
18+
519
logger = logging.getLogger(__name__.split(".")[0])
620

721
log_level = os.getenv("DJ_LOG_LEVEL", "info").upper()
822

9-
log_format = logging.Formatter("[%(asctime)s][%(levelname)s]: %(message)s")
1023

11-
stream_handler = logging.StreamHandler() # default handler
12-
stream_handler.setFormatter(log_format)
24+
class LevelAwareFormatter(logging.Formatter):
25+
"""Format INFO messages cleanly, show level for warnings/errors and JOBS."""
26+
27+
def format(self, record):
28+
timestamp = self.formatTime(record, "%Y-%m-%d %H:%M:%S")
29+
if record.levelno >= logging.WARNING:
30+
return f"[{timestamp}][{record.levelname}]: {record.getMessage()}"
31+
elif record.levelno == JOBS:
32+
return f"[{timestamp}][JOBS]: {record.getMessage()}"
33+
else:
34+
return f"[{timestamp}] {record.getMessage()}"
35+
36+
37+
# Use stdout for all logging (avoids red highlighting in terminals/IDEs)
38+
# Users needing stderr can configure their own handlers
39+
stream_handler = logging.StreamHandler(sys.stdout)
40+
stream_handler.setFormatter(LevelAwareFormatter())
1341

1442
logger.setLevel(level=log_level)
1543
logger.handlers = [stream_handler]

0 commit comments

Comments
 (0)