• 利用Log4j创建日志服务器


    最*因为*台升级改造做了双机模式,日志的记录就成了一个大问题。以前都是一个应用起一个实例只打印一个日志,现在则是因为一个应用起了两个实例,而这两个实例又分别打印日志,这种情况造成我们查看日志,诊断问题的不便,因为必须把两个实例打的日志都拿到,才是这个应用的全部日志。
    另外*台有多个子系统组成,这些子系统都有自己的日志,并且运行在不同的操作系统和主机上,收集这些日志对运营人员来说也比较困难。
    针对以上两个问题,我们决定在*台中采用日志服务器来做到集中日志管理,*台中所有子系统通过socket方式将日志信息传到日志服务器,再由日志服务器统一记录。这样既避免了一个应用日志不同实例分别打印,也可以将所有子系统日志集中管理,并能够自定义输出路径。
    我们的*台基于J2EE架构实现,故在各应用和各子系统均使用了Log4j,考虑到Log4j提供了SocketAppender可以直接调用,我们决定日志服务器仍然基于Log4j实现。
    Log4j提供了一个简单的基于socket的日志服务器,但直接使用这个服务器不能完全满足我们的需求,首先它自身代码存在问题,需要修改;其次即使修改正确,该服务器也只能按客户端IP配置打印appender,而我们有些子系统是运行在同一主机,直接使用该服务器只能将运行在同一主机上的子系统日志打在一起,不便于分析处理。我们要求按照不同应用输出日志。
    稍经改造,Log4j提供的这个服务器就能较好地提供服务,满足我们的使用要求了。
    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配置信息来处理。
    修改后的相关代码和配置文件如下:

    //#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的配置,注意SocketAppender的application属性,要求和服务器端某个应用的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

  • 相关阅读:
    create-react-app搭建的项目中添加bootstrap
    用es6的Array.reduce()方法计算一个字符串中每个字符出现的次数
    为Docker配置阿里加速器,系统为Debian8
    基于Spring Boot,使用JPA动态调用Sql查询数据
    基于Spring Boot,使用JPA调用Sql Server数据库的存储过程并返回记录集合
    基于Spring Boot,使用JPA操作Sql Server数据库完成CRUD
    ES6,Array.includes()函数的用法
    【编程风格】c++命名约定
    【转】吴恩达的视频课程做成了文字版 ~~~
    【专业学习】常用的技术网站
  • 原文地址:https://www.cnblogs.com/duanxz/p/2864391.html
Copyright © 2020-2023  润新知