在考虑了一段时间后,我在这里拥有的所有选项:
将日志保存在内存中,每次用户调用时将它们保存到 CRX info/warn/error
。优点:日志与迁移任务元数据存储在同一位置,易于定位和访问。缺点:在大量日志条目的情况下,可能是所有方法中最慢且资源效率最低的。
将日志保存在内存中,仅在迁移结束时将它们保存到 JCR。优点:易于重构当前解决方案,迁移过程中对 CRX 的压力较小。缺点:无法跟踪实时进度,在意外错误或实例关闭期间可能丢失日志。
为每个日志条目创建一个自定义类型的节点,而不是 log.txt。通过特殊的日志 servlet 在文本文件中聚合日志。即/var/migration/uuid/log.txt
或/var/migration/uuid/log.json
。优点:更多的 JCR 方式来存储此类内容。使用自定义节点类型和索引应该足够快,可以考虑作为一个选项。具有多样性支持文本和json格式的日志。 缺点:与当前方法的性能比较不清楚。由于大量节点位于同一级别而导致的潜在问题。用户应该知道日志 servlet 的存在,否则用户无法以方便的格式看到它们。在大量日志条目的情况下,日志 servlet 性能不清楚。
在文件系统上创建日志文件(比如说在crx-quickstart/logs/migration/<uuid>.log
),通过 API 显示它的内容(如果需要),能够将日志 API 响应中继到最后 100-1000 行。优点:当日志文件存储在文件系统上时,经典且众所周知的日志方法。Sling 提供已配置的绑定slf4j
到LogBack
所有需要的 LogBack 依赖项,这些依赖项导出以在您的自定义捆绑包中使用。缺点:日志和任务元数据分离。用户应该知道磁盘上的日志文件位置。
从选项 1开始,我意识到日志条目数量可能会扩展到数十万 - 罕见但可能的情况。所以最后决定选择选项 4。
如果有人会面临类似的任务,我将在此处发布选项 4的实现细节,因为它并不像起初看起来那么简单。
我正在使用AEM 6.2
(Felix-Jackrabbit-Sling under the hood)并且我希望每个迁移任务都运行 - 这实际上只是一个单独的线程 - 以创建它自己的具有特殊名称的日志文件 - 该迁移过程的唯一标识符。
现在,Sling 本身允许您通过org.apache.sling.commons.log.LogManager.factory.config
OSGi 配置定义多个日志配置。然而,这些日志配置对于这种情况来说太简单了——你不能用它创建在 LogBack 中调用的SiftingAppender——日志附加器的特殊情况,它将为每个线程的特定记录器实例化附加器,而不是一次和应用程序范围的附加器——换句话说,你无法指示 LogBack 使用 OSGi 配置为每个线程创建文件。
因此,从逻辑上讲,您可能希望在运行时以编程方式获取 Sling 的 LogBack 配置(例如,在您上传自定义包并激活它的那一刻)并使用它为特定记录器配置此类附加程序。不幸的是,虽然有很多关于如何logback.xml
通过.ch.qos.logback.classic.LoggerContext
SiftingAppender
因此,在阅读了 LogBack 源代码和测试后,我最终得到了这个帮助类:
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.sift.MDCBasedDiscriminator;
import ch.qos.logback.classic.sift.SiftingAppender;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.sift.AppenderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.Objects;
/**
* This class dynamically adds configuration to AEM's LogBack logging implementation behind slf4j.
* The point is to provide loggers bound to specific task ID and therefore specific log file, so
* each migration task run will be written in it's standalone log file.
* */
public class LogUtil {
static {
LoggerContext rootContext = (LoggerContext) LoggerFactory.getILoggerFactory();
ch.qos.logback.classic.Logger logger = rootContext.getLogger("migration-logger");
//since appender lives until AEM instance restarted
//we are checking if appender had being registered previously
//to ensure we won't do it more than once
if(logger.getAppender("MIGRATION-TASK-SIFT") == null) {
MDCBasedDiscriminator mdcBasedDiscriminator = new MDCBasedDiscriminator();
mdcBasedDiscriminator.setContext(rootContext);
mdcBasedDiscriminator.setKey("taskId");
mdcBasedDiscriminator.setDefaultValue("no-task-id");
mdcBasedDiscriminator.start();
SiftingAppender siftingAppender = new SiftingAppender();
siftingAppender.setContext(rootContext);
siftingAppender.setName("MIGRATION-TASK-SIFT");
siftingAppender.setDiscriminator(mdcBasedDiscriminator);
siftingAppender.setAppenderFactory(new FileAppenderFactory());
siftingAppender.start();
logger.setAdditive(false);
logger.setLevel(ch.qos.logback.classic.Level.ALL);
logger.addAppender(siftingAppender);
}
}
public static class FileAppenderFactory implements AppenderFactory<ILoggingEvent> {
@Override
public Appender<ILoggingEvent> buildAppender(Context context, String taskId) throws JoranException {
PatternLayoutEncoder logEncoder = new PatternLayoutEncoder();
logEncoder.setContext(context);
logEncoder.setPattern("%-12date{YYYY-MM-dd HH:mm:ss.SSS} %-5level - %msg%n");
logEncoder.start();
FileAppender<ILoggingEvent> appender = new FileAppender<>();
appender.setContext(context);
appender.setName("migration-log-file");
appender.setFile("crx-quickstart/logs/migration/task-" + taskId + ".log");
appender.setEncoder(logEncoder);
appender.setAppend(true);
appender.start();
//need to add cleanup configuration for old logs ?
return appender;
}
}
private LogUtil(){
}
public static Logger getTaskLogger(String taskId) {
Objects.requireNonNull(taskId);
MDC.put("taskId", taskId);
return LoggerFactory.getLogger("migration-logger");
}
public static void releaseTaskLogger() {
MDC.remove("taskId");
}
}
要注意的部分是SiftingAppender
需要您实现AppenderFactory
接口,该接口将为每个线程的记录器生成配置的附加程序以使用。
现在您可以通过以下方式获取记录器:
LogUtil.getTaskLogger("some-task-uuid")
并使用它来创建crq-quickstart/logs/migration/task-<taskId>.log
每个提供的日志文件taskId
根据文档,您还需要在完成后发布此类记录器
LogUtil.releaseTaskLogger()
差不多就是这样。