Skip to content

Commit 87518d3

Browse files
committed
Added support for customised timestamp format from config file. Created new format variable "custime" for customised timestamp format.
1 parent aab7ff6 commit 87518d3

File tree

6 files changed

+117
-8
lines changed

6 files changed

+117
-8
lines changed

supervisor/dispatchers.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,15 @@ def _init_normallog(self):
127127
backups = getattr(config, '%s_logfile_backups' % channel)
128128
to_syslog = getattr(config, '%s_syslog' % channel)
129129
prepend_timestamp = getattr(config, '%s_prepend_timestamp' % channel)
130+
prepend_timestamp_format = getattr(config, '%s_prepend_timestamp_format' % channel)
130131

131132
formatter = '%(message)s'
132-
if prepend_timestamp:
133-
formatter = '%(asctime)s: %(message)s'
133+
134+
if prepend_timestamp and not prepend_timestamp_format:
135+
formatter = '%(asctime)s %(message)s'
136+
137+
if prepend_timestamp and prepend_timestamp_format:
138+
formatter = '%(custime)s %(message)s'
134139

135140
if logfile or to_syslog:
136141
self.normallog = config.options.getLogger()
@@ -140,6 +145,7 @@ def _init_normallog(self):
140145
self.normallog,
141146
filename=logfile,
142147
fmt=formatter,
148+
datefmt=prepend_timestamp_format,
143149
rotating=not not maxbytes, # optimization
144150
maxbytes=maxbytes,
145151
backups=backups

supervisor/loggers.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def getLevelNumByDescription(description):
5151

5252
class Handler:
5353
fmt = '%(message)s'
54+
datefmt = ""
5455
level = LevelsByName.INFO
5556

5657
def __init__(self, stream=None):
@@ -60,6 +61,9 @@ def __init__(self, stream=None):
6061
def setFormat(self, fmt):
6162
self.fmt = fmt
6263

64+
def setDateFormat(self, datefmt):
65+
self.datefmt = datefmt
66+
6367
def setLevel(self, level):
6468
self.level = level
6569

@@ -95,7 +99,10 @@ def emit(self, record):
9599
if binary:
96100
msg = record.msg
97101
else:
98-
msg = self.fmt % record.asdict()
102+
if isinstance(self, FileHandler) and self.datefmt is not None:
103+
msg = self.fmt % record.asdict(self.datefmt)
104+
else:
105+
msg = self.fmt % record.asdict()
99106
if binary_stream:
100107
msg = msg.encode('utf-8')
101108
try:
@@ -282,7 +289,7 @@ def __init__(self, level, msg, **kw):
282289
self.kw = kw
283290
self.dictrepr = None
284291

285-
def asdict(self):
292+
def asdict(self, datefmt=None):
286293
if self.dictrepr is None:
287294
now = time.time()
288295
msecs = (now - long(now)) * 1000
@@ -294,13 +301,16 @@ def asdict(self):
294301
msg = msg % self.kw
295302
self.dictrepr = {'message':msg, 'levelname':levelname,
296303
'asctime':asctime}
304+
if datefmt:
305+
self.dictrepr['custime'] = time.strftime(datefmt, time.localtime(now))
297306
return self.dictrepr
298307

299308
class Logger:
300-
def __init__(self, level=None, handlers=None):
309+
def __init__(self, level=None, handlers=None, datefmt=None):
301310
if level is None:
302311
level = LevelsByName.INFO
303312
self.level = level
313+
self.datefmt = datefmt
304314

305315
if handlers is None:
306316
handlers = []
@@ -407,7 +417,7 @@ def handle_syslog(logger, fmt):
407417
handler.setLevel(logger.level)
408418
logger.addHandler(handler)
409419

410-
def handle_file(logger, filename, fmt, rotating=False, maxbytes=0, backups=0):
420+
def handle_file(logger, filename, fmt, datefmt=None, rotating=False, maxbytes=0, backups=0):
411421
"""Attach a new file handler to an existing Logger. If the filename
412422
is the magic name of 'syslog' then make it a syslog handler instead."""
413423
if filename == 'syslog': # TODO remove this
@@ -417,6 +427,8 @@ def handle_file(logger, filename, fmt, rotating=False, maxbytes=0, backups=0):
417427
handler = FileHandler(filename)
418428
else:
419429
handler = RotatingFileHandler(filename, 'a', maxbytes, backups)
430+
if datefmt:
431+
handler.setDateFormat(datefmt)
420432
handler.setFormat(fmt)
421433
handler.setLevel(logger.level)
422434
logger.addHandler(handler)

