利用Log4j 创建日志服务器

简介:
最近因为平台升级改造做了双机模式,日志的记录就成了一个大问题。以前都是一个应用起一个实例只打印一个日志,现在则是因为一个应用起了两个实例,而这两个实例又分别打印日志,这种情况造成我们查看日志,诊断问题的不便,因为必须把两个实例打的日志都拿到,才是这个应用的全部日志。

另外平台有多个子系统组成,这些子系统都有自己的日志,并且运行在不同的操作系统和主机上,收集这些日志对运营人员来说也比较困难。

针对以上两个问题,我们决定在平台中采用日志服务器来做到集中日志管理,平台中所有子系统通过socket方式将日志信息传到日志服务器,再由日志服务器统一记录。这样既避免了一个应用日志不同实例分别打印,也可以将所有子系统日志集中管理,并能够自定义输出路径。

我们的平台基于J2EE架构实现,故在各应用和各子系统均使用了Log4j,考虑到Log4j提供了SocketAppender可以直接调用,我们决定日志服务器仍然基于Log4j实现。

Log4j 提供了一个简单的基于socket的日志服务器,但直接使用这个服务器不能完全满足我们的需求,首先它自身代码存在问题,需要修改;其次即使修改正确,该服务器也只能按客户端IP配置打印appender,而我们有些子系统是运行在同一主机,直接使用该服务器只能将运行在同一主机上的子系统日志打在一起,不便于分析处理。我们要求按照不同应用输出日志。

稍经改造,Log4j提供的这个服务器就能较好地提供服务,满足我们的使用要求了。

Log4j 提供的日志服务器由SocketServer.javaSocketNode.java实现,我们需要改造这两个类,以达到我们的目的。

Log4j 提供的SocketServer利用一个Hashtable的变量 hierarchyMap 保存各个客户端的log4j配置信息,一旦侦听到某个客户端发送请求过来,则立刻New一个SocketNode来处理该请求,该SocketNode的构造参数中有一个是从 hierarchyMap 中获取的log4j配置信息,SocketNode将根据该配置信息直接输出客户端发送的日志请求。

改造后的日志服务器, SocketServer仍然利用 hierarchyMap 保存各个客户端的log4j配置信息,但这次不是基于客户端IP,而是基于应用的,当侦听到某个客户端发送请求过来,则同样New一个SocketNode来处理该请求, hierarchyMap 将作为 改造后的SocketNode一个构造参数,这样SocketNode自己就能够根据客户端请求内容来决定使用哪个log4j配置信息输出客户端日志请求,这里有个关键就是客户端需要上传信息表明自己是哪个应用。

分析Log4j源码,我们发现可以为SocketAppender配置一个属性 application ,而这个属性在服务端是可以获取的,SocketNode读取这个属性并自动选择相应的log4j配置信息来处理。

修改后的相关代码和配置文件如下:
//#SocketServer.java

import java.io.File;

import java.net.InetAddress;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Hashtable;



import org.apache.log4j.Hierarchy;

import org.apache.log4j.Level;

import org.apache.log4j.Logger;

import org.apache.log4j.PropertyConfigurator;

import org.apache.log4j.spi.RootLogger;



public  class SocketServer {



         static String CLIENT_DIR =  "client";

         static String CONFIG_FILE_EXT =  ".properties";



         static Logger cat = Logger.getLogger(SocketServer. class);

         static SocketServer server;

         static  int port; // key=application, value=hierarchy

        Hashtable<String, Hierarchy> hierarchyMap;

        String dir;



         public  static  void main(String argv[]) {

                  if (argv.length == 2)

                        init(argv[0], argv[1]);

                 else

                        usage( "Wrong number of arguments.");



                 //init("30020", "config");

                 try {

                        cat.info( "Listening on port " + port);

                        ServerSocket serverSocket =  new ServerSocket(port);

                         while ( true) {

                                cat.info( "Waiting to accept a new client.");

                                Socket socket = serverSocket.accept();

                                InetAddress inetAddress = socket.getInetAddress();

                                cat.info( "Connected to client at " + inetAddress);

                                cat.info( "Starting new socket node.");

                                 new Thread( new SocketNode(socket, server.hierarchyMap)).start();

                        }

                }  catch (Exception e) {

                        e.printStackTrace();

                }

        }



         static  void usage(String msg) {

                System.err.println(msg);

                System.err.println( "Usage: java " + SocketServer. class.getName() +  " port configFile directory");

                System.exit(1);

        }



         static  void init(String srvPort, String configDir) {

                 try {

                        port = Integer.parseInt(srvPort);

                }  catch (java.lang.NumberFormatException e) {

                        e.printStackTrace();

                        usage( "Could not interpret port number [" + srvPort +  "].");

                }



                PropertyConfigurator.configure(configDir + File.separator +  "socketserver.properties");



                server =  new SocketServer(configDir);

        }



