谈谈Linux kernel的list结构

目录

最近在解决关于关于cfg80211.ko的问题,需要阅读net/wireless目录下的代码,看到如下代码感觉有点懵逼:
include/net/regulatory.h

1
2
3
4
5
6
7
8
9
10
11
12
struct regulatory_request {
struct rcu_head rcu_head;
int wiphy_idx;
enum nl80211_reg_initiator initiator;
enum nl80211_user_reg_hint_type user_reg_hint_type;
char alpha2[2];
enum nl80211_dfs_regions dfs_region;
bool intersect;
bool processed;
enum environment_cap country_ie_env;
struct list_head list;
};

net/wireless/reg.c

1
2
3
4
5
6
7
8
9
10
11
12
13
static LIST_HEAD(reg_requests_list);
...
static void queue_regulatory_request(struct regulatory_request *request)
{
request->alpha2[0] = toupper(request->alpha2[0]);
request->alpha2[1] = toupper(request->alpha2[1]);
spin_lock(&reg_requests_lock);
list_add_tail(&request->list, &reg_requests_list);
spin_unlock(&reg_requests_lock);
schedule_work(&reg_work);
}

list_add_tail(&request->list, &reg_requests_list); 这行代码把request->list成员的地址(而不是request结构的地址)加到以reg_list_list为head节点的链表中,那么后面怎么访问除了list外的其他成员呢?且看下文分解:
net/wireless/reg.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void reg_process_pending_hints(void)
{
struct regulatory_request *reg_request, *lr;
lr = get_last_request();
/* When last_request->processed becomes true this will be rescheduled */
if (lr && !lr->processed) {
reg_process_hint(lr);
return;
}
spin_lock(&reg_requests_lock);
if (list_empty(&reg_requests_list)) {
spin_unlock(&reg_requests_lock);
return;
reg_request = list_first_entry(&reg_requests_list,
struct regulatory_request,
list);
list_del_init(&reg_request->list);
spin_unlock(&reg_requests_lock);
reg_process_hint(reg_request);
}

list_first_entry方法用于访问reg_requests_list为head的链表的首元素,继续跟踪下其实现,如下(以下代码片段位于include/linux/list.h):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* list_first_entry - get the first element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*
* Note, that list is expected to be not empty.
*/
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)

接近真相了,奥秘就在container_of这个方法中:
include/stddef.h

1
2
3
4
5
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif

include/kernel.h

1
2
3
4
5
6
7
8
9
10
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr);
(type *)( (char *)__mptr - offsetof(type,member) );})

结合一下,就是

1
2
#define container_of(ptr, type, member) \
(type *)((char *)(ptr) - (char *) &((type *)0)->member)

看到这句代码(char ) &((type )0)->member), 还能这样玩,顿时感觉c的指针确实能做很多事,像magic一样,研究了下,这句代码是用于获取type结构的member成员的相对首地址的偏移。
所以kernel中list使用原理:获取结构中list成员的地址,然后-list成员在结构中的相对偏移,从而得到type结构的地址,很巧妙。

下面是我的一段测试代码,不明白的话可以再自己研究下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Returns a pointer to the container of this list element.
*
* Example:
* struct foo* f;
* f = container_of(&foo->entry, struct foo, entry);
* assert(f == foo);
*
* @param ptr Pointer to the struct list_head.
* @param type Data type of the list element.
* @param member Member name of the struct list_head field in the list element.
* @return A pointer to the data struct containing the list head.
*/
#include <stdio.h>
#ifndef container_of
#define container_of(ptr, type, member) \
(type *)((char *)(ptr) - (char *) &((type *)0)->member)
#endif
struct entry{
int a;
};
struct foo{
int data;
int key;
struct entry list;
};
int main()
{
struct foo f;
struct foo *ptr;
ptr = container_of(&(f.list), struct foo, list);
printf("ptr address of foo: %p \n", (char *)&f);
printf("ptr address of foo list : %p \n",(char *) &(f.list));
printf("ptr address of 0->member: %p \n", (char *)&((struct foo*)0)->list);
if (ptr == &f)
{
printf("return ok\n");
return 0;
}
return -1;
}

编译运行后,输出:

1
2
3
4
ptr address of foo: 0x7fff587c1af0
ptr address of foo list : 0x7fff587c1af8
ptr address of 0->member: 0x8
return ok

memcache、redis原理对比

目录
  1. memcache和redis的分布式实现对比

分享一篇文件
memcache、redis原理对比

memcache和redis的分布式实现对比

  • memcache分布式实现
    memcached的分布式算法存在于客户端,即由客户端根据分布式算法找到对应的memcached服务器,然后发起请求。
  • Redis的分布式实现
    2.8以前的版本:与Memcached一致,可以在客户端实现,也可以使用代理,twitter已开发出用于Redis和Memcached的代理Twemproxy 。
    3.0 以后的版本:相较于Memcached只能采用客户端实现分布式存储,Redis则在服务器端构建分布式存储。Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本,它没有中心节点,各个节点地位一致,具有线性可伸缩的功能。如图给出Redis Cluster的分布式存储架构,其中节点与节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。在数据的放置策略上,Redis Cluster将整个 key的数值域分成16384个哈希槽,每个节点上可以存储一个或多个哈希槽,也就是说当前Redis Cluster支持的最大节点数就是16384

谈谈libevent

目录
  1. 简介
  2. libevent 源码分析

简介

libevent是一套跨平台的事件处理接口的封装,能够兼容包括这些操作系统:
Windows/Linux/BSD/Solaris等操作系统的事件处理。

libevent的一些常用接口包括:
poll、select(Windows)、epoll、kqueue(BSD)、/dev/pool(Solaris)

libevent中文帮助文档

哪些软件使用了libevent:

  1. Memcached使用libevent来进行网络并发连接的处理,能够保持在很大并发情况下,仍然能否保持快速的响应能力(所以安装memcached需要先安装libevent)

libevent 源码分析

nginx

目录
  1. nginx安装
  2. nginx模块

nginx安装

  • 下载链接:
    http://nginx.org/download/
    依赖库zlib, pcre

  • 编译及安装
    ./configure –prefix=/home/mycount/software/nginx-1.13.6 –with-pcre=/home/mycount/pcre-8.39 –with-zlib=/home/mycount/Downloads/zlib-1.2.11
    make install
    其中pcre-8.39及zlib-1.2.11 为pcre和zlib的源码目录(注意是源码目录)

  • 运行
    启动和停止nginx需要以root身份运行,或者使用sudo命令
    sbin/nginx #使用默认的配置文件conf/nginx.conf

  • 验证安装
    nginx的默认配置文件开启了localhost:80服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@localhost ~]# curl -v -o /dev/null http://localhost/index.html
* About to connect() to localhost port 80 (#0)
* Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /index.html HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.21 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: localhost
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Server: nginx/1.13.6
< Date: Tue, 05 Dec 2017 14:35:32 GMT
< Content-Type: text/html
< Content-Length: 169
< Connection: keep-alive
<
{ [data not shown]
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
169 169 169 169 0 0 8154 0 --:--:-- --:--:-- --:--:-- 0* Connection #0 to host localhost left intact
* Closing connection #0

使用ps命令也可以看到所有nginx进程,也可以验证nginx是否正常运行:

1
2
3
4
ps aux |greo nignx
[root@localhost 13.6]# ps aux |grep nginx
root 109323 0.0 0.0 15768 624 ? Ss 22:34 0:00 nginx: master process /home/hy/software/nginx-1/13.6/sbin/nginx
nobody 109324 0.0 0.1 16212 1532 ? S 22:34 0:00 nginx: worker process

nginx模块

编译
[root@localhost nginx-1.13.6]# ./configure –prefix=/home/hy/software/nginx-1.13.6 –add-module=/home/hy/Documents/nginx-1.13.6/src/test/ –with-pcre=/home/hy/Downloads/pcre-8.39 –with-zlib=/home/hy/Downloads/zlib-1.2.11

编译时会提示:
configuring additional modules
adding module in /home/hy/Documents/nginx-1.13.6/src/test/

  • ngx_http_hello_module was configured
    creating objs/Makefile

如何支持C++文件?
需要简单了解下nginx的编译过程
1、直接修改Makefile文件
2、通过修改auto目录下的文件

使用
使用一个模块需要根据这个模块定义的配置指令来做。比如我们这个简单的hello handler module的使用就很简单。在我的测试服务器的配置文件里,就是在http里面的默认的server里面加入如下的配置:

location /test {
hello_string jizhao;
hello_counter on;
}
当我们访问这个地址的时候, lynx http://127.0.0.1/test的时候,就可以看到返回的结果。

jizhao Visited Times:1

当然你访问多次,这个次数是会增加的。

nginx

目录
  1. nginx模块
    1. nginx模块的组成要素?
    2. 如何编写一个nginx模块?
      1. 为模块添加config脚本及编译
  2. nginx中几个重要的模块分析

nginx模块

nginx模块的组成要素?

  1. 模块数据结构ngx_module_t,用于集成模块
  2. 模块配置指令数组ngx_command_t
  3. 具体的模块,如HTTP模块ngx_http_module_t
  4. config配置文件,声明模块名及源码路径

需要掌握的知识结构:

  • http请求处理阶段
  • 组成要素中列举的关键数据结构
  • nginx基本编程风格及常用函数及类型

如何编写一个nginx模块?

####定义模块的配置指令数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static ngx_command_t ngx_http_hello_commands[] = {
{
ngx_string("hello_string"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
ngx_http_hello_string,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_hello_loc_conf_t, hello_string),
NULL },
{
ngx_string("hello_counter"),
NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_http_hello_counter,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_hello_loc_conf_t, hello_counter),
NULL },
ngx_null_command
};

其中ngx_http_hello_loc_conf_t为配置数据对应的数据结构,其定义如下:

1
2
3
4
5
typedef struct
{
ngx_str_t hello_string;
ngx_int_t hello_counter;
}ngx_http_hello_loc_conf_t;

####定义HTTP模块

1
2
3
4
5
6
7
8
9
10
static ngx_http_module_t ngx_http_hello_module_ctx = {
NULL, /* preconfiguration */
ngx_http_hello_init, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_hello_create_loc_conf, /* create location configuration */
NULL /* merge location configuration */
};

其中ngx_http_hello_init函数将在nginx配置解析阶段调用,向nginx框架注册我们自己的处理函数(ngx_http_hello_handler,注册到NGX_HTTP_CONTENT_PHASE阶段),其实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static ngx_int_t
ngx_http_hello_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;
//ngx_http_core_module模块负责管理所有的http功能模块,其对应的域为main
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_hello_handler;
return NGX_OK;
}

