Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 on lines +27 to +29
}

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 +61 to +63
}

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