By Bogomil Shopov - Bogo

Implement Client-side Bug Reporting with UserSnap

By Bogomil Shopov - Bogo

Imagine the following scenario: your clients visit the website (let’s imagine this one) and see anything but the expected result. The normal reaction is to call you (at the most inappropriate time) and ask you to fix it ASAP, because they’re losing money.

How can we help the user report the bug as accurately as possible?

The bug

Let’s have a really simple JSON request and an error to be able to reproduce our case:

$json_data = '{"value":1,"apples":2,"name":3,"oranges":4,"last one":5}';

//we will simulate the json data, but imagine that this is the normal data exchanged daily between your client’s website and a 3rd party API

$ch = curl_init('');                                                                 
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");                                                                
curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data);                                  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);                                    curl_setopt($ch, CURLOPT_HTTPHEADER, array(                                                'Content-Type: application/json',                      
        'Content-Length: ' . strlen($json_data)));
//the normal CURL request, nothing strange here:
$result = curl_exec($ch);

//receiving the data back
$f_data =  json_decode($result,true);

//showing a greeting with the output
echo  “Welcome”. $f_data['username'];

If you visit the test website now, you will notice that there’s a problem.

The question is – how can the client report it faster with all the data you need to fight the bug:

  • Json data,
  • Server-side Javascript and XmlHttpsRequest errors,
  • Some core PHP errors
  • …and meta data.

An interesting solution for gathering this information is UserSnap. A widget that lets your users mark up a screenshot of the site they’re on and send you everything that’s in the JavaScript console. PHP errors don’t end up there, though, do they? Let’s make them. First, we’ll gather the server side errors.

Gathering Errors

Let’s add some more code to the example in order to see how we can collect the data we need. Introducing a really simple class to help us:

class SendToUsersnap
    var $m;
    //Save all logs in an array. You can use an even more elegant approach but right now I need it to be simple for the sake of this tutorial.
    function log ( $data, $type ) {
        if( is_array( $data ) || is_object( $data ) ) {
            $this->m[]= "console.".$type."('PHP: ".json_encode($data)."');";
        } else {
            $this->m[] = "console.".$type."('PHP: ".$data."');";
  // Print all logs that have been set from the previous function. Let’s keep it simple.
    function  out (){
         for ($i=0;$i<count($this->m);$i++)
              echo $this->m[$i]."\n";

Now let’s use this class to record all errors and logs we may need.

    $s = new SendToUsersnap;

    $json_data = '{"value":1,"apples":2,"name":3,"oranges":4,"last one":5}';
    $s->log('Initializing the JSON request',"info");
    $ch = curl_init('');             
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
    curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data);                           
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);                         
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(                                 
        'Content-Type: application/json',                           
        'Content-Length: ' . strlen($json_data)));                                                                                                                   
    $result = curl_exec($ch);
    $f_data =  json_decode($result,true);
    echo  'Welcome'. $f_data['usersname'];

Pass it to UserSnap

Let’s add the UserSnap code snippet at the end of our page and see what happens. (You may need an account to use this widget. Usersnap offers free licenses for Open Source projects and a free testing period for commercial ones.

<script type="text/javascript">
  (function() {
  var s = document.createElement("script");
    s.type = "text/javascript";
    s.async = true;
    s.src = '//'+
    var x = document.getElementsByTagName('script')[0];
    x.parentNode.insertBefore(s, x);

 var _usersnapconfig = {
   loadHandler: function() {
    //just print all errors collected from the buffer.
 $s->out(); ?>

Note: You would do this in a view template in a real framework, but as we’re not using a real MVC app here, we’re skipping that part.

The user will see a “report bug” button on the page and will write you a message like “it’s not working, fix it asap”. Generally, this would be useless information, but this time, we get this gorgeous error log attached, too:

A beatiful error log attached to your bug

Now you can see that there is initialization in place:

$s->log('Initializing the JSON request',"info");

You can see the data we will send – the same as usual


And you can see the output. Yes, the problem is there. Obviously there is an auth problem.


You can get even the core PHP errors to help you with the debugging.


Your client will only have to click the button once and you will get everything you need to fight the bug faster:

  1. Errors and Logs (as shown above)
  2. Annotated screenshot – if the problem is more complex than this simple example
  3. Screen size, Browser version, OS and the plugins installed in the browser

Of course you can turn this feature on only when your client needs it. To limit the availability of the widget, add an IP filter or a query param barrier, open a dev.* subdomain, etc.


This is not a script-monitoring tool and will not deliver information automatically when and if the problem appears. This approach will work only if an user or a client reports a bug and you as a developer or QA need more info on how to fight the bug. Imagine it as a remote debugger, but for client-side errors + events and data you send from PHP to JavaScript.

I’d love to answer all of your questions! Leave your feedback below!

  • I’d love to answer all of your questions :)

  • Seems that the PHP error is reported clearly on the JavaScript console. Do you think it’s a safe practice? Or this tool is meant only to be used in a staging or test environment?

    • In this case you can turn the widget on to help your client or to use any other privacy method. When is not needed to pass PHP errors to the JS, you can leave it all the time of course.

      • Thanks for your great answer @bogomilshopovbogo:disqus. Probably we can encrypt the php message to keep it unreadable from the client side.
        Seems a feasible thing to do by looking at your example. What do you think about it?

        • Yes, this is an excellent idea as well. Go ahead and try it and please let me know if it works :)

  • Roy

    I don’t get what Usersnap does here or why it’s necessary. It looks like you’re simply sending logging messages to the console; don’t you already log such messages in a logging file on your server anyway? That way you avoid leaking implementation details on your live site through the console.

    Dubious as to how user-friendly this is anyway. Just asking clients to open up the console is pushing it with some of my clients, but for those that can unless the logging messages are short enough they’ll be truncated so a screenshot of them is virtually useless anyway…

    • Gregor Dorfbauer

      Hi Roy, CTO of Usersnap here.

      I think this post is somewhat unorthodox way of using Usersnap. I don’t believe that sending backend log messages to the frontend is the way to do.

      However, Usersnap is excellent at collecting front-end bugs and messages. Add it to your project and every bug report which comes from it contains a screenshot, browser information and *drumroll* a full console log recording of frontend (that’s the use case which is somehow abused here) JavaScript errors and XHR.

      And by the way – you’ll get the log messages in textual form, not as a screenshot, so nothing will be truncated. Does this make sense to you?

  • Leo Cavalcante

    There are many much, much better ways to handle errors in PHP development. My favorite is to use set_error_handler to catch any exceptions then, log the error (which will point the file and line, besides the error message), e-mail the developer about the problem, then display to the user the error-log-id, something that identifies the log with some kind of message like: “Sorry we have a problem, the developer was already notified about this error, come back soon, blah, blah, blah. Error code/track/blah: #error-log-id”.

    You will have error number, error message, error file and error line number to fight the bug.
    The developer will be automatically notified about the problem.
    The user can send the error-log-id to somebody, or track the solution (can be used in the commit log, “Fix for error-log-id”, for example).
    Nothing “strange” is publicity logged.

    • Gregor Dorfbauer

      Totally agree. Backend error messages and log files should not get public. If we combine your example together with the post, it would be great to have the error code with every bug report.

Get the latest in PHP, once a week, for free.