Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restructure penna-api #95

Merged
merged 8 commits into from
Mar 22, 2024
Merged
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
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