Маленький дебаггер для flex

December 22, 2007

Утилита “smalldebugger” служит для отладки flash/flex приложений. Состоит из двух частей: сервер и клиент. Коммуникация между этими двумя частями идет с помощью LocalConnection.

Клиент представляет собой небольшой файл blzdebugger.as, подключаемый к вашему проекту. В файле blzdebugger.as находится одноименный класс blzdebugger реализующий логику сбора информации и отправку ее на сервер. Пример использования приведен ниже:
  1. protected function foo1 ():void{
  2.   foo2 ();
  3. }
  4.  
  5. protected function foo2 ():void{
  6.   foo3 ();
  7. }
  8.  
  9. protected function foo3 ():void{
  10.   var blz2 : blzdebugger = new blzdebugger ('def connect 1'); 
  11.   blz2.warn ("X привет вася");
  12.   blz2.error ("Y пока петя");			
  13.   blz2.fatal ('Z дети пришли в лес');
  14.   blz2.info("W bug or not to bug");
  15.   // вызов следующей функции в иерархии 
  16.   foo4 ();
  17. }
  18.  
  19. protected function foo4 ():void{
  20.   var blz : blzdebugger = new blzdebugger ('def connect 0'); 
  21.   blz.warn ("привет вася");
  22.   blz.error ("пока петя");
  23.   blz.fatal ('дети пришли в лес');
  24.   blz.info("bug or not to bug");
  25. }
  26.  
  27. // конструктор некоторого класса, нуждающийся в отладке, 
  28. // выполняет запуск цепочки вызовов функций foo1 – foo2 – foo3 – foo4
  29. public function ScrollerTest (){
  30. // возможно включать и отключать подсистему отправки сообщений 
  31. // В составе класса blzdebugger есть статическая булева переменная 
  32. // IF_DEBUGGER_ACTIVE, изначально данная переменная равна true
  33.   foo1 ();
  34. //Для отключения подсистемы присвойте этой переменной значение false
  35.   blzdebugger.IF_DEBUGGER_ACTIVE = false;
  36.   foo1 ();
  37. }
Методы класса warn, error, fatal, info, debug служат для отправки сообщений заданного типа серверу.

Предупреждение: Автор целенаправленно отказался от поддержки в клиентской части рекомендуемых adobe интерфейсов ILogger, ILoggingTarget из пакета mx.logging. Дело в том, что данный пакет доступен только для flex-основанных проектов, а для flash8/9 такой поддержки нет, что сузило бы сферу применения.

Серверная часть утилиты “smalldebugger” представляет собой flex-приложение. После своего запуска она создает подключение LocalConnection и слушает сообщения от клиентов. Если создать подключение не возможно, например, вы по ошибке запустили два экземпляра данной утилиты, то будет выведено сообщение об ошибке.



После успешного запуска вы видите следующее окно утилиты:



Окно разделено на три части:

- Вверху расположена панель управления. Она состоит из падающего списка, в котором перечислены имена полученных сессий.

Выбор сессии определяется при создании клиенсткой части logger-а:
  1. // имя первой сессии будет def connect 0
  2.    var blz : blzdebugger = new blzdebugger ('def connect 0'); 
  3.    // имя второй сессии будет def connect 1
  4.    var blz2 : blzdebugger = new blzdebugger ('def connect 1');
Все полученные сесии выводятся в падающем списке:



Кнопка “clear log” приводит к очистке журнала сообщений. Выводится текстовая надпись со сведениями: количество сессий, количество записей в текущей сессии и количество записей во всех сессиях. Для удобства работы добавлена на панель картинка с изображением буквы B, всякий раз, когда приходит сообщение, данная картинка начинает вращение.

Также на панели управления расположен набор checkbox-ов, управляющих параметрами фильтрации сообщений. Для каждого из типов сообщений указывается то, сколько сообщений заданного типа было “поймано”. Убирая отметки, вы управляете тем, какие сообщения будут отображены в расположенной в центральной части экрана сетке.

