本文主要使用的技术栈:
主要实现功能:
通过websocket实时查看指定文件日志,类似tail -f 命令
一,channels安装及配置
参考官方文档:Installation — Channels 4.0.0 documentation
a.通过pip安装
python -m pip install -U channels["daphne"]
b.在django配置文件 INSTALLED_APPS
最开始添加 daphne
INSTALLED_APPS = (
"daphne", # 一定放到第一位
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
...
)
c.创建一个 message 的app
python3 manage.py startapp message
该操作会创建一个 message的目录,文件如下
message/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
将该app注册到settings里面
INSTALLED_APPS = (
"daphne", # 一定放到第一位
"message", # 刚才创建的app
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
...
)
d.新增路由文件 routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r"ws/message/(?P[\w+|\-?]+)+/(?P\w+)$", consumers.RobotMessageConsumer.as_asgi()),
]
e.新增consumers文件consumers.py
import asyncio
import logging
import os
import aiofiles
from channels.generic.websocket import AsyncJsonWebsocketConsumer
logger = logging.getLogger(__name__)
class RobotMessageConsumer(AsyncJsonWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(args, kwargs)
self.room_group_name = None
self.disconnected = True
async def connect(self):
username = self.scope["url_route"]["kwargs"].get('username')
token = self.scope["url_route"]["kwargs"].get('token')
if username == token:
self.disconnected = False
self.room_group_name = f"message_{username}"
# Join room group
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
else:
logger.error(f"username:{username} token:{token} auth failed")
await self.close()
async def disconnect(self, close_code):
self.disconnected = True
if self.room_group_name:
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
# Receive message from WebSocket
async def receive_json(self, content, **kwargs):
filepath = content.get('filepath')
await self.async_handle_task(filepath)
async def async_handle_task(self, filepath):
while not self.disconnected:
if not os.path.exists(filepath):
await self.send_json({'message': '.', 'filepath': filepath})
await asyncio.sleep(0.5)
else:
await self.send_task_log(filepath)
break
async def send_task_log(self, filepath):
await self.send_json({'message': '\r\n'})
try:
logger.debug('file log path: {}'.format(filepath))
async with aiofiles.open(filepath, 'rb') as log_f:
await log_f.seek(0, os.SEEK_END)
print(await log_f.tell())
backup = min(4096 * 5, await log_f.tell())
await log_f.seek(-backup, os.SEEK_END)
while not self.disconnected:
data = await log_f.read(4096)
if data:
data = data.replace(b'\n', b'\r\n')
await self.send_json(
{'message': data.decode(errors='ignore'), 'filepath': filepath}
)
await asyncio.sleep(0.2)
except OSError as e:
logger.warning('file log path open failed: {}'.format(e))
f.修改asgi.py,增加路由配置
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
django_asgi_app = get_asgi_application()
import message.routing
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(
message.routing.websocket_urlpatterns
))
),
}
)
g.在settings.py中增加asgi 支持
ASGI_APPLICATION = "server.asgi.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [f"redis://:[email protected]:6379/0"],
},
},
}
二,vue及配置
yarn add xterm
yarn add xterm-addon-fit
创建 MessageTail.vue文件,内容如下:
查看日志