使用tornado实时输出日志


  1import tornado
  2from tornado.web import Application
  3from tornado.web import RequestHandler
  4from tornado.websocket import WebSocketHandler
  5import os
  6import json
  7
  8template = '''<!doctype html>
  9<html lang="en">
 10  <head>
 11    <meta charset="UTF-8"/>
 12    <title>Document</title>
 13    <meta name="viewport" content="width=device-width, initial-scale=1">
 14    <script src="https://cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
 15    <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
 16    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
 17    <script type="text/javascript" charset="utf-8">
 18     $(function() {
 19         if (typeof String.prototype.startsWith != 'function') {
 20             String.prototype.startsWith = function (prefix){
 21                 return this.slice(0, prefix.length) === prefix;
 22             };
 23         }
 24         var output = $("#stdout-output").html();
 25         function addToOutput(msg) {
 26             /* output = $("#output").html() + '<br/>' + msg;*/
 27             if (msg.startsWith('stderr:')) {
 28                 if (!$('a[href="#stderr"]').hasClass("alarm-report") && $('ul[role="tablist"] li.active a').attr('href') != '#stderr') {
 29                     $('a[href="#stderr"]').addClass("alarm-report")
 30                 }
 31                 output = $("#stderr-output").html() + msg.replace(/^stderr:/,'');
 32                 $("#stderr-output").html(output);
 33                 $('#stderr-output').scrollTop($('#stderr-output')[0].scrollHeight);
 34             }else {
 35                 if (!$('a[href="#stdout"]').hasClass("alarm-report") && $('ul[role="tablist"] li.active a').attr('href') != '#stdout') {
 36                     $('a[href="#stdout"]').addClass("alarm-report")
 37                 }
 38                 output = $("#stdout-output").html() + msg.replace(/^stdout:/,'');
 39                 $("#stdout-output").html(output);
 40                 $('#stdout-output').scrollTop($('#stdout-output')[0].scrollHeight);
 41             }
 42         }
 43         $('a#clear').click(function() {
 44             var active_tab = $('ul[role="tablist"] li.active a').attr('href');
 45             $(active_tab + '-output').html('');
 46         })
 47         $('a#load').click(function() {
 48             var active_tab = $('ul[role="tablist"] li.active a').attr('href');
 49             ws.send(JSON.stringify({room:'load',msg:active_tab.replace(/^#/,'')}));
 50         })
 51        $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
 52            if ($(e.target).hasClass("alarm-report")) {
 53                $(e.target).removeClass("alarm-report")
 54            }
 55        })
 56         if ("MozWebSocket" in window) {
 57             WebSocket = MozWebSocket;
 58         }
 59         if (WebSocket) {
 60             var ws = new WebSocket("ws://%s/show");
 61             ws.onopen = function() {};
 62             ws.onmessage = function (evt) {
 63                 addToOutput(evt.data);
 64             };
 65             ws.onclose = function() {};
 66         } else {
 67             alert("WebSocket not supported");
 68         }
 69     })
 70    </script>
 71    <style type="text/css">
 72     .input-group {
 73         margin-bottom: 5px;
 74     }
 75     .input-group-addon {
 76         background-color:#337ab7;
 77         color:#fff;
 78         border-color:#337ab7;
 79     }
 80     li[aria-selected="true"] {
 81         display:none;
 82     }
 83     li[role="presentation"] a{
 84         border-bottom-left-radius:0;
 85         border-bottom-right-radius:0;
 86     }
 87     .well {
 88         color:#eee;
 89         border-top:0;
 90         border-top-left-radius:0;
 91         border-top-right-radius:0;
 92     }
 93     #stdout-output,#stderr-output {
 94         background-color:#333;
 95         height:600px;
 96         overflow-y:auto;
 97         padding:10px;
 98     }
 99     .alarm-report {
100         border:2px solid #333;
101         border-bottom:none;
102         animation: flash 1s linear infinite;
103     }
104
105     @keyframes flash{
106         from {
107             border-color: #333;
108         }
109         to {
110             border-color: red;
111         }
112     }
113    </style>
114  </head>
115  <body>
116    <div class="row">
117      <div class="col-md-offset-2 col-md-8">
118        <!-- Nav tabs -->
119        <ul class="nav nav-pills nav-justified" role="tablist">
120          <li role="presentation" class="active">
121            <a href="#stdout" aria-controls="stdout" role="tab" data-toggle="tab">标准输出</a>
122          </li>
123          <li role="presentation">
124            <a href="#stderr" aria-controls="stderr" role="tab" data-toggle="tab">错误输出</a>
125          </li>
126          <li role="presentation">
127            <a href="javascript:void(0);" id="load">载入历史</a>
128          </li>
129          <li role="presentation">
130            <a href="javascript:void(0);" id="clear">清空</a>
131          </li>
132        </ul>
133        <!-- Tab panes -->
134        <div class="tab-content">
135          <div role="tabpanel" class="tab-pane active" id="stdout">
136            <pre contentEditable="false" class="well" id="stdout-output"></pre>
137          </div>
138          <div role="tabpanel" class="tab-pane" id="stderr">
139            <pre contentEditable="false" class="well" id="stderr-output"></pre>
140          </div>
141        </div>
142      </div>
143    </div>
144  </body>
145</html>
146'''
147
148
149def create_app():
150    handlers = [(r"/", AnsibleHandler), (r'/show', CommandHandler)]
151    app = Application(
152        handlers=handlers, debug=True, cookie_secret='asdadasdadasdasdasda')
153    return app
154
155
156class cd(object):
157    def __init__(self, newPath):
158        self.newPath = os.path.expanduser(newPath)
159
160    def __enter__(self):
161        self.savedPath = os.getcwd()
162        os.chdir(self.newPath)
163
164    def __exit__(self, etype, value, traceback):
165        os.chdir(self.savedPath)
166
167
168class AnsibleHandler(RequestHandler):
169    def get(self):
170        self.write(template % (self.request.host))
171
172
173class CommandHandler(WebSocketHandler):
174    def open(self):
175        print("WebSocket opened")
176        LISTENERS.append(self)
177
178    def on_message(self, message):
179        message = json.loads(message)
180        # self.write_message(u"You said: " + message['msg'])
181        if message['room'] == 'load':
182            out_file = STDOUT_FILENAME if message[
183                'msg'] == 'stdout' else STDERR_FILENAME
184            if os.path.getsize(out_file) > 10240000:
185                self.write_message('file is too large!')
186            else:
187                with open(out_file, 'r') as f:
188                    f.seek(0, os.SEEK_END)
189                    fsize = f.tell()
190                    f.seek(max(fsize - 1024, 0), 0)
191                    for line in f.readlines()[-800:]:
192                        self.write_message('{}:{}'.format(message['msg'],
193                                                          line))
194
195    def on_close(self):
196        print("WebSocket closed")
197        try:
198            LISTENERS.remove(self)
199        except:
200            pass
201
202
203def tail_file():
204    where = stdout_file.tell()
205    line = stdout_file.readline()
206    if not line:
207        stdout_file.seek(where)
208    else:
209        for element in LISTENERS:
210            element.write_message('stdout:{}'.format(line))
211    where = stderr_file.tell()
212    line = stderr_file.readline()
213    if not line:
214        stderr_file.seek(where)
215    else:
216        for element in LISTENERS:
217            element.write_message('stderr:{}'.format(line))
218
219
220if __name__ == '__main__':
221    STDOUT_FILENAME = 'logs/tail.log'
222    STDERR_FILENAME = 'logs/tail_err.log'
223    STDOUT_FILENAME = os.path.abspath(STDOUT_FILENAME)
224    STDERR_FILENAME = os.path.abspath(STDERR_FILENAME)
225    LISTENERS = []
226    stdout_file = open(STDOUT_FILENAME)
227    stderr_file = open(STDERR_FILENAME)
228    stdout_file.seek(os.path.getsize(STDOUT_FILENAME))
229    stderr_file.seek(os.path.getsize(STDERR_FILENAME))
230
231    app = create_app()
232
233    http_server = tornado.httpserver.HTTPServer(app)
234    http_server.listen(8000, '0.0.0.0')
235
236    tailed_callback = tornado.ioloop.PeriodicCallback(tail_file, 5)
237    tailed_callback.start()
238
239    io_loop = tornado.ioloop.IOLoop.instance()
240    try:
241        io_loop.start()
242    except SystemExit as KeyboardInterrupt:
243        io_loop.stop()
244        stdout_file.close()
245        stderr_file.close()
作者: honmaple
链接: https://honmaple.me/articles/2017/11/使用tornado实时输出日志.html
版权: CC BY-NC-SA 4.0 知识共享署名-非商业性使用-相同方式共享4.0国际许可协议
wechat
alipay

加载评论