日志模块是被频繁反复制作的轮子之一。即使了有了log4cpp、log4C等,新的日志库仍不断出现。但与这些复杂的模块相比,很多时候仍只愿意用printf。SCADA系统是一种实时系统,日志仅用来在异常情况下分析问题,如果在保证实时性的同时加一些调试手段,是设计日志模块的初衷。
一、约束
日志模块不属于核心模块,对于现有的SCADA系统,如果增加了日志模块,不能影响的是:
1) 效率:增加日志后,系统的处理效率不至于有明显的下降;系统部分时间敏感的处理过程不应受影响
2) 安全:不能因为增加日志导致某些未知的错误,日志处理应越简单越好。
可以想象,现有的程序中或多或少已经有些一些日志处理,比如printf或者输出到文件。应可方便的从已有的日志处理切换到新的统一日志模块,以减少阻力。
3) 调用简单:日志接口使用应符合使用习惯。不应有过多的初始化、清理等过程。简单的一行函数调用即可。
此外,由于系统的特性,还必须做到:
4) 多进程/多线程处理:由于是平台公用的日志模块,因此必须支持多进程/多线程处理。
5) 异常时的日志保存:SCADA系统的日志极多,正常情况下,只保存高级别的日志即可;但在异常情况下,如进程异常退出,应能保存这一时间内的全部日志,以供分析。
二、设计
1. 共享内存的引入
一个简单的日志函数可能如下:
int DebugLog( char* format , ... );
调用这个函数后,实际输入的信息包括:
1) 日志等级:常用的日志分级一般包括TRACE,DEBUG,INFO,WARN,ERROR,FATAL。DebugLog意即属于DEBUG级别。
2) 日志分类:上述函数未对日志进行分类,可以认为是采用了默认的分类。分类用来区别不同的进程或者进程内的不同处理。
3) 日志内容:format等
4) 附加信息:__FILE__和__LINE__等。
在调用该函数后,一般进行的处理如下:
1) 格式化日志内容
2) 附加当前时标
3) 输出,比如到文件,控制台,syslog,或者网络分发等。
在以上处理过程中,日志的输出占用较多的资源。因此我们希望,能将日志的输入和输出分开,即接口调用者(应用程序)只管日志的生成,而日志服务负责日志的存储和显示。两者之间通过共享内存进行通讯,如下图。
对于日志接口而已,只是把日志格式化、附加时标并输出到共享内存中。
2. 共享内存的设计
在日志处理中当然不希望有锁的存在。频繁的获取锁或释放锁,可能会占用大部分时间;但又很难实现一个完全无锁的日志模块。因此我们希望折衷处理。我们把日志共享内存分成许多段,这样在写入日志时,可针对某个共享内存段进行加锁,而不是针对全部共享内存加锁。段与段之间互不影响。
每个内存段都是一块循环日志缓冲区,可以保持固定条数的日志(比如,1万条),遵循FIFO原则。如果日志写入的速度高于日志存储服务的存储速度,是会造成日志的遗失。但我们可以通过以下方式来预防该问题的出现:
1)内存段可保存日志的条目数只能在编译前设定,或者在配置文件中设定。对于某段时间内某个内存段日志条目不够用的情况,可以增加二级缓存。
2) 每个日志分类均有一个显示等级,一个保存等级。只有不低于显示等级或不低于保存等级的日志才需要进共享内存。这样在日志显示界面不启动的情况下,一般只有WARN等级的日志才会输出。
3. 内存段分配的竞争机制
每个内存段可以供一个日志分类或多个日志分类使用;而应用程序所能使用的日志分类数量也是不限制的。理论上如果分配的内存段足够多,达到每个日志分类占用一个内存段,而每个线程使用一个日志分类的话,就会类似于无锁。但是,有以下问题导致无法实现:
1) 进程的开发和日志模块应该尽量的解耦,有哪些日志分类预先是不知道的;
2) 某些进程才是偶尔运行,分配一个内存段太奢侈了;
3) 日志分类个数会相当多,内存不是无限;
因此,需要一种分配机制,在日志分类较少的情况下,每个分类一个内存段;在日志分类较多的情况下,一段时间内日志数量较少的分类共用内存段;某个分类较长时间内无日志产生,可回收该分类占用的日志段。
当某个应用程序启动时,初始化时主动申请内存段,如果存在未使用的内存段则使用之,如果没有未使用的内存段,则使用内存段零。当然,这些都封装在接口中,调用者并不用关心这些问题。
日志存储服务进程定时对内存段的分配进行调度,调度依据为对每个分类某一时间内的日志数量n,每个分类上次输入日志的时间t。
最终达到如下效果:
1)当内存段个数>=当前运行的日志分类,每个日志分类均具备独立的内存段;
2)当内存段个数<当前运行的日志分类,由一段时间内日志数量较少的分类占用同一个内存段。
3)日志存储服务和应用进程的先后启动是没有影响的。
4. 日志存储服务
日志存储服务作为一个7X24小时运行的进程,主要用来调节内存段的分配,并进行日志的保存。日志的保存可以有多种方式,比如:
1)STDOUT或STDERR
2)文件,固定大小或按日保存
3)Syslog(Unix)和事件日志(Windows)
4)分发到其他网络服务
5)……
此外,也可以定时检测某些进程是否存在,如果进程退出则把该进程相关分类的日志保存下来。
5. 日志显示及设置界面
一个简单的界面,用来设置日志分类的显示等级,并显示当前的日志。同时也可以用来进行一些配置,如保存等级的设置。