Module jumpscale.tools.alerthandler.alerthandler

Expand source code
from jumpscale.loader import j
import gevent


def _get_identifier(app_name, message, public_message, category, alert_type):
    return j.data.hash.md5(":".join([app_name, message, public_message, category, alert_type]))


class Alert:
    def __init__(self):
        self.id = None
        self.type = None
        self.level = 0
        self.app_name = None
        self.category = None
        self.message = None
        self.public_message = None
        self.count = 0
        self.status = None
        self.first_occurrence = None
        self.last_occurrence = None
        self.data = None
        self.event = []
        self.tracebacks = []

    @classmethod
    def loads(cls, value):
        json = j.data.serializers.json.loads(value)
        instance = cls()
        instance.__dict__ = json
        return instance

    @property
    def identifier(self):
        return _get_identifier(self.app_name, self.message, self.public_message, self.category, self.type)

    @property
    def json(self):
        return self.__dict__

    def dumps(self):
        return j.data.serializers.json.dumps(self.__dict__)


class AlertsHandler:
    def __init__(self):
        self._rkey = "alerts"
        self._rkey_id = "alerts:id"
        self._rkey_incr = "alerts:id:incr"
        self._db = None
        self.handlers = []

    def __dir__(self):
        return ("get", "find", "alert_raise", "count", "reset", "delete", "delete_all")

    @property
    def db(self):
        if self._db is None:
            self._db = j.core.db
        return self._db

    def get(self, alert_id: int = None, identifier: str = None, die: bool = True) -> Alert:
        """Get alert by its id or identifier

        Keyword Arguments:
            alert_id {int} -- alert id (default: {None})
            identifier {str} -- alert identifier (default: {None})
            die {bool} -- flag to rasie exception if alert is not found (default: {True})

        Raises:
            j.core.exceptions.NotFound: alert is not found
            j.core.exceptions.Value: invalid arguments

        Returns:
            Alert -- [description]
        """
        if not (alert_id or identifier):
            raise j.core.exceptions.Value("Either alert id or alert identifier are required")

        alert_id = alert_id or self.db.hget(self._rkey_id, identifier)
        if alert_id:
            alert = self.db.hget(self._rkey, alert_id)
            if alert:
                return Alert.loads(alert)
        if die:
            raise j.core.exceptions.NotFound("Requested alert is not found")

    def find(
        self,
        app_name: str = "",
        category: str = "",
        message: str = "",
        pid: int = None,
        start_time: int = None,
        end_time: int = None,
    ) -> list:

        """Find alerts

        Keyword Arguments:
            app_name (str):  filter by allert app name (default: {""})
            category {str} -- filter by alert category (default: {""})
            message {str} -- filter by alert message (default: {""})
            pid {int} -- filter by process id (default: {None})
            start_time {int} -- filter by start time (default: {None})
            end_time {int} -- filter by end time (default: {None})

        Returns:
            list of Alert objects
        """

        app_name = app_name.strip().lower()
        category = category.strip().lower()
        message = message.strip().lower()

        alerts = []
        items = self.db.hscan_iter(self._rkey)

        for item in items:
            alert = Alert.loads(item[1])

            if app_name and app_name != alert.app_name.strip().lower():
                continue

            if category and category != alert.category.strip().lower():
                continue

            if message and (
                message not in alert.message.strip().lower() and message not in alert.public_message.strip().lower()
            ):
                continue

            if start_time and start_time < alert.first_occurrence:
                continue

            if end_time and end_time > alert.last_occurrence:
                continue

            if pid:
                for traceback in alert.tracebacks:
                    if traceback["process_id"] == pid:
                        break
                else:
                    continue

            alerts.append(alert)
        return sorted(alerts, key=lambda alert: alert.id)

    def alert_raise(
        self,
        app_name,
        message,
        public_message: str = "",
        category: str = "",
        alert_type: str = "event_system",
        level: int = 40,
        data: dict = None,
        timestamp: float = None,
        traceback: dict = None,
    ) -> Alert:

        """Raise a new alert

        Arguments:
            message {str} -- alert message

        Keyword Arguments:
            public_message {str} -- alert public message (default: {""})
            category {str} -- alert category (default: {""})
            alert_type {str} -- alert type (default: {"event_system"})
            level {int} -- alert level (default: {40})
            traceback {dict} -- alert traceback (default: {None})

        Returns:
            Alert -- alert object
        """
        if not self.db.is_running():
            return

        identifier = _get_identifier(app_name, message, public_message, category, alert_type)
        alert = self.get(identifier=identifier, die=False) or Alert()

        if alert.id:
            if alert.status == "new":
                alert.status = "open"
            elif alert.status == "closed":
                alert.status = "reopened"
        else:
            alert.status = "new"
            alert.first_occurrence = timestamp or j.data.time.now().timestamp

        alert.app_name = app_name
        alert.category = category
        alert.message = message
        alert.public_message = public_message
        alert.level = level
        alert.type = alert_type
        alert.count += 1
        alert.last_occurrence = timestamp or j.data.time.now().timestamp

        if traceback:
            if len(alert.tracebacks) > 5:
                alert.tracebacks.pop(0)

        alert.tracebacks.append(traceback)
        self._save(alert)
        for handler_func, handler_level in self.handlers:
            if level >= handler_level:
                gevent.spawn(handler_func, alert)
        return alert

    def count(self) -> int:
        """Gets alerts count

        Returns:
            int -- total number of alerts
        """
        return self.db.hlen(self._rkey)

    def _save(self, alert: Alert):
        """Saves alert object in db

        Arguments:
            alert {Alert} -- alert object
        """
        if not alert.id:
            alert.id = self.db.incr(self._rkey_incr)

        self.db.hset(self._rkey, alert.id, alert.dumps())
        self.db.hset(self._rkey_id, alert.identifier, alert.id)

    def delete(self, alert_id: int = None, identifier: str = None):
        """Delete alert by its id or identifier

        Raises:
            j.core.exceptions.Value: invalid arguments

        Keyword Arguments:
            alert_id {int} -- alert id (default: {None})
            identifier {str} -- alert identifier (default: {None})
        """
        if not (alert_id or identifier):
            raise j.core.exceptions.Value("Either alert id or alert identifier are required")

        alert_id = alert_id or self.db.hget(self._rkey_id, identifier)
        if alert_id:
            self.db.hdel(self._rkey, alert_id)

    def delete_all(self):
        """Deletes all alerts"""
        self.db.delete(self._rkey, self._rkey_id)

    def reset(self):
        """Delete all alerts and reset the db"""
        self.delete_all()
        self.db.delete(self._rkey_incr)

    def register_handler(self, handler: callable, level: int = 40):
        """Register new alert handler

        Arguments:
            handler (callable): error handler callable

        Keyword Arguments:
            level (int): exception level (default: {40})
        """
        if (handler, level) not in self.handlers:
            self.handlers.append((handler, level))

