From 93b22d7e6fb755060f6e26678c1b825daa132f98 Mon Sep 17 00:00:00 2001 From: "dmitry.kubakhov" Date: Tue, 15 Nov 2022 12:41:20 +0100 Subject: [PATCH] Adding new module with supporting graalvm appenders. Signed-off-by: Dmitry Kubakhov Signed-off-by: DmitryKubakhov --- graalvm/build.gradle | 6 + graalvm/gradle.properties | 4 + .../appenders/GraalVMLazyAsyncAppender.java | 40 +++ .../appenders/GraalVMRollingFileAppender.java | 40 +++ .../graalvm/appenders/LazyAsyncAppender.java | 38 +++ .../appenders/LazyAsyncAppenderBase.java | 307 ++++++++++++++++++ settings.gradle | 4 +- 7 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 graalvm/build.gradle create mode 100644 graalvm/gradle.properties create mode 100644 graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/GraalVMLazyAsyncAppender.java create mode 100644 graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/GraalVMRollingFileAppender.java create mode 100644 graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/LazyAsyncAppender.java create mode 100644 graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/LazyAsyncAppenderBase.java diff --git a/graalvm/build.gradle b/graalvm/build.gradle new file mode 100644 index 0000000..d98b9ee --- /dev/null +++ b/graalvm/build.gradle @@ -0,0 +1,6 @@ + +description = POM_DESCRIPTION +dependencies { + compileOnly 'ch.qos.logback:logback-classic:1.2.3' + compileOnly 'org.graalvm.sdk:graal-sdk:22.3.0' +} diff --git a/graalvm/gradle.properties b/graalvm/gradle.properties new file mode 100644 index 0000000..4cf9b1d --- /dev/null +++ b/graalvm/gradle.properties @@ -0,0 +1,4 @@ +POM_NAME=logback-ext-graalvm +POM_ARTIFACT_ID=logback-ext-graalvm +POM_PACKAGING=jar +POM_DESCRIPTION="Logback Extensions :: GraalVM" \ No newline at end of file diff --git a/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/GraalVMLazyAsyncAppender.java b/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/GraalVMLazyAsyncAppender.java new file mode 100644 index 0000000..4eea690 --- /dev/null +++ b/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/GraalVMLazyAsyncAppender.java @@ -0,0 +1,40 @@ +package ch.qos.logback.ext.graalvm.appenders; + +import org.graalvm.nativeimage.ImageInfo; + +import ch.qos.logback.classic.spi.ILoggingEvent; + +/** + * Async appender for using with GraalVM. + */ +public class GraalVMLazyAsyncAppender extends LazyAsyncAppender { + + /** + * In the build phase of the image appender should not be started. + */ + @Override + public void start() { + if (!ImageInfo.inImageBuildtimeCode()) { + super.start(); + } + } + + /** + * While getting the even in NOT build image phase async appender must be started - otherwise start it. + * + * @param eventObject event to append. + */ + @Override + public void doAppend(ILoggingEvent eventObject) { + if (!ImageInfo.inImageBuildtimeCode()) { + if (!isStarted()) { + synchronized (this) { + if (!isStarted()) { + super.start(); + } + } + } + super.doAppend(eventObject); + } + } +} diff --git a/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/GraalVMRollingFileAppender.java b/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/GraalVMRollingFileAppender.java new file mode 100644 index 0000000..8b6c9f2 --- /dev/null +++ b/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/GraalVMRollingFileAppender.java @@ -0,0 +1,40 @@ +package ch.qos.logback.ext.graalvm.appenders; + +import org.graalvm.nativeimage.ImageInfo; + +import ch.qos.logback.core.rolling.RollingFileAppender; + +/** + * GraalVM implementation of the {@link RollingFileAppender} with initialization of all files descriptors not in the build image phase. + */ +public class GraalVMRollingFileAppender extends RollingFileAppender { + + /** + * In the build phase of the image appender should not be started. + */ + @Override + public void start() { + if (!ImageInfo.inImageBuildtimeCode()) { + super.start(); + } + } + + /** + * While getting the even in NOT build image phase async appender must be started - otherwise start it. + * + * @param eventObject event to append. + */ + @Override + public void doAppend(E eventObject) { + if (!ImageInfo.inImageBuildtimeCode()) { + if (!isStarted()) { + synchronized (this) { + if (!isStarted()) { + super.start(); + } + } + } + super.doAppend(eventObject); + } + } +} diff --git a/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/LazyAsyncAppender.java b/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/LazyAsyncAppender.java new file mode 100644 index 0000000..bf3a48d --- /dev/null +++ b/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/LazyAsyncAppender.java @@ -0,0 +1,38 @@ +package ch.qos.logback.ext.graalvm.appenders; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; + +/** + * Lazy async appender with worker initialization on start phase. + */ +public class LazyAsyncAppender extends LazyAsyncAppenderBase { + + boolean includeCallerData = false; + + /** + * Events of level TRACE, DEBUG and INFO are deemed to be discardable. + * + * @param event + * @return true if the event is of level TRACE, DEBUG or INFO false otherwise. + */ + protected boolean isDiscardable(ILoggingEvent event) { + Level level = event.getLevel(); + return level.toInt() <= Level.INFO_INT; + } + + protected void preprocess(ILoggingEvent eventObject) { + eventObject.prepareForDeferredProcessing(); + if (includeCallerData) + eventObject.getCallerData(); + } + + public boolean isIncludeCallerData() { + return includeCallerData; + } + + public void setIncludeCallerData(boolean includeCallerData) { + this.includeCallerData = includeCallerData; + } + +} diff --git a/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/LazyAsyncAppenderBase.java b/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/LazyAsyncAppenderBase.java new file mode 100644 index 0000000..c2a4d08 --- /dev/null +++ b/graalvm/src/main/java/ch/qos/logback/ext/graalvm/appenders/LazyAsyncAppenderBase.java @@ -0,0 +1,307 @@ +/** + * Logback: the reliable, generic, fast and flexible logging framework. + * Copyright (C) 1999-2015, QOS.ch. All rights reserved. + *

+ * This program and the accompanying materials are dual-licensed under + * either the terms of the Eclipse Public License v1.0 as published by + * the Eclipse Foundation + *

+ * or (per the licensee's choosing) + *

+ * under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation. + */ +package ch.qos.logback.ext.graalvm.appenders; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.UnsynchronizedAppenderBase; +import ch.qos.logback.core.spi.AppenderAttachable; +import ch.qos.logback.core.spi.AppenderAttachableImpl; +import ch.qos.logback.core.util.InterruptUtil; + +/** + * Lazy copy of the {@link ch.qos.logback.core.AsyncAppenderBase}. + * There is only 1 different - worker will be initialized in the start process. + */ +public class LazyAsyncAppenderBase extends UnsynchronizedAppenderBase implements AppenderAttachable { + + AppenderAttachableImpl aai = new AppenderAttachableImpl(); + BlockingQueue blockingQueue; + + /** + * The default buffer size. + */ + public static final int DEFAULT_QUEUE_SIZE = 256; + int queueSize = DEFAULT_QUEUE_SIZE; + + int appenderCount = 0; + + static final int UNDEFINED = -1; + int discardingThreshold = UNDEFINED; + boolean neverBlock = false; + + Worker worker; + + /** + * The default maximum queue flush time allowed during appender stop. If the + * worker takes longer than this time it will exit, discarding any remaining + * items in the queue + */ + public static final int DEFAULT_MAX_FLUSH_TIME = 1000; + int maxFlushTime = DEFAULT_MAX_FLUSH_TIME; + + /** + * Is the eventObject passed as parameter discardable? The base class's + * implementation of this method always returns 'false' but sub-classes may (and + * do) override this method. + *

+ * Note that only if the buffer is nearly full are events discarded. Otherwise, + * when the buffer is "not full" all events are logged. + * + * @param eventObject + * @return - true if the event can be discarded, false otherwise + */ + protected boolean isDiscardable(E eventObject) { + return false; + } + + /** + * Pre-process the event prior to queueing. The base class does no + * pre-processing but subclasses can override this behavior. + * + * @param eventObject + */ + protected void preprocess(E eventObject) { + } + + @Override + public void start() { + if (isStarted()) + return; + if (appenderCount == 0) { + addError("No attached appenders found."); + return; + } + if (queueSize < 1) { + addError("Invalid queue size [" + queueSize + "]"); + return; + } + blockingQueue = new ArrayBlockingQueue(queueSize); + + if (discardingThreshold == UNDEFINED) + discardingThreshold = queueSize / 5; + addInfo("Setting discardingThreshold to " + discardingThreshold); + worker = new Worker(); + worker.setDaemon(true); + worker.setName("AsyncAppender-Worker-" + getName()); + // make sure this instance is marked as "started" before staring the worker + // Thread + super.start(); + worker.start(); + } + + @Override + public void stop() { + if (!isStarted()) + return; + + // mark this appender as stopped so that Worker can also processPriorToRemoval + // if it is invoking + // aii.appendLoopOnAppenders + // and sub-appenders consume the interruption + super.stop(); + + // interrupt the worker thread so that it can terminate. Note that the + // interruption can be consumed by sub-appenders + worker.interrupt(); + + InterruptUtil interruptUtil = new InterruptUtil(context); + + try { + interruptUtil.maskInterruptFlag(); + + worker.join(maxFlushTime); + + // check to see if the thread ended and if not add a warning message + if (worker.isAlive()) { + addWarn("Max queue flush timeout (" + maxFlushTime + " ms) exceeded. Approximately " + + blockingQueue.size() + " queued events were possibly discarded."); + } else { + addInfo("Queue flush finished successfully within timeout."); + } + + } catch (InterruptedException e) { + int remaining = blockingQueue.size(); + addError("Failed to join worker thread. " + remaining + " queued events may be discarded.", e); + } finally { + interruptUtil.unmaskInterruptFlag(); + } + } + + @Override + protected void append(E eventObject) { + if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) { + return; + } + preprocess(eventObject); + put(eventObject); + } + + private boolean isQueueBelowDiscardingThreshold() { + return (blockingQueue.remainingCapacity() < discardingThreshold); + } + + private void put(E eventObject) { + if (neverBlock) { + blockingQueue.offer(eventObject); + } else { + putUninterruptibly(eventObject); + } + } + + private void putUninterruptibly(E eventObject) { + boolean interrupted = false; + try { + while (true) { + try { + blockingQueue.put(eventObject); + break; + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + public int getQueueSize() { + return queueSize; + } + + public void setQueueSize(int queueSize) { + this.queueSize = queueSize; + } + + public int getDiscardingThreshold() { + return discardingThreshold; + } + + public void setDiscardingThreshold(int discardingThreshold) { + this.discardingThreshold = discardingThreshold; + } + + public int getMaxFlushTime() { + return maxFlushTime; + } + + public void setMaxFlushTime(int maxFlushTime) { + this.maxFlushTime = maxFlushTime; + } + + /** + * Returns the number of elements currently in the blocking queue. + * + * @return number of elements currently in the queue. + */ + public int getNumberOfElementsInQueue() { + return blockingQueue.size(); + } + + public void setNeverBlock(boolean neverBlock) { + this.neverBlock = neverBlock; + } + + public boolean isNeverBlock() { + return neverBlock; + } + + /** + * The remaining capacity available in the blocking queue. + *

+ * See also {@link BlockingQueue#remainingCapacity() + * BlockingQueue#remainingCapacity()} + * + * @return the remaining capacity + * + */ + public int getRemainingCapacity() { + return blockingQueue.remainingCapacity(); + } + + public void addAppender(Appender newAppender) { + if (appenderCount == 0) { + appenderCount++; + addInfo("Attaching appender named [" + newAppender.getName() + "] to AsyncAppender."); + aai.addAppender(newAppender); + } else { + addWarn("One and only one appender may be attached to AsyncAppender."); + addWarn("Ignoring additional appender named [" + newAppender.getName() + "]"); + } + } + + public Iterator> iteratorForAppenders() { + return aai.iteratorForAppenders(); + } + + public Appender getAppender(String name) { + return aai.getAppender(name); + } + + public boolean isAttached(Appender eAppender) { + return aai.isAttached(eAppender); + } + + public void detachAndStopAllAppenders() { + aai.detachAndStopAllAppenders(); + } + + public boolean detachAppender(Appender eAppender) { + return aai.detachAppender(eAppender); + } + + public boolean detachAppender(String name) { + return aai.detachAppender(name); + } + + class Worker extends Thread { + + public void run() { + LazyAsyncAppenderBase parent = LazyAsyncAppenderBase.this; + AppenderAttachableImpl aai = parent.aai; + + // loop while the parent is started + while (parent.isStarted()) { + try { + List elements = new ArrayList(); + E e0 = parent.blockingQueue.take(); + elements.add(e0); + parent.blockingQueue.drainTo(elements); + for (E e : elements) { + aai.appendLoopOnAppenders(e); + } + } catch (InterruptedException e1) { + // exit if interrupted + break; + } + } + + addInfo("Worker thread will flush remaining events before exiting. "); + + for (E e : parent.blockingQueue) { + aai.appendLoopOnAppenders(e); + parent.blockingQueue.remove(e); + } + + aai.detachAndStopAllAppenders(); + } + } +} diff --git a/settings.gradle b/settings.gradle index c4241fe..963096b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,8 @@ rootProject.name = 'logback-ext-parent' include ':logback-ext-loggly' include ':logback-ext-spring' +include ':logback-ext-graalvm' project(':logback-ext-loggly').projectDir = "$rootDir/loggly" as File -project(':logback-ext-spring').projectDir = "$rootDir/spring" as File \ No newline at end of file +project(':logback-ext-spring').projectDir = "$rootDir/spring" as File +project(':logback-ext-graalvm').projectDir = "$rootDir/graalvm" as File \ No newline at end of file