SimpleDebugger
SimpleDebugger is a python class definition appended to CommonServerUserPython and dynamically included in every automation by the XSOAR automation execution environment. It is for debugging custom python automations in XSOAR. You can visually trace code execution, set breakpoints, step through the code, display local variables, and profile execution times of python functions.
python · Simple Debugger
Source
from collections import OrderedDict from datetime import datetime # Append this to CommonServerUserPython from types import FrameType import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 EXCLUDELIST = [ "iterencode", "dump", "dumps", "loads", "decode", "encode", "raw_decode", "append", "len", "strip", "split", "get_token", "parse", "now", "timestamp", "strftime", "sub", "compile", ] SILENTLIST = [ "executeCommand", "args", "incident", "incidents", "context", "error", "is_error", "isError", "getError", "return_error", "return_results", "CommandResults", "execute_command", "results", "fileResult", ] def SdbgDictMarkdown(nested, indent: str) -> str: md = "" indent += " " if isinstance(nested, dict): for key, val in nested.items(): if isinstance(val, dict): md += f"{indent} {key}\n" md += SdbgDictMarkdown(val, indent) elif isinstance(val, list): md += f"{indent} {key}\n" md += SdbgDictMarkdown(val, indent) else: md += f"{indent} {key}: {val}\n" elif isinstance(nested, list): for val in nested: if isinstance(val, dict): md += SdbgDictMarkdown(val, indent) elif isinstance(val, list): md += f"{indent} {val}\n" md += SdbgDictMarkdown(val, indent) else: md += f"{indent} {val}\n" else: md += f"{indent} {nested}\n" return md class SimpleDebugger: def __init__(self): self.incid = "" self.code = OrderedDict() self.data = {} self.indent = "" self.currfunc = {} self.output = "" self.stepmode = False self.quietmode = False self.profmode = False self.logmode = True self.stack = [] self.profile = {} self.excdepth = 0 self.linebreak = [] self.funcbreak = [] self.printfunc = [] self.prevlineno = 0 self.lastcheck = 0 self.exclude = EXCLUDELIST self.silent = SILENTLIST execute_command( "setIncident", {"simpledebuggeroutput": " ", "simpledebuggercode": " ", "simpledebuggerdata": " ", "simpledebuggercmd": ""}, ) self.SdbgLoadCommands() self.incid = demisto.incident()["id"] def SdbgTraceOn(self): sys.settrace(self.SdbgTrace) def SdbgTraceOff(self): sys.settrace(None) def SdbgLoadCommands(self): fields = demisto.incident()["CustomFields"] if "simpledebuggerinput" in fields: lines = fields["simpledebuggerinput"].split("\n") for line in lines: if line.strip() == "": continue cmd = line.split(" ", 1) command = cmd[0].strip() if command == "break": # break lineno1, lineno2, func1, func2, ... self.SdbgSetBreakpoint(cmd[1].split(",")) elif command == "print": # print func, func.var1, func.var2, ... self.SdbgSetPrint(cmd[1].split(",")) elif command == "quiet": # quiet self.quietmode = True elif command == "profile": # profile self.profmode = True elif command == "nolog": # nolog self.logmode = False elif command == "silent": self.SdbgSetSilent(cmd[1].split(",")) elif command == "exclude": self.SdbgSetExclude(cmd[1].split(",")) def SdbgSetBreakpoint(self, breakpoints): for b in breakpoints: if b.strip().isdigit(): self.linebreak.append(int(b)) else: self.funcbreak.append(b.strip()) def SdbgSetPrint(self, functions: list[str]): for f in functions: self.printfunc.append(f.strip()) def SdbgSetSilent(self, functions: list[str]): for f in functions: self.silent.append(f.strip()) def SdbgSetExclude(self, functions: list[str]): for f in functions: self.exclude.append(f.strip()) def SdbgLog(self, message: str): if self.logmode: time_ms = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] self.output = f"{time_ms} | {message}\n" + self.output execute_command("setIncident", {"simpledebuggeroutput": self.output}) def SdbgCodeMd(self): markdown = "" for _key, val in self.code.items(): markdown += f"{val}\n" return markdown def SdbgDataMd(self, data): md = f"{self.currfunc['name']}():\n" md += SdbgDictMarkdown(data, "") return md def SdbgPrintLocals(self): execute_command("setIncident", {"simpledebuggerdata": self.SdbgDataMd(self.data[self.currfunc["name"]])}) def SdbgPrintCode(self): execute_command("setIncident", {"simpledebuggercode": self.SdbgCodeMd()}) def SdbgPrintFunc(self, funclist: str): md = "" funcs = funclist.split(",") for ff in funcs: fv = ff.split(".") fname = fv[0].strip() if fname in self.data: if len(fv) == 1: md += f"{fname}():\n" md += SdbgDictMarkdown(self.data[fname], "") elif fv[1].strip() in self.data[fname]: md += f"{fname}():\n" vname = fv[1].strip() value = {vname: self.data[fname][vname]} md += SdbgDictMarkdown(value, "") if md != "": execute_command("setIncident", {"simpledebuggerdata": md}) def SdbgPrintProfile(self): markdown = "|Function|Count|Average Duration|\n" markdown += "|:---|:---|:---|\n" for key, val in self.profile.items(): markdown += f"|{key}|{val['count']}|{round(val['duration']/val['count'], 3)}|\n" execute_command("setIncident", {"simpledebuggerdata": markdown}) def SdbgProfileEvent(self, frame: FrameType, event: str): if event == "call": if frame.f_code.co_name not in self.profile: self.profile[frame.f_code.co_name] = {"count": 0, "duration": 0.0, "starts": []} self.profile[frame.f_code.co_name]["starts"].append(time.time()) elif event == "return": start = self.profile[frame.f_code.co_name]["starts"].pop() self.profile[frame.f_code.co_name]["count"] += 1 self.profile[frame.f_code.co_name]["duration"] += time.time() - start def SdbgTraceCall(self, frame: FrameType, event: str) -> str: self.SdbgLog(f"Called > {frame.f_code.co_name}") self.currfunc = {"name": frame.f_code.co_name, "lineno": frame.f_lineno} self.stack.append(self.currfunc) self.data[self.currfunc["name"]] = {} c = f"_{frame.f_lineno}:[0] {self.indent}> {frame.f_code.co_name}(" for i in range(frame.f_code.co_argcount): if i > 0: c += ", " name = frame.f_code.co_varnames[i] c += name c += ")_" self.indent += " " return c def SdbgTraceReturn(self, frame: FrameType, event: str) -> str: self.SdbgLog(f"Return < {frame.f_code.co_name}") self.SdbgPrintFunc(",".join(self.printfunc)) c = f"{frame.f_lineno}:[{frame.f_lineno - self.currfunc['lineno']}] {self.indent}< {frame.f_code.co_name}()" self.indent = self.indent.replace(" ", "", 1) self.stack.pop() if len(self.stack) > 0: self.currfunc = self.stack[-1] return c def SdbgTraceLine(self, frame: FrameType, event: str) -> str: c = f"**{frame.f_lineno}:[{frame.f_lineno - self.currfunc['lineno']}] {self.indent} {frame.f_code.co_name}()**" self.data[self.currfunc["name"]] = frame.f_locals return c def SdbgSetCurrentLineno(self, frame: FrameType, c: str) -> bool: newcode = False if frame.f_lineno not in self.code: self.code[frame.f_lineno] = c self.code = OrderedDict(sorted(self.code.items())) newcode = True else: self.code[frame.f_lineno] = c if self.prevlineno != 0: self.code[self.prevlineno] = self.code[self.prevlineno].lstrip("**").rstrip("**") # noqa: B005 self.prevlineno = frame.f_lineno return newcode def SdbgCommand(self, frame: FrameType, breakpnt: bool) -> bool: if breakpoint is False: return False fields = execute_command("getIncidents", {"id": self.incid})["data"][0]["CustomFields"] cmd = fields["simpledebuggercmd"].split(" ", 1) command = cmd[0].strip() if command == "continue": self.SdbgLog("Continue execution") self.stepmode = False breakpnt = False elif command == "step": self.stepmode = True breakpnt = False elif command == "print": self.SdbgLog(f"Print {cmd[1]}") self.SdbgPrintFunc(cmd[1]) elif command == "stop": self.SdbgLog("Stop debugger and exit") self.SdbgTraceOff() sys.exit() elif command == "break": self.SdbgLog("Setting breakpoints") self.SdbgSetBreakpoint(cmd[1].split(",")) elif command == "nolog": self.logmode = False elif command == "log": self.logmode = True execute_command("setIncident", {"simpledebuggercmd": ""}) return breakpnt def SdbgException(self): self.SdbgLog(f"Exception thrown at line: {self.code[self.prevlineno].replace(' ', '')}, printing local variables") self.SdbgPrintLocals() self.SdbgTraceOff() def SdbgBreak(self, frame: FrameType, event: str) -> bool: if frame.f_lineno in self.linebreak: self.SdbgLog(f"Breakpoint at line > {frame.f_lineno}") return True if event == "call" and frame.f_code.co_name in self.funcbreak: self.SdbgLog(f"Breakpoint at function > {frame.f_code.co_name}") return True return bool(self.stepmode) def SdbgExclude(self, frame: FrameType, event: str) -> bool: if frame.f_code.co_name in self.silent and event == "line": return True if frame.f_code.co_name in self.exclude: return True if frame.f_code.co_name.startswith("_"): return True if frame.f_code.co_name.startswith("<"): return True return bool(frame.f_code.co_name.startswith("Sdbg")) def SdbgTrace(self, frame: FrameType, event: str, _arg: Any): if event == "exception": self.SdbgException() return None if self.SdbgExclude(frame, event): if event == "call": self.excdepth += 1 elif event == "return": self.excdepth -= 1 return self.SdbgTrace if self.excdepth > 0: return self.SdbgTrace if breakpnt := self.SdbgBreak(frame, event): self.SdbgPrintLocals() count = 0 while breakpnt: breakpnt = self.SdbgCommand(frame, breakpnt) if not breakpnt: break time.sleep(1.0) count += 1 if count > 300: self.SdbgLog("Timeout waiting for continue command, abandoning breakpoint") break elif self.lastcheck == 20: self.SdbgCommand(frame, breakpnt) self.lastcheck = 0 else: self.lastcheck += 1 c = f"_{frame.f_lineno}: {self.indent}{event} {frame.f_code.co_name}" if event == "call": c = self.SdbgTraceCall(frame, event) elif event == "line": c = self.SdbgTraceLine(frame, event) elif event == "return": c = self.SdbgTraceReturn(frame, event) newcode = self.SdbgSetCurrentLineno(frame, c) if self.profmode: self.SdbgProfileEvent(frame, event) if newcode or not self.quietmode: self.SdbgPrintCode() if frame.f_code.co_name == "main" and event == "return" and self.profmode: self.SdbgPrintProfile() return self.SdbgTrace
README
SimpleDebugger is a python class definition appended to CommonServerUserPython and dynamically included in every automation by the XSOAR automation execution environment. It is for debugging custom python automations in XSOAR. You can visually trace code execution, set breakpoints, step through the code, display local variables, and profile execution times of python functions.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
Inputs
There are no inputs for this script.
Outputs
There are no outputs for this script.