mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
A rotating log
This commit is contained in:
parent
dadeff7cb5
commit
ffa1ab1104
@ -166,14 +166,15 @@ def prints(*args, **kwargs):
|
|||||||
Has the same signature as the print function from Python 3, except for the
|
Has the same signature as the print function from Python 3, except for the
|
||||||
additional keyword argument safe_encode, which if set to True will cause the
|
additional keyword argument safe_encode, which if set to True will cause the
|
||||||
function to use repr when encoding fails.
|
function to use repr when encoding fails.
|
||||||
|
|
||||||
|
Returns the number of bytes written.
|
||||||
'''
|
'''
|
||||||
file = kwargs.get('file', sys.stdout)
|
file = kwargs.get('file', sys.stdout)
|
||||||
sep = kwargs.get('sep', ' ')
|
sep = bytes(kwargs.get('sep', ' '))
|
||||||
end = kwargs.get('end', '\n')
|
end = bytes(kwargs.get('end', '\n'))
|
||||||
enc = preferred_encoding
|
enc = 'utf-8' if 'CALIBRE_WORKER' in os.environ else preferred_encoding
|
||||||
safe_encode = kwargs.get('safe_encode', False)
|
safe_encode = kwargs.get('safe_encode', False)
|
||||||
if 'CALIBRE_WORKER' in os.environ:
|
count = 0
|
||||||
enc = 'utf-8'
|
|
||||||
for i, arg in enumerate(args):
|
for i, arg in enumerate(args):
|
||||||
if isinstance(arg, unicode):
|
if isinstance(arg, unicode):
|
||||||
if iswindows:
|
if iswindows:
|
||||||
@ -181,8 +182,10 @@ def prints(*args, **kwargs):
|
|||||||
cs = Detect(file)
|
cs = Detect(file)
|
||||||
if cs.is_console:
|
if cs.is_console:
|
||||||
cs.write_unicode_text(arg)
|
cs.write_unicode_text(arg)
|
||||||
|
count += len(arg)
|
||||||
if i != len(args)-1:
|
if i != len(args)-1:
|
||||||
file.write(bytes(sep))
|
file.write(sep)
|
||||||
|
count += len(sep)
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
arg = arg.encode(enc)
|
arg = arg.encode(enc)
|
||||||
@ -211,12 +214,18 @@ def prints(*args, **kwargs):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
file.write(arg)
|
file.write(arg)
|
||||||
|
count += len(arg)
|
||||||
except:
|
except:
|
||||||
import repr as reprlib
|
import repr as reprlib
|
||||||
file.write(reprlib.repr(arg))
|
arg = reprlib.repr(arg)
|
||||||
|
file.write(arg)
|
||||||
|
count += len(arg)
|
||||||
if i != len(args)-1:
|
if i != len(args)-1:
|
||||||
file.write(bytes(sep))
|
file.write(sep)
|
||||||
file.write(bytes(end))
|
count += len(sep)
|
||||||
|
file.write(end)
|
||||||
|
count += len(sep)
|
||||||
|
return count
|
||||||
|
|
||||||
class CommandLineError(Exception):
|
class CommandLineError(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -8,6 +8,7 @@ __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
import httplib, ssl, os, socket, time
|
import httplib, ssl, os, socket, time
|
||||||
from unittest import skipIf
|
from unittest import skipIf
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from calibre.utils.certgen import create_server_cert
|
from calibre.utils.certgen import create_server_cert
|
||||||
@ -21,6 +22,34 @@ from calibre.ptempfile import TemporaryDirectory
|
|||||||
|
|
||||||
class LoopTest(BaseTest):
|
class LoopTest(BaseTest):
|
||||||
|
|
||||||
|
def test_log_rotation(self):
|
||||||
|
'Test log rotation'
|
||||||
|
from calibre.srv.utils import RotatingLog
|
||||||
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
|
with TemporaryDirectory() as tdir:
|
||||||
|
fname = os.path.join(tdir, 'log')
|
||||||
|
l = RotatingLog(fname, max_size=100)
|
||||||
|
|
||||||
|
def history():
|
||||||
|
return {int(x.rpartition('.')[-1]) for x in glob(fname + '.*')}
|
||||||
|
|
||||||
|
def log_size():
|
||||||
|
ssize = l.outputs[0].stream.tell()
|
||||||
|
self.ae(ssize, l.outputs[0].current_pos)
|
||||||
|
self.ae(ssize, os.path.getsize(fname))
|
||||||
|
return ssize
|
||||||
|
|
||||||
|
self.ae(log_size(), 0)
|
||||||
|
l('a' * 99)
|
||||||
|
self.ae(log_size(), 100)
|
||||||
|
l('b'), l('c')
|
||||||
|
self.ae(log_size(), 2)
|
||||||
|
self.ae(history(), {1})
|
||||||
|
for i in 'abcdefg':
|
||||||
|
l(i * 101)
|
||||||
|
self.assertLessEqual(log_size(), 100)
|
||||||
|
self.ae(history(), {1,2,3,4,5})
|
||||||
|
|
||||||
def test_workers(self):
|
def test_workers(self):
|
||||||
' Test worker semantics '
|
' Test worker semantics '
|
||||||
with TestServer(lambda data:(data.path[0] + data.read()), worker_count=3) as server:
|
with TestServer(lambda data:(data.path[0] + data.read()), worker_count=3) as server:
|
||||||
@ -42,6 +71,7 @@ class LoopTest(BaseTest):
|
|||||||
self.ae(1, sum(int(w.is_alive()) for w in pool.workers))
|
self.ae(1, sum(int(w.is_alive()) for w in pool.workers))
|
||||||
|
|
||||||
def test_ring_buffer(self):
|
def test_ring_buffer(self):
|
||||||
|
'Test the ring buffer used for reads'
|
||||||
class FakeSocket(object):
|
class FakeSocket(object):
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
@ -13,8 +13,11 @@ import repr as reprlib
|
|||||||
from email.utils import formatdate
|
from email.utils import formatdate
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
|
from calibre import prints
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
|
from calibre.utils.filenames import atomic_rename
|
||||||
from calibre.utils.socket_inheritance import set_socket_inherit
|
from calibre.utils.socket_inheritance import set_socket_inherit
|
||||||
|
from calibre.utils.logging import ThreadSafeLog
|
||||||
|
|
||||||
HTTP1 = 'HTTP/1.0'
|
HTTP1 = 'HTTP/1.0'
|
||||||
HTTP11 = 'HTTP/1.1'
|
HTTP11 = 'HTTP/1.1'
|
||||||
@ -175,6 +178,49 @@ def eintr_retry_call(func, *args, **kwargs):
|
|||||||
continue
|
continue
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
class RotatingStream(object):
|
||||||
|
|
||||||
|
def __init__(self, filename, max_size=None, history=5):
|
||||||
|
self.filename, self.history, self.max_size = filename, history, max_size
|
||||||
|
self.set_output()
|
||||||
|
|
||||||
|
def set_output(self):
|
||||||
|
self.stream = lopen(self.filename, 'ab', 1) # line buffered
|
||||||
|
try:
|
||||||
|
self.current_pos = self.stream.tell()
|
||||||
|
except EnvironmentError:
|
||||||
|
# Happens if filename is /dev/stdout for example
|
||||||
|
self.current_pos = 0
|
||||||
|
self.max_size = None
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
self.stream.flush()
|
||||||
|
|
||||||
|
def prints(self, level, *args, **kwargs):
|
||||||
|
kwargs['safe_encode'] = True
|
||||||
|
kwargs['file'] = self.stream
|
||||||
|
self.current_pos += prints(*args, **kwargs)
|
||||||
|
self.rollover()
|
||||||
|
|
||||||
|
def rollover(self):
|
||||||
|
if self.max_size is None or self.current_pos <= self.max_size:
|
||||||
|
return
|
||||||
|
self.stream.close()
|
||||||
|
for i in xrange(self.history - 1, 0, -1):
|
||||||
|
try:
|
||||||
|
atomic_rename('%s.%d' % (self.filename, i), '%s.%d' % (self.filename, i+1))
|
||||||
|
except EnvironmentError as e:
|
||||||
|
if e.errno != errno.ENOENT: # the source of the rename does not exist
|
||||||
|
raise
|
||||||
|
atomic_rename(self.filename, '%s.%d' % (self.filename, 1))
|
||||||
|
self.set_output()
|
||||||
|
|
||||||
|
class RotatingLog(ThreadSafeLog):
|
||||||
|
|
||||||
|
def __init__(self, filename, max_size=None, history=5):
|
||||||
|
ThreadSafeLog.__init__(self)
|
||||||
|
self.outputs = [RotatingStream(filename, max_size, history)]
|
||||||
|
|
||||||
class HandleInterrupt(object): # {{{
|
class HandleInterrupt(object): # {{{
|
||||||
|
|
||||||
# On windows socket functions like accept(), recv(), send() are not
|
# On windows socket functions like accept(), recv(), send() are not
|
||||||
|
Loading…
x
Reference in New Issue
Block a user