Skip to content
Snippets Groups Projects
Commit d04d1716 authored by Piotr Maślanka's avatar Piotr Maślanka
Browse files

preliminary logging system

parent bff8228d
No related branches found
No related tags found
No related merge requests found
......@@ -3,6 +3,7 @@ import sys
import traceback
class StoredVariable(object):
"""Class used to store a variable. Picklable."""
__slots__ = ('repr', 'pickle')
def __init__(self, value):
try:
......@@ -28,9 +29,7 @@ class StoredVariable(object):
return pickle.loads(self.pickle)
class StackFrame(object):
"""
Class used to verily preserve stack frames
"""
"""Class used to verily preserve stack frames. Picklable."""
__slots__ = ('locals', 'globals', 'name', 'filename', 'lineno')
def __init__(self, frame):
self.name = frame.f_code.co_name
......@@ -47,10 +46,7 @@ class StackFrame(object):
class Trackback(object):
"""
Class used to verily preserve exceptions.
Picklable.
"""
"""Class used to verily preserve exceptions. Picklable."""
__slots__ = ('formatted_traceback', 'frames')
def __init__(self):
"""To be invoked while processing an exception is in progress"""
......
from satella.instrumentation.logging.base import LogEntry, LogSet
\ No newline at end of file
import time
class LogEntry(object):
"""Class that represents a single log entry in the system"""
def __init__(self, who, tags, when=None):
"""
@param who: Name of the logging service. System components in granularity-descending
hierarchy should be separated with dots, eg. "http.network.accept"
@type who: str
@param tags: a sequence of str or a str (space-separated) of tags that can be used to
machine-process the sequence set
@param when: optional, UTC timestamp of time that event happened
@type when: int or long
"""
if when == None: when = time.time()
self.when = when #: UTC timestamp of the event
self.who = who #: service that logged. Hierarchy separated by dotch
if isinstance(tags, str):
self.tags = tags.split(' ')
else:
self.tags = tags
self.attachments = [] #: list of pair (attachment name, picklable attachment object)
def attach(self, *args):
"""
Attaches a piece of data to the log entry.
Invoke with either one argument (will attach the data without a name) or two arguments
(first of them will be a str, name of the entry, second one - the data to attach)
"""
if len(args) == 1: # Attach an attachment without a name
self.attachments.append((None, args[0]))
elif len(args) == 2: # Attach a named attachment
self.attachments.append(args)
else:
raise ValueError, 'more than 2 arguments'
return self
class LogSet(object):
"""
A sequence of .when-ordered log events along with methods to process the stream
"""
def __init__(self, events=[]):
self.events = events #: list of LogEntry
def count(self):
"""Return the number of log entries in this set"""
return len(self.events)
def filter_tag(self, tag):
"""
Returns the subset (as iterator) of this event set where tag occurs.
@param tag: Tag that is supposed to occur in each of the returned entries
@type tag: str
@return: L{LogSet} with entries
"""
return LogSet([x for x in self.events if tag in x.tags])
def filter_hierarchy(self, hstart):
"""
Returns the subset (as iterator) of this event set where hstart is the start of
logger hierarchy.
Eg. if we have events:
satella.instrumentation.test
satella.instrumentation
satella.instrumentationes
satella.helloworld
And we call .filter_hierarchy('satella.instrumentation') we'll receive only TWO
first results
@param hstart: String that hierarchy is supposed to start from
@type tag: str
@return: L{LogSet} with entries
"""
f = len(hstart)
n = []
for evt in (x for x in self.events if x.who.startswith(hstart)):
if len(evt.who) == f:
n.append(evt)
elif evt.who[f] == '.':
n.append(evt)
return LogSet(n)
\ No newline at end of file
......@@ -4,3 +4,4 @@ from pulsecounter import PulseCounterTest
from countercollection import CounterCollectionTest
from deltacounter import DeltaCounterTest
from exctrack import TrackbackTest
from logging import LoggingTest
\ No newline at end of file
from satella.instrumentation.logging import LogEntry, LogSet
import unittest
class LogsetTest(unittest.TestCase):
def test_logset_filtering(self):
f = [
LogEntry('x.y.z.a', 'satella test'),
LogEntry('x.y.z', 'satella test'),
LogEntry('x.y.za', 'satella test'),
LogEntry('x.y.b', 'satella notest'),
LogEntry('x.f', 'satella notest'),
]
ls = LogSet(f)
self.assertEquals(ls.count(), 5)
self.assertEquals(ls.filter_tag('satella').count(), 5)
self.assertEquals(ls.filter_tag('test').count(), 3)
self.assertEquals(ls.filter_hierarchy('x.y.z').count(), 2)
self.assertEquals(ls.filter_hierarchy('x.y').count(), 4)
self.assertEquals(ls.filter_hierarchy('x').count(), 5)
self.assertEquals(ls.filter_hierarchy('x.y').filter_tag('notest').count(), 1)
class LoggingTest(unittest.TestCase):
def test_base_attachments(self):
# test whether both variants of .attach() work
le = LogEntry('satella.instrumentation.unit_tests', ('satella', 'test'))
le.attach('hello world')
le.attach('test string', 'hello world')
self.assertEquals(len(le.attachments), 2)
self.assertEquals(tuple(le.tags), ('satella', 'test'))
# test fluid interface of .attach()
le = LogEntry('satella.instrumentation.unit_tests', 'satella test')
le = le.attach('hello world').attach('test string', 'hello world')
self.assertEquals(len(le.attachments), 2)
self.assertEquals(tuple(le.tags), ('satella', 'test'))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment