You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2017/01/03 15:00:09 UTC

[3/4] syncope git commit: [SYNCOPE-882] Initial commit: support in common, core and cli with new log4j MemoryAppend which retains in memory the last N statements

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/common/lib/src/main/java/org/apache/syncope/common/lib/log/package-info.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/log/package-info.java b/common/lib/src/main/java/org/apache/syncope/common/lib/log/package-info.java
new file mode 100644
index 0000000..1aa449d
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/log/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+@XmlSchema(
+        namespace = SyncopeConstants.NS,
+        xmlns = { @XmlNs(prefix = SyncopeConstants.NS_PREFIX,
+                    namespaceURI = SyncopeConstants.NS) })
+package org.apache.syncope.common.lib.log;
+
+import javax.xml.bind.annotation.XmlNs;
+import javax.xml.bind.annotation.XmlSchema;
+import org.apache.syncope.common.lib.SyncopeConstants;

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/common/lib/src/main/java/org/apache/syncope/common/lib/to/EventCategoryTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/EventCategoryTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/EventCategoryTO.java
deleted file mode 100644
index 544e204..0000000
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/EventCategoryTO.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.common.lib.to;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.ArrayList;
-import java.util.List;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlType;
-import org.apache.syncope.common.lib.AbstractBaseBean;
-import org.apache.syncope.common.lib.types.AuditElements;
-
-@XmlRootElement(name = "eventCategory")
-@XmlType
-public class EventCategoryTO extends AbstractBaseBean {
-
-    private static final long serialVersionUID = -4340060002701633401L;
-
-    private AuditElements.EventCategoryType type;
-
-    private String category;
-
-    private String subcategory;
-
-    private final List<String> events = new ArrayList<>();
-
-    /**
-     * Constructor for Type.REST event category.
-     */
-    public EventCategoryTO() {
-        this(AuditElements.EventCategoryType.LOGIC);
-    }
-
-    /**
-     * Constructor for the given Type event category.
-     *
-     * @param type event category type
-     */
-    public EventCategoryTO(final AuditElements.EventCategoryType type) {
-        super();
-        this.type = type;
-    }
-
-    public AuditElements.EventCategoryType getType() {
-        return type;
-    }
-
-    public void setType(final AuditElements.EventCategoryType type) {
-        this.type = type == null ? AuditElements.EventCategoryType.CUSTOM : type;
-    }
-
-    public String getCategory() {
-        return category;
-    }
-
-    public void setCategory(final String category) {
-        this.category = category;
-    }
-
-    public String getSubcategory() {
-        return subcategory;
-    }
-
-    public void setSubcategory(final String subcategory) {
-        this.subcategory = subcategory;
-    }
-
-    @XmlElementWrapper(name = "events")
-    @XmlElement(name = "event")
-    @JsonProperty("events")
-    public List<String> getEvents() {
-        return events;
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/common/lib/src/main/java/org/apache/syncope/common/lib/to/LoggerTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/LoggerTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/LoggerTO.java
deleted file mode 100644
index 0dfa8b5..0000000
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/LoggerTO.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.common.lib.to;
-
-import javax.ws.rs.PathParam;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlType;
-import org.apache.syncope.common.lib.AbstractBaseBean;
-import org.apache.syncope.common.lib.types.LoggerLevel;
-
-@XmlRootElement(name = "logger")
-@XmlType
-public class LoggerTO extends AbstractBaseBean {
-
-    private static final long serialVersionUID = -7794833835668648505L;
-
-    private String key;
-
-    private LoggerLevel level;
-
-    public LoggerLevel getLevel() {
-        return level;
-    }
-
-    public void setLevel(final LoggerLevel level) {
-        this.level = level;
-    }
-
-    public String getKey() {
-        return key;
-    }
-
-    @PathParam("key")
-    public void setKey(final String key) {
-        this.key = key;
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
index 25eaf9e..6988062 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
@@ -26,7 +26,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.AbstractBaseBean;
-import org.apache.syncope.common.lib.to.EventCategoryTO;
+import org.apache.syncope.common.lib.log.EventCategoryTO;
 import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType;
 import org.apache.syncope.common.lib.types.AuditElements.Result;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/LoggerWrapper.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/LoggerWrapper.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/LoggerWrapper.java
index a8c6f04..99f3f9c 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/LoggerWrapper.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/LoggerWrapper.java
@@ -21,7 +21,7 @@ package org.apache.syncope.common.rest.api;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import org.apache.syncope.common.lib.to.LoggerTO;
+import org.apache.syncope.common.lib.log.LoggerTO;
 import org.apache.syncope.common.lib.types.AuditLoggerName;
 import org.apache.syncope.common.lib.types.LoggerLevel;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/LoggerService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/LoggerService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/LoggerService.java
index fcdab01..7250b7a 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/LoggerService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/LoggerService.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.common.rest.api.service;
 
 import java.util.List;
+import java.util.Queue;
 import javax.validation.constraints.NotNull;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -28,8 +29,10 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
-import org.apache.syncope.common.lib.to.EventCategoryTO;
-import org.apache.syncope.common.lib.to.LoggerTO;
+import org.apache.syncope.common.lib.log.EventCategoryTO;
+import org.apache.syncope.common.lib.log.LogAppender;
+import org.apache.syncope.common.lib.log.LogStatementTO;
+import org.apache.syncope.common.lib.log.LoggerTO;
 import org.apache.syncope.common.lib.types.LoggerType;
 
 /**
@@ -39,7 +42,28 @@ import org.apache.syncope.common.lib.types.LoggerType;
 public interface LoggerService extends JAXRSService {
 
     /**
-     * Returns a list of all managed events in audit.
+     * Returns the list of memory appenders available in the current logging configuration.
+     *
+     * @return the list of memory appenders available in the current logging configuration
+     */
+    @GET
+    @Path("memoryAppenders")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    List<LogAppender> memoryAppenders();
+
+    /**
+     * Return the last log statements available in the provided memory appender.
+     *
+     * @param memoryAppender memory appender name
+     * @return the last log statements available in the provided memory appender
+     */
+    @GET
+    @Path("memoryAppenders/{memoryAppender}/lastLogStatements")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    Queue<LogStatementTO> getLastLogStatements(@NotNull @PathParam("memoryAppender") String memoryAppender);
+
+    /**
+     * Returns the list of all managed events in audit.
      *
      * @return list of all managed events in audit
      */

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
index 070395e..0e3c887 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
@@ -23,6 +23,7 @@ import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Queue;
 import java.util.Set;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.IteratorUtils;
@@ -34,8 +35,10 @@ import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.to.EventCategoryTO;
-import org.apache.syncope.common.lib.to.LoggerTO;
+import org.apache.syncope.common.lib.log.EventCategoryTO;
+import org.apache.syncope.common.lib.log.LogAppender;
+import org.apache.syncope.common.lib.log.LogStatementTO;
+import org.apache.syncope.common.lib.log.LoggerTO;
 import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType;
 import org.apache.syncope.common.lib.types.AuditLoggerName;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
@@ -48,6 +51,7 @@ import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.core.logic.init.LoggerLoader;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.LoggerDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
@@ -76,6 +80,9 @@ import org.springframework.util.SystemPropertyUtils;
 public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> {
 
     @Autowired
+    private LoggerLoader loggerLoader;
+
+    @Autowired
     private LoggerDAO loggerDAO;
 
     @Autowired
@@ -101,6 +108,33 @@ public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> {
 
     @PreAuthorize("hasRole('" + StandardEntitlement.LOG_LIST + "') and authentication.details.domain == "
             + "T(org.apache.syncope.common.lib.SyncopeConstants).MASTER_DOMAIN")
+    public List<LogAppender> memoryAppenders() {
+        return CollectionUtils.collect(
+                loggerLoader.getMemoryAppenders().keySet(),
+                new Transformer<String, LogAppender>() {
+
+            @Override
+            public LogAppender transform(final String input) {
+                LogAppender logAppender = new LogAppender();
+                logAppender.setName(input);
+                return logAppender;
+            }
+        }, new ArrayList<LogAppender>());
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.LOG_READ + "') and authentication.details.domain == "
+            + "T(org.apache.syncope.common.lib.SyncopeConstants).MASTER_DOMAIN")
+    public Queue<LogStatementTO> getLastLogStatements(final String memoryAppender) {
+        MemoryAppender appender = loggerLoader.getMemoryAppenders().get(memoryAppender);
+        if (appender == null) {
+            throw new NotFoundException("Appender " + memoryAppender);
+        }
+
+        return appender.getStatements();
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.LOG_LIST + "') and authentication.details.domain == "
+            + "T(org.apache.syncope.common.lib.SyncopeConstants).MASTER_DOMAIN")
     @Transactional(readOnly = true)
     public List<LoggerTO> listLogs() {
         return list(LoggerType.LOG);

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/core/logic/src/main/java/org/apache/syncope/core/logic/MemoryAppender.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/MemoryAppender.java b/core/logic/src/main/java/org/apache/syncope/core/logic/MemoryAppender.java
new file mode 100644
index 0000000..8183979
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/MemoryAppender.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import java.util.Queue;
+import org.apache.commons.collections4.queue.CircularFifoQueue;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.ReusableMessage;
+import org.apache.syncope.common.lib.log.LogStatementTO;
+import org.apache.syncope.common.lib.types.LoggerLevel;
+import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
+
+@Plugin(name = "Memory", category = "Core", elementType = Appender.ELEMENT_TYPE, printObject = true)
+public class MemoryAppender extends AbstractAppender {
+
+    private final CircularFifoQueue<LogStatementTO> statements;
+
+    protected MemoryAppender(
+            final String name,
+            final int size,
+            final Filter filter,
+            final boolean ignoreExceptions) {
+
+        super(name, filter, null, ignoreExceptions);
+        this.statements = new CircularFifoQueue<>(size);
+    }
+
+    @Override
+    public void append(final LogEvent event) {
+        LogStatementTO statement = new LogStatementTO();
+
+        statement.setLevel(LoggerLevel.fromLevel(event.getLevel()));
+        statement.setLoggerName(event.getLoggerName());
+
+        Message msg = event.getMessage();
+        statement.setMessage((msg instanceof ReusableMessage
+                ? ((ReusableMessage) msg).memento()
+                : msg).getFormattedMessage());
+
+        statement.setTimeMillis(event.getTimeMillis());
+
+        if (event.getThrown() != null) {
+            statement.setStackTrace(ExceptionUtils2.getFullStackTrace(event.getThrown()));
+        }
+
+        statement.setThreadId(event.getThreadId());
+        statement.setThreadName(event.getThreadName());
+        statement.setThreadPriority(event.getThreadPriority());
+
+        this.statements.add(statement);
+    }
+
+    public Queue<LogStatementTO> getStatements() {
+        return statements;
+    }
+
+    @PluginFactory
+    public static MemoryAppender createAppender(
+            @PluginAttribute("name") final String name,
+            @PluginAttribute(value = "size", defaultInt = 10) final int size,
+            @PluginElement("Filter") final Filter filter,
+            @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final boolean ignoreExceptions) {
+
+        return new MemoryAppender(
+                name,
+                size,
+                filter,
+                ignoreExceptions);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
index 9f96ccc..3280fe0 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.logic.init;
 
 import java.sql.Connection;
 import java.sql.SQLException;
+import java.util.HashMap;
 import java.util.Map;
 import javax.sql.DataSource;
 import org.apache.logging.log4j.Level;
@@ -30,6 +31,7 @@ import org.apache.logging.log4j.core.appender.db.jdbc.ColumnConfig;
 import org.apache.logging.log4j.core.appender.db.jdbc.ConnectionSource;
 import org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender;
 import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.syncope.core.logic.MemoryAppender;
 import org.apache.syncope.core.provisioning.java.AuditManagerImpl;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.persistence.api.DomainsHolder;
@@ -47,6 +49,8 @@ public class LoggerLoader implements SyncopeLoader {
     @Autowired
     private LoggerAccessor loggerAccessor;
 
+    private final Map<String, MemoryAppender> memoryAppenders = new HashMap<>();
+
     @Override
     public Integer getPriority() {
         return 300;
@@ -56,6 +60,12 @@ public class LoggerLoader implements SyncopeLoader {
     public void load() {
         final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
 
+        for (Map.Entry<String, Appender> entry : ctx.getConfiguration().getAppenders().entrySet()) {
+            if (entry.getValue() instanceof MemoryAppender) {
+                memoryAppenders.put(entry.getKey(), (MemoryAppender) entry.getValue());
+            }
+        }
+
         // Audit table and DataSource for each configured domain
         ColumnConfig[] columns = {
             ColumnConfig.createColumnConfig(ctx.getConfiguration(), "EVENT_DATE", null, null, "true", null, null),
@@ -97,6 +107,10 @@ public class LoggerLoader implements SyncopeLoader {
         ctx.updateLoggers();
     }
 
+    public Map<String, MemoryAppender> getMemoryAppenders() {
+        return memoryAppenders;
+    }
+
     private static class DataSourceConnectionSource implements ConnectionSource {
 
         private final DataSource dataSource;

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/LoggerServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/LoggerServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/LoggerServiceImpl.java
index b8a9fa7..bc406ed 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/LoggerServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/LoggerServiceImpl.java
@@ -20,9 +20,12 @@ package org.apache.syncope.core.rest.cxf.service;
 
 import java.text.ParseException;
 import java.util.List;
+import java.util.Queue;
 import javax.ws.rs.BadRequestException;
-import org.apache.syncope.common.lib.to.EventCategoryTO;
-import org.apache.syncope.common.lib.to.LoggerTO;
+import org.apache.syncope.common.lib.log.EventCategoryTO;
+import org.apache.syncope.common.lib.log.LogAppender;
+import org.apache.syncope.common.lib.log.LogStatementTO;
+import org.apache.syncope.common.lib.log.LoggerTO;
 import org.apache.syncope.common.lib.types.AuditLoggerName;
 import org.apache.syncope.common.lib.types.LoggerType;
 import org.apache.syncope.common.rest.api.LoggerWrapper;
@@ -38,6 +41,16 @@ public class LoggerServiceImpl extends AbstractServiceImpl implements LoggerServ
     private LoggerLogic logic;
 
     @Override
+    public List<LogAppender> memoryAppenders() {
+        return logic.memoryAppenders();
+    }
+
+    @Override
+    public Queue<LogStatementTO> getLastLogStatements(final String memoryAppender) {
+        return logic.getLastLogStatements(memoryAppender);
+    }
+
+    @Override
     public void delete(final LoggerType type, final String name) {
         switch (type) {
             case LOG:

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/fit/core-reference/src/main/resources/log4j2.xml
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/resources/log4j2.xml b/fit/core-reference/src/main/resources/log4j2.xml
index 02bb591..7416d92 100644
--- a/fit/core-reference/src/main/resources/log4j2.xml
+++ b/fit/core-reference/src/main/resources/log4j2.xml
@@ -32,6 +32,7 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
+    <Memory name="mainMemory" size="25"/>
 
     <RollingRandomAccessFile name="persistence" fileName="${log.directory}/core-persistence.log"
                              filePattern="${log.directory}/core-persistence-%d{yyyy-MM-dd}.log.gz"
@@ -44,6 +45,7 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
+    <Memory name="persistenceMemory" size="25"/>
 
     <RollingRandomAccessFile name="rest" fileName="${log.directory}/core-rest.log"
                              filePattern="${log.directory}/core-rest-%d{yyyy-MM-dd}.log.gz"
@@ -56,6 +58,7 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
+    <Memory name="restMemory" size="25"/>
 
     <RollingRandomAccessFile name="connid" fileName="${log.directory}/core-connid.log"
                              filePattern="${log.directory}/core-connid-%d{yyyy-MM-dd}.log.gz"
@@ -68,6 +71,7 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
+    <Memory name="connidMemory" size="25"/>
     
   </appenders>
   
@@ -75,61 +79,79 @@ under the License.
     
     <asyncLogger name="org.apache.syncope.core.persistence" additivity="false" level="INFO">
       <appender-ref ref="persistence"/>
+      <appender-ref ref="persistenceMemory"/>
     </asyncLogger>
     <asyncLogger name="org.springframework.orm" additivity="false" level="INFO">
       <appender-ref ref="persistence"/>
+      <appender-ref ref="persistenceMemory"/>
     </asyncLogger>
     
     <asyncLogger name="org.apache.syncope.core.rest" additivity="false" level="INFO">
       <appender-ref ref="rest"/>
+      <appender-ref ref="restMemory"/>
     </asyncLogger>
     <asyncLogger name="org.springframework.web" additivity="false" level="INFO">
       <appender-ref ref="rest"/>
+      <appender-ref ref="restMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.http" additivity="false" level="INFO">
       <appender-ref ref="rest"/>
+      <appender-ref ref="restMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.cxf" additivity="false" level="ERROR">
       <appender-ref ref="rest"/>
+      <appender-ref ref="restMemory"/>
     </asyncLogger>
     
     <asyncLogger name="org.identityconnectors" additivity="false" level="DEBUG">
       <appender-ref ref="connid"/>
+      <appender-ref ref="connidMemory"/>
     </asyncLogger>
     <asyncLogger name="net.tirasa.connid" additivity="false" level="DEBUG">
       <appender-ref ref="connid"/>
+      <appender-ref ref="connidMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.syncope.core.provisioning.api.ConnIdBundleManager" additivity="false" level="INFO">
       <appender-ref ref="connid"/>
+      <appender-ref ref="connidMemory"/>
     </asyncLogger>
     
     <asyncLogger name="org.apache.syncope" additivity="false" level="INFO">
       <appender-ref ref="main"/>
+      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.syncope.core.provisioning" additivity="false" level="INFO">
       <appender-ref ref="main"/>
+      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.syncope.core.logic" additivity="false" level="INFO">
       <appender-ref ref="main"/>
+      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.springframework" additivity="false" level="INFO">
       <appender-ref ref="main"/>
+      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.quartz" additivity="false" level="INFO">
       <appender-ref ref="main"/>
+      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.activiti" additivity="false" level="ERROR">
       <appender-ref ref="main"/>
+      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.camel" additivity="false" level="ERROR">
       <appender-ref ref="main"/>
+      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="io.swagger" additivity="false" level="ERROR">
       <appender-ref ref="main"/>
+      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     
     <root level="INFO">
       <appender-ref ref="main"/>
+      <appender-ref ref="mainMemory"/>
     </root>
     
   </loggers>

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/fit/core-reference/src/test/java/org/apache/syncope/fit/cli/CLIITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/cli/CLIITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/cli/CLIITCase.java
index 080e8f2..366770d 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/cli/CLIITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/cli/CLIITCase.java
@@ -38,6 +38,7 @@ import org.apache.syncope.client.cli.commands.connector.ConnectorCommand;
 import org.apache.syncope.client.cli.commands.entitlement.EntitlementCommand;
 import org.apache.syncope.client.cli.commands.group.GroupCommand;
 import org.apache.syncope.client.cli.commands.install.InstallCommand;
+import org.apache.syncope.client.cli.commands.logger.LoggerCommand;
 import org.apache.syncope.client.cli.commands.policy.PolicyCommand;
 import org.apache.syncope.client.cli.commands.report.ReportCommand;
 import org.apache.syncope.client.cli.commands.role.RoleCommand;
@@ -307,4 +308,24 @@ public class CLIITCase extends AbstractITCase {
             }
         }
     }
+
+    @Test
+    public void lastStatements() {
+        Process process = null;
+        try {
+            PROCESS_BUILDER.command(getCommand(
+                    new LoggerCommand().getClass().getAnnotation(Command.class).name(),
+                    LoggerCommand.LoggerOptions.LAST_STATEMENTS.getOptionName(),
+                    "connidMemory"));
+            process = PROCESS_BUILDER.start();
+            final String result = IOUtils.toString(process.getInputStream(), SyncopeConstants.DEFAULT_CHARSET);
+            assertTrue(result.contains("\"level\" : \"DEBUG\","));
+        } catch (IOException e) {
+            fail(e.getMessage());
+        } finally {
+            if (process != null) {
+                process.destroy();
+            }
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java
index d192d57..fb3b071 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java
@@ -22,7 +22,7 @@ import static org.junit.Assert.assertNotNull;
 
 import java.lang.reflect.InvocationTargetException;
 import org.apache.syncope.client.console.pages.Logs;
-import org.apache.syncope.common.lib.to.LoggerTO;
+import org.apache.syncope.common.lib.log.LoggerTO;
 import org.apache.wicket.Component;
 import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
 import org.apache.wicket.core.util.lang.PropertyResolver;

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LoggerITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LoggerITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LoggerITCase.java
index 373f28a..07416c6 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LoggerITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LoggerITCase.java
@@ -26,13 +26,16 @@ import static org.junit.Assert.fail;
 
 import java.text.ParseException;
 import java.util.List;
+import java.util.Queue;
 import javax.ws.rs.core.Response;
 import javax.xml.ws.WebServiceException;
 import org.apache.commons.collections4.IterableUtils;
 import org.apache.commons.collections4.Predicate;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.to.EventCategoryTO;
-import org.apache.syncope.common.lib.to.LoggerTO;
+import org.apache.syncope.common.lib.log.EventCategoryTO;
+import org.apache.syncope.common.lib.log.LogAppender;
+import org.apache.syncope.common.lib.log.LogStatementTO;
+import org.apache.syncope.common.lib.log.LoggerTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType;
@@ -51,6 +54,31 @@ import org.junit.Test;
 public class LoggerITCase extends AbstractITCase {
 
     @Test
+    public void listMemoryAppenders() {
+        List<LogAppender> memoryAppenders = loggerService.memoryAppenders();
+        assertNotNull(memoryAppenders);
+        assertFalse(memoryAppenders.isEmpty());
+        for (LogAppender appender : memoryAppenders) {
+            assertNotNull(appender);
+            assertNotNull(appender.getName());
+        }
+    }
+
+    @Test
+    public void lastStatements() {
+        Queue<LogStatementTO> statements = loggerService.getLastLogStatements("connidMemory");
+        assertNotNull(statements);
+        assertFalse(statements.isEmpty());
+
+        LogStatementTO statement = statements.element();
+        assertNotNull(statement);
+        assertNotNull(statement.getLoggerName());
+        assertNotNull(statement.getLevel());
+        assertNotNull(statement.getMessage());
+        assertNotNull(statement.getTimeMillis());
+    }
+
+    @Test
     public void listLogs() {
         List<LoggerTO> loggers = loggerService.list(LoggerType.LOG);
         assertNotNull(loggers);

http://git-wip-us.apache.org/repos/asf/syncope/blob/c25e1da0/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java
index 9f9f8be..4dc4663 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java
@@ -37,7 +37,7 @@ import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.report.AuditReportletConf;
 import org.apache.syncope.common.lib.report.UserReportletConf;
 import org.apache.syncope.common.lib.to.BulkActionResult;
-import org.apache.syncope.common.lib.to.LoggerTO;
+import org.apache.syncope.common.lib.log.LoggerTO;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.ReportTO;
 import org.apache.syncope.common.lib.types.AuditElements;