2
\$\begingroup\$

I wrote a simple little script, designed to load some log file. Clearing lines and loading updates is done by double clicking on the log line to clear.

<html lang="en">
<head>
    <meta charset="utf-8">
    <title>🌳🐞</title>
    <style>
    pre {
      margin: 0;
    }
    </style>
    <script>
    var chunks = [];
    var last_seen_time;
    var cursor = 0;
    var log_dom;

    var log_file = 'django.log';
    if (location.search && location.search.substr(1).length > 0) {
        log_file = location.search.substr(1);
    }

    function html_entities(str) {
        return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
    }

    function toggle_color(e) {
        if (e.target.id=='logs_go_here') { return; }
        if (!e.target.dataset.clicked) {
            e.target.dataset.clicked = 1;
            e.target.style.color = 'red';
        } else {
            while(e.target.previousSibling) {
                var node = e.target.previousSibling;
                node.parentNode.removeChild(node);
            }
            load_log();
        }
    }

    function load_log() {
        var xhr = new XMLHttpRequest();
        xhr.onload = function(){
            var html = '';

            if (xhr.readyState === 4) {
              if (xhr.status === 200) {
                var mtime = new Date(xhr.getResponseHeader('Last-Modified'));
                var lines = xhr.responseText.substr(cursor);
                lines = lines.split("\n");
                for (var i = 0, len = lines.length; i < len; i++) {
                    if (i == len - 1 && lines[i] == "") {
                        cursor -= 1;
                        break;
                    }
                    cursor += lines[i].length + 1;
                    html += "<pre>" + html_entities(lines[i]) + "</pre>";
                 }
                 var current_nodes = log_dom.children;
                 for (var i = 0, len = current_nodes.length; i < len; i++) {
                     if (current_nodes[i].style.color != 'red') {
                         current_nodes[i].style.color = '#555';
                     }
                 }
                    log_dom.innerHTML += html;
                 document.title = '🌳🐞';
              } else {
                document.title = '😞';
              }
            }
        };
        xhr.open("get", log_file, true);
        xhr.send();
    }

    function refresh() {
        document.title = '🔄';
        load_log();
    }

    document.addEventListener("DOMContentLoaded", function(event) { 
        log_dom = document.getElementById('logs_go_here');
        log_dom.addEventListener("click", toggle_color);
        refresh();
    });
    </script>
</head>
<body>
    <div id="logs_go_here">
    </div>
    <div style="position:fixed;right:0;top:0;width:100px;">
        <span style="font-size: 100px;cursor:pointer;z-index:1000;" onClick="refresh();">🔄</span>
    </div>
</body>

\$\endgroup\$
1
  • \$\begingroup\$ Unrelated to the actual code: Your log files are directly accessible via a URL? Sounds like a major security no-no. \$\endgroup\$
    – Flambino
    Commented May 30, 2015 at 17:29

1 Answer 1

2
\$\begingroup\$

Looking at your code, it appears to be a django log. I'm not sure why you are reading logs with a webpage when you can always read them using the terminal. cat, grep, tailf and a few other commands would be your friends in this case.

As @Flambino mentioned, direct access to the log is a problem. If you are going to pursue this method, you should at least have the server side validate your access. You could use a login system, and if the user isn't logged in, you can simply throw a 403 (Forbidden) as the result of the AJAX. Otherwise, return the log contents.

Now back to the code. First off, you could wrap everything in a closure. That way, nothing is modifiable from the global scope. That prevents naming collisions and all that. At least your code is protected from other code that may reside in your page.

;(function(){

  // all your code here

}());

Next, you can use an AJAX library to abstract out your XHR code. That way, you don't have to deal with all the intricacies of XHR. A good minimum that I use, which is also forward-compatible, is using a Promise and fetch polyfill. Promise is needed for fetch to work. It's just under 20kb when minified. Rather big but keeps you sane.

fetch(url).then(function(response){
  // This is a required intermediate parsing step for fetch
  return response.text();
}).then(function(responseText){
  // Do what needs to be done with the log
}).catch(function(error){
  // Do what needs to be done during error
});

Another suggestion is to use a <textarea> instead of a <div> as your log output. That way, you don't have to deal with formatting, collapsing of tabs, escaping of characters. You can always set it to readonly to prevent editing, and modify scrollTop on update if ever you always want the bottom to be visible.

Sever logs can be huge and will have impact on your fetching operation. This happens especially if your team is using the logger extensively for easy debugging. I suggest you have some server-side logic to paginate your results and not just dump everything into the page.

I noticed your funny little characters for the title bar. While they are fun, not all computers might have those glyphs built in. On my rig, this character (😞) doesn't appear, just a box with a question mark (or just a box on others). You might want to consider being creative in the ascii range of characters and not totally rely on these non-ascii ones. For example, this sequence -\|/- for a rotating bar.

On a side note, it appears as though you're trying to achieve something similar to what Scalyr does - a web-based log monitoring and search.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I'd use a pre instead of trying to make one out of a readonly textarea. Give it an overflow rule, and it'll scroll, too. Besides, pre elements use a monospaced font by default (well, by convention) well-suited for logs, whereas textareas do not. \$\endgroup\$
    – Flambino
    Commented May 30, 2015 at 20:31
  • \$\begingroup\$ @Flambino unless I'm missing something (and I think I do), if I use something like <pre>, HTML will be rendered as HTML. Then one tries to implement something like htmlentities on JS for completeness. On the other hand, if I use <textarea>, I can just pop in value. Something like: jsfiddle.net/eq64p8m6/1 \$\endgroup\$
    – Joseph
    Commented May 30, 2015 at 22:46
  • \$\begingroup\$ You're right. I wasn't considering html content. I'm too used to jQuery's text() and server-side automatic html-escaping, both of which sidestep that issue \$\endgroup\$
    – Flambino
    Commented May 30, 2015 at 22:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.