Skip to content

Commit

Permalink
Merge pull request #95 from hkupty/improve-tests
Browse files Browse the repository at this point in the history
Restructure penna-api
  • Loading branch information
hkupty authored Mar 22, 2024
2 parents 2441a6c + d256c25 commit 4fe7ceb
Show file tree
Hide file tree
Showing 33 changed files with 313 additions and 130 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ the PATCH component is omitted when its value is `0`.
#### Changed

- Replace configuration interfaces([#89](https://github.com/hkupty/penna/pull/89))
- Restructure namespaces([#95](https://github.com/hkupty/penna/pull/95))

### `penna-core`

Expand All @@ -22,6 +23,7 @@ the PATCH component is omitted when its value is `0`.
- Remove ad-hoc configuration mechanism([#89](https://github.com/hkupty/penna/pull/89))
- Remove sink proxy([#94](https://github.com/hkupty/penna/pull/94))
- Minor cleanups and adjusts for JDK21 ([#94](https://github.com/hkupty/penna/pull/94))
- Provide optional runtime controls([#95](https://github.com/hkupty/penna/pull/95))

### `penna-yaml-config`

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=0.8.0-rc1
version=0.8.0-rc2
2 changes: 2 additions & 0 deletions penna-api/src/main/java/penna/api/config/ConfigToLogger.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package penna.api.config;

import penna.api.models.Config;

/**
* Class exists to contextually bind a configuration object to a logger by its name.
*/
Expand Down
113 changes: 28 additions & 85 deletions penna-api/src/main/java/penna/api/config/Manager.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package penna.api.config;

import penna.api.config.internal.ManagerImpl;
import penna.api.models.Config;

import java.util.Objects;
import java.util.ServiceLoader;
import java.util.function.Function;
import java.util.function.Supplier;

Expand All @@ -10,50 +12,33 @@
* This allows the ManagerImpl to evolve its internal configuration while still encapsulating its behavior
* from outside third-parties.
*/
public sealed interface Manager {
/**
* This is where the concrete implementation of a {@link Manager} will be created and stored
*/
class Factory {
private Factory() {}

private static final ServiceLoader<Provider> loader = ServiceLoader.load(Provider.class);
private static ManagerImpl instance;

/**
* Returns a singleton instance of {@link Manager} once and if it's created, returning null otherwise.
*
* @return a concrete implementation of {@link Manager} or null.
*/
public static ManagerImpl getInstance() {
return instance;
}
public sealed interface Manager permits ManagerImpl {

/**
* Used to initialize the {@link Manager} for a given {@link Storage} implementation.
*
* @param storage The concrete {@link Storage} implementation that will effectively store the configuration.
*/
public static void initialize(Storage storage) {
// Doesn't re-initializes;
if (instance != null) return;

loader.reload();
instance = new ManagerImpl(storage);
var providers = loader.stream()
.map(provider -> {
try {
return provider.get();
} catch (Throwable ex) {
return null;
}
})
.filter(Objects::nonNull)
.filter(provider -> provider.register(instance))
.toList();

providers.forEach(Provider::init);
}
/**
* This method initializes and creates a manager, but does not replace the existing one.
* @param storage The implementation of the logger storage that holds the configurations
* @return a new initialized instance of the manager that initialized {@link Provider}s
* and registered itself with them
*/
static Manager create(Storage storage) {
ManagerImpl.loader.reload();
var instance = new ManagerImpl(storage);
var providers = ManagerImpl.loader.stream()
.map(provider -> {
try {
return provider.get();
} catch (Throwable ex) {
return null;
}
})
.filter(Objects::nonNull)
.filter(provider -> provider.register(instance))
.toList();

providers.forEach(Provider::init);

return instance;
}

/**
Expand All @@ -80,46 +65,4 @@ public static void initialize(Storage storage) {
*/
void update(String logger, Function<Config, Config> action);

/**
* This is the central piece of the configuration management.
* It should not be created manually, but instead through {@link Factory#initialize(Storage)} and subsequent
* instance requests fetched through {@link Factory#getInstance()}.
*/
final class ManagerImpl implements Manager {
private final Storage storage;

ManagerImpl(Storage storage) {
this.storage = storage;
}

@Override
public void set(ConfigToLogger... configs) {
storage.apply(configs);
}

@Override
public void set(String logger, Supplier<Config> action) {
Config config = action.get();
ConfigToLogger item = switch (logger) {
case String path when path.isEmpty() -> new ConfigToLogger.RootLoggerConfigItem(config);
case String path -> new ConfigToLogger.NamedLoggerConfigItem(path, config);
};

storage.apply(item);
}

@Override
public void update(String logger, Function<Config, Config> action) {
var current = storage.get(logger);
if (current != null) {
Config config = action.apply(current);
ConfigToLogger item = switch (logger) {
case String path when path.isEmpty() -> new ConfigToLogger.RootLoggerConfigItem(config);
case String path -> new ConfigToLogger.NamedLoggerConfigItem(path, config);
};

storage.apply(item);
}
}
}
}
9 changes: 7 additions & 2 deletions penna-api/src/main/java/penna/api/config/Storage.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package penna.api.config;

import org.jetbrains.annotations.NotNull;
import penna.api.config.internal.ManagerImpl;
import penna.api.models.Config;

/**
* A Config Storage defines a class that stores the configurations for all logs in the hierarchy.
* <br/>
* Such class is never expected to be called directly by its interface, but through the
* {@link Manager.ManagerImpl} that will receive it.
* {@link ManagerImpl} that will receive it.
*/
public interface Storage {

Expand All @@ -23,5 +27,6 @@ public interface Storage {
* @param logger The path/name of the logger
* @return The configuration for the logger
*/
Config get(String logger);
@NotNull
Config get(@NotNull String logger);
}
60 changes: 60 additions & 0 deletions penna-api/src/main/java/penna/api/config/internal/ManagerImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package penna.api.config.internal;

import penna.api.config.ConfigToLogger;
import penna.api.config.Manager;
import penna.api.config.Provider;
import penna.api.config.Storage;
import penna.api.models.Config;

import java.util.ServiceLoader;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* This is the central piece of the configuration management.
* It should not be created manually, but instead through {@link Manager#create(Storage)}.
*/
public final class ManagerImpl implements Manager {
/**
* The service loader for Providers
*/
public static final ServiceLoader<Provider> loader = ServiceLoader.load(Provider.class);

private final Storage storage;

/**
* Initializes the Manager with an instance of {@link Storage}
* @param storage The component that stores loggers and the respective configuration
*/
public ManagerImpl(Storage storage) {
this.storage = storage;
}

@Override
public void set(ConfigToLogger... configs) {
storage.apply(configs);
}

@Override
public void set(String logger, Supplier<Config> action) {
Config config = action.get();
ConfigToLogger item = switch (logger) {
case String path when path.isEmpty() -> new ConfigToLogger.RootLoggerConfigItem(config);
case String path -> new ConfigToLogger.NamedLoggerConfigItem(path, config);
};

storage.apply(item);
}

@Override
public void update(String logger, Function<Config, Config> action) {
var current = storage.get(logger);
Config config = action.apply(current);
ConfigToLogger item = switch (logger) {
case String path when path.isEmpty() -> new ConfigToLogger.RootLoggerConfigItem(config);
case String path -> new ConfigToLogger.NamedLoggerConfigItem(path, config);
};

storage.apply(item);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package penna.api.config;
package penna.api.models;

import org.jetbrains.annotations.NotNull;
import org.slf4j.event.Level;
import penna.api.models.LogField;

import java.util.Arrays;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package penna.api.config;
package penna.api.models;

/**
* This record holds configuration on how Penna should handle logging exception fields.
Expand Down
2 changes: 2 additions & 0 deletions penna-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ dependencies {
implementation libs.slf4j
compileOnly libs.jetbrains.annotations

testImplementation libs.junit.pioneer

testImplementation libs.junit.api
testRuntimeOnly libs.junit.engine
testImplementation libs.jackson.core
Expand Down
2 changes: 2 additions & 0 deletions penna-core/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@

// Exposes a service provider for SLF4j
provides org.slf4j.spi.SLF4JServiceProvider with PennaServiceProvider;

exports penna.core.api;
}
36 changes: 36 additions & 0 deletions penna-core/src/main/java/penna/core/api/LoggerController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package penna.core.api;

import org.slf4j.event.Level;
import penna.api.config.Manager;
import penna.api.config.Provider;
import penna.core.internals.ManagerHolder;

/**
* This is class provides the runtime with a façade for simple runtime control over the loggers.
* For a finer level of control, resort to implementing a custom {@link Provider} instead.
*/
public class LoggerController {

private LoggerController() {}

/**
* Convenience runtime function for changing the logger level.
* For a finer level of control, resort to implementing a custom {@link Provider}
* @param loggerName Name of the logger (or prefix of the logger) for the level change
* @param level The target level for supplied logger
*/
public static void changeLoggerLevel(String loggerName, Level level) {
Manager instance;
if ((instance = ManagerHolder.getInstance()) != null) {
instance.update(loggerName, config -> config.replaceLevel(level));
}
}

/**
* Convenience runtime function for changing the logger level.
* For a finer level of control, resort to implementing a custom {@link Provider}
* @param klass The class whose name is assigned to the logger
* @param level The target level for supplied logger
*/
public static void changeLoggerLevel(Class<?> klass, Level level) { changeLoggerLevel(klass.getName(), level);}
}
26 changes: 26 additions & 0 deletions penna-core/src/main/java/penna/core/internals/ManagerHolder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package penna.core.internals;

import org.jetbrains.annotations.Nullable;
import penna.api.config.Manager;

/**
* This class exists as a middle ground for both the concrete implementation and the {@link Manager}
* so the creation of the Manager instance, which happens at the concrete implementation during runtime, can
* still provide the Manager's interface with an instance for its static methods.
*/
public class ManagerHolder {
private ManagerHolder() {}
private static Manager instance;

/**
* After creating an instance, this method should be called to store it for "global" availability;
* @param manager The concrete {@link Manager} instance.
*/
public static void setManager(Manager manager) { instance = manager; }

/**
* Returns the stored Manager.
* @return the stored Manager.
*/
public static @Nullable Manager getInstance() { return instance; }
}
25 changes: 17 additions & 8 deletions penna-core/src/main/java/penna/core/logger/LoggerStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.VisibleForTesting;
import penna.api.config.Config;
import penna.api.models.Config;
import penna.core.internals.StringNavigator;

import java.util.concurrent.locks.Lock;
Expand Down Expand Up @@ -170,21 +170,30 @@ public void replaceConfig(@NotNull String prefix,
cursor.setConfigAndUpdateRecursively(newConfig);
}

public Config getConfig(@NotNull String prefix) {
/**
* Returns the configuration that is applied to the requested prefix, if not directly associated, the one
* applied to the nearest ancestor.
* @param prefix the path to the logger
* @return The configuration instance that is applied to it
*/
public @NotNull Config getConfig(@NotNull String prefix) {
StringNavigator path = new StringNavigator(prefix);
Node cursor = root;
int nodeIndex = 2; // Anything will be greater than ""
Config configRef = root.configRef;
int nodeIndex = 2; // given root is "", any child is located at index 2, so first hop is "free"
while (cursor != null && path.hasNext()) {
StringNavigator.StringView view = path.next();
do {
cursor = cursor.children[nodeIndex];
} while (cursor != null && (nodeIndex = view.indexCompare(cursor.component)) != 1);
}
if (cursor != null) {
return cursor.configRef;
}

return null;
configRef = switch (cursor) {
case null -> configRef;
case Node c when c.configRef == null -> configRef;
default -> cursor.configRef;
};
}
return configRef;
}

public void replaceConfig(@NotNull Config newConfig) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import org.slf4j.Marker;
import org.slf4j.event.LoggingEvent;
import org.slf4j.spi.LoggingEventBuilder;
import penna.api.config.Config;
import penna.api.models.Config;
import penna.core.logger.guard.LevelGuard;
import penna.core.models.LogConfig;

Expand Down
Loading

0 comments on commit 4fe7ceb

Please sign in to comment.