libvirt log系统分析
1.编译和安装
配置参数需要加上–enable-debug=yes,相关定义在src/util/virlog.h文件中定义
图1-1 ENABLE_DEBUG宏
如果没有加这个编译参数,调用VIR_DEBUG_INT宏的函数或者其他宏,就没有任何效果,这是一切的一切的基础。其他编译安装省略,可见其他相关文档。
2.查看libvirtd配置文件
#从libvirtd入手,打开libvirtd的配置文件
[root@localhost ~]# vi /etc/libvirt/libvirtd.conf
#################################################################
#
# Logging controls
#
#log_level =
#log_filters="3:remote 4:event"
#log_outputs="3:syslog:libvirtd"
# : DEBUG
# : INFO
# : WARNING
# : ERROR
省略了一些介绍性的文字,只列出# Logging controls部分最重要的三个配置选项,以及几个日志等级,虽然每个配置都有自己的等级的介绍,但是这几个其实是一样的,在源码中只有一份定义,如图所示
图2-1 日志等级定义
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
# : DEBUG
# : INFO
# : WARNING
# : 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 =
之后再重启libvirtd服务,可以看到,log信息确实写入了log_outputs指定的文件里。
[root@localhost ~]# cat /var/log/libvirt/libvirtd.log|grep "Decided on pid file path"
-- ::40.573+: : debug : main: : 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
Breakpoint at 0x17888: file libvirtd.c, line .
(gdb) r
Starting program: /usr/sbin/libvirtd
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
-- ::46.299+: : info : libvirt version: 2.0.
-- ::46.299+: : info : hostname: localhost.localdomain
-- ::46.299+: : debug : virLogParseFilters: : filters=:remote :event
-- ::46.299+: : debug : virLogParseFilter: : filter=:remote
-- ::46.299+: : debug : virLogParseFilter: : filter=:event
-- ::46.299+: : debug : virLogParseOutputs: : outputs=:file:/var/log/libvirt/libvirtd.log
-- ::46.299+: : debug : virLogParseOutput: : output=:file:/var/log/libvirt/libvirtd.log Breakpoint , main (argc=<optimized out>, argv=0x7fffffffe638) at libvirtd.c:
VIR_DEBUG("Decided on pid file path '%s'", NULLSTR(pid_file));
(gdb) b virLogVMessage
Breakpoint at 0x7ffff738d470: file util/virlog.c, line . (gdb) c
Continuing.
进入virLogVMessage函数后,列出各种变量信息
图4-1列出变量信息
source->priority的值是会在virLogSourceUpdate函数中刷新的,只要virLogSourceUpdate被调用,继续跟踪断点可以看到,猜测是正确的,如下图所示:
图4-4 virLogSourceUpdate内部
可以看到,virLogSourceUpdate函数给source->priority进行了重新赋值,使之成为了配置文件中log_level设定的数值4,至于virLogSourceUpdate具体做了什么,这要结合最后一个filter参数来参数,比较复杂,不影响此处的理解,之后再来分析,这里可以不用深究。接下来:
图4-5 判断是否打印
[root@localhost ~]# cat /var/log/libvirt/libvirtd.log|grep "Decided on pid file path"
-- ::37.838+: : error : main: : Decided on pid file path '/var/run/libvirtd.pid'
可以看到,VIR_ERROR宏具有最高权限,随后是VIR_WARN,VIR_INFO,VIR_DEBUG,高级别的宏可以打印低级别的log_level的log日志,但是反过来不行。注意,这个地方是控制打印的宏是否有资格做出打印这个动作,但是无法决定是否有权力写到某个文件中,如果打印的动作都无法做出,就会跳转到clean之后直接返回了,不会执行以下代码。log信息决定是否有权力写到某个文件中是由log_outputs参数决定的,如下图所示,这样两种行为的区别就分清楚了。
图4-7判断是否允许打印到输出对象(文件或者标准输出等)
# util/virlog.c: 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。首先分析这个函数:
图5-1 virLogSourceUpdate
(gdb) p virLogFiltersSerial
$ =
(gdb) p virLogNbFilters
$ =
(gdb) p virLogFilters
$ = (virLogFilterPtr) 0x5555557e11b0
(gdb) p virLogFilters[]
$ = {match = 0x5555557ec040 "daemon", priority = VIR_LOG_WARN, flags = }
(gdb) p source[]
$ = {name = 0x5555555ad318 "daemon.libvirtd", priority = , serial = , flags = }
(gdb) p virLogFilters[]
$ = {match = 0x5555557ebfa0 "event", priority = VIR_LOG_ERROR, flags = }
可以看到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宏
图5-2 声明本文件的log标志
图5-3 宏的具体信息
6.libvirtd日志序列化(serialization)
6.1.virLogFiltersSerial的意义
前面章节中忽略掉的最后一样东西,就是本章要提到的序列化(这样称呼不确定是否合理,暂且这么称呼),当调用virLogSourceUpdate函数之前,要用以下语句作为判断
if (source->serial < virLogFiltersSerial)
virLogSourceUpdate(source);
图6-1解析filter
在每次调用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
图6-2增加打印信息
红色字体部分即为发生抢占了,两个name为util.file的source想调用virLogSourceUpdate函数并利用virLogFiltersSerial全局变量修改自身的序列号,由于锁的存在,发生的抢占,只有一个成功调用了virLogSourceUpdate函数并且修改了变量,另外一个进程就不会在同一时刻进入此函数的临界区了。
疑问
1.log_filters="3:remote 4:event"里面的remote和event是在哪定义的?具有什么意义?
A:不需要定义,只是字符串而已,详情见第5章
长期更新维护…
[1]:Linux下编译安装qemu和libvirt
[2]:Networking
libvirt log系统分析的更多相关文章
- KVM 介绍(5):libvirt 介绍 [ Libvrit for KVM/QEMU ]
学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...
- KVM(五)libvirt 介绍
1. Libvirt 是什么 为什么需要Libvirt? Hypervisor 比如 qemu-kvm 的命令行虚拟机管理工具参数众多,难于使用. Hypervisor 种类众多,没有统一的编程接口来 ...
- kvm详细介绍
KVM详解,太详细太深入了,经典 2016-07-18 19:56:38 分类: 虚拟化 原文地址:KVM详解,太详细太深入了,经典 作者:zzjlzx KVM 介绍(1):简介及安装 http:// ...
- openstack-lanch an instance and nova compute log analysis
1. how to launch an instance: [root@localhost ~(keystone_admin)]# nova flavor-list+----+-----------+ ...
- Nova 操作汇总(限 libvirt 虚机) [Nova Operations Summary]
本文梳理一下 Nova 主要操作的流程. 0. Nova REST-CLI-Horizon 操作对照表 Nova 基本的 CRUD 操作和 extensions: # 类别 Nova V2 REST ...
- KVM 介绍(6):Nova 通过 libvirt 管理 QEMU/KVM 虚机 [Nova Libvirt QEMU/KVM Domain]
学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...
- libvirt 基于C API基本使用案例
玩开源分享,需要有干到底的精神,今晚随便逛逛技术论坛突发有感;Ruiy不足之处,需跟进了; 最近变的较懒了,干活有点没劲,也不怪干来干去收获不大,缺少鼓励! 现在玩的技术大多是上不了台面了,想过没,你 ...
- QEMU KVM Libvirt手册(7): 硬件虚拟化
在openstack中,如果我们启动一个虚拟机,我们会看到非常复杂的参数 qemu-system-x86_64 -enable-kvm -name instance-00000024 -S -mach ...
- QEMU KVM libvirt手册(4) – images
RAW raw是默认的格式,格式简单,容易转换为其他的格式.需要文件系统的支持才能支持sparse file 创建image # qemu-img create -f raw flat.img 10G ...
随机推荐
- texture2dArray
https://medium.com/@calebfaith/how-to-use-texture-arrays-in-unity-a830ae04c98b http://cdn.imgtec.com ...
- LiteOS的内核——RTOS基本的特性
在其他的rtos中,基本上也有类似的功能,ucos freertos,要是rtos的时候,务必选择自带的rtos功能,和裸机运行时有区别的
- /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- redis (LoadError)
报错信息: /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file ...
- luogu 4234 最小差值生成树 LCT
感觉码力严重下降~ #include <bits/stdc++.h> #define N 400006 #define inf 1000000000 #define setIO(s) fr ...
- luogu P2585 [ZJOI2006]三色二叉树
P2585 [ZJOI2006]三色二叉树 题目描述 输入输出格式 输入格式: 输入文件名:TRO.IN 输入文件仅有一行,不超过10000个字符,表示一个二叉树序列. 输出格式: 输出文件名:TRO ...
- Ubuntu 出现 Invalid operation update 或 Invalid operation upgrade的解决办法
输入 sudo apt update && sudo apt full-upgrade
- jQuery的入口函数
原生的JS的入口函数指的是:window.onload = function(){}: 如下所示: //原生js的入口函数.页面上所有内容加载完毕, 才执行.//不仅要等文本加载完毕, 而且要等图片也 ...
- ie中兼容性问题
由于项目要要兼容到ie8原本没有问题的代码一但用ie8打开js的报错找不到对象就都来了,其实总结起来就是ie越老的版本就越多方法名识别不到,那就少什么方法添加什么,比如说我的项目就要引入<scr ...
- Light Switching(SPOJ LITE)—— 线段树成段更新异或值
题目连接:http://www.spoj.com/problems/LITE/en/. 题意:有若干个灯泡,每次对一段操作,这一段原先是亮的,就关了:原先是关着的,就打开.询问某一段的打开的灯泡的个数 ...
- CountDownLatch和CylicBarrier以及Semaphare你使用过吗
CountDownLatch 是什么 CountDownLatch的字面意思:倒计时 门栓 它的功能是:让一些线程阻塞直到另一些线程完成一系列操作后才唤醒. 它通过调用await方法让线程进入阻塞状态 ...