ngx_http_hello_handler的实现如下(实现访问次数统计和简单的信息回复):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
static ngx_int_t
ngx_http_hello_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_hello_loc_conf_t* my_conf;
u_char ngx_hello_string[1024] = {0};
ngx_uint_t content_length = 0;
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "ngx_http_hello_handler is called!");
my_conf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
if (my_conf->hello_string.len == 0 )
{
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string is empty!");
return NGX_DECLINED;
}
if (my_conf->hello_counter == NGX_CONF_UNSET
|| my_conf->hello_counter == 0)
{
ngx_sprintf(ngx_hello_string, "%s", my_conf->hello_string.data);
}
else
{
ngx_sprintf(ngx_hello_string, "%s Visited Times:%d", my_conf->hello_string.data,
++ngx_hello_visited_times);
}
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string:%s", ngx_hello_string);
content_length = ngx_strlen(ngx_hello_string);
/* we response to 'GET' and 'HEAD' requests only */
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
/* discard request body, since we don't need it here */
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
/* set the 'Content-type' header */
/*
*r->headers_out.content_type.len = sizeof("text/html") - 1;
*r->headers_out.content_type.data = (u_char *)"text/html";
*/
ngx_str_set(&r->headers_out.content_type, "text/html");
/* send the header only, if the request type is http 'HEAD' */
if (r->method == NGX_HTTP_HEAD) {
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = content_length;
return ngx_http_send_header(r);
}
/* allocate a buffer for your response body */
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* attach this buffer to the buffer chain */
out.buf = b;
out.next = NULL;
/* adjust the pointers of the buffer */
b->pos = ngx_hello_string;
b->last = ngx_hello_string + content_length;
b->memory = 1; /* this buffer is in memory */
b->last_buf = 1; /* this is the last buffer in the buffer chain
/* set the status line */
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = content_length;
/* send the headers of your response */
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
/* send the buffer chain of your response */
return ngx_http_output_filter(r, &out);
}

ngx_http_hello_create_loc_conf函数用于在配置解析时使用内存池创建对象(nginx要求模块自己分配配置数据结构的内存),其实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_hello_loc_conf_t* local_conf = NULL;
local_conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
if (local_conf == NULL)
{
return NULL;
}
ngx_str_null(&local_conf->hello_string);
local_conf->hello_counter = NGX_CONF_UNSET;
return local_conf;
}

####集成配置指令
ngx_module_t是Nginx真正定义模块的数据结构,它集成了ngx_http_module_t和ngx_commond_t数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ngx_module_t ngx_http_hello_module = {
NGX_MODULE_V1,
&ngx_http_hello_module_ctx, /* module context */
ngx_http_hello_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};

为模块添加config脚本及编译

nginx要求模块的Shell脚本名字必须是config,里面使用特定的Shell变量告诉configure模块相关的信息,其config文件如下

1
2
3
ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"

编译命令

./configure --prefix=$nginx_src_home/nginx-1.13.6 --add-module=$nginx_module_src/test/ --with-pcre=$HOME/pcre-8.39 --with-zlib=$HOME/zlib-1.2.11

其中nginx-1.13.6版本依赖于pcre和zlib,所以configure的时候要加上路径

nginx中几个重要的模块分析

upstream模块

load-balance模块

filter模块

http模块

protocol-buffer

目录
  1. protocol-buffer简介
  2. 使用方式
  3. 应用举例:

protocol-buffer简介

protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。

一般c/s模式下的数据传输方式主要有xml(可读性好,但是带宽高),json(可读性次于xml,但是带宽优于xml), 二进制(需要c/s通信双方约定传输的数据格式,可读性差,但是带宽最低)。protocol-buffer则属于二进制方式,其相比xml及json的优势是传输效率更高。

使用方式

  1. 下载protocol-buffer源码,搭建使用环境;详情参考

Protocol Buffers - Google’s data interchange format

  1. 定义.proto文件
  2. 编译 .proto 文件,将生成.pb.h 和.pb.cc两个文件
  3. 利用.pb.h中生成的对象及方法 编写 writer 和 Reader

参考: Google Protocol Buffer 的使用和原理

应用举例:

使用protocol-buffer在C/S通信中传递信息,能够将信息进行格式化(发生端对消息进行Serialize,接收端进行DeSerialize,还原消息很方便),一个使用protocol-buffer进行tcp socket通信的例子如下:
Protocol Buffer over socket in C++

c++11新特性

c++11相对c++99引入了很多新特性(很多特性是从boost库引入的),详细说明可以查看C++ primer 5
下面列出一些个人认为比较实用的新特性:

  • 智能指针
    将boost库的shared_ptr和unique_ptr引入到c++标准库
  • 范围for语句
    语法如下(java类似):
