<filename>ziggy/context.py
# -*- coding: utf-8 -*-

"""
ziggy.context
~~~~~~~~

This module provides the concept of 'Context' for collecting data that will
generate a log event.

:copyright: (c) 2012 by <NAME>
:license: ISC, see LICENSE for more details.

"""
import time
import os
import random
import struct

from . import utils
from . import network

# This will be redefined by our configuration to tell us where to record stuff
# to.
_recorder_function = None

class Context(object):
    __slots__ = ["name", "data", "id", "_writable", "start_time", "_sample_checks", "enabled"]
    def __init__(self, type_name, id=None, sample=None):

        if type_name.startswith('.'):
            parent_ctx = current_context()
            if parent_ctx is None:
                self.name = type_name[1:]
            else:
                self.name = parent_ctx.name + type_name
        else:
            parent_ctx = None
            self.name = type_name

        self.data = {}
        self.start_time = time.time()
        self._sample_checks = {}

        if id is not None:
            self.id = id
        elif parent_ctx:
            self.id = parent_ctx.id
        else:
            # Generate an id if one wasn't provided and we don't have any parents
            # We're going to encode the time as the front 4 bytes so we have some order to the ids
            # that could prove useful later on by making sorting a little easier.
            self.id = (struct.pack(">L", time.time()) + os.urandom(12)).encode('hex')

        if parent_ctx and not parent_ctx.enabled:
            self.enabled = False
        elif sample:
            sample_name, rate = sample
            if sample_name == type_name or sample_name == '.':
                self.enabled = bool(random.random() <= rate)
            elif parent_ctx and sample_name == '..':
                self.enabled = parent_ctx.sampled_for(type_name, rate)
            else:
                self.enabled = _get_context(sample_name).sampled_for(type_name, rate)
        else:
            self.enabled = True

        self._writable = False

    @property
    def writable(self):
        """Indicates the contest is open and can be written to"""
        return self._writable

    def sampled_for(self, name, rate):
        if name not in self._sample_checks:
            self._sample_checks[name] = bool(random.random() <= rate)
        return self._sample_checks[name]

    def set(self, key, *args, **kwargs):
        if not self.writable:
            raise ValueError()

        if args and kwargs:
            raise ValueError()

        if len(args) > 1:
            utils.set_deep(self.data, key, args)
        elif args:
            utils.set_deep(self.data, key, args[0])
        elif kwargs:
            existing_value = utils.get_deep(self.data, key, {})
            existing_value.update(kwargs)
            utils.set_deep(self.data, key, existing_value)

    def append(self, key, value):
        if not self.writable:
            raise ValueError()

        existing_value = utils.get_deep(self.data, key, [])
        existing_value.append(value)
        if len(existing_value) == 1:
            utils.set_deep(self.data, key, existing_value)

    def add(self, key, value):
        if not self.writable:
            raise ValueError()

        existing_value = utils.get_deep(self.data, key, 0)
        utils.set_deep(self.data, key, existing_value + value)

    def to_dict(self):
        return {'id': self.id,
                'type': self.name,
                'host': os.uname()[1],
                'pid': os.getpid(),
                'start': self.start_time,
                'end': time.time(),
                'body': self.data
               }

    def start(self):
        _add_context(self)
        self._writable = True

    def stop(self):
        self._writable = False
        _remove_context(self)

    def done(self):
        self.stop() # Just be sure
        if self.enabled and _recorder_function:
            _recorder_function(self)

        # Make sure we don't get any duplicate data
        # I would clear out all the data here, but that makes testing a little
        # more challenging.
        self.enabled = False

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, type, value, traceback):
        self.stop()
        self.done()

_contexts = []
_contexts_by_name = {}

def _add_context(context):
    if context.name in _contexts_by_name:
        return
    _contexts_by_name[context.name] = context
    _contexts.append(context)

def _get_context(name):
    return _contexts_by_name.get(str(name))

def _remove_context(context):
    try:
        del _contexts_by_name[context.name]
    except KeyError:
        pass

    try:
        _contexts.remove(context)
    except ValueError:
        pass

def current_context():
    try:
        return _contexts[-1]
    except IndexError:
        return None

def set(*args, **kwargs):
    context = current_context()
    if context:
        context.set(*args, **kwargs)

def append(*args, **kwargs):
    context = current_context()
    if context:
        context.append(*args, **kwargs)

def add(*args, **kwargs):
    context = current_context()
    if context:
        context.add(*args, **kwargs)

<filename>ziggy/context.py
# -*- coding: utf-8 -*-

"""
ziggy.context
~~~~~~~~

This module provides the concept of 'Context' for collecting data that will
generate a log event.

:copyright: (c) 2012 by <NAME>
:license: ISC, see LICENSE for more details.

"""
import time
import os
import random
import struct

