Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve SKLL logging #380

Merged
merged 20 commits into from
Nov 8, 2017
Merged

Improve SKLL logging #380

merged 20 commits into from
Nov 8, 2017

Conversation

desilinguist
Copy link
Member

@desilinguist desilinguist commented Oct 31, 2017

The goal of this PR is to improve how logging works in SKLL. The current situation is that many, if not all, of the warning messages are logged to the console but never stored in any of the log files. This is not great since a lot of people may not pay attention to the console messages.

In an ideal situation, the solution would be quite simple. The way that logging works is that logging.getLogger(<name>) always returns the same logger instance no matter where that call is made as long as the call is within the same Python interpreter process. However, the problem is that SKLL jobs can also run on other machines in completely different Python processes.

The solution in this PR works as follows:

  1. I create a function called get_skll_logger() that returns a Logger instance that can log messages both to the sys.stderr as well as to a specified file. Repeated invocations of get_skll_logger() with the same name (and the same file path) will return the same Logger instance.

  2. get_skll_logger() is first called from config.py to create an experiment-level logger where messages that are relevant to configuration parsing are logged. It is called again from experiments.py at the top level to log messages (to the same Logger instance) about the whole experiment (not the individual jobs). This logger writes to the console as well as to a file named <experiment_name>.log that is automatically created in the log directory.

  3. get_skll_logger() is called a third time from inside _classify_featureset(). This is a second Logger instance that is at the individual job level (where job = featureset + learner + objective). All of the messages from learner.py, readers.py, and writers.py are logged by this logger. This logger writes to the console as well as to the files named <job_name>.log that SKLL already creates in the log directory.

  4. So, at the end of the experiment, there is (a) <experiment_name>.log containing messages pertaining to configuration parsing as well as the top-level experiment, and (b) multiple <job_name>.log files containing messages specific to each job.

Notes:

  • To get Learner, Reader, and Writer to log to the same job log file, I had to modify them to accept loggers as keyword arguments.

  • Although the console output shows the logger names ("experiment" for the main logger and <job_name> for the job logger(s)), the log files themselves just show the time stamps, the logging level, and the actual message. This is because the names of the files themselves tell you what is what.

  • I also had to pass in logger arguments to some top-level functions for things to work perfectly and make a one of the static methods be a regular method instead.

  • I changed the default logging level for SKLL to be INFO rather than warnings since the INFO messages are actually quite useful. The -v flag for run_experiment changes the logging level to include DEBUG messages as well.

  • I ran several tests both on and off the grid and the log files are created properly and with the right messages. However, I encourage you to run some tests (e.g., the SKLL examples) yourself.

  • I added a couple of tests that look at the new log files.

- Default logging level is now INFO instead of WARNING.
- Verbose now means that DEBUG messages are included in the logging output.
- Pass through the log level to `run_configuration()`.
- Add a new function that creates a logger that includes a file handler as well as console logging.
- Create a new experiment-level log file named `<experiment_name>.log` that will contain all of the configuration and experiment level logging messages.
- The job level log file that is already created now contains all of the info messages and warnings that were only printed out before.
- Use an exception instead since we don't want to pass loggers at this level.
@desilinguist desilinguist added this to the 1.5 milestone Oct 31, 2017
@desilinguist desilinguist self-assigned this Oct 31, 2017
@desilinguist
Copy link
Member Author

@dan-blanchard I tagged you on this review because you are probably more of an expert at logging than I am so I really would like your input.

@coveralls
Copy link

coveralls commented Oct 31, 2017

Coverage Status

Coverage decreased (-0.003%) to 92.009% when pulling 4f74ad6 on improve-logging into 28a80b1 on master.

in the configuration file), SKLL also produces a single, top level "experiment"
log file with only ``EXPERIMENT`` as the prefix. While the job-level log files
contain messages that pertain to the specific characteristics of the job, the
experiment-level log file will contains logging messages that pertain to the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

contains --> contain


TIMESTAMP - LEVEL - MSG

where ``TIMESTAMP`` refers to the exact time when the messages was logged,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

messages -> message

@@ -695,19 +696,24 @@ def safe_float(text, replace_dict=None):
floats. Anything not in the mapping will be kept the
same.
:type replace_dict: dict from str to str
:param text: The Logger instance to use to log messages. Used instead of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation here and below should have logger instead of text as the parameter name.

@coveralls
Copy link

coveralls commented Oct 31, 2017

Coverage Status

Coverage increased (+0.05%) to 92.064% when pulling a00b2a1 on improve-logging into 28a80b1 on master.

created and run on a DRMAA-compatible cluster.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
conflict_handler='resolve')
parser = argparse.ArgumentParser(description='Runs the scikit-learn '
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: To save on space, it might be slightly better to just import ArgumentParser/ArgumentDefaultsHelpFormatter directly.

Copy link
Collaborator

@aoifecahill aoifecahill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. I ran run_experiment with and without gridmap using a config that had multiple learners and multiple objectives and the logging looks good to me. It's convenient to have the gridmap job output in the logs directory, even if it is a bit noisy.

@@ -143,7 +144,7 @@ def _write_summary_file(result_json_paths, output_file, ablation=0):
learner_result_dicts = []
# Map from feature set names to all features in them
all_features = defaultdict(set)
logger = logging.getLogger(__name__)
logger = get_skll_logger('experiment')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this (hard coding the name of the logger to be experiment) cause any conflicts if multiple experiments are being called on the same machine at the same time? Or would those always be in separate python processes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the Logging Cookbook:

Multiple calls to logging.getLogger('someLogger') return a reference to the same logger object. This is true not only within the same module, but also across modules as long as it is in the same Python interpreter process.

So, I don't think there should be any conflicts as long as you are using multiple run_experiment calls.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants