chore: improve analytics (#11714)

* chore: improve analytics

* lint

* log more events, add note in UPDATING.md

* handling base class

* more events\!

* get ref through

* right before @expose

* fix context

* touchups
This commit is contained in:
Maxime Beauchemin
2020-11-25 08:45:02 -08:00
committed by GitHub
parent 9215a31fa2
commit 0504cf1a00
13 changed files with 229 additions and 50 deletions

View File

@@ -30,15 +30,35 @@ from sqlalchemy.exc import SQLAlchemyError
from superset.stats_logger import BaseStatsLogger
def strip_int_from_path(path: Optional[str]) -> str:
"""Simple function to remove ints from '/' separated paths"""
if path:
return "/".join(["<int>" if s.isdigit() else s for s in path.split("/")])
return ""
class AbstractEventLogger(ABC):
@abstractmethod
def log(
self, user_id: Optional[int], action: str, *args: Any, **kwargs: Any
def log( # pylint: disable=too-many-arguments
self,
user_id: Optional[int],
action: str,
dashboard_id: Optional[int],
duration_ms: Optional[int],
slice_id: Optional[int],
path: Optional[str],
path_no_int: Optional[str],
ref: Optional[str],
referrer: Optional[str],
*args: Any,
**kwargs: Any,
) -> None:
pass
@contextmanager
def log_context(self, action: str) -> Iterator[Callable[..., None]]:
def log_context(
self, action: str, ref: Optional[str] = None, log_to_statsd: bool = True,
) -> Iterator[Callable[..., None]]:
"""
Log an event while reading information from the request context.
`kwargs` will be appended directly to the log payload.
@@ -69,7 +89,8 @@ class AbstractEventLogger(ABC):
except (TypeError, ValueError):
slice_id = 0
self.stats_logger.incr(action)
if log_to_statsd:
self.stats_logger.incr(action)
# bulk insert
try:
@@ -86,18 +107,38 @@ class AbstractEventLogger(ABC):
slice_id=slice_id,
duration_ms=round((time.time() - start_time) * 1000),
referrer=referrer,
path=request.path,
path_no_int=strip_int_from_path(request.path),
ref=ref,
)
def log_this(self, f: Callable[..., Any]) -> Callable[..., Any]:
def _wrapper(
self, f: Callable[..., Any], **wrapper_kwargs: Any
) -> Callable[..., Any]:
action_str = wrapper_kwargs.get("action") or f.__name__
ref = f.__qualname__ if hasattr(f, "__qualname__") else None
@functools.wraps(f)
def wrapper(*args: Any, **kwargs: Any) -> Any:
with self.log_context(f.__name__) as log:
with self.log_context(action_str, ref, **wrapper_kwargs) as log:
value = f(*args, **kwargs)
log(**kwargs)
return value
return wrapper
def log_this(self, f: Callable[..., Any]) -> Callable[..., Any]:
"""Decorator that uses the function name as the action"""
return self._wrapper(f)
def log_this_with_context(self, **kwargs: Any) -> Callable[..., Any]:
"""Decorator that can override kwargs of log_context"""
def func(f: Callable[..., Any]) -> Callable[..., Any]:
return self._wrapper(f, **kwargs)
return func
def log_manually(self, f: Callable[..., Any]) -> Callable[..., Any]:
"""Allow a function to manually update"""
@@ -162,16 +203,23 @@ def get_event_logger_from_cfg_value(cfg_value: Any) -> AbstractEventLogger:
class DBEventLogger(AbstractEventLogger):
"""Event logger that commits logs to Superset DB"""
def log( # pylint: disable=too-many-locals
self, user_id: Optional[int], action: str, *args: Any, **kwargs: Any
def log( # pylint: disable=too-many-arguments,too-many-locals
self,
user_id: Optional[int],
action: str,
dashboard_id: Optional[int],
duration_ms: Optional[int],
slice_id: Optional[int],
path: Optional[str],
path_no_int: Optional[str],
ref: Optional[str],
referrer: Optional[str],
*args: Any,
**kwargs: Any,
) -> None:
from superset.models.core import Log
records = kwargs.get("records", list())
dashboard_id = kwargs.get("dashboard_id")
slice_id = kwargs.get("slice_id")
duration_ms = kwargs.get("duration_ms")
referrer = kwargs.get("referrer")
logs = list()
for record in records:
@@ -188,6 +236,9 @@ class DBEventLogger(AbstractEventLogger):
duration_ms=duration_ms,
referrer=referrer,
user_id=user_id,
path=path,
path_no_int=path_no_int,
ref=ref,
)
logs.append(log)
try: