Skip to content

Commit

Permalink
Beautified logging messages
Browse files Browse the repository at this point in the history
  • Loading branch information
cip999 committed Jan 15, 2023
1 parent 709a28c commit 8ae7688
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 105 deletions.
8 changes: 4 additions & 4 deletions p2d/domjudge_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import io
import logging
import os
import random
import requests
Expand All @@ -9,6 +8,7 @@
import yaml

from p2d._version import __version__
from p2d.logging_utils import logger

def generate_externalid(problem):
random_suffix = ''.join(random.choice(string.ascii_uppercase) for _ in range(6))
Expand Down Expand Up @@ -37,11 +37,11 @@ def update_problem_api(package_zip, problem_domjudge_id, credentials):
credentials)

if res.status_code != 200 or not res.json()['problem_id']:
logging.error('Error sending the package to the DOMjudge server: %s.'
logger.error('Error sending the package to the DOMjudge server: %s.'
% res.json())
return False
else:
logging.debug('Successfully sent the package to the DOMjudge server.')
logger.debug('Successfully sent the package to the DOMjudge server.')
return True

# Adds an "empty" problem to a contest.
Expand All @@ -67,7 +67,7 @@ def add_problem_to_contest_api(problem, credentials):
os.unlink(problem_yaml)

if res.status_code != 200:
logging.error('Error adding the problem to the contest: %s.' % res.json())
logger.error('Error adding the problem to the contest: %s.' % res.json())
return False

problem['domjudge_id'] = res.json()[0]
Expand Down
14 changes: 7 additions & 7 deletions p2d/generate_domjudge_package.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json
import logging
import os
import pathlib
import re
Expand All @@ -12,6 +11,7 @@
import zipfile

from p2d._version import __version__
from p2d.logging_utils import logger
from p2d import tex_utilities

RESOURCES_PATH = os.path.join(
Expand Down Expand Up @@ -55,12 +55,12 @@
# - problemname-solution.{tex,pdf}
# params is a dictionary with keys contest_name, hide_balloon, hide_tlml.
def generate_domjudge_package(problem, domjudge, tex_dir, params):
logging.debug('Creating the DOMjudge package directory \'%s\'.' % domjudge)
logger.debug('Creating the DOMjudge package directory \'%s\'.' % domjudge)

problem_yaml_data = {}

# Metadata
logging.debug('Writing \'domjudge-problem.ini\'.')
logger.debug('Writing \'domjudge-problem.ini\'.')
ini_file = os.path.join(domjudge, 'domjudge-problem.ini')
ini_content = [
'short-name = %s' % problem['name'],
Expand All @@ -81,7 +81,7 @@ def generate_domjudge_package(problem, domjudge, tex_dir, params):
tex_utilities.generate_solution_pdf(problem, tex_dir, params)

# Tests
logging.debug('Copying the tests in the DOMjudge package.')
logger.debug('Copying the tests in the DOMjudge package.')

sample_dir = os.path.join(domjudge, 'data', 'sample')
secret_dir = os.path.join(domjudge, 'data', 'secret')
Expand All @@ -105,7 +105,7 @@ def generate_domjudge_package(problem, domjudge, tex_dir, params):
os.path.join(domjudge, 'output_validators', 'interactor.cpp'))
elif problem['checker']['name'] is not None:
checker_name = problem['checker']['name']
logging.debug('Standard checker \'%s\'.' % checker_name)
logger.debug('Standard checker \'%s\'.' % checker_name)

checker_name_match = re.match(r'std\:\:([a-z0-9]+)\.cpp', checker_name)
assert(checker_name_match)
Expand All @@ -117,7 +117,7 @@ def generate_domjudge_package(problem, domjudge, tex_dir, params):
problem_yaml_data['validator_flags'] = \
CHECKER_POLYGON2DOMJUDGE[checker_name]
else:
logging.debug('Custom checker.')
logger.debug('Custom checker.')
problem_yaml_data['validation'] = 'custom'
pathlib.Path(domjudge, 'output_validators').mkdir()
shutil.copyfile(
Expand All @@ -141,7 +141,7 @@ def generate_domjudge_package(problem, domjudge, tex_dir, params):

# Write problem.yaml
yaml_path = os.path.join(domjudge, 'problem.yaml')
logging.debug(
logger.debug(
'Writing into \'%s\' the dictionary %s'
% (yaml_path, problem_yaml_data))
with open(yaml_path, 'w', encoding='utf-8') as f:
Expand Down
6 changes: 3 additions & 3 deletions p2d/generate_testlib_for_domjudge.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import logging
import requests
import os
import re

from p2d._version import __version__
from p2d.logging_utils import logger

HEADER_COMMENT = '''\
// Modified by a script to work with DOMjudge.
Expand Down Expand Up @@ -121,12 +121,12 @@ def replace_function(lines, function):
# The applied patch is copied from
# https://github.com/cn-xcpc-tools/testlib-for-domjudge.
def generate_testlib_for_domjudge(dst_path):
logging.debug('Downloading testlib.h from github.')
logger.debug('Downloading testlib.h from github.')
req = requests.get(
'https://raw.githubusercontent.com/MikeMirzayanov/testlib/master/testlib.h')
lines = req.text.splitlines()

logging.debug('Patching testlib.')
logger.debug('Patching testlib.')
lines = add_header(lines, HEADER_COMMENT)
for exit_code in NEW_EXIT_CODES:
lines = replace_exit_code(lines, exit_code, NEW_EXIT_CODES[exit_code])
Expand Down
50 changes: 50 additions & 0 deletions p2d/logging_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import sys
import logging

logger = logging.getLogger('Pol2Dom')

# Custom formatter for the console logger handler.
# Adapted from https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output
class Pol2DomLoggingFormatter(logging.Formatter):
INDENT = 0

bold_gray = '\033[1;90m'
bold_green = '\033[1;92m'
bold_yellow = '\033[1;33m'
bold_red = '\033[1;91m'
yellow = '\033[33m'
red = '\033[91m'
reset = '\033[0m'

COLORS = {
logging.DEBUG: (bold_gray, reset),
logging.INFO: (bold_green, reset),
logging.WARNING: (bold_yellow, yellow),
logging.ERROR: (bold_red, red)
}

def format(self, record):
level_color, message_color = self.COLORS.get(record.levelno)
padding = ' ' * (10 - len(record.levelname))
log_fmt = ' ' * self.INDENT + level_color + '{levelname}' + self.reset + padding + message_color + '{message}' + self.reset
formatter = logging.Formatter(log_fmt, style='{')
return formatter.format(record)

def configure_logging(verbosity):
logging.basicConfig(
stream=sys.stdout,
format='{levelname}\t{message}',
style='{',
level=eval('logging.' + verbosity.upper())
)

requests_log = logging.getLogger('requests.packages.urllib3')
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True

logger.setLevel(eval('logging.' + verbosity.upper()))
console_handler = logging.StreamHandler()
console_handler.setLevel(eval('logging.' + verbosity.upper()))
console_handler.setFormatter(Pol2DomLoggingFormatter())
logger.addHandler(console_handler)
logger.propagate = False
47 changes: 25 additions & 22 deletions p2d/p2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
from argparse import ArgumentParser

from p2d._version import __version__
from p2d.logging_utils import logger
from p2d import (domjudge_api,
generate_domjudge_package,
generate_testlib_for_domjudge,
parse_polygon_package,
polygon_api,
p2d_utils,
logging_utils,
tex_utilities)
RESOURCES_PATH = os.path.join(
os.path.split(os.path.realpath(__file__))[0], 'resources')
Expand All @@ -19,7 +21,7 @@
def prepare_argument_parser():
parser = ArgumentParser(description='Utility script to import a whole contest from polygon into DOMjudge.')
parser.add_argument('contest_directory', help='The directory containing the config.yaml file describing the contest. This directory will store also the polygon and DOMjudge packages.')
parser.add_argument('--problem', help='Use this flag to pass the name of a problem if you want to execute the script on a single problem instead of all the problems.')
parser.add_argument('--problems', nargs='+', help='Use this flag to pass the name of one or more problems if you want to execute the script on only on those problems.')
parser.add_argument('--polygon', '--import', '--get', '--download', action='store_true', help='Whether the problem packages should be downloaded from Polygon. Otherwise only the packages already present in the system will be considered.')
parser.add_argument('--convert', action='store_true', help='Whether the polygon packages should be converted to DOMjudge packages. Otherwise only the DOMjudge packages already present in the system will be considered.')
parser.add_argument('--domjudge', '--export', '--send', '--upload', action='store_true', help='Whether the DOMjudge packages shall be uploaded to the DOMjudge instance specified in config.yaml.')
Expand All @@ -36,13 +38,13 @@ def prepare_argument_parser():


def p2d(args):
p2d_utils.configure_logging(args.verbosity)
logging_utils.configure_logging(args.verbosity)

# Downloading and patching testlib.h if necessary.
testlib_h = os.path.join(RESOURCES_PATH, 'testlib.h')
if not os.path.isfile(testlib_h) or args.update_testlib:
generate_testlib_for_domjudge.generate_testlib_for_domjudge(testlib_h)
logging.info('The file testlib.h was successfully downloaded and patched. The local version can be found at \'%s\'.' % testlib_h)
logger.info('The file testlib.h was successfully downloaded and patched. The local version can be found at \'%s\'.' % testlib_h)

contest_dir = args.contest_directory

Expand All @@ -54,34 +56,34 @@ def p2d(args):
and args.from_contest is None \
and not args.pdf_contest \
and not args.clear_dir and not args.clear_domjudge_ids:
logging.error('At least one of the flags --polygon, --convert, --domjudge, --from-contest, --contestpdf, --clear-dir, --clear-domjudge-ids is necessary.')
logger.error('At least one of the flags --polygon, --convert, --domjudge, --from-contest, --contestpdf, --clear-dir, --clear-domjudge-ids is necessary.')
exit(1)

if args.clear_dir:
for problem in config['problems']:
if args.problem and args.problem != problem['name']:
if args.problems and problem['name'] not in args.problems:
continue
p2d_utils.remove_problem_data(problem, contest_dir)

p2d_utils.save_config_yaml(config, contest_dir)
logging.info('Deleted the problems\' data from \'%s\'.' % contest_dir)
logger.info('Deleted the problems\' data from \'%s\'.' % contest_dir)

if args.clear_domjudge_ids:
for problem in config['problems']:
if args.problem and args.problem != problem['name']:
if args.problems and problem['name'] not in args.problems:
continue
problem['domjudge_server_version'] = -1
problem.pop('domjudge_id', None)
problem.pop('domjudge_externalid', None)

p2d_utils.save_config_yaml(config, contest_dir)
logging.info('Deleted the DOMjudge IDs from config.yaml.')
logger.info('Deleted the DOMjudge IDs from config.yaml.')

if (args.polygon or args.from_contest) \
and ('polygon' not in config
or 'key' not in config['polygon']
or 'secret' not in config['polygon']):
logging.error('The entries polygon:key and polygon:secret must be '
logger.error('The entries polygon:key and polygon:secret must be '
'present in config.yaml to access Polygon problems.')
exit(1)

Expand All @@ -90,7 +92,7 @@ def p2d(args):
or 'server' not in config['domjudge']
or 'username' not in config['domjudge']
or 'password' not in config['domjudge']):
logging.error('The entries domjudge:contest_id, domjudge:server, '
logger.error('The entries domjudge:contest_id, domjudge:server, '
'domjudge:username, domjudge:password must be present '
'in config.yaml to upload problems on DOMjudge.')
exit(1)
Expand All @@ -111,16 +113,17 @@ def p2d(args):
# 3. Upload the DOMjudge package (to a running DOMjudge server).
problem_selected_exists = False
for problem in config['problems']:
if args.problem and args.problem != problem['name']:
if args.problems and problem['name'] not in args.problems:
continue
if not args.polygon and not args.convert and not args.domjudge:
continue

problem_selected_exists = True
print('\033[1m' + problem['name'] + '\033[0m') # Bold
print('Processing problem \033[96m' + problem['name'] + '\033[39m') # Cyan
logging_utils.Pol2DomLoggingFormatter.INDENT += 1

if 'label' not in problem:
logging.warning('The problem does not have a label.')
logger.warning('The problem does not have a label.')

if args.polygon:
if args.no_cache:
Expand Down Expand Up @@ -148,31 +151,31 @@ def p2d(args):
contest_dir, 'domjudge', problem['name']), problem)
p2d_utils.save_config_yaml(config, contest_dir)

print()
logging_utils.Pol2DomLoggingFormatter.INDENT -= 1

if args.problem and not problem_selected_exists:
logging.warning('The problem specified with --problem does not appear '
if args.problems and not problem_selected_exists:
logger.warning('The problem specified with --problem does not appear '
'in config.yaml.')
return

if args.problem:
if args.problems:
return

if args.pdf_contest:
p2d_utils.generate_problemset_solutions(config, contest_dir)

# Guidelines for error tracing and logging:
#
# Use logging everywhere for info/warning/error printing.
# Use logging_utils.logger everywhere for info/warning/error printing.
# Do not use print.
# Use exceptions when appropriate.
#
# For errors, use logging.error followed by exit(1) or raise an exception.
# For warnings, use logging.warning.
# For errors, use logger.error followed by exit(1) or raise an exception.
# For warnings, use logger.warning.
#
# For information:
# - Use logging.info in this p2d.py (for useful information).
# - Use logging.debug in all other files (and for not-so-useful information
# - Use logger.info in this p2d.py (for useful information).
# - Use logger.debug in all other files (and for not-so-useful information
# in this file).
def main():
args = prepare_argument_parser().parse_args()
Expand Down
Loading

0 comments on commit 8ae7688

Please sign in to comment.