from functools import wraps import typing from flask import request, has_request_context from atlas_um.auth.utils import current_user from atlas_um.consts import SystemEvents from .processors import BaseAuditProcessor class Auditor: """ Main class to abstract and decouple audit log logic from possible backends and add additional context data. Possible ways for auditing events: - explicit with `log` - tracking functions with `track` decorator Examples: auditor = Auditor() # function auditor.log(SystemEvents.some_event, arg1="val1", arg_n="val_n") # decorator @auditor.track(SystemEvents.some_event) def audited_function(arg1="val1", arg_n="val_n"): return arg1, arg_n """ def __init__(self): self._processors = [] def attach_processor(self, processor: BaseAuditProcessor): self._processors.append(processor) def detach_processor(self, processor: BaseAuditProcessor): self._processors.remove(processor) def log(self, event: SystemEvents, **kwargs): for processor in self._processors: processor.process(event, self._get_context(), **kwargs) def track(self, event: SystemEvents) -> typing.Callable: def wrapped(func): @wraps(func) def decorated_func(*args, **kwargs): result = func(*args, **kwargs) self.log(event, result=result, args=args, **kwargs) return result return decorated_func return wrapped def _get_context(self): context = {} if has_request_context(): context.update({"ip": request.remote_addr, "user": current_user}) return context