All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.questdb.log.LogFactory Maven / Gradle / Ivy

/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2023 QuestDB
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

package io.questdb.log;

import io.questdb.Metrics;
import io.questdb.mp.*;
import io.questdb.std.*;
import io.questdb.std.datetime.microtime.MicrosecondClock;
import io.questdb.std.datetime.microtime.MicrosecondClockImpl;
import io.questdb.std.str.CharSinkBase;
import io.questdb.std.str.StringSink;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;

public class LogFactory implements Closeable {

    public static final String CONFIG_SYSTEM_PROPERTY = "out";
    public static final String DEBUG_TRIGGER = "ebug";
    public static final String DEBUG_TRIGGER_ENV = "QDB_DEBUG";
    public static final String DEFAULT_CONFIG_NAME = "log.conf";
    // placeholder that can be used in log.conf to point to $root/log/ dir
    public static final String LOG_DIR_VAR = "${log.dir}";
    // name of default logging configuration file (in jar and in $root/conf/ dir)
    private static final String DEFAULT_CONFIG = "/io/questdb/site/conf/" + DEFAULT_CONFIG_NAME;
    private static final int DEFAULT_LOG_LEVEL = LogLevel.INFO | LogLevel.ERROR | LogLevel.CRITICAL | LogLevel.ADVISORY;
    private static final int DEFAULT_MSG_SIZE = 4 * 1024;
    private static final int DEFAULT_QUEUE_DEPTH = 1024;
    private static final String EMPTY_STR = "";
    private static final LengthDescendingComparator LDC = new LengthDescendingComparator();
    private static final CharSequenceHashSet reserved = new CharSequenceHashSet();
    private static LogFactory INSTANCE;
    private static boolean envEnabled = true;
    private static boolean overwriteWithSyncLogging = false;
    private static String rootDir;
    private final MicrosecondClock clock;
    private final AtomicBoolean closed = new AtomicBoolean();
    private final ObjList deferredLoggers = new ObjList<>();
    private final ObjHashSet jobs = new ObjHashSet<>();
    private final AtomicBoolean running = new AtomicBoolean();
    private final CharSequenceObjHashMap scopeConfigMap = new CharSequenceObjHashMap<>();
    private final ObjList scopeConfigs = new ObjList<>();
    private final StringSink sink = new StringSink();
    private final WorkerPool workerPool;
    private boolean configured = false;
    private int queueDepth = DEFAULT_QUEUE_DEPTH;
    private int recordLength = DEFAULT_MSG_SIZE;

    public LogFactory() {
        this(MicrosecondClockImpl.INSTANCE);
    }

    private LogFactory(MicrosecondClock clock) {
        this.clock = clock;
        workerPool = new WorkerPool(new WorkerPoolConfiguration() {
            @Override
            public String getPoolName() {
                return "logging";
            }

            @Override
            public int getWorkerCount() {
                return 1;
            }

            @Override
            public boolean isDaemonPool() {
                return true;
            }
        }, Metrics.disabled().health());
    }

    public static synchronized void closeInstance() {
        LogFactory logFactory = INSTANCE;
        if (logFactory != null) {
            logFactory.close(true);
            INSTANCE = null;
        }
    }

    public static void configureAsync() {
        overwriteWithSyncLogging = false;
    }

    public static void configureRootDir(String rootDir) {
        LogFactory.rootDir = rootDir;
    }

    public static void configureSync() {
        overwriteWithSyncLogging = true;
    }

    public static void disableEnv() {
        envEnabled = false;
    }

    public static void enableEnv() {
        envEnabled = true;
    }

    public static synchronized LogFactory getInstance() {
        LogFactory logFactory = INSTANCE;
        if (logFactory == null) {
            logFactory = new LogFactory();
            // Some log writers created in the later init() call may do some logging,
            // so we store the instance before the factory was fully initialized.
            // Any logging calls done on a non-initialized log factory and its loggers
            // are no-op. Once the factory is fully configured, it replaces no-op
            // loggers with the end ones.
            INSTANCE = logFactory;
            logFactory.init(rootDir);
        }
        return logFactory;
    }

    public static Log getLog(Class clazz) {
        return getLog(clazz.getName());
    }

    public static Log getLog(String key) {
        return getInstance().create(key);
    }

    public static synchronized void haltInstance() {
        LogFactory logFactory = INSTANCE;
        if (logFactory != null) {
            logFactory.haltThread();
        }
    }

    @SuppressWarnings({"EmptyMethod", "unused"})
    public static void init() {
    }

