-
-
Notifications
You must be signed in to change notification settings - Fork 12
/
logger.py
171 lines (141 loc) · 4.65 KB
/
logger.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
from __future__ import unicode_literals
import io
import os
import shutil
import sys
import common
try:
import queue
except:
import Queue as queue
class StdErrRedirector():
"""
Use to redirect stderr to a Logger object. You could also just do `stderr = Logger(...)`, but this way
lets you add special behavior when stderr output is received (like logging to a special 'error' file)
"""
def __init__(self, attachedLogger):
self.attachedLogger = attachedLogger
def write(self, message):
self.attachedLogger.write(message)
def flush(self):
pass
# From https://stackoverflow.com/a/14906787/848627
# Replace with the standard "https://docs.python.org/2/library/logging.html" module later?
class Logger(object):
globalLogger = None
def __init__(self, logPath):
self.logPath = logPath
self.terminal = sys.stdout
common.makeDirsExistOK(os.path.dirname(logPath))
self.logFile = io.open(logPath, "a", encoding='UTF-8')
self.secondaryLogFile = None
self.secondaryLogFilePath = None
self.callbacks = {}
self.queue = queue.Queue(maxsize=100000)
def write(self, message, runCallbacks=True, noTerminal=False):
if common.Globals.IS_PYTHON_2 and isinstance(message, str):
message = message.decode(encoding='UTF-8', errors='replace')
if not noTerminal:
self.terminal.write(message)
if self.logFile:
self.logFile.write(message)
if self.secondaryLogFile is not None:
try:
self.secondaryLogFile.write(message)
except:
pass
self._tryPutInQueue(message)
#execute all bound callbacks
if runCallbacks:
for callback in self.callbacks.values():
callback(message)
#TODO: probably should flush every X seconds rather than every write
if self.logFile:
self.logFile.flush()
if self.secondaryLogFile is not None:
try:
self.secondaryLogFile.flush()
except:
pass
def flush(self):
#this flush method is needed for python 3 compatibility.
#this handles the flush command by doing nothing.
#you might want to specify some extra behavior here.
pass
def threadSafeRead(self):
# type: () -> str
"""
:return: the latest message, or None if no more data to read
"""
try:
return self.queue.get_nowait()
except queue.Empty:
return None
def threadSafeReadAll(self):
# type: () -> [str]
"""
:return: An array of strings. If nothing to read, returns the empt ylist
"""
return list(iter(self.threadSafeRead, None))
def _tryPutInQueue(self, item):
# type: (str) -> None
try:
self.queue.put_nowait(item)
except queue.Full:
if not self.queue_full_error:
self.queue_full_error = True
self.terminal.write("WARNING: Install status message queue is full (possibly GUI was closed but console left open)")
def trySetSecondaryLoggingPath(self, newLogFilePath):
# type: (str) -> None
"""
Specify the secondary path for the log file.
The current log file will be copied to the newLogFilePath.
Any additional writes will go to both the existing log file and the new one.
:param newLogFilePath: the path where the new log file will be created (and updated)
:return: None
"""
# If new log file path is the same as current one, don't do anything
if self.secondaryLogFilePath is not None:
if os.path.normpath(self.secondaryLogFilePath) == os.path.normpath(newLogFilePath):
return
try:
common.makeDirsExistOK(os.path.dirname(newLogFilePath))
shutil.copy(self.logPath, newLogFilePath)
if self.secondaryLogFile is not None:
fileToClose = self.secondaryLogFile
self.secondaryLogFile = None
fileToClose.close()
print("Closed log file at: [{}]".format(newLogFilePath))
self.secondaryLogFile = io.open(newLogFilePath, "a", encoding='UTF-8')
self.secondaryLogFilePath = newLogFilePath
print("Successfully created secondary log file at: [{}]".format(newLogFilePath))
except Exception as e:
print("Couldn't create secondary log at: [{}] Error: {}".format(newLogFilePath, e))
def close_all_logs(self):
if self.logFile is not None:
self.logFile.close()
self.logFile = None
if self.secondaryLogFile is not None:
self.secondaryLogFile.close()
self.secondaryLogFile = None
def getGlobalLogger():
# type: () -> Logger
return Logger.globalLogger
def setGlobalLogger(logger):
# type: (Logger) -> None
Logger.globalLogger = logger
def registerLoggerCallback(callbackKey, callback):
"""
NOTE: the order in which the callbacks are executed is random!
:param callbackKey:
:param callback:
:return:
"""
Logger.globalLogger.callbacks[callbackKey] = callback
def deregisterLoggerCallback(callbackKey):
return Logger.globalLogger.callbacks.pop(callbackKey, None)
def printNoTerminal(message):
Logger.globalLogger.write(
"{}\n".format(message),
noTerminal=True
)