         public SocketServer(String configDir) {

                 this.dir = configDir;

                hierarchyMap =  new Hashtable<String, Hierarchy>(11);

                configureHierarchy();



        }



         // This method assumes that there is no hiearchy for inetAddress

         // yet. It will configure one and return it.

         void configureHierarchy() {

                File configFile =  new File(dir + File.separator + CLIENT_DIR);

                 if (configFile.exists() && configFile.isDirectory()) {

                        String[] clients = configFile.list();

                         for ( int i = 0; i < clients.length; i++) {

                                File client =  new File(dir + File.separator + CLIENT_DIR + File.separator + clients[i]);

                                 if (client.isFile()) {

                                        Hierarchy h =  new Hierarchy( new RootLogger(Level.DEBUG));

                                        String application = clients[i].substring(0, clients[i].indexOf( "."));

                                        cat.info( "Locating configuration file for " + application);

                                        hierarchyMap.put(application, h);

                                         new PropertyConfigurator().doConfigure(client.getAbsolutePath(), h);

                                }

                        }

                }

        }

}

//#SocketNode.java

import java.io.BufferedInputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.net.Socket;

import java.util.Hashtable;



import org.apache.log4j.Hierarchy;

import org.apache.log4j.Logger;

import org.apache.log4j.spi.LoggingEvent;



// Contributors: Moses Hohman <mmhohman@rainbow.uchicago.edu>



/**

* Read {@link LoggingEvent} objects sent from a remote client using Sockets

* (TCP). These logging events are logged according to local policy, as if they

* were generated locally.

*

* <p>

* For example, the socket node might decide to log events to a local file and

* also resent them to a second socket node.

*

* @author Ceki Gülcü

*

* @since 0.8.4

*/


public  class SocketNode  implements Runnable {



        Socket socket;

        ObjectInputStream ois;

        Hashtable<String, Hierarchy> hashtable;



         static Logger logger = Logger.getLogger(SocketNode. class);



         public SocketNode(Socket socket, Hashtable<String, Hierarchy> hashtable) {

                 this.socket = socket;

                 this.hashtable = hashtable;

                 try {

                        ois =  new ObjectInputStream( new BufferedInputStream(socket.getInputStream()));

                }  catch (Exception e) {

                        logger.error( "Could not open ObjectInputStream to " + socket, e);

                }

        }



         // public

         // void finalize() {

         // System.err.println("-------------------------Finalize called");

         // System.err.flush();

         // }



         public  void run() {

                LoggingEvent event;

                Logger remoteLogger;



                 try {

                         if (ois !=  null) {

                                 while ( true) {

                                         // read an event from the wire

                                        event = (LoggingEvent) ois.readObject();

                                        Object application = event.getMDC( "application");

                                         if (application !=  null) {

                                                 // get a logger from the hierarchy. The name of the

                                                 // logger

                                                 // is taken to be the name contained in the event.

                                                remoteLogger = hashtable.get(application).getLogger(event.getLoggerName());



                                                 // logger.info(remoteLogger.getAppender(application.toString()));

                                                 // event.logger = remoteLogger;

                                                 // apply the logger-level filter

                                                 if (remoteLogger !=  null && event.getLevel().isGreaterOrEqual(remoteLogger.getEffectiveLevel())) {

                                                         // finally log the event as if was generated locally

                                                        remoteLogger.callAppenders(event);

                                                }



                                        }

                                }

                        }

                }  catch (java.io.EOFException e) {

                        logger.info( "Caught java.io.EOFException closing conneciton.");

                }  catch (java.net.SocketException e) {

                        logger.info( "Caught java.net.SocketException closing conneciton.");

                }  catch (IOException e) {

                        logger.info( "Caught java.io.IOException: " + e);

                        logger.info( "Closing connection.");

                }  catch (Exception e) {

                        logger.error( "Unexpected exception. Closing conneciton.", e);

                }  finally {

                         if (ois !=  null) {

                                 try {

                                        ois.close();

                                }  catch (Exception e) {

                                        logger.info( "Could not close connection.", e);

                                }

                        }

                         if (socket !=  null) {

                                 try {

                                        socket.close();

                                }  catch (IOException ex) {

                                }

                        }

                }

}

}
 
日志服务器参数的配置,文件名必须为socketserver.properties,该配置文件必须置于日志服务器的启动脚本LogServer.bat上层目录下的配置文件夹下,该配置文件夹在LogServer.bat中指定,本文中提到的配置文件夹为config

# 文件名socketserver.properties
log4j.rootCategory= INFO,   STDOUT

 

