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/05 16:17:44 UTC

[1/2] syncope git commit: [SYNCOPE-882] Log viewer for the admin console

Repository: syncope
Updated Branches:
  refs/heads/2_0_X dfaa982fb -> 3f44dda91
  refs/heads/master 3684437eb -> f4c717e7d


[SYNCOPE-882] Log viewer for the admin console


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/3f44dda9
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/3f44dda9
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/3f44dda9

Branch: refs/heads/2_0_X
Commit: 3f44dda917af7eebb73eee8560d574920bf322a2
Parents: dfaa982
Author: Francesco Chicchiricc� <il...@apache.org>
Authored: Wed Jan 4 09:07:24 2017 +0100
Committer: Francesco Chicchiricc� <il...@apache.org>
Committed: Thu Jan 5 17:16:59 2017 +0100

----------------------------------------------------------------------
 .../syncope/client/console/pages/BasePage.java  |   2 +-
 .../syncope/client/console/pages/LogViewer.java | 243 +++++++++++++++++++
 .../console/panels/AbstractLogsPanel.java       |  10 +-
 .../client/console/panels/CoreLogPanel.java     |   6 +
 .../client/console/panels/ExecMessageModal.java |  12 +-
 .../console/panels/LogStatementPanel.java       |  93 +++++++
 .../client/console/rest/LoggerRestClient.java   |  30 +++
 .../client/console/widgets/JobActionPanel.java  |   9 +-
 .../client/console/widgets/JobWidget.java       |  11 +-
 .../syncope/client/console/pages/LogViewer.html |  34 +++
 .../client/console/panels/CoreLogPanel.html     |   5 +
 .../client/console/panels/LabelPanel.html       |  11 +-
 .../console/panels/LogStatementPanel.html       |  33 +++
 .../syncope/common/lib/log/LogStatementTO.java  |   8 +-
 .../src/main/resources/log4j2.xml               |  52 ++--
 .../org/apache/syncope/fit/cli/CLIITCase.java   |   2 +-
 .../apache/syncope/fit/core/LoggerITCase.java   |   2 +-
 .../pom.xml                                     |  22 ++
 pom.xml                                         |   6 +
 .../workingwithapachesyncope/cli/logger.adoc    |  10 +-
 20 files changed, 545 insertions(+), 56 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index 58c7804..dee1ee1 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -259,7 +259,7 @@ public class BasePage extends WebPage implements IAjaxIndicatorAware {
             }
         });
         body.add(new Label("domain", SyncopeConsoleSession.get().getDomain()));