Classes

class Alert
Expand source code
class Alert:
    def __init__(self):
        self.id = None
        self.type = None
        self.level = 0
        self.app_name = None
        self.category = None
        self.message = None
        self.public_message = None
        self.count = 0
        self.status = None
        self.first_occurrence = None
        self.last_occurrence = None
        self.data = None
        self.event = []
        self.tracebacks = []

    @classmethod
    def loads(cls, value):
        json = j.data.serializers.json.loads(value)
        instance = cls()
        instance.__dict__ = json
        return instance

    @property
    def identifier(self):
        return _get_identifier(self.app_name, self.message, self.public_message, self.category, self.type)

    @property
    def json(self):
        return self.__dict__

    def dumps(self):
        return j.data.serializers.json.dumps(self.__dict__)

Static methods

def loads(value)
Expand source code
@classmethod
def loads(cls, value):
    json = j.data.serializers.json.loads(value)
    instance = cls()
    instance.__dict__ = json
    return instance

Instance variables

var identifier
Expand source code
@property
def identifier(self):
    return _get_identifier(self.app_name, self.message, self.public_message, self.category, self.type)
var json
Expand source code
@property
def json(self):
    return self.__dict__

Methods

def dumps(self)
Expand source code
def dumps(self):
    return j.data.serializers.json.dumps(self.__dict__)