1
2
for(declaration: expression)
statement

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>
using namespace std;
int main() {
int a[3] = {1,2,3};
//string a("Hello");
for(auto var: a)
{
cout << var << endl;
}
return 0;
}
  • auto关键字
    支持使用auto关键字自动推断变量类型
  • lambda表达式
    一个lambda表达式表示一个可调用的代码单元(类似于一个匿名函数)。与函数类似,lambda表达式具有一个返回类型,一个参数列表和一个函数体。其具有如下形式:
    capture list->return type {function body}
    其中capture list为捕获的参数列表,即为函数外的变量,通常为空;parameter list为参数列表,可以省略;其中return type为返回值类型,可以省略。

示例代码:

1
2
auto f = [](int a, int b)->int{ return a+b;};
cout << "2+3=" << f(2,3) << endl;
  • 使用 = default生成默认构造函数
    使用场景:在我们即需要其它形式的构造函数(通常为拷贝构造函数),也需要默认的构造函数时。
    示例代码:
1
2
3
4
5
6
7
Class Base{
public:
Base() = default;
Base(int sz): size(sz) {}
private:
int size;
};
  • 移动语义
    为了支持移动操作,新标准引入了一种新的引用类型——右值引用。所谓右值引用就是必须绑定到右值的引用。通过&&来获取右值引用。右值引用有一个重要的性质,只能绑定到一个将要销毁的对象(如形参),因此,我们可以自由地将一个优质引用的资源“移动”到另一个对象中
    例子:
    int i = 1;
    int &&ri = i 42; //因为i 42 会生成个临时的变量,将其移动到ri上
    int &&ri1= i; //错误,不能将一个右值引用绑定到一个左值上。这种其实可以通过move函数来实现,但是使用这种操作后以为着不能再使用i了,所有对i的操作应该变为对ri1操作,有点像auto_ptr指针
    合适使用右值引用能减少不必要的对象构造和析构,提高程序运行的效率,这边就不继续展开了
    另一个与右值引用相关的概念是move函数:move函数告诉编译器,我们有一个左值,但是我们希望像一个右值一样处理他(需要由调用者保证后续不再使用这个左值,因为其会被销毁)
    此外,还有移动构造函数和移动赋值函数

  • 正则表达式
    c++11引入了regex类来处理正则表达式

  • 其它
    还有很多其它的新特性,如随机数,概率分布,final关键字,override指示符(尾置),可变参数模版,mem_fn类膜拜等等

syslog-ng配置语法

目录
  1. 日志级别及日志设备
  2. 使用syslog函数打印日志到syslog
  3. 配置syslog-ng
    1. 基本语法
    2. 格式化输出
    3. 变量
    4. filter
    5. TLS传输
    6. 其它
  4. 结语

在“运行syslog-ng”中我们简单介绍了一个基本配置文件的大概结构,保证syslog-ng能正常运行起来。这边将详细介绍下syslog-ng的日志配置及如何进行高效的配置,先介绍下syslog的一些基本知识:

日志级别及日志设备

syslog-ng和syslog一样,日志级别都有以下8种:
img1

级别越低代表越重要的日志。

设备号有以下几种:
img

由设备号和日志级别我们可以计算出log的级别(facility_num * 8 + level_num)

使用syslog函数打印日志到syslog

了解了日志级别及日志设备后,我们就可以使用syslog函数来往设备(/dev/log)发送日志了(syslog-ng的system()设备其实就是获取送往/dev/log设备的日志,传统的syslog也会监控该设备)。函数用法可以使用man 3 syslog查看:

NAME
   closelog, openlog, syslog, vsyslog - send messages to the system logger

SYNOPSIS
   #include <syslog.h>

   void openlog(const char *ident, int option, int facility);
   void syslog(int priority, const char *format, ...);
   void closelog(void);

使用例子如下:

openlog("redis_server", LOG_PID, 1);  //设置设备号为1,ident(标识为redis_server),option为LOG_PID
syslog(LOG_DEBUG, "%s", “redis_server is started!”); //打印debug日志信息
loselog(); //使用完后关闭

了解完上面知识后,我们来看看如何对syslog-ng进行日志配置:

配置syslog-ng

基本语法

syslog-ng的配置文件为syslog-ng.conf,syslog-ng 运行时会解析该配置文件,然后对日志来源进行监听(看描述符是否可读),再对收集来的日志做一些指定的处理(如过滤,替换,格式化等)后,将日志写往各个destination:
log{
source(s_xxx);
filter(f_xxx);
destination(d_xxx)
};

其中source支持的设备主要有:file,network, tcp, udp, pipe, unix-dgram等
具体参见说明文档:Chapter 6. Collecting log messages — sources and source drivers

destination支持的有:file, network, syslog, redis, sql, hdfs, mongodb
具体参见说明文档:Chapter 7. Sending and storing log messages — destinations and destination drivers
(随着大数据和分布式时代的到来,syslog-ng也增加了对redis,hdfs等的支持,更新很快的,这也是开源的优势)

syslog-ng支持将收集的日志进行不同的处理,然后写入不同的目的地,这时候语法如下:
log{
source(s1);
source(s2);
{filter(f1); destination(d1);}
{filter(f2); destination(d2);}
};

格式化输出

格式化输出就是对捕获的日志进行排版,以一种方便阅读和使用的格式存储日志(如方便使用awk等进行日志诊断)。在日志格式化时,就不能不提syslog协议,其格式如下:

HEADER SYSLOGPRO_VERSION ISOTIMESTAMP HOSTNAME APPLICATION PROC_ID MESSAGEID STRUCTURED-DATA BOM(\xEF\BB\xBF) MSG  

其中HEADER为信息头,通常包含信息长度(HEADER以后内容的长度)及日志优先级PRI(logFacility * 8 + logLevel), 具体说明请参考RFC5424The Syslog Protocol
举例:

<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8  

对应的格式如下(这边没有proc_id,也没有structured data,两者都用’-‘替代了, BOM指出使用的是UTF-8编码):

<PRI>SYSLOGPRO_VERSION ISOTIMESTAMP HOSTNAME APPLICATION PROC_ID MESSAGEID STRUCTURED-DATA BOM MSG  

在syslog-ng中,支持自定义日志格式,这时候我们可以使用template语法:

template global_format{
    template("<${PRI}> ${ISODATE} ${HOST} ${PROGRAM} ${PID} ${MESSAGE}\n");
};
options { proto-template(global_format); };  #用于protocol-like destination, like syslog() and network()   
options { file-template(global_format); }; #用于file-like destination

其中也可以使用${MSGHDR}来替代$PROGRAM[${PID}]的使用;新的syslog-ng版本也支持单独在template destination中指定template,则该template只会在该destination中生效(需要较新的syslog-ng版本才支持,比如3.4.2版本就不支持这种语法)。如:

destination d_file {
    file ("/var/log/messages"
    template("${ISODATE} ${HOST} ${MESSAGE}\n") );  #或template(template_name)
};

下面我们在syslog-ng.conf中实际操作以下,增加上面的template语法,然后启动syslog-ng,输出的日志变为:

<45> 2017-09-16T09:16:35+08:00 localhost syslog-ng 86650 syslog-ng starting up; version='3.4.2'

变量

syslog-ng中的marco变量十分丰富(如HOST, LEVEL_NUM等等)。此外,其还支持变量展开,作用类似shell脚本的$var功能: var=hello; echo $var ,下面来看看如何自定义和使用变量.

  1. 定义语法
    使用@define语句定义一个变量(或者叫宏),如下:

    @define local_loglevel 5
    @define local_file “/va/log/messages”

  2. 使用语法
    使用``符号进行变量展开,如:

    filter f_level{“${LEVEL_NUM}” <= “`local_loglevel`“;}

  3. 文件包含
    可以使用@include ‘filename’,将某个配置文件包含进syslog-ng.conf,这种语法在需要对配置文件拆分的场合很有用,如有个配置文件中的参数会变化(被外部修改),这时可以单独把这些参数放到另外的文件中,方便修改,也减少了破坏syslog-ng.conf文件的可能

