我要运行一个可能需要几分钟甚至几小时的过程。为了跟踪此类运行的历史记录,我在每次运行时创建一个自定义类型的节点,其中存储了相关的流程另外我想在这样的节点下存储日志文件。这似乎是更加一致和方便的方法,而不是将日志文件存储在磁盘上,与进程元分开。
现在nt:file
nodetype本身有一个jcr:content
子节点,其jcr:data
属性允许我存储二进制内容。这适用于文件的一次性或不经常的内容更改。
但是我会不断向该文件附加新内容,除此之外,还要在单独的线程中调查它的内容(以跟踪进度)。
面对javax.jcr.ValueFactory
,javax.jcr.Binary
的JCR API似乎并不支持这样的方法,我宁愿被迫覆盖该文件(或更准确地说 - 二元属性)每次我添加一行日志时都会重复。我担心表现。
我搜索了文档中的工具,这些工具可以让我打开该文件的输出流,并定期将该流的更改刷新到JCR,但似乎没有类似的可用。
那么还有什么比使用普通javax.jcr.ValueFactory
和javax.jcr.Binary
更聪明吗?
答案 0 :(得分:2)
在考虑了我在这里的所有选项之后:
将日志保存在内存中,每次用户调用info/warn/error
时将其保存到CRX。 优点:日志存储在与迁移任务元数据相同的位置,便于查找和访问。 缺点:在大量日志条目的情况下,所有方法可能是最慢且资源效率最低的。
将日志保留在内存中,仅在迁移结束时将其保存到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)并且我想要运行每个迁移任务 - 这实际上只是一个单独的线程 - 来创建它自己的具有特殊名称的日志文件 - 一个独特的该迁移过程的标识符。
现在,Sling本身允许您通过org.apache.sling.commons.log.LogManager.factory.config
OSGi配置定义多个日志配置。但是这些日志配置对于这种情况来说太简单了 - 你不能用它来创建LogBack中调用的SiftingAppender - 日志追加器的特殊情况,它将为特定记录器每个线程实例化appender,而不是而不是一次和整个应用程序 - 换句话说,你不能指示LogBack使用OSGi配置为每个线程创建文件。
所以从逻辑上考虑你想要以编程方式在运行时采用Sling的LogBack配置(例如,在你上传自定义包并激活它的那一刻)并使用它为特定记录器配置这样的追加器。遗憾的是,虽然有很多关于如何通过logback.xml
配置LogBack的文档,但很少有文档描述如何通过LogBack的Java对象(如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
接口,这将为每个线程生成记录器配置的appender。
现在您可以通过以下方式获取记录器:
LogUtil.getTaskLogger("some-task-uuid")
并使用它按照crq-quickstart/logs/migration/task-<taskId>.log
taskId
之类的日志文件
根据文档,您还需要在完成此操作后释放此类记录器
LogUtil.releaseTaskLogger()
几乎就是这样。