class AlertsHandler
Expand source code
class AlertsHandler:
    def __init__(self):
        self._rkey = "alerts"
        self._rkey_id = "alerts:id"
        self._rkey_incr = "alerts:id:incr"
        self._db = None
        self.handlers = []

    def __dir__(self):
        return ("get", "find", "alert_raise", "count", "reset", "delete", "delete_all")

    @property
    def db(self):
        if self._db is None:
            self._db = j.core.db
        return self._db

    def get(self, alert_id: int = None, identifier: str = None, die: bool = True) -> Alert:
        """Get alert by its id or identifier

        Keyword Arguments:
            alert_id {int} -- alert id (default: {None})
            identifier {str} -- alert identifier (default: {None})
            die {bool} -- flag to rasie exception if alert is not found (default: {True})

        Raises:
            j.core.exceptions.NotFound: alert is not found
            j.core.exceptions.Value: invalid arguments

        Returns:
            Alert -- [description]
        """
        if not (alert_id or identifier):
            raise j.core.exceptions.Value("Either alert id or alert identifier are required")

        alert_id = alert_id or self.db.hget(self._rkey_id, identifier)
        if alert_id:
            alert = self.db.hget(self._rkey, alert_id)
            if alert:
                return Alert.loads(alert)
        if die:
            raise j.core.exceptions.NotFound("Requested alert is not found")

    def find(
        self,
        app_name: str = "",
        category: str = "",
        message: str = "",
        pid: int = None,
        start_time: int = None,
        end_time: int = None,
    ) -> list:

        """Find alerts

        Keyword Arguments:
            app_name (str):  filter by allert app name (default: {""})
            category {str} -- filter by alert category (default: {""})
            message {str} -- filter by alert message (default: {""})
            pid {int} -- filter by process id (default: {None})
            start_time {int} -- filter by start time (default: {None})
            end_time {int} -- filter by end time (default: {None})

        Returns:
            list of Alert objects
        """

        app_name = app_name.strip().lower()
        category = category.strip().lower()
        message = message.strip().lower()

        alerts = []
        items = self.db.hscan_iter(self._rkey)

        for item in items:
            alert = Alert.loads(item[1])

            if app_name and app_name != alert.app_name.strip().lower():
                continue

            if category and category != alert.category.strip().lower():
                continue

            if message and (
                message not in alert.message.strip().lower() and message not in alert.public_message.strip().lower()
            ):
                continue

            if start_time and start_time < alert.first_occurrence:
                continue

            if end_time and end_time > alert.last_occurrence:
                continue

            if pid:
                for traceback in alert.tracebacks:
                    if traceback["process_id"] == pid:
                        break
                else:
                    continue

            alerts.append(alert)
        return sorted(alerts, key=lambda alert: alert.id)

    def alert_raise(
        self,
        app_name,
        message,
        public_message: str = "",
        category: str = "",
        alert_type: str = "event_system",
        level: int = 40,
        data: dict = None,
        timestamp: float = None,
        traceback: dict = None,
    ) -> Alert:

        """Raise a new alert

        Arguments:
            message {str} -- alert message

        Keyword Arguments:
            public_message {str} -- alert public message (default: {""})
            category {str} -- alert category (default: {""})
            alert_type {str} -- alert type (default: {"event_system"})
            level {int} -- alert level (default: {40})
            traceback {dict} -- alert traceback (default: {None})

        Returns:
            Alert -- alert object
        """
        if not self.db.is_running():
            return

        identifier = _get_identifier(app_name, message, public_message, category, alert_type)
        alert = self.get(identifier=identifier, die=False) or Alert()

        if alert.id:
            if alert.status == "new":
                alert.status = "open"
            elif alert.status == "closed":
                alert.status = "reopened"
        else:
            alert.status = "new"
            alert.first_occurrence = timestamp or j.data.time.now().timestamp

        alert.app_name = app_name
        alert.category = category
        alert.message = message
        alert.public_message = public_message
        alert.level = level
        alert.type = alert_type
        alert.count += 1
        alert.last_occurrence = timestamp or j.data.time.now().timestamp

        if traceback:
            if len(alert.tracebacks) > 5:
                alert.tracebacks.pop(0)

        alert.tracebacks.append(traceback)
        self._save(alert)
        for handler_func, handler_level in self.handlers:
            if level >= handler_level:
                gevent.spawn(handler_func, alert)
        return alert

    def count(self) -> int:
        """Gets alerts count

        Returns:
            int -- total number of alerts
        """
        return self.db.hlen(self._rkey)

    def _save(self, alert: Alert):
        """Saves alert object in db

        Arguments:
            alert {Alert} -- alert object
        """
        if not alert.id:
            alert.id = self.db.incr(self._rkey_incr)

        self.db.hset(self._rkey, alert.id, alert.dumps())
        self.db.hset(self._rkey_id, alert.identifier, alert.id)

    def delete(self, alert_id: int = None, identifier: str = None):
        """Delete alert by its id or identifier

        Raises:
            j.core.exceptions.Value: invalid arguments

        Keyword Arguments:
            alert_id {int} -- alert id (default: {None})
            identifier {str} -- alert identifier (default: {None})
        """
        if not (alert_id or identifier):
            raise j.core.exceptions.Value("Either alert id or alert identifier are required")

        alert_id = alert_id or self.db.hget(self._rkey_id, identifier)
        if alert_id:
            self.db.hdel(self._rkey, alert_id)

    def delete_all(self):
        """Deletes all alerts"""
        self.db.delete(self._rkey, self._rkey_id)

    def reset(self):
        """Delete all alerts and reset the db"""
        self.delete_all()
        self.db.delete(self._rkey_incr)

    def register_handler(self, handler: callable, level: int = 40):
        """Register new alert handler

        Arguments:
            handler (callable): error handler callable

        Keyword Arguments:
            level (int): exception level (default: {40})
        """
        if (handler, level) not in self.handlers:
            self.handlers.append((handler, level))

