使用py 和flask 实现的服务器系统目录浏览,日志文件实时显示到网页的功能
看日志希望带有彩色,希望从浏览器上看到,不用连到机器上看。
浏览系统的文件夹,scan + 系统文件夹的层级名字当做url路由,可以深层次看到机器上任何层级的文件夹,实现系统文件夹浏览下载。
如果是点击文件夹进入子目录。
如果是点击文件,尝试以文本格式读取文件,并以实时更新的方式显示到浏览器日志控制台并加彩。 主要是要做到不遗漏推送日志和不重复推送日志,采用的是python 操作文件的seek和tell。
浏览系统目录和下载文件的页面
查看实时日志更新的页面,提供了暂停功能和自动下拉功能。把日志根据级别加了彩色,更容易观察哪些是严重的,哪些是debug的。
实现代码。
# -*- coding: utf-8 -*- # @Author : ydf # @Time : 2019/6/14 17:33 import os from pathlib import Path from flask import Flask, send_from_directory, url_for, jsonify, request, render_template, current_app, abort, g, send_file from flask_httpauth import HTTPBasicAuth from flask_bootstrap import Bootstrap from app.utils_ydf import LogManager, nb_print, time_util print(str((Path(__file__).parent / Path('ydf_dir')).absolute())) app = Flask(__name__, template_folder=str((Path(__file__).parent / Path('ydf_dir')).absolute())) app.config['JSON_AS_ASCII'] = False app.config['REFRESH_MSEC'] = 1000 auth = HTTPBasicAuth() LogManager(app.logger.name).get_logger_and_add_handlers() bootstrap = Bootstrap(app) @app.route('/favicon.ico') def favicon(): print(Path(__file__).parent / Path('ydf_dir/').absolute()) return send_from_directory(str(Path(__file__).parent / Path('ydf_dir/').absolute()), 'log_favicon.ico', mimetype='image/vnd.microsoft.icon') @app.route("/ajax0/<path:fullname>/") def info0(fullname): fullname = f'/{fullname}' position = int(request.args.get('position')) current_app.logger.debug(position) # if os.path.isfile(full_name): # fo = open(full_name,encoding='utf8') # content = fo.read() # return content # else : # return "There is no log file" with open(fullname, 'rb') as f: try: if position == 0: f.seek(-50000, 2) else: f.seek(position, 0) except Exception: current_app.logger.exception('读取错误') f.seek(0, 0) content_text = f.read().decode() # nb_print([content_text]) content_text = content_text.replace('\n', '<br>') # nb_print(content_text) position_new = f.tell() current_app.logger.debug(position_new) # nb_print(len(content_text)) return jsonify(content_text=content_text, position=position_new) @app.route("/ajax/<path:fullname>/") def info(fullname): fullname = f'/{fullname}' position = int(request.args.get('position')) current_app.logger.debug(position) # if os.path.isfile(full_name): # fo = open(full_name,encoding='utf8') # content = fo.read() # return content # else : # return "There is no log file" with open(fullname, 'rb') as f: try: if position == 0: f.seek(-50000, 2) else: f.seek(position, 0) except Exception: current_app.logger.exception('读取错误') f.seek(0, 0) lines = f.readlines() content_text = '' for line in lines: line = line.strip().decode() if '- DEBUG -' in line: color = '#00FF00' elif '- INFO -' in line: color = '#00FFFF' elif '- WARNING -' in line: color = 'yellow' elif '- ERROR -' in line: color = '#FF00FF' elif '- CRITICAL -' in line: color = '#FF0033' else: color = '' content_text += f'<p style="color:{color}"> {line} </p>' # content_text = f.read().decode() # # nb_print([content_text]) # content_text = content_text.replace('\n', '<br>') # # nb_print(content_text) position_new = f.tell() current_app.logger.debug(position_new) # nb_print(content_text) return jsonify(content_text=content_text, position=position_new) @app.route("/view/<path:fullname>") def view(fullname): view_html = ''' <html> <head> <title>查看 %s </title> <script type="text/javascript" src="http://libs.baidu.com/jquery/2.1.1/jquery.min.js"> </script> </head> <body> <div id="result"></div> <hr> <button onclick="toggle_scroll()"> 自动滚动浏览器滚动条 </button> <div style="display: inline" id="auto_scroll_stat">ON</div> <button id= "runButton" style="margin-left:300px" onclick="startOrStop()"> 运行中 </button> <button id= "runButton" style="margin-left:300px" > <a href="/%s/d" download="%s">下载 %s</a></button> </body> <script> var autoscroll = "ON"; toggle_scroll = function(){ if(autoscroll == "ON") autoscroll = "OFF"; else autoscroll = "ON"; } var position = 0; function downloadFile(){ } get_log = function(){ $.ajax({url: "/%s/a", data: {"position":position} ,success: function(result){ console.debug(4444); var resultObj = result; console.debug(6666); //var html = document.getElementById("div_id").innerHTML; var html = $("#result").html(); var htmlShort = html.substr(-40000); console.debug(htmlShort); document.getElementById("result").innerHTML = htmlShort || ""; console.debug($("#result").html()); $("#result").append( resultObj.content_text); console.debug(resultObj.position); position = resultObj.position; if(autoscroll == "ON") window.scrollTo(0,document.body.scrollHeight); $("#auto_scroll_stat").text(autoscroll); }}); } iid = setInterval(get_log,%s); status = 1; function startRun(){ $("#runButton").text("运行中"); iid = setInterval(get_log,%s); status = 1; } function stopRun(){ $("#runButton").text("停止了"); clearInterval(iid); status = 0; } function startOrStop(){ if(status == 1){ stopRun();} else {startRun();} } </script> </html> ''' # return view_html % (logfilename,logfilename,logfilename,logfilename,logfilename, REFRESH_MSEC, REFRESH_MSEC) return render_template('/log_view_html.html', fullname=fullname) @app.route('/download/<path:fullname>', ) def download_file(fullname): current_app.logger.debug(fullname) return send_file(f'/{fullname}') # return send_from_directory(f'/{logs_dir}', # filename, as_attachment=True, ) @app.route('/scan/', ) @app.route('/scan/<path:logs_dir>', ) def index(logs_dir=''): current_app.logger.debug(logs_dir) file_ele_list = list() dir_ele_list = list() for f in (Path('/') / Path(logs_dir)).iterdir(): fullname = str(f).replace('\\', '/') if f.is_file(): # current_app.logger.debug(str(f).replace('\\', '/')[1:]) # current_app.logger.debug((logs_dir, str(f).replace('\\','/')[1:])) current_app.logger.debug(str(f)) current_app.logger.debug(url_for('download_file', fullname=fullname[0:])) # current_app.logger.debug(url_for('download_file', logs_dir='', filename='windows_to_linux_syn_config.json')) file_ele_list.append({'is_dir': 0, 'filesize': os.path.getsize(f) / 1000000, 'last_modify_time': time_util.DatetimeConverter(os.stat(str(fullname)).st_mtime).datetime_str, 'url': url_for('view', fullname=fullname[1:]), 'download_url': url_for('download_file', fullname=fullname[1:]), 'fullname': fullname}) if f.is_dir(): fullname = str(f).replace('\\', '/') dir_ele_list.append({'is_dir': 1, 'filesize': 0, 'last_modify_time': time_util.DatetimeConverter(os.stat(str(f)).st_mtime).datetime_str, 'url': url_for('index', logs_dir=fullname[1:]), 'download_url': url_for('index', logs_dir=fullname[1:]), 'fullname': fullname}) return render_template('dir_view.html', ele_list=dir_ele_list + file_ele_list, logs_dir=logs_dir) @app.template_filter() def file_filter(filefullname, file_name_part): if file_name_part == 1: return str(Path(filefullname).parent) if file_name_part == 2: return str(Path(filefullname).name) @app.context_processor def dir_processor(): def format_logs_dir_to_multi(logs_dir): parent_dir_list = list() pa = Path(f'/{logs_dir}') while True: nb_print(pa.as_posix()) parent_dir_list.append({'url': url_for('index', logs_dir=pa.as_posix()[1:]), 'dir_name': pa.name[:]}) pa = pa.parent if pa == Path('/'): parent_dir_list.append({'url': url_for('index', logs_dir=''), 'dir_name': '根目录'}) break nb_print(parent_dir_list) return parent_dir_list return dict(format_logs_dir_to_multi=format_logs_dir_to_multi) @auth.verify_password def verify_password(username, password): if username == 'user' and password == 'mtfy123': return True return False @app.before_request @auth.login_required def before_request(): pass if __name__ == "__main__": # main() print(app.url_map) app.run(host="0.0.0.0", port=8888, threaded=True, )
dir_view.html
{% extends 'bootstrap/base.html' %} {% block title %} {{ logs_dir }} {% endblock %} {% block scripts %} {{ super() }} <script> (function ($) { //插件 $.extend($, { //命名空间 sortTable: { sort: function (tableId, Idx) { var table = document.getElementById(tableId); var tbody = table.tBodies[0]; var tr = tbody.rows; var trValue = new Array(); for (var i = 0; i < tr.length; i++) { trValue[i] = tr[i]; //将表格中各行的信息存储在新建的数组中 } if (tbody.sortCol == Idx) { trValue.reverse(); //如果该列已经进行排序过了,则直接对其反序排列 } else { //trValue.sort(compareTrs(Idx)); //进行排序 trValue.sort(function (tr1, tr2) { var value1 = tr1.cells[Idx].innerHTML; var value2 = tr2.cells[Idx].innerHTML; return value1.localeCompare(value2); }); } var fragment = document.createDocumentFragment(); //新建一个代码片段,用于保存排序后的结果 for (var i = 0; i < trValue.length; i++) { fragment.appendChild(trValue[i]); } tbody.appendChild(fragment); //将排序的结果替换掉之前的值 tbody.sortCol = Idx; } } }); })(jQuery); </script> {% endblock %} {% block content %} <div class="container" style="width: 80%"> <span class="label label-default">常用文件夹</span> <a class="label label-primary" href="{{url_for('index',logs_dir='pythonlogs/')}}" target="_blank">pythonlogs/</a> <a class="label label-primary" href="{{url_for('index',logs_dir='data/supervisorout')}}" target="_blank">data/supervisorout/</a> <table class="table" id="table1"> {# <caption> / {{ logs_dir }} 的文件浏览</caption> #} <caption> {% for dir_item in format_logs_dir_to_multi(logs_dir)|reverse %} /<a href="{{ dir_item.url }}" > {{ dir_item.dir_name }}</a> {% endfor %} 的文件浏览 </caption> <thead> <tr> <th onclick="$.sortTable.sort('table1',0)"> <button>名称</button> </th> <th onclick="$.sortTable.sort('table1',1)"> <button>最后修改时间</button> </th> <th onclick="$.sortTable.sort('table1',2)"> <button>文件大小</button> </th> <th>下载文件</th> </tr> </thead> <tbody> {% for ele in ele_list %} {% if ele.is_dir %} <tr class="warning"> {% else %} <tr class="success"> {% endif %} <td><a href="{{ ele.url }}" >{{ ele.fullname | file_filter(2) }}</a></td> <td>{{ ele.last_modify_time }}</td> <td>{{ ele.filesize }} M</td> <td><a href="{{ ele.download_url }}" download={{ ele.fullname | file_filter(2) }}>下载 {{ ele.fullname | file_filter(2) }}</a></td> </tr> {% endfor %} </tbody> </table> </div> {% endblock %}
log_view.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>查看 {{ fullname| file_filter(2) }} </title> <script type="text/javascript" src="http://libs.baidu.com/jquery/2.1.1/jquery.min.js"> </script> </head> <style> .page { background-color: #000000; color: #FFFFFF; } </style> <body class="page"> <div id="result"></div> <hr> <button onclick="toggle_scroll()"> 自动滚动浏览器滚动条</button> <div style="display: inline" id="auto_scroll_stat">ON</div> <button id="runButton" style="margin-left:300px" onclick="startOrStop()"> 运行中</button> <button style="margin-left:300px"><a href="{{ url_for('download_file',fullname=fullname) }}" download={{ fullname| file_filter(2) }}>下载 {{ fullname| file_filter(2) }} </a></button> <script> var autoscroll = "ON"; toggle_scroll = function () { if (autoscroll === "ON") autoscroll = "OFF"; else autoscroll = "ON"; }; var position = 0; get_log = function () { $.ajax({ url: "{{ url_for('info',fullname=fullname) }}", data: {"position": position}, success: function (result) { console.debug(4444); var resultObj = result; console.debug(6666); //var html = document.getElementById("div_id").innerHTML; var resultEle = $("#result"); var html = resultEle.html(); var htmlShort = html.substr(-50000); console.debug(htmlShort); document.getElementById("result").innerHTML = htmlShort; console.debug(resultEle.html()); resultEle.append(resultObj.content_text); console.debug(resultObj.position); position = resultObj.position; if (autoscroll === "ON") { window.scrollTo(0, document.body.scrollHeight); } $("#auto_scroll_stat").text(autoscroll); } }); }; iid = setInterval(get_log, {{ config.REFRESH_MSEC }}); runStatus = 1; function startRun() { $("#runButton").text("运行中"); iid = setInterval(get_log, {{ config.REFRESH_MSEC }}); runStatus = 1; } function stopRun() { $("#runButton").text("暂停了"); clearInterval(iid); runStatus = 0; } function startOrStop() { if (runStatus === 1) { stopRun(); } else { startRun(); } } </script> </body> </html>
反对极端面向过程编程思维方式,喜欢面向对象和设计模式的解读,喜欢对比极端面向过程编程和oop编程消耗代码代码行数的区别和原因。致力于使用oop和36种设计模式写出最高可复用的框架级代码和使用最少的代码行数完成任务,致力于使用oop和设计模式来使部分代码减少90%行,使绝大部分py文件最低减少50%-80%行的写法。