Log4J学习【二十三】常用的Appender之SocketAppender

来源:互联网 发布:linux arp 清除 编辑:程序博客网 时间:2024/05/09 08:33
前面这几个Appender就是Log4J提供的基于文件系统的Appender。当然,在一些特殊的需要记录到文件的Appender来说,我们只需要选择一个合适的Appender来继承并完成自己的逻辑即可。关于自定义Appender,待会再看。下面来看几个比较特殊的Appender。

    前面所有介绍的Appender都有一个共同的特点,就是他们都需要配置一个Layout对象,下面要介绍的这个Appender非常特殊,他本身不需要任何Layout对象,这个Appender就是SocketAppender。注意,要能理解SocketAppender,必须对java网络编程和java对象序列化有相关了解。
    顾名思义SocketAppender是基于Socket的Appender,他不会对日志消息做任何的处理,而仅仅是通过Socket将日志请求发送到特定的服务器上。既然是把日志请求发送到特定的服务器上,首先要考虑的就是日志消息使用什么样的方式发送,在SocketAppender中,只是简单的把LoggingEvent对象直接通过序列化的方式发送到Socket的输出流上。所以,要能正常使用SocketAppender,就不单单是使用日志的应用了,还需要一个能统一和接受日志消息的独立的日志服务器应用了。我们下面就简单的把使用日志功能的应用称为Logging Client,把接收和处理日志消息的服务器应用称为Logging Server。
    要配置Logging Client端的appender这个已经不是难点了,下面的问题是我们需要自己来写一个logging server来接收我们的日志消息。要完成这样一个服务器端的应用,首先我们要确定几个需求。第一,我们从Socket中读到的是序列化了的LoggingEvent对象,所以,我们得首先反序列化这个对象,得到LoggingEvent对象;第二,得到这个对象后,我们要确定处理这个对象的方式,这里我们就简单处理,直接使用System.out打印出日志消息即可。那么,我们可以先把这个Server的框架代码写出来:
public static void main(String[] args) throws Exception {
    ServerSocket socket = new ServerSocket(4560);
    while (true) {
    Socket client = socket.accept();
    Thread t = new Thread(new LogRunner(client));
    t.start();
    }
}

    我们使用一个ServerSocket循环的监听4560端口,得到请求后,就把这个客户请求交给一个LogRunner线程去处理。
下面就是LogRunner的代码:
static class LogRunner implements Runnable {
    private ObjectInputStream ois;

        public LogRunner(Socket client) {
            try {
                 this.ois = new ObjectInputStream(client.getInputStream());
            } catch (Exception e) {
        }
    }

@Override
    public void run() {
        try {
            while (true) {
                LoggingEvent event = (LoggingEvent) ois.readObject();
                System.out.println(event.getLoggerName() + ":  " + event.getMessage());
            }
        } catch (Exception e) {
        } finally {
        }
    }
}

    这里面有几个点需要特别注意:
    1,我们使用socket的InputStream创建了一个ObjectInputStream用来反序列化Logging Client发送的LoggingEvent对象。
    2,特别注意,我们在run方法中,使用了一个while(true)无线循环来不断的读入并将读入的二进制内容转成Object对象,并将Object对象再强转成LoggingEvent。并且,当我们读入一个LoggingEvent对象之后,并没有去关闭ois。为什么要这样做呢?其实需要考虑SocketAppender的工作方式。当一个Logging Client运行的时候,他对应的SocketAppender就会启动,并创建一个到指定Logging Server的Socket连接。每当一个日志消息到达SocketAppender之后,就会直接向这个Socket的OutputStream写入序列化的对象数据,并通过flush发送到服务器端。换句话说,就是当一个Logging Client应用创建好之后,就会一直有一个Socket连接到服务器,这个连接是不会断的,并且一旦有日志消息到达,就直接发送序列化数据。所以我们的服务器端,就必须在得到一个Socket连接之后,一直维护这个连接,不断的从连接中读取数据,并且不能关闭这个连接。
     然后我们将这个应用先运行起来。再来看客户端。
     首先来看看SocketAppender,他有几个常用的配置:
    RemoteHost:指定远端的Logging Server的地址。比如对于我们的应用来说,直接配成LocalHost就可以了。
    Port:指定远端的Logging Server的端口。这里,我们的服务器端监听的是4560端口,所以,这里就填4560。其实,默认情况下,SocketAppender就是请求的4560端口。
    Application:可以设置应用的名称。因为使用SocketAppender,就可以使用同一个专门的Logging Server来统一完成日志记录。那么就有可能会有不同的应用提交日志到Server上,怎么区分日志信息是哪个客户端发过来的呢?就可以通过配置Application信息即可。具体这个信息从LoggingEvent中怎么获取,待会再介绍。
    reconnectionDelay:如果客户端连接不上服务器端,那么会在reconnectionDelay指定的时间之后重新连接。这里需要注意的一个问题就是,客户端发送日志请求和服务器之间的通信问题。在网络通信正常的情况下,每一条客户端的日志请求都会正常的发送给服务器端处理,这是没有问题的。但是考虑网络的不稳定性,就会出现以下的情况:
    1,当网络发送包的频率大于日志频率,会正常的按照日志频率发送信息;
    2,当网络发送包的频率低于日志频率,则只会按照网络发送包的频率来发送客户端的日志信息。这里就有可能出现日志消息丢失的问题;
    3,当服务器连接断开,SocketAppender会尝试重新连接服务器端,但是在这个断开的过程当中,所有的日志信息将会丢失。
    所以,在使用SocketAppender的时候,也需要考虑到网络的因素。当然,我们也能自己实现一个Appender,当网络断开的时候,先将LoggingEvent对象暂时序列化到本地,当网络再次连接上之后,再把缓存到本地的LoggingEvent对象发送。要完成这个功能,只要了解了Appender的工作原理就能比较轻松的完成。这个我们后面再看。
    下面,就完成客户端的配置,并运行测试:
log4j.rootLogger=DEBUG,socket
log4j.appender.socket=org.apache.log4j.net.SocketAppender
log4j.appender.socket.RemoteHost=localhost
log4j.appender.socket.port=4560
log4j.appender.socket.application=localclient

    我们定义了一个socketAppender,并配置了服务器地址和端口号,最后为我们的client取名为localclient。
   下面来看看测试:
@Test
public void testSocketAppender() throws Exception{
    Logger logger = Logger.getLogger("cd.itcast");
    Logger barLogger = Logger.getLogger("cd.itcast.log");
    for (int i = 0; i < 100; i++) {
        logger.warn("logger warn");
        logger.debug("logger debug");
        barLogger.info("bar logger info");
        barLogger.debug("bar logger debug long long ");
    }
}

同样的测试,运行100次,这次来运行,可以看到,速度非常的快,原因就是,使用SocketAppender,在客户端根本不会对LoggingEvent做任何过多的处理,而直接就把序列化的对象发送到远端server上,并且,序列化和发送的逻辑,还是独立运行在一个单独的线程中,对我们应用所在的线程性能没有任何影响。
查看服务器端的控制台输出:
localclient:cd.itcast:  logger warn
localclient:cd.itcast:  logger debug
localclient:cd.itcast.log:  bar logger info
localclient:cd.itcast.log:  bar logger debug long long 

可以看到内容完整的输出了。
其实,我们也没有必要自己来写这个Server端的应用,因为Log4J为我们实现了一个Server端:SimpleSocketServer。
这个SimpleSocketServer的使用非常简单,他相当于把接受到的LoggingEvent作为本地的日志记录事件,再使用在服务器端配置的Log4J环境来记录日志。画个简单的图:

    在启动SimpleSocketServer的时候,有两个启动选项,一个是监听的端口,一个是配置文件地址,我们可以这样测试,首先额外创建一个log4j.properties文件来控制SimpleSocketServer上面的Log4J环境。
log4j.rootLogger=DEBUG,file
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.conversionPattern=%r [%t] %p %c %x - %m%n
log4j.appender.file.file=serverlog.log

    我们把这个配置文件命名为log4jserver.properties,
    然后使用参数4560 log4jserver.properties来启动SimpleSocketServer。
    再次运行我们的测试,可以发现,在应用目录下多了log4jserver.log日志文件,并且里面的内容:
0 [main] INFO org.apache.log4j.net.SimpleSocketServer  - Listening on port 4560
0 [main] INFO org.apache.log4j.net.SimpleSocketServer  - Waiting to accept a new client.
29968 [main] INFO org.apache.log4j.net.SimpleSocketServer  - Connected to client at /192.168.1.101
29968 [main] INFO org.apache.log4j.net.SimpleSocketServer  - Starting new socket node.
29968 [main] INFO org.apache.log4j.net.SimpleSocketServer  - Waiting to accept a new client.
87406 [main] INFO org.apache.log4j.net.SimpleSocketServer  - Connected to client at /192.168.1.101
87406 [main] INFO org.apache.log4j.net.SimpleSocketServer  - Starting new socket node.
87422 [main] INFO org.apache.log4j.net.SimpleSocketServer  - Waiting to accept a new client.
87422 [main] WARN cd.itcast  - logger warn
87437 [main] DEBUG cd.itcast  - logger debug
87437 [main] INFO cd.itcast.log  - bar logger info
87437 [main] DEBUG cd.itcast.log  - bar logger debug long long 

     确实为我们客户端的日志消息。
    
    关于Appender,我们就先暂时看到这里,至于后面的SMTPAppender和JdbcAppender,大家按照我们学习这些Appender的思路,去运用就可以了。
0 0