log4j.appender.STDOUT= org.apache.log4j.ConsoleAppender

log4j.appender.STDOUT.layout= org.apache.log4j.PatternLayout

log4j.appender.STDOUT.layout.ConversionPattern= [%d{yyyy-MM-dd   HH\ : mm\:ss}][%5p][%5t][%l]   %m%n

日志服务器端各应用的log4j配置文件,必须放在config/client目录下,这里可以部署多个配置文件,日志服务器启动的时候会一次读入所有的配置信息。

# 文件名test.properties
log4j.rootLogger= info,test

log4j.category.org.springframework.jdbc= debug,test

log4j.category.test= debug,test

log4j.additivity.test= false

log4j.additivity.org.springframework.jdbc= false

 

log4j.appender.test= org.apache.log4j.DailyRollingFileAppender

log4j.appender.test.DatePattern= '.'yyyy-MM-dd

log4j.appender.test.File= ${logPath}/test/bmr.log

log4j.appender.test.Append= true

log4j.appender.test.Threshold= INFO

log4j.appender.test.layout= org.apache.log4j.PatternLayout

log4j.appender.test.layout.ConversionPattern= [%d{yyyy-MM-dd   HH\ : mm\:ss}][%5p][%5t][%l]   %m%n


日志服务器的启动脚本LogServer.bat

 @echo off
java -cp .\log4j-1.2.8.jar -DlogPath=D:\LogServer\log *****.SocketServer 30020 config

 

某个客户端Log4j.properties的配置,注意SocketAppenderapplication属性,要求和服务器端某个应用的log4j配置文件对应。

log4j.rootCategory=, test

log4j.appender.test=org.apache.log4j.net.SocketAppender
log4j.appender.test.RemoteHost=“
日志计算机
log4j.appender.test.Port=30020 
log4j.appender.test.application=test






  本文转自 cuiyingfeng 51CTO博客,原文链接:http://blog.51cto.com/cuiyingfeng/271817,如需转载请自行联系原作者


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
14天前
|
Java
使用Java代码打印log日志
使用Java代码打印log日志
69 1
|
15天前
|
Linux Shell
Linux手动清理Linux脚本日志定时清理日志和log文件执行表达式
Linux手动清理Linux脚本日志定时清理日志和log文件执行表达式
70 1
|
19天前
|
SQL 关系型数据库 MySQL
MySQL数据库,可以使用二进制日志(binary log)进行时间点恢复
对于MySQL数据库,可以使用二进制日志(binary log)进行时间点恢复。二进制日志是MySQL中记录所有数据库更改操作的日志文件。要进行时间点恢复,您需要执行以下步骤: 1. 确保MySQL配置文件中启用了二进制日志功能。在配置文件(通常是my.cnf或my.ini)中找到以下行,并确保没有被注释掉: Copy code log_bin = /path/to/binary/log/file 2. 在需要进行恢复的时间点之前创建一个数据库备份。这将作为恢复的基准。 3. 找到您要恢复到的时间点的二进制日志文件和位置。可以通过执行以下命令来查看当前的二进制日志文件和位
|
24天前
|
监控 Shell Linux
【Shell 命令集合 系统管理 】Linux 自动轮转(log rotation)日志文件 logrotate命令 使用指南
【Shell 命令集合 系统管理 】Linux 自动轮转(log rotation)日志文件 logrotate命令 使用指南
45 0
|
26天前
|
存储 数据库
ALTER MATERIALIZED VIEW LOG :语句来更改现有物化视图日志的存储特征或类型。
`ALTER MATERIALIZED VIEW LOG` 语句用于修改已有的物化视图日志的存储属性或类型。配合示例中的动画图像(由于格式限制无法显示),该语句帮助优化数据库的性能和管理。
44 0
|
20天前
|
XML 运维 监控
【深入探究 C++ 日志库清理策略】glog、log4cplus 和 spdlog 的日志文件管理策略
【深入探究 C++ 日志库清理策略】glog、log4cplus 和 spdlog 的日志文件管理策略
58 0
|
27天前
|
安全 编译器 API
C++系统日志库精选:深入剖析glog与log4cplus,轻松搭建高效日志系统
C++系统日志库精选:深入剖析glog与log4cplus,轻松搭建高效日志系统
86 0
|
1月前
|
存储
Hudi Log日志文件格式分析(一)
Hudi Log日志文件格式分析(一)
25 1
|
1月前
|
缓存 索引
Hudi Log日志文件写入分析(二)
Hudi Log日志文件写入分析(二)
20 1
|
1月前
|
缓存
Hudi Log日志文件读取分析(三)
Hudi Log日志文件读取分析(三)
22 0