徐萌飞 王军玲
( 中国船舶重工集团公司第七一二研究所,武汉 430064 )
日志功能作为软件系统的重要组成部分,在记录软件运行状态、调测和故障定位方面发挥着重要的作用。在船舶系统中,控制系统要长期频繁地和其他子系统进行信息交互。控制系统除了发出控制指令,还要从事被控制对象的状态信息采集,检测控制指令的执行情况等多个动作,触发事件多,实时性要求高,所以准确高效的日志是控制系统必需功能。
在控制系统软件的设计过程中,为满足实际运行需求,日志功能要经过全面设计,使用高效、可靠的实现方式,才能够在实际运行中充分发挥其功用。本文将对日志记录功能的软件设计的若干要点及其实现方法进行探讨,为后续软件日志功能的设计和开发工作提供参考。
在软件中,一般采用统一的日志记录函数来完成日志功能,或者使用统一的对象来处理日志。要尽量避免在每一处需要记录日志的地方单独使用代码实现日志功能。
使用统一的日志模块具有如下优势:
(1)减少无价值的重复开发量
在日志中,每记录一条日志都单独编写日志代码,造成代码冗余。而反复调用基本的输入输出语句(如文件的打开和关闭),导致效率低下。
(2)因需求变化导致的修改量小
采用共用日志模块函数或对象方法,对日志记录格式或者内容提出了新的需求,只需要一处修改,即可全面生效。而单独使用代码记录的,可能需要处处修改。
日志作为记录程序运行过程的载体,必须能够完整记录下运行时间,运行对象,执行动作,执行结果,执行前状态量,执行后状态量等信息,这样在发生故障后才能追根溯源。国外已经有很多成熟软件和相应的技术规范。
以UNIX下的syslog为例,它已经形成了一种事实上的工业标准,其日志记录格式按RFC3164 (The BSD log protocol)定义,并对消息头部进行扩展,其格式为:
<优先级>时间戳 主机名 模块名/级别/信息摘要:内容
<priority>time stamp sysname module/level/digest:content
控制系统软件的日志格式,可以参考这类方式实现。
在以往的日志功能设计过程中,往往容易遗漏对代码执行前状态量的记录,导致在回溯日志时会凸现其缺陷。例如:系统发生了无法重现的问题,如果从代码逻辑上检视不出问题,而又缺乏对执行前系统状态量的记录,就不能确定代码执行前系统是否处于正常工作状态,也无法定位是哪部分子系统故障或者硬件功能失效。
在主控系统和各个子系统共同运行的情况下,尤其是联调过程中,各个子系统不能因为主系统在记录日志信息就放弃自身的日志功能。因为在主控系统和子系统接口信息传递过程中,子系统日志和主控系统日志之间可以相互印证。缺少了子系统的日志作为判断依据,会增加对消息传递通道以及子系统接口功能模块的检查成本。
控制系统的动作往往是具有时序性的,而且集散控制系统中,更是采用集中管理、分散控制,所以保证日志记录精度对后期分析尤为重要。
当控制系统启动后,一个重要的原则是必须有一个可靠的时间基准,即统一的时间源。在多个子系统的动作具有时序关系或者相互影响的情况下,如果没有统一的时间基准,将无法根据日志确定故障发生的时间,以及确定当时各个系统所处的状态,更无法找到问题发生的根源。
建立统一时间基准的方法有多种,在系统要求不高的情况下,一般可以利用系统内部的晶振时钟。精度要求高的情况下,可以引入外部高精度时钟。外部时钟的引入,可以通过外部时钟硬件,也可以通过网络获取标准时间。
在与外界通讯隔绝的封闭式系统中,由于没有获取标准时间的条件,则需要尽可能将系统(一般是主控系统)的初始上电时间作为其内部时钟的日志记录起点时间。各子系统上电后也要通过通讯接口与主控系统进行时钟同步,来保证后续日志记录在时间维度上的一致性。
在控制系统对处理精度要求较高的情况下,有的子系统本身会带有时钟,虽然子系统可以靠自身的时钟来记录时间,但是在进行业务循环时,子系统仍需要在每次业务处理开始时,和主控系统进行一次时间再同步。如果不做这个同步的动作,那么在子系统时钟和主控系统时钟精度不同的情况下,随着业务循环次数增加,时间偏差将积累扩大,一旦经过多次业务循环后发生故障,则两个系统之间日志记录的时序匹配关系会发生偏差,会很大程度上干扰问题定位。
在故障日志记录的功能设计中,都需要对错误进行编号和分类工作,分类的依据有多种,例如有的系统按不同软件模块产生的错误进行编号,有的按不同错误类型进行分类编号。开发人员定义了错误号后,在内部要保留一份错误号列表,便于故障出现后的快速检索。例如可以使用如表1格式:
表1 故障日志中错误的编号和分类
尤其需要注意的是,只要是能够详细区分的错误,应当使用不同的错误号进行标识。否则故障发生后,没有唯一的故障原因与之对应,会给定位带来冗余的工作量。
最后,在多个进程同时运行的情况下,建议每个进程分别记录自己的日志。如果多个进程使用同一个日志文件,往往导致资源冲突,并且各个进程记录的日志相互交错,不便于日后查看。
由于日志记录了整个业务运行流程,所以未经加密保护的日志可能被逆向分析或者被篡改,从而导致系统架构和设计方面的泄密,甚至导致软件系统被攻击,给厂商带来安全隐患和知识产权隐患。例如在电信行业曾经出现过由于充值日志被逆向分析,导致充值卡被伪造盗用的情况。其次,由于日志透露了详细运行信息,尤其是故障发生后,日志记录功能为了便于后续定位,往往全面记录故障发生时系统状态和故障产生原因,一旦被他人获取,可能给相关厂商带来商业上的纠纷和损失。例如某些市场关系不好的客户会根据日志提供的信息夸大造成的损失向供应商提出索赔。在军用方面,日志的泄露,不仅会导致系统设计缺陷暴露,还可能导致军用设备运行状态失密。例如,如果未被加密的日志中记录了设备运行周期时间或GPS定位的位置信息,则一旦信息流失,可能导致设备性能参数被泄露或船舶运行航线被暴露。
故此,很多软件中也开始增强了对日志的保护。不少厂商采用加密方式记录日志,用普通软件无法查看其内容,必须通过厂商提供的专有软件进行解密处理。
控制系统运行时,产生的日志和告警信息很多,如果都向上位机控制台转发,存在如下弊端,
(1)重要信息往往被海量的普通信息所掩盖,无法引起运维人员关注,增大了系统失控的概率;
(2)日志中大量出现的告警会引起用户的反感,甚至给客户带来软件质量不稳定的感觉。
所以,日志记录使用分级和开关控制显示已成为软件设计的必要因素。国外成熟的系统软件,都对日志和告警进行了分级定义,仍以syslog为例,将日志分为八种安全级别,分别代表不同的含义,如表2。
对于日志级别为0-3的消息,系统应该以非常明显的显示方式和提醒方式将信息传递给运维人员,例如用红色或大字体传递到上位机,同时发出声音告警,甚至可以发送警告短信到运维人员手机,启动呼叫中心来呼叫运维人员。
表2 八种安全级别日志的含义
对于3级以下的信息,可根据实际需求,使用开关进行控制日志显示。例如处于5级的通知信息,就可以考虑在平时关闭显示开关而不上传到上位机,仅录入日志文件。处于6-7级的信息,只在软件调测或联调故障定位时才打开记录开关,平时运行时关闭,软件不会把调试信息记录到文件中,从而节省了存储空间和处理时间。
另外,随着积累的日志不断增加,未来对数据进行整理和分析的需要也会产生。对于长期运行的控制系统,建议在记录日志信息时使用标准化的格式,便于后续使用分析软件做数据挖掘和智能分析。
由于控制系统软件不是孤立对象,其运行与操作系统和系统所在的硬件平台有密切的关系,尤其是嵌入式系统,更需要仔细分析。
虽然目前计算机的存储能力在不断增强,但在很多情况下,从设备占用空间和成本考虑,还是有不少系统提供的存储空间容量有限。如一些ARM板上使用的存储芯片,只有几 k的存储空间,一旦记录内容过多,就可能造成空间不足而记录失败。在这种情况下,要考虑精简日志内容或者使用压缩编码格式减少空间占用。
此外,基于某些操作系统运行的软件,如果长时间记录日志,会导致日志文件过大,延长了后续写日志时每次打开文件的速度。甚至当文件大小超过了操作系统可以支持上限时,(例如某些版本的UNIX文件,如果不打开大文件开关,会有2G大小的限制),可能会发生日志文件被破坏或者日志记录丢失的情况。
故此,对长期运行且产生日志数据量大的系统,要设计定期备份和压缩打包机制,将历史日志记录转存到系统外大容量介质(如磁带机)上。
一些存储芯片或者操作系统,在每次重新记录数据时,使用的写入机制不是清空机制而是复写机制。即存储介质上的旧数据不被清除,而使用从首地址记录,用新记录来覆盖掉原记录。例如在某嵌入式软件设计中,使用了flash芯片做日志存储,整个业务处于不断循环中。在每次循环开始时,都重新记录日志。由于使用了覆写机制,刚开始多次业务流程都是成功的,但在某次业务循环过程中发生了故障,系统日志记录也被中断,结果发现旧的运行成功的日志记录的后半部分和新的业务运行的前半部分混淆在一起。加上只在每次业务循环开始时加上了时间戳,在后续日志记录过程中没有继续加时间戳。故此,当故障发生后,定位缺陷时,查看日志仍以为是一份成功的日志,无法确认程序运行时发生故障的时间以及存在缺陷的代码。如图1所示。
图1 存在缺陷的日志
为避免这种情况发生,一般建议在记录每条日志信息时,带上时间戳标记。如图2所示。
如果实在无法建立时间戳标记,可以通过日志流水号来区分。具体做法是,在存储区使用一个专用地址区来记录当前最大的日志流水号。在每次需要记录日志时,将最大流水号累加,更新到专用地址区,此最大流水号也包含在日志记录中。这样即使采用覆盖式写入的日志内容,也能够通过不同的流水号段来区分,如图3所示。
图2 带时间戳标记日志
图3 流水号日志
日志模块对此部分过程处理的流程图如图4。
在很多实时控制系统中,由于系统硬件性能的限制,随着日志内容的增加,记录的读写速度会越来越低,而且频繁打开和关闭文件也会占用系统资源。在这种情况下,也要考虑精简日志,或者通过日志开关减少不重要的日志记录动作。
可以考虑的一种设计是,在系统具有富余内存时,可借助更高性能的内存来提高日志记录效率。例如程序在进行业务流程循环时,如果内存足够大,可以申请一块内存作为内存日志记录区。每次业务流程开始后,通过日志开关控制将当前日志信息写入内存日志区,等业务流程处理结束时,切换日志开关,一次性将内存日志记录到硬盘中,形成硬盘上的日志文件。当业务流程处理过程中发生中断或者错误时,也通过控制参数调用日志记录函数或方法,一次性将内存日志记录到硬盘中。日志函数的流程图如图5。
这样处理的方式有几个优点:
1) 写入内存速度比硬盘速度高近百倍,因此整个日志记录过程速度会更快。
2) 一次性写入日志到日志文件,减少了频繁打开关闭日志文件的次数,提高了处理效率;
3)节约了硬盘空间,因为在业务运行正常的情况下只记录关键信息,不必记录过多细节。
4)在业务流程处理失败的情况下保留了足够多的用于故障定位的日志信息。
图4 流水号日志的流程图
这种处理方式的唯一风险是系统突然掉电导致内存日志区的数据丢失。折中的处理办法是每次在业务循环处理中的关键节点处将内存日志区的内容写入硬盘中,适当牺牲小部分性能来换取可靠性。
日志的主要功能是记录系统运行状态以及为调测和故障定位提供必要信息,是软件设计中需要考虑的重要部分。在进行日志功能设计时,除了常规的运行记录需求外,还必须结合软硬件环境和特定客户需求,从内容、格式、实现方法等多方面考虑,才能在后续的调测、联调、正式运行中发挥功用。本文中提到的日志功能的设计要点及实现方式,在控制系统的软件设计上是需要经常加以关注的。
图5 日志函数的流程图
[1]www.w3.org. RFC3164. 2001年8月.
[2]谢美意 朱虹 冯玉才. 自修复数据库系统日志机制研究. 计算机科学 2010年4月.
[3]王伟 杨永川. Windows Vista系统日志文件格式分析及数据恢复. 计算机安全, 2009年4月.
[4]JIM TURNER. 确保成功地备份事件日志的一种方法. Windows IT Pro magazine, 2008年8月.
[5]李甜. 基于 Syslog的日志审计系统的研究和实现.中国新通讯, 2008年9月.