Instance variables

var db
Expand source code
@property
def db(self):
    if self._db is None:
        self._db = j.core.db
    return self._db

Methods

def alert_raise(self, app_name, message, public_message: str = '', category: str = '', alert_type: str = 'event_system', level: int = 40, data: dict = None, timestamp: float = None, traceback: dict = None) ‑> Alert

Raise a new alert

Arguments

message {str} – alert message

Keyword Arguments: public_message {str} – alert public message (default: {""}) category {str} – alert category (default: {""}) alert_type {str} – alert type (default: {"event_system"}) level {int} – alert level (default: {40}) traceback {dict} – alert traceback (default: {None})

Returns

Alert – alert object

Expand source code
def alert_raise(
    self,
    app_name,
    message,
    public_message: str = "",
    category: str = "",
    alert_type: str = "event_system",
    level: int = 40,
    data: dict = None,
    timestamp: float = None,
    traceback: dict = None,
) -> Alert:

    """Raise a new alert

    Arguments:
        message {str} -- alert message

    Keyword Arguments:
        public_message {str} -- alert public message (default: {""})
        category {str} -- alert category (default: {""})
        alert_type {str} -- alert type (default: {"event_system"})
        level {int} -- alert level (default: {40})
        traceback {dict} -- alert traceback (default: {None})

    Returns:
        Alert -- alert object
    """
    if not self.db.is_running():
        return

    identifier = _get_identifier(app_name, message, public_message, category, alert_type)
    alert = self.get(identifier=identifier, die=False) or Alert()

    if alert.id:
        if alert.status == "new":
            alert.status = "open"
        elif alert.status == "closed":
            alert.status = "reopened"
    else:
        alert.status = "new"
        alert.first_occurrence = timestamp or j.data.time.now().timestamp

    alert.app_name = app_name
    alert.category = category
    alert.message = message
    alert.public_message = public_message
    alert.level = level
    alert.type = alert_type
    alert.count += 1
    alert.last_occurrence = timestamp or j.data.time.now().timestamp

    if traceback:
        if len(alert.tracebacks) > 5:
            alert.tracebacks.pop(0)

    alert.tracebacks.append(traceback)
    self._save(alert)
    for handler_func, handler_level in self.handlers:
        if level >= handler_level:
            gevent.spawn(handler_func, alert)
    return alert
def count(self) ‑> int

Gets alerts count

Returns

int – total number of alerts

Expand source code
def count(self) -> int:
    """Gets alerts count

    Returns:
        int -- total number of alerts
    """
    return self.db.hlen(self._rkey)
def delete(self, alert_id: int = None, identifier: str = None)

Delete alert by its id or identifier

Raises

j.core.exceptions.Value
invalid arguments

Keyword Arguments: alert_id {int} – alert id (default: {None}) identifier {str} – alert identifier (default: {None})

Expand source code
def delete(self, alert_id: int = None, identifier: str = None):
    """Delete alert by its id or identifier

    Raises:
        j.core.exceptions.Value: invalid arguments

    Keyword Arguments:
        alert_id {int} -- alert id (default: {None})
        identifier {str} -- alert identifier (default: {None})
    """
    if not (alert_id or identifier):
        raise j.core.exceptions.Value("Either alert id or alert identifier are required")

    alert_id = alert_id or self.db.hget(self._rkey_id, identifier)
    if alert_id:
        self.db.hdel(self._rkey, alert_id)