supervisor/options.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,10 @@ def get(section, opt, *args, **kwargs):
10001000
prepend_ts = boolean(get(section, prepend_ts_key, False))
10011001
logfiles[prepend_ts_key] = prepend_ts
10021002

1003+
prepend_ts_fmt_key = '%s_prepend_timestamp_format' % k
1004+
prepend_ts_fmt = get(section, prepend_ts_fmt_key, "%%Y-%%m-%%d %%H:%%M:%%S")
1005+
logfiles[prepend_ts_fmt_key] = prepend_ts_fmt
1006+
10031007
# rewrite deprecated "syslog" magic logfile into the equivalent
10041008
# TODO remove this in a future version
10051009
if lf_val is Syslog:
@@ -1044,13 +1048,15 @@ def get(section, opt, *args, **kwargs):
10441048
uid=uid,
10451049
stdout_logfile=logfiles['stdout_logfile'],
10461050
stdout_prepend_timestamp = logfiles['stdout_prepend_timestamp'],
1051+
stdout_prepend_timestamp_format = logfiles['stdout_prepend_timestamp_format'],
10471052
stdout_capture_maxbytes = stdout_cmaxbytes,
10481053
stdout_events_enabled = stdout_events,
10491054
stdout_logfile_backups=logfiles['stdout_logfile_backups'],
10501055
stdout_logfile_maxbytes=logfiles['stdout_logfile_maxbytes'],
10511056
stdout_syslog=logfiles['stdout_syslog'],
10521057
stderr_logfile=logfiles['stderr_logfile'],
10531058
stderr_prepend_timestamp = logfiles['stderr_prepend_timestamp'],
1059+
stderr_prepend_timestamp_format = logfiles['stderr_prepend_timestamp_format'],
10541060
stderr_capture_maxbytes = stderr_cmaxbytes,
10551061
stderr_events_enabled = stderr_events,
10561062
stderr_logfile_backups=logfiles['stderr_logfile_backups'],
@@ -1881,7 +1887,8 @@ class ProcessConfig(Config):
18811887
'stderr_events_enabled', 'stderr_syslog',
18821888
'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup',
18831889
'exitcodes', 'redirect_stderr' ]
1884-
optional_param_names = [ 'environment', 'serverurl', 'stdout_prepend_timestamp', 'stderr_prepend_timestamp' ]
1890+
optional_param_names = ['environment', 'serverurl', 'stdout_prepend_timestamp', 'stdout_prepend_timestamp_format',
1891+
'stderr_prepend_timestamp', 'stderr_prepend_timestamp_format']
18851892

18861893
def __init__(self, options, **params):
18871894
self.options = options

supervisor/skel/sample.conf

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,16 @@ serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
105105
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
106106
;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10)
107107
;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
108-
;stdout_prepend_timestamp=true ; prepend timestamp to stdout log file ( default false)
108+
;stdout_prepend_timestamp=true ; prepend timestamp to stdout log file (default false)
109+
;stdout_prepend_timestamp_format="%%Y-%%m-%%d %%H:%%M:%%S"; customise prepend timestamp to stdout log file (default false)
109110
;stdout_events_enabled=false ; emit events on stdout writes (default false)
110111
;stdout_syslog=false ; send stdout to syslog with process name (default false)
111112
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
112113
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
113114
;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10)
114115
;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
115116
;stderr_prepend_timestamp=true ; prepend timestamp to stderr log file (default false)
117+
;stderr_prepend_timestamp_format="%%Y-%%m-%%d %%H:%%M:%%S"; customise prepend timestamp to stdout log file (default false)
116118
;stderr_events_enabled=false ; emit events on stderr writes (default false)
117119
;stderr_syslog=false ; send stderr to syslog with process name (default false)
118120
;environment=A="1",B="2" ; process environment additions (def no adds)