    public synchronized void add(final LogWriterConfig config) {
        assert !configured;
        final int index = scopeConfigMap.keyIndex(config.getScope());
        ScopeConfiguration scopeConf;
        if (index > -1) {
            scopeConfigMap.putAt(index, config.getScope(), scopeConf = new ScopeConfiguration(LogLevel.MAX));
            scopeConfigs.add(scopeConf);
        } else {
            scopeConf = scopeConfigMap.valueAtQuick(index);
        }
        scopeConf.add(config);
    }

    public synchronized void bind() {
        if (configured) {
            return;
        }

        configured = true;

        for (int i = 0, n = scopeConfigs.size(); i < n; i++) {
            ScopeConfiguration conf = scopeConfigs.get(i);
            conf.bind(jobs, queueDepth, recordLength);
        }

        scopeConfigMap.sortKeys(LDC);

        for (int i = 0, n = jobs.size(); i < n; i++) {
            LogWriter job = jobs.get(i);
            job.bindProperties(this);
            workerPool.assign(job);
        }
    }

    @Override
    public void close() {
        close(false);
    }

    public void close(boolean flush) {
        if (closed.compareAndSet(false, true)) {
            haltThread();
            for (int i = 0, n = jobs.size(); i < n; i++) {
                LogWriter job = jobs.get(i);
                try {
                    if (job != null && flush) {
                        try {
                            // noinspection StatementWithEmptyBody
                            while (job.run(0, Job.TERMINATING_STATUS)) {
                                // Keep running the job until it returns false to log all the buffered messages
                            }
                        } catch (Exception th) {
                            // Exception means we cannot log anymore. Perhaps network is down or disk is full.
                            // Switch to the next job.
                        }
                    }
                } finally {
                    Misc.freeIfCloseable(job);
                }
            }
            for (int i = 0, n = scopeConfigs.size(); i < n; i++) {
                Misc.free(scopeConfigs.getQuick(i));
            }
        }
    }

    public Log create(Class clazz) {
        return create(clazz.getName());
    }

    public synchronized Log create(String key) {
        if (!configured) {
            DeferredLogger log = new DeferredLogger(key);
            deferredLoggers.add(log);
            return log;
        }

        ScopeConfiguration scopeConfiguration = find(key);
        if (scopeConfiguration == null) {
            return new Logger(
                    clock,
                    compressScope(key, sink),
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null,
                    null
            );
        }
        final Holder inf = scopeConfiguration.getHolder(Numbers.msb(LogLevel.INFO));
        final Holder dbg = scopeConfiguration.getHolder(Numbers.msb(LogLevel.DEBUG));
        final Holder err = scopeConfiguration.getHolder(Numbers.msb(LogLevel.ERROR));
        final Holder cri = scopeConfiguration.getHolder(Numbers.msb(LogLevel.CRITICAL));
        final Holder adv = scopeConfiguration.getHolder(Numbers.msb(LogLevel.ADVISORY));
        if (!overwriteWithSyncLogging) {
            return new Logger(
                    clock,
                    compressScope(key, sink),
                    dbg == null ? null : dbg.ring,
                    dbg == null ? null : dbg.lSeq,
                    inf == null ? null : inf.ring,
                    inf == null ? null : inf.lSeq,
                    err == null ? null : err.ring,
                    err == null ? null : err.lSeq,
                    cri == null ? null : cri.ring,
                    cri == null ? null : cri.lSeq,
                    adv == null ? null : adv.ring,
                    adv == null ? null : adv.lSeq
            );
        }

        return new SyncLogger(
                clock,
                compressScope(key, sink),
                dbg == null ? null : dbg.ring,
                dbg == null ? null : dbg.lSeq,
                inf == null ? null : inf.ring,
                inf == null ? null : inf.lSeq,
                err == null ? null : err.ring,
                err == null ? null : err.lSeq,
                cri == null ? null : cri.ring,
                cri == null ? null : cri.lSeq,
                adv == null ? null : adv.ring,
                adv == null ? null : adv.lSeq
        );
    }

    @TestOnly
    public void flushJobs() {
        pauseThread();
        for (int i = 0, n = jobs.size(); i < n; i++) {
            LogWriter job = jobs.get(i);
            if (job != null) {
                try {
                    job.drain(0);
                } catch (Exception th) {
                    // Exception means we cannot log anymore. Perhaps network is down or disk is full.
                    // Switch to the next job.
                }
            }
        }
        startThread();
    }