- Посередине “сетка” с перечислением полученных сообщений. Для каждого сообщения выводится его порядковый номер (#); тип (kind); время, прошедшее от момента получения первого сообщения (в текущей сессии) до момента получения данного сообщения(delta); собственно текст сообщения (message); имя функции, которая выбросила данное сообщение (function); имя класса, выбросившего данное сообщение (class). Стандартные возможности: сортировка отображаемой информации в сетке при клике по заголовку столбца.

- Внизу расположена панель с указанием подробных характеристик сообщения. В ней указываются все свойства видимые в сетке, но, кроме того, выводится в списке иерархия вызовов (stacktrace), приведших к генерации данного сообщения.



Также на нижней панели можно найти кнопки для запуска следующих функций:



1-ая кнопка приводит к тому, что область, занимаемая панелью свойств, становится меньше, чтобы уместить на экране большее количество сообщений в “сетке”.



2-ая кнопка служит для того, чтобы скопировать в буфер обмена код в виде html-таблицы с перечислением всего того, что вы видите на экране. Затем вы можете вставить этот текст внутрь excel, например, так:



3-я кнопка служит для того, чтобы выполнить копирование содержимого сетки (списка сообщений) внутрь сервера mysql. Здесь надо сказать сначала о настройках подключения к mysql-серверу. Здесь же на панели свойств располагается форма для ввода параметров подключения, вы должны указать имя сервера, если сервер mysql работает на не стандартном порту (не на 3306), то имя сервера должно включать в себя имя:порт, например, localhost:3307. Также вам нужно указать имя пользователя и пароль для подключения. Следующий шаг выбрать базу данных, в которой находится таблица специального вида. Именно в эту таблицу будут помещаться полученные сервером отладки записи. Если вы точно не помните имя базы, то, используя кнопку “list dbs”, вы заставите программу подключится к серверу и получить список всех баз данных, имена этих баз будут помещены в падающий список, хотя вы можете ввести в данное поле название базы и без помощи мастера.



В выбранной базе вы должны указать имя таблицы. В том случае если вы опять забыли имя таблицы, то используйте кнопку подсказки “list tables”, что приведет к тому, что в падающем списке появятся имена всех таблиц в текущей базе данных, опять таки, вы можете явно ввести имя таблицы в текстовое поле без подсказки.

Для создания таблицы журнала используется 4-ая кнопка. Если ее нажать то, будет автоматически создана таблица специальной структуры.



Будьте внимательны, если вы выбрали в списке “table” имя некоторой существующей таблицы, то она будет автоматически удалена.

Указав все параметры подключения к серверу, вы можете включить режим автоматического перенаправления всех полученных сообщений в таблицу-журнал. Используйте для этого checkbox “auto redirect”. Или же вы можете выполнять копирование записей по требованию с помощью 3-ей кнопки.

Для удобства работы вы можете сохранить введенные параметры подключения к серверу с помощью SharedObject, либо прочитать из SharedObject эти свойства. Используйте соответственно кнопки номер 5 или 6.



Последняя 6-ая кнопка просто выводит окно сообщения об авторе данной программке. Ведь страна должна знать своих героев.


  1. package {
  2. 	import flash.net.LocalConnection;
  3. 	import flash.utils.describeType;
  4.  
  5. 	/**
  6. 	 Класс клиент системы отладки, служит для отправки сообщений серверу
  7. 	*/
  8. 	public class blzdebugger {
  9.  
  10. 		/**
  11. 		Переменная управляющая тем активен или нет дебаг
  12. 		*/
  13. 		public static var IF_DEBUGGER_ACTIVE : Boolean = true;
  14.  
  15. 		/**
  16. 		 * Избирательный запрет на debug из определенных классов
  17. 		 * Есть два варианта запретов на уровне конкретного экземпляра объекта отладчика
  18. 		 * или на глобальном уровне для всех отладчиков
  19. 		 */ 
  20. 		protected var obj_disallowedClasses : Array = new Array(); 
  21. 		protected static var stat_disallowedClasses : Array = new Array(); 		
  22.  
  23. 		/**
  24. 		 Переменная в которой хранится строка с именем сессии
  25. 		*/
  26. 		private var sessionname : String  = 'default';
  27. 		/**
  28. 		 Объект LocalConnection служащий для обмена сообщениями с сервером
  29. 		*/
  30. 		private var sessionpipe : LocalConnection = new LocalConnection ();
  31.  
  32. 		/**
  33. 		 * Все объекты логгеры регистрируются внутри специального массива
  34. 		 */ 
  35. 		private static var logger_registry : Array = new Array ();
  36. 		/**
  37. 		 * Функция по заданному имени логгера ищет его в списке зарегистрированных объектов
  38. 		 * в случае если такого логгера нет,то возвращается null
  39. 		 * @param loggerName Имя логгера который надо найти
  40. 		 */ 
  41. 		public static function getExistsLogger (loggerName : String):blzdebugger{
  42. 			for (var i : int  = 0; i < logger_registry.length; i++){
  43. 				var blz : blzdebugger = logger_registry[i] as blzdebugger;
  44. 				if (blz.sessionname == loggerName)
  45. 					return blz;
  46. 			}
  47. 			return null;
  48. 		}
  49. 		/**
  50. 		 * Функция по заданному имени логгера ищет его в списке зарегистрированных объектов
  51. 		 * в случае если такого логгера нет, то он создается и возвращается 
  52. 		 * @param loggerName Имя логгера который надо найти
  53. 		 */ 
  54. 		public static function getExistsOrNewLogger (loggerName : String):blzdebugger{
  55. 			var blz  : blzdebugger   = getExistsLogger(loggerName);
  56. 			if (blz != null) return blz;
  57. 			blz = new blzdebugger(loggerName);
  58. 			logger_registry.push(blz);
  59. 			return blz;
  60. 		}
  61.  
  62. 		/**
  63. 		Конструктор класса клиента, получает в качестве параметра строку с именем сессии отладки
  64. 		*/
  65. 		public function blzdebugger(sessionname: String) {
  66. 			this.sessionname = sessionname;
  67. 		}
  68.  
  69. 		public function disableDebugFor (className : String):void{
  70. 			if (! obj_disallowedClasses.indexOf(className))
  71. 				obj_disallowedClasses.push(className);
  72. 		}
  73. 		public function enableDebugFor (className : String):void{
  74. 			var pos : int = 0;
  75. 			pos = obj_disallowedClasses.indexOf(className);
  76. 			if (pos >= 0)
  77. 				obj_disallowedClasses.splice(pos, 1);
  78. 		}
  79.  
  80. 		public static function disableGlobalDebugFor (className : String):void{
  81. 			if (! stat_disallowedClasses.indexOf(className))
  82. 				stat_disallowedClasses.push(className);
  83. 		}
  84. 		public static function enableGlobalDebugFor (className : String):void{
  85. 			var pos : int = 0;
  86. 			pos  = stat_disallowedClasses.indexOf(className);
  87. 			if (pos >= 0)
  88. 				stat_disallowedClasses.splice(pos, 1);
  89. 		}
  90.  
  91.  
  92.  
  93. 		protected function startsWith (what : String, by : String):Boolean{
  94. 			if (by == "") return true;
  95. 			return what.substr(0, by.length) == by;
  96. 		}
  97.  
  98. 		protected function isDisallowedClass (className : String):Boolean{
  99. 			return _isDisallowedClass  (className, obj_disallowedClasses) ||
  100. 				   _isDisallowedClass  (className , stat_disallowedClasses)
  101. 			;
  102. 		}
  103.  
  104.  
  105. 		protected function _isDisallowedClass (className : String, disallowedClasses : Array):Boolean{
  106. 			//pack1.pack2::class1
  107. 			//class2
  108. 			//*
  109. 			//class*
  110. 			//pack1.pack2.*
  111. 			for (var i : int = 0; i < disallowedClasses.length; i++){
  112. 				var cla : String = disallowedClasses[i] as String;
  113. 				var pos_star : int  = cla.indexOf("*");
  114. 				if (pos_star != -1)
  115. 					cla = cla.substring(0, pos_star);
  116. 				if (startsWith (className, cla) ) return true;
  117.  
  118. 			}
  119. 			return false;
  120. 		}
  121.  
  122.  
  123. 		/**
  124. 		Родовая функция служит для сбора информации об "происшествии" и отправляет ее серверу
  125. 		*/
  126. 		protected function unified_send(kind: String, msg : String, rest : Array):void {
  127. 			try {
  128. 				throw new Error("nullexception");
  129. 			} catch (e:Error) {
  130. 				var stacktraces_clean: Array = [];
  131. 				var stacktraces : String = e.getStackTrace ();
  132.  
  133. 				var arrS : Array = stacktraces.split ("\n");
  134.  
  135. 				arrS.splice(0,3);
  136.  
  137. 				var fst_class : String, fst_function: String;
  138. 				for (var i:int = 0; i < arrS.length; i++) {
  139. 					var tmps : String = arrS[i];
  140. 					var regexpi : Array = tmps.match(/^\s*at\s*([^ ].*)$/);
  141. 					if (regexpi != null) {
  142. 						tmps = regexpi [1];
  143. 						if (i == 0) {
  144. 							var pos_slash:int = tmps.indexOf ('/');
  145. 							var pos_cash:int = tmps.indexOf ('$');
  146. 							if (pos_slash != -1) {
  147. 								fst_function =  tmps.substr(pos_slash+1);
  148. 								fst_class =  tmps.substr(0, pos_slash);
  149. 							}
  150. 							if (pos_cash != -1) {
  151. 								fst_function =  tmps.substr(pos_cash+1);
  152. 								fst_class =  tmps.substr(0, pos_cash);
  153. 							}
  154. 						}
  155. 						stacktraces_clean.push(tmps);
  156.  
  157. 					}
  158. 				}
  159. 				if (! isDisallowedClass (fst_class)){
  160. 					var rest2 : XML = <param name="variables" />;
  161. 					for (var j : int = 0; j < rest.length; j++){
  162. 						var x : XML = recBuild(rest[j]);
  163. 						rest2.appendChild(x);
  164. 					}
  165. 					sessionpipe.send(
  166. "blz-debugserver", "debug", sessionname, kind, msg, fst_function, fst_class, stacktraces_clean, rest2);
  167. 				}
  168. 			}
  169. 		}
  170.  
  171. 		/**
  172. 		 * Функция выполняющая форматирование объекта (все его свойства) в виде xml-документа
  173. 		 * @param obj Объект подлежащий форматированию
  174. 		 */ 
  175. 		protected function recBuild (obj : Object):XML{
  176. 			if (obj == null) return <param name="null" />;
  177. 			var x : XML = _recBuild (obj, []);
  178. 			if (x.localName() == '__tostring__'){
  179. 				var x2 : XML = <param name="__scalar__" />;
  180. 				x2.appendChild(x.children().toString());
  181. 				x = x2;
  182. 			}
  183. 			return x;
  184. 		}
  185.  
  186. 		/**
  187. 		 * Вспомогательнаф функция выполняюща форматирование объекта в виде xml
  188. 		 * @param obj Объект подлежащий форматированию
  189. 		 * @param history История объектов. Нужна для того чтобы избежать рекурсии в ходе обработки
  190. 		 */ 
  191. 		protected function _recBuild (obj : Object, history : Array):XML{
  192. 			if (obj == null) return null;
  193. 			if (history.indexOf(obj) >= 0) return <prop name="#was#" />;
  194. 			var x : XML = <prop />;
  195. 			var cnt_props:int = 0;
  196. 			for (var okey : Object in obj){
  197. 				var ovalue : Object = obj[okey];
  198. 				var xprop : XML = <prop />;
  199. 				xprop.@name = ""+okey;
  200. 				history.push(obj);
  201. 				var tmp : XML = _recBuild(ovalue, history);
  202. 				if (tmp == null){
  203. 					xprop.appendChild("null");	
  204. 				}
  205. 				else{
  206. 				if (tmp.localName() == "__tostring__")
  207. 					xprop.appendChild(tmp.children().toString());
  208. 				else
  209. 					xprop.appendChild(tmp);
  210. 				}
  211. 				history.pop();
  212. 				x.appendChild(xprop);
  213. 				cnt_props++;
  214. 			}
  215. 			if (cnt_props == 0){
  216. 				x = <__tostring__ />;
  217. 				x.appendChild(""+obj);
  218. 				return x;
  219. 			}
  220.  
  221. 			return x;
  222. 		}
  223.  
  224.  
  225.  
  226. 		/**
  227. 		Функция отправляющая сообщение типа warn, для своей работы перевызывает функцию unified_send
  228. 		*/
  229. 		public function warn(msg:String, ...args: Array):void {
  230. 			if (IF_DEBUGGER_ACTIVE)	unified_send('warn', msg, args);
  231. 		}
  232.  
  233. 		/**
  234. 		Функция отправляющая сообщение типа fatal, для своей работы перевызывает функцию unified_send
  235. 		*/
  236. 		public function fatal(msg:String, ...args: Array):void {
  237. 			if (IF_DEBUGGER_ACTIVE)	unified_send('fatal', msg, args);
  238. 		}
  239.  
  240. 		/**
  241. 		Функция отправляющая сообщение типа info, для своей работы перевызывает функцию unified_send
  242. 		*/
  243. 		public function info(msg:String, ...args: Array):void {
  244. 			if (IF_DEBUGGER_ACTIVE) unified_send('info', msg, args);
  245. 		}
  246.  
  247. 		/**
  248. 		Функция отправляющая сообщение типа debug, для своей работы перевызывает функцию unified_send
  249. 		*/
  250. 		public function debug(msg:String, ...args: Array):void {
  251. 			if (IF_DEBUGGER_ACTIVE)	unified_send('debug', msg, args);
  252. 		}
  253.  
  254. 		/**
  255. 		Функция отправляющая сообщение типа error, для своей работы перевызывает функцию unified_send
  256. 		*/
  257. 		public function error(msg:String, ...args: Array):void {
  258. 			if (IF_DEBUGGER_ACTIVE)	unified_send('error', msg, args);
  259. 		}
  260.  
  261. 		//----------------------------------- набор статических функций --------------------
  262. 		/**
  263. 		Функция отправляющая сообщение типа warn, для своей работы перевызывает функцию unified_send
  264. 		*/
  265. 		public static function warn(sessionName : String, msg:String, ...args : Array):void {
  266. 			if (IF_DEBUGGER_ACTIVE)	getExistsOrNewLogger(sessionName).unified_send('warn', msg, args);
  267. 		}
  268.  
  269. 		/**
  270. 		Функция отправляющая сообщение типа fatal, для своей работы перевызывает функцию unified_send
  271. 		*/
  272. 		public static function fatal(sessionName : String,msg:String, ...args: Array):void {
  273. 			if (IF_DEBUGGER_ACTIVE)	getExistsOrNewLogger(sessionName).unified_send('fatal', msg, args);
  274. 		}
  275.  
  276. 		/**
  277. 		Функция отправляющая сообщение типа info, для своей работы перевызывает функцию unified_send
  278. 		*/
  279. 		public static function info(sessionName : String,msg:String, ...args: Array):void {
  280. 			if (IF_DEBUGGER_ACTIVE) getExistsOrNewLogger(sessionName).unified_send('info', msg, args);
  281. 		}
  282.  
  283. 		/**
  284. 		Функция отправляющая сообщение типа debug, для своей работы перевызывает функцию unified_send
  285. 		*/
  286. 		public static function debug(sessionName : String,msg:String, ...args: Array):void {
  287. 			if (IF_DEBUGGER_ACTIVE)	getExistsOrNewLogger(sessionName).unified_send('debug', msg, args);
  288. 		}
  289.  
  290. 		/**
  291. 		Функция отправляющая сообщение типа error, для своей работы перевызывает функцию unified_send
  292. 		*/
  293. 		public static function error(sessionName : String,msg:String, ...args: Array):void {
  294. 			if (IF_DEBUGGER_ACTIVE)	getExistsOrNewLogger(sessionName).unified_send('error', msg, args);
  295. 		}
  296.  
  297. 	}// end of CLASS
  298. }// end of PACKAGE

Улучшения и дополнения от 22.12.2007



Незначительно изменен интерфейс серверной части. В верхней панели выводится также текущий Security.sandboxType. Включена поддержка следующих доменов для соединения с сервером:
  1. lc.allowDomain("*", "localhost");
  2.  lc.allowInsecureDomain("*", "localhost");
Также была добавлена кнопка "Clear All" очищающая журналы для всех сессий, а не только для текущей как работала ранее кнопка "Clear Log".



Основные изменения в клиентской части. Здесь добавлена возможность фильтрации сообщений которые отправляются на сервер на основании шаблонов имен классов (ранее можно было только включать и отключать отправку всех сообщений не избирательно). Теперь вы может для каждого из объектов логгеров по-отдельности либо для всех вместе запретить отправлять сообщения из некоторых классов. При этом возможно использовать шаблоны:
  1. // запрет на отправку сообщений из классов начинающихся на слово Hessian и находящихся в пакете logic
  2.  // этот запрет будет действовать для всех объектов-логгеров
  3.  blzdebugger.disableGlobalDebugFor("logic::Hessian*");
  4.  blzdebugger.disableGlobalDebugFor("user_components.logic::*");				
  5.  // запреты можно давать и для конкретных объектов-логгеров
  6.  var loc : blzdebugger = new blzdebugger("session_1");
  7.  loc.disableDebugFor("logic.datasets*");
  8.  loc.disableDebugFor("foo*"); 
  9.  // запреты можно и отменить для этого используются функции
  10.  loc.enableDebugFor("foo*");
  11.  blzdebugger.enableGlobalDebugFor("bar.tar::*");
Следующее крупное улучшение - возможность отправлять не только текстовое сообщение на сервер, но и произвольные переменные, для этого функции warn, info, debug, fatal и другие были расширены так чтобы могли получать кроме текста сообщения произвольный список переменных:
  1. public function info(msg:String, ...args: Array):void {
  2.    ....
  3.  }
Так что при вызове функции вы можете указать что хотите отправить еще и следующие переменные:
  1. protected  var logger : blzdebugger = new blzdebugger ("Analytics");
  2.   ....
  3.   var chartdata : Array = data['result']['chartdata'];
  4.   var kind : String = data['result']['kind'];
  5.   // обратите на передачу двух переменных внутрь функции info
  6.   logger.info("got server response", kind, chartdata);
Переменные будут преобразованы в xml-документ и отправлены на сервер. Для того чтобы отобразить эту информацию были сделаны небольшие правки интерфейса: панель свойств экспорта сообщений в mysql-сервер была перемещена, а вместо ее отображается дерево с содержимым переменных:





И ссылка на исходники проекта (там же находится скомпилированная версия файла):



https://github.com/study-and-dev-site-attachments/all-in-one/tree/master/flash/smalldebugger

(все исходники откорректированы по состоянию на 22.12.2007)