77import logging
88import logging .handlers
99import os
10- from typing import cast
10+ from typing import Tuple , cast
1111
1212from io import StringIO
1313import configparser
1414from urllib .parse import urlparse
1515
1616logger = logging .getLogger (__name__ )
17+ openml_logger = logging .getLogger ('openml' )
18+ console_handler = None
19+ file_handler = None
1720
1821
19- def configure_logging (console_output_level : int , file_output_level : int ):
20- """ Sets the OpenML logger to DEBUG, with attached Stream- and FileHandler. """
21- # Verbosity levels as defined (https://github.com/openml/OpenML/wiki/Client-API-Standards)
22- # don't match Python values directly:
23- verbosity_map = {0 : logging .WARNING , 1 : logging .INFO , 2 : logging .DEBUG }
22+ def _create_log_handlers ():
23+ """ Creates but does not attach the log handlers. """
24+ global console_handler , file_handler
25+ if console_handler is not None or file_handler is not None :
26+ logger .debug ("Requested to create log handlers, but they are already created." )
27+ return
2428
25- openml_logger = logging .getLogger ('openml' )
26- openml_logger .setLevel (logging .DEBUG )
2729 message_format = '[%(levelname)s] [%(asctime)s:%(name)s] %(message)s'
2830 output_formatter = logging .Formatter (message_format , datefmt = '%H:%M:%S' )
2931
30- console_stream = logging .StreamHandler ()
31- console_stream .setFormatter (output_formatter )
32- console_stream .setLevel (verbosity_map [console_output_level ])
32+ console_handler = logging .StreamHandler ()
33+ console_handler .setFormatter (output_formatter )
3334
34- one_mb = 2 ** 20
35+ one_mb = 2 ** 20
3536 log_path = os .path .join (cache_directory , 'openml_python.log' )
36- file_stream = logging .handlers .RotatingFileHandler (log_path , maxBytes = one_mb , backupCount = 1 )
37- file_stream .setLevel (verbosity_map [file_output_level ])
38- file_stream .setFormatter (output_formatter )
37+ file_handler = logging .handlers .RotatingFileHandler (
38+ log_path , maxBytes = one_mb , backupCount = 1 , delay = True
39+ )
40+ file_handler .setFormatter (output_formatter )
3941
40- openml_logger .addHandler (console_stream )
41- openml_logger .addHandler (file_stream )
42- return console_stream , file_stream
42+
43+ def _convert_log_levels (log_level : int ) -> Tuple [int , int ]:
44+ """ Converts a log level that's either defined by OpenML/Python to both specifications. """
45+ # OpenML verbosity level don't match Python values directly:
46+ openml_to_python = {0 : logging .WARNING , 1 : logging .INFO , 2 : logging .DEBUG }
47+ python_to_openml = {logging .DEBUG : 2 , logging .INFO : 1 , logging .WARNING : 0 ,
48+ logging .CRITICAL : 0 , logging .ERROR : 0 }
49+ # Because the dictionaries share no keys, we use `get` to convert as necessary:
50+ openml_level = python_to_openml .get (log_level , log_level )
51+ python_level = openml_to_python .get (log_level , log_level )
52+ return openml_level , python_level
53+
54+
55+ def _set_level_register_and_store (handler : logging .Handler , log_level : int ):
56+ """ Set handler log level, register it if needed, save setting to config file if specified. """
57+ oml_level , py_level = _convert_log_levels (log_level )
58+ handler .setLevel (py_level )
59+
60+ if openml_logger .level > py_level or openml_logger .level == logging .NOTSET :
61+ openml_logger .setLevel (py_level )
62+
63+ if handler not in openml_logger .handlers :
64+ openml_logger .addHandler (handler )
65+
66+
67+ def set_console_log_level (console_output_level : int ):
68+ """ Set console output to the desired level and register it with openml logger if needed. """
69+ global console_handler
70+ _set_level_register_and_store (cast (logging .Handler , console_handler ), console_output_level )
71+
72+
73+ def set_file_log_level (file_output_level : int ):
74+ """ Set file output to the desired level and register it with openml logger if needed. """
75+ global file_handler
76+ _set_level_register_and_store (cast (logging .Handler , file_handler ), file_output_level )
4377
4478
4579# Default values (see also https://github.com/openml/OpenML/wiki/Client-API-Standards)
4680_defaults = {
4781 'apikey' : None ,
4882 'server' : "https://www.openml.org/api/v1/xml" ,
49- 'verbosity' : 0 , # WARNING
50- 'file_verbosity' : 2 , # DEBUG
5183 'cachedir' : os .path .expanduser (os .path .join ('~' , '.openml' , 'cache' )),
5284 'avoid_duplicate_runs' : 'True' ,
5385 'connection_n_retries' : 2 ,
@@ -176,9 +208,7 @@ def _setup():
176208
177209
178210def _parse_config ():
179- """Parse the config file, set up defaults.
180- """
181-
211+ """ Parse the config file, set up defaults. """
182212 config = configparser .RawConfigParser (defaults = _defaults )
183213
184214 if not os .path .exists (config_file ):
@@ -189,6 +219,7 @@ def _parse_config():
189219 "create an empty file there." % config_file )
190220
191221 try :
222+ # The ConfigParser requires a [SECTION_HEADER], which we do not expect in our config file.
192223 # Cheat the ConfigParser module by adding a fake section header
193224 config_file_ = StringIO ()
194225 config_file_ .write ("[FAKE_SECTION]\n " )
@@ -255,7 +286,4 @@ def set_cache_directory(cachedir):
255286]
256287
257288_setup ()
258-
259- _console_log_level = cast (int , _defaults ['verbosity' ])
260- _file_log_level = cast (int , _defaults ['file_verbosity' ])
261- console_log , file_log = configure_logging (_console_log_level , _file_log_level )
289+ _create_log_handlers ()
0 commit comments