diff --git a/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/LogMessage.java b/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/LogMessage.java index a748c3ec4..4be50c8ba 100644 --- a/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/LogMessage.java +++ b/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/LogMessage.java @@ -1,9 +1,10 @@ package li.strolch.handler.operationslog; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ResourceBundle; import com.google.gson.JsonObject; - import li.strolch.agent.api.StrolchAgent; import li.strolch.model.Locator; import li.strolch.model.Tags.Json; @@ -13,6 +14,7 @@ import li.strolch.utils.helper.ExceptionHelper; public class LogMessage extends I18nMessage { private final String id; + private ZonedDateTime zonedDateTime; private final String realm; private final Locator locator; private final LogSeverity severity; @@ -20,6 +22,7 @@ public class LogMessage extends I18nMessage { public LogMessage(String realm, Locator locator, LogSeverity logSeverity, ResourceBundle bundle, String key) { super(bundle, key); this.id = StrolchAgent.getUniqueId(); + this.zonedDateTime = ZonedDateTime.now(); this.realm = realm; this.locator = locator; this.severity = logSeverity; @@ -29,6 +32,10 @@ public class LogMessage extends I18nMessage { return this.id; } + public ZonedDateTime getZonedDateTime() { + return this.zonedDateTime; + } + public String getRealm() { return this.realm; } @@ -57,9 +64,10 @@ public class LogMessage extends I18nMessage { JsonObject jsonObject = new JsonObject(); jsonObject.addProperty(Json.ID, this.id); + jsonObject.addProperty(Json.DATE, this.zonedDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); jsonObject.addProperty(Json.KEY, getKey()); jsonObject.addProperty(Json.MESSAGE, formatMessage()); - jsonObject.addProperty(Json.SEVERITY, this.severity.getSeverity()); + jsonObject.addProperty(Json.SEVERITY, this.severity.name()); jsonObject.addProperty(Json.REALM, this.realm); jsonObject.addProperty(Json.LOCATOR, this.locator.toString()); JsonObject values = new JsonObject(); diff --git a/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/LogSeverity.java b/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/LogSeverity.java index 76f17f182..eca3c7d31 100644 --- a/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/LogSeverity.java +++ b/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/LogSeverity.java @@ -1,28 +1,9 @@ package li.strolch.handler.operationslog; public enum LogSeverity { - INFO("Info"), // - NOTIFICATION("Notification"), // - WARN("Warn"), // - ERROR("Error"), // - EXCEPTION("Exception"); - - private String severity; - - private LogSeverity(String severity) { - this.severity = severity; - } - - public String getSeverity() { - return this.severity; - } - - public static LogSeverity from(String value) { - for (LogSeverity type : values()) { - if (type.severity.equals(value)) - return type; - } - - throw new IllegalArgumentException("No severity exists for " + value); - } + Info, + Notification, + Warning, + Error, + Exception; } diff --git a/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/OperationsLog.java b/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/OperationsLog.java index 8f5e7328c..9617298dd 100644 --- a/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/OperationsLog.java +++ b/li.strolch.agent/src/main/java/li/strolch/handler/operationslog/OperationsLog.java @@ -1,14 +1,6 @@ package li.strolch.handler.operationslog; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; @@ -36,7 +28,7 @@ public class OperationsLog extends StrolchComponent { super.initialize(configuration); } - public void addMessage(LogMessage logMessage) { + public synchronized void addMessage(LogMessage logMessage) { // store in global list LinkedHashMap logMessages = this.logMessagesByRealmAndId @@ -46,25 +38,25 @@ public class OperationsLog extends StrolchComponent { // store under locator LinkedHashMap> logMessagesLocator = this.logMessagesByLocator .computeIfAbsent(logMessage.getRealm(), this::newBoundedLocatorMap); - LinkedHashSet messages = logMessagesLocator.computeIfAbsent(logMessage.getLocator(), - (l) -> new LinkedHashSet()); + LinkedHashSet messages = logMessagesLocator + .computeIfAbsent(logMessage.getLocator(), (l) -> new LinkedHashSet<>()); messages.add(logMessage); } - public void clearMessages(String realm, Locator locator) { + public synchronized void clearMessages(String realm, Locator locator) { LinkedHashMap> logMessages = this.logMessagesByLocator.get(realm); if (logMessages != null) logMessages.remove(locator); } - public Optional> getMessagesFor(String realm, Locator locator) { + public synchronized Optional> getMessagesFor(String realm, Locator locator) { LinkedHashMap> logMessages = this.logMessagesByLocator.get(realm); if (logMessages == null) return Optional.empty(); return Optional.ofNullable(logMessages.get(locator)); } - public List getMessages(String realm) { + public synchronized List getMessages(String realm) { LinkedHashMap logMessages = this.logMessagesByRealmAndId.get(realm); if (logMessages == null) return Collections.emptyList(); diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java index b850eef13..7e74d84c3 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java @@ -1321,7 +1321,7 @@ public abstract class AbstractTransaction implements StrolchTransaction { if (this.operationsLog != null) { this.operationsLog.addMessage( new LogMessage(this.realm.getRealm(), Locator.valueOf(AGENT, "tx", StrolchAgent.getUniqueId()), - LogSeverity.EXCEPTION, ResourceBundle.getBundle("strolch-agent"), "agent.tx.failed") + LogSeverity.Exception, ResourceBundle.getBundle("strolch-agent"), "agent.tx.failed") .value("reason", e)); } diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/OperationsLogResource.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/OperationsLogResource.java index 8627f3885..d25c5afc4 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/OperationsLogResource.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/OperationsLogResource.java @@ -1,18 +1,21 @@ package li.strolch.rest.endpoint; -import java.util.List; +import static java.util.Comparator.comparing; +import static li.strolch.utils.helper.StringHelper.isNotEmpty; import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.stream.Collectors; +import java.util.stream.Stream; import li.strolch.handler.operationslog.LogMessage; +import li.strolch.handler.operationslog.LogSeverity; import li.strolch.handler.operationslog.OperationsLog; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.helper.ResponseUtil; @@ -24,15 +27,47 @@ public class OperationsLogResource { @GET @Path("{realm}") @Produces(MediaType.APPLICATION_JSON) - public Response getOperationLog(@Context HttpServletRequest request, @QueryParam("offset") int offset, - @QueryParam("limit") int limit, @PathParam("realm") String realm) { + public Response getOperationLog(@Context HttpServletRequest request, @PathParam("realm") String realm, + @QueryParam("offset") int offset, @QueryParam("limit") int limit, @QueryParam("severity") String severityS, + @QueryParam("from") String fromS, @QueryParam("to") String toS, @QueryParam("query") String query) { // TODO do privilege check OperationsLog operationsLog = RestfulStrolchComponent.getInstance().getComponent(OperationsLog.class); - List messages = operationsLog.getMessages(realm); + Stream messages = operationsLog.getMessages(realm).stream(); - Paging paging = Paging.asPage(messages, offset, limit); + if (isNotEmpty(severityS)) { + LogSeverity severity = LogSeverity.valueOf(severityS); + messages = messages.filter(logMessage -> logMessage.getSeverity() == severity); + } + + if (isNotEmpty(query)) { + messages = messages.filter(logMessage -> logMessage.getMessage().toLowerCase().contains(query) // + || logMessage.getLocator().getPathElements().contains(query)); + } + + if (isNotEmpty(fromS) && isNotEmpty(toS)) { + + ZonedDateTime from = LocalDate.parse(fromS).atStartOfDay(ZoneId.systemDefault()); + ZonedDateTime to = LocalDate.parse(toS).plusDays(1).atStartOfDay(ZoneId.systemDefault()); + messages = messages.filter(logMessage -> from.isBefore(logMessage.getZonedDateTime()) && to + .isAfter(logMessage.getZonedDateTime())); + + } else if (isNotEmpty(fromS)) { + + ZonedDateTime from = LocalDate.parse(fromS).atStartOfDay(ZoneId.systemDefault()); + messages = messages.filter(logMessage -> from.isBefore(logMessage.getZonedDateTime())); + + } else if (isNotEmpty(toS)) { + + ZonedDateTime to = LocalDate.parse(toS).plusDays(1).atStartOfDay(ZoneId.systemDefault()); + messages = messages.filter(logMessage -> to.isAfter(logMessage.getZonedDateTime())); + + } + + messages = messages.sorted(comparing(LogMessage::getId).reversed()); + + Paging paging = Paging.asPage(messages.collect(Collectors.toList()), offset, limit); return ResponseUtil.toResponse(paging, LogMessage::toJson); } } diff --git a/li.strolch.service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java b/li.strolch.service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java index 4d6b5c210..0137dfe34 100644 --- a/li.strolch.service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java +++ b/li.strolch.service/src/main/java/li/strolch/execution/EventBasedExecutionHandler.java @@ -163,7 +163,7 @@ public class EventBasedExecutionHandler extends ExecutionHandler { logger.error("Failed to set " + locator + " to execution due to " + e.getMessage(), e); if (getContainer().hasComponent(OperationsLog.class)) { - getComponent(OperationsLog.class).addMessage(new LogMessage(realm, locator, LogSeverity.EXCEPTION, + getComponent(OperationsLog.class).addMessage(new LogMessage(realm, locator, LogSeverity.Exception, ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.execution") .value("reason", e)); } @@ -186,7 +186,7 @@ public class EventBasedExecutionHandler extends ExecutionHandler { logger.error("Failed to set " + locator + " to executed due to " + e.getMessage(), e); if (getContainer().hasComponent(OperationsLog.class)) { - getComponent(OperationsLog.class).addMessage(new LogMessage(realm, locator, LogSeverity.EXCEPTION, + getComponent(OperationsLog.class).addMessage(new LogMessage(realm, locator, LogSeverity.Exception, ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.executed") .value("reason", e)); } @@ -205,7 +205,7 @@ public class EventBasedExecutionHandler extends ExecutionHandler { logger.error("Failed to set " + locator + " to stopped due to " + e.getMessage(), e); if (getContainer().hasComponent(OperationsLog.class)) { - getComponent(OperationsLog.class).addMessage(new LogMessage(realm, locator, LogSeverity.EXCEPTION, + getComponent(OperationsLog.class).addMessage(new LogMessage(realm, locator, LogSeverity.Exception, ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.stopped") .value("reason", e)); } @@ -224,7 +224,7 @@ public class EventBasedExecutionHandler extends ExecutionHandler { logger.error("Failed to set " + locator + " to error due to " + e.getMessage(), e); if (getContainer().hasComponent(OperationsLog.class)) { - getComponent(OperationsLog.class).addMessage(new LogMessage(realm, locator, LogSeverity.EXCEPTION, + getComponent(OperationsLog.class).addMessage(new LogMessage(realm, locator, LogSeverity.Exception, ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.error") .value("reason", e)); } @@ -243,7 +243,7 @@ public class EventBasedExecutionHandler extends ExecutionHandler { logger.error("Failed to set " + locator + " to warning due to " + e.getMessage(), e); if (getContainer().hasComponent(OperationsLog.class)) { - getComponent(OperationsLog.class).addMessage(new LogMessage(realm, locator, LogSeverity.EXCEPTION, + getComponent(OperationsLog.class).addMessage(new LogMessage(realm, locator, LogSeverity.Exception, ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.warning") .value("reason", e)); } @@ -283,7 +283,7 @@ public class EventBasedExecutionHandler extends ExecutionHandler { if (getContainer().hasComponent(OperationsLog.class)) { getComponent(OperationsLog.class).addMessage( - new LogMessage(realm, activityLoc, LogSeverity.EXCEPTION, + new LogMessage(realm, activityLoc, LogSeverity.Exception, ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.archive") .value("reason", e)); } diff --git a/li.strolch.service/src/main/java/li/strolch/execution/policy/ToErrorReservationExecution.java b/li.strolch.service/src/main/java/li/strolch/execution/policy/ToErrorReservationExecution.java index b157ac91a..9ede92588 100644 --- a/li.strolch.service/src/main/java/li/strolch/execution/policy/ToErrorReservationExecution.java +++ b/li.strolch.service/src/main/java/li/strolch/execution/policy/ToErrorReservationExecution.java @@ -10,9 +10,9 @@ import li.strolch.model.activity.Action; import li.strolch.persistence.api.StrolchTransaction; /** - * The {@link ToErrorReservationExecution} executes same as {@link ReservationExection} with the difference that - * {@link #isExecutable(Action)} always returns true, and if the action's resource is currently reserved, the execution - * fails and the state is set to ERROR + * The {@link ToErrorReservationExecution} executes same as {@link ReservationExection} with the difference that {@link + * #isExecutable(Action)} always returns true, and if the action's resource is currently reserved, the execution fails + * and the state is set to ERROR * * @author Robert von Burg */ @@ -44,7 +44,7 @@ public class ToErrorReservationExecution extends ReservationExection { if (action.getType().equals(TYPE_RESERVE) && isReserved(action)) { setActionState(action, State.EXECUTION); - toError(new LogMessage(tx().getRealmName(), action.getLocator(), LogSeverity.ERROR, + toError(new LogMessage(tx().getRealmName(), action.getLocator(), LogSeverity.Error, ResourceBundle.getBundle("strolch-service"), "execution.policy.reservation.alreadyReserved") .value("resourceLoc", getResource(action).getLocator().toString())); } else { diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java b/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java index 0020bc9a1..9db2e5a09 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java @@ -145,7 +145,7 @@ public class Migrations { List list = migrationsRan.getList(realm); for (Version version : list) { LogMessage logMessage = new LogMessage(realm, locator.append(StrolchAgent.getUniqueId()), - LogSeverity.INFO, ResourceBundle.getBundle("strolch-service"), + LogSeverity.Info, ResourceBundle.getBundle("strolch-service"), "execution.handler.migrations.version").value("version", version.toString()); operationsLog.addMessage(logMessage); } diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java b/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java index b66c6d9ec..324b636fb 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java @@ -202,7 +202,7 @@ public class MigrationsHandler extends StrolchComponent { if (getContainer().hasComponent(OperationsLog.class)) { getComponent(OperationsLog.class).addMessage( new LogMessage(Tags.AGENT, getLocator().append(StrolchAgent.getUniqueId()), - LogSeverity.EXCEPTION, ResourceBundle.getBundle("strolch-service"), + LogSeverity.Exception, ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.executed").value("reason", e)); } } diff --git a/li.strolch.utils/src/main/java/li/strolch/utils/I18nMessage.java b/li.strolch.utils/src/main/java/li/strolch/utils/I18nMessage.java index 3f0cdf93a..ce8f2884f 100644 --- a/li.strolch.utils/src/main/java/li/strolch/utils/I18nMessage.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/I18nMessage.java @@ -18,6 +18,7 @@ public class I18nMessage { private ResourceBundle bundle; private String key; private Properties values; + private String message; public I18nMessage(ResourceBundle bundle, String key) { DBC.INTERIM.assertNotNull("bundle must be set!", bundle); @@ -35,6 +36,10 @@ public class I18nMessage { return this.values; } + public String getMessage() { + return formatMessage(); + } + public I18nMessage value(String key, String value) { DBC.INTERIM.assertNotEmpty("key must be set!", key); this.values.setProperty(key, value == null ? "-" : value); @@ -42,13 +47,18 @@ public class I18nMessage { } public String formatMessage() { + if (this.message != null) + return this.message; + try { String string = this.bundle.getString(this.key); - return StringHelper.replacePropertiesIn(this.values, EMPTY, string); + this.message = StringHelper.replacePropertiesIn(this.values, EMPTY, string); } catch (MissingResourceException e) { logger.error("Key " + this.key + " is missing in bundle " + this.bundle.getBaseBundleName()); - return this.key; + this.message = this.key; } + + return this.message; } @Override