-
Notifications
You must be signed in to change notification settings - Fork 52
/
loggingrepy_core.py
executable file
·282 lines (196 loc) · 7.04 KB
/
loggingrepy_core.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
"""
Author: Justin Cappos
Start Date: 22 July 2008
Description:
Refactored logging code that used to be in emulfile
"""
# needed for remove and path.exists
import os
# for Lock
import threading
# I need to rename file so that the checker doesn't complain...
myfile = file
# used to make stdout flush as written This is private to my code
class flush_logger_core:
"""
A file-like class that can be used in lieu of stdout. It always flushes
data after a write.
"""
def __init__(self, fobj):
self.fileobj = fobj
# I do not use these. This is merely for API convenience
self.mode = None
self.name = None
self.softspace = 0
return None
def close(self):
return self.fileobj.close()
def flush(self):
return self.fileobj.flush()
def write(self,writeitem):
self.fileobj.write(writeitem)
self.flush()
def writelines(self,writelist):
self.fileobj.writelines(writelist)
self.flush()
# End of flush_logger class
# helper function
def get_size(fn):
fo = myfile(fn,"r")
data = fo.read()
fo.close()
return len(data)
# used to implement the circular log buffer
class circular_logger_core:
"""
A file-like class that writes to what is conceptually a circular buffer.
After being filled, the buffer is always >=16KB and always flushed after
write...
I accomplish this by actually writing to two files. Once >=16 KB has been
written, the first file will always* be of size 16KB and the second file
fills as the user writes. Once the second file reaches 16KB, it is
moved to overwrite the first file and a new second file is created.
*not always on some systems because moving files isn't atomic
"""
def __init__(self, fnp, mbs = 16 * 1024):
# I do not use these. This is merely for API convenience
self.mode = None
self.name = None
self.softspace = 0
# the size before we "rotate" the logfiles
self.maxbuffersize = mbs # default listed in constructor
# filenames we'll use for the log data
self.filenameprefix = fnp
self.oldfn = fnp+".old"
self.newfn = fnp+".new"
# prevent race conditions when writing
self.writelock = threading.Lock()
# we need to set up the currentsize, activefo and first variables...
if os.path.exists(self.newfn):
# the new file exists.
if os.path.exists(self.oldfn):
# the old file exists too (the common case)
self.currentsize = get_size(self.newfn)
self.activefo = myfile(self.newfn,"a")
self.first = False
# now we have the fileobject and the size set up. We're ready...
return
else:
# a corner case. The old file was removed but the new was not yet
# copied over
os.rename(self.newfn, self.oldfn)
self.currentsize = 0
self.activefo = myfile(self.newfn,"w")
self.first = False
return
else:
if os.path.exists(self.oldfn):
# the old file name exists, so we should start from here
self.currentsize = get_size(self.oldfn)
self.activefo = myfile(self.oldfn,"a")
self.first = True
# now we have the fileobject and the size set up. We're ready...
return
else:
# starting from nothing...
self.currentsize = 0
self.activefo = myfile(self.oldfn,"w")
self.first = True
return
# No-op
def close(self):
return
# No-op, I always flush myself
def flush(self):
return
def write(self,writeitem):
# they / we can always log info (or else what happens on exception?)
# acquire (and release later no matter what)
self.writelock.acquire()
try:
writeamt = self.writedata(writeitem)
finally:
self.writelock.release()
def writelines(self,writelist):
# we / they can always log info (or else what happens on exception?)
# acquire (and release later no matter what)
self.writelock.acquire()
try:
for writeitem in writelist:
self.writedata(writeitem)
finally:
self.writelock.release()
# internal functions (not externally called)
# rotate the log files (make the new the old, and get a new file
def rotate_log(self):
self.activefo.close()
try:
os.rename(self.newfn, self.oldfn)
except WindowsError: # Windows no likey when rename overwrites
os.remove(self.oldfn)
os.rename(self.newfn, self.oldfn)
self.activefo = myfile(self.newfn,"w")
def write_first_log(self):
self.activefo.close()
self.activefo = myfile(self.newfn,"w")
# I could write this in about 1/4 the code, but it would be much harder to
# read.
def writedata(self, data):
# first I'll dispose of the common case
if len(str(data)) + self.currentsize <= self.maxbuffersize:
# didn't fill the buffer
self.activefo.write(str(data))
self.activefo.flush()
self.currentsize = self.currentsize + len(str(data))
return len(str(data))
# now I'll deal with the "longer-but-still-fits case"
if len(str(data))+self.currentsize <= self.maxbuffersize*2:
# finish off this file
splitindex = self.maxbuffersize - self.currentsize
self.activefo.write(str(data[:splitindex]))
self.activefo.flush()
# rotate logs
if self.first:
self.write_first_log()
self.first = False
else:
self.rotate_log()
# now write the last bit of data...
self.activefo.write(str(data[splitindex:]))
self.activefo.flush()
self.currentsize = len(str(data[splitindex:]))
return len(str(data))
# now the "really-long-write case"
# Note, I'm going to avoid doing any extra "alignment" on the data. In
# other words, if they write some multiple of 16KB, and they currently have
# a full file and a file with 7 bytes, they'll end up with a full file and
# a file with 7 bytes
datasize = len(str(data))
# this is what data the new file should contain (the old file will contain
# the 16KB of data before this)
lastchunk = (datasize + self.currentsize) % self.maxbuffersize
# I'm going to write the old file and new file now
#
# Note: I break some of the guarantees about being able to
# recover disk state here
self.activefo.close()
if self.first:
# remove existing files (unnecessary on some platforms)
os.remove(self.oldfn)
else:
# remove existing files (unnecessary on some platforms)
os.remove(self.oldfn)
os.remove(self.newfn)
oldfo = myfile(self.oldfn,"w")
# write the data counting backwards from the end of the file
oldfo.write(data[-(lastchunk+self.maxbuffersize):-lastchunk])
oldfo.close()
# next...
self.activefo = myfile(self.newfn,"w")
# now write the last bit of data...
self.activefo.write(str(data[-lastchunk:]))
self.activefo.flush()
self.currentsize = len(str(data[-lastchunk:]))
# charge them for only the data we actually wrote
return self.currentsize + self.maxbuffersize
# End of circular_logger class