跨应用程序的多个实例维护StringBuffer类中的日志

时间:2015-03-26 21:32:38

标签: java node.js spring shared-data

过去几天我一直在努力解决这个问题。

要求 在我们的应用程序中,我们通过nodejs开发的UI上传文件,然后通过亚马逊简单工作流程(SWF)处理文件记录。对amazon SWF的调用是通过一个Spring应用程序进行的,node应用程序将在处理文件时调用它。要求是,对于每个正在处理的文件,应用程序需要创建一个日志文件,详细说明处理过程中记录的内容。

我是如何实施的? 在触发SWF的spring应用程序中,我创建了一个FileLogger类,它将维护一个静态的StringBuffer变量。此fileLogger类设置为工作流范围,这意味着,将为每次执行工作流创建该类,并在其结束时销毁该类。在处理文件时,我会继续将日志附加到FileLogger类中的StringBuffer,并在处理结束时写入文件并保存。

问题描述 只要我们只运行一个应用程序实例,这个解决方案似乎工作正常。一旦我们将应用程序部署到多个amazon ec-2实例中,就会将不完整的日志保存在文件中。进一步调查显示,应用程序的每个实例都有自己的stringBuffer来维护日志,当我们写入应用程序时,只读取一个字符串缓冲区内容,从而读取不完整的日志。不用说,日志模式是随机的。我观察到如果我们部署应用程序的N个实例,我们将有N个StringBuffer实例。

这是FileLogger类

private static final Logger logger = LoggerFactory.getLogger(FileLogger.class);

    //private static final Logger logger = LoggerFactory.getLogger(FileLogger.class);   
    private static final SimpleDateFormat logFileDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
    private static final String COLON = ":";
    private static StringBuffer logAppender = null;

    public synchronized void debug(Date date, String logMessage){
        if(appConfig.getLogLevel().equalsIgnoreCase(LogLevel.DEBUG.name())){
            this.log(LogLevel.DEBUG.name(), date, logMessage);
        }
    }

    public synchronized void info(Date date, String logMessage){
        if(appConfig.getLogLevel().equalsIgnoreCase(LogLevel.INFO.name()) || 
                appConfig.getLogLevel().equalsIgnoreCase(LogLevel.DEBUG.name())){
            this.log(LogLevel.INFO.name(), date, logMessage);
        }
    }

    public synchronized void error(Date date, String logMessage){
        if(appConfig.getLogLevel().equalsIgnoreCase(LogLevel.ERROR.name()) ||
                appConfig.getLogLevel().equalsIgnoreCase(LogLevel.DEBUG.name())){
            this.log(LogLevel.ERROR.name(), date, logMessage);
        }
    }

    private synchronized void log(String logLevel, Date date, String logMessage){
        logger.info("logAppender Hashcode: "+logAppender.hashCode());
        if(!logLevel.equalsIgnoreCase(LogLevel.NONE.name())){
            //StringBuffer logAppender = getLogAppender();
            getLogAppender().append(getLogAppender().hashCode());
            getLogAppender().append(COLON);
            getLogAppender().append(getFormattedDate(date, logFileDateFormat));
            getLogAppender().append(COLON);
            getLogAppender().append(logLevel);
            getLogAppender().append(COLON);
            getLogAppender().append(logMessage);
            getLogAppender().append(System.getProperty("line.separator"));
        }
    }
private synchronized StringBuffer getLogAppender(){
        logger.info("Getting logAppender .."+logAppender);
        if(logAppender == null){
            logger.info("Log appender is null");
            logAppender = new StringBuffer();
        }
        return logAppender;
    }

问题 如何确保我的应用程序的多个实例中只有一个StringBuffer(logAppender)实例,我可以继续附加日志,然后在结束时读取并在保存之前将内容写入文件?

2 个答案:

答案 0 :(得分:1)

首先,您在非静态方法中使用à静态字段:这是不好的做法,要么使用静态方法,要么使用非静态字段的单例。

其次,如果您担心性能,最好在不需要时删除所有同步的内容。您只需要在log()方法上使用它。 Error(),warn()和info()不以需要同步的方式访问共享数据(只读)。另外,为什么在log()中多次调用getLogAppender()?

使用equalsIgnoreCase()测试日志级别也是低效的,尽管它不会引人注意。并且你有一个错误错误():如果级别是INFO则不会记录。

现在,到目前为止:就像之前的其他人所说的那样,JVM不会在实例之间共享用户数据。并且JVM可能不是同一OS实例上的事件。没有简单的方法。如果配置得好,记录到数据库可能对性能没有那么糟糕:由于您不更新任何现有数据,因此根本不需要事务隔离。您也可以尝试使用日志服务器:类似Unix的操作系统提供了大量的解决方案。或者在刷新缓冲区时,从所有服务器实例中刷新。

答案 1 :(得分:0)

我只是想回来提一下,作为一个解决方案,我最终选择了亚马逊的elastiCache(redis实现)来暂时存储我的日志,然后在操作结束时,从缓存中读取所有内容并写入文件中亚马逊s3。希望这会有所帮助。