def delete_all(self)

Deletes all alerts

Expand source code
def delete_all(self):
    """Deletes all alerts"""
    self.db.delete(self._rkey, self._rkey_id)
def find(self, app_name: str = '', category: str = '', message: str = '', pid: int = None, start_time: int = None, end_time: int = None) ‑> list

Find alerts

Keyword Arguments: app_name (str): filter by allert app name (default: {""}) category {str} – filter by alert category (default: {""}) message {str} – filter by alert message (default: {""}) pid {int} – filter by process id (default: {None}) start_time {int} – filter by start time (default: {None}) end_time {int} – filter by end time (default: {None})

Returns

list of Alert objects

Expand source code
def find(
    self,
    app_name: str = "",
    category: str = "",
    message: str = "",
    pid: int = None,
    start_time: int = None,
    end_time: int = None,
) -> list:

    """Find alerts

    Keyword Arguments:
        app_name (str):  filter by allert app name (default: {""})
        category {str} -- filter by alert category (default: {""})
        message {str} -- filter by alert message (default: {""})
        pid {int} -- filter by process id (default: {None})
        start_time {int} -- filter by start time (default: {None})
        end_time {int} -- filter by end time (default: {None})

    Returns:
        list of Alert objects
    """

    app_name = app_name.strip().lower()
    category = category.strip().lower()
    message = message.strip().lower()

    alerts = []
    items = self.db.hscan_iter(self._rkey)

    for item in items:
        alert = Alert.loads(item[1])

        if app_name and app_name != alert.app_name.strip().lower():
            continue

        if category and category != alert.category.strip().lower():
            continue

        if message and (
            message not in alert.message.strip().lower() and message not in alert.public_message.strip().lower()
        ):
            continue

        if start_time and start_time < alert.first_occurrence:
            continue

        if end_time and end_time > alert.last_occurrence:
            continue

        if pid:
            for traceback in alert.tracebacks:
                if traceback["process_id"] == pid:
                    break
            else:
                continue

        alerts.append(alert)
    return sorted(alerts, key=lambda alert: alert.id)
def get(self, alert_id: int = None, identifier: str = None, die: bool = True) ‑> Alert

Get alert by its id or identifier

Keyword Arguments: alert_id {int} – alert id (default: {None}) identifier {str} – alert identifier (default: {None}) die {bool} – flag to rasie exception if alert is not found (default: {True})

Raises

j.core.exceptions.NotFound
alert is not found
j.core.exceptions.Value
invalid arguments

Returns

Alert – [description]

Expand source code
def get(self, alert_id: int = None, identifier: str = None, die: bool = True) -> Alert:
    """Get alert by its id or identifier

    Keyword Arguments:
        alert_id {int} -- alert id (default: {None})
        identifier {str} -- alert identifier (default: {None})
        die {bool} -- flag to rasie exception if alert is not found (default: {True})

    Raises:
        j.core.exceptions.NotFound: alert is not found
        j.core.exceptions.Value: invalid arguments

    Returns:
        Alert -- [description]
    """
    if not (alert_id or identifier):
        raise j.core.exceptions.Value("Either alert id or alert identifier are required")

    alert_id = alert_id or self.db.hget(self._rkey_id, identifier)
    if alert_id:
        alert = self.db.hget(self._rkey, alert_id)
        if alert:
            return Alert.loads(alert)
    if die:
        raise j.core.exceptions.NotFound("Requested alert is not found")
def register_handler(self, handler: , level: int = 40)

Register new alert handler

Arguments

handler (callable): error handler callable

Keyword Arguments: level (int): exception level (default: {40})

Expand source code
def register_handler(self, handler: callable, level: int = 40):
    """Register new alert handler

    Arguments:
        handler (callable): error handler callable

    Keyword Arguments:
        level (int): exception level (default: {40})
    """
    if (handler, level) not in self.handlers:
        self.handlers.append((handler, level))
def reset(self)

Delete all alerts and reset the db

Expand source code
def reset(self):
    """Delete all alerts and reset the db"""
    self.delete_all()
    self.db.delete(self._rkey_incr)