Thursday, May 21, 2009

Silverlight Logging Extension Method

Most of you will know that a Silverlight application runs in the Browser and Console.WriteLine() doesn’t work. During the development process you could use System.Diagnostics.Debug.WriteLine() to write messages to Visual Studio’s Output window. But what if an application is deployed and no Visual Studio is nearby and you just want simple Console output and not a logging framework like Clog, where a web service is involved? Or don’t want to use Visual Studio’s Output window?

The answer is simple if you are using Firefox with the Firebug add-on or Internet Explorer 8: Use the console.log mechanism and write your debug output to the Browser’s debug console.

Here's a simple extension method, which logs an object to the console.log:

public static void Log(this object obj)
{
   HtmlWindow window = HtmlPage.Window;
   var isConsoleAvailable = (bool)window.Eval("typeof(console) != 'undefined' && typeof(console.log) != 'undefined'");
   if (isConsoleAvailable)
   {
      var console = (window.Eval("console.log") as ScriptObject);
      if (console != null)
      {
         console.InvokeSelf(obj);
      }
   }
}

How to you use it:
"Hello Universe!".Log();
int a = 42;
a.Log();
String.Format("The answer to all questions is {0}.", a).Log();

And that's how the
debug output looks within Firebug:













16 comments:

  1. Nice tip! I might just use this. Thanks.

    ReplyDelete
  2. Thanks for this tip! It really helped me out.

    I modified the code slightly to check for the existence of console and console.log. That way it will quietly fail (i.e., it won't throw an InvalidOperationException on Eval) if it's running in IE8 with the "Developer Tools" window closed or in earlier versions of IE.

    HtmlWindow window = HtmlPage.Window;
    object hasConsole = window.Eval("typeof(console) != 'undefined' && typeof(console.log) != 'undefined'");
    if (hasConsole is bool && (bool)hasConsole)
    {
    ScriptObject consoleLogFunction = window.Eval("console.log") as ScriptObject;
    if (consoleLogFunction != null)
    {
    consoleLogFunction.InvokeSelf(entry);
    }
    }

    ReplyDelete
  3. @Bill I totally missed that. Thanks for your comment! I updated the code snippet.

    ReplyDelete
  4. Where can you get System.Windows.Forms for silverlight?

    ReplyDelete
  5. HtmlPage.Window fails in none UI thread, so I call the above code from System.Windows.Deployment.Current.Dispatcher.BeginInvoke().

    ReplyDelete
  6. It also works for Google Chrome if you install FireBug lite for chrome.

    ReplyDelete
  7. It fails unless you call the Log method form the UI thread. Any idea on how to retrieve the Dispatcher of the current Visual element?

    ReplyDelete
  8. Hi Jarana,

    sure, it's the Application.Current.RootVisual.Dispatcher

    Use it like this:
    Application.Current.RootVisual.Dispatcher.BeginInvoke(() => "Log from another thread in the UI thread!".Log());

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. Thanks a lot, that's a great info.

    Just to note that in case you are using Google Chrome without FireBug lite for chrome (which I just installed) the code is likely to crash during the consoleLogFunction.InvokeSelf, due to a System.InvalidOperationException exception.

    You could do the Try/Catch trick to surround the call, unless there is a better test you could perform instead.
    Vincent Thavonekham.

    ReplyDelete
  12. I just wanted to share my workaround for the Chrome exception when Invoking console.log.

    My solution creates a user defined javascript function containing the call to console.log and passing the message as a parameter. I then call eval.call (window.execScript in IE) to load the function into global scope and then I can invoke my custom function instead of console.log directly. This works perfectly in Chrome.

    public void Log(LogLevel loglevel, string message)
    {
    if (loglevel <= level)
    {
    HtmlWindow window = HtmlPage.Window;

    //only log if a console is available
    var isConsoleAvailable = (bool)window.Eval("typeof(console) != 'undefined' && typeof(console.log) != 'undefined'");

    if (isConsoleAvailable)
    {
    var createLogFunction = (bool)window.Eval("typeof(ssplog) == 'undefined'");
    if (createLogFunction)
    {
    // Load the logging function into global scope:
    string logFunction = @"function ssplog(msg) { console.log(msg); }";
    string code = string.Format(@"if(window.execScript) {{ window.execScript('{0}'); }} else {{ eval.call(null, '{0}'); }}", logFunction);
    window.Eval(code);
    }

    // Prepare the message
    DateTime dateTime = DateTime.Now;
    string output = string.Format("{0} - {1} - {2}", dateTime.ToString("u"), loglevel.ToString(), message);

    // Invoke the logging function:
    var logger = window.Eval("ssplog") as ScriptObject;
    logger.InvokeSelf(output);
    }
    }
    }

    ReplyDelete
    Replies
    1. Thanks for this, it worked for chrome and IE

      Delete
    2. What is it LogLevel ?

      IMHO, I prefer if (!isConsoleAvailable) return;

      Delete
  13. Really nice stuff. I am trying to use your logger but it's working on IE9 but not on Chrome... By any chance, do you have a workaround to make it work on Chrome too? or at least make it fail silently on Chrome?

    Thanks a lot,
    Albino

    ReplyDelete
  14. works great! thanks! I tested in IE and Chrome both worked fine!

    ReplyDelete