-        body.add(new BookmarkablePageLink<>("logout", Logout.class));
+        body.add(new BookmarkablePageLink<Page>("logout", Logout.class));
 
         // set 'active' menu item for everything but extensions
         // 1. check if current class is set to top-level menu

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java
new file mode 100644
index 0000000..c96ee1c
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java
@@ -0,0 +1,243 @@
+/*
+ * 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.client.console.pages;
+
+import static org.apache.wicket.Component.ENABLE;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.collections4.list.SetUniqueList;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.panels.LogStatementPanel;
+import org.apache.syncope.client.console.rest.LoggerRestClient;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.common.lib.log.LogStatementTO;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.wicket.Application;
+import org.apache.wicket.ThreadContext;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.util.ListModel;
+import org.apache.wicket.protocol.ws.WebSocketSettings;
+import org.apache.wicket.protocol.ws.api.WebSocketBehavior;
+import org.apache.wicket.protocol.ws.api.WebSocketPushBroadcaster;
+import org.apache.wicket.protocol.ws.api.event.WebSocketPushPayload;
+import org.apache.wicket.protocol.ws.api.message.ConnectedMessage;
+import org.apache.wicket.protocol.ws.api.message.IWebSocketPushMessage;
+import org.apache.wicket.protocol.ws.api.registry.IKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LogViewer extends WebPage {
+
+    private static final Logger LOG = LoggerFactory.getLogger(LogViewer.class);
+
+    private static final int MAX_STATEMENTS_PER_APPENDER = 50;
+
+    private static final long serialVersionUID = -7578329899052708105L;
+
+    private final LoggerRestClient restClient = new LoggerRestClient();
+
+    private final IModel<Long> lastTimeInMillis = Model.of(0L);
+
+    private final WebMarkupContainer stContainer;
+
+    private final IModel<List<LogStatementTO>> statementViewModel;
+
+    private final ListView<LogStatementTO> statementView;
+
+    public LogViewer() {
+        final WebMarkupContainer viewer = new WebMarkupContainer("viewer");
+        viewer.setOutputMarkupId(true);
+        add(viewer);
+
+        final AjaxDropDownChoicePanel<String> appenders = new AjaxDropDownChoicePanel<>(
+                "appenders", "Appender", new Model<String>(), false);
+        MetaDataRoleAuthorizationStrategy.authorize(appenders, ENABLE, StandardEntitlement.LOG_READ);
+        appenders.setChoices(restClient.listMemoryAppenders());
+        viewer.add(appenders);
+
+        stContainer = new WebMarkupContainer("stContainer");
+        stContainer.setOutputMarkupId(true);
+        viewer.add(stContainer);
+
+        statementViewModel = new ListModel<>(new ArrayList<LogStatementTO>());
+        statementView = new ListView<LogStatementTO>("statements", statementViewModel) {
+
+            private static final long serialVersionUID = -9180479401817023838L;
+
+            @Override
+            protected void populateItem(final ListItem<LogStatementTO> item) {
+                LogStatementPanel panel = new LogStatementPanel("statement", item.getModelObject());
+                panel.setOutputMarkupId(true);
+                item.add(panel);
+            }
+        };
+        statementView.setOutputMarkupId(true);
+        stContainer.add(statementView);
+
+        appenders.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                List<LogStatementTO> lastStatements = appenders.getModelObject() == null
+                        ? new ArrayList<LogStatementTO>()
+                        : restClient.getLastLogStatements(appenders.getModelObject(), 0);
+                statementViewModel.setObject(lastStatements);
+                target.add(stContainer);
+
+                lastTimeInMillis.setObject(0L);
+            }
+        });
+
+        add(new WebSocketBehavior() {
+
+            private static final long serialVersionUID = 3507933905864454312L;
+
+            @Override
+            protected void onConnect(final ConnectedMessage message) {
+                super.onConnect(message);
+
+                SyncopeConsoleSession.get().scheduleAtFixedRate(
+                        new LogStatementUpdater(message, restClient, appenders, lastTimeInMillis),
+                        0, 10, TimeUnit.SECONDS);
+            }
+
+        });
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof WebSocketPushPayload) {
+            WebSocketPushPayload wsEvent = (WebSocketPushPayload) event.getPayload();
+            if (wsEvent.getMessage() instanceof LogViewerMessage) {
+                List<LogStatementTO> recentLogStatements =
+                        ((LogViewerMessage) wsEvent.getMessage()).getRecentLogStatements();
+
+                if (!recentLogStatements.isEmpty()) {
+                    // save scroll position
+                    wsEvent.getHandler().prependJavaScript(
+                            String.format("window.scrollTop = $('#%s').scrollTop();", stContainer.getMarkupId()));
+
+                    int currentSize = statementView.getModelObject().size();
+                    int recentSize = recentLogStatements.size();
+
+                    List<LogStatementTO> newModelObject = SetUniqueList.<LogStatementTO>setUniqueList(
+                            new ArrayList<LogStatementTO>(MAX_STATEMENTS_PER_APPENDER));
+                    if (currentSize <= MAX_STATEMENTS_PER_APPENDER - recentSize) {
+                        newModelObject.addAll(statementView.getModelObject());
+                    } else {
+                        newModelObject.addAll(statementView.getModelObject().subList(recentSize, currentSize));
+                    }
+                    newModelObject.addAll(recentLogStatements);
+
+                    statementViewModel.setObject(newModelObject);
+                    wsEvent.getHandler().add(LogViewer.this.stContainer);
+
+                    // restore scroll position - might not work perfectly if items were removed from the top
+                    wsEvent.getHandler().appendJavaScript(
+                            String.format("$('#%s').scrollTop(window.scrollTop);", stContainer.getMarkupId()));
+                }
+            }
+        }
+    }
+
+    private static final class LogStatementUpdater implements Runnable {
+
+        private final Application application;
+
+        private final SyncopeConsoleSession session;
+
+        private final IKey key;
+
+        private final LoggerRestClient restClient;
+
+        private final AjaxDropDownChoicePanel<String> appenders;
+
+        private final IModel<Long> lastTimeInMillis;
+
+        LogStatementUpdater(
+                final ConnectedMessage message,
+                final LoggerRestClient restClient,
+                final AjaxDropDownChoicePanel<String> appenders,
+                final IModel<Long> lastTimeInMillis) {
+
+            this.application = message.getApplication();
+            this.session = SyncopeConsoleSession.get();
+            this.key = message.getKey();
+            this.restClient = restClient;
+            this.appenders = appenders;
+            this.lastTimeInMillis = lastTimeInMillis;
+        }
+
+        @Override
+        public void run() {
+            try {
+                ThreadContext.setApplication(application);
+                ThreadContext.setSession(session);
+
+                List<LogStatementTO> recentLogStatements = appenders.getModelObject() == null
+                        ? new ArrayList<LogStatementTO>()
+                        : restClient.getLastLogStatements(appenders.getModelObject(), lastTimeInMillis.getObject());
+                if (!recentLogStatements.isEmpty()) {
+                    lastTimeInMillis.setObject(recentLogStatements.get(recentLogStatements.size() - 1).getTimeMillis());
+                }
+
+                WebSocketSettings settings = WebSocketSettings.Holder.get(application);
+                WebSocketPushBroadcaster broadcaster = new WebSocketPushBroadcaster(settings.getConnectionRegistry());
+                broadcaster.broadcast(
+                        new ConnectedMessage(application, session.getId(), key),
+                        new LogViewerMessage(recentLogStatements));
+            } catch (Throwable t) {
+                LOG.error("Unexpected error while checking for recent log statements", t);
+            } finally {
+                ThreadContext.detach();
+            }
+        }
+    }
+
+    private static class LogViewerMessage implements IWebSocketPushMessage, Serializable {
+
+        private static final long serialVersionUID = 7241149017008105769L;
+
+        private final List<LogStatementTO> recentLogStatements;
+
+        LogViewerMessage(final List<LogStatementTO> recentLogStatements) {
+            this.recentLogStatements = recentLogStatements;
+        }
+
+        public List<LogStatementTO> getRecentLogStatements() {
+            return recentLogStatements;
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
index dac4ab5..fef5be5 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
@@ -48,6 +48,8 @@ public abstract class AbstractLogsPanel<T extends AbstractBaseBean> extends Pane
 
     private static final long serialVersionUID = -6313532280206208227L;
 
+    protected WebMarkupContainer loggerContainer;
+
     public AbstractLogsPanel(
             final String id,
             final PageReference pageRef,
@@ -55,9 +57,9 @@ public abstract class AbstractLogsPanel<T extends AbstractBaseBean> extends Pane
 
         super(id);
 
-        WebMarkupContainer container = new WebMarkupContainer("loggerContainer");
-        container.setOutputMarkupId(true);
-        add(container);
+        loggerContainer = new WebMarkupContainer("loggerContainer");
+        loggerContainer.setOutputMarkupId(true);
+        add(loggerContainer);
 
         ListViewPanel.Builder<LoggerTO> builder = new ListViewPanel.Builder<LoggerTO>(LoggerTO.class, pageRef) {
 
@@ -104,7 +106,7 @@ public abstract class AbstractLogsPanel<T extends AbstractBaseBean> extends Pane
                 withChecks(ListViewPanel.CheckAvailability.NONE).
                 setReuseItem(false);
 
-        container.add(builder.build("logger"));
+        loggerContainer.add(builder.build("logger"));
     }
 
     protected abstract void update(final LoggerTO loggerTO);

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/java/org/apache/syncope/client/console/panels/CoreLogPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/CoreLogPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/CoreLogPanel.java
index aeb672e..1fc45a8 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/CoreLogPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/CoreLogPanel.java
@@ -19,10 +19,13 @@
 package org.apache.syncope.client.console.panels;
 
 import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.pages.LogViewer;
 import org.apache.syncope.common.lib.log.LoggerTO;
 import org.apache.syncope.common.lib.types.LoggerType;
 import org.apache.syncope.common.rest.api.service.LoggerService;
 import org.apache.wicket.PageReference;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.PopupSettings;
 
 public class CoreLogPanel extends AbstractLogsPanel<LoggerTO> {
 
@@ -31,6 +34,9 @@ public class CoreLogPanel extends AbstractLogsPanel<LoggerTO> {
     public CoreLogPanel(final String id, final PageReference pageReference) {
         super(id, pageReference, SyncopeConsoleSession.get().getService(LoggerService.class).list(LoggerType.LOG));
 
+        BookmarkablePageLink<Void> viewer = new BookmarkablePageLink<>("viewer", LogViewer.class);
+        viewer.setPopupSettings(new PopupSettings().setHeight(600).setWidth(800));
+        loggerContainer.add(viewer);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/java/org/apache/syncope/client/console/panels/ExecMessageModal.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ExecMessageModal.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ExecMessageModal.java
index 23f411b..91e0b98 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ExecMessageModal.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ExecMessageModal.java
@@ -19,16 +19,20 @@
 package org.apache.syncope.client.console.panels;
 
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
-import org.apache.wicket.PageReference;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.panel.Panel;
 
 public class ExecMessageModal extends Panel implements ModalPanel {
 
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = -8751533037282034918L;
 
-    public ExecMessageModal(final PageReference pageRef, final String executionMessage) {
-        super(BaseModal.CONTENT_ID);
+    public ExecMessageModal(final String executionMessage) {
+        this(BaseModal.CONTENT_ID, executionMessage);
+    }
+
+    public ExecMessageModal(final String id, final String executionMessage) {
+        super(id);
         add(new Label("executionMessage", executionMessage).setOutputMarkupId(true));
     }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java
new file mode 100644
index 0000000..abae16b
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java
@@ -0,0 +1,93 @@
+/*
+ * 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.client.console.panels;
+
+import com.googlecode.wicket.jquery.core.Options;
+import com.googlecode.wicket.jquery.ui.JQueryUIBehavior;
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Alert;
+import de.agilecoders.wicket.core.util.Attributes;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.FastDateFormat;
+import org.apache.syncope.common.lib.log.LogStatementTO;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+
+public class LogStatementPanel extends Panel {
+
+    private static final long serialVersionUID = 1610867968070669922L;
+
+    private static final FastDateFormat FORMAT = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS");
+
+    private final String labelCssClass;
+
+    public LogStatementPanel(final String id, final LogStatementTO statement) {
+        super(id);
+
+        Alert.Type type;
+        switch (statement.getLevel()) {
+            case DEBUG:
+                type = Alert.Type.Success;
+                break;
+
+            case INFO:
+                type = Alert.Type.Info;
+                break;
+
+            case ERROR:
+                type = Alert.Type.Danger;
+                break;
+
+            case WARN:
+                type = Alert.Type.Warning;
+                break;
+
+            default:
+                type = Alert.Type.Info;
+        }
+        labelCssClass = "label-" + type.name().toLowerCase();
+
+        add(new Label("logger", Model.of(statement.getLoggerName())));
+        add(new Label("instant", Model.of(FORMAT.format(statement.getTimeMillis()))));
+        add(new Label("message", Model.of(statement.getMessage())));
+
+        WebMarkupContainer collapse = new WebMarkupContainer("collapse");
+        collapse.setOutputMarkupId(true);
+        collapse.setOutputMarkupPlaceholderTag(true);
+        collapse.setVisible(StringUtils.isNotBlank(statement.getStackTrace()));
+        collapse.add(new JQueryUIBehavior(
+                "#" + collapse.getMarkupId(), "accordion", new Options("active", false).set("collapsible", true)));
+        add(collapse);
+
+        Label stacktrace = new Label("stacktrace", Model.of(statement.getStackTrace()));
+        stacktrace.setOutputMarkupId(true);
+        collapse.add(stacktrace);
+    }
+
+    @Override
+    protected void onComponentTag(final ComponentTag tag) {
+        super.onComponentTag(tag);
+
+        checkComponentTag(tag, "div");
+        Attributes.addClass(tag, labelCssClass);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
index 4922940..bc31608 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
@@ -18,13 +18,21 @@
  */
 package org.apache.syncope.client.console.rest;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.collections4.TransformerUtils;
 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.LoggerLevel;
