使用tornado实时输出日志


import tornado
from tornado.web import Application
from tornado.web import RequestHandler
from tornado.websocket import WebSocketHandler
import os
import json

template = '''<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <title>Document</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <script type="text/javascript" charset="utf-8">
     $(function() {
         if (typeof String.prototype.startsWith != 'function') {
             String.prototype.startsWith = function (prefix){
                 return this.slice(0, prefix.length) === prefix;
             };
         }
         var output = $("#stdout-output").html();
         function addToOutput(msg) {
             /* output = $("#output").html() + '<br/>' + msg;*/
             if (msg.startsWith('stderr:')) {
                 if (!$('a[href="#stderr"]').hasClass("alarm-report") && $('ul[role="tablist"] li.active a').attr('href') != '#stderr') {
                     $('a[href="#stderr"]').addClass("alarm-report")
                 }
                 output = $("#stderr-output").html() + msg.replace(/^stderr:/,'');
                 $("#stderr-output").html(output);
                 $('#stderr-output').scrollTop($('#stderr-output')[0].scrollHeight);
             }else {
                 if (!$('a[href="#stdout"]').hasClass("alarm-report") && $('ul[role="tablist"] li.active a').attr('href') != '#stdout') {
                     $('a[href="#stdout"]').addClass("alarm-report")
                 }
                 output = $("#stdout-output").html() + msg.replace(/^stdout:/,'');
                 $("#stdout-output").html(output);
                 $('#stdout-output').scrollTop($('#stdout-output')[0].scrollHeight);
             }
         }
         $('a#clear').click(function() {
             var active_tab = $('ul[role="tablist"] li.active a').attr('href');
             $(active_tab + '-output').html('');
         })
         $('a#load').click(function() {
             var active_tab = $('ul[role="tablist"] li.active a').attr('href');
             ws.send(JSON.stringify({room:'load',msg:active_tab.replace(/^#/,'')}));
         })
        $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
            if ($(e.target).hasClass("alarm-report")) {
                $(e.target).removeClass("alarm-report")
            }
        })
         if ("MozWebSocket" in window) {
             WebSocket = MozWebSocket;
         }
         if (WebSocket) {
             var ws = new WebSocket("ws://%s/show");
             ws.onopen = function() {};
             ws.onmessage = function (evt) {
                 addToOutput(evt.data);
             };
             ws.onclose = function() {};
         } else {
             alert("WebSocket not supported");
         }
     })
    </script>
    <style type="text/css">
     .input-group {
         margin-bottom: 5px;
     }
     .input-group-addon {
         background-color:#337ab7;
         color:#fff;
         border-color:#337ab7;
     }
     li[aria-selected="true"] {
         display:none;
     }
     li[role="presentation"] a{
         border-bottom-left-radius:0;
         border-bottom-right-radius:0;
     }
     .well {
         color:#eee;
         border-top:0;
         border-top-left-radius:0;
         border-top-right-radius:0;
     }
     #stdout-output,#stderr-output {
         background-color:#333;
         height:600px;
         overflow-y:auto;
         padding:10px;
     }
     .alarm-report {
         border:2px solid #333;
         border-bottom:none;
         animation: flash 1s linear infinite;
     }

     @keyframes flash{
         from {
             border-color: #333;
         }
         to {
             border-color: red;
         }
     }
    </style>
  </head>
  <body>
    <div class="row">
      <div class="col-md-offset-2 col-md-8">
        <!-- Nav tabs -->
        <ul class="nav nav-pills nav-justified" role="tablist">
          <li role="presentation" class="active">
            <a href="#stdout" aria-controls="stdout" role="tab" data-toggle="tab">标准输出</a>
          </li>
          <li role="presentation">
            <a href="#stderr" aria-controls="stderr" role="tab" data-toggle="tab">错误输出</a>
          </li>
          <li role="presentation">
            <a href="javascript:void(0);" id="load">载入历史</a>
          </li>
          <li role="presentation">
            <a href="javascript:void(0);" id="clear">清空</a>
          </li>
        </ul>
        <!-- Tab panes -->
        <div class="tab-content">
          <div role="tabpanel" class="tab-pane active" id="stdout">
            <pre contentEditable="false" class="well" id="stdout-output"></pre>
          </div>
          <div role="tabpanel" class="tab-pane" id="stderr">
            <pre contentEditable="false" class="well" id="stderr-output"></pre>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
'''


def create_app():
    handlers = [(r"/", AnsibleHandler), (r'/show', CommandHandler)]
    app = Application(
        handlers=handlers, debug=True, cookie_secret='asdadasdadasdasdasda')
    return app


class cd(object):
    def __init__(self, newPath):
        self.newPath = os.path.expanduser(newPath)

    def __enter__(self):
        self.savedPath = os.getcwd()
        os.chdir(self.newPath)

    def __exit__(self, etype, value, traceback):
        os.chdir(self.savedPath)


class AnsibleHandler(RequestHandler):
    def get(self):
        self.write(template % (self.request.host))


class CommandHandler(WebSocketHandler):
    def open(self):
        print("WebSocket opened")
        LISTENERS.append(self)

    def on_message(self, message):
        message = json.loads(message)
        # self.write_message(u"You said: " + message['msg'])
        if message['room'] == 'load':
            out_file = STDOUT_FILENAME if message[
                'msg'] == 'stdout' else STDERR_FILENAME
            if os.path.getsize(out_file) > 10240000:
                self.write_message('file is too large!')
            else:
                with open(out_file, 'r') as f:
                    f.seek(0, os.SEEK_END)
                    fsize = f.tell()
                    f.seek(max(fsize - 1024, 0), 0)
                    for line in f.readlines()[-800:]:
                        self.write_message('{}:{}'.format(message['msg'],
                                                          line))

    def on_close(self):
        print("WebSocket closed")
        try:
            LISTENERS.remove(self)
        except:
            pass


def tail_file():
    where = stdout_file.tell()
    line = stdout_file.readline()
    if not line:
        stdout_file.seek(where)
    else:
        for element in LISTENERS:
            element.write_message('stdout:{}'.format(line))
    where = stderr_file.tell()
    line = stderr_file.readline()
    if not line:
        stderr_file.seek(where)
    else:
        for element in LISTENERS:
            element.write_message('stderr:{}'.format(line))


if __name__ == '__main__':
    STDOUT_FILENAME = 'logs/tail.log'
    STDERR_FILENAME = 'logs/tail_err.log'
    STDOUT_FILENAME = os.path.abspath(STDOUT_FILENAME)
    STDERR_FILENAME = os.path.abspath(STDERR_FILENAME)
    LISTENERS = []
    stdout_file = open(STDOUT_FILENAME)
    stderr_file = open(STDERR_FILENAME)
    stdout_file.seek(os.path.getsize(STDOUT_FILENAME))
    stderr_file.seek(os.path.getsize(STDERR_FILENAME))

    app = create_app()

    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(8000, '0.0.0.0')

    tailed_callback = tornado.ioloop.PeriodicCallback(tail_file, 5)
    tailed_callback.start()

    io_loop = tornado.ioloop.IOLoop.instance()
    try:
        io_loop.start()
    except SystemExit as KeyboardInterrupt:
        io_loop.stop()
        stdout_file.close()
        stderr_file.close()

There are comments.