diff --git a/WOTSTAT/res/scripts/client/gui/mods/wot_stat/common/config.py b/WOTSTAT/res/scripts/client/gui/mods/wot_stat/common/config.py index 933323f..05cad97 100644 --- a/WOTSTAT/res/scripts/client/gui/mods/wot_stat/common/config.py +++ b/WOTSTAT/res/scripts/client/gui/mods/wot_stat/common/config.py @@ -16,6 +16,7 @@ class Config: 'eventURL': 'https://wotstat.info/api/events/send', 'updateURL': 'https://api.github.com/repos/WOT-STAT/WOTMOD/releases/latest', 'statusURL': 'https://wotstat.info/api', + 'lokiURL': 'https://loki.wotstat.info/loki/api/v1/push', 'hideServer': False } @@ -48,6 +49,3 @@ def __init__(self, ConfigPath, DefaultParams=None): def get(self, key): return self.config[key] if key in self.config else self.defaultParams[ key] if key in self.defaultParams else None - - -Config = Config diff --git a/WOTSTAT/res/scripts/client/gui/mods/wot_stat/load_mod.py b/WOTSTAT/res/scripts/client/gui/mods/wot_stat/load_mod.py index 3431dbf..e6d8a1b 100644 --- a/WOTSTAT/res/scripts/client/gui/mods/wot_stat/load_mod.py +++ b/WOTSTAT/res/scripts/client/gui/mods/wot_stat/load_mod.py @@ -2,19 +2,20 @@ import BigWorld import json -from gui import SystemMessages from .common.config import Config +configPath = './mods/configs/wot_stat/config.cfg' +config = Config(configPath) # type: Config + +from gui import SystemMessages from .common.modAutoUpdate import update_game_version, update_mod_version from .common.modNotification import show_notification, OPEN_PERSONAL_WOTSTAT_EVENT from .common.asyncResponse import get_async -from .utils import print_log - -configPath = './mods/configs/wot_stat/config.cfg' -config = Config(configPath) # type: Config +from .utils import print_log from .logger.eventLogger import eventLogger from .logger.wotHookEvents import wotHookEvents from .logger.sessionStorage import sessionStorage +from .serverLogger import setupLogger, send is_success_check = None api_server_time = None @@ -98,9 +99,8 @@ def hello_message(): def init_mod(): - global logger - print_log('version ' + config.get('version')) + setupLogger(config.get('lokiURL'), config.get('version')) get_async(config.get('statusURL'), callback=on_status_check, error_callback=on_status_check_fail) update_game_version(mod_name()) @@ -110,6 +110,7 @@ def init_mod(): on_updated=new_version_update_end, on_success_check=on_success_check) sessionStorage.on_load_mod() + send("INFO", 'Mod init') wotHookEvents.onConnected += on_connected diff --git a/WOTSTAT/res/scripts/client/gui/mods/wot_stat/logger/wotHookEvents.py b/WOTSTAT/res/scripts/client/gui/mods/wot_stat/logger/wotHookEvents.py index 6bb0c9b..aa4d720 100644 --- a/WOTSTAT/res/scripts/client/gui/mods/wot_stat/logger/wotHookEvents.py +++ b/WOTSTAT/res/scripts/client/gui/mods/wot_stat/logger/wotHookEvents.py @@ -8,9 +8,26 @@ from Vehicle import Vehicle from helpers import dependency from skeletons.connection_mgr import IConnectionManager -import Event +from Event import Event +from debug_utils import LOG_CURRENT_EXCEPTION from ..common.hook import g_overrideLib +from ..serverLogger import send_current_exception + + +class SafeEvent(Event): + __slots__ = () + + def __init__(self, manager=None): + super(SafeEvent, self).__init__(manager) + + def __call__(self, *args, **kwargs): + for delegate in self[:]: + try: + delegate(*args, **kwargs) + except: + send_current_exception() + LOG_CURRENT_EXCEPTION() class WotHookEvents: @@ -22,29 +39,29 @@ def __init__(self): self.listeners = {} # ------------------INIT------------------# - self.onConnected = Event.Event() - self.Account_onBecomePlayer = Event.Event() - self.BattleQueue_populate = Event.Event() - self.PlayerAvatar_onEnterWorld = Event.Event() - self.PlayerAvatar_updateTargetingInfo = Event.Event() - self.PlayerAvatar_onArenaPeriodChange = Event.Event() + self.onConnected = SafeEvent() + self.Account_onBecomePlayer = SafeEvent() + self.BattleQueue_populate = SafeEvent() + self.PlayerAvatar_onEnterWorld = SafeEvent() + self.PlayerAvatar_updateTargetingInfo = SafeEvent() + self.PlayerAvatar_onArenaPeriodChange = SafeEvent() # -------------------MOVE------------------# - self.VehicleGunRotator_setShotPosition = Event.Event() - self.VehicleGunRotator_updateGunMarker = Event.Event() - self.PlayerAvatar_updateGunMarker = Event.Event() + self.VehicleGunRotator_setShotPosition = SafeEvent() + self.VehicleGunRotator_updateGunMarker = SafeEvent() + self.PlayerAvatar_updateGunMarker = SafeEvent() # -------------------SHOT------------------# - self.PlayerAvatar_shoot = Event.Event() - self.PlayerAvatar_showTracer = Event.Event() - self.PlayerAvatar_showShotResults = Event.Event() - self.Vehicle_onHealthChanged = Event.Event() - self.PlayerAvatar_showOwnVehicleHitDirection = Event.Event() + self.PlayerAvatar_shoot = SafeEvent() + self.PlayerAvatar_showTracer = SafeEvent() + self.PlayerAvatar_showShotResults = SafeEvent() + self.Vehicle_onHealthChanged = SafeEvent() + self.PlayerAvatar_showOwnVehicleHitDirection = SafeEvent() # -------------------EXPLOSION------------------# - self.PlayerAvatar_explodeProjectile = Event.Event() - self.Vehicle_showDamageFromShot = Event.Event() + self.PlayerAvatar_explodeProjectile = SafeEvent() + self.Vehicle_showDamageFromShot = SafeEvent() # -------------------PROJECTILE-------------------# - self.ProjectileMover_killProjectile = Event.Event() + self.ProjectileMover_killProjectile = SafeEvent() # -------------------HELP-------------------# - self.PlayerAvatar_enableServerAim = Event.Event() + self.PlayerAvatar_enableServerAim = SafeEvent() def __onConnected(self): self.onConnected() diff --git a/WOTSTAT/res/scripts/client/gui/mods/wot_stat/serverLogger.py b/WOTSTAT/res/scripts/client/gui/mods/wot_stat/serverLogger.py new file mode 100644 index 0000000..f6af672 --- /dev/null +++ b/WOTSTAT/res/scripts/client/gui/mods/wot_stat/serverLogger.py @@ -0,0 +1,155 @@ +import sys +import os +import json +import time +import hashlib + +from typing import List + +import BigWorld +from traceback import format_exception +import excepthook +from debug_utils import _addTagsToMsg, _makeMsgHeader, LOG_CURRENT_EXCEPTION, _src_file_trim_to, _g_logLock +from constants import AUTH_REALM +from account_shared import readClientServerVersion + +from .common.asyncResponse import post_async + +logger = None # type: ServerLogger +modVersion = 'unknown' + +GAME_VERSION = readClientServerVersion()[1] + + +def setupLogger(url, version): + global logger, modVersion + modVersion = version + logger = ServerLogger(url) + + +def send(level, msg): + if logger: + logger.send(level, msg) + else: + print("[WOTSTAT] LOGGER ERROR. Call before init") + + +def send_current_exception(tags=None, frame=1): + msg = _makeMsgHeader(sys._getframe(frame)) + '\n' + etype, value, tb = sys.exc_info() + msg += ''.join(format_exception(etype, value, tb, None)) + with _g_logLock: + line = '' + line += '[EXCEPTION]' + _addTagsToMsg(tags, msg) + extMsg = excepthook.extendedTracebackAsString(_src_file_trim_to, None, None, etype, value, tb) + if extMsg: + line += '[EXCEPTION]' + _addTagsToMsg(tags, extMsg) + + send(LEVELS.ERROR, line) + + +def withExceptionHandling(l): + try: + l() + except: + send_current_exception() + LOG_CURRENT_EXCEPTION() + + +def _generate_session_id(): + current_time = str(time.time()).encode('utf-8') + random_bytes = os.urandom(16) + unique_bytes = current_time + random_bytes + session_id = hashlib.sha256(unique_bytes).hexdigest() + + return session_id + + +def _get_player_name(): + player = BigWorld.player() + + if not player: return 'unknown_player' + if not player.name: return 'unknown_name' + return player.name + + +def _get_game_version(): + if not GAME_VERSION: return 'unknown_version' + return GAME_VERSION + + +def _get_mod_version(): + return modVersion + + +def _get_region(): + return AUTH_REALM + + +class LEVELS: + DEBUG = 'DEBUG' + INFO = 'INFO' + WARN = 'WARN' + ERROR = 'ERROR' + + +LEVELS_NAMES = [LEVELS.DEBUG, LEVELS.INFO, LEVELS.WARN, LEVELS.ERROR] + + +class Message: + def __init__(self, level, message): + self.level = level if level in LEVELS_NAMES else LEVELS.INFO + self.message = message if message else "empty" + self.time = int(time.time() * 1e9) + + +def _on_send_error(res): + print('[WOTSTAT SERVER LOGGER] sending error') + print(res) + + +class ServerLogger: + session_id = _generate_session_id() + logs_queue = [] # type: List[Message] + + def __init__(self, url): + self.url = url + print("[WOTSTAT SERVER LOGGER]: Init server logger to: %s", self.url) + self._sending_loop() + + def send(self, level, msg): + self.logs_queue.append(Message(level, msg)) + + def _sending_loop(self): + BigWorld.callback(5, self._sending_loop) + + try: + if len(self.logs_queue) == 0: return + + defaultStreamMeta = { + "service": "mod", + "playerName": _get_player_name(), + "region": _get_region(), + "gameVersion": _get_game_version(), + "modVersion": _get_mod_version(), + "session": self.session_id + } + + streams = [] + + for level in LEVELS_NAMES: + current = filter(lambda l: l.level == level, self.logs_queue) + if len(current) == 0: continue + + streams.append({ + "stream": dict(defaultStreamMeta, level=level), + "values": map(lambda l: [str(l.time), l.message], current) + }) + + data = {"streams": streams} + post_async(self.url, data=json.dumps(data), error_callback=_on_send_error) + + self.logs_queue = [] + except: + print("[WOTSTAT SERVER EXCEPTION]") + LOG_CURRENT_EXCEPTION() diff --git a/WOTSTAT/res/scripts/client/gui/mods/wot_stat/utils.py b/WOTSTAT/res/scripts/client/gui/mods/wot_stat/utils.py index 5acff89..8a6fe5f 100644 --- a/WOTSTAT/res/scripts/client/gui/mods/wot_stat/utils.py +++ b/WOTSTAT/res/scripts/client/gui/mods/wot_stat/utils.py @@ -1,8 +1,10 @@ import BigWorld +from .serverLogger import send, LEVELS def print_log(log): print("%s [MOD_WOT_STAT]: %s" % (BigWorld.serverTime(), str(log))) + send(LEVELS.INFO, str(log)) def print_debug(log):