    @TestOnly
    public ObjHashSet getJobs() {
        return jobs;
    }

    public int getQueueDepth() {
        return queueDepth;
    }

    public int getRecordLength() {
        return recordLength;
    }

    public synchronized void init(@Nullable String rootDir) {
        if (configured) {
            return;
        }

        String conf = System.getProperty(CONFIG_SYSTEM_PROPERTY);
        if (conf == null) {
            conf = DEFAULT_CONFIG;
        }

        boolean initialized = false;
        // prevent creating blank log dir from unit tests
        String logDir = ".";
        if (rootDir != null && DEFAULT_CONFIG.equals(conf)) {
            logDir = Paths.get(rootDir, "log").toString();
            File logDirFile = new File(logDir);
            if (!logDirFile.exists() && logDirFile.mkdir()) {
                System.err.printf("Created log directory: %s%n", logDir);
            }

            String logPath = Paths.get(rootDir, "conf", DEFAULT_CONFIG_NAME).toString();
            File f = new File(logPath);
            if (f.isFile() && f.canRead()) {
                System.err.printf("Reading log configuration from %s%n", logPath);
                try (FileInputStream fis = new FileInputStream(logPath)) {
                    Properties properties = new Properties();
                    properties.load(fis);
                    configureFromProperties(properties, logDir);
                    initialized = true;
                } catch (IOException e) {
                    throw new LogError("Cannot read " + logPath, e);
                }
            }
        }

        if (!initialized) {
            // in this order of initialization specifying -Dout might end up using internal jar resources ...
            try (InputStream is = LogFactory.class.getResourceAsStream(conf)) {
                if (is != null) {
                    Properties properties = new Properties();
                    properties.load(is);
                    configureFromProperties(properties, logDir);
                    System.err.println("Log configuration loaded from default internal file.");
                } else {
                    File f = new File(conf);
                    if (f.canRead()) {
                        try (FileInputStream fis = new FileInputStream(f)) {
                            Properties properties = new Properties();
                            properties.load(fis);
                            configureFromProperties(properties, logDir);
                            System.err.printf("Log configuration loaded from: %s%n", conf);
                        }
                    } else {
                        configureDefaultWriter();
                        System.err.println("Log configuration loaded using factory defaults.");
                    }
                }
            } catch (IOException e) {
                if (!DEFAULT_CONFIG.equals(conf)) {
                    throw new LogError("Cannot read " + conf, e);
                } else {
                    configureDefaultWriter();
                }
            }
        }

        // swap no-op loggers created by the configured log writers with the real ones
        for (int i = 0, n = deferredLoggers.size(); i < n; i++) {
            deferredLoggers.get(i).init(this);
        }
        deferredLoggers.clear();

        startThread();
    }

    public void startThread() {
        assert !closed.get();
        if (running.compareAndSet(false, true)) {
            for (int i = 0, n = jobs.size(); i < n; i++) {
                workerPool.assign(jobs.get(i));
            }
            workerPool.start();
        }
    }

    /**
     * Converts fully qualified class name into an abbreviated form:
     * com.questdb.mp.Sequence -> c.n.m.Sequence
     *
     * @param key     typically class name
     * @param builder used for producing the resulting form
     * @return abbreviated form of key
     */
    private static CharSequence compressScope(CharSequence key, StringSink builder) {
        builder.clear();
        char c = 0;
        boolean pick = true;
        int z = 0;
        for (int i = 0, n = key.length(); i < n; i++) {
            char a = key.charAt(i);
            if (a == '.') {
                if (!pick) {
                    builder.put(c).put('.');
                    pick = true;
                }
            } else if (pick) {
                c = a;
                z = i;
                pick = false;
            }
        }

        for (; z < key.length(); z++) {
            builder.put(key.charAt(z));
        }

        builder.put(' ');

        return builder.toString();
    }