supervisor/tests/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ def __init__(self, options, name, command, directory=None, umask=None,
517517
stderr_logfile=None, stderr_capture_maxbytes=0,
518518
stderr_events_enabled=False,
519519
stdout_prepend_timestamp=False, stderr_prepend_timestamp=False,
520+
stdout_prepend_timestamp_format=None, stderr_prepend_timestamp_format=None,
520521
stderr_logfile_backups=0, stderr_logfile_maxbytes=0,
521522
stderr_syslog=False,
522523
redirect_stderr=False,
@@ -545,7 +546,9 @@ def __init__(self, options, name, command, directory=None, umask=None,
545546
self.stderr_syslog = stderr_syslog
546547
self.redirect_stderr = redirect_stderr
547548
self.stdout_prepend_timestamp = stdout_prepend_timestamp
549+
self.stdout_prepend_timestamp_format = stdout_prepend_timestamp_format
548550
self.stderr_prepend_timestamp = stderr_prepend_timestamp
551+
self.stderr_prepend_timestamp_format = stderr_prepend_timestamp_format
549552
if stopsignal is None:
550553
import signal
551554
stopsignal = signal.SIGTERM

supervisor/tests/test_dispatchers.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,44 @@ def test_stdout_prepend_timestamp(self):
607607
dispatcher.childlog.close()
608608
dispatcher.close()
609609

610+
def test_stdout_prepend_timestamp_format(self):
611+
import time
612+
from supervisor import loggers
613+
from supervisor.loggers import getLogger
614+
615+
options = DummyOptions()
616+
options.getLogger = getLogger # actually use real logger
617+
options.loglevel = loggers.LevelsByName.TRAC
618+
619+
logfile = '/tmp/foo'
620+
message = "testing prepand"
621+
config = DummyPConfig(options, 'process1', '/bin/process1',
622+
stdout_logfile=logfile, stdout_prepend_timestamp=True,
623+
stdout_prepend_timestamp_format="%H:%M:%S")
624+
process = DummyProcess(config)
625+
626+
dispatcher = self._makeOne(process)
627+
dispatcher.removelogs()
628+
dispatcher.output_buffer = message
629+
dispatcher.record_output()
630+
631+
# flush out the log into log files
632+
[x.flush() for x in dispatcher.childlog.handlers]
633+
634+
# logger will prefix the stdout log with the timestamp down to milliseconds
635+
# but not feasible to test to that resolution
636+
timestamp_prefix = time.strftime("%H:%M:%S")
637+
638+
with open(logfile, 'rb') as f:
639+
content = f.read()
640+
# check if the timestamp is prepended to the log
641+
self.assertEqual(timestamp_prefix.encode(), content[0:len(timestamp_prefix)])
642+
# check if the message is at the end of the log line
643+
self.assertEqual(message.encode(), content[-len(message):])
644+
645+
dispatcher.childlog.close()
646+
dispatcher.close()
647+
610648
def test_stderr_prepend_timestamp(self):
611649
import time
612650
from supervisor import loggers
@@ -644,6 +682,47 @@ def test_stderr_prepend_timestamp(self):
644682
dispatcher.childlog.close()
645683
dispatcher.close()
646684

685+
def test_stderr_prepend_timestamp_format(self):
686+
import time
687+
from supervisor import loggers
688+
from supervisor.loggers import getLogger
689+
690+
options = DummyOptions()
691+
options.getLogger = getLogger # actually use real logger
692+
options.loglevel = loggers.LevelsByName.TRAC
693+
694+
logfile = '/tmp/foo'
695+
message = "testing prepand"
696+
config = DummyPConfig(options, 'process1', '/bin/process1',
697+
stderr_logfile=logfile, stderr_prepend_timestamp=True,
698+
stderr_prepend_timestamp_format="%H:%M:%S")
699+
process = DummyProcess(config)
700+
701+
dispatcher = self._makeOne(process, channel='stderr')
702+
dispatcher.output_buffer = message
703+
dispatcher.removelogs()
704+
dispatcher.record_output()
705+
706+
# flush out the log into log files
707+
[x.flush() for x in dispatcher.childlog.handlers]
708+
709+
# logger will prefix the stdout log with the timestamp down to milliseconds
710+
# but not feasible to test to that resolution
711+
timestamp_prefix = time.strftime("%H:%M:%S")
712+
713+
with open(logfile, 'rb') as f:
714+
content = f.read()
715+
# check if the timestamp is prepended to the log
716+
self.assertEqual(timestamp_prefix.encode(), content[0:len(timestamp_prefix)])
717+
# check if the message is at the end of the log line
718+
self.assertEqual(message.encode(), content[-len(message):])
719+
720+
dispatcher.childlog.close()
721+
dispatcher.close()
722+
723+
724+
725+
647726
class PInputDispatcherTests(unittest.TestCase):
648727
def _getTargetClass(self):
649728
from supervisor.dispatchers import PInputDispatcher

0 commit comments

Comments
 (0)