Tailing a log file is a common task when we deploy or maintain some software in production. Instead of logging into the server and tail -f
, it would be nice if we can tail a log file in the browser. With WebSocket, this can be done easily. In this article, I’ll walk you through a simple logviewer (source) utility that is written in Python.
WebSocket Intro
WebSocket is standard protocol over TCP, that provides full-duplex communication between client and server side, usually a browser and a web server. Before WebSocket, when we want to keep an alive browser-server connection, we choose from long polling, forever frame or Comet techniques. Now that WebSocket is widely supported by major browsers, we can use it to implement web chatroom, games, realtime dashboard, etc. Besides, WebSocket connection can be established by an HTTP upgrade request, and communicate over 80 port, so as to bring minimum impact on existing network facility.
Python’s websockets
Package
websockets
is a Python package that utilize Python’s asyncio
to develop WebSocket servers and clients. The package can be installed via pip
, and it requires Python 3.3+.
1 | pip install websockets |
Following is a simple Echo server:
1 | import asyncio |
Here we use Python’s coroutines to handle client requests. Coroutine enables single-threaded application to run concurrent codes, such as handling socket I/O. Note that Python 3.5 introduced two new keywords for coroutine, async
and await
, so the Echo server can be rewritten as:
1 | async def echo(websocket, path): |
For client side, we use the built-in WebSocket
class. You can simply paste the following code into Chrome’s JavaScript console:
1 | let ws = new WebSocket('ws://localhost:8765') |
Tail a Log File
We’ll take the following steps to implement a log viewer:
- Client opens a WebSocket connection, and puts the file path in the url, like
ws://localhost:8765/tmp/build.log?tail=1
; - Server parses the file path, along with a flag that indicates whether this is a view once or tail request;
- Open file and start sending contents within a for loop.
Full code can be found on GitHub, so here I’ll select some important parts:
1 |
|
Miscellaneous
- Sometimes the client browser will not close the connection properly, so it’s necessary to add some heartbeat mechanism. For instance:
1 | if time.time() - last_heartbeat > HEARTBEAT_INTERVAL: |
- Log files may contain ANSI color codes (e.g. logging level). We can use
ansi2html
package to convert them into HTML:
1 | from ansi2html import Ansi2HTMLConverter |
- It’s also necessary to do some permission checks on the file path. For example, convert to absolute path and do a simple prefix check.