-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathlog.py
136 lines (99 loc) · 3.86 KB
/
log.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python3
import os
import sys
import datetime
import traceback
DEBUG = 5
INFO = 4
WARNING = 3
ERROR = 2
FATAL = 1
_FATAL_NOTHROW = 0 # Same as fatal, but doesn't throw.
LEVEL_MAP = {
DEBUG: "D",
INFO: "I",
WARNING: "W",
ERROR: "E",
FATAL: "F",
_FATAL_NOTHROW: "F"
}
DEFAULT_LOG_DIR = "/tmp"
DEFAULT_LOG_FILE = os.path.splitext(os.path.basename(sys.argv[0]))[0] + ".log"
def get_timestamp():
return datetime.datetime.now().strftime("%Y%m%d-%H%M%S.%f")[:-3]
START_TIMESTAMP = get_timestamp()
class FatalError(RuntimeError):
def __init__(self, message):
super().__init__(message)
class Logger:
def __init__(self, level=INFO):
""" It is expected that `output_dir` already exists. """
self._file = None
self._level = level
def _write(self, level, fmt, *args):
if level > self._level:
return
message = fmt.format(*args)
level_abbrev = LEVEL_MAP[level]
stream = sys.stdout if level > ERROR else sys.stderr
timestamp = datetime.datetime.now().strftime("%m%d %H%M%S.%f")[:-3]
prefix = "{}{} {}".format(level_abbrev, timestamp, os.getpid())
console_message = "{}] {}".format(prefix, message)
print(console_message, file=stream)
if self._file:
# Log on disk will also contain the call site.
caller = traceback.extract_stack(limit=3)[0]
caller_file = os.path.basename(caller.filename)
file_message = "{} {}:{}] {}".format(prefix, caller_file,
caller.lineno, message)
print(file_message, file=self._file)
if level == FATAL:
raise FatalError(message)
def open_file(self, output_dir, log_file_name):
""" Opens the log file. Unless this is called, all logging will only be
done to the console. """
assert self._file == None
assert os.path.isdir(output_dir)
log_path = os.path.join(output_dir, log_file_name)
self._file = open(log_path, "a", 1) # Log will be line-buffered.
def close_file(self):
""" Closes the log file and stops logging to disk. """
if self._file:
self._file.close()
self._file = None
# By default, this will only write to console. If you'd like to log into a file
# as well, call `open_file()`.
_logger = Logger(level=INFO)
_tensorboard_writer = None
def set_level(level):
if level not in LEVEL_MAP or level == _FATAL_NOTHROW:
raise ValueError("Log level may not be set to {}.".format(level))
def open_log_file(output_dir=DEFAULT_LOG_DIR, log_file_name=DEFAULT_LOG_FILE):
""" Opens the log file. All output to log methods will be duplicated to the
provided log file. Default filename is the name of the program with the
extension stripped, followed by '.log'. """
_logger.open_file(output_dir=output_dir, log_file_name=log_file_name)
def close_log_file():
""" Closes the log file. """
_logger.close_file()
def debug(fmt, *args):
_logger._write(DEBUG, fmt, *args)
def info(fmt, *args):
_logger._write(INFO, fmt, *args)
def warning(fmt, *args):
_logger._write(WARNING, fmt, *args)
def error(fmt, *args):
_logger._write(ERROR, fmt, *args)
def fatal(fmt, *args):
""" Raises FatalError, a subclass of RuntimeError. """
_logger._write(FATAL, fmt, *args)
# TODO: Filter stack frames within this module, if possible.
def exception(exception):
""" Logs exception with traceback, if traceback is present. """
assert issubclass(exception.__class__, Exception)
if exception.__traceback__:
frames = traceback.format_tb(exception.__traceback__)
message = "\n".join([f.rstrip() for f in frames])
_logger._write(_FATAL_NOTHROW, "Traceback:")
_logger._write(_FATAL_NOTHROW, message)
_logger._write(_FATAL_NOTHROW, str(exception))