功能说明
一个系统可能有多个子系统组成,这些子系统都有自己的日志,并且运行在不同的操作系统和主机上,收集这些日志对运营人员来说也比较困难。
因此决定在平台中采用日志服务器来做到集中日志管理,平台中所有子系统通过socket方式将日志信息传到日志服务器,再由日志服务器统一记录。这样既避免了一个应用日志不同实例分别打印,也可以将所有子系统日志集中管理,并能够自定义输出路径。
实现原理
Log4j提供了一个简单的基于socket的日志服务器,但直接使用这个服务器不能完全满足我们的需求,首先它自身代码存在问题,需要修改;其次即使修改正确,该服务器也只能按客户端IP配置打印appender,而我们有些子系统是运行在同一主机,直接使用该服务器只能将运行在同一主机上的子系统日志打在一起,不便于分析处理。我们要求按照不同应用输出日志。因此我们要对其进行改造。
Log4j提供的日志服务器由SocketServer.java和SocketNode.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配置信息来处理
更改代码
1 SocketNode.java
- package org.apache.log4j.net;
- 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;
- /**
- *
- * 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 zhoulianglg * */
- 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 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.
- if(hashtable.get(application)==null || hashtable.get(application).equals("")){
- application="default";
- logger.info("Using the default");
- }
- remoteLogger = hashtable.get(application).getLogger(
- event.getLoggerName());
- // 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) {
- }
- }
- }
- }
- }
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;
/**
*
* 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 zhoulianglg * */
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 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.
if(hashtable.get(application)==null || hashtable.get(application).equals("")){
application="default";
logger.info("Using the default");
}
remoteLogger = hashtable.get(application).getLogger(
event.getLoggerName());
// 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) {
}
}
}
}
}
2 SocketServer.java
- package org.apache.log4j.net;
- 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("8899", "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);
- }
- }
- }
- }
- }
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("8899", "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);
}
}
}
}
}
服务端
运行环境
jdk:jdk1.5以上(含1.5)
环境配置
对于运行于linux环境,我们可以用如下步骤配置运行环境。
方法一 配置系统变量
打开系统变量文件(.profile),在文件的末尾依次添加以下代码:
export JAVA_HOME=/usr/java/jdk1.5.0(以实际路径为准)
export CLASSPATH=$JAVA_HOME/lib.tools.jar:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$HOME/bin:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
方法二 配置局部变量
在log4jsocket.sh中添加环境变量配置
#!/bin/sh
export JAVA_HOME=/usr/java/jdk1.5.0
export CLASSPATH=$JAVA_HOME/lib.tools.jar:$JAVA_HOME/lib:$JAVA_HOME/jre/lib
export PATH=$HOME/bin:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
java -cp ./log4j-1.2.16.jar org.apache.log4j.net.SocketServer 8899 config
注:建议使用方法二不影响其它系统
获取安装文件
http://download.csdn.net/detail/zhoulianglg/4025803
获取文件后,解压到要安装的路径。
目录结构
- logservers
- config
- client
- default.properties
- test1.properties
- test2.properties
- socketserver.properties
- client
- log4j-1.2.16.jar
- log4jsocket.bat
- log4jsocket.sh
- config
要在client目录添加.propreties 文件,文件名跟客户端发动过来的参数名相同 例:test.properties
log4j.rootLogger=debug,test
log4j.appender.test=org.apache.log4j.DailyRollingFileAppender
log4j.appender.test.File=logs/test.log
log4j.appender.test.Encoding=GBK
log4j.appender.test.layout=org.apache.log4j.PatternLayout
log4j.appender.test.DatePattern='.'yyyy-MM-dd
log4j.appender.test.layout.ConversionPattern=test%d{yyyy-MM-dd HH:mm:ss}[%24F:%-3L:%-5p]%x %m%n
注:具体配置就跟客户端log4j配置方式一样,这里如何配置log服务器就会按照这里的配置生成log文件
若此处没有配置相应的配置文件 默认执行 default.properties 文件
启用服务器命令
windows 直接双击打开 log4jsocket.bat
unix运行 sh log4jsocket.sh
日志服务器部署地址
IP:192.168.0.14
目录:/home/ifxt/sibas-cs/logservers
客户端
环境要求
log4j-1.2.15以上
客户端配置
在log4j.properties中加入
log4j.rootCategory=debug,test
log4j.appender.test=org.apache.log4j.net.SocketAppender
#发送到服务器端的地址
log4j.appender.test.RemoteHost=192.168.0.189
#端口号
log4j.appender.test.Port=8899
log4j.appender.test.LocationInfo=true
#发送过去的参数
log4j.appender.sibas.application=sibas
应用步骤
(以windows系统为例)
第一步获取安装log4j日志服务器
http://download.csdn.net/detail/zhoulianglg/4025803
获取文件后,解压到你想存放的路径想。如:d:\
第二步为客户端配置连接
打开你要配置的项目的log4j.properties 文件
在log4j.properties中加入
log4j.rootCategory=debug,test
log4j.appender.test=org.apache.log4j.net.SocketAppender
#发送到服务器端的地址
log4j.appender.test.RemoteHost=192.168.0.189
#端口号
log4j.appender.test.Port=8899
log4j.appender.test.LocationInfo=true
#发送过去的参数
log4j.appender.sibas.application=test
配置服务端
在logservers/config/client 目录下添加一个命名为 test.properties 文件 文件名称与你在客户端配置的
log4j.appender.test.application=test名称相同
具体参考配置
log4j.rootLogger=debug,test
log4j.appender.test=org.apache.log4j.DailyRollingFileAppender
log4j.appender.test.File=logs/sibas.log
log4j.appender.test.Encoding=GBK
log4j.appender.test.layout=org.apache.log4j.PatternLayout
log4j.appender.test.DatePattern='.'yyyy-MM-dd
log4j.appender.test.layout.ConversionPattern=test%d{yyyy-MM-dd HH:mm:ss}[%24F:%-3L:%-5p]%x %m%n
注:此处配置作用是log4j服务器把客户端送过来的信息 按照现在的配置模式输出日志,日志模式跟此处配置有关,与客户端原有配置没有关系
此处要没有配置 相应的文件,就会默认 执行 default.properties 文件
启动日志服务
运行命令
java -cp ./log4j-1.2.16.jar org.apache.log4j.net.SocketServer 端口号 配置目录
例:java -cp ./log4j-1.2.16.jar org.apache.log4j.net.SocketServer 8899 config
注:端口号即为客户端log4j.appender.sibas.Port=8899 配置的端口号