3

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

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

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

面对的 JCR APIjavax.jcr.ValueFactory似乎javax.jcr.Binary并不真正支持这种方法,我宁愿每次添加一行日志时都被迫一遍又一遍地覆盖该文件(或更准确地说 - 二进制属性)。我担心性能。

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

那么还有什么比使用简单的javax.jcr.ValueFactoryand更聪明的javax.jcr.Binary吗?

4

1 回答 1

2

在考虑了一段时间后,我在这里拥有的所有选项:

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

  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 依赖项,这些依赖项导出以在您的自定义捆绑包中使用。缺点:日志和任务元数据分离。用户应该知道磁盘上的日志文件位置。

选项 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.LoggerContextSiftingAppender

因此,在阅读了 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()

差不多就是这样。

于 2017-05-24T14:20:34.023 回答