別小看tail 命令,它難倒了技術(shù)總監(jiān)
本文轉(zhuǎn)載自微信公眾號(hào)「小姐姐味道」,作者小姐姐養(yǎng)的狗 。轉(zhuǎn)載本文請(qǐng)聯(lián)系小姐姐味道公眾號(hào)。
tail命令能夠看到日志的滾動(dòng),非常方便。于是xjjdog想,既然我們能夠用這個(gè)命令,看到所有的日志,那能不能使用tail命令,做日志收集呢?
想象歸想象,如果你想要一個(gè)快速的實(shí)時(shí)日志收集工具,那tail確實(shí)是個(gè)非常棒的工具。它比什么flume、logstatsh,比什么filebeat之類的,快捷的多。事實(shí)上,在工具缺乏的舊年代,我就曾經(jīng)這么干過,而且它工作的很好。
下面是一段使用Java語言書寫的代碼。我們可以按行讀取日志,然后使用自己喜歡的語言,做任何事情。
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- public class TailReader {
- public static void main(String[] args) throws Exception {
- ProcessBuilder ps = new ProcessBuilder("tail", "-f", "/tmp/tail0");
- //把錯(cuò)誤輸出也打印
- ps.redirectErrorStream(true);
- Process process = ps.start();
- //持續(xù)讀取tail的輸出
- try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
- String line;
- while ((line = in.readLine()) != null) {
- setLogToKafka(line);
- //注意這里不要產(chǎn)生異常,否則會(huì)打斷while循環(huán)
- }
- }
- }
- //模擬發(fā)送到kafka,我們這里只簡(jiǎn)單的打印出來
- static void setLogToKafka(String line) {
- System.out.println(line);
- }
- }
主要的思想,就是使用Java的Process啟動(dòng)一個(gè)子tail進(jìn)程,一直監(jiān)控著文件的輸出。然后把標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤流,全部定向到BufferedReader中。接下來,你能做你想要做的任何事。
這有一定的風(fēng)險(xiǎn),假如tail命令被殺掉了,我們的Java程序就失去了作用。
程序很簡(jiǎn)單,但xjjdog在這里討論的卻不是這個(gè)簡(jiǎn)單的收集程序,而是tail命令的一些有趣的特性,你可以從中一窺一些日志收集工具對(duì)文件的特殊處理。
你知道tail -f和tail -F的區(qū)別么?
在回答這個(gè)問題之前,我們先回憶一下,Java常用的日志框架,對(duì)日志的處理。
- <configuration>
- <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
- <!-- Support multiple-JVM writing to the same log file -->
- <prudent>true</prudent>
- <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
- <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
- <maxHistory>30</maxHistory>
- <totalSizeCap>3GB</totalSizeCap>
- </rollingPolicy>
- <encoder>
- <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
- </encoder>
- </appender>
- <root level="DEBUG">
- <appender-ref ref="FILE" />
- </root>
- </configuration>
上面的配置,將在每晚凌晨的時(shí)候,滾動(dòng)形成一個(gè)新的文件。
那這個(gè)滾動(dòng),是如何做的呢?我們可以收工模擬這個(gè)過程。
- mv run.log run.2020-11-02.log
- touch run.log
測(cè)試一下
文件滾動(dòng),會(huì)生成新的文件,那tail命令還能跟蹤到么?
我們來測(cè)試一下。
第一步,創(chuàng)建要監(jiān)控的文件
- touch /tmp/tail0
第二步,啟動(dòng)我們的Java代碼
第三步,生成一個(gè)不間斷的流
- watch -n 1 'echo `date` >> /tmp/tail0 '
上面的命令每隔1秒鐘,往我們的文件中打印一下當(dāng)前的日期,可以看到Java端已經(jīng)收到了這些數(shù)據(jù)。
第四步,模擬文件滾動(dòng)
- mv /tmp/tail0 /tmp/tail.bak
- touch /tmp/tail0
此時(shí),我們可以看到,Java端此時(shí)已經(jīng)接受不到數(shù)據(jù)了。
Why?
為了看到這是為什么,我們使用兩個(gè)命令來看一下進(jìn)程的一些狀態(tài)。
首先,使用ps命令,查看當(dāng)前的tail進(jìn)程。
- ps -ef|grep tail
- 501 21374 21373 0 1:51PM ?? 0:00.01 tail -f /tmp/tail0
這正是我們的命令。
我們使用lsof命令去查看這個(gè)進(jìn)程所關(guān)聯(lián)的文件。
- lsof -p 21374 | awk '{print $4 "\t" $9}'
- FD NAME
- cwd /tmp/
- txt /usr/bin/tail
- txt /usr/lib/dyld
- 3r /private/tmp/tail.bak
我們看到tail進(jìn)程所監(jiān)控的文件,其實(shí)是tail.bak文件,已經(jīng)和tail命令沒什么關(guān)系了。
我們嘗試像tail.bak輸入一點(diǎn)內(nèi)容。
- echo "haha: xjjdog, i am from tail.bak" >> /tmp/tail.bak
此時(shí)如我們所愿,Java進(jìn)程有反應(yīng)了,正常輸出了這句話。
怎么辦?
就如同我們問題中問的一樣,把tail -f換成tail -F就可以了。
tail -f的意思是,根據(jù)文件描述符進(jìn)行追蹤。
tail -F的意思是,根據(jù)文件名進(jìn)行追蹤,它會(huì)有重試的動(dòng)作。
所以,我們的日志收集程序,毫無疑問是根據(jù)日志名稱追蹤的,應(yīng)該把f改成F。
End
既然知道了這些小區(qū)別,我們就對(duì)日常工作中遇到的一些靈異問題有了解釋。
大家都知道rm命令,能夠刪除一個(gè)文件。如果有這個(gè)文件,正在被其他進(jìn)程所使用,那這些文件你看起來像是刪掉了,但它的內(nèi)容卻不釋放。
- lsof | grep deleted
上面這個(gè)命令,能夠看到這些失控的文件。一般你kill掉相應(yīng)的進(jìn)程,這些句柄也就釋放了。但你刪除這些文件的本意,就是為了避免重啟應(yīng)用,這可真讓人糾結(jié)。
- cat /dev/null > logpath
所以我們?cè)趧h除文件的時(shí)候,一般不會(huì)使用rm,而應(yīng)該使用重定向符號(hào)。將萬物皆空的/dev/null,發(fā)向它們。
作者簡(jiǎn)介:小姐姐味道 (xjjdog),一個(gè)不允許程序員走彎路的公眾號(hào)。聚焦基礎(chǔ)架構(gòu)和Linux。十年架構(gòu),日百億流量,與你探討高并發(fā)世界,給你不一樣的味道。我的個(gè)人微信xjjdog0,歡迎添加好友,進(jìn)一步交流。