文章目錄
1.編譯和安裝
配置參數需要加上–enable-debug=yes,相關定義在src/util/virlog.h文件中定義
如果沒有加這個編譯參數,調用VIR_DEBUG_INT宏的函數或者其他宏,就沒有任何效果,這是一切的一切的基礎。其他編譯安裝省略,可見其他相關文檔。
2.查看libvirtd配置文件
#從libvirtd入手,打開libvirtd的配置文件
[root@localhost ~]# vi /etc/libvirt/libvirtd.conf
#################################################################
#
# Logging controls
#
#log_level = 3
#log_filters="3:remote 4:event"
#log_outputs="3:syslog:libvirtd"
# 1: DEBUG
# 2: INFO
# 3: WARNING
# 4: ERROR
省略了一些介紹性的文字,只列出# Logging controls部分最重要的三個配置選項,以及幾個日誌等級,雖然每個配置都有自己的等級的介紹,但是這幾個其實是一樣的,在源碼中只有一份定義,如圖所示
3.libvirtd日誌輸出方式log_outputs
先看log_outpus, 以下是配置文件中完整的註釋信息
# Logging outputs:
# An output is one of the places to save logging information
# The format for an output can be:
# x:stderr
# output goes to stderr
# x:syslog:name
# use syslog for the output and use the given name as the ident
# x:file:file_path
# output to a file, with the given filepath
# x:journald
# output to journald logging system
# In all case the x prefix is the minimal level, acting as a filter
# 1: DEBUG
# 2: INFO
# 3: WARNING
# 4: ERROR
#
# Multiple outputs can be defined, they just need to be separated by spaces.
# e.g. to log all warnings and errors to syslog under the libvirtd ident:
#log_outputs="3:syslog:libvirtd"
可以看到,前面的數字表示了打印的等級,第二位和第三位是根據輸出類型不同,意義也不同,例如默認的 log_outputs=“3:syslog:libvirtd” 中,代表輸出的內容的等級爲3: WARNING,並且使用linux系統自身的syslog來進行記錄日誌,日誌條目的名字爲libvirtd。目前libvirtd默認用的什麼配置沒有研究過,並且我們不想借用系統自帶的syslog系統,因爲那樣可能會和系統其他程序或者系統自身的log信息混在一起,不方便調試,因此選擇打印到獨立的文件中
log_level = 4//決定了待打印的log信息的等級
log_outputs=“1:file:/var/log/libvirt/libvirtd.log”//這兩個配置表示日誌信息會被當作ERROR的級別,並且在輸出的控制是DEBUG,即只要log級別大於等於DEBUG都可以被輸出到/var/log/libvirt/libvirtd.log文件。
查看libvirtd.c的main函數1353行:
VIR_DEBUG("Decided on pid file path '%s'", NULLSTR(pid_file));
#重新啓動libvirtd進程
[root@localhost ~]# systemctl restart libvirtd.service
#查看日誌文件
[root@localhost ~]# cat /var/log/libvirt/libvirtd.log|grep "Decided on pid file path"
並沒有看到相關日誌信息,這是什麼原因呢?下一節再分析,此處先做一個workaround,不深究原因,更改log_level的數值
log_level = 1
之後再重啓libvirtd服務,可以看到,log信息確實寫入了log_outputs指定的文件裏。
[root@localhost ~]# cat /var/log/libvirt/libvirtd.log|grep "Decided on pid file path"
2018-08-17 02:12:40.573+0000: 21095: debug : main:1353 : Decided on pid file path '/var/run/libvirtd.pid'
4.libvirtd日誌輸入級別log_level
從上一節可以看到,log_level確實可以影響日誌是否能正常被打印到log_outputs所指定的文件中。具體如何影響的,分析代碼。先將log_level改成4,分析爲什麼無法打印。
#關閉後臺進程,直接用gdb跟蹤
[root@localhost ~]# systemctl stop libvirtd.service
(gdb) b 1353
Breakpoint 1 at 0x17888: file libvirtd.c, line 1353.
(gdb) r
Starting program: /usr/sbin/libvirtd
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
2018-08-17 02:25:46.299+0000: 21314: info : libvirt version: 2.0.0
2018-08-17 02:25:46.299+0000: 21314: info : hostname: localhost.localdomain
2018-08-17 02:25:46.299+0000: 21314: debug : virLogParseFilters:1289 : filters=1:remote 4:event
2018-08-17 02:25:46.299+0000: 21314: debug : virLogParseFilter:1227 : filter=1:remote
2018-08-17 02:25:46.299+0000: 21314: debug : virLogParseFilter:1227 : filter=4:event
2018-08-17 02:25:46.299+0000: 21314: debug : virLogParseOutputs:1191 : outputs=1:file:/var/log/libvirt/libvirtd.log
2018-08-17 02:25:46.299+0000: 21314: debug : virLogParseOutput:1097 : output=1:file:/var/log/libvirt/libvirtd.log
Breakpoint 1, main (argc=<optimized out>, argv=0x7fffffffe638) at libvirtd.c:1353
1353 VIR_DEBUG("Decided on pid file path '%s'", NULLSTR(pid_file));
(gdb) b virLogVMessage
Breakpoint 2 at 0x7ffff738d470: file util/virlog.c, line 549.
(gdb) c
Continuing.
進入virLogVMessage函數後,列出各種變量信息
source->priority的值是會在virLogSourceUpdate函數中刷新的,只要virLogSourceUpdate被調用,繼續跟蹤斷點可以看到,猜測是正確的,如下圖所示:
可以看到,virLogSourceUpdate函數給source->priority進行了重新賦值,使之成爲了配置文件中log_level設定的數值4,至於virLogSourceUpdate具體做了什麼,這要結合最後一個filter參數來參數,比較複雜,不影響此處的理解,之後再來分析,這裏可以不用深究。接下來:
[root@localhost ~]# cat /var/log/libvirt/libvirtd.log|grep "Decided on pid file path"
2018-08-17 08:07:37.838+0000: 27663: error : main:1354 : Decided on pid file path '/var/run/libvirtd.pid'
可以看到,VIR_ERROR宏具有最高權限,隨後是VIR_WARN,VIR_INFO,VIR_DEBUG,高級別的宏可以打印低級別的log_level的log日誌,但是反過來不行。注意,這個地方是控制打印的宏是否有資格做出打印這個動作,但是無法決定是否有權力寫到某個文件中,如果打印的動作都無法做出,就會跳轉到clean之後直接返回了,不會執行以下代碼。log信息決定是否有權力寫到某個文件中是由log_outputs參數決定的,如下圖所示,這樣兩種行爲的區別就分清楚了。
# util/virlog.c:619 virLogOutputs[i].f(source, priority,
filename, linenr, funcname,
timestamp, metadata, filterflags,
str, msg, virLogOutputs[i].data);=>virLogOutputToFd
通過循環語句,把所有log_outputs所描述的輸出途徑都遍歷一遍並且進行輸出,真正寫入是調用這個函數。整個日誌大致框架就是這樣。
5.libvirtd過濾器log_filter
5.1.virLogSourceUpdate函數分析
上一章圖4-2和圖4-3可以看到,當滿足source->serial < virLogFiltersSerial的時候,就會調用virLogSourceUpdate(source),在這個函數內部,會對source的各種變量進行重新設置,依據就是前一章提到的log_level,以及本章要提到的filter。首先分析這個函數:
(gdb) p virLogFiltersSerial
$2 = 4
(gdb) p virLogNbFilters
$3 = 2
(gdb) p virLogFilters
$4 = (virLogFilterPtr) 0x5555557e11b0
(gdb) p virLogFilters[0]
$5 = {match = 0x5555557ec040 "daemon", priority = VIR_LOG_WARN, flags = 0}
(gdb) p source[0]
$6 = {name = 0x5555555ad318 "daemon.libvirtd", priority = 3, serial = 1, flags = 0}
(gdb) p virLogFilters[1]
$7 = {match = 0x5555557ebfa0 "event", priority = VIR_LOG_ERROR, flags = 0}
可以看到virLogNbFilters就是對應的兩個filter的數量,virLogFilters就是兩個filter的內容,"daemon"的優先級爲 VIR_LOG_WARN,"event"的優先級爲VIR_LOG_ERROR,也就是log_filters中設置的3和4。
479行的循環會遍歷virLogFilters數組,用if (strstr(source->name, virLogFilters[i].match)) 來判斷,filter數組virLogFilters裏面的內容是不是source->name的子串,是的話,則匹配成功。
此處,virLogFilters[0]的daemon就可以和source->name匹配,這樣source的優先級也會被filter中定義的優先級給刷新。
可以看到,log_outputs, log_level, log_filter三者組合起來使用就可以很靈活的控制LOG打印,三者之間的關係是log_level會指定默認的輸入數據(source)的級別(級別不夠的宏無法被打印,因此打印出來的一定是級別夠的),而過濾器log_filter會改變這種級別,使之以過濾器處理過的級別來輸出(可以理解成是一種中間人攻擊,中途篡改了優先級),log_filter定義的輸出通道決定了輸出的級別,如果source數據的級別小於輸出通道的級別,就無法從這個通道輸出。
5.2.使用VIR_LOG_INIT宏
上一節可以看到,log_filter可以對信息進行過濾,既然能過濾,說明log信息一定存在某種標識符,這個就是VIR_LOG_INIT決定的,例如src/qemu/qemu_process.c文件裏面開頭的VIR_LOG_INIT(“qemu.qemu_process”);
這樣,如果配置log_filters=“1:qemu.qemu_process”,該文件下的所有信息都可以被轉化爲優先級1,並且這一步是在與log_level比較之前生效的,從而小於等於log_level,因此都可以被打印出來,這裏filter的功能不涉及優先級大小的條件,只要是滿足VIR_LOG_INIT宏所定義的字符串的的前驅值,都能任意替換掉優先級,無論放大還是放小。
5.3.分析VIR_LOG_INIT宏
6.libvirtd日誌序列化(serialization)
6.1.virLogFiltersSerial的意義
前面章節中忽略掉的最後一樣東西,就是本章要提到的序列化(這樣稱呼不確定是否合理,暫且這麼稱呼),當調用virLogSourceUpdate函數之前,要用以下語句作爲判斷
if (source->serial < virLogFiltersSerial)
virLogSourceUpdate(source);
在每次調用virLogSourceUpdate結束前,都會把source->serial 更新爲 virLogFiltersSerial,這樣代表源數據的信息已經經過filter更新過了,不需要再執行這個函數,從這個角度看,用一個bool型的變量也可以完成此功能,比如說flag屬性,但是當前的設計可以應對filter的數量動態增加的情況,這樣,virLogSourceUpdate就會被再次調用了,只是目前沒在使用和分析中發現filter數量動態增加的情況,背後的設計思路還未知,這裏不用深究。 ## 6.2.LogLock的意義 上一小節裏面提到的virLogSourceUpdate所有內容,都是在一對virLogLock()和virLogUnlock();之間的,這樣做的意義何在,按照代碼註釋描述:
/*
* 3 intentionally non-thread safe variable reads.
* Since writes to the variable are serialized on
* virLogLock, worst case result is a log message
* is accidentally dropped or emitted, if another
* thread is updating log filter list concurrently
* with a log message emission.
*/
大致意思就是多線程同時寫log的時候,爲了防止衝突,增加了一個鎖,在virLogSourceUpdate函數被調用之前增加一個打印,在bash下啓動libvirtd
疑問
1.log_filters="3:remote 4:event"裏面的remote和event是在哪定義的?具有什麼意義?
A:不需要定義,只是字符串而已,詳情見第5章
長期更新維護…
[1]: Linux下編譯安裝qemu和libvirt
[2]: Networking