[Major] Refactored Operations Log

This commit is contained in:
Robert von Burg 2018-08-03 14:13:46 +02:00
parent 10ffa01dc9
commit e644b43e2c
10 changed files with 92 additions and 66 deletions

View File

@ -1,9 +1,10 @@
package li.strolch.handler.operationslog; package li.strolch.handler.operationslog;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchAgent;
import li.strolch.model.Locator; import li.strolch.model.Locator;
import li.strolch.model.Tags.Json; import li.strolch.model.Tags.Json;
@ -13,6 +14,7 @@ import li.strolch.utils.helper.ExceptionHelper;
public class LogMessage extends I18nMessage { public class LogMessage extends I18nMessage {
private final String id; private final String id;
private ZonedDateTime zonedDateTime;
private final String realm; private final String realm;
private final Locator locator; private final Locator locator;
private final LogSeverity severity; 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) { public LogMessage(String realm, Locator locator, LogSeverity logSeverity, ResourceBundle bundle, String key) {
super(bundle, key); super(bundle, key);
this.id = StrolchAgent.getUniqueId(); this.id = StrolchAgent.getUniqueId();
this.zonedDateTime = ZonedDateTime.now();
this.realm = realm; this.realm = realm;
this.locator = locator; this.locator = locator;
this.severity = logSeverity; this.severity = logSeverity;
@ -29,6 +32,10 @@ public class LogMessage extends I18nMessage {
return this.id; return this.id;
} }
public ZonedDateTime getZonedDateTime() {
return this.zonedDateTime;
}
public String getRealm() { public String getRealm() {
return this.realm; return this.realm;
} }
@ -57,9 +64,10 @@ public class LogMessage extends I18nMessage {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(Json.ID, this.id); 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.KEY, getKey());
jsonObject.addProperty(Json.MESSAGE, formatMessage()); 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.REALM, this.realm);
jsonObject.addProperty(Json.LOCATOR, this.locator.toString()); jsonObject.addProperty(Json.LOCATOR, this.locator.toString());
JsonObject values = new JsonObject(); JsonObject values = new JsonObject();

View File

@ -1,28 +1,9 @@
package li.strolch.handler.operationslog; package li.strolch.handler.operationslog;
public enum LogSeverity { public enum LogSeverity {
INFO("Info"), // Info,
NOTIFICATION("Notification"), // Notification,
WARN("Warn"), // Warning,
ERROR("Error"), // Error,
EXCEPTION("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);
}
} }

View File

@ -1,14 +1,6 @@
package li.strolch.handler.operationslog; package li.strolch.handler.operationslog;
import java.util.ArrayList; import java.util.*;
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 li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.ComponentContainer;
import li.strolch.agent.api.StrolchComponent; import li.strolch.agent.api.StrolchComponent;
@ -36,7 +28,7 @@ public class OperationsLog extends StrolchComponent {
super.initialize(configuration); super.initialize(configuration);
} }
public void addMessage(LogMessage logMessage) { public synchronized void addMessage(LogMessage logMessage) {
// store in global list // store in global list
LinkedHashMap<LogMessage, LogMessage> logMessages = this.logMessagesByRealmAndId LinkedHashMap<LogMessage, LogMessage> logMessages = this.logMessagesByRealmAndId
@ -46,25 +38,25 @@ public class OperationsLog extends StrolchComponent {
// store under locator // store under locator
LinkedHashMap<Locator, LinkedHashSet<LogMessage>> logMessagesLocator = this.logMessagesByLocator LinkedHashMap<Locator, LinkedHashSet<LogMessage>> logMessagesLocator = this.logMessagesByLocator
.computeIfAbsent(logMessage.getRealm(), this::newBoundedLocatorMap); .computeIfAbsent(logMessage.getRealm(), this::newBoundedLocatorMap);
LinkedHashSet<LogMessage> messages = logMessagesLocator.computeIfAbsent(logMessage.getLocator(), LinkedHashSet<LogMessage> messages = logMessagesLocator
(l) -> new LinkedHashSet<LogMessage>()); .computeIfAbsent(logMessage.getLocator(), (l) -> new LinkedHashSet<>());
messages.add(logMessage); messages.add(logMessage);
} }
public void clearMessages(String realm, Locator locator) { public synchronized void clearMessages(String realm, Locator locator) {
LinkedHashMap<Locator, LinkedHashSet<LogMessage>> logMessages = this.logMessagesByLocator.get(realm); LinkedHashMap<Locator, LinkedHashSet<LogMessage>> logMessages = this.logMessagesByLocator.get(realm);
if (logMessages != null) if (logMessages != null)
logMessages.remove(locator); logMessages.remove(locator);
} }
public Optional<Set<LogMessage>> getMessagesFor(String realm, Locator locator) { public synchronized Optional<Set<LogMessage>> getMessagesFor(String realm, Locator locator) {
LinkedHashMap<Locator, LinkedHashSet<LogMessage>> logMessages = this.logMessagesByLocator.get(realm); LinkedHashMap<Locator, LinkedHashSet<LogMessage>> logMessages = this.logMessagesByLocator.get(realm);
if (logMessages == null) if (logMessages == null)
return Optional.empty(); return Optional.empty();
return Optional.ofNullable(logMessages.get(locator)); return Optional.ofNullable(logMessages.get(locator));
} }
public List<LogMessage> getMessages(String realm) { public synchronized List<LogMessage> getMessages(String realm) {
LinkedHashMap<LogMessage, LogMessage> logMessages = this.logMessagesByRealmAndId.get(realm); LinkedHashMap<LogMessage, LogMessage> logMessages = this.logMessagesByRealmAndId.get(realm);
if (logMessages == null) if (logMessages == null)
return Collections.emptyList(); return Collections.emptyList();

View File

@ -1321,7 +1321,7 @@ public abstract class AbstractTransaction implements StrolchTransaction {
if (this.operationsLog != null) { if (this.operationsLog != null) {
this.operationsLog.addMessage( this.operationsLog.addMessage(
new LogMessage(this.realm.getRealm(), Locator.valueOf(AGENT, "tx", StrolchAgent.getUniqueId()), 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)); .value("reason", e));
} }

View File

@ -1,18 +1,21 @@
package li.strolch.rest.endpoint; 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.servlet.http.HttpServletRequest;
import javax.ws.rs.GET; import javax.ws.rs.*;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; 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.LogMessage;
import li.strolch.handler.operationslog.LogSeverity;
import li.strolch.handler.operationslog.OperationsLog; import li.strolch.handler.operationslog.OperationsLog;
import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.RestfulStrolchComponent;
import li.strolch.rest.helper.ResponseUtil; import li.strolch.rest.helper.ResponseUtil;
@ -24,15 +27,47 @@ public class OperationsLogResource {
@GET @GET
@Path("{realm}") @Path("{realm}")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Response getOperationLog(@Context HttpServletRequest request, @QueryParam("offset") int offset, public Response getOperationLog(@Context HttpServletRequest request, @PathParam("realm") String realm,
@QueryParam("limit") int limit, @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 // TODO do privilege check
OperationsLog operationsLog = RestfulStrolchComponent.getInstance().getComponent(OperationsLog.class); OperationsLog operationsLog = RestfulStrolchComponent.getInstance().getComponent(OperationsLog.class);
List<LogMessage> messages = operationsLog.getMessages(realm); Stream<LogMessage> messages = operationsLog.getMessages(realm).stream();
Paging<LogMessage> 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<LogMessage> paging = Paging.asPage(messages.collect(Collectors.toList()), offset, limit);
return ResponseUtil.toResponse(paging, LogMessage::toJson); return ResponseUtil.toResponse(paging, LogMessage::toJson);
} }
} }

View File

@ -163,7 +163,7 @@ public class EventBasedExecutionHandler extends ExecutionHandler {
logger.error("Failed to set " + locator + " to execution due to " + e.getMessage(), e); logger.error("Failed to set " + locator + " to execution due to " + e.getMessage(), e);
if (getContainer().hasComponent(OperationsLog.class)) { 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") ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.execution")
.value("reason", e)); .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); logger.error("Failed to set " + locator + " to executed due to " + e.getMessage(), e);
if (getContainer().hasComponent(OperationsLog.class)) { 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") ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.executed")
.value("reason", e)); .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); logger.error("Failed to set " + locator + " to stopped due to " + e.getMessage(), e);
if (getContainer().hasComponent(OperationsLog.class)) { 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") ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.stopped")
.value("reason", e)); .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); logger.error("Failed to set " + locator + " to error due to " + e.getMessage(), e);
if (getContainer().hasComponent(OperationsLog.class)) { 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") ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.error")
.value("reason", e)); .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); logger.error("Failed to set " + locator + " to warning due to " + e.getMessage(), e);
if (getContainer().hasComponent(OperationsLog.class)) { 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") ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.warning")
.value("reason", e)); .value("reason", e));
} }
@ -283,7 +283,7 @@ public class EventBasedExecutionHandler extends ExecutionHandler {
if (getContainer().hasComponent(OperationsLog.class)) { if (getContainer().hasComponent(OperationsLog.class)) {
getComponent(OperationsLog.class).addMessage( getComponent(OperationsLog.class).addMessage(
new LogMessage(realm, activityLoc, LogSeverity.EXCEPTION, new LogMessage(realm, activityLoc, LogSeverity.Exception,
ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.archive") ResourceBundle.getBundle("strolch-service"), "execution.handler.failed.archive")
.value("reason", e)); .value("reason", e));
} }

View File

@ -10,9 +10,9 @@ import li.strolch.model.activity.Action;
import li.strolch.persistence.api.StrolchTransaction; import li.strolch.persistence.api.StrolchTransaction;
/** /**
* The {@link ToErrorReservationExecution} executes same as {@link ReservationExection} with the difference that * The {@link ToErrorReservationExecution} executes same as {@link ReservationExection} with the difference that {@link
* {@link #isExecutable(Action)} always returns true, and if the action's resource is currently reserved, the execution * #isExecutable(Action)} always returns true, and if the action's resource is currently reserved, the execution fails
* fails and the state is set to ERROR * and the state is set to ERROR
* *
* @author Robert von Burg <eitch@eitchnet.ch> * @author Robert von Burg <eitch@eitchnet.ch>
*/ */
@ -44,7 +44,7 @@ public class ToErrorReservationExecution extends ReservationExection {
if (action.getType().equals(TYPE_RESERVE) && isReserved(action)) { if (action.getType().equals(TYPE_RESERVE) && isReserved(action)) {
setActionState(action, State.EXECUTION); 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") ResourceBundle.getBundle("strolch-service"), "execution.policy.reservation.alreadyReserved")
.value("resourceLoc", getResource(action).getLocator().toString())); .value("resourceLoc", getResource(action).getLocator().toString()));
} else { } else {

View File

@ -145,7 +145,7 @@ public class Migrations {
List<Version> list = migrationsRan.getList(realm); List<Version> list = migrationsRan.getList(realm);
for (Version version : list) { for (Version version : list) {
LogMessage logMessage = new LogMessage(realm, locator.append(StrolchAgent.getUniqueId()), 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()); "execution.handler.migrations.version").value("version", version.toString());
operationsLog.addMessage(logMessage); operationsLog.addMessage(logMessage);
} }

View File

@ -202,7 +202,7 @@ public class MigrationsHandler extends StrolchComponent {
if (getContainer().hasComponent(OperationsLog.class)) { if (getContainer().hasComponent(OperationsLog.class)) {
getComponent(OperationsLog.class).addMessage( getComponent(OperationsLog.class).addMessage(
new LogMessage(Tags.AGENT, getLocator().append(StrolchAgent.getUniqueId()), 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)); "execution.handler.failed.executed").value("reason", e));
} }
} }

View File

@ -18,6 +18,7 @@ public class I18nMessage {
private ResourceBundle bundle; private ResourceBundle bundle;
private String key; private String key;
private Properties values; private Properties values;
private String message;
public I18nMessage(ResourceBundle bundle, String key) { public I18nMessage(ResourceBundle bundle, String key) {
DBC.INTERIM.assertNotNull("bundle must be set!", bundle); DBC.INTERIM.assertNotNull("bundle must be set!", bundle);
@ -35,6 +36,10 @@ public class I18nMessage {
return this.values; return this.values;
} }
public String getMessage() {
return formatMessage();
}
public I18nMessage value(String key, String value) { public I18nMessage value(String key, String value) {
DBC.INTERIM.assertNotEmpty("key must be set!", key); DBC.INTERIM.assertNotEmpty("key must be set!", key);
this.values.setProperty(key, value == null ? "-" : value); this.values.setProperty(key, value == null ? "-" : value);
@ -42,13 +47,18 @@ public class I18nMessage {
} }
public String formatMessage() { public String formatMessage() {
if (this.message != null)
return this.message;
try { try {
String string = this.bundle.getString(this.key); 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) { } catch (MissingResourceException e) {
logger.error("Key " + this.key + " is missing in bundle " + this.bundle.getBaseBundleName()); logger.error("Key " + this.key + " is missing in bundle " + this.bundle.getBaseBundleName());
return this.key; this.message = this.key;
} }
return this.message;
} }
@Override @Override