55
66The logger has 4 major steps:
77
8- 1. Inputs, such as a simple string or something more complicated like
9- TabularInput, are passed to the log() method of an instantiated Logger.
8+ 1. Inputs, such as a simple string or something more complicated like
9+ a distribution, are passed to the log() or logkv() method of an
10+ instantiated Logger.
1011
11- 2. The Logger class checks for any outputs that have been added to it, and
12- calls the record() method of any outputs that accept the type of input.
12+ 2. The Logger class checks for any outputs that have been added to it, and
13+ calls the record() method of any outputs that accept the type of input.
1314
14- 3. The output (a subclass of LogOutput) receives the input via its record()
15- method and handles it in whatever way is expected.
15+ 3. The output (a subclass of LogOutput) receives the input via its record()
16+ method and handles it in whatever way is expected.
1617
17- 4. (only in some cases) The dump method is used to dump the output to file.
18- It is necessary for some LogOutput subclasses, like TensorBoardOutput .
18+ 4. (only in some cases) The dump method is used to dump the output to file
19+ and to log any key-value pairs that have been stored .
1920
2021
2122# Here's a demonstration of dowel:
6162
6263# And another output.
6364
64- from dowel import CsvOutput
65- logger.add_output(CsvOutput ('log_folder/table.csv '))
65+ from dowel import TensorBoardOutput
66+ logger.add_output(TensorBoardOutput ('log_folder/tensorboard '))
6667
6768 +---------+
6869 +------>StdOutput|
7273|logger+------>TextOutput|
7374+------+ +----------+
7475 |
75- | +---------+
76- +------>CsvOutput |
77- +---------+
76+ | +----------------- +
77+ +------>TensorBoardOutput |
78+ +----------------- +
7879
7980# The logger will record anything passed to logger.log to all outputs that
8081# accept its type.
8182
83+
84+ # Now let's try logging a string again.
85+
8286logger.log('test')
8387
8488 +---------+
8993|logger+---'test'--->TextOutput|
9094+------+ +----------+
9195 |
92- | +---------+
93- +-----!!----->CsvOutput |
94- +---------+
96+ | +----------------- +
97+ +-----!!----->TensorBoardOutput |
98+ +----------------- +
9599
96- # !! Note that the logger knows not to send CsvOutput the string 'test'
97- # Similarly, more complex objects like tf.tensor won't be sent to (for
100+ # !! Note that the logger knows not to send 'test' to TensorBoardOutput.
101+ # Similarly, more complex objects like tf.Graph won't be sent to (for
98102# example) TextOutput.
99103# This behavior is defined in each output's types_accepted property
100104
101105# Here's a more complex example.
102- # TabularInput, instantiated for you as the tabular, can log key/ value pairs.
106+ # We can log key- value pairs using logger.logkv
103107
104- from dowel import tabular
105- tabular.record('key', 72)
106- tabular.record('foo', 'bar')
107- logger.log(tabular)
108+ logger.logkv('key', 72)
109+ logger.logkv('foo', 'bar')
110+ logger.dump_all()
108111
109- +---------+
110- +---tabular --->StdOutput|
111- | +---------+
112+ +---------+
113+ +------>StdOutput|
114+ | +---------+
112115 |
113- +------+ +----------+
114- |logger+---tabular --->TextOutput|
115- +------+ +----------+
116+ +------+ +----------+
117+ |logger+------>TextOutput|
118+ +------+ +----------+
116119 |
117- | +---------+
118- +---tabular --->CsvOutput|
119- +---------+
120+ | +---------+
121+ +------>CsvOutput|
122+ +---------+
120123
121- # Note that LogOutputs which consume TabularInputs must call
122- # TabularInput.mark() on each key they log. This helps the logger detect when
123- # tabular data is not logged.
124+ # Note that the key-value pairs are saved in each output until we call
125+ # dump_all().
124126
125127# Console Output:
126128--- ---
133135"""
134136import abc
135137import contextlib
138+ import re
136139import warnings
137140
138141from dowel .utils import colorize
139142
140143
141144class LogOutput (abc .ABC ):
142- """Abstract class for Logger Outputs."""
145+ """Abstract class for Logger Outputs.
143146
144- @property
145- def types_accepted (self ):
146- """Pass these types to this logger output.
147+ :param keys_accepted: Regex for which keys this output should accept.
148+ """
147149
148- The types in this tuple will be accepted by this output.
150+ def __init__ (self , keys_accepted = r'^$' ):
151+ self ._keys_accepted = keys_accepted
149152
150- :return: A tuple containing all valid input types.
151- """
153+ @property
154+ def types_accepted (self ):
155+ """Returns a tuple containing all valid input value types."""
152156 return ()
153157
158+ @property
159+ def keys_accepted (self ):
160+ """Returns a regex string matching keys to be sent to this output."""
161+ return self ._keys_accepted
162+
154163 @abc .abstractmethod
155- def record (self , data , prefix = '' ):
164+ def record (self , key , value , prefix = '' ):
156165 """Pass logger data to this output.
157166
158- :param data: The data to be logged by the output.
167+ :param key: The key to be logged by the output.
168+ :param value: The value to be logged by the output.
159169 :param prefix: A prefix placed before a log entry in text outputs.
160170 """
161171 pass
@@ -186,7 +196,7 @@ def __init__(self):
186196 self ._warned_once = set ()
187197 self ._disable_warnings = False
188198
189- def log (self , data ):
199+ def logkv (self , key , value ):
190200 """Magic method that takes in all different types of input.
191201
192202 This method is the main API for the logger. Any data to be logged goes
@@ -195,24 +205,30 @@ def log(self, data):
195205 Any data sent to this method is sent to all outputs that accept its
196206 type (defined in the types_accepted property).
197207
198- :param data: Data to be logged. This can be any type specified in the
208+ :param key: Key to be logged. This must be a string.
209+ :param value: Value to be logged. This can be any type specified in the
199210 types_accepted property of any of the logger outputs.
200211 """
201212 if not self ._outputs :
202213 self ._warn ('No outputs have been added to the logger.' )
203214
204215 at_least_one_logged = False
205216 for output in self ._outputs :
206- if isinstance (data , output .types_accepted ):
207- output .record (data , prefix = self ._prefix_str )
217+ if isinstance (value , output .types_accepted ) and re .match (
218+ output .keys_accepted , key ):
219+ output .record (key , value , prefix = self ._prefix_str )
208220 at_least_one_logged = True
209221
210222 if not at_least_one_logged :
211223 warning = (
212224 'Log data of type {} was not accepted by any output' .format (
213- type (data ).__name__ ))
225+ type (value ).__name__ ))
214226 self ._warn (warning )
215227
228+ def log (self , value ):
229+ """Log just a value without a key."""
230+ self .logkv ('' , value )
231+
216232 def add_output (self , output ):
217233 """Add a new output to the logger.
218234
0 commit comments