@@ -36,6 +44,28 @@ public class LoggerRestClient extends BaseRestClient {
 
     private static final long serialVersionUID = 4579786978763032240L;
 
+    public List<String> listMemoryAppenders() {
+        return CollectionUtils.collect(getService(LoggerService.class).memoryAppenders(),
+                new Transformer<LogAppender, String>() {
+
+            @Override
+            public String transform(final LogAppender input) {
+                return input.getName();
+            }
+        }, new ArrayList<String>());
+    }
+
+    public List<LogStatementTO> getLastLogStatements(final String appender, final long lastStatementTime) {
+        return CollectionUtils.collect(IterableUtils.filteredIterable(
+                getService(LoggerService.class).getLastLogStatements(appender), new Predicate<LogStatementTO>() {
+
+            @Override
+            public boolean evaluate(final LogStatementTO object) {
+                return object.getTimeMillis() > lastStatementTime;
+            }
+        }), TransformerUtils.<LogStatementTO>nopTransformer(), new ArrayList<LogStatementTO>());
+    }
+
     public List<LoggerTO> listLogs() {
         return getService(LoggerService.class).list(LoggerType.LOG);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
index ab245c7..30797ae 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
@@ -57,8 +57,13 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> {
 
     private final BaseModal<Serializable> jobModal;
 
-    public JobActionPanel(final String id, final JobTO jobTO, final JobWidget widget,
-            final BaseModal<Serializable> jobModal, final PageReference pageRef) {
+    public JobActionPanel(
+            final String id,
+            final JobTO jobTO,
+            final JobWidget widget,
+            final BaseModal<Serializable> jobModal,
+            final PageReference pageRef) {
+
         super(id, true);
         this.jobModal = jobModal;
         setOutputMarkupId(true);

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
index e02f729..bc9db0d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
@@ -280,6 +280,10 @@ public class JobWidget extends BaseWidget {
 
             columns.add(new PropertyColumn<JobTO, String>(new ResourceModel("refDesc"), "refDesc", "refDesc"));
 
+            columns.add(new BooleanPropertyColumn<JobTO>(new ResourceModel("scheduled"), "scheduled", "scheduled"));
+
+            columns.add(new DatePropertyColumn<JobTO>(new ResourceModel("start"), "start", "start"));
+
             columns.add(new AbstractColumn<JobTO, String>(new Model<>(""), "running") {
 
                 private static final long serialVersionUID = -4008579357070833846L;
@@ -309,10 +313,6 @@ public class JobWidget extends BaseWidget {
 
             });
 
-            columns.add(new BooleanPropertyColumn<JobTO>(new ResourceModel("scheduled"), "scheduled", "scheduled"));
-
-            columns.add(new DatePropertyColumn<JobTO>(new ResourceModel("start"), "start", "start"));
-
             return columns;
         }
 
@@ -412,8 +412,7 @@ public class JobWidget extends BaseWidget {
                                     StringResourceModel stringResourceModel =
                                             new StringResourceModel("execution.view", JobWidget.this, model);
                                     detailModal.header(stringResourceModel);
-                                    detailModal.
-                                            setContent(new ExecMessageModal(pageRef, model.getObject().getMessage()));
+                                    detailModal.setContent(new ExecMessageModal(model.getObject().getMessage()));
                                     detailModal.show(true);
                                     target.add(detailModal);
                                 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/resources/org/apache/syncope/client/console/pages/LogViewer.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/LogViewer.html b/client/console/src/main/resources/org/apache/syncope/client/console/pages/LogViewer.html
new file mode 100644
index 0000000..c68b2e4
--- /dev/null
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/LogViewer.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+  <head>
+    <title>Apache Syncope - Log Viewer</title>
+  </head>
+  <body>
+    <div wicket:id="viewer" style="padding: 5px;">
+      <div wicket:id="appenders" style="margin-bottom: 10px;"></div>
+      <div wicket:id="stContainer" style="overflow-y: auto; overflow-x: auto; max-width: 100vw; max-height: 85vh;">
+        <div wicket:id="statements">
+          <div wicket:id="statement"/>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/resources/org/apache/syncope/client/console/panels/CoreLogPanel.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/CoreLogPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/panels/CoreLogPanel.html
index a4cd024..ccf729f 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/CoreLogPanel.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/CoreLogPanel.html
@@ -20,6 +20,11 @@ under the License.
   <wicket:panel>
     <span wicket:id="loggerContainer">
       <div class="logs">
+        <div class="pull-right">
+          <button class="btn btn-primary" wicket:id="viewer">
+            <span class="glyphicon glyphicon-list-alt"></span> Log Viewer
+          </button>
+        </div>
         <span wicket:id="logger">[logger]</span>
       </div>
     </span>

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/resources/org/apache/syncope/client/console/panels/LabelPanel.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/LabelPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/panels/LabelPanel.html
index 745e35a..c2ec0a9 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/LabelPanel.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/LabelPanel.html
@@ -17,12 +17,7 @@ specific language governing permissions and limitations
 under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-  <head>
-    <title>Label  panel</title>
-  </head>
-  <body>
-    <wicket:panel>
-      <label wicket:id="label" />
-    </wicket:panel>
-  </body>
+  <wicket:panel>
+    <label wicket:id="label"/>
+  </wicket:panel>
 </html>

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/client/console/src/main/resources/org/apache/syncope/client/console/panels/LogStatementPanel.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/LogStatementPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/panels/LogStatementPanel.html
new file mode 100644
index 0000000..95d49b0
--- /dev/null
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/LogStatementPanel.html
@@ -0,0 +1,33 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+  <wicket:panel>
+    <div style="padding: 5px; font-family: monospace;">
+      [<span wicket:id="instant"></span>]
+      <span wicket:id="logger"></span><br/>
+      <span wicket:id="message"></span>
+      <div wicket:id="collapse">
+        <span>+ <u>Error Details</u></span>
+        <div>
+          <pre wicket:id="stacktrace" style="white-space: pre-wrap;max-width: 95vw;"></pre>
+        </div>
+      </div>
+    </div>
+  </wicket:panel>
+</html>

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/common/lib/src/main/java/org/apache/syncope/common/lib/log/LogStatementTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/log/LogStatementTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/log/LogStatementTO.java
index 11e22c9..7d28e15 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/log/LogStatementTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/log/LogStatementTO.java
@@ -18,10 +18,14 @@
  */
 package org.apache.syncope.common.lib.log;
 
-import java.io.Serializable;
+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;
 
-public class LogStatementTO implements Serializable {
+@XmlRootElement(name = "logStatement")
+@XmlType
+public class LogStatementTO extends AbstractBaseBean {
 
     private static final long serialVersionUID = -2931205859104653385L;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/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 7416d92..d8a0c6c 100644
--- a/fit/core-reference/src/main/resources/log4j2.xml
+++ b/fit/core-reference/src/main/resources/log4j2.xml
@@ -21,7 +21,7 @@ under the License.
 
   <appenders>
     
-    <RollingRandomAccessFile name="main" fileName="${log.directory}/core.log"
+    <RollingRandomAccessFile name="mainFile" fileName="${log.directory}/core.log"
                              filePattern="${log.directory}/core-%d{yyyy-MM-dd}.log.gz"
                              immediateFlush="false" append="true">
       <PatternLayout>
@@ -32,9 +32,9 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
-    <Memory name="mainMemory" size="25"/>
+    <Memory name="main" size="25"/>
 
-    <RollingRandomAccessFile name="persistence" fileName="${log.directory}/core-persistence.log"
+    <RollingRandomAccessFile name="persistenceFile" fileName="${log.directory}/core-persistence.log"
                              filePattern="${log.directory}/core-persistence-%d{yyyy-MM-dd}.log.gz"
                              immediateFlush="false" append="true">
       <PatternLayout>
@@ -45,9 +45,9 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
-    <Memory name="persistenceMemory" size="25"/>
+    <Memory name="persistence" size="25"/>
 
-    <RollingRandomAccessFile name="rest" fileName="${log.directory}/core-rest.log"
+    <RollingRandomAccessFile name="restFile" fileName="${log.directory}/core-rest.log"
                              filePattern="${log.directory}/core-rest-%d{yyyy-MM-dd}.log.gz"
                              immediateFlush="false" append="true">
       <PatternLayout>
@@ -58,9 +58,9 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
-    <Memory name="restMemory" size="25"/>
+    <Memory name="rest" size="25"/>
 
-    <RollingRandomAccessFile name="connid" fileName="${log.directory}/core-connid.log"
+    <RollingRandomAccessFile name="connidFile" fileName="${log.directory}/core-connid.log"
                              filePattern="${log.directory}/core-connid-%d{yyyy-MM-dd}.log.gz"
                              immediateFlush="false" append="true">
       <PatternLayout>
@@ -71,87 +71,87 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
-    <Memory name="connidMemory" size="25"/>
+    <Memory name="connid" size="25"/>
     
   </appenders>
   
   <loggers>
     
     <asyncLogger name="org.apache.syncope.core.persistence" additivity="false" level="INFO">
+      <appender-ref ref="persistenceFile"/>
       <appender-ref ref="persistence"/>
-      <appender-ref ref="persistenceMemory"/>
     </asyncLogger>
     <asyncLogger name="org.springframework.orm" additivity="false" level="INFO">
+      <appender-ref ref="persistenceFile"/>
       <appender-ref ref="persistence"/>
-      <appender-ref ref="persistenceMemory"/>
     </asyncLogger>
     
     <asyncLogger name="org.apache.syncope.core.rest" additivity="false" level="INFO">
+      <appender-ref ref="restFile"/>
       <appender-ref ref="rest"/>
-      <appender-ref ref="restMemory"/>
     </asyncLogger>
     <asyncLogger name="org.springframework.web" additivity="false" level="INFO">
+      <appender-ref ref="restFile"/>
       <appender-ref ref="rest"/>
-      <appender-ref ref="restMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.http" additivity="false" level="INFO">
+      <appender-ref ref="restFile"/>
       <appender-ref ref="rest"/>
-      <appender-ref ref="restMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.cxf" additivity="false" level="ERROR">
+      <appender-ref ref="restFile"/>
       <appender-ref ref="rest"/>
-      <appender-ref ref="restMemory"/>
     </asyncLogger>
     
     <asyncLogger name="org.identityconnectors" additivity="false" level="DEBUG">
+      <appender-ref ref="connidFile"/>
       <appender-ref ref="connid"/>
-      <appender-ref ref="connidMemory"/>
     </asyncLogger>
     <asyncLogger name="net.tirasa.connid" additivity="false" level="DEBUG">
+      <appender-ref ref="connidFile"/>
       <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="connidFile"/>
       <appender-ref ref="connid"/>
-      <appender-ref ref="connidMemory"/>
     </asyncLogger>
     
     <asyncLogger name="org.apache.syncope" additivity="false" level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.syncope.core.provisioning" additivity="false" level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.syncope.core.logic" additivity="false" level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.springframework" additivity="false" level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.quartz" additivity="false" level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.activiti" additivity="false" level="ERROR">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.camel" additivity="false" level="ERROR">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="io.swagger" additivity="false" level="ERROR">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     
     <root level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </root>
     
   </loggers>

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/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 366770d..d139820 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
@@ -316,7 +316,7 @@ public class CLIITCase extends AbstractITCase {
             PROCESS_BUILDER.command(getCommand(
                     new LoggerCommand().getClass().getAnnotation(Command.class).name(),
                     LoggerCommand.LoggerOptions.LAST_STATEMENTS.getOptionName(),
-                    "connidMemory"));
+                    "connid"));
             process = PROCESS_BUILDER.start();
             final String result = IOUtils.toString(process.getInputStream(), SyncopeConstants.DEFAULT_CHARSET);
             assertTrue(result.contains("\"level\" : \"DEBUG\","));

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/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 07416c6..d534764 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
@@ -66,7 +66,7 @@ public class LoggerITCase extends AbstractITCase {
 
     @Test
     public void lastStatements() {
-        Queue<LogStatementTO> statements = loggerService.getLastLogStatements("connidMemory");
+        Queue<LogStatementTO> statements = loggerService.getLastLogStatements("connid");
         assertNotNull(statements);
         assertFalse(statements.isEmpty());
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/pom.xml
----------------------------------------------------------------------
diff --git a/ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/pom.xml b/ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/pom.xml
index 322502a..16a88e8 100644
--- a/ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/pom.xml
+++ b/ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/pom.xml
@@ -53,9 +53,31 @@ under the License.
 
   <build>
     <outputDirectory>./bin</outputDirectory>
+
     <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-clean-plugin</artifactId>
+        <configuration>
+          <filesets>
+            <fileset>
+              <directory>bin</directory>
+	      <includes>
+		<include>**/*</include>
+	      </includes>
+            </fileset>
+            <fileset>
+              <directory>lib</directory>
+	      <includes>
+		<include>**/*</include>
+	      </includes>
+            </fileset>
+          </filesets>
+        </configuration>
+      </plugin>
+      
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-dependency-plugin</artifactId>
         <executions>
           <execution>

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 8d47402..ff43950 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1411,6 +1411,12 @@ under the License.
         
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-clean-plugin</artifactId>
+          <version>3.0.0</version>
+        </plugin>
+        
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-resources-plugin</artifactId>
           <version>3.0.2</version>
           <configuration>

http://git-wip-us.apache.org/repos/asf/syncope/blob/3f44dda9/src/main/asciidoc/reference-guide/workingwithapachesyncope/cli/logger.adoc
----------------------------------------------------------------------
diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/cli/logger.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/cli/logger.adoc
index 6ebc5a3..236cde9 100644
--- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/cli/logger.adoc
+++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/cli/logger.adoc
@@ -26,7 +26,10 @@ This command is meant for tweaking runtime logger configuration.
 Usage: logger [options]
   Options:
     --help 
-    --details 
+    --details
+    --list-memory-appenders
+    --last-statements
+       Syntax: --last-statements {APPENDER-NAME}
     --list 
     --read 
        Syntax: --read {LOG-NAME} {LOG-NAME} [...]
@@ -47,6 +50,11 @@ Usage: logger [options]
 This option shows a table with some details about logger configuration.
 --list::
 Running the command with this option you will see the table of the loggers configuration.
+--list-memory-appenders
+Running the command with this option you will see the table of the memory appenders, whose last statements can be
+inspected
+--last-statements
+The option to get the last statements available for the passed memory appender
 --read::
 The option to read all the information of specified loggers.
 --update::


[2/2] syncope git commit: [SYNCOPE-882] Log viewer for the admin console

Posted by il...@apache.org.
[SYNCOPE-882] Log viewer for the admin console


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/f4c717e7
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/f4c717e7
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/f4c717e7

Branch: refs/heads/master
Commit: f4c717e7d61e20cd205741dea1aad2c920362822
Parents: 3684437
Author: Francesco Chicchiricc� <il...@apache.org>
Authored: Wed Jan 4 09:07:24 2017 +0100
Committer: Francesco Chicchiricc� <il...@apache.org>
Committed: Thu Jan 5 17:17:29 2017 +0100

----------------------------------------------------------------------
 .../syncope/client/console/pages/BasePage.java  |   2 +-
 .../syncope/client/console/pages/LogViewer.java | 243 +++++++++++++++++++
 .../console/panels/AbstractLogsPanel.java       |  10 +-
 .../client/console/panels/CoreLogPanel.java     |   6 +
 .../client/console/panels/ExecMessageModal.java |  12 +-
 .../console/panels/LogStatementPanel.java       |  93 +++++++
 .../client/console/rest/LoggerRestClient.java   |  30 +++
 .../client/console/widgets/JobActionPanel.java  |   9 +-
 .../client/console/widgets/JobWidget.java       |  11 +-
 .../syncope/client/console/pages/LogViewer.html |  34 +++
 .../client/console/panels/CoreLogPanel.html     |   5 +
 .../client/console/panels/LabelPanel.html       |  11 +-
 .../console/panels/LogStatementPanel.html       |  33 +++
 .../syncope/common/lib/log/LogStatementTO.java  |   8 +-
 .../src/main/resources/log4j2.xml               |  52 ++--
 .../org/apache/syncope/fit/cli/CLIITCase.java   |   2 +-
 .../apache/syncope/fit/core/LoggerITCase.java   |   2 +-
 .../pom.xml                                     |  22 ++
 pom.xml                                         |   6 +
 .../workingwithapachesyncope/cli/logger.adoc    |  10 +-
 20 files changed, 545 insertions(+), 56 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index 2335b23..255145f 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -259,7 +259,7 @@ public class BasePage extends WebPage implements IAjaxIndicatorAware {
             }
         });
         body.add(new Label("domain", SyncopeConsoleSession.get().getDomain()));
-        body.add(new BookmarkablePageLink<>("logout", Logout.class));
+        body.add(new BookmarkablePageLink<Page>("logout", Logout.class));
 
         // set 'active' menu item for everything but extensions
         // 1. check if current class is set to top-level menu

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java
new file mode 100644
index 0000000..c96ee1c
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/LogViewer.java
@@ -0,0 +1,243 @@
+/*
+ * 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.client.console.pages;
+
+import static org.apache.wicket.Component.ENABLE;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.collections4.list.SetUniqueList;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.panels.LogStatementPanel;
+import org.apache.syncope.client.console.rest.LoggerRestClient;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.common.lib.log.LogStatementTO;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.wicket.Application;
+import org.apache.wicket.ThreadContext;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.util.ListModel;
+import org.apache.wicket.protocol.ws.WebSocketSettings;
+import org.apache.wicket.protocol.ws.api.WebSocketBehavior;
+import org.apache.wicket.protocol.ws.api.WebSocketPushBroadcaster;
+import org.apache.wicket.protocol.ws.api.event.WebSocketPushPayload;
+import org.apache.wicket.protocol.ws.api.message.ConnectedMessage;
+import org.apache.wicket.protocol.ws.api.message.IWebSocketPushMessage;
+import org.apache.wicket.protocol.ws.api.registry.IKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LogViewer extends WebPage {
+
+    private static final Logger LOG = LoggerFactory.getLogger(LogViewer.class);
+
+    private static final int MAX_STATEMENTS_PER_APPENDER = 50;
+
+    private static final long serialVersionUID = -7578329899052708105L;
+
+    private final LoggerRestClient restClient = new LoggerRestClient();
+
+    private final IModel<Long> lastTimeInMillis = Model.of(0L);
+
+    private final WebMarkupContainer stContainer;
+
+    private final IModel<List<LogStatementTO>> statementViewModel;
+
+    private final ListView<LogStatementTO> statementView;
+
+    public LogViewer() {
+        final WebMarkupContainer viewer = new WebMarkupContainer("viewer");
+        viewer.setOutputMarkupId(true);
+        add(viewer);
+
+        final AjaxDropDownChoicePanel<String> appenders = new AjaxDropDownChoicePanel<>(
+                "appenders", "Appender", new Model<String>(), false);
+        MetaDataRoleAuthorizationStrategy.authorize(appenders, ENABLE, StandardEntitlement.LOG_READ);
+        appenders.setChoices(restClient.listMemoryAppenders());
+        viewer.add(appenders);
+
+        stContainer = new WebMarkupContainer("stContainer");
+        stContainer.setOutputMarkupId(true);
+        viewer.add(stContainer);
+
+        statementViewModel = new ListModel<>(new ArrayList<LogStatementTO>());
+        statementView = new ListView<LogStatementTO>("statements", statementViewModel) {
+
+            private static final long serialVersionUID = -9180479401817023838L;
+
+            @Override
+            protected void populateItem(final ListItem<LogStatementTO> item) {
+                LogStatementPanel panel = new LogStatementPanel("statement", item.getModelObject());
+                panel.setOutputMarkupId(true);
+                item.add(panel);
+            }
+        };
+        statementView.setOutputMarkupId(true);
+        stContainer.add(statementView);
+
+        appenders.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                List<LogStatementTO> lastStatements = appenders.getModelObject() == null
+                        ? new ArrayList<LogStatementTO>()
+                        : restClient.getLastLogStatements(appenders.getModelObject(), 0);
+                statementViewModel.setObject(lastStatements);
+                target.add(stContainer);
+
+                lastTimeInMillis.setObject(0L);
+            }
+        });
+
+        add(new WebSocketBehavior() {
+
+            private static final long serialVersionUID = 3507933905864454312L;
+
+            @Override
+            protected void onConnect(final ConnectedMessage message) {
+                super.onConnect(message);
+
+                SyncopeConsoleSession.get().scheduleAtFixedRate(
+                        new LogStatementUpdater(message, restClient, appenders, lastTimeInMillis),
+                        0, 10, TimeUnit.SECONDS);
+            }
+
+        });
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof WebSocketPushPayload) {
+            WebSocketPushPayload wsEvent = (WebSocketPushPayload) event.getPayload();
+            if (wsEvent.getMessage() instanceof LogViewerMessage) {
+                List<LogStatementTO> recentLogStatements =
+                        ((LogViewerMessage) wsEvent.getMessage()).getRecentLogStatements();
+
+                if (!recentLogStatements.isEmpty()) {
+                    // save scroll position
+                    wsEvent.getHandler().prependJavaScript(
+                            String.format("window.scrollTop = $('#%s').scrollTop();", stContainer.getMarkupId()));
+
+                    int currentSize = statementView.getModelObject().size();
+                    int recentSize = recentLogStatements.size();
+
+                    List<LogStatementTO> newModelObject = SetUniqueList.<LogStatementTO>setUniqueList(
+                            new ArrayList<LogStatementTO>(MAX_STATEMENTS_PER_APPENDER));
+                    if (currentSize <= MAX_STATEMENTS_PER_APPENDER - recentSize) {
+                        newModelObject.addAll(statementView.getModelObject());
+                    } else {
+                        newModelObject.addAll(statementView.getModelObject().subList(recentSize, currentSize));
+                    }
+                    newModelObject.addAll(recentLogStatements);
+
+                    statementViewModel.setObject(newModelObject);
+                    wsEvent.getHandler().add(LogViewer.this.stContainer);
+
+                    // restore scroll position - might not work perfectly if items were removed from the top
+                    wsEvent.getHandler().appendJavaScript(
+                            String.format("$('#%s').scrollTop(window.scrollTop);", stContainer.getMarkupId()));
+                }
+            }
+        }
+    }
+
+    private static final class LogStatementUpdater implements Runnable {
+
+        private final Application application;
+
+        private final SyncopeConsoleSession session;
+
+        private final IKey key;
+
+        private final LoggerRestClient restClient;
+
+        private final AjaxDropDownChoicePanel<String> appenders;
+
+        private final IModel<Long> lastTimeInMillis;
+
+        LogStatementUpdater(
+                final ConnectedMessage message,
+                final LoggerRestClient restClient,
+                final AjaxDropDownChoicePanel<String> appenders,
+                final IModel<Long> lastTimeInMillis) {
+
+            this.application = message.getApplication();
+            this.session = SyncopeConsoleSession.get();
+            this.key = message.getKey();
+            this.restClient = restClient;
+            this.appenders = appenders;
+            this.lastTimeInMillis = lastTimeInMillis;
+        }
+
+        @Override
+        public void run() {
+            try {
+                ThreadContext.setApplication(application);
+                ThreadContext.setSession(session);
+
+                List<LogStatementTO> recentLogStatements = appenders.getModelObject() == null
+                        ? new ArrayList<LogStatementTO>()
+                        : restClient.getLastLogStatements(appenders.getModelObject(), lastTimeInMillis.getObject());
+                if (!recentLogStatements.isEmpty()) {
+                    lastTimeInMillis.setObject(recentLogStatements.get(recentLogStatements.size() - 1).getTimeMillis());
+                }
+
+                WebSocketSettings settings = WebSocketSettings.Holder.get(application);
+                WebSocketPushBroadcaster broadcaster = new WebSocketPushBroadcaster(settings.getConnectionRegistry());
+                broadcaster.broadcast(
+                        new ConnectedMessage(application, session.getId(), key),
+                        new LogViewerMessage(recentLogStatements));
+            } catch (Throwable t) {
+                LOG.error("Unexpected error while checking for recent log statements", t);
+            } finally {
+                ThreadContext.detach();
+            }
+        }
+    }
+
+    private static class LogViewerMessage implements IWebSocketPushMessage, Serializable {
+
+        private static final long serialVersionUID = 7241149017008105769L;
+
+        private final List<LogStatementTO> recentLogStatements;
+
+        LogViewerMessage(final List<LogStatementTO> recentLogStatements) {
+            this.recentLogStatements = recentLogStatements;
+        }
+
+        public List<LogStatementTO> getRecentLogStatements() {
+            return recentLogStatements;
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
index dac4ab5..fef5be5 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
@@ -48,6 +48,8 @@ public abstract class AbstractLogsPanel<T extends AbstractBaseBean> extends Pane
 
     private static final long serialVersionUID = -6313532280206208227L;
 
+    protected WebMarkupContainer loggerContainer;
+
     public AbstractLogsPanel(
             final String id,
             final PageReference pageRef,
@@ -55,9 +57,9 @@ public abstract class AbstractLogsPanel<T extends AbstractBaseBean> extends Pane
 
         super(id);
 
-        WebMarkupContainer container = new WebMarkupContainer("loggerContainer");
-        container.setOutputMarkupId(true);
-        add(container);
+        loggerContainer = new WebMarkupContainer("loggerContainer");
+        loggerContainer.setOutputMarkupId(true);
+        add(loggerContainer);
 
         ListViewPanel.Builder<LoggerTO> builder = new ListViewPanel.Builder<LoggerTO>(LoggerTO.class, pageRef) {
 
@@ -104,7 +106,7 @@ public abstract class AbstractLogsPanel<T extends AbstractBaseBean> extends Pane
                 withChecks(ListViewPanel.CheckAvailability.NONE).
                 setReuseItem(false);
 
-        container.add(builder.build("logger"));
+        loggerContainer.add(builder.build("logger"));
     }
 
     protected abstract void update(final LoggerTO loggerTO);

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/java/org/apache/syncope/client/console/panels/CoreLogPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/CoreLogPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/CoreLogPanel.java
index aeb672e..1fc45a8 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/CoreLogPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/CoreLogPanel.java
@@ -19,10 +19,13 @@
 package org.apache.syncope.client.console.panels;
 
 import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.pages.LogViewer;
 import org.apache.syncope.common.lib.log.LoggerTO;
 import org.apache.syncope.common.lib.types.LoggerType;
 import org.apache.syncope.common.rest.api.service.LoggerService;
 import org.apache.wicket.PageReference;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.PopupSettings;
 
 public class CoreLogPanel extends AbstractLogsPanel<LoggerTO> {
 
@@ -31,6 +34,9 @@ public class CoreLogPanel extends AbstractLogsPanel<LoggerTO> {
     public CoreLogPanel(final String id, final PageReference pageReference) {
         super(id, pageReference, SyncopeConsoleSession.get().getService(LoggerService.class).list(LoggerType.LOG));
 
+        BookmarkablePageLink<Void> viewer = new BookmarkablePageLink<>("viewer", LogViewer.class);
+        viewer.setPopupSettings(new PopupSettings().setHeight(600).setWidth(800));
+        loggerContainer.add(viewer);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/java/org/apache/syncope/client/console/panels/ExecMessageModal.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ExecMessageModal.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ExecMessageModal.java
index 23f411b..91e0b98 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ExecMessageModal.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ExecMessageModal.java
@@ -19,16 +19,20 @@
 package org.apache.syncope.client.console.panels;
 
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
-import org.apache.wicket.PageReference;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.panel.Panel;
 
 public class ExecMessageModal extends Panel implements ModalPanel {
 
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = -8751533037282034918L;
 
-    public ExecMessageModal(final PageReference pageRef, final String executionMessage) {
-        super(BaseModal.CONTENT_ID);
+    public ExecMessageModal(final String executionMessage) {
+        this(BaseModal.CONTENT_ID, executionMessage);
+    }
+
+    public ExecMessageModal(final String id, final String executionMessage) {
+        super(id);
         add(new Label("executionMessage", executionMessage).setOutputMarkupId(true));
     }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java
new file mode 100644
index 0000000..abae16b
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/LogStatementPanel.java
@@ -0,0 +1,93 @@
+/*
+ * 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.client.console.panels;
+
+import com.googlecode.wicket.jquery.core.Options;
+import com.googlecode.wicket.jquery.ui.JQueryUIBehavior;
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Alert;
+import de.agilecoders.wicket.core.util.Attributes;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.FastDateFormat;
+import org.apache.syncope.common.lib.log.LogStatementTO;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+
+public class LogStatementPanel extends Panel {
+
+    private static final long serialVersionUID = 1610867968070669922L;
+
+    private static final FastDateFormat FORMAT = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS");
+
+    private final String labelCssClass;
+
+    public LogStatementPanel(final String id, final LogStatementTO statement) {
+        super(id);
+
+        Alert.Type type;
+        switch (statement.getLevel()) {
+            case DEBUG:
+                type = Alert.Type.Success;
+                break;
+
+            case INFO:
+                type = Alert.Type.Info;
+                break;
+
+            case ERROR:
+                type = Alert.Type.Danger;
+                break;
+
+            case WARN:
+                type = Alert.Type.Warning;
+                break;
+
+            default:
+                type = Alert.Type.Info;
+        }
+        labelCssClass = "label-" + type.name().toLowerCase();
+
+        add(new Label("logger", Model.of(statement.getLoggerName())));
+        add(new Label("instant", Model.of(FORMAT.format(statement.getTimeMillis()))));
+        add(new Label("message", Model.of(statement.getMessage())));
+
+        WebMarkupContainer collapse = new WebMarkupContainer("collapse");
+        collapse.setOutputMarkupId(true);
+        collapse.setOutputMarkupPlaceholderTag(true);
+        collapse.setVisible(StringUtils.isNotBlank(statement.getStackTrace()));
+        collapse.add(new JQueryUIBehavior(
+                "#" + collapse.getMarkupId(), "accordion", new Options("active", false).set("collapsible", true)));
+        add(collapse);
+
+        Label stacktrace = new Label("stacktrace", Model.of(statement.getStackTrace()));
+        stacktrace.setOutputMarkupId(true);
+        collapse.add(stacktrace);
+    }
+
+    @Override
+    protected void onComponentTag(final ComponentTag tag) {
+        super.onComponentTag(tag);
+
+        checkComponentTag(tag, "div");
+        Attributes.addClass(tag, labelCssClass);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
index 4922940..bc31608 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/LoggerRestClient.java
@@ -18,13 +18,21 @@
  */
 package org.apache.syncope.client.console.rest;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.collections4.TransformerUtils;
 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.LoggerLevel;
@@ -36,6 +44,28 @@ public class LoggerRestClient extends BaseRestClient {
 
     private static final long serialVersionUID = 4579786978763032240L;
 
+    public List<String> listMemoryAppenders() {
+        return CollectionUtils.collect(getService(LoggerService.class).memoryAppenders(),
+                new Transformer<LogAppender, String>() {
+
+            @Override
+            public String transform(final LogAppender input) {
+                return input.getName();
+            }
+        }, new ArrayList<String>());
+    }
+
+    public List<LogStatementTO> getLastLogStatements(final String appender, final long lastStatementTime) {
+        return CollectionUtils.collect(IterableUtils.filteredIterable(
+                getService(LoggerService.class).getLastLogStatements(appender), new Predicate<LogStatementTO>() {
+
+            @Override
+            public boolean evaluate(final LogStatementTO object) {
+                return object.getTimeMillis() > lastStatementTime;
+            }
+        }), TransformerUtils.<LogStatementTO>nopTransformer(), new ArrayList<LogStatementTO>());
+    }
+
     public List<LoggerTO> listLogs() {
         return getService(LoggerService.class).list(LoggerType.LOG);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
index ab245c7..30797ae 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobActionPanel.java
@@ -57,8 +57,13 @@ public class JobActionPanel extends WizardMgtPanel<Serializable> {
 
     private final BaseModal<Serializable> jobModal;
 
-    public JobActionPanel(final String id, final JobTO jobTO, final JobWidget widget,
-            final BaseModal<Serializable> jobModal, final PageReference pageRef) {
+    public JobActionPanel(
+            final String id,
+            final JobTO jobTO,
+            final JobWidget widget,
+            final BaseModal<Serializable> jobModal,
+            final PageReference pageRef) {
+
         super(id, true);
         this.jobModal = jobModal;
         setOutputMarkupId(true);

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
index e02f729..bc9db0d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
@@ -280,6 +280,10 @@ public class JobWidget extends BaseWidget {
 
             columns.add(new PropertyColumn<JobTO, String>(new ResourceModel("refDesc"), "refDesc", "refDesc"));
 
+            columns.add(new BooleanPropertyColumn<JobTO>(new ResourceModel("scheduled"), "scheduled", "scheduled"));
+
+            columns.add(new DatePropertyColumn<JobTO>(new ResourceModel("start"), "start", "start"));
+
             columns.add(new AbstractColumn<JobTO, String>(new Model<>(""), "running") {
 
                 private static final long serialVersionUID = -4008579357070833846L;
@@ -309,10 +313,6 @@ public class JobWidget extends BaseWidget {
 
             });
 
-            columns.add(new BooleanPropertyColumn<JobTO>(new ResourceModel("scheduled"), "scheduled", "scheduled"));
-
-            columns.add(new DatePropertyColumn<JobTO>(new ResourceModel("start"), "start", "start"));
-
             return columns;
         }
 
@@ -412,8 +412,7 @@ public class JobWidget extends BaseWidget {
                                     StringResourceModel stringResourceModel =
                                             new StringResourceModel("execution.view", JobWidget.this, model);
                                     detailModal.header(stringResourceModel);
-                                    detailModal.
-                                            setContent(new ExecMessageModal(pageRef, model.getObject().getMessage()));
+                                    detailModal.setContent(new ExecMessageModal(model.getObject().getMessage()));
                                     detailModal.show(true);
                                     target.add(detailModal);
                                 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/resources/org/apache/syncope/client/console/pages/LogViewer.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/LogViewer.html b/client/console/src/main/resources/org/apache/syncope/client/console/pages/LogViewer.html
new file mode 100644
index 0000000..c68b2e4
--- /dev/null
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/LogViewer.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+  <head>
+    <title>Apache Syncope - Log Viewer</title>
+  </head>
+  <body>
+    <div wicket:id="viewer" style="padding: 5px;">
+      <div wicket:id="appenders" style="margin-bottom: 10px;"></div>
+      <div wicket:id="stContainer" style="overflow-y: auto; overflow-x: auto; max-width: 100vw; max-height: 85vh;">
+        <div wicket:id="statements">
+          <div wicket:id="statement"/>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/resources/org/apache/syncope/client/console/panels/CoreLogPanel.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/CoreLogPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/panels/CoreLogPanel.html
index a4cd024..ccf729f 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/CoreLogPanel.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/CoreLogPanel.html
@@ -20,6 +20,11 @@ under the License.
   <wicket:panel>
     <span wicket:id="loggerContainer">
       <div class="logs">
+        <div class="pull-right">
+          <button class="btn btn-primary" wicket:id="viewer">
+            <span class="glyphicon glyphicon-list-alt"></span> Log Viewer
+          </button>
+        </div>
         <span wicket:id="logger">[logger]</span>
       </div>
     </span>

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/resources/org/apache/syncope/client/console/panels/LabelPanel.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/LabelPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/panels/LabelPanel.html
index 745e35a..c2ec0a9 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/panels/LabelPanel.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/LabelPanel.html
@@ -17,12 +17,7 @@ specific language governing permissions and limitations
 under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-  <head>
-    <title>Label  panel</title>
-  </head>
-  <body>
-    <wicket:panel>
-      <label wicket:id="label" />
-    </wicket:panel>
-  </body>
+  <wicket:panel>
+    <label wicket:id="label"/>
+  </wicket:panel>
 </html>

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/client/console/src/main/resources/org/apache/syncope/client/console/panels/LogStatementPanel.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/panels/LogStatementPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/panels/LogStatementPanel.html
new file mode 100644
index 0000000..95d49b0
--- /dev/null
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/panels/LogStatementPanel.html
@@ -0,0 +1,33 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+  <wicket:panel>
+    <div style="padding: 5px; font-family: monospace;">
+      [<span wicket:id="instant"></span>]
+      <span wicket:id="logger"></span><br/>
+      <span wicket:id="message"></span>
+      <div wicket:id="collapse">
+        <span>+ <u>Error Details</u></span>
+        <div>
+          <pre wicket:id="stacktrace" style="white-space: pre-wrap;max-width: 95vw;"></pre>
+        </div>
+      </div>
+    </div>
+  </wicket:panel>
+</html>

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/common/lib/src/main/java/org/apache/syncope/common/lib/log/LogStatementTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/log/LogStatementTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/log/LogStatementTO.java
index 11e22c9..7d28e15 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/log/LogStatementTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/log/LogStatementTO.java
@@ -18,10 +18,14 @@
  */
 package org.apache.syncope.common.lib.log;
 
-import java.io.Serializable;
+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;
 
-public class LogStatementTO implements Serializable {
+@XmlRootElement(name = "logStatement")
+@XmlType
+public class LogStatementTO extends AbstractBaseBean {
 
     private static final long serialVersionUID = -2931205859104653385L;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/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 7416d92..d8a0c6c 100644
--- a/fit/core-reference/src/main/resources/log4j2.xml
+++ b/fit/core-reference/src/main/resources/log4j2.xml
@@ -21,7 +21,7 @@ under the License.
 
   <appenders>
     
-    <RollingRandomAccessFile name="main" fileName="${log.directory}/core.log"
+    <RollingRandomAccessFile name="mainFile" fileName="${log.directory}/core.log"
                              filePattern="${log.directory}/core-%d{yyyy-MM-dd}.log.gz"
                              immediateFlush="false" append="true">
       <PatternLayout>
@@ -32,9 +32,9 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
-    <Memory name="mainMemory" size="25"/>
+    <Memory name="main" size="25"/>
 
-    <RollingRandomAccessFile name="persistence" fileName="${log.directory}/core-persistence.log"
+    <RollingRandomAccessFile name="persistenceFile" fileName="${log.directory}/core-persistence.log"
                              filePattern="${log.directory}/core-persistence-%d{yyyy-MM-dd}.log.gz"
                              immediateFlush="false" append="true">
       <PatternLayout>
@@ -45,9 +45,9 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
-    <Memory name="persistenceMemory" size="25"/>
+    <Memory name="persistence" size="25"/>
 
-    <RollingRandomAccessFile name="rest" fileName="${log.directory}/core-rest.log"
+    <RollingRandomAccessFile name="restFile" fileName="${log.directory}/core-rest.log"
                              filePattern="${log.directory}/core-rest-%d{yyyy-MM-dd}.log.gz"
                              immediateFlush="false" append="true">
       <PatternLayout>
@@ -58,9 +58,9 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
-    <Memory name="restMemory" size="25"/>
+    <Memory name="rest" size="25"/>
 
-    <RollingRandomAccessFile name="connid" fileName="${log.directory}/core-connid.log"
+    <RollingRandomAccessFile name="connidFile" fileName="${log.directory}/core-connid.log"
                              filePattern="${log.directory}/core-connid-%d{yyyy-MM-dd}.log.gz"
                              immediateFlush="false" append="true">
       <PatternLayout>
@@ -71,87 +71,87 @@ under the License.
         <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
     </RollingRandomAccessFile>
-    <Memory name="connidMemory" size="25"/>
+    <Memory name="connid" size="25"/>
     
   </appenders>
   
   <loggers>
     
     <asyncLogger name="org.apache.syncope.core.persistence" additivity="false" level="INFO">
+      <appender-ref ref="persistenceFile"/>
       <appender-ref ref="persistence"/>
-      <appender-ref ref="persistenceMemory"/>
     </asyncLogger>
     <asyncLogger name="org.springframework.orm" additivity="false" level="INFO">
+      <appender-ref ref="persistenceFile"/>
       <appender-ref ref="persistence"/>
-      <appender-ref ref="persistenceMemory"/>
     </asyncLogger>
     
     <asyncLogger name="org.apache.syncope.core.rest" additivity="false" level="INFO">
+      <appender-ref ref="restFile"/>
       <appender-ref ref="rest"/>
-      <appender-ref ref="restMemory"/>
     </asyncLogger>
     <asyncLogger name="org.springframework.web" additivity="false" level="INFO">
+      <appender-ref ref="restFile"/>
       <appender-ref ref="rest"/>
-      <appender-ref ref="restMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.http" additivity="false" level="INFO">
+      <appender-ref ref="restFile"/>
       <appender-ref ref="rest"/>
-      <appender-ref ref="restMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.cxf" additivity="false" level="ERROR">
+      <appender-ref ref="restFile"/>
       <appender-ref ref="rest"/>
-      <appender-ref ref="restMemory"/>
     </asyncLogger>
     
     <asyncLogger name="org.identityconnectors" additivity="false" level="DEBUG">
+      <appender-ref ref="connidFile"/>
       <appender-ref ref="connid"/>
-      <appender-ref ref="connidMemory"/>
     </asyncLogger>
     <asyncLogger name="net.tirasa.connid" additivity="false" level="DEBUG">
+      <appender-ref ref="connidFile"/>
       <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="connidFile"/>
       <appender-ref ref="connid"/>
-      <appender-ref ref="connidMemory"/>
     </asyncLogger>
     
     <asyncLogger name="org.apache.syncope" additivity="false" level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.syncope.core.provisioning" additivity="false" level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.syncope.core.logic" additivity="false" level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.springframework" additivity="false" level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.quartz" additivity="false" level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.activiti" additivity="false" level="ERROR">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="org.apache.camel" additivity="false" level="ERROR">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     <asyncLogger name="io.swagger" additivity="false" level="ERROR">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </asyncLogger>
     
     <root level="INFO">
+      <appender-ref ref="mainFile"/>
       <appender-ref ref="main"/>
-      <appender-ref ref="mainMemory"/>
     </root>
     
   </loggers>

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/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 366770d..d139820 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
@@ -316,7 +316,7 @@ public class CLIITCase extends AbstractITCase {
             PROCESS_BUILDER.command(getCommand(
                     new LoggerCommand().getClass().getAnnotation(Command.class).name(),
                     LoggerCommand.LoggerOptions.LAST_STATEMENTS.getOptionName(),
-                    "connidMemory"));
+                    "connid"));
             process = PROCESS_BUILDER.start();
             final String result = IOUtils.toString(process.getInputStream(), SyncopeConstants.DEFAULT_CHARSET);
             assertTrue(result.contains("\"level\" : \"DEBUG\","));

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/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 07416c6..d534764 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
@@ -66,7 +66,7 @@ public class LoggerITCase extends AbstractITCase {
 
     @Test
     public void lastStatements() {
-        Queue<LogStatementTO> statements = loggerService.getLastLogStatements("connidMemory");
+        Queue<LogStatementTO> statements = loggerService.getLastLogStatements("connid");
         assertNotNull(statements);
         assertFalse(statements.isEmpty());
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/pom.xml
----------------------------------------------------------------------
diff --git a/ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/pom.xml b/ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/pom.xml
index 3999eb5..8a35ff0 100644
--- a/ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/pom.xml
+++ b/ide/eclipse/bundles/org.apache.syncope.ide.eclipse.plugin/pom.xml
@@ -53,9 +53,31 @@ under the License.
 
   <build>
     <outputDirectory>./bin</outputDirectory>
+
     <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-clean-plugin</artifactId>
+        <configuration>
+          <filesets>
+            <fileset>
+              <directory>bin</directory>
+	      <includes>
+		<include>**/*</include>
+	      </includes>
+            </fileset>
+            <fileset>
+              <directory>lib</directory>
+	      <includes>
+		<include>**/*</include>
+	      </includes>
+            </fileset>
+          </filesets>
+        </configuration>
+      </plugin>
+      
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-dependency-plugin</artifactId>
         <executions>
           <execution>

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 28b1da2..eb38151 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1406,6 +1406,12 @@ under the License.
         
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-clean-plugin</artifactId>
+          <version>3.0.0</version>
+        </plugin>
+        
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-resources-plugin</artifactId>
           <version>3.0.2</version>
           <configuration>

http://git-wip-us.apache.org/repos/asf/syncope/blob/f4c717e7/src/main/asciidoc/reference-guide/workingwithapachesyncope/cli/logger.adoc
----------------------------------------------------------------------
diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/cli/logger.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/cli/logger.adoc
index 6ebc5a3..236cde9 100644
--- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/cli/logger.adoc
+++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/cli/logger.adoc
@@ -26,7 +26,10 @@ This command is meant for tweaking runtime logger configuration.
 Usage: logger [options]
   Options:
     --help 
-    --details 
+    --details
+    --list-memory-appenders
+    --last-statements
+       Syntax: --last-statements {APPENDER-NAME}
     --list 
     --read 
        Syntax: --read {LOG-NAME} {LOG-NAME} [...]
@@ -47,6 +50,11 @@ Usage: logger [options]
 This option shows a table with some details about logger configuration.
 --list::
 Running the command with this option you will see the table of the loggers configuration.
+--list-memory-appenders
+Running the command with this option you will see the table of the memory appenders, whose last statements can be
+inspected
+--last-statements
+The option to get the last statements available for the passed memory appender
 --read::
 The option to read all the information of specified loggers.
 --update::