diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/Logger.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/Logger.java new file mode 100644 index 0000000000..4483feb988 --- /dev/null +++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/Logger.java @@ -0,0 +1,52 @@ +package gov.nasa.jpl.aerie.contrib.streamline.debugging; + +import gov.nasa.jpl.aerie.merlin.framework.Registrar; + +import java.util.Arrays; +import java.util.Map; + +import static java.util.stream.Collectors.toMap; + +public class Logger { + private static final int LEVEL_INDICATOR_SIZE = Arrays.stream(LogLevel.values()) + .map(v -> v.toString().length()) + .max(Integer::compareTo) + .orElseThrow(); + private static final String LOG_MESSAGE_FORMAT = "[%-" + LEVEL_INDICATOR_SIZE + "s] %s"; + + private final Map subLoggers; + + public Logger(Registrar registrar) { + subLoggers = Arrays.stream(LogLevel.values()).collect(toMap( + level -> level, + level -> new SimpleLogger(level.name(), registrar))); + } + + public void log(LogLevel level, String messageFormat, Object... args) { + String message = messageFormat.formatted(args); + subLoggers.get(level).log(LOG_MESSAGE_FORMAT.formatted(level, message)); + } + + public void debug(String messageFormat, Object... args) { + log(LogLevel.DEBUG, messageFormat, args); + } + + public void info(String messageFormat, Object... args) { + log(LogLevel.INFO, messageFormat, args); + } + + public void warning(String messageFormat, Object... args) { + log(LogLevel.WARNING, messageFormat, args); + } + + public void error(String messageFormat, Object... args) { + log(LogLevel.ERROR, messageFormat, args); + } + + public enum LogLevel { + DEBUG, + INFO, + WARNING, + ERROR + } +} diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/Logging.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/Logging.java new file mode 100644 index 0000000000..f9741e8047 --- /dev/null +++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/Logging.java @@ -0,0 +1,27 @@ +package gov.nasa.jpl.aerie.contrib.streamline.debugging; + +import gov.nasa.jpl.aerie.merlin.framework.Registrar; + +public final class Logging { + private Logging() {} + + /** + * The "main" logger. Unless you have a compelling reason to direct logging somewhere else, + * this logger should be used by virtually all model components. + * This logger will be initialized automatically when a registrar is constructed. + */ + public static Logger LOGGER; + + /** + * Initialize the primary logger. + * This is called when constructing a {@link gov.nasa.jpl.aerie.contrib.streamline.modeling.Registrar}, + * and does not need to be called directly by the model. + */ + public static void init(final Registrar registrar) { + if (LOGGER == null) { + LOGGER = new Logger(registrar); + } else { + LOGGER.warning("Attempting to re-initialize primary logger. This attempt is being ignored."); + } + } +} diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/SimpleLogger.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/SimpleLogger.java new file mode 100644 index 0000000000..1dc4e68a99 --- /dev/null +++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/debugging/SimpleLogger.java @@ -0,0 +1,51 @@ +package gov.nasa.jpl.aerie.contrib.streamline.debugging; + +import gov.nasa.jpl.aerie.merlin.framework.CellRef; +import gov.nasa.jpl.aerie.merlin.framework.Registrar; +import gov.nasa.jpl.aerie.merlin.protocol.model.CellType; +import gov.nasa.jpl.aerie.merlin.protocol.model.EffectTrait; +import gov.nasa.jpl.aerie.merlin.protocol.types.Unit; + +import static gov.nasa.jpl.aerie.contrib.serialization.rulesets.BasicValueMappers.string; +import static gov.nasa.jpl.aerie.merlin.protocol.types.Unit.UNIT; + +public class SimpleLogger { + private final CellRef cellRef = CellRef.allocate(UNIT, new CellType<>() { + @Override + public EffectTrait getEffectType() { + return new EffectTrait<>() { + @Override + public Unit empty() { + return UNIT; + } + + @Override + public Unit sequentially(Unit prefix, Unit suffix) { + return UNIT; + } + + @Override + public Unit concurrently(Unit left, Unit right) { + return UNIT; + } + }; + } + + @Override + public Unit duplicate(Unit unit) { + return unit; + } + + @Override + public void apply(Unit unit, Unit s) { + } + }, $ -> UNIT); + + public SimpleLogger(String name, Registrar registrar) { + registrar.topic(name, cellRef, string()); + } + + public void log(String message) { + cellRef.emit(message); + } +} diff --git a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/Registrar.java b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/Registrar.java index 124eb56d5c..a8ab5ecce3 100644 --- a/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/Registrar.java +++ b/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/streamline/modeling/Registrar.java @@ -2,41 +2,31 @@ import gov.nasa.jpl.aerie.contrib.serialization.mappers.IntegerValueMapper; import gov.nasa.jpl.aerie.contrib.serialization.mappers.NullableValueMapper; -import gov.nasa.jpl.aerie.contrib.serialization.mappers.StringValueMapper; import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource; import gov.nasa.jpl.aerie.contrib.streamline.core.Dynamics; import gov.nasa.jpl.aerie.contrib.streamline.core.Resource; import gov.nasa.jpl.aerie.contrib.streamline.core.Resources; import gov.nasa.jpl.aerie.contrib.streamline.core.monads.ThinResourceMonad; +import gov.nasa.jpl.aerie.contrib.streamline.debugging.Logging; import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete; import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.monads.DiscreteResourceMonad; import gov.nasa.jpl.aerie.contrib.streamline.modeling.linear.Linear; import gov.nasa.jpl.aerie.merlin.framework.ValueMapper; import gov.nasa.jpl.aerie.merlin.protocol.types.RealDynamics; import gov.nasa.jpl.aerie.merlin.protocol.types.Unit; -import org.apache.commons.lang3.exception.ExceptionUtils; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.resource; import static gov.nasa.jpl.aerie.contrib.streamline.core.Reactions.whenever; import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentData; import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentValue; +import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Logging.LOGGER; import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Naming.*; import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Profiling.profile; import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Tracing.trace; import static gov.nasa.jpl.aerie.contrib.streamline.modeling.Registrar.ErrorBehavior.*; -import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources.not; -import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources.when; -import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.monads.DiscreteDynamicsMonad.effect; -import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.monads.DiscreteResourceMonad.map; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteEffects.increment; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources.*; import static gov.nasa.jpl.aerie.contrib.streamline.modeling.linear.Linear.linear; import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.waitUntil; -import static java.util.stream.Collectors.joining; /** * Wrapper for {@link gov.nasa.jpl.aerie.merlin.framework.Registrar} specialized for {@link Resource}. @@ -49,12 +39,12 @@ public class Registrar { private final gov.nasa.jpl.aerie.merlin.framework.Registrar baseRegistrar; private boolean trace = false; private boolean profile = false; - private final MutableResource>>> errors; private final ErrorBehavior errorBehavior; + private final MutableResource> numberOfErrors = discreteResource(0); public enum ErrorBehavior { /** - * Log errors to the error state, + * Log errors to {@link Logging#LOGGER} * and replace resource value with null. */ Log, @@ -66,27 +56,11 @@ public enum ErrorBehavior { public Registrar(final gov.nasa.jpl.aerie.merlin.framework.Registrar baseRegistrar, final ErrorBehavior errorBehavior) { Resources.init(); + Logging.init(baseRegistrar); this.baseRegistrar = baseRegistrar; this.errorBehavior = errorBehavior; - errors = resource(Discrete.discrete(Map.of())); - var errorString = map(errors, errors$ -> errors$.entrySet().stream().map(entry -> formatError(entry.getKey(), entry.getValue())).collect(joining("\n\n"))); - - // Register the errors and number of errors resources for output - // TODO consider using serializable events, rather than resources, to log errors - discrete("errors", errorString, new StringValueMapper()); - discrete("numberOfErrors", map(errors, Map::size), new IntegerValueMapper()); - } - - private static String formatError(Throwable e, Collection affectedResources) { - return "Error affecting %s:%n%s".formatted( - String.join(", ", affectedResources), - formatException(e)); - } - private static String formatException(Throwable e) { - return ExceptionUtils.stream(e) - .map(ExceptionUtils::getMessage) - .collect(joining("\nCaused by: ")); + discrete("numberOfErrors", numberOfErrors, new IntegerValueMapper()); } public void setTrace() { @@ -148,21 +122,9 @@ private > void logErrors(String name, Resource resou }); } - // TODO: Consider using a MultiMap instead of doing this by hand below private Unit logError(String resourceName, Throwable e) { - errors.emit(effect(s -> { - var s$ = new HashMap<>(s); - s$.compute(e, (e$, affectedResources) -> { - if (affectedResources == null) { - return Set.of(resourceName); - } else { - var affectedResources$ = new HashSet<>(affectedResources); - affectedResources$.add(resourceName); - return affectedResources$; - } - }); - return s$; - })); + LOGGER.error("Error affecting %s: %s", resourceName, e); + increment(numberOfErrors); return Unit.UNIT; }