diff --git a/pom.xml b/pom.xml
index ffe4f13..a49160e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,7 +31,7 @@
io.github.elf4j
elf4j-engine
- 7.0.5
+ 8.0.0
jar
elf4j-engine
A stand-alone Java log engine implementing the ELF4J (Easy Logging Facade for Java) API
@@ -69,6 +69,11 @@
elf4j
2.2.4
+
+ io.github.q3769
+ conseq4j
+ 20230128.20230506.0
+
com.dslplatform
dsl-json-java8
diff --git a/src/main/java/elf4j/engine/NativeLogger.java b/src/main/java/elf4j/engine/NativeLogger.java
index a3335bf..b46af52 100644
--- a/src/main/java/elf4j/engine/NativeLogger.java
+++ b/src/main/java/elf4j/engine/NativeLogger.java
@@ -28,17 +28,9 @@
import elf4j.Level;
import elf4j.Logger;
import elf4j.engine.service.LogService;
-import lombok.EqualsAndHashCode;
import lombok.NonNull;
-import lombok.Value;
import javax.annotation.concurrent.ThreadSafe;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-
-import static java.util.stream.Collectors.toMap;
/**
* Any instance of this class is thread-safe; it can be safely used as static, instance, or local variables. However,
@@ -47,11 +39,7 @@
* of variables.
*/
@ThreadSafe
-@Value
public class NativeLogger implements Logger {
- private static final Map> NATIVE_LOGGERS =
- EnumSet.allOf(Level.class).stream().collect(toMap(Function.identity(), l -> new HashMap<>()));
-
/**
* Name of this logger's "owner class" - the logging service client class that first requested this logger instance
* via the {@link Logger#instance()} service access method. The owner class is usually the same as the "caller
@@ -66,9 +54,9 @@ public class NativeLogger implements Logger {
* only the caller class name, this field will be used in liu of checking the stack trace; i.e. the stack trace
* walking is needed only when more caller details (e.g. method name, file name, line number) are required.
*/
- @NonNull String ownerClassName;
- @NonNull Level level;
- @EqualsAndHashCode.Exclude @NonNull LogService logService;
+ private final @NonNull String ownerClassName;
+ private final @NonNull Level level;
+ private final @NonNull NativeLoggerFactory nativeLoggerFactory;
/**
* Constructor only meant to be used by {@link NativeLoggerFactory} and this class itself
@@ -77,24 +65,30 @@ public class NativeLogger implements Logger {
* name of the owner class that requested this instance via the {@link Logger#instance()} method
* @param level
* severity level of this logger instance
- * @param logService
- * service delegate to do the logging
+ * @param nativeLoggerFactory
+ * log service access point from this instance, not reloadable
*/
- public NativeLogger(@NonNull String ownerClassName, @NonNull Level level, @NonNull LogService logService) {
+ public NativeLogger(@NonNull String ownerClassName,
+ @NonNull Level level,
+ @NonNull NativeLoggerFactory nativeLoggerFactory) {
this.ownerClassName = ownerClassName;
this.level = level;
- this.logService = logService;
+ this.nativeLoggerFactory = nativeLoggerFactory;
}
@Override
public NativeLogger atLevel(Level level) {
- return this.level == level ? this : NATIVE_LOGGERS.get(level)
- .computeIfAbsent(this.ownerClassName, k -> new NativeLogger(k, level, this.logService));
+ return this.level == level ? this : this.nativeLoggerFactory.getLogger(level, this.ownerClassName);
+ }
+
+ @Override
+ public @NonNull Level getLevel() {
+ return this.level;
}
@Override
public boolean isEnabled() {
- return this.logService.isEnabled(this);
+ return getLogService().isEnabled(this);
}
@Override
@@ -122,7 +116,21 @@ public void log(Throwable throwable, String message, Object... arguments) {
this.service(throwable, message, arguments);
}
+ /**
+ * @return directly callable log service, useful for log APIs than elf4j to use this engine
+ */
+ public LogService getLogService() {
+ return this.nativeLoggerFactory.getLogService();
+ }
+
+ /**
+ * @return owner/caller class of this logger instance
+ */
+ public @NonNull String getOwnerClassName() {
+ return this.ownerClassName;
+ }
+
private void service(Throwable throwable, Object message, Object[] arguments) {
- this.logService.log(this, NativeLogger.class, throwable, message, arguments);
+ getLogService().log(this, NativeLogger.class, throwable, message, arguments);
}
}
diff --git a/src/main/java/elf4j/engine/NativeLoggerFactory.java b/src/main/java/elf4j/engine/NativeLoggerFactory.java
index 757b21b..f3b15bd 100644
--- a/src/main/java/elf4j/engine/NativeLoggerFactory.java
+++ b/src/main/java/elf4j/engine/NativeLoggerFactory.java
@@ -33,8 +33,12 @@
import elf4j.spi.LoggerFactory;
import lombok.NonNull;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Function;
+
+import static java.util.stream.Collectors.toMap;
/**
*
@@ -44,15 +48,14 @@ public class NativeLoggerFactory implements LoggerFactory {
* Default to TRACE for this native implementation
*/
private static final Level DEFAULT_LOGGER_SEVERITY_LEVEL = Level.INFO;
- private static final Class> LOGGING_SERVICE_ACCESS_CLASS = Logger.class;
/**
* Made injectable for extensions other than this native ELF4J implementation
*/
@NonNull private final Level defaultLoggerLevel;
- private final Map nativeLoggers = new HashMap<>();
+ private final Map> nativeLoggers;
/**
- * The class that the API client calls first to get a logger instance. The client caller class of this class will be
- * the "owner class" of the logger instances this factory produces.
+ * The class or interface that the API client calls first to get a logger instance. The client caller class of this
+ * class will be the "owner class" of the logger instances this factory produces.
*
* For this native implementation, the service access class is the {@link Logger} interface itself as the client
* calls the static factory method {@link Logger#instance()} first to get a logger instance. If this library is used
@@ -66,12 +69,12 @@ public class NativeLoggerFactory implements LoggerFactory {
* Default constructor required by {@link java.util.ServiceLoader}
*/
public NativeLoggerFactory() {
- this(LOGGING_SERVICE_ACCESS_CLASS);
+ this(Logger.class);
}
/**
* @param serviceAccessClass
- * the class that the API client uses to obtain access to a logger instance
+ * the class or interface that the API client application calls first to a logger instance
*/
public NativeLoggerFactory(@NonNull Class> serviceAccessClass) {
this(DEFAULT_LOGGER_SEVERITY_LEVEL, serviceAccessClass, new DispatchingLogService());
@@ -82,6 +85,8 @@ public NativeLoggerFactory(@NonNull Class> serviceAccessClass) {
@NonNull LogService logService) {
this.defaultLoggerLevel = defaultLoggerLevel;
this.serviceAccessClass = serviceAccessClass;
+ this.nativeLoggers =
+ EnumSet.allOf(Level.class).stream().collect(toMap(Function.identity(), l -> new HashMap<>()));
this.logService = logService;
}
@@ -93,7 +98,15 @@ public NativeLoggerFactory(@NonNull Class> serviceAccessClass) {
*/
@Override
public NativeLogger logger() {
- return this.nativeLoggers.computeIfAbsent(StackTraceUtils.callerOf(this.serviceAccessClass).getClassName(),
- ownerClassName -> new NativeLogger(ownerClassName, this.defaultLoggerLevel, this.logService));
+ return getLogger(this.defaultLoggerLevel, StackTraceUtils.callerOf(this.serviceAccessClass).getClassName());
+ }
+
+ @NonNull LogService getLogService() {
+ return logService;
+ }
+
+ NativeLogger getLogger(Level level, String ownerClassName) {
+ return this.nativeLoggers.get(level)
+ .computeIfAbsent(ownerClassName, ownerClass -> new NativeLogger(ownerClass, level, this));
}
}
diff --git a/src/main/java/elf4j/engine/service/ConseqLogEventProcessor.java b/src/main/java/elf4j/engine/service/ConseqLogEventProcessor.java
new file mode 100644
index 0000000..5fa30ff
--- /dev/null
+++ b/src/main/java/elf4j/engine/service/ConseqLogEventProcessor.java
@@ -0,0 +1,109 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Qingtian Wang
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+package elf4j.engine.service;
+
+import conseq4j.execute.ConseqExecutor;
+import conseq4j.execute.SequentialExecutor;
+import elf4j.Level;
+import elf4j.engine.service.configuration.LogServiceConfiguration;
+import elf4j.engine.service.util.PropertiesUtils;
+import elf4j.engine.service.writer.LogWriter;
+import elf4j.util.InternalLogger;
+import lombok.NonNull;
+import org.awaitility.Awaitility;
+
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Log events are asynchronously processed, optionally by multiple concurrent threads. However, events issued by the
+ * same caller application thread are processed sequentially with the {@link ConseqExecutor} API. Thus, logs by
+ * different caller threads may arrive at the final destination (e.g. system Console or a log file) in any order;
+ * meanwhile, logs from the same caller thread will arrive sequentially in the same order as they are called in the
+ * orginal thread.
+ */
+public class ConseqLogEventProcessor implements LogEventProcessor {
+ private static final int DEFAULT_FRONT_BUFFER_CAPACITY = 256;
+ private static final int DEFAULT_CONCURRENCY = Runtime.getRuntime().availableProcessors();
+ private final LogWriter logWriter;
+ private final SequentialExecutor conseqExecutor;
+
+ private ConseqLogEventProcessor(LogWriter logWriter, SequentialExecutor conseqExecutor) {
+ this.logWriter = logWriter;
+ this.conseqExecutor = conseqExecutor;
+ LogServiceManager.INSTANCE.registerStop(this);
+ }
+
+ /**
+ * @param logServiceConfiguration
+ * entire configuration
+ * @return conseq executor
+ */
+ public static ConseqLogEventProcessor from(LogServiceConfiguration logServiceConfiguration) {
+ Properties properties = logServiceConfiguration.getProperties();
+ Integer workQueueCapacity = getWorkQueueCapacity(properties);
+ InternalLogger.INSTANCE.log(Level.INFO, "Log event work queue capacity: " + workQueueCapacity);
+ Integer concurrency = getConcurrency(properties);
+ InternalLogger.INSTANCE.log(Level.INFO, "Log process concurrency: " + concurrency);
+ SequentialExecutor conseqExecutor =
+ new ConseqExecutor.Builder().concurrency(concurrency).workQueueCapacity(workQueueCapacity).build();
+ return new ConseqLogEventProcessor(logServiceConfiguration.getLogServiceWriter(), conseqExecutor);
+ }
+
+ @NonNull
+ private static Integer getConcurrency(Properties properties) {
+ Integer concurrency = PropertiesUtils.getAsInteger("concurrency", properties);
+ concurrency = concurrency == null ? DEFAULT_CONCURRENCY : concurrency;
+ if (concurrency < 1) {
+ throw new IllegalArgumentException("Unexpected concurrency: " + concurrency + ", cannot be less than 1");
+ }
+ return concurrency;
+ }
+
+ @NonNull
+ private static Integer getWorkQueueCapacity(Properties properties) {
+ Integer workQueueCapacity = PropertiesUtils.getAsInteger("buffer.front", properties);
+ workQueueCapacity = workQueueCapacity == null ? DEFAULT_FRONT_BUFFER_CAPACITY : workQueueCapacity;
+ if (workQueueCapacity < 0) {
+ throw new IllegalArgumentException("Unexpected buffer.front: " + workQueueCapacity);
+ }
+ if (workQueueCapacity == 0) {
+ workQueueCapacity = 1;
+ }
+ return workQueueCapacity;
+ }
+
+ @Override
+ public void process(LogEvent logEvent) {
+ conseqExecutor.execute(() -> logWriter.write(logEvent), logEvent.getCallerThread().getId());
+ }
+
+ @Override
+ public void stop() {
+ this.conseqExecutor.shutdown();
+ Awaitility.with().timeout(30, TimeUnit.MINUTES).await().until(this.conseqExecutor::isTerminated);
+ }
+}
diff --git a/src/main/java/elf4j/engine/service/DispatchingLogService.java b/src/main/java/elf4j/engine/service/DispatchingLogService.java
index 3c68931..e7836b4 100644
--- a/src/main/java/elf4j/engine/service/DispatchingLogService.java
+++ b/src/main/java/elf4j/engine/service/DispatchingLogService.java
@@ -41,7 +41,7 @@ public class DispatchingLogService implements LogService {
*
*/
public DispatchingLogService() {
- this(Configuration.INSTANCE);
+ this(new RefreshableLogServiceConfiguration());
}
DispatchingLogService(LogServiceConfiguration logServiceConfiguration) {
@@ -80,16 +80,9 @@ public void log(@NonNull NativeLogger nativeLogger,
if (this.includeCallerDetail()) {
logEventBuilder.callerStack(new Throwable().getStackTrace()).serviceInterfaceClass(serviceInterfaceClass);
}
- if (this.includeCallerThread()) {
- Thread callerThread = Thread.currentThread();
- logEventBuilder.callerThread(new LogEvent.ThreadValue(callerThread.getName(), callerThread.getId()));
- }
- this.logServiceConfiguration.getLogEventIntakeThread()
- .execute(() -> this.logServiceConfiguration.getLogServiceWriter().write(logEventBuilder.build()));
- }
-
- private static class Configuration {
- private static final LogServiceConfiguration INSTANCE = new RefreshableLogServiceConfiguration();
+ Thread callerThread = Thread.currentThread();
+ logEventBuilder.callerThread(new LogEvent.ThreadValue(callerThread.getName(), callerThread.getId()));
+ this.logServiceConfiguration.getLogEventProcessor().process(logEventBuilder.build());
}
}
diff --git a/src/main/java/elf4j/engine/service/FixedCapacitySingleThreadExecutor.java b/src/main/java/elf4j/engine/service/FixedCapacitySingleThreadExecutor.java
deleted file mode 100644
index 96ec65a..0000000
--- a/src/main/java/elf4j/engine/service/FixedCapacitySingleThreadExecutor.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * MIT License
- *
- * Copyright (c) 2023 Qingtian Wang
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- *
- */
-
-package elf4j.engine.service;
-
-import elf4j.Level;
-import elf4j.util.InternalLogger;
-import lombok.NonNull;
-import org.awaitility.Awaitility;
-
-import java.util.concurrent.*;
-
-/**
- *
- */
-public class FixedCapacitySingleThreadExecutor implements LogEventIntakeThread {
- private static final int DEFAULT_FRONT_BUFFER_CAPACITY = 262144;
- private final ExecutorService executorService;
-
- /**
- * @param bufferCapacity
- * async work queue capacity for log events
- */
- public FixedCapacitySingleThreadExecutor(Integer bufferCapacity) {
- bufferCapacity = bufferCapacity == null ? DEFAULT_FRONT_BUFFER_CAPACITY : bufferCapacity;
- InternalLogger.INSTANCE.log(Level.INFO, "Service thread buffer capacity: " + bufferCapacity);
- this.executorService = new ThreadPoolExecutor(1,
- 1,
- 0L,
- TimeUnit.MILLISECONDS,
- bufferCapacity == 0 ? new SynchronousQueue<>() : new ArrayBlockingQueue<>(bufferCapacity),
- r -> new Thread(r, "elf4j-engine-writer-thread"),
- new BlockingRetryHandler());
- LogServiceManager.INSTANCE.registerStop(this);
- }
-
- @Override
- public void execute(@NonNull Runnable command) {
- this.executorService.execute(command);
- }
-
- @Override
- public void stop() {
- this.executorService.shutdown();
- Awaitility.with().timeout(30, TimeUnit.MINUTES).await().until(this.executorService::isTerminated);
- }
-
- /**
- *
- */
- static class BlockingRetryHandler implements RejectedExecutionHandler {
- private static void forceRetry(Runnable r, @NonNull ThreadPoolExecutor executor) {
- BlockingQueue workQueue = executor.getQueue();
- if (workQueue.offer(r)) {
- return;
- }
- boolean interrupted = false;
- try {
- while (true) {
- try {
- workQueue.put(r);
- break;
- } catch (InterruptedException e) {
- InternalLogger.INSTANCE.log(Level.ERROR,
- e,
- "Thread interrupted while enqueuing log task " + r + " to executor service "
- + executor);
- interrupted = true;
- }
- }
- } finally {
- if (interrupted) {
- InternalLogger.INSTANCE.log(Level.INFO,
- "Log task " + r + " was enqueued to executor service " + executor
- + " in spite of thread interruption");
- Thread.currentThread().interrupt();
- }
- }
- }
-
- @Override
- public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
- forceRetry(r, executor);
- }
- }
-}
diff --git a/src/main/java/elf4j/engine/service/LogEvent.java b/src/main/java/elf4j/engine/service/LogEvent.java
index f8c7cb6..227e147 100644
--- a/src/main/java/elf4j/engine/service/LogEvent.java
+++ b/src/main/java/elf4j/engine/service/LogEvent.java
@@ -44,13 +44,13 @@
public class LogEvent {
private static final int ADDITIONAL_STRING_BUILDER_CAPACITY = 32;
@NonNull NativeLogger nativeLogger;
+ @NonNull ThreadValue callerThread;
@NonNull Instant timestamp = Instant.now();
@Nullable Object message;
@Nullable Object[] arguments;
@Nullable Throwable throwable;
@Nullable Class> serviceInterfaceClass;
@Nullable StackTraceElement[] callerStack;
- @Nullable ThreadValue callerThread;
private static @NonNull CharSequence resolve(Object message, Object[] arguments) {
String suppliedMessage = Objects.toString(supply(message), "");
diff --git a/src/main/java/elf4j/engine/service/LogEventIntakeThread.java b/src/main/java/elf4j/engine/service/LogEventProcessor.java
similarity index 88%
rename from src/main/java/elf4j/engine/service/LogEventIntakeThread.java
rename to src/main/java/elf4j/engine/service/LogEventProcessor.java
index 22b9395..8bb3808 100644
--- a/src/main/java/elf4j/engine/service/LogEventIntakeThread.java
+++ b/src/main/java/elf4j/engine/service/LogEventProcessor.java
@@ -25,10 +25,13 @@
package elf4j.engine.service;
-import java.util.concurrent.Executor;
-
/**
*
*/
-public interface LogEventIntakeThread extends Executor, Stoppable {
-}
\ No newline at end of file
+public interface LogEventProcessor extends Stoppable {
+ /**
+ * @param logEvent
+ * to process
+ */
+ void process(LogEvent logEvent);
+}
diff --git a/src/main/java/elf4j/engine/service/LogServiceManager.java b/src/main/java/elf4j/engine/service/LogServiceManager.java
index 0eb2bed..ac8bbe2 100644
--- a/src/main/java/elf4j/engine/service/LogServiceManager.java
+++ b/src/main/java/elf4j/engine/service/LogServiceManager.java
@@ -79,15 +79,15 @@ public void refreshAll(Properties properties) {
*
*/
public void stopAll() {
- stopService();
- stopOutput();
+ stopFrontend();
+ stopBackend();
}
- private void stopOutput() {
- stoppables.stream().filter(s -> !(s instanceof LogEventIntakeThread)).parallel().forEach(Stoppable::stop);
+ private void stopBackend() {
+ stoppables.stream().filter(s -> !(s instanceof LogEventProcessor)).parallel().forEach(Stoppable::stop);
}
- private void stopService() {
- stoppables.stream().filter(LogEventIntakeThread.class::isInstance).parallel().forEach(Stoppable::stop);
+ private void stopFrontend() {
+ stoppables.stream().filter(LogEventProcessor.class::isInstance).parallel().forEach(Stoppable::stop);
}
}
diff --git a/src/main/java/elf4j/engine/service/configuration/LogServiceConfiguration.java b/src/main/java/elf4j/engine/service/configuration/LogServiceConfiguration.java
index 42df9f8..39917d2 100644
--- a/src/main/java/elf4j/engine/service/configuration/LogServiceConfiguration.java
+++ b/src/main/java/elf4j/engine/service/configuration/LogServiceConfiguration.java
@@ -26,7 +26,7 @@
package elf4j.engine.service.configuration;
import elf4j.engine.NativeLogger;
-import elf4j.engine.service.LogEventIntakeThread;
+import elf4j.engine.service.LogEventProcessor;
import elf4j.engine.service.writer.LogWriter;
import elf4j.engine.service.writer.StandardOutput;
@@ -55,12 +55,12 @@ public interface LogServiceConfiguration {
boolean isEnabled(NativeLogger nativeLogger);
/**
- * @return async executor for log event processing
+ * @return buffered standard out stream writer
*/
- LogEventIntakeThread getLogEventIntakeThread();
+ StandardOutput getStandardOutput();
/**
- * @return buffered standard out stream writer
+ * @return configured log event processor
*/
- StandardOutput getStandardOutput();
+ LogEventProcessor getLogEventProcessor();
}
diff --git a/src/main/java/elf4j/engine/service/configuration/RefreshableLogServiceConfiguration.java b/src/main/java/elf4j/engine/service/configuration/RefreshableLogServiceConfiguration.java
index 6711b6b..302a8bb 100644
--- a/src/main/java/elf4j/engine/service/configuration/RefreshableLogServiceConfiguration.java
+++ b/src/main/java/elf4j/engine/service/configuration/RefreshableLogServiceConfiguration.java
@@ -27,8 +27,8 @@
import elf4j.Level;
import elf4j.engine.NativeLogger;
-import elf4j.engine.service.FixedCapacitySingleThreadExecutor;
-import elf4j.engine.service.LogEventIntakeThread;
+import elf4j.engine.service.ConseqLogEventProcessor;
+import elf4j.engine.service.LogEventProcessor;
import elf4j.engine.service.LogServiceManager;
import elf4j.engine.service.util.PropertiesUtils;
import elf4j.engine.service.writer.BufferedStandardOutput;
@@ -53,9 +53,9 @@ public class RefreshableLogServiceConfiguration implements LogServiceConfigurati
private boolean noop;
private CallerLevels callerLevels;
private Map loggerEnablementCache;
- private LogEventIntakeThread logEventIntakeThread;
private StandardOutput standardOutput;
private LogWriter logServiceWriter;
+ private LogEventProcessor logEventProcessor;
/**
*
@@ -89,16 +89,15 @@ public boolean isEnabled(NativeLogger nativeLogger) {
return this.loggerEnablementCache.computeIfAbsent(nativeLogger, this::loadLoggerConfigurationCache);
}
- @Override
- public LogEventIntakeThread getLogEventIntakeThread() {
- return this.logEventIntakeThread;
- }
-
@Override
public StandardOutput getStandardOutput() {
return this.standardOutput;
}
+ public LogEventProcessor getLogEventProcessor() {
+ return this.logEventProcessor;
+ }
+
@Override
public void refresh(@Nullable Properties properties) {
this.properties = properties == null ? this.propertiesLoader.load() : properties;
@@ -127,10 +126,9 @@ private void parse(@Nullable Properties properties) {
}
this.callerLevels = CallerLevels.from(properties);
this.loggerEnablementCache = new ConcurrentHashMap<>();
- this.logEventIntakeThread =
- new FixedCapacitySingleThreadExecutor(PropertiesUtils.getAsInteger("buffer.front", properties));
this.standardOutput = new BufferedStandardOutput(properties.getProperty("stream"),
PropertiesUtils.getAsInteger("buffer.back", properties));
this.logServiceWriter = CooperatingWriterGroup.from(this);
+ this.logEventProcessor = ConseqLogEventProcessor.from(this);
}
}
diff --git a/src/test/java/elf4j/engine/NativeLoggerTest.java b/src/test/java/elf4j/engine/NativeLoggerTest.java
index 31988a7..67e03c2 100644
--- a/src/test/java/elf4j/engine/NativeLoggerTest.java
+++ b/src/test/java/elf4j/engine/NativeLoggerTest.java
@@ -35,8 +35,7 @@
import java.util.function.Supplier;
-import static elf4j.Level.TRACE;
-import static elf4j.Level.WARN;
+import static elf4j.Level.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.same;
@@ -46,11 +45,14 @@
class NativeLoggerTest {
@Mock LogService mockLogService;
+ NativeLoggerFactory mockNativeLoggerFactory;
+
NativeLogger nativeLogger;
@BeforeEach
void init() {
- nativeLogger = new NativeLogger(this.getClass().getName(), TRACE, mockLogService);
+ mockNativeLoggerFactory = new NativeLoggerFactory(INFO, NativeLogger.class, mockLogService);
+ nativeLogger = new NativeLogger(this.getClass().getName(), TRACE, mockNativeLoggerFactory);
}
@Nested
diff --git a/src/test/java/elf4j/engine/service/DispatchingLogServiceTest.java b/src/test/java/elf4j/engine/service/DispatchingLogServiceTest.java
index f7ab784..c854a3f 100644
--- a/src/test/java/elf4j/engine/service/DispatchingLogServiceTest.java
+++ b/src/test/java/elf4j/engine/service/DispatchingLogServiceTest.java
@@ -27,8 +27,10 @@
import elf4j.Level;
import elf4j.engine.NativeLogger;
+import elf4j.engine.NativeLoggerFactory;
import elf4j.engine.service.configuration.LogServiceConfiguration;
import elf4j.engine.service.writer.LogWriter;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -53,10 +55,12 @@ class isEnabled {
DispatchingLogService logService;
@Mock LogServiceConfiguration mockLogServiceConfiguration;
+ @Mock NativeLoggerFactory mockNativeLoggerFactory;
+
@Test
void delegateToConfiguration() {
logService = new DispatchingLogService(mockLogServiceConfiguration);
- stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService);
+ stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, mockNativeLoggerFactory);
logService.isEnabled(stubLogger);
@@ -71,14 +75,22 @@ class log {
@Mock LogServiceConfiguration mockLogServiceConfiguration;
@Mock LogWriter mockLogWriter;
@Captor ArgumentCaptor captorLogEntry;
+ LogEventProcessor stubLogEventProcessor;
+
+ @Mock NativeLoggerFactory mockNativeLoggerFactory;
+
+ @BeforeEach
+ void beforeEach() {
+ stubLogEventProcessor = new StubLogEventProcessor(mockLogWriter);
+ }
@Test
void callWriter() {
logService = new DispatchingLogService(mockLogServiceConfiguration);
- stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService);
+ stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, mockNativeLoggerFactory);
given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(true);
given(mockLogServiceConfiguration.getLogServiceWriter()).willReturn(mockLogWriter);
- given(mockLogServiceConfiguration.getLogEventIntakeThread()).willReturn(new StubLogEventIntakeThread());
+ given(mockLogServiceConfiguration.getLogEventProcessor()).willReturn(stubLogEventProcessor);
logService.log(stubLogger, this.getClass(), null, null, null);
@@ -88,11 +100,10 @@ void callWriter() {
@Test
void callThreadRequired() {
logService = new DispatchingLogService(mockLogServiceConfiguration);
- stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService);
- given(mockLogWriter.includeCallerThread()).willReturn(true);
+ stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, mockNativeLoggerFactory);
given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(true);
given(mockLogServiceConfiguration.getLogServiceWriter()).willReturn(mockLogWriter);
- given(mockLogServiceConfiguration.getLogEventIntakeThread()).willReturn(new StubLogEventIntakeThread());
+ given(mockLogServiceConfiguration.getLogEventProcessor()).willReturn(stubLogEventProcessor);
logService.log(stubLogger, this.getClass(), null, null, null);
@@ -102,28 +113,13 @@ void callThreadRequired() {
assertEquals(Thread.currentThread().getId(), captorLogEntry.getValue().getCallerThread().getId());
}
- @Test
- void callThreadNotRequired() {
- logService = new DispatchingLogService(mockLogServiceConfiguration);
- stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService);
- given(mockLogWriter.includeCallerThread()).willReturn(false);
- given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(true);
- given(mockLogServiceConfiguration.getLogServiceWriter()).willReturn(mockLogWriter);
- given(mockLogServiceConfiguration.getLogEventIntakeThread()).willReturn(new StubLogEventIntakeThread());
-
- logService.log(stubLogger, this.getClass(), null, null, null);
-
- then(mockLogWriter).should().write(captorLogEntry.capture());
- assertNull(captorLogEntry.getValue().getCallerThread());
- }
-
@Test
void callerDetailRequired() {
logService = new DispatchingLogService(mockLogServiceConfiguration);
- stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService);
+ stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, mockNativeLoggerFactory);
given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(true);
given(mockLogServiceConfiguration.getLogServiceWriter()).willReturn(mockLogWriter);
- given(mockLogServiceConfiguration.getLogEventIntakeThread()).willReturn(new StubLogEventIntakeThread());
+ given(mockLogServiceConfiguration.getLogEventProcessor()).willReturn(stubLogEventProcessor);
given(mockLogWriter.includeCallerDetail()).willReturn(true);
logService.log(stubLogger, this.getClass(), null, null, null);
@@ -135,11 +131,11 @@ void callerDetailRequired() {
@Test
void callDetailNotRequired() {
logService = new DispatchingLogService(mockLogServiceConfiguration);
- stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService);
+ stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, mockNativeLoggerFactory);
given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(true);
given(mockLogWriter.includeCallerDetail()).willReturn(false);
given(mockLogServiceConfiguration.getLogServiceWriter()).willReturn(mockLogWriter);
- given(mockLogServiceConfiguration.getLogEventIntakeThread()).willReturn(new StubLogEventIntakeThread());
+ given(mockLogServiceConfiguration.getLogEventProcessor()).willReturn(stubLogEventProcessor);
logService.log(stubLogger, this.getClass(), null, null, null);
@@ -150,24 +146,30 @@ void callDetailNotRequired() {
@Test
void onlyLogWhenEnabled() {
logService = new DispatchingLogService(mockLogServiceConfiguration);
- stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, logService);
+ stubLogger = new NativeLogger(this.getClass().getName(), Level.TRACE, mockNativeLoggerFactory);
given(mockLogServiceConfiguration.isEnabled(any(NativeLogger.class))).willReturn(false);
logService.log(stubLogger, this.getClass(), null, null, null);
then(mockLogServiceConfiguration).should(never()).getLogServiceWriter();
}
+ }
- private class StubLogEventIntakeThread implements LogEventIntakeThread {
- @Override
- public void stop() {
+ class StubLogEventProcessor implements LogEventProcessor {
+ final LogWriter logWriter;
+
+ StubLogEventProcessor(LogWriter logWriter) {
+ this.logWriter = logWriter;
+ }
+
+ @Override
+ public void process(LogEvent logEvent) {
+ logWriter.write(logEvent);
+ }
- }
+ @Override
+ public void stop() {
- @Override
- public void execute(Runnable command) {
- command.run();
- }
}
}
}
\ No newline at end of file
diff --git a/src/test/java/elf4j/engine/service/pattern/JsonPatternTest.java b/src/test/java/elf4j/engine/service/pattern/JsonPatternTest.java
index cd730af..50c354e 100644
--- a/src/test/java/elf4j/engine/service/pattern/JsonPatternTest.java
+++ b/src/test/java/elf4j/engine/service/pattern/JsonPatternTest.java
@@ -27,6 +27,7 @@
import elf4j.Level;
import elf4j.engine.NativeLogger;
+import elf4j.engine.NativeLoggerFactory;
import elf4j.engine.service.LogEvent;
import elf4j.engine.service.LogService;
import org.junit.jupiter.api.BeforeEach;
@@ -42,13 +43,15 @@
@ExtendWith(MockitoExtension.class)
class JsonPatternTest {
@Mock LogService stubLogService;
+
+ @Mock NativeLoggerFactory mockNativeLoggerFactory;
LogEvent mockLogEvent;
String mockMessage = "testLogMessage {}";
@BeforeEach
void beforeEach() {
mockLogEvent = LogEvent.builder()
- .nativeLogger(new NativeLogger("testLoggerName", Level.ERROR, stubLogService))
+ .nativeLogger(new NativeLogger("testLoggerName", Level.ERROR, mockNativeLoggerFactory))
.callerThread(LogEvent.ThreadValue.builder()
.name(Thread.currentThread().getName())
.id(Thread.currentThread().getId())
diff --git a/src/test/java/elf4j/engine/service/pattern/MessageAndExceptionPatternTest.java b/src/test/java/elf4j/engine/service/pattern/MessageAndExceptionPatternTest.java
index 398459f..66a6f68 100644
--- a/src/test/java/elf4j/engine/service/pattern/MessageAndExceptionPatternTest.java
+++ b/src/test/java/elf4j/engine/service/pattern/MessageAndExceptionPatternTest.java
@@ -27,6 +27,7 @@
import elf4j.Level;
import elf4j.engine.NativeLogger;
+import elf4j.engine.NativeLoggerFactory;
import elf4j.engine.service.LogEvent;
import elf4j.engine.service.LogService;
import org.junit.jupiter.api.BeforeEach;
@@ -42,6 +43,8 @@
@ExtendWith(MockitoExtension.class)
class MessageAndExceptionPatternTest {
@Mock LogService stubLogService;
+
+ @Mock NativeLoggerFactory mockNativeLoggerFactory;
LogEvent mockLogEvent;
String mockMessage = "testLogMessage {}";
Object[] mockArgs = new Object[] { "testArg1" };
@@ -50,7 +53,7 @@ class MessageAndExceptionPatternTest {
@BeforeEach
void beforeEach() {
mockLogEvent = LogEvent.builder()
- .nativeLogger(new NativeLogger("testLoggerName", Level.ERROR, stubLogService))
+ .nativeLogger(new NativeLogger("testLoggerName", Level.ERROR, mockNativeLoggerFactory))
.callerThread(LogEvent.ThreadValue.builder()
.name(Thread.currentThread().getName())
.id(Thread.currentThread().getId())
diff --git a/src/test/resources/elf4j-test.properties b/src/test/resources/elf4j-test.properties
index 19e5959..5280233 100644
--- a/src/test/resources/elf4j-test.properties
+++ b/src/test/resources/elf4j-test.properties
@@ -52,6 +52,9 @@ writer3=standard
writer3.pattern={json}
writer4=standard
writer4.pattern={timestamp:yyyy-MM-dd'T'HH:mm:ss.SSSXXX} {level:5} [{thread:name}] {class:compressed}#{method}(L{linenumber}@{filename}) -- {message}
-### Optional buffer capacity
-buffer.front=262144
-buffer.back=256
\ No newline at end of file
+### Optional front buffer - log event processor work queue capacity, default 256 log events (as hydrated in-memory objects)
+#buffer.front=256
+### Optional back buffer - output stream batch size, default 256 log events (as byte arrays)
+#buffer.back=256
+### Optional log event processing concurrency, default is jvm runtime available processors at application start time
+#concurrency=20
\ No newline at end of file