99import logging .handlers
1010import numbers
1111import os
12- import pprint
1312import re
1413import shutil
1514import sys
@@ -145,7 +144,7 @@ def emit(self, record):
145144 except OSError as e :
146145 raise LoggingError ('logging failed' ) from e
147146
148- self .baseFilename = os .path .join (dirname , record .check_name + '.log' )
147+ self .baseFilename = os .path .join (dirname , record .check . name + '.log' )
149148 self .stream = self ._streams .get (self .baseFilename , None )
150149 super ().emit (record )
151150 self ._streams [self .baseFilename ] = self .stream
@@ -165,7 +164,65 @@ def _format_time_rfc3339(timestamp, datefmt):
165164 return re .sub (r'(%)?\:z' , tz_rfc3339 , time .strftime (datefmt , timestamp ))
166165
167166
168- class RFC3339Formatter (logging .Formatter ):
167+ def _xfmt (val ):
168+ from reframe .core .deferrable import _DeferredExpression
169+
170+ if val is None :
171+ return '<undefined>'
172+
173+ if isinstance (val , _DeferredExpression ):
174+ try :
175+ return val .evaluate ()
176+ except BaseException :
177+ return '<error>'
178+
179+ if isinstance (val , str ):
180+ return val
181+
182+ if isinstance (val , collections .abc .Mapping ):
183+ return ',' .join (f'{ k } ={ v } ' for k , v in val .items ())
184+
185+ if isinstance (val , collections .abc .Iterable ):
186+ return ',' .join (val )
187+
188+ return val
189+
190+
191+ class CheckFieldFormatter (logging .Formatter ):
192+ '''Log formatter that dynamically looks up format specifiers inside a
193+ regression test.'''
194+
195+ def __init__ (self , fmt = None , datefmt = None , style = '%' ):
196+ super ().__init__ (fmt , datefmt , style )
197+
198+ # NOTE: This will work only when style='%'
199+ self .__extras = {
200+ spec : None for spec in re .findall (r'\%\((check_\S+?)\)s' , fmt )
201+ }
202+
203+ # Set the default value for 'check_name'
204+ if 'check_name' in self .__extras :
205+ self .__extras ['check_name' ] = 'reframe'
206+
207+ def format (self , record ):
208+ # Fill in the check-specific record attributes
209+ if record .check :
210+ for spec in self .__extras :
211+ if hasattr (record , spec ):
212+ # Attribute set elsewhere
213+ continue
214+
215+ attr = spec .split ('_' , maxsplit = 1 )[1 ]
216+ val = getattr (record .check , attr , None )
217+ record .__dict__ [spec ] = _xfmt (val )
218+ else :
219+ # Update record with the dynamic extras even if check is not set
220+ record .__dict__ .update (self .__extras )
221+
222+ return super ().format (record )
223+
224+
225+ class RFC3339Formatter (CheckFieldFormatter ):
169226 def formatTime (self , record , datefmt = None ):
170227 datefmt = datefmt or self .default_time_format
171228 if '%:z' not in datefmt :
@@ -176,7 +233,7 @@ def formatTime(self, record, datefmt=None):
176233
177234 def format (self , record ):
178235 datefmt = self .datefmt or self .default_time_format
179- if record .check_job_completion_time_unix is not None :
236+ if record .check_job_completion_time_unix != _xfmt ( None ) :
180237 ct = self .converter (record .check_job_completion_time_unix )
181238 record .check_job_completion_time = _format_time_rfc3339 (
182239 ct , datefmt
@@ -388,26 +445,24 @@ def __init__(self, logger=None, check=None):
388445 super ().__init__ (
389446 logger ,
390447 {
391- 'check_name' : 'reframe' ,
392- 'check_jobid' : '-1' ,
393- 'check_job_completion_time' : None ,
394- 'check_job_completion_time_unix' : None ,
448+ # Here we only set the format specifiers that do not
449+ # correspond directly to check attributes
450+ 'check' : check ,
451+ 'check_jobid' : _xfmt (None ),
452+ 'check_job_completion_time' : _xfmt (None ),
453+ 'check_job_completion_time_unix' : _xfmt (None ),
395454 'check_info' : 'reframe' ,
396- 'check_system' : None ,
397- 'check_partition' : None ,
398- 'check_environ' : None ,
399- 'check_outputdir' : None ,
400- 'check_stagedir' : None ,
401- 'check_num_tasks' : None ,
402- 'check_perf_var' : None ,
403- 'check_perf_value' : None ,
404- 'check_perf_ref' : None ,
405- 'check_perf_lower_thres' : None ,
406- 'check_perf_upper_thres' : None ,
407- 'check_perf_unit' : None ,
408- 'osuser' : osext .osuser () or '<unknown>' ,
409- 'osgroup' : osext .osgroup () or '<unknown>' ,
410- 'check_tags' : None ,
455+ 'check_system' : _xfmt (None ),
456+ 'check_partition' : _xfmt (None ),
457+ 'check_environ' : _xfmt (None ),
458+ 'check_perf_var' : _xfmt (None ),
459+ 'check_perf_value' : _xfmt (None ),
460+ 'check_perf_ref' : _xfmt (None ),
461+ 'check_perf_lower_thres' : _xfmt (None ),
462+ 'check_perf_upper_thres' : _xfmt (None ),
463+ 'check_perf_unit' : _xfmt (None ),
464+ 'osuser' : _xfmt (osext .osuser ()),
465+ 'osgroup' : _xfmt (osext .osgroup ()),
411466 'version' : osext .reframe_version (),
412467 }
413468 )
@@ -428,15 +483,11 @@ def std_stream_handlers(self):
428483
429484 def _update_check_extras (self ):
430485 '''Return a dictionary with all the check-specific information.'''
486+
431487 if self .check is None :
432488 return
433489
434- self .extra ['check_name' ] = self .check .name
435490 self .extra ['check_info' ] = self .check .info ()
436- self .extra ['check_outputdir' ] = self .check .outputdir
437- self .extra ['check_stagedir' ] = self .check .stagedir
438- self .extra ['check_num_tasks' ] = self .check .num_tasks
439- self .extra ['check_tags' ] = ',' .join (self .check .tags )
440491 if self .check .current_system :
441492 self .extra ['check_system' ] = self .check .current_system .name
442493
0 commit comments