在应用程序的多个实例中维护 StringBuffer class 中的日志
Maintaining logs in a StringBuffer class across multiple instances of the application
过去几天我一直在为这个问题苦苦挣扎。
要求
在我们的应用程序中,我们通过 nodejs 开发的 UI 上传文件,然后通过 amazon simple work flow (SWF) 处理文件记录。对亚马逊 SWF 的调用是通过 Spring 应用程序发生的,nodejs 应用程序将在处理文件时调用该应用程序。要求是,对于每个正在处理的文件,应用程序需要创建一个日志文件,详细说明记录在处理过程中发生的情况。
我是如何实现的?
在触发 SWF 的 spring 应用程序中,我创建了一个 FileLogger class,它将维护一个静态 StringBuffer 变量。此 fileLogger class 设置为工作流范围,这意味着 class 将为工作流的每次执行创建并在其结束时销毁。在处理文件时,我会继续将日志附加到 FileLogger class 中的 StringBuffer,并在处理结束时写入文件并保存。
问题描述
只要我们只有一个应用程序实例 运行,这个解决方案似乎就可以正常工作。一旦我们将应用程序部署到多个 amazon ec-2 实例中,似乎不完整的日志就保存在文件中。进一步调查发现,应用程序的每个实例都有自己的 stringBuffer 来维护日志,当我们写入应用程序时,只读取其中一个 stringbuffers 内容,因此日志不完整。不用说,日志模式是随机的。我观察到如果我们部署应用程序的 N 个实例,我们将有 N 个 StringBuffer 实例。
这是 FileLogger class
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) 实例,我可以将日志附加到它,然后在最后读取并将内容写入文件,然后再保存它?
首先,您在非静态方法中使用静态字段:这是不好的做法,要么使用静态方法,要么使用非静态字段的单例。
其次,如果您担心性能,最好在不需要时删除所有同步的。您只需要在 log() 方法上使用它。 Error()、warn() 和 info() 不以需要同步(只读)的方式访问共享数据。另外,为什么在 log() 中多次调用 getLogAppender() ?
使用 equalsIgnoreCase() 测试日志级别也是低效的,尽管它不会被注意到。而且您在 error() 中有一个错误:如果级别为 INFO,它不会记录。
现在,就您的观点而言:正如之前其他人所说,JVM 不在实例之间共享用户数据。而且 JVM 可能不是同一 OS 实例上的事件。没有简单的出路。如果配置得当,记录到数据库可能不会对性能造成那么大的影响:您根本不需要事务隔离,因为您不更新任何现有数据。您也可以尝试使用日志服务器:类 unix OSes 有很多解决方案可供提供。或者当您刷新缓冲区时,从所有服务器实例中刷新。
我只是想回来说一下,作为一个解决方案,我最终选择了亚马逊的 elastiCache(redis 实现)来临时存储我的日志,然后在操作结束时,从缓存中读取所有内容并写入到亚马逊 s3 中的文件。希望这会有所帮助。
过去几天我一直在为这个问题苦苦挣扎。
要求 在我们的应用程序中,我们通过 nodejs 开发的 UI 上传文件,然后通过 amazon simple work flow (SWF) 处理文件记录。对亚马逊 SWF 的调用是通过 Spring 应用程序发生的,nodejs 应用程序将在处理文件时调用该应用程序。要求是,对于每个正在处理的文件,应用程序需要创建一个日志文件,详细说明记录在处理过程中发生的情况。
我是如何实现的? 在触发 SWF 的 spring 应用程序中,我创建了一个 FileLogger class,它将维护一个静态 StringBuffer 变量。此 fileLogger class 设置为工作流范围,这意味着 class 将为工作流的每次执行创建并在其结束时销毁。在处理文件时,我会继续将日志附加到 FileLogger class 中的 StringBuffer,并在处理结束时写入文件并保存。
问题描述 只要我们只有一个应用程序实例 运行,这个解决方案似乎就可以正常工作。一旦我们将应用程序部署到多个 amazon ec-2 实例中,似乎不完整的日志就保存在文件中。进一步调查发现,应用程序的每个实例都有自己的 stringBuffer 来维护日志,当我们写入应用程序时,只读取其中一个 stringbuffers 内容,因此日志不完整。不用说,日志模式是随机的。我观察到如果我们部署应用程序的 N 个实例,我们将有 N 个 StringBuffer 实例。
这是 FileLogger class
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) 实例,我可以将日志附加到它,然后在最后读取并将内容写入文件,然后再保存它?
首先,您在非静态方法中使用静态字段:这是不好的做法,要么使用静态方法,要么使用非静态字段的单例。
其次,如果您担心性能,最好在不需要时删除所有同步的。您只需要在 log() 方法上使用它。 Error()、warn() 和 info() 不以需要同步(只读)的方式访问共享数据。另外,为什么在 log() 中多次调用 getLogAppender() ?
使用 equalsIgnoreCase() 测试日志级别也是低效的,尽管它不会被注意到。而且您在 error() 中有一个错误:如果级别为 INFO,它不会记录。
现在,就您的观点而言:正如之前其他人所说,JVM 不在实例之间共享用户数据。而且 JVM 可能不是同一 OS 实例上的事件。没有简单的出路。如果配置得当,记录到数据库可能不会对性能造成那么大的影响:您根本不需要事务隔离,因为您不更新任何现有数据。您也可以尝试使用日志服务器:类 unix OSes 有很多解决方案可供提供。或者当您刷新缓冲区时,从所有服务器实例中刷新。
我只是想回来说一下,作为一个解决方案,我最终选择了亚马逊的 elastiCache(redis 实现)来临时存储我的日志,然后在操作结束时,从缓存中读取所有内容并写入到亚马逊 s3 中的文件。希望这会有所帮助。