Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ mockito = "5.20.0"
assertj-core = "4.0.0-M1"
# https://mvnrepository.com/artifact/org.awaitility/awaitility
awaitility = "4.3.0"
# https://mvnrepository.com/artifact/tools.jackson.core/jackson-core/3.2.0
jackson = "3.2.0"

[libraries]
project-reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "project-reactor" }
Expand All @@ -40,6 +42,8 @@ mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }
assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj-core" }
awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" }
jackson-core = { module = "tools.jackson.core:jackson-core", version.ref = "jackson" }
jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" }

[bundles]
mail = ["jakarta-mail-api", "angus-mail"]
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*/
public interface LoggerFactory {

String ROOT_LOGGER_NAME = "ROOT";

/**
* Creates or gets a logger with the specified name.
*
Expand All @@ -26,12 +28,12 @@ public interface LoggerFactory {
Logger getLogger(Class<?> type);

/**
* Returns the default logger.
* Returns the root logger.
*
* @return the default logger
* @return the root logger
* @since 10.0.0
*/
Logger getDefault();
Logger getRootLogger();

/**
* Returns the logger service.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class LoggerManager {
* @since 10.0.0
*/
public static Logger getDefaultLogger() {
return LOGGER_FACTORY.getDefault();
return LOGGER_FACTORY.getRootLogger();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public Logger getLogger(Class<?> type) {
}

@Override
public Logger getDefault() {
public Logger getRootLogger() {
return NO_OPS_LOGGER;
}

Expand Down
10 changes: 10 additions & 0 deletions rlib-logger-impl-json/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
id("configure-java")
id("configure-publishing")
}

dependencies {
api projects.rlibLoggerImpl
api libs.jackson.core
api libs.jackson.databind
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package javasabr.rlib.logger.impl.config.loader.json;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javasabr.rlib.common.util.StringUtils;
import javasabr.rlib.logger.api.LoggerLevel;
import javasabr.rlib.logger.impl.config.LoggerConfig;
import javasabr.rlib.logger.impl.config.consumer.LogMessageConsumer;
import javasabr.rlib.logger.impl.config.consumer.impl.ConsoleMessageConsumer;
import javasabr.rlib.logger.impl.config.consumer.impl.CustomLogMessageConsumer;
import javasabr.rlib.logger.impl.config.impl.LoggerConfigBuilder;
import javasabr.rlib.logger.impl.config.loader.LoggerConfigLoader;
import javasabr.rlib.logger.impl.config.loader.json.dto.JsonLoggerConfigDto;
import javasabr.rlib.logger.impl.config.loader.json.dto.JsonLoggerConfigDto.ConsumerDto;
import javasabr.rlib.logger.impl.config.loader.json.dto.JsonLoggerConfigDto.LoggerDto;
import javasabr.rlib.logger.impl.config.loader.json.dto.JsonLoggerConfigDto.RenderDto;
import javasabr.rlib.logger.impl.config.render.LogMessageRender;
import javasabr.rlib.logger.impl.config.render.impl.CustomLogMessageRender;
import javasabr.rlib.logger.impl.config.render.impl.SimpleLogMessageRender;
import javasabr.rlib.logger.impl.config.render.impl.pattern.PatternLogMessageRender;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import tools.jackson.databind.ObjectMapper;

@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class JsonLoggerConfigLoader implements LoggerConfigLoader {

public static final String FILE_MAIN = "rlib.logger.json";
public static final String FILE_TEST = "rlib.logger-test.json";

ObjectMapper objectMapper = new ObjectMapper();

@Override
public Optional<LoggerConfig> tryToLoad() {
ClassLoader classLoader = Thread
.currentThread()
.getContextClassLoader();
InputStream configStream = classLoader.getResourceAsStream(FILE_TEST);
if (configStream == null) {
configStream = classLoader.getResourceAsStream(FILE_MAIN);
}
if (configStream == null) {
return Optional.empty();
} else {
return Optional.of(load(configStream));
}
}

@Override
public int order() {
return LoggerConfigLoader.ORDER_NORMAL;
}

private LoggerConfig load(InputStream inputStream) {
try (var in = inputStream) {
JsonLoggerConfigDto configDto = objectMapper.readValue(in, JsonLoggerConfigDto.class);
return load(configDto);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private LoggerConfig load(JsonLoggerConfigDto configDto) {

var builder = new LoggerConfigBuilder();

List<CreatedRender> createdRenders = configDto
.renders()
.stream()
.map(this::createRender)
.toList();

Map<String, LogMessageRender> renderMap = createdRenders
.stream()
.collect(Collectors.toMap(CreatedRender::name, CreatedRender::render));

List<CreatedConsumer> createdConsumers = configDto
.consumers()
.stream()
.map(consumerDto -> createConsumer(renderMap, consumerDto))
.toList();

Map<String, LogMessageConsumer> consumerMap = createdConsumers
.stream()
.collect(Collectors.toMap(CreatedConsumer::name, CreatedConsumer::consumer));

for (LoggerDto loggerDto : configDto.loggers()) {
if (loggerDto.level() != null) {
builder.registerLoggerLevel(loggerDto.name(), loggerDto.level());
}
Set<String> consumerNames = loggerDto.consumerNames();
if (!consumerNames.isEmpty()) {
for (String consumerName : consumerNames) {
LogMessageConsumer consumer = consumerMap.get(consumerName);
if (consumer == null) {
throw new RuntimeException("Consumer " + consumerName + " not found");
}
LoggerLevel level = loggerDto.level();
if (level == null) {
level = LoggerLevel.TRACE;
}
builder.registerLoggerConsumer(loggerDto.name(), level, consumer);
}
}
}

return builder.build();
}

CreatedRender createRender(RenderDto renderDto) {
LogMessageRender render = switch (renderDto.type()) {
case SIMPLE -> new SimpleLogMessageRender();
case PATTERN -> new PatternLogMessageRender(renderDto.args());
case CUSTOM -> createCustomRender(renderDto);
};
return new CreatedRender(renderDto.name(), render);
}

LogMessageRender createCustomRender(RenderDto renderDto) {
String className = renderDto.className();
if (StringUtils.isBlank(className)) {
throw new IllegalArgumentException("'class'' is required for custom render");
}
Comment on lines +128 to +130
ClassLoader classLoader = Thread
.currentThread()
.getContextClassLoader();
try {
var targetClass = (Class<? extends CustomLogMessageRender>) classLoader.loadClass(className);
Constructor<? extends CustomLogMessageRender> constructor = targetClass.getDeclaredConstructor(Map.class);
return constructor.newInstance(renderDto.args());
Comment on lines +135 to +137
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}

CreatedConsumer createConsumer(Map<String, LogMessageRender> renderMap, ConsumerDto consumerDto) {
LogMessageRender render = renderMap.get(consumerDto.renderName());
if (render == null) {
throw new IllegalArgumentException("Unknown render with name: " + consumerDto.renderName());
}
LogMessageConsumer consumer = switch (consumerDto.type()) {
case CONSOLE -> new ConsoleMessageConsumer(render);
case CUSTOM -> createCustomConsumer(render, consumerDto);
};
return new CreatedConsumer(consumerDto.name(), consumer);
}

LogMessageConsumer createCustomConsumer(LogMessageRender render, ConsumerDto consumerDto) {
String className = consumerDto.className();
if (StringUtils.isBlank(className)) {
throw new IllegalArgumentException("'class'' is required for custom consumer");
}
Comment on lines +158 to +160
ClassLoader classLoader = Thread
.currentThread()
.getContextClassLoader();
try {
var targetClass = (Class<? extends CustomLogMessageConsumer>) classLoader.loadClass(className);
Constructor<? extends CustomLogMessageConsumer> constructor = targetClass
.getDeclaredConstructor(LogMessageRender.class, Map.class);
return constructor.newInstance(render, consumerDto.args());
Comment on lines +165 to +168
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}

private record CreatedRender(String name, LogMessageRender render) {}
private record CreatedConsumer(String name, LogMessageConsumer consumer) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package javasabr.rlib.logger.impl.config.loader.json;

import javasabr.rlib.collections.array.Array;
import javasabr.rlib.logger.impl.config.loader.LoggerConfigLoader;
import javasabr.rlib.logger.impl.config.loader.spi.LoggerConfigLoadersProvider;

public class JsonLoggerConfigLoadersProvider implements LoggerConfigLoadersProvider {

private static final Array<LoggerConfigLoader> LOADERS = Array.of(new JsonLoggerConfigLoader());

@Override
public Array<LoggerConfigLoader> getLoggerConfigLoaders() {
return LOADERS;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package javasabr.rlib.logger.impl.config.loader.json.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javasabr.rlib.common.util.StringUtils;
import javasabr.rlib.logger.api.LoggerLevel;
import org.jspecify.annotations.Nullable;

public record JsonLoggerConfigDto(
List<LoggerDto> loggers,
List<RenderDto> renders,
List<ConsumerDto> consumers) {

public JsonLoggerConfigDto {
loggers = loggers == null ? List.of() : List.copyOf(loggers);
renders = renders == null ? List.of() : List.copyOf(renders);
consumers = consumers == null ? List.of() : List.copyOf(consumers);
}

public record LoggerDto(
String name,
@Nullable LoggerLevel level,
@JsonProperty("consumers") Set<String> consumerNames) {

public LoggerDto {
consumerNames = consumerNames == null ? Set.of() : Set.copyOf(consumerNames);
}
Comment thread
Copilot marked this conversation as resolved.
}

public record RenderDto(
String name,
RenderType type,
@JsonProperty("class") @Nullable String className,
Map<String, Object> args) {

public RenderDto {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("name is blank");
} else if (type == null) {
throw new IllegalArgumentException("type is null");
}
args = args == null ? Map.of() : Map.copyOf(args);
}
}

public enum RenderType {
SIMPLE,
PATTERN,
CUSTOM
}

public record ConsumerDto(
String name,
ConsumerType type,
@JsonProperty("render") String renderName,
@JsonProperty("class") @Nullable String className,
Map<String, Object> args) {

public ConsumerDto {
args = args == null ? Map.of() : Map.copyOf(args);
}
Comment on lines +64 to +71
}

public enum ConsumerType {
CONSOLE,
CUSTOM
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NullMarked
package javasabr.rlib.logger.impl.config.loader.json.dto;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NullMarked
package javasabr.rlib.logger.impl.config.loader.json;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
javasabr.rlib.logger.impl.config.loader.json.JsonLoggerConfigLoadersProvider
Loading
Loading