Skip to content

Commit bf3bbef

Browse files
author
Vasileios Karakasis
committed
Add full RFC3339 compliance to time format
1 parent af6f7ea commit bf3bbef

File tree

4 files changed

+63
-19
lines changed

4 files changed

+63
-19
lines changed

reframe/core/logging.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import shutil
99
import sys
1010
import socket
11+
import time
1112
from datetime import datetime
1213

1314
import reframe
@@ -132,6 +133,37 @@ def close(self):
132133
super().close()
133134

134135

136+
def _format_time_rfc3339(timestamp, datefmt):
137+
tz_suffix = time.strftime('%z', timestamp)
138+
tz_rfc3339 = tz_suffix[:-2] + ':' + tz_suffix[-2:]
139+
return time.strftime(datefmt, timestamp).replace(':z', tz_rfc3339)
140+
141+
142+
class RFC3339Formatter(logging.Formatter):
143+
def __init__(self, fmt=None, datefmt=None, style='%'):
144+
super().__init__(fmt, datefmt, style)
145+
146+
# Formatter objects store fmt in `_fmt`, but we use our own variable
147+
# here just to stay on the safe side if Formatter's implementation
148+
# changes
149+
self.__fmt = fmt
150+
151+
def formatTime(self, record, datefmt=None):
152+
datefmt = datefmt or self.default_time_format
153+
if '%:z' not in datefmt:
154+
return super().formatTime(record, datefmt)
155+
else:
156+
timestamp = self.converter(record.created)
157+
return _format_time_rfc3339(timestamp, datefmt)
158+
159+
def usesTime(self):
160+
# Extend usesTime() so as to trigger time formatting for our own
161+
# custom time formats
162+
return (super().usesTime() or
163+
'%(check_job_completion_time)' in self.__fmt or
164+
'{check_job_completion_time}' in self.__fmt)
165+
166+
135167
def load_from_dict(logging_config):
136168
if not isinstance(logging_config, collections.abc.Mapping):
137169
raise TypeError('logging configuration is not a dict')
@@ -305,7 +337,7 @@ def _extract_handlers(handlers_list):
305337
level = handler_config.get('level', 'debug').lower()
306338
fmt = handler_config.get('format', '%(message)s')
307339
datefmt = handler_config.get('datefmt', '%FT%T')
308-
hdlr.setFormatter(logging.Formatter(fmt=fmt, datefmt=datefmt))
340+
hdlr.setFormatter(RFC3339Formatter(fmt=fmt, datefmt=datefmt))
309341
hdlr.setLevel(_check_level(level))
310342
handlers.append(hdlr)
311343

@@ -428,11 +460,7 @@ def _update_check_extras(self):
428460
if self.check.job:
429461
self.extra['check_jobid'] = self.check.job.jobid
430462
if self.check.job.completion_time:
431-
# Use the logging handlers' date format to format
432-
# completion_time
433-
# NOTE: All handlers use the same date format
434-
fmt = self.logger.handlers[0].formatter.datefmt
435-
ct = self.check.job.completion_time.strftime(fmt)
463+
ct = self.check.job.completion_time
436464
self.extra['check_job_completion_time'] = ct
437465

438466
def log_performance(self, level, tag, value, ref,

reframe/core/schedulers/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44

55
import abc
6-
from datetime import datetime
6+
import time
77

88
import reframe.core.environments as env
99
import reframe.core.fields as fields
@@ -17,6 +17,8 @@
1717
class JobScheduler(abc.ABC):
1818
@abc.abstractmethod
1919
def completion_time(self, job):
20+
'''The completion time of this job expressed in seconds from the Epoch.
21+
'''
2022
pass
2123

2224
@abc.abstractmethod
@@ -332,7 +334,7 @@ def wait(self):
332334
raise JobNotStartedError('cannot wait an unstarted job')
333335

334336
self.scheduler.wait(self)
335-
self._completion_time = self._completion_time or datetime.now()
337+
self._completion_time = self._completion_time or time.time()
336338

337339
def cancel(self):
338340
if self.jobid is None:
@@ -346,7 +348,7 @@ def finished(self):
346348

347349
done = self.scheduler.finished(self)
348350
if done:
349-
self._completion_time = self._completion_time or datetime.now()
351+
self._completion_time = self._completion_time or time.time()
350352

351353
return done
352354

reframe/core/schedulers/slurm.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def completion_time(self, job):
103103
not slurm_state_completed(job.state)):
104104
return self._completion_time
105105

106-
with env.temp_environment(variables={'SLURM_TIME_FORMAT': 'standard'}):
106+
with env.temp_environment(variables={'SLURM_TIME_FORMAT': '%s'}):
107107
completed = os_ext.run_command(
108108
'sacct -S %s -P -j %s -o jobid,end' %
109109
(datetime.now().strftime('%F'), job.jobid),
@@ -116,11 +116,7 @@ def completion_time(self, job):
116116
if not state_match:
117117
return None
118118

119-
self._completion_time = max(
120-
datetime.strptime(s.group('end'), '%Y-%m-%dT%H:%M:%S')
121-
for s in state_match
122-
)
123-
119+
self._completion_time = max(float(s.group('end')) for s in state_match)
124120
return self._completion_time
125121

126122
def _format_option(self, var, option):

unittests/test_logging.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging.handlers
33
import os
44
import sys
5+
import re
56
import tempfile
67
import unittest
78
from datetime import datetime
@@ -22,7 +23,7 @@ def setUp(self):
2223

2324
self.logger = rlog.Logger('reframe')
2425
self.handler = logging.handlers.RotatingFileHandler(self.logfile)
25-
self.formatter = logging.Formatter(
26+
self.formatter = rlog.RFC3339Formatter(
2627
fmt='[%(asctime)s] %(levelname)s: %(check_name)s: %(message)s',
2728
datefmt='%FT%T')
2829

@@ -38,10 +39,10 @@ def setUp(self):
3839
def tearDown(self):
3940
os.remove(self.logfile)
4041

41-
def found_in_logfile(self, string):
42+
def found_in_logfile(self, pattern):
4243
found = False
43-
with open(self.logfile, 'rt') as f:
44-
found = string in f.read()
44+
with open(self.logfile, 'rt') as fp:
45+
found = re.search(pattern, fp.read()) is not None
4546

4647
return found
4748

@@ -97,6 +98,23 @@ def test_logger_levels(self):
9798
self.assertFalse(self.found_in_logfile('bar'))
9899
self.assertTrue(self.found_in_logfile('foo'))
99100

101+
def test_rfc3339_timezone_extension(self):
102+
self.formatter = rlog.RFC3339Formatter(
103+
fmt='[%(asctime)s] %(levelname)s: %(check_name)s: %(message)s',
104+
datefmt='%FT%T%:z')
105+
self.handler.setFormatter(self.formatter)
106+
self.logger_without_check.info('foo')
107+
assert not self.found_in_logfile(r'%%:z')
108+
assert self.found_in_logfile(r'(\+|-)\d\d:\d\d')
109+
110+
def test_rfc3339_timezone_wrong_directive(self):
111+
self.formatter = rlog.RFC3339Formatter(
112+
fmt='[%(asctime)s] %(levelname)s: %(check_name)s: %(message)s',
113+
datefmt='%FT%T:z')
114+
self.handler.setFormatter(self.formatter)
115+
self.logger_without_check.info('foo')
116+
assert self.found_in_logfile(':z')
117+
100118

101119
class TestLoggingConfiguration(unittest.TestCase):
102120
def setUp(self):

0 commit comments

Comments
 (0)