filter

日志处理中最常用的filter当属级别过滤。此外,filter还具有很强大的关键字过滤功能,支持多个filter同时使用,具体语法参考Chapter 8. Routing messages: log paths and filters。

TLS传输

详见Chapter 10. TLS-encrypted message transfer
关于证书生成和配置的操作可参考证书相关概念及使用openssl生成自认证证书

其它

  • 关键字替换

rewrite r_rewrite_set{set("myhost", value("HOST")}; condition(program("myapplication")));}; log { source(s1); rewrite(r_rewrite_set); destination(d1); };

  • flow control
    tcp: max-connections(300)
    指定发送往网络目的地的日志缓冲区大小: log-fifo-size(40000)
    在destination中启用flow control: flags(flow-control)

结语

syslog-ng支持的配置语法众多,具体可根据使用时查阅手册进行配置,本文讲解的是一些比较常用的操作,如有错误,欢迎指出。
后续会讲解下syslog-ng的源码,作为一个优秀的日志服务器和客户端,深入理解下应该还是能有不少收获的

syslog-ng详解——简介及安装syslog-ng

目录
  1. 安装syslog-ng

日志管理模块作为软件系统的一个重要子模块,其记录着系统运行的有关信息。当系统故障时,日志是用于问题定位的必备信息。所以,一个设计良好的日志模块及其重要,在Linux系统上往往用syslog(rsyslogd及klogd程序)来管理系统的日志。本文介绍的syslog-ng是syslog的next generation,具有比syslog更强大的功能和性能,优势体现在以下几个方面:

  • 兼容syslog的功能,可扩展性强(支持plugin),更新速度快
  • 更多的配置语法,管理日志方式更加(日志源和日志目的地很丰富)
  • 支持网络日志传输(TLS方式传输),即分布式日志管理功能

安装syslog-ng

本文只介绍从源码包安装syslog-ng的方法,掌握了源码包安装后,就可以在嵌入式系统(如Android)上用类似的方法集成syslog-ng。
a) 下载源码包
https://my.balabit.com/downloads/syslog-ng/open-source-edition上下载syslog-ng源码包,这边我们选择3.4.2(因为带依赖库eventlog源码包,可以一起下载)。

b) 编译及安装
由于syslog-ng依赖于eventlog和glib,所以需要先编译eventlog和 glib (这边用的glib为2.46.2版本,依赖zlib, libffi,可以从http://ftp.gnome.org/pub/gnome/sources/glib/上下载)

从源码包编译软件的步骤如下(以eventlog为例,假设系统用户为user):

#./configure –-prefix=/home/user/software/eventlog-0.2.13
#make install

然后eventlog软件就被安装到了~/software/eventlog-0.2.13目录下。

用同样的方法编译安装glib到/home/user/software/glib-2.46.2/目录,然后就可以编译安装syslog-ng.详细步骤如下:

在syslog-ng-3.4.2源码包下执行

#export PKG_CONFIG_PATH=/home/user/software/eventlog-0.2.13/lib/pkgconfig/:/home/user/software/glib-2.46.2/lib/pkgconfig/:$PKG_CONFIG_PATH
#./configure –-prefix=~/software/syslog-ng-3.4.2
#make install

其中,PKG_CONFIG_PATH用于告诉configure依赖库的安装路径,否则configure的时候会报No package ‘xxx’ found
这样,就是成功安装了syslog-ng到~/software/syslog-ng-3.4.2目录下,下一篇讲解如何运行syslog-ng及其配置文件语法。

本站总访问量