<commit_before>"""Support for system log."""
from collections import OrderedDict
import logging
import re
import traceback

import voluptuous as vol

from homeassistant import __path__ as HOMEASSISTANT_PATH
from homeassistant.components.http import HomeAssistantView
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP

CONF_MAX_ENTRIES = 'max_entries'
CONF_FIRE_EVENT = 'fire_event'
CONF_MESSAGE = 'message'
CONF_LEVEL = 'level'
CONF_LOGGER = 'logger'

DATA_SYSTEM_LOG = 'system_log'
DEFAULT_MAX_ENTRIES = 50
DEFAULT_FIRE_EVENT = False
DEPENDENCIES = ['http']
DOMAIN = 'system_log'

EVENT_SYSTEM_LOG = 'system_log_event'

SERVICE_CLEAR = 'clear'
SERVICE_WRITE = 'write'

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Optional(CONF_MAX_ENTRIES, default=DEFAULT_MAX_ENTRIES):
            cv.positive_int,
        vol.Optional(CONF_FIRE_EVENT, default=DEFAULT_FIRE_EVENT): cv.boolean,
    }),
}, extra=vol.ALLOW_EXTRA)

SERVICE_CLEAR_SCHEMA = vol.Schema({})
SERVICE_WRITE_SCHEMA = vol.Schema({
    vol.Required(CONF_MESSAGE): cv.string,
    vol.Optional(CONF_LEVEL, default='error'):
        vol.In(['debug', 'info', 'warning', 'error', 'critical']),
    vol.Optional(CONF_LOGGER): cv.string,
})


def _figure_out_source(record, call_stack, hass):
    paths = [HOMEASSISTANT_PATH[0], hass.config.config_dir]
    try:
        # If netdisco is installed check its path too.
        from netdisco import __path__ as netdisco_path
        paths.append(netdisco_path[0])
    except ImportError:
        pass
    # If a stack trace exists, extract file names from the entire call stack.
    # The other case is when a regular "log" is made (without an attached
    # exception). In that case, just use the file where the log was made from.
    if record.exc_info:
        stack = [x[0] for x in traceback.extract_tb(record.exc_info[2])]
    else:
        index = -1
        for i, frame in enumerate(call_stack):
            if frame == record.pathname:
                index = i
                break
        if index == -1:
            # For some reason we couldn't find pathname in the stack.
            stack = [record.pathname]
        else:
            stack = call_stack[0:index+1]

    # Iterate through the stack call (in reverse) and find the last call from
    # a file in Home Assistant. Try to figure out where error happened.
    paths_re = r'(?:{})/(.*)'.format('|'.join([re.escape(x) for x in paths]))
    for pathname in reversed(stack):

        # Try to match with a file within Home Assistant
        match = re.match(paths_re, pathname)
        if match:
            return match.group(1)
    # Ok, we don't know what this is
    return record.pathname


class LogEntry:
    """Store HA log entries."""

    def __init__(self, record, stack, source):
        """Initialize a log entry."""
        self.first_occured = self.timestamp = record.created
        self.level = record.levelname
        self.message = record.getMessage()
        if record.exc_info:
            self.exception = ''.join(
                traceback.format_exception(*record.exc_info))
            _, _, tb = record.exc_info  # pylint: disable=invalid-name
            # Last line of traceback contains the root cause of the exception
            self.root_cause = str(traceback.extract_tb(tb)[-1])
        else:
            self.exception = ''
            self.root_cause = None
        self.source = source
        self.count = 1<commit_msg>check we have a tb (#21670)<commit_after>"""Support for system log."""
from collections import OrderedDict
import logging
import re
import traceback

import voluptuous as vol

from homeassistant import __path__ as HOMEASSISTANT_PATH
from homeassistant.components.http import HomeAssistantView
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP

CONF_MAX_ENTRIES = 'max_entries'
CONF_FIRE_EVENT = 'fire_event'
CONF_MESSAGE = 'message'
CONF_LEVEL = 'level'
CONF_LOGGER = 'logger'

DATA_SYSTEM_LOG = 'system_log'
DEFAULT_MAX_ENTRIES = 50
DEFAULT_FIRE_EVENT = False
DEPENDENCIES = ['http']
DOMAIN = 'system_log'

EVENT_SYSTEM_LOG = 'system_log_event'

SERVICE_CLEAR = 'clear'
SERVICE_WRITE = 'write'

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Optional(CONF_MAX_ENTRIES, default=DEFAULT_MAX_ENTRIES):
            cv.positive_int,
        vol.Optional(CONF_FIRE_EVENT, default=DEFAULT_FIRE_EVENT): cv.boolean,
    }),
}, extra=vol.ALLOW_EXTRA)

SERVICE_CLEAR_SCHEMA = vol.Schema({})
SERVICE_WRITE_SCHEMA = vol.Schema({
    vol.Required(CONF_MESSAGE): cv.string,
    vol.Optional(CONF_LEVEL, default='error'):
        vol.In(['debug', 'info', 'warning', 'error', 'critical']),
    vol.Optional(CONF_LOGGER): cv.string,
})


def _figure_out_source(record, call_stack, hass):
    paths = [HOMEASSISTANT_PATH[0], hass.config.config_dir]
    try:
        # If netdisco is installed check its path too.
        from netdisco import __path__ as netdisco_path
        paths.append(netdisco_path[0])
    except ImportError:
        pass
    # If a stack trace exists, extract file names from the entire call stack.
    # The other case is when a regular "log" is made (without an attached
    # exception). In that case, just use the file where the log was made from.
    if record.exc_info:
        stack = [x[0] for x in traceback.extract_tb(record.exc_info[2])]
    else:
        index = -1
        for i, frame in enumerate(call_stack):
            if frame == record.pathname:
                index = i
                break
        if index == -1:
            # For some reason we couldn't find pathname in the stack.
            stack = [record.pathname]
        else:
            stack = call_stack[0:index+1]

    # Iterate through the stack call (in reverse) and find the last call from
    # a file in Home Assistant. Try to figure out where error happened.
    paths_re = r'(?:{})/(.*)'.format('|'.join([re.escape(x) for x in paths]))
    for pathname in reversed(stack):

        # Try to match with a file within Home Assistant
        match = re.match(paths_re, pathname)
        if match:
            return match.group(1)
    # Ok, we don't know what this is
    return record.pathname


class LogEntry:
    """Store HA log entries."""

    def __init__(self, record, stack, source):
        """Initialize a log entry."""
        self.first_occured = self.timestamp = record.created
        self.level = record.levelname
        self.message = record.getMessage()
        self.exception = ''
        self.root_cause = None
        if record.exc_info:
            self.exception = ''.join(
                traceback.format_exception(*record.exc_info))
            _, _, tb = record.exc_info  # pylint: disable=invalid-name
            # Last line of traceback contains the root cause of the exception
            if traceback.extract_tb(tb):
                self.root_cause = str(traceback.extract_tb(tb)[-1])
        self.source = source
        self.count = 1
{'fraction_non_alphanumeric': 0.09754714305969091, 'fraction_numerical': 0.004395292783212817, 'mean_word_length': 2.8843612334801763, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4931579', 'n_tokens_mistral': 2152, 'n_tokens_neox': 2127, 'n_words': 617}