[Update - Part 2] Logging Flex 2 and AS3 applications with Firebug and ThunderBolt
Posted on June 20, 2007
This is the second part about the latest update of ThunderBolt AS3. ThunderBolt AS3 is an open source logger extension for Flex 2 or Flash ActionScript 3 applications using Firebug within Firefox.
In part 1 I described a way for using ThunderBolt AS3 with the Flex 2 Logging Framework using an own log target called ThunderBoltTarget. This is a handy extension, but it has a disadvantage: The Flex 2 Logging Framework doesn't support outputs of nested objects because the original Flex 2 Log
instance uses an instance of the LogLogger
which dispatches only a message of the logged object typed as String.
For this issue is better to use the ThunderBolt Logger instance directly for logging objects and its nested objects including all properties ;-) . With the latest release I have added a common way for using log levels as well. Check out the the following instructions.
Example
To see this content latest Flash Player Plugin is required.
Screen Shot: Logging to Firebug
Instructions
1) Grab the latest ThunderBolt AS3 package (includes two classes only) from its repository via SVN on Google Code or download it directly. Make sure that you have installed Firebug as well.
2) For logging to Firebug call the ThunderBolt Logger class methods such as info, warn, error, debug. You don't need to create an instance of Logger because all of its public methods and public properties are static.
1 //
2 // import ThunderBolt Logger
3 import org.osflash.thunderbolt.Logger;
4
5 //
6 // log a string as an info message
7 var myString: String = "Lorem ipsum";
8 Logger.info ("A simple string", myString);
9
10 //
11 // Log two objects (or more ;-) ) as an error message
12 var myNumber: int = 5;
13 var myString2: String = "Lorem ipsum";
14 Logger.error ("Two log objects: A number typed as int and a string", myNumber, myString2);
15
16 //
17 // Log an array with a nested object as a warn message
18 var myArray: Array = ["firstValue",{x: 100, y: 200}, "secondValue"];
19 Logger.warn ("An array with a nested object: ", myArray);
20
21 //
22 // Log an object with a nested array as a debug message
23 var myObject: Object = {exampleArray: ["firstValue", "secondValue"], y: 10, exampleString: "Hello", nestedObject: {x: 100, y: 200}};
24 Logger.debug ("An object with a nested object and nested array", o);
25
26 //
27 // Optionally you can hide the time stamp
28 Logger.includeTime = false;
3) That's all - happy logging ;-)
Source
You will find the latest source of the ThunderBolt AS 3 package on Google Code too. It's open source and based on the Mozilla Public License 1.1.
1 /**
2 * Logging Flex and AS3 projects with Firebug using ThunderBolt AS3
3 *
4 * @version 0.9.1
5 * @author Jens Krause [www.websector.de]
6 * @date 06/29/07
7 * @see http://www.websector.de/blog/?s=thunderbolt
8 * @see http://code.google.com/p/flash-thunderbolt/
9 * @source http://flash-thunderbolt.googlecode.com/svn/trunk/as3/
10 *
11 * ***********************
12 * HAPPY LOGGING ;-)
13 * ***********************
14 *
15 */
16
17 package org.osflash.thunderbolt
18 {
19 import flash.external.ExternalInterface;
20 import flash.utils.describeType;
21 import flash.utils.getQualifiedClassName;
22 import flash.utils.getDefinitionByName;
23 import mx.logging.LogEventLevel;
24 import mx.logging.LogEvent;
25
26 /**
27 * Thunderbolts AS3 Logger class
28 */
29 public class Logger
30 {
31 //
32 // Firebug supports 4 log levels only
33 protected static const INFO: String = "info";
34 protected static const WARN: String = "warn";
35 protected static const ERROR: String = "error";
36 protected static const LOG: String = "log";
37
38 protected static const FIELD_SEPERATOR: String = " :: ";
39 protected static const MAX_DEPTH: int = 255;
40 private static var _stopLog: Boolean = false;
41
42 private static var depth: int;
43 private static var logLevel: String;
44
45 public static var includeTime: Boolean = true;
46
47 /**
48 * Logs info messages including objects for calling Firebug
49 *
50 * @param msg log Message
51 * @param logObjects log objects
52 *
53 */
54 public static function info (msg: String = null, ... logObjects): void
55 {
56 Logger.trace(LogEventLevel.INFO, msg, logObjects);
57 }
58
59 /**
60 * Logs warn messages including objects for calling Firebug
61 *
62 * @param msg log Message
63 * @param logObjects log objects
64 *
65 */
66 public static function warn (msg: String = null, ... logObjects): void
67 {
68 Logger.trace(LogEventLevel.WARN, msg, logObjects);
69 }
70
71 /**
72 * Logs error messages including objects for calling Firebug
73 *
74 * @param msg log Message
75 * @param logObjects log objects
76 *
77 */
78 public static function error (msg: String = null, ... logObjects): void
79 {
80 Logger.trace(LogEventLevel.ERROR, msg, logObjects);
81 }
82
83 /**
84 * Logs debug messages messages including objects for calling Firebug
85 *
86 * @param msg log Message
87 * @param logObjects log objects
88 *
89 */
90 public static function debug (msg: String = null, ... logObjects): void
91 {
92 Logger.trace(LogEventLevel.DEBUG, msg, logObjects);
93 }
94
95 /**
96 * Calls Firebugs command line API to write log information
97 *
98 * @param msg log Message
99 * @param logObjects log objects
100 */
101 public static function trace (level: Number = 0, msg: String = null, ... logObjects): void
102 {
103 depth = 0;
104 // get log level
105 logLevel = Logger.getLogLevel(level);
106 // add log level to log messagef
107 var logMsg: String = "[" + logLevel.toUpperCase() + "] ";
108 // add time to log message
109 if (includeTime) logMsg += getCurrentTime();
110 // add message text to log message
111 logMsg += (msg != null && msg.length) ? msg : "";
112 // call Firebug
113 ExternalInterface.call("console." + logLevel, logMsg);
114 // log objects
115 for (var i:uint = 0; i < logObjects.length; i++)
116 {
117 Logger.logObject(logObjects[i]);
118 }
119 }
120
121 /**
122 * Translates Flex log levels to Firebugs log levels
123 *
124 * @param msg
125 * @return level description
126 *
127 */
128 private static function getLogLevel (logLevel: Number): String
129 {
130 var level: String;
131
132 switch (logLevel)
133 {
134 case LogEventLevel.INFO:
135 level = Logger.INFO;
136 break;
137 case LogEventLevel.WARN:
138 level = Logger.WARN;
139 break;
140 case LogEventLevel.ERROR:
141 level = Logger.ERROR;
142 break;
143 // Firebug doesn't support a fatal level
144 // so we use here Firebugs ERROR level when you're using ThunderBoltTarget
145 case LogEventLevel.FATAL:
146 level = Logger.ERROR;
147 break;
148 default:
149 // for LogEventLevel.DEBUG && LogEventLevel.ALL
150 // so we use here Firebugs LOG level when you're using ThunderBoltTarget
151 level = Logger.LOG;
152 }
153
154 return level;
155 }
156
157 /**
158 * Logs nested instances and properties
159 *
160 * @param logObj log object
161 * @param id short description of log object
162 */
163 private static function logObject (logObj: *, id: String = null): void
164 {
165
166
167 if (depth < Logger.MAX_DEPTH)
168 {
169 ++ depth;
170
171 var propID: String = id || "";
172 var description:XML = describeType(logObj);
173 var type: String = description.@name;
174
175 if (primitiveType(type))
176 {
177 var msg: String = (propID.length) ? "[" + type + "] " + propID + " = " + logObj
178 : "[" + type + "] " + logObj;
179
180 ExternalInterface.call("console." + Logger.LOG, msg);
181 }
182 else if (type == "Object")
183 {
184 ExternalInterface.call("console.group", "[Object] " + propID);
185 for (var element: String in logObj)
186 {
187 logObject(logObj[element], element);
188 }
189 ExternalInterface.call("console.groupEnd");
190 }
191 else if (type == "Array")
192 {
193 /* don't create a group on depth 1 when we are using the ... (rest) parameter calling by Logger.trace() ;-) */
194 if (depth > 1) ExternalInterface.call("console.group", "[Array] " + propID);
195 for (var i: int = 0; i < logObj.length; i++)
196 {
197 logObject(logObj[i]);
198 }
199 ExternalInterface.call("console.groupEnd");
200 }
201 else
202 {
203 // log private props as well - thx Rob Herman [http://www.toolsbydesign.com] ;-)
204 var list: XMLList = description..accessor;
205
206 if (list.length())
207 {
208 for each(var item: XML in list)
209 {
210 var propItem: String = item.@name;
211 var typeItem: String = item.@type;
212 var access: String = item.@access;
213
214 // log objects && properties accessing "readwrite" and "readonly" only
215 if (access && access != "writeonly")
216 {
217 //TODO: filter classes
218 // var classReference: Class = getDefinitionByName(typeItem) as Class;
219 var valueItem: * = logObj[propItem];
220 logObject(valueItem, propItem);
221 }
222 }
223 }
224 else
225 {
226 logObject(logObj, type);
227 }
228 }
229
230 }
231 else
232 {
233 // call one stop message only ;-)
234 if (!_stopLog)
235 {
236 ExternalInterface.call("console." + Logger.WARN, "STOP LOGGING: More than " + depth + " nested objects or properties.");
237 _stopLog = true;
238 }
239 }
240 }
241
242 /**
243 * Checking for primitive types
244 *
245 * @param type type of object
246 * @return isPrimitiveType isPrimitiveType
247 *
248 */
249 private static function primitiveType (type: String): Boolean
250 {
251 var isPrimitiveType: Boolean;
252
253 switch (type)
254 {
255 case "Boolean":
256 case "void":
257 case "int":
258 case "uint":
259 case "Number":
260 case "String":
261 case "undefined":
262 case "null":
263 isPrimitiveType = true;
264 break;
265 default:
266 isPrimitiveType = false;
267 }
268
269 return isPrimitiveType;
270 }
271
272 /**
273 * Creates a valid time value
274 * @param number Hour, minute or second
275 * @return string A valid hour, minute or second
276 */
277
278 private static function getCurrentTime ():String
279 {
280 var currentDate: Date = new Date();
281
282 var currentTime: String = "time "
283 + timeToValidString(currentDate.getHours())
284 + ":"
285 + timeToValidString(currentDate.getHours())
286 + ":"
287 + timeToValidString(currentDate.getMinutes())
288 + ":"
289 + timeToValidString(currentDate.getSeconds())
290 + "."
291 + timeToValidString(currentDate.getMilliseconds()) + FIELD_SEPERATOR;
292 return currentTime;
293 }
294
295 /**
296 * Creates a valid time value
297 * @param number Hour, minute or second
298 * @return string A valid hour, minute or second
299 */
300
301 private static function timeToValidString(timeValue: Number):String
302 {
303 return timeValue > 9 ? timeValue.toString() : "0" + timeValue.toString();
304 }
305
306
307 }
308 }