from . import utils
from . import network

# This will be redefined by our configuration to tell us where to record stuff
# to.
_recorder_function = None

class Context(object):
    __slots__ = ["name", "data", "id", "_writable", "start_time", "_sample_checks", "enabled"]
    def __init__(self, type_name, id=None, sample=None):

        if type_name.startswith('.'):
            parent_ctx = current_context()
            if parent_ctx is None:
                self.name = type_name[1:]
            else:
                self.name = parent_ctx.name + type_name
        else:
            parent_ctx = None
            self.name = type_name

        self.data = {}
        self.start_time = time.time()
        self._sample_checks = {}

        if id is not None:
            self.id = id
        elif parent_ctx:
            self.id = parent_ctx.id
        else:
            # Generate an id if one wasn't provided and we don't have any parents
            # We're going to encode the time as the front 4 bytes so we have some order to the ids
            # that could prove useful later on by making sorting a little easier.
            self.id = (struct.pack(">L", time.time()) + os.urandom(12)).encode('hex')

        if parent_ctx and not parent_ctx.enabled:
            self.enabled = False
        elif sample:
            sample_name, rate = sample
            if sample_name == type_name or sample_name == '.':
                self.enabled = bool(random.random() <= rate)
            elif parent_ctx and sample_name == '..':
                self.enabled = parent_ctx.sampled_for(type_name, rate)
            else:
                self.enabled = _get_context(sample_name).sampled_for(type_name, rate)
        else:
            self.enabled = True

        self._writable = False

    @property
    def writable(self):
        """Indicates the contest is open and can be written to"""
        return self._writable

    def sampled_for(self, name, rate):
        if name not in self._sample_checks:
            self._sample_checks[name] = bool(random.random() <= rate)
        return self._sample_checks[name]

    def set(self, key, *args, **kwargs):
        if not self.writable:
            raise ValueError()

        if args and kwargs:
            raise ValueError()

        if len(args) > 1:
            utils.set_deep(self.data, key, args)
        elif args:
            utils.set_deep(self.data, key, args[0])
        elif kwargs:
            existing_value = utils.get_deep(self.data, key, {})
            existing_value.update(kwargs)
            utils.set_deep(self.data, key, existing_value)

    def append(self, key, value):
        if not self.writable:
            raise ValueError()

        existing_value = utils.get_deep(self.data, key, [])
        existing_value.append(value)
        if len(existing_value) == 1:
            utils.set_deep(self.data, key, existing_value)

    def add(self, key, value):
        if not self.writable:
            raise ValueError()

        existing_value = utils.get_deep(self.data, key, 0)
        utils.set_deep(self.data, key, existing_value + value)

    def to_dict(self):
        return {'id': self.id,
                'type': self.name,
                'host': os.uname()[1],
                'pid': os.getpid(),
                'start': self.start_time,
                'end': time.time(),
                'body': self.data
               }

    def start(self):
        _add_context(self)
        self._writable = True

    def stop(self):
        self._writable = False
        _remove_context(self)

    def done(self):
        self.stop() # Just be sure
        if self.enabled and _recorder_function:
            _recorder_function(self)

        # Make sure we don't get any duplicate data
        # I would clear out all the data here, but that makes testing a little
        # more challenging.
        self.enabled = False

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, type, value, traceback):
        self.stop()
        self.done()

_contexts = []
_contexts_by_name = {}

def _add_context(context):
    if context.name in _contexts_by_name:
        return
    _contexts_by_name[context.name] = context
    _contexts.append(context)

def _get_context(name):
    return _contexts_by_name.get(str(name))

def _remove_context(context):
    try:
        del _contexts_by_name[context.name]
    except KeyError:
        pass

    try:
        _contexts.remove(context)
    except ValueError:
        pass

def current_context():
    try:
        return _contexts[-1]
    except IndexError:
        return None

def set(*args, **kwargs):
    context = current_context()
    if context:
        context.set(*args, **kwargs)

def append(*args, **kwargs):
    context = current_context()
    if context:
        context.append(*args, **kwargs)

def add(*args, **kwargs):
    context = current_context()
    if context:
        context.add(*args, **kwargs)

# -*- coding: utf-8 -*- ziggy.context ~~~~~~~~  This module provides the concept of 'Context' for collecting data that will generate a log event.  :copyright: (c) 2012 by <NAME> :license: ISC, see LICENSE for more details. # This will be redefined by our configuration to tell us where to record stuff # to. # Generate an id if one wasn't provided and we don't have any parents # We're going to encode the time as the front 4 bytes so we have some order to the ids # that could prove useful later on by making sorting a little easier. Indicates the contest is open and can be written to # Just be sure # Make sure we don't get any duplicate data # I would clear out all the data here, but that makes testing a little # more challenging.