    @SuppressWarnings("rawtypes")
    private static LogWriterConfig createWriter(final Properties properties, String writerName, String logDir) {
        final String writer = "w." + writerName + '.';
        final String clazz = getProperty(properties, writer + "class");
        final String levelStr = getProperty(properties, writer + "level");
        final String scope = getProperty(properties, writer + "scope");

        if (clazz == null) {
            return null;
        }

        final Class cl;
        final Constructor constructor;
        try {
            cl = Class.forName(clazz);
            constructor = cl.getDeclaredConstructor(RingQueue.class, SCSequence.class, int.class);
        } catch (ClassNotFoundException e) {
            throw new LogError("Class not found " + clazz, e);
        } catch (NoSuchMethodException e) {
            throw new LogError("Constructor(RingQueue, Sequence, int) expected: " + clazz, e);
        }

        int level = 0;
        if (levelStr != null) {
            for (String s : levelStr.split(",")) {
                switch (s.toUpperCase()) {
                    case "DEBUG":
                        level |= LogLevel.DEBUG;
                        break;
                    case "INFO":
                        level |= LogLevel.INFO;
                        break;
                    case "ERROR":
                        level |= LogLevel.ERROR;
                        break;
                    case "CRITICAL":
                        level |= LogLevel.CRITICAL;
                        break;
                    case "ADVISORY":
                        level |= LogLevel.ADVISORY;
                        break;
                    default:
                        throw new LogError("Unknown level: " + s);
                }
            }
        }

        if (isForcedDebug()) {
            level = level | LogLevel.DEBUG;
        }

        // enable all LOG levels above the minimum set one
        // ((-1 >>> (msb-1)) << msb) | level
        final int msb = Numbers.msb(level);
        level = (((-1 >>> (msb - 1)) << msb) | level) & LogLevel.MASK;

        return new LogWriterConfig(scope == null ? EMPTY_STR : scope, level, (ring, seq, level1) -> {
            try {
                LogWriter w1 = (LogWriter) constructor.newInstance(ring, seq, level1);

                for (String n : properties.stringPropertyNames()) {
                    if (n.startsWith(writer)) {
                        String p = n.substring(writer.length());

                        if (reserved.contains(p)) {
                            continue;
                        }

                        try {
                            Field f = cl.getDeclaredField(p);
                            if (f.getType() == String.class) {

                                String value = getProperty(properties, n);
                                if (logDir != null && value.contains(LOG_DIR_VAR)) {
                                    value = value.replace(LOG_DIR_VAR, logDir);
                                }

                                Unsafe.getUnsafe().putObject(w1, Unsafe.getUnsafe().objectFieldOffset(f), value);
                            }
                        } catch (Exception e) {
                            throw new LogError("Unknown property: " + n, e);
                        }
                    }
                }
                return w1;
            } catch (Exception e) {
                throw new LogError("Error creating log writer", e);
            }
        });
    }

    private static String getProperty(final Properties properties, String key) {
        if (envEnabled) {
            final String envKey = "QDB_LOG_" + key.replace('.', '_').toUpperCase();
            final String envValue = System.getenv(envKey);
            if (envValue == null) {
                return properties.getProperty(key);
            }
            System.err.println("    Using env: " + envKey + "=" + envValue);
            return envValue;
        }
        return properties.getProperty(key);
    }

    private static boolean isForcedDebug() {
        return System.getProperty(DEBUG_TRIGGER) != null || System.getenv().containsKey(DEBUG_TRIGGER_ENV);
    }

    private void configureDefaultWriter() {
        int level = DEFAULT_LOG_LEVEL;
        if (isForcedDebug()) {
            level = level | LogLevel.DEBUG;
        }
        add(new LogWriterConfig(level, LogConsoleWriter::new));
        bind();
    }

    private void configureFromProperties(Properties properties, String logDir) {
        String writers = getProperty(properties, "writers");

        if (writers == null) {
            configured = true;
            return;
        }

        String s = getProperty(properties, "queueDepth");
        if (s != null && s.length() > 0) {
            try {
                setQueueDepth(Numbers.parseInt(s));
            } catch (NumericException e) {
                throw new LogError("Invalid value for queueDepth");
            }
        }

        s = getProperty(properties, "recordLength");
        if (s != null && s.length() > 0) {
            try {
                setRecordLength(Numbers.parseInt(s));
            } catch (NumericException e) {
                throw new LogError("Invalid value for recordLength");
            }
        }

        for (String w : writers.split(",")) {
            LogWriterConfig conf = createWriter(properties, w.trim(), logDir);
            if (conf != null) {
                add(conf);
            }
        }

        bind();
    }

    private ScopeConfiguration find(CharSequence key) {
        ObjList keys = scopeConfigMap.keys();
        CharSequence k = null;

        for (int i = 0, n = keys.size(); i < n; i++) {
            CharSequence s = keys.getQuick(i);
            if (Chars.startsWith(key, s)) {
                k = s;
                break;
            }
        }

        if (k == null) {
            return null;
        }

        return scopeConfigMap.get(k);
    }

    private void haltThread() {
        if (running.compareAndSet(true, false)) {
            workerPool.halt();
        }
    }

