Web 实现日志文件实时监听

在 Linux 中,若想要查看日志文件的实时输出内容,会使用 tailf 命令。现在想要在 Web 项目中实现这个功能。

思路

第一个想法,是 java 接收 tail 命令返回,然后通过 websocket 推送给前端。也搜到了有人利用此方法实现了。

但是个人觉得日志数据量较大,此方法效率偏低。巧的是,Apache Commons 包里已经帮忙实现了这个方法。

要注意的是,2.4 之前不支持中文。Commons 包较为常见,一开始我没有考虑版本,拿着 2.4 的包用,结果乱码,且没有相关参数指定编码,一度打算舍弃它。

实现

Apache Commons 包中的 Tailer 类实现了这个,一旦了解了这个类,实现起来就简单了。直接贴代码了。

websocket 入口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import org.apache.commons.io.input.Tailer;
import org.apache.commons.io.input.TailerListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.File;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * websocket
 */
@ServerEndpoint("/websocket/log/{sid}")
@Component
public class WebSocketServerLog {
    private static Logger LOG = LoggerFactory.getLogger(WebSocketServerLog.class);

    private static AtomicInteger onlineCount = new AtomicInteger(0);

    private Tailer tailer;

    //接收sid
    private String sid="";

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        addOnlineCount();
        this.sid = sid;

        //tail listen
        TailerListener listener = new MyTailerListener(session);
        tailer = Tailer.create(new File("C:\\Users\\lenovo\\Desktop\\" + sid), listener, 1000, true);

        LOG.info("sid " + sid + " on open and online count " + getOnlineCount());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        subOnlineCount();
        tailer.stop();
        LOG.info("sid " + sid + " on close and online count " + getOnlineCount());
    }

    @OnError
    public void onError(Throwable error) {
        LOG.error("sid {} error: {}", sid, error.getMessage());
    }

    public static int getOnlineCount() {
        return onlineCount.get();
    }

    public static void addOnlineCount() {
        onlineCount.incrementAndGet();
    }

    public static synchronized void subOnlineCount() {
        onlineCount.decrementAndGet();
    }
}

继承 Tailer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.apache.commons.io.input.TailerListenerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.Session;
import java.io.IOException;

/**
 * 继承 TailerListenerAdapter
 */
public class MyTailerListener extends TailerListenerAdapter {

    private static Logger LOG = LoggerFactory.getLogger(MyTailerListener.class);

    private Session session;

    public MyTailerListener(Session session) {
        this.session = session;
    }

    @Override
    public void handle(String line) {
        try {
            LOG.info(line);
            if (session.isOpen()){
                session.getBasicRemote().sendText(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

参考

Java WebSocket + tail命令实现Web实时日志|叉叉哥的BLOG

使用 Hugo 构建
主题 StackJimmy 设计