syslog-ng基础
http://tsecer.blog.163.com/blog/static/15018172012615114243736/
一、syslog
通常对于前台任务来说,可以方面的将自己想表达的意思通过printf打印到标准输出之上,但是后台任务是不食人间烟火的,它在创建的开始执行了一些比较极端的动作来保持自己的先进性,例如通过fork+exit来欺骗父进程完成标准终端的释放,通过close来关闭自己所有的文件描述符,通过chdir将自己的工作路径设置到根目录等。
这些做法都很好,但是当它真正想说些什么东西的时候,问题就来了。例如它运行的过程中发现了一些需要注意的信息,或者说自己处理不了的信息,那么就需要通过打印一些警告或者是错误信息来告诉外界自己的内部出了什么问题,大家可以认为是小道消息,不会是来自CCAV的报道了。
syslog当时的设计还是能够满足需要的,而且在C库封装了几个比较友好的接口,例如openlog、syslog、closelog 三个接口来进行日志记录。关于这个syslog,大家要和内核中的sys_syslog区分开来,内核中的这个主要是操作和控制内核打印的信息。因为内核也是一个苦鳖的货,它有些话要说也不是可以随随便便就喷薄而出的,它一般是通过printk打印,具体打印到哪里,这里就不再细究了。
二、syslog简单功能说明
在linux下的man看一下这些函数的说明
void openlog(const char *ident, int
option, int
facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
这里看到的option和facility的命名还是比较准确的,这个准确是相对于C库中的命名,由于C库的命名不是很准确,我们这里就不再列出来了。这里的逻辑是这样的:openlog是一个比较统领性的操作,因为一次openlog可以被任意多的syslog来使用,而这些syslog都会受到这个openlog的影响,这一点是一个比较直观的基本常识,所以不再解释。
正如之前所说的,这个option是一个选项,就是影响日志记录的一些行为,或者说是影响“动作”的一类设置,或者我们可以看做是oepn系统调用的一些只读、只写等标志位,而之后的facility则是一些“描述”性设置,它设置的内容将会作为一些描述信息体现在每条日志的记录中。
下面是一些可能的option
#define LOG_PID 0x01 /* log the pid with each message */
#define LOG_CONS 0x02 /* log on the console if errors in sending */
#define LOG_ODELAY 0x04 /* delay open until first syslog() (default) */
#define LOG_NDELAY 0x08 /* don't delay open */
#define LOG_NOWAIT 0x10 /* don't wait for console forks: DEPRECATED */
#define LOG_PERROR 0x20 /* log to stderr as well */
接下来一些可能的设备类型
/* facility codes */
#define LOG_KERN (0<<3) /* kernel messages */
#define LOG_USER (1<<3) /* random user-level messages */
#define LOG_MAIL (2<<3) /* mail system */
#define LOG_DAEMON (3<<3) /* system daemons */
#define LOG_AUTH (4<<3) /* security/authorization messages */
#define LOG_SYSLOG (5<<3) /* messages generated internally by syslogd */
#define LOG_LPR (6<<3) /* line printer subsystem */
#define LOG_NEWS (7<<3) /* network news subsystem */
#define LOG_UUCP (8<<3) /* UUCP subsystem */
#define LOG_CRON (9<<3) /* clock daemon */
#define LOG_AUTHPRIV (10<<3) /* security/authorization messages (private) */
#define LOG_FTP (11<<3) /* ftp daemon */
/* other codes through 15 reserved for system use */
#define LOG_LOCAL0 (16<<3) /* reserved for local use */
#define LOG_LOCAL1 (17<<3) /* reserved for local use */
#define LOG_LOCAL2 (18<<3) /* reserved for local use */
#define LOG_LOCAL3 (19<<3) /* reserved for local use */
#define LOG_LOCAL4 (20<<3) /* reserved for local use */
#define LOG_LOCAL5 (21<<3) /* reserved for local use */
#define LOG_LOCAL6 (22<<3) /* reserved for local use */
#define LOG_LOCAL7 (23<<3) /* reserved for local use */
还有一点,
就是openlog中设置的facility参数并不是一成不变的,如果你觉得默认的openlog中的facility不满足你的要求,那么在执行syslog的时候同样可以在第一个参数中设置facility的类型,因为毕竟定制的设置都会高于通用的设置,例如make中构建目标的选择都是通过首先完成最准确匹配,然后才是通用配置的匹配。glibc中syslog相关代码为
/* Set default facility if none specified. */
if ((pri & LOG_FACMASK) == 0)这里pri就是syslog传递的第一个参数,如果在syslog中指定了facility的类型,那么覆盖openlog中默认值。
pri |= LogFacility;
三、syslog实现简单分析
1、openlog
if (ident != NULL)
LogTag = ident;
LogStat = logstat;
if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
重新设置各种标志位。
LogFacility = logfac;
……
if (LogFile == -1) {
文件如果已经打开,不重复打开。
SyslogAddr.sun_family = AF_UNIX;
(void)strncpy(SyslogAddr.sun_path, _PATH_LOG,
sizeof(SyslogAddr.sun_path));
……
if (LogFile != -1 && !connected)
如果已经connect,不再connect
{
int old_errno = errno;
if (__connect(LogFile, &SyslogAddr, sizeof(SyslogAddr))
== -1)
其中
#define _PATH_LOG "/dev/log"
static int LogType = SOCK_DGRAM; /* type of socket connection */
也就是默认是通过dgram格式向/deg/log中发送日志内容。
2、日志记录
__fsetlocking (f, FSETLOCKING_BYCALLER);
prioff = fprintf (f, "<%d>", pri);
首先是<facility+priority>组合的数值表示。
(void) time (&now);
f->_IO_write_ptr += __strftime_l (f->_IO_write_ptr,
打印格式化后本地时间,对于一个日志来说,记录时间大家是没有异议的吧?
f->_IO_write_end
- f->_IO_write_ptr,
"%h %e %T ",
__localtime_r (&now, &now_tm),
_nl_C_locobj_ptr);
msgoff = ftell (f);
if (LogTag == NULL)
LogTag = __progname;
记录程序名字,这个就是openlog中的第一参数,
if (LogTag != NULL)
fputs_unlocked (LogTag, f);
if (LogStat & LOG_PID)
如果有LOG_PID,记录进程名,包含在中括号中,和之前的尖括号区别。
fprintf (f, "[%d]", (int) __getpid ());
if (LogTag != NULL)//
冒号加空格开始syslog中传递内容。
{
putc_unlocked (':', f);
putc_unlocked (' ', f);
}
四、syslog-ng
该功能是对syslog功能的扩展,它在一些比较复杂的网络布局中常有用到,因为很多日志不仅需要记录到本地,还需要通过网络集中发送到专门的日志服务器上,所以这种最为原始的syslog可能就无法满足需要。
syslog-ng的源代码并不复杂,这里说不复杂是从代码量上来看,但是它里面用到了一些不太常用的功能,例如gtk中的多源事件循环功能、各种yacc语法文件对配置文件的解析等,所以事实上它的代码还是不太容易理解的。这里再次说明,代码本身一般都不会太复杂,复杂的是代码之外表达的意境。
1、各种语法文件
其主控语法文件位于cfg-grammar.y文件中,然后一些相对比较独立的功能放入各个对应的语法文件,例如我们比较关心的过滤语法配置位于filter-expr-grammar.y文件。而对于主要的log语法,其定义位于主控文件
log_items
: log_item log_semicolons log_items { log_pipe_item_append($1, $3); $$ = $1; }
| { $$ = NULL; }
;
log_item
: KW_SOURCE '(' string ')' { $$ = log_pipe_item_new(EP_SOURCE, $3); free($3); }
| KW_FILTER '(' string ')' { $$ = log_pipe_item_new(EP_FILTER, $3); free($3); }
| KW_PARSER '(' string ')' { $$ = log_pipe_item_new(EP_PARSER, $3); free($3); }
| KW_REWRITE '(' string ')' { $$ = log_pipe_item_new(EP_REWRITE, $3); free($3); }
| KW_DESTINATION '(' string ')' { $$ = log_pipe_item_new(EP_DESTINATION, $3); free($3); }
;
……
log_semicolons
: ';'
| ';' log_semicolons
;
所以log中的各项必须使用分号';'分割开来,否则有语法错误的哦,亲。
2、filter中的match语法
| KW_MATCH '(' string
{
last_re_filter = (FilterRE *) filter_match_new();
}
filter_match_opts ')'
{
if(!filter_re_set_regexp(last_re_filter,
$3))
YYERROR;
free($3);
$$ = &last_re_filter->super;
if (last_re_filter->value_handle == 0)
{
static gboolean warn_written = FALSE;
if (!warn_written)
{
msg_warning("WARNING: the match() filter without the use of the value() option is deprecated and hinders performance, please update your configuration",
NULL);
warn_written = TRUE;
}
}
}
……
filter_re_opt
: KW_TYPE '(' string ')'
{
filter_re_set_matcher(last_re_filter, log_matcher_new($3));
free($3);
}
|
KW_FLAGS '(' regexp_option_flags ')' { filter_re_set_flags(last_re_filter, $3); }
;
其中正则表达是的处理为
gboolean
filter_re_set_regexp(FilterRE *self, gchar *re)
{
if(!self->matcher)
self->matcher =
log_matcher_posix_re_new();
return log_matcher_compile(self->matcher, re);
}
LogMatcher *
log_matcher_posix_re_new(void)
{
LogMatcherPosixRe *self = g_new0(LogMatcherPosixRe, 1);
log_matcher_init(&self->super, LMR_POSIX_REGEXP);
self->super.compile =
log_matcher_posix_re_compile;
}
static gboolean
log_matcher_posix_re_compile(LogMatcher *s, const gchar *re)
{
LogMatcherPosixRe *self = (LogMatcherPosixRe *) s;
gint rc;
const gchar *re_comp = re;
gint flags = REG_EXTENDED;
if (re[0] == '(' && re[1] == '?')
{
gint i;
for (i = 2; re[i] && re[i] != ')'; i++)
{
if (re[i] == 'i')
{
static gboolean warn_written = FALSE;
if (!warn_written)
{
/* deprecated */
msg_warning("WARNING: Your configuration file uses an obsoleted regexp option, please update your configuration",
evt_tag_str("option", "(?i)"),
evt_tag_str("change", "use ignore-case flag instead of (?i)"),
NULL);
warn_written = TRUE;
}
flags |= REG_ICASE;
}
}
if (re[i])
{
re_comp = &re[i + 1];
}
else
{
msg_error("Invalid regexp flags",
evt_tag_str("re", re),
NULL);
return FALSE;
}
}
if (self->super.flags & LMF_ICASE)
flags |= REG_ICASE;
if (self->super.flags & LMF_NEWLINE)
flags |= REG_NEWLINE;
if ((self->super.flags & (LMF_MATCH_ONLY + LMF_STORE_MATCHES)) == LMF_MATCH_ONLY)
flags |= REG_NOSUB;
rc =
regcomp(&self->pattern, re_comp, flags);
这个regcomp就是glibc中提供的正则表达式功能,大家可以在linux下man一下这个函数。
if (rc)
{
gchar buf[256];
regerror(rc, &self->pattern, buf, sizeof(buf));
msg_error("Error compiling regular expression",
evt_tag_str("re", re),
evt_tag_str("error", buf),
NULL);
return FALSE;
}
return TRUE;
}
3、正则表达式标志字符的解析
regexp_option_flags
: string regexp_option_flags { $$ =
log_matcher_lookup_flag($1) | $2; free($1); }
| { $$ = 0; }
;
gint
log_matcher_lookup_flag(const gchar* flag)
{
if (strcmp(flag, "global") == 0)
return LMF_GLOBAL;
else if (strcmp(flag, "
icase") == 0 || strcmp(flag, "
ignore-case") == 0 || strcmp(flag, "ignore_case") == 0)
这说明syslog-ng的正则表达式是区分大小写的。
return LMF_ICASE;
else if (strcmp(flag, "newline") == 0)
return LMF_NEWLINE;
else if (strcmp(flag, "unicode") == 0 || strcmp(flag, "utf8") == 0)
return LMF_UTF8;
else if (strcmp(flag, "store-matches") == 0 || strcmp(flag, "store_matches") == 0)
return LMF_STORE_MATCHES;
else if (strcmp(flag, "substring") == 0)
return LMF_SUBSTRING;
else if (strcmp(flag, "prefix") == 0)
return LMF_PREFIX;
else
return 0x0;
}