    @TestOnly
    private void pauseThread() {
        if (running.compareAndSet(true, false)) {
            workerPool.pause();
        }
    }

    private void setQueueDepth(int queueDepth) {
        this.queueDepth = queueDepth;
    }

    private void setRecordLength(int recordLength) {
        this.recordLength = recordLength;
    }

    private static class DeferredLogger implements Log {

        private static final NoOpLogRecord noOpRecord = new NoOpLogRecord();

        private final String key;
        private Log delegate;

        public DeferredLogger(String key) {
            this.key = key;
        }

        @Override
        public LogRecord advisory() {
            if (delegate != null) {
                return delegate.advisory();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord advisoryW() {
            if (delegate != null) {
                return delegate.advisoryW();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord critical() {
            if (delegate != null) {
                return delegate.critical();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord criticalW() {
            if (delegate != null) {
                return delegate.criticalW();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord debug() {
            if (delegate != null) {
                return delegate.debug();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord debugW() {
            if (delegate != null) {
                return delegate.debugW();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord error() {
            if (delegate != null) {
                return delegate.error();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord errorW() {
            if (delegate != null) {
                return delegate.errorW();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord info() {
            if (delegate != null) {
                return delegate.info();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord infoW() {
            if (delegate != null) {
                return delegate.infoW();
            }
            return noOpRecord;
        }

        public void init(LogFactory logFactory) {
            this.delegate = logFactory.create(key);
        }

        @Override
        public LogRecord xDebugW() {
            if (delegate != null) {
                return delegate.xDebugW();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord xInfoW() {
            if (delegate != null) {
                return delegate.xInfoW();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord xadvisory() {
            if (delegate != null) {
                return delegate.xadvisory();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord xcritical() {
            if (delegate != null) {
                return delegate.xcritical();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord xdebug() {
            if (delegate != null) {
                return delegate.xdebug();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord xerror() {
            if (delegate != null) {
                return delegate.xerror();
            }
            return noOpRecord;
        }

        @Override
        public LogRecord xinfo() {
            if (delegate != null) {
                return delegate.xinfo();
            }
            return noOpRecord;
        }
    }

    private static class Holder implements Closeable {
        private final Sequence lSeq;
        private final RingQueue ring;
        private FanOut fanOut;
        private SCSequence wSeq;

        public Holder(int queueDepth, final int recordLength) {
            this.ring = new RingQueue<>(
                    LogRecordSink::new,
                    Numbers.ceilPow2(recordLength),
                    queueDepth,
                    MemoryTag.NATIVE_LOGGER
            );
            this.lSeq = new MPSequence(queueDepth);
        }

        @Override
        public void close() {
            Misc.free(ring);
        }
    }

    private static class LengthDescendingComparator implements Comparator, Serializable {
        @Override
        public int compare(CharSequence o1, CharSequence o2) {
            int l1, l2;
            if ((l1 = o1.length()) < (l2 = o2.length())) {
                return 1;
            }

            if (l1 > l2) {
                return -11;
            }

            return 0;
        }
    }

    private static class NoOpLogRecord implements LogRecord {

        @Override
        public void $() {
        }

        @Override
        public LogRecord $(CharSequence sequence) {
            return this;
        }

        @Override
        public LogRecord $(CharSequence sequence, int lo, int hi) {
            return this;
        }

        @Override
        public LogRecord $(int x) {
            return this;
        }

        @Override
        public LogRecord $(double x) {
            return this;
        }

        @Override
        public LogRecord $(long l) {
            return this;
        }

        @Override
        public LogRecord $(boolean x) {
            return this;
        }

        @Override
        public LogRecord $(char c) {
            return this;
        }

        @Override
        public LogRecord $(Throwable e) {
            return this;
        }

        @Override
        public LogRecord $(File x) {
            return this;
        }

        @Override
        public LogRecord $(Object x) {
            return this;
        }

        @Override
        public LogRecord $(Sinkable x) {
            return this;
        }

        @Override
        public LogRecord $256(long a, long b, long c, long d) {
            return this;
        }

        @Override
        public LogRecord $hex(long value) {
            return this;
        }

        @Override
        public LogRecord $hexPadded(long value) {
            return this;
        }

        @Override
        public LogRecord $ip(long ip) {
            return this;
        }

        @Override
        public LogRecord $ts(long x) {
            return this;
        }

        @Override
        public LogRecord $utf8(long lo, long hi) {
            return this;
        }

        @Override
        public boolean isEnabled() {
            return false;
        }

        @Override
        public LogRecord microTime(long x) {
            return this;
        }

        @Override
        public CharSinkBase put(char c) {
            return this;
        }

        @Override
        public LogRecord ts() {
            return this;
        }

        @Override
        public LogRecord utf8(CharSequence sequence) {
            return this;
        }
    }

    private static class ScopeConfiguration implements Closeable {
        private final int[] channels;
        private final ObjList holderList = new ObjList<>();
        private final IntObjHashMap holderMap = new IntObjHashMap<>();
        private final ObjList writerConfigs = new ObjList<>();
        private int ci = 0;

        public ScopeConfiguration(int levels) {
            this.channels = new int[levels];
        }

        public void bind(ObjHashSet jobs, int queueDepth, int recordLength) {
            // create queues for processed channels
            for (int index : channels) {
                if (index > 0) {
                    int keyIndex = holderMap.keyIndex(index);
                    if (keyIndex > -1) {
                        Holder h = new Holder(queueDepth, recordLength);
                        holderMap.putAt(keyIndex, index, h);
                        holderList.add(h);
                    }
                }
            }

            for (int i = 0, n = writerConfigs.size(); i < n; i++) {
                LogWriterConfig c = writerConfigs.getQuick(i);
                // the channels array has a guarantee that
                // all bits in level mask will point to the same queue,
                // so we just get most significant bit number
                // and dereference queue on its index
                Holder h = holderMap.get(channels[Numbers.msb(c.getLevel())]);
                // check if this queue was used by another writer
                if (h.wSeq != null) {
                    // yes, it was
                    if (h.fanOut == null) {
                        h.fanOut = FanOut.to(h.wSeq).and(h.wSeq = new SCSequence());
                    } else {
                        h.fanOut.and(h.wSeq = new SCSequence());
                    }
                } else {
                    // we are here first!
                    h.wSeq = new SCSequence();
                }
                // now h.wSeq contains out writer's sequence
                jobs.add(c.getFactory().createLogWriter(h.ring, h.wSeq, c.getLevel()));
            }

            // and the last step is to link dependent sequences
            for (int i = 0, n = holderList.size(); i < n; i++) {
                Holder h = holderList.getQuick(i);
                if (h.fanOut != null) {
                    h.lSeq.then(h.fanOut).then(h.lSeq);
                } else {
                    h.lSeq.then(h.wSeq).then(h.lSeq);
                }
            }
        }

        @Override
        public void close() {
            for (int i = 0, n = holderList.size(); i < n; i++) {
                Misc.free(holderList.getQuick(i));
            }
        }

        /**
         * Aggregates channels into set of queues. Consumer interest is represented by
         * level, where consumer sets bits corresponding to channel indexes is it interested in.
         * 

* Consumer 1 requires channels D & E. So its interest looks like {1,0,1} * Consumer 2 requires channel I, so its interest is {0,1,0} *

* This method combines these interests as follows: *

* channels = {1,2,1} *

* which means that there will be need to 2 queues (1 and 2) and that Consumer 1 * will be using queue 1 and consumer 2 will be using queue 2. *

* More complex scenario where consumer interests overlap, for example: *

* consumer 1 {1,1,0} * consumer 2 {0,1,1} *

* these interests will be combined as follows: *

* channels = {1,1,1} *

* which means that both consumers will be sharing same queue and they will have to * filter applicable messages as they get them. *

* Algorithm iterates over set of bits in "level" twice. First pass is to establish * minimum number of channel[] element out of those entries where bit in level is set. * Additionally, this pass will set channel[] elements to current consumer index where * channel[] element is zero. *

* Second pass sets channel[] element to min value found on first pass. * * @param conf LogWriterConfig */ private void add(LogWriterConfig conf) { int mask = conf.getLevel(); int min = Integer.MAX_VALUE; int q = ++ci; for (int i = 0, n = channels.length; i < n; i++) { if (((mask >> i) & 1) == 1) { int that = channels[i]; if (that == 0) { channels[i] = q; } if (that > 0 && that < min) { min = that; } } } if (mask > 1 && min < Integer.MAX_VALUE) { for (int i = 0, n = channels.length; i < n; i++) { if (((mask >> i) & 1) == 1) { channels[i] = min; } } } writerConfigs.add(conf); } private Holder getHolder(int index) { return holderMap.get(channels[index]); } } static { reserved.add("scope"); reserved.add("class"); reserved.add("level"); Os.init(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy