django 异步 查看 服务器日志
实例 可以查看我编写的这个项目:
https://github.com/hequan2017/chain
模块
需要安装以下模块
安装后会有一个版本号报错,不影响
channels==2.0.2
channels-redis==2.1.0
amqp==1.4.9
anyjson==0.3.3
asgi-redis==1.4.3
asgiref==2.3.0
async-timeout==2.0.0
attrs==17.4.0
cd /tmp/
wget https://files.pythonhosted.org/packages/12/2a/e9e4fb2e6b2f7a75577e0614926819a472934b0b85f205ba5d5d2add54d0/Twisted-18.4.0.tar.bz2
tar xf Twisted-18.4.0.tar.bz2
cd Twisted-18.4.0
python3 setup.py install
启动redis
原理
点击 查看日志页面,启动一个websocket连接,执行命令。
根据 登录的用户名,返回后端生成的消息,前端负责消费。
目录
chain/
chain/
settings.py
asgi.py
consumers.py
routing.py
templates/
tasks/
tail.html
settings.py
INSTALLED_APPS = [
'channels',
]
# django-channels配置
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
# 配置ASGI
ASGI_APPLICATION = "chain.routing.application"
consumers.py
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
class EchoConsumer(WebsocketConsumer):
def connect(self):
# 创建channels group, 命名为:用户名,并使用channel_layer写入到redis
async_to_sync(self.channel_layer.group_add)(self.scope['user'].username, self.channel_name)
# 返回给receive方法处理
self.accept()
def receive(self, text_data):
async_to_sync(self.channel_layer.group_send)(
self.scope['user'].username,
{
"type": "user.message",
"text": text_data,
},
)
def user_message(self, event):
# 消费
self.send(text_data=event["text"])
def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)(self.scope['user'].username, self.channel_name)
asgi.py
import os
import django
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chain.settings")
django.setup()
application = get_default_application()
routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import URLRouter, ProtocolTypeRouter
from django.urls import path
from .consumers import EchoConsumer
application = ProtocolTypeRouter({
"websocket": AuthMiddlewareStack(
URLRouter([
path(r"ws/", EchoConsumer),
# path(r"stats/", StatsConsumer),
])
)
})
网页设置:
逻辑:
- tail页面 向后面 post 主机信息,要查看的日志路径
- 后端去利用 paramiko 执行命令,一直读取此接口,先返回一遍信息,如有新日志生成,再次返回给前端。
- 点击停止,会修改一个环境变量, 上面paramiko监测到此环境变量为false后,就停止执行命令。
查看
不看了,必须点停止
$(function () {
$(document).on('click','#tail',function () {
$.ajax({
url: "{% url 'tasks:tail_perform' %}",
timeout : 5000, //超时时间设置,单位毫秒
type: 'POST',
data: $('.cmd_from').serialize(),
success: function (data) {
var obj = JSON.parse(data);
if (obj.status) {
toastr.success("执行成功!")
} else {
toastr.error(obj.error)
}
}
})
});
$(document).on('click','#tail_stop',function () {
$.ajax({
url: "{% url 'tasks:tail_perform_stop' %}",
timeout : 5000, //超时时间设置,单位毫秒
type: 'POST',
data: {"status":"stop"},
success: function (data) {
var obj = JSON.parse(data);
if (obj.status) {
toastr.success("停止成功!")
} else {
toastr.success("停止失败!")
}
}
})
});
});
$(document).ready(function () {
CreateWebSocket();
});
function CreateWebSocket() {
var socket = new WebSocket('ws://' + window.location.host + '/ws/');
socket.onmessage = function (message) {
var result = JSON.parse(message.data);
var status = result.status;
var data = result.data;
var output_html = '';
if (status === 0) {
{# $('#output_append').empty();#}
output_html = data;
}
else if (status === 1) {
$('#output_append').empty();
output_html = data;
}
$("#output_append").prepend(output_html);
}
}
urls.py
path('tail.html', views.TasksTail.as_view(), name='tail'),
path('tailperform.html', views.taskstailperform, name='tail_perform'),
path('tailperform-stop.html', views.taskstailstopperform, name='tail_perform_stop'),
views.py
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
import json
import paramiko
def taillog(request, hostname, port, username, password, private, tail):
"""
执行 tail log 接口
"""
channel_layer = get_channel_layer()
user = request.user.username
os.environ["".format(user)] = "true"
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
if password:
ssh.connect(hostname=hostname, port=port, username=username, password=decrypt_p(password))
else:
pkey = paramiko.RSAKey.from_private_key_file(private)
ssh.connect(hostname=hostname, port=port, username=username, pkey=pkey)
cmd = "tail " + tail
stdin, stdout, stderr = ssh.exec_command(cmd, get_pty=True)
for line in iter(stdout.readline, ""):
if os.environ.get("".format(user)) == 'false':
break
result = {"status": 0, 'data': line}
result_all = json.dumps(result)
async_to_sync(channel_layer.group_send)(user, {"type": "user.message", 'text': result_all})
def taskstailperform(request):
"""
执行 tail_log 命令
"""
if request.method == "POST":
ret = {'status': True, 'error': None, }
name = Names.objects.get(username=request.user)
ids = request.POST.get('id')
tail = request.POST.get('tail', None)
if not ids or not tail:
ret['status'] = False
ret['error'] = "请选择服务器,输入参数及日志地址."
return HttpResponse(json.dumps(ret))
obj = AssetInfo.objects.get(id=ids)
try:
taillog(request, obj.network_ip, obj.port, obj.user.username, obj.user.password, obj.user.private_key, tail)
except Exception as e:
ret['status'] = False
ret['error'] = "错误{0}".format(e)
logger.error(e)
return HttpResponse(json.dumps(ret))
def taskstailstopperform(request):
"""
执行 tail_log 命令
"""
if request.method == "POST":
ret = {'status': True, 'error': None, }
name = request.user.username
os.environ["".format(name)] = "false"
return HttpResponse(json.dumps(ret))