Enables [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear), checking for bugs/design problems, and [fixes the issues found](https://pastebin.com/fEDBz6Ta) (some interesting ones, particularly with mutable args). Related to https://github.com/grafana/oncall/pull/3448
131 lines
4.3 KiB
Python
131 lines
4.3 KiB
Python
import enum
|
|
import json
|
|
import logging
|
|
import re
|
|
from abc import ABC, abstractmethod
|
|
|
|
from .insight_logs_enabled_check import is_insight_logs_enabled
|
|
|
|
insight_logger = logging.getLogger("insight_logger")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class EntityEvent(enum.Enum):
|
|
CREATED = "created"
|
|
UPDATED = "updated"
|
|
DELETED = "deleted"
|
|
|
|
|
|
class InsightLoggable(ABC):
|
|
@property
|
|
@abstractmethod
|
|
def id(self) -> int:
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def public_primary_key(self) -> str:
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def insight_logs_verbal(self) -> str:
|
|
"""
|
|
insight_logs_verbal returns resource name for insight_log
|
|
"""
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def insight_logs_type_verbal(self) -> str:
|
|
"""
|
|
insight_logs_type_verbal resource type for insight_log
|
|
"""
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def insight_logs_serialized(self) -> dict:
|
|
"""
|
|
insight_logs_serialized returns resource, serialized for insight_log
|
|
"""
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def insight_logs_metadata(self) -> dict:
|
|
"""
|
|
insight_logs_metadata returns resource's fields which should be always present in the insight_log line even if
|
|
they weren't changed
|
|
"""
|
|
pass
|
|
|
|
|
|
def write_resource_insight_log(instance: InsightLoggable, author, event: EntityEvent, prev_state=None, new_state=None):
|
|
try:
|
|
organization = author.organization
|
|
if is_insight_logs_enabled(organization):
|
|
tenant_id = organization.stack_id
|
|
author_id = author.public_primary_key
|
|
author = json.dumps(author.username)
|
|
entity_type = instance.insight_logs_type_verbal
|
|
try:
|
|
entity_id: str | int = instance.public_primary_key
|
|
except AttributeError:
|
|
# Fallback for entities which have no public_primary_key, E.g. public api token, schedule export token
|
|
entity_id = instance.id
|
|
entity_name = json.dumps(instance.insight_logs_verbal)
|
|
metadata = instance.insight_logs_metadata
|
|
log_line = f"tenant_id={tenant_id} author_id={author_id} author={author} action_type=resource action_name={event.value} resource_type={entity_type} resource_id={entity_id} resource_name={entity_name}" # noqa
|
|
for k, v in metadata.items():
|
|
log_line += f" {k}={json.dumps(v)}"
|
|
if prev_state and new_state:
|
|
prev_state, new_state = state_diff_finder(prev_state, new_state)
|
|
prev_state = escape_json_str_for_insight_log(json.dumps(format_state_for_insight_log(prev_state)))
|
|
new_state = escape_json_str_for_insight_log(json.dumps(format_state_for_insight_log(new_state)))
|
|
log_line += f' prev_state="{prev_state}"'
|
|
log_line += f' new_state="{new_state}"'
|
|
insight_logger.info(log_line)
|
|
except Exception as e:
|
|
logger.warning(f"insight_log.failed_to_write_entity_insight_log exception={e} instance_id={instance.id}")
|
|
|
|
|
|
def state_diff_finder(prev_state: dict, new_state: dict):
|
|
"""
|
|
state_diff_finder returns diff between two serialized representations of the resource
|
|
"""
|
|
before_diff = {}
|
|
after_diff = {}
|
|
for k, v in prev_state.items():
|
|
if k not in new_state:
|
|
before_diff[k] = v
|
|
continue
|
|
if new_state[k] != v:
|
|
before_diff[k] = prev_state[k]
|
|
after_diff[k] = new_state[k]
|
|
for k, v in new_state.items():
|
|
if k not in prev_state:
|
|
after_diff[k] = v
|
|
return before_diff, after_diff
|
|
|
|
|
|
def escape_json_str_for_insight_log(string):
|
|
"""
|
|
escape_json_str escapes double quotes near keys and values in json string
|
|
"""
|
|
return re.sub(r"(?<!\\)(\")", r"\\\1", string)
|
|
|
|
|
|
def format_state_for_insight_log(diff_dict):
|
|
"""
|
|
format_state_for_insight_log formats serialized resource data for the insight log.
|
|
It hides and prunes fields which shouldn't be exposed
|
|
"""
|
|
fields_to_prune = ()
|
|
fields_to_hide = ("verified_phone_number", "unverified_phone_number")
|
|
for k, _ in diff_dict.items():
|
|
if k in fields_to_prune:
|
|
diff_dict[k] = "Diff not supported"
|
|
if k in fields_to_hide:
|
|
diff_dict[k] = "*****"
|
|
return diff_dict
|