JCR

时间:2017-05-11 17:18:32

标签: aem jcr jackrabbit crx jackrabbit-oak

我要运行一个可能需要几分钟甚至几小时的过程。为了跟踪此类运行的历史记录,我在每次运行时创建一个自定义类型的节点,其中存储了相关的流程另外我想在这样的节点下存储日志文件。这似乎是更加一致和方便的方法,而不是将日志文件存储在磁盘上,与进程元分开。

现在nt:file nodetype本身有一个jcr:content子节点,其jcr:data属性允许我存储二进制内容。这适用于文件的一次性或不经常的内容更改。

但是我会不断向该文件附加新内容,除此之外,还要在单独的线程中调查它的内容(以跟踪进度)。

面对javax.jcr.ValueFactoryjavax.jcr.Binary的JCR API似乎并不支持这样的方法,我宁愿被迫覆盖该文件(或更准确地说 - 二元属性)每次我添加一行日志时都会重复。我担心表现。

我搜索了文档中的工具,这些工具可以让我打开该文件的输出流,并定期将该流的更改刷新到JCR,但似乎没有类似的可用。

那么还有什么比使用普通javax.jcr.ValueFactoryjavax.jcr.Binary更聪明吗?

1 个答案:

答案 0 :(得分:2)

在考虑了我在这里的所有选项之后:

  1. 将日志保存在内存中,每次用户调用info/warn/error时将其保存到CRX。 优点:日志存储在与迁移任务元数据相同的位置,便于查找和访问。 缺点:在大量日志条目的情况下,所有方法可能是最慢且资源效率最低的。

  2. 将日志保留在内存中,仅在迁移结束时将其保存到JCR。 优点:简化重构当前解决方案,减少迁移过程中CRX的压力。 缺点:无法跟踪实时进度,在意外错误或实例关闭期间潜在地丢失日志。

  3. 为每个日志条目而不是log.txt创建自定义类型的节点。通过特殊日志servlet在文本文件中聚合日志。即/var/migration/uuid/log.txt/var/migration/uuid/log.json优点:更多JCR存储此类内容的方式。使用自定义节点类型和索引应该足够快,可以考虑作为选项。具有支持文本和日志的json格式的多样性。 缺点:与当前方法的性能比较不明确。由于大量节点位于同一级别而导致的潜在问题。用户应该知道日志servlet存在,否则用户无法以方便的格式看到它们。如果有大量日志条目,则日志servlet性能不明确。

  4. 在文件系统上创建日志文件(比如说crx-quickstart/logs/migration/<uuid>.log),通过API显示内容(如果需要),能够将日志API响应中继到最后100-1000行。 优点:日志文件存储在文件系统中时的经典和众所周知的日志方法。 Sling提供已配置的绑定slf4jLogBack,并导出所有需要的LogBack依赖项,以便在自定义捆绑包中使用。 缺点:分离日志和任务元数据。用户应该知道磁盘上的日志文件位置。

  5. 选项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()
    

    几乎就是这样。