syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From andreapatrice...@apache.org
Subject syncope git commit: [SYNCOPE-1144] configurable audit appenders with message rewrite option
Date Thu, 13 Jul 2017 12:24:01 GMT
Repository: syncope
Updated Branches:
  refs/heads/2_0_X bfd47df70 -> dc4b83ef1


[SYNCOPE-1144] configurable audit appenders with message rewrite option


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

Branch: refs/heads/2_0_X
Commit: dc4b83ef1cbf76240bf24c9b8a7e8cc9ae0a9994
Parents: bfd47df
Author: Andrea Patricelli <andreapatricelli@apache.org>
Authored: Tue Jul 4 12:09:45 2017 +0200
Committer: Andrea Patricelli <andreapatricelli@apache.org>
Committed: Thu Jul 13 14:21:14 2017 +0200

----------------------------------------------------------------------
 .../core/logic/AbstractAuditAppender.java       | 71 +++++++++++++++
 .../syncope/core/logic/AuditAppender.java       | 51 +++++++++++
 .../core/logic/DefaultAuditAppender.java        | 54 ++++++++++++
 .../core/logic/DefaultRewriteAuditAppender.java | 64 ++++++++++++++
 .../apache/syncope/core/logic/LoggerLogic.java  | 46 +++++++++-
 .../core/logic/PassThroughRewritePolicy.java    | 41 +++++++++
 .../init/ClassPathScanImplementationLookup.java | 15 ++++
 .../syncope/core/logic/init/LoggerAccessor.java | 12 ++-
 .../syncope/core/logic/init/LoggerLoader.java   | 79 +++++++++++++++++
 .../persistence/api/ImplementationLookup.java   |  5 +-
 .../jpa/DummyImplementationLookup.java          |  5 ++
 .../provisioning/java/AuditManagerImpl.java     | 13 ++-
 .../AbstractPropagationTaskExecutor.java        |  2 +-
 .../java/DummyImplementationLookup.java         |  5 ++
 .../core/reference/ITImplementationLookup.java  | 13 +++
 .../reference/SyslogRewriteAuditAppender.java   | 80 +++++++++++++++++
 .../core/reference/TestFileAuditAppender.java   | 86 ++++++++++++++++++
 .../reference/TestFileRewriteAuditAppender.java | 84 ++++++++++++++++++
 .../fit/core/reference/TestRewritePolicy.java   | 46 ++++++++++
 .../apache/syncope/fit/core/LoggerITCase.java   | 93 ++++++++++++++++++++
 .../src/test/resources/core-test.properties     |  1 +
 .../reference-guide/concepts/audit.adoc         | 50 ++++++++++-
 .../workingwithapachesyncope/customization.adoc |  6 ++
 23 files changed, 910 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAuditAppender.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAuditAppender.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAuditAppender.java
new file mode 100644
index 0000000..5383a81
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAuditAppender.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+
+public abstract class AbstractAuditAppender implements AuditAppender {
+
+    protected String domainName;
+
+    protected Appender targetAppender;
+
+    protected RewriteAppender rewriteAppender;
+
+    @Override
+    public abstract void init();
+
+    public abstract void initTargetAppender();
+
+    public abstract void initRewriteAppender();
+
+    @Override
+    public abstract RewritePolicy getRewritePolicy();
+
+    @Override
+    public String getDomainName() {
+        return domainName;
+    }
+
+    @Override
+    public void setDomainName(final String domainName) {
+        this.domainName = domainName;
+    }
+
+    @Override
+    public abstract String getTargetAppenderName();
+
+    @Override
+    public boolean isRewriteEnabled() {
+        return rewriteAppender != null;
+    }
+
+    @Override
+    public RewriteAppender getRewriteAppender() {
+        return rewriteAppender;
+    }
+
+    @Override
+    public Appender getTargetAppender() {
+        return targetAppender;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/AuditAppender.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AuditAppender.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AuditAppender.java
new file mode 100644
index 0000000..93dbfed
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AuditAppender.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import java.util.Set;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+
+/**
+ * Basic interface to implement to define a custom audit appender
+ *
+ * @see org.apache.syncope.core.logic.DefaultRewriteAuditAppender or org.apache.syncope.core.logic.DefaultAuditAppender
+ */
+public interface AuditAppender {
+
+    void init();
+
+    Set<AuditLoggerName> getEvents();
+
+    Appender getTargetAppender();
+
+    RewritePolicy getRewritePolicy();
+
+    String getTargetAppenderName();
+    
+    void setDomainName(String name);
+
+    String getDomainName();
+
+    boolean isRewriteEnabled();
+
+    RewriteAppender getRewriteAppender();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultAuditAppender.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultAuditAppender.java b/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultAuditAppender.java
new file mode 100644
index 0000000..1fd9e5e
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultAuditAppender.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import java.util.Collections;
+import java.util.Set;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+
+/**
+ * Default (abstract) implementation of custom audit appender.
+ * It is bound to an empty collection of events, i.e. it does not create any logger.
+ * This class has to be extended by non-rewrite appenders
+ *
+ * @see org.apache.syncope.fit.core.reference.TestFileAuditAppender
+ */
+public abstract class DefaultAuditAppender extends AbstractAuditAppender {
+
+    @Override
+    public void init() {
+        initTargetAppender();
+    }
+
+    @Override
+    public Set<AuditLoggerName> getEvents() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public void initRewriteAppender() {
+    }
+
+    @Override
+    public RewritePolicy getRewritePolicy() {
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultRewriteAuditAppender.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultRewriteAuditAppender.java b/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultRewriteAuditAppender.java
new file mode 100644
index 0000000..207502e
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/DefaultRewriteAuditAppender.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import java.util.Collections;
+import java.util.Set;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+import org.apache.logging.log4j.core.config.AppenderRef;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+
+/**
+ * Default (abstract) implementation of custom rewriting audit appender; it provides rewrite appender definition and 
+ * a default "pass-through" policy. It is bound to an empty collection of events, i.e. it does not create any logger.
+ * This class has to be extended by rewrite appenders.
+ *
+ * @see org.apache.syncope.fit.core.reference.TestFileRewriteAuditAppender
+ */
+public abstract class DefaultRewriteAuditAppender extends AbstractAuditAppender {
+
+    @Override
+    public void init() {
+        initTargetAppender();
+        initRewriteAppender();
+    }
+
+    @Override
+    public void initRewriteAppender() {
+        rewriteAppender = RewriteAppender.createAppender(getTargetAppenderName() + "_rewrite",
+                "true",
+                new AppenderRef[] { AppenderRef.createAppenderRef(getTargetAppenderName(), Level.DEBUG, null) },
+                ((LoggerContext) LogManager.getContext(false)).getConfiguration(), getRewritePolicy(), null);
+    }
+
+    @Override
+    public Set<AuditLoggerName> getEvents() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public RewritePolicy getRewritePolicy() {
+        return PassThroughRewritePolicy.createPolicy();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
index 5315ea9..667efc8 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
@@ -25,7 +25,9 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.IterableUtils;
 import org.apache.commons.collections4.IteratorUtils;
+import org.apache.commons.collections4.Predicate;
 import org.apache.commons.collections4.PredicateUtils;
 import org.apache.commons.collections4.Transformer;
 import org.apache.commons.collections4.TransformerUtils;
@@ -60,9 +62,11 @@ import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.Logger;
 import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.core.provisioning.java.AuditManagerImpl;
 import org.apache.syncope.core.spring.BeanUtils;
 import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
 import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.io.Resource;
 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
@@ -216,10 +220,41 @@ public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> {
         syncopeLogger.setLevel(LoggerLevel.fromLevel(level));
         syncopeLogger = loggerDAO.save(syncopeLogger);
 
+        boolean isAudit = LoggerType.AUDIT.equals(syncopeLogger.getType());
         LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+        String domainAuditLoggerName =
+                AuditManagerImpl.getDomainAuditEventLoggerName(AuthContextUtils.getDomain(), syncopeLogger.
+                        getKey());
         LoggerConfig logConf = SyncopeConstants.ROOT_LOGGER.equals(name)
                 ? ctx.getConfiguration().getLoggerConfig(LogManager.ROOT_LOGGER_NAME)
-                : ctx.getConfiguration().getLoggerConfig(name);
+                : isAudit
+                        ? ctx.getConfiguration().getLoggerConfig(domainAuditLoggerName)
+                        : ctx.getConfiguration().getLoggerConfig(name);
+
+        if (isAudit) {
+            // SYNCOPE-1144 For each custom audit appender class add related appenders to log4j logger
+            List<AuditAppender> auditAppenders = loggerLoader.auditAppenders(AuthContextUtils.getDomain());
+            boolean isRootLogConf = LogManager.ROOT_LOGGER_NAME.equals(logConf.getName());
+            final String loggerKey = syncopeLogger.getKey();
+            if (isRootLogConf) {
+                logConf = new LoggerConfig(domainAuditLoggerName, null, false);
+            }
+            for (AuditAppender auditAppender : auditAppenders) {
+
+                if (IterableUtils.matchesAny(auditAppender.getEvents(), new Predicate<AuditLoggerName>() {
+
+                    @Override
+                    public boolean evaluate(final AuditLoggerName auditLoggerName) {
+                        return loggerKey.equalsIgnoreCase(auditLoggerName.toLoggerName());
+                    }
+                })) {
+                    loggerLoader.addAppenderToContext(ctx, auditAppender, logConf);
+                }
+            }
+            if (isRootLogConf) {
+                ctx.getConfiguration().addLogger(domainAuditLoggerName, logConf);
+            }
+        }
         logConf.setLevel(level);
         ctx.updateLoggers();
 
@@ -254,6 +289,7 @@ public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> {
         if (expectedType != syncopeLogger.getType()) {
             throwInvalidLogger(expectedType);
         }
+        boolean isAudit = LoggerType.AUDIT.equals(syncopeLogger.getType());
 
         LoggerTO loggerToDelete = new LoggerTO();
         BeanUtils.copyProperties(syncopeLogger, loggerToDelete);
@@ -263,8 +299,14 @@ public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> {
 
         // set log level to OFF in order to disable configured logger until next reboot
         LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+        String domainAuditLoggerName =
+                AuditManagerImpl.getDomainAuditEventLoggerName(AuthContextUtils.getDomain(), syncopeLogger.
+                        getKey());
         org.apache.logging.log4j.core.Logger logger = SyncopeConstants.ROOT_LOGGER.equals(name)
-                ? ctx.getLogger(LogManager.ROOT_LOGGER_NAME) : ctx.getLogger(name);
+                ? ctx.getLogger(LogManager.ROOT_LOGGER_NAME)
+                : isAudit
+                        ? ctx.getLogger(domainAuditLoggerName)
+                        : ctx.getLogger(name);
         logger.setLevel(Level.OFF);
         ctx.updateLoggers();
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/PassThroughRewritePolicy.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/PassThroughRewritePolicy.java b/core/logic/src/main/java/org/apache/syncope/core/logic/PassThroughRewritePolicy.java
new file mode 100644
index 0000000..08c6c4e
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/PassThroughRewritePolicy.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+@Plugin(name = "PassThroughRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy",
+        printObject = true)
+public class PassThroughRewritePolicy implements RewritePolicy {
+
+    @Override
+    public LogEvent rewrite(final LogEvent event) {
+        return event;
+    }
+
+    @PluginFactory
+    public static PassThroughRewritePolicy createPolicy() {
+        return new PassThroughRewritePolicy();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
index fd2f1fb..902fa4d 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
@@ -28,6 +28,7 @@ import java.util.Set;
 import org.apache.syncope.common.lib.policy.AccountRuleConf;
 import org.apache.syncope.common.lib.policy.PasswordRuleConf;
 import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.core.logic.AuditAppender;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator;
 import org.apache.syncope.core.persistence.api.dao.AccountRule;
@@ -77,6 +78,8 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
 
     private Map<Class<? extends PasswordRuleConf>, Class<? extends PasswordRule>> passwordRuleClasses;
 
+    private Set<Class<?>> auditAppenderClasses;
+    
     @Override
     public Integer getPriority() {
         return 400;
@@ -103,6 +106,7 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
         reportletClasses = new HashMap<>();
         accountRuleClasses = new HashMap<>();
         passwordRuleClasses = new HashMap<>();
+        auditAppenderClasses = new HashSet<>();
 
         ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
         scanner.addIncludeFilter(new AssignableTypeFilter(JWTSSOProvider.class));
@@ -119,6 +123,7 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
         scanner.addIncludeFilter(new AssignableTypeFilter(PullCorrelationRule.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(Validator.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(NotificationRecipientsProvider.class));
+        scanner.addIncludeFilter(new AssignableTypeFilter(AuditAppender.class));
 
         for (BeanDefinition bd : scanner.findCandidateComponents(getBasePackage())) {
             try {
@@ -207,6 +212,11 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
                 if (NotificationRecipientsProvider.class.isAssignableFrom(clazz) && !isAbstractClazz) {
                     classNames.get(Type.NOTIFICATION_RECIPIENTS_PROVIDER).add(bd.getBeanClassName());
                 }
+
+                if (AuditAppender.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+                    classNames.get(Type.AUDIT_APPENDER).add(clazz.getName());
+                    auditAppenderClasses.add(clazz);
+                }
             } catch (Throwable t) {
                 LOG.warn("Could not inspect class {}", bd.getBeanClassName(), t);
             }
@@ -249,4 +259,9 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
 
         return passwordRuleClasses.get(passwordRuleConfClass);
     }
+
+    @Override
+    public Set<Class<?>> getAuditAppenderClasses() {
+        return auditAppenderClasses;
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerAccessor.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerAccessor.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerAccessor.java
index 47fa990..675fd94 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerAccessor.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerAccessor.java
@@ -30,6 +30,7 @@ import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.persistence.api.dao.LoggerDAO;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.Logger;
+import org.apache.syncope.core.provisioning.java.AuditManagerImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
@@ -57,7 +58,8 @@ public class LoggerAccessor {
             }
         }
         for (Logger syncopeLogger : loggerDAO.findAll(LoggerType.AUDIT)) {
-            syncopeLoggers.put(syncopeLogger.getKey(), syncopeLogger);
+            syncopeLoggers.put(AuditManagerImpl.getDomainAuditEventLoggerName(AuthContextUtils.getDomain(),
+                    syncopeLogger.getKey()), syncopeLogger);
         }
 
         /*
@@ -71,7 +73,8 @@ public class LoggerAccessor {
                 if (syncopeLoggers.containsKey(loggerName)) {
                     logConf.setLevel(syncopeLoggers.get(loggerName).getLevel().getLevel());
                     syncopeLoggers.remove(loggerName);
-                } else if (!loggerName.equals(LoggerType.AUDIT.getPrefix())) {
+                } else if (!loggerName.startsWith(LoggerType.AUDIT.getPrefix()) || !loggerName.startsWith(
+                        AuthContextUtils.getDomain() + "." + LoggerType.AUDIT.getPrefix())) {
                     Logger syncopeLogger = entityFactory.newEntity(Logger.class);
                     syncopeLogger.setKey(loggerName);
                     syncopeLogger.setLevel(LoggerLevel.fromLevel(logConf.getLevel()));
@@ -84,8 +87,9 @@ public class LoggerAccessor {
         /*
          * Foreach SyncopeLogger not found in log4j create a new log4j logger with given name and level.
          */
-        for (Logger syncopeLogger : syncopeLoggers.values()) {
-            LoggerConfig logConf = ctx.getConfiguration().getLoggerConfig(syncopeLogger.getKey());
+        for (Map.Entry<String, Logger> entry : syncopeLoggers.entrySet()) {
+            Logger syncopeLogger = entry.getValue();
+            LoggerConfig logConf = ctx.getConfiguration().getLoggerConfig(entry.getKey());
             logConf.setLevel(syncopeLogger.getLevel().getLevel());
         }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
index 60d02eb..4a24d32 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/LoggerLoader.java
@@ -20,7 +20,9 @@ package org.apache.syncope.core.logic.init;
 
 import java.sql.Connection;
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import javax.sql.DataSource;
 import org.apache.logging.log4j.Level;
@@ -31,13 +33,20 @@ import org.apache.logging.log4j.core.appender.db.ColumnMapping;
 import org.apache.logging.log4j.core.appender.db.jdbc.ColumnConfig;
 import org.apache.logging.log4j.core.appender.db.jdbc.ConnectionSource;
 import org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender;
+import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender;
 import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.core.logic.AuditAppender;
 import org.apache.syncope.core.logic.MemoryAppender;
 import org.apache.syncope.core.provisioning.java.AuditManagerImpl;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.persistence.api.DomainsHolder;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.SyncopeLoader;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
 import org.springframework.jdbc.datasource.DataSourceUtils;
 import org.springframework.stereotype.Component;
 
@@ -50,6 +59,9 @@ public class LoggerLoader implements SyncopeLoader {
     @Autowired
     private LoggerAccessor loggerAccessor;
 
+    @Autowired
+    private ImplementationLookup implementationLookup;
+
     private final Map<String, MemoryAppender> memoryAppenders = new HashMap<>();
 
     @Override
@@ -81,6 +93,7 @@ public class LoggerLoader implements SyncopeLoader {
             setConfiguration(ctx.getConfiguration()).setName("THROWABLE").setPattern("%ex{full}").build()
         };
         ColumnMapping[] columnMappings = new ColumnMapping[0];
+
         for (Map.Entry<String, DataSource> entry : domainsHolder.getDomains().entrySet()) {
             Appender appender = ctx.getConfiguration().getAppender("audit_for_" + entry.getKey());
             if (appender == null) {
@@ -103,6 +116,9 @@ public class LoggerLoader implements SyncopeLoader {
             logConf.setLevel(Level.DEBUG);
             ctx.getConfiguration().addLogger(AuditManagerImpl.getDomainAuditLoggerName(entry.getKey()), logConf);
 
+            // SYNCOPE-1144 For each custom audit appender class add related appenders to log4j logger
+            configureCustomAppenders(entry.getKey(), ctx);
+
             AuthContextUtils.execWithAuthContext(entry.getKey(), new AuthContextUtils.Executable<Void>() {
 
                 @Override
@@ -120,6 +136,69 @@ public class LoggerLoader implements SyncopeLoader {
         return memoryAppenders;
     }
 
+    public void configureCustomAppenders(final String domainName, final LoggerContext ctx) {
+        List<AuditAppender> auditAppenders = auditAppenders(domainName);
+        for (AuditAppender auditAppender : auditAppenders) {
+            for (AuditLoggerName event : auditAppender.getEvents()) {
+                String domainAuditLoggerName =
+                        AuditManagerImpl.getDomainAuditEventLoggerName(domainName, event.toLoggerName());
+                LoggerConfig eventLogConf = ctx.getConfiguration().getLoggerConfig(domainAuditLoggerName);
+                boolean isRootLogConf = LogManager.ROOT_LOGGER_NAME.equals(eventLogConf.getName());
+
+                if (isRootLogConf) {
+                    eventLogConf = new LoggerConfig(domainAuditLoggerName, null, false);
+                }
+                addAppenderToContext(ctx, auditAppender, eventLogConf);
+                eventLogConf.setLevel(Level.DEBUG);
+                if (isRootLogConf) {
+                    ctx.getConfiguration().addLogger(domainAuditLoggerName, eventLogConf);
+                }
+            }
+        }
+    }
+
+    public List<AuditAppender> auditAppenders(final String domainName) throws BeansException {
+        List<AuditAppender> auditAppenders = new ArrayList<>();
+        for (Class<?> clazz : implementationLookup.getAuditAppenderClasses()) {
+            AuditAppender auditAppender;
+            if (ApplicationContextProvider.getBeanFactory().containsSingleton(clazz.getName())) {
+                auditAppender = (AuditAppender) ApplicationContextProvider.getBeanFactory().
+                        getSingleton(clazz.getName());
+            } else {
+                auditAppender = (AuditAppender) ApplicationContextProvider.getBeanFactory().
+                        createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
+                auditAppender.setDomainName(domainName);
+                auditAppender.init();
+            }
+            auditAppenders.add(auditAppender);
+        }
+        return auditAppenders;
+    }
+
+    public void addAppenderToContext(
+            final LoggerContext ctx,
+            final AuditAppender auditAppender,
+            final LoggerConfig eventLogConf) {
+        Appender targetAppender = ctx.getConfiguration().getAppender(auditAppender.getTargetAppenderName());
+        if (targetAppender == null) {
+            targetAppender = auditAppender.getTargetAppender();
+        }
+        targetAppender.start();
+        ctx.getConfiguration().addAppender(targetAppender);
+        if (auditAppender.isRewriteEnabled()) {
+            RewriteAppender rewriteAppender = ctx.getConfiguration().getAppender(auditAppender.
+                    getTargetAppenderName() + "_rewrite");
+            if (rewriteAppender == null) {
+                rewriteAppender = auditAppender.getRewriteAppender();
+            }
+            rewriteAppender.start();
+            ctx.getConfiguration().addAppender(rewriteAppender);
+            eventLogConf.addAppender(rewriteAppender, Level.DEBUG, null);
+        } else {
+            eventLogConf.addAppender(targetAppender, Level.DEBUG, null);
+        }
+    }
+
     private static class DataSourceConnectionSource implements ConnectionSource {
 
         private final DataSource dataSource;

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
index 2d3438f..c510677 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
@@ -43,7 +43,8 @@ public interface ImplementationLookup extends SyncopeLoader {
         PUSH_ACTIONS,
         PULL_CORRELATION_RULE,
         VALIDATOR,
-        NOTIFICATION_RECIPIENTS_PROVIDER;
+        NOTIFICATION_RECIPIENTS_PROVIDER,
+        AUDIT_APPENDER;
 
     }
 
@@ -56,4 +57,6 @@ public interface ImplementationLookup extends SyncopeLoader {
     Class<? extends AccountRule> getAccountRuleClass(Class<? extends AccountRuleConf> accountRuleConfClass);
 
     Class<? extends PasswordRule> getPasswordRuleClass(Class<? extends PasswordRuleConf> passwordRuleConfClass);
+
+    Set<Class<?>> getAuditAppenderClasses();
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
index 4a785ff..1875fb1 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
@@ -75,4 +75,9 @@ public class DummyImplementationLookup implements ImplementationLookup {
         return DefaultPasswordRule.class;
     }
 
+    @Override
+    public Set<Class<?>> getAuditAppenderClasses() {
+        return Collections.emptySet();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditManagerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditManagerImpl.java
index 14180ec..760a9fa 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditManagerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/AuditManagerImpl.java
@@ -45,6 +45,10 @@ public class AuditManagerImpl implements AuditManager {
         return LoggerType.AUDIT.getPrefix() + "." + domain;
     }
 
+    public static String getDomainAuditEventLoggerName(final String domain, final String loggerName) {
+        return domain + "." + loggerName;
+    }
+
     @Override
     public boolean auditRequested(
             final AuditElements.EventCategoryType type,
@@ -118,10 +122,15 @@ public class AuditManagerImpl implements AuditManager {
                 loggerDAO.find(auditEntry.getLogger().toLoggerName());
         if (syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG) {
             Logger logger = LoggerFactory.getLogger(getDomainAuditLoggerName(AuthContextUtils.getDomain()));
+            Logger eventLogger = LoggerFactory.getLogger(getDomainAuditEventLoggerName(AuthContextUtils.getDomain(),
+                    syncopeLogger.getKey()));
+            String serializedAuditEntry = POJOHelper.serialize(auditEntry);
             if (throwable == null) {
-                logger.debug(POJOHelper.serialize(auditEntry));
+                logger.debug(serializedAuditEntry);
+                eventLogger.debug(POJOHelper.serialize(auditEntry));
             } else {
-                logger.debug(POJOHelper.serialize(auditEntry), throwable);
+                logger.debug(serializedAuditEntry, throwable);
+                eventLogger.debug(serializedAuditEntry, throwable);
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index ff91c41..a14ba2f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -508,7 +508,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         for (PropagationActions action : actions) {
             action.after(task, execution, afterObj);
         }
-
+        // SYNCOPE-1136
         String anyTypeKind = task.getAnyTypeKind() == null ? "realm" : task.getAnyTypeKind().name().toLowerCase();
         String operation = task.getOperation().name().toLowerCase();
         boolean notificationsAvailable = notificationManager.notificationsAvailable(

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
index 011364c..cac936d 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
@@ -75,4 +75,9 @@ public class DummyImplementationLookup implements ImplementationLookup {
         return DefaultPasswordRule.class;
     }
 
+    @Override
+    public Set<Class<?>> getAuditAppenderClasses() {
+        return Collections.emptySet();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
index 3ea2715..81f94b9 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
@@ -146,6 +146,11 @@ public class ITImplementationLookup implements ImplementationLookup {
             classNames = new HashSet<>();
             classNames.add(TestNotificationRecipientsProvider.class.getName());
             put(Type.NOTIFICATION_RECIPIENTS_PROVIDER, classNames);
+
+            classNames = new HashSet<>();
+            classNames.add(TestFileRewriteAuditAppender.class.getName());
+            classNames.add(TestFileAuditAppender.class.getName());
+            put(Type.AUDIT_APPENDER, classNames);
         }
     };
 
@@ -256,4 +261,12 @@ public class ITImplementationLookup implements ImplementationLookup {
 
         return PASSWORD_RULE_CLASSES.get(passwordRuleConfClass);
     }
+
+    @Override
+    public Set<Class<?>> getAuditAppenderClasses() {
+        Set<Class<?>> classes = new HashSet<>();
+        classes.add(TestFileRewriteAuditAppender.class);
+        classes.add(TestFileAuditAppender.class);
+        return classes;
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java
new file mode 100644
index 0000000..9a192df
--- /dev/null
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java
@@ -0,0 +1,80 @@
+/*
+ * 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.fit.core.reference;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.logging.log4j.core.appender.SyslogAppender;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.net.Facility;
+import org.apache.logging.log4j.core.net.Protocol;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.core.logic.ConnectorLogic;
+import org.apache.syncope.core.logic.DefaultRewriteAuditAppender;
+import org.apache.syncope.core.logic.ResourceLogic;
+
+public class SyslogRewriteAuditAppender extends DefaultRewriteAuditAppender {
+
+    @Override
+    public Set<AuditLoggerName> getEvents() {
+        Set<AuditLoggerName> events = new HashSet<>();
+        events.add(
+                new AuditLoggerName(
+                        AuditElements.EventCategoryType.LOGIC,
+                        ResourceLogic.class.getSimpleName(),
+                        null,
+                        "update",
+                        AuditElements.Result.SUCCESS));
+        events.add(
+                new AuditLoggerName(
+                        AuditElements.EventCategoryType.LOGIC,
+                        ConnectorLogic.class.getSimpleName(),
+                        null,
+                        "update",
+                        AuditElements.Result.SUCCESS));
+        events.add(
+                new AuditLoggerName(
+                        AuditElements.EventCategoryType.LOGIC,
+                        ResourceLogic.class.getSimpleName(),
+                        null,
+                        "delete",
+                        AuditElements.Result.SUCCESS));
+        return events;
+    }
+
+    @Override
+    public void initTargetAppender() {
+        targetAppender = SyslogAppender.newSyslogAppenderBuilder()
+                .withName(getTargetAppenderName())
+                .withHost("localhost")
+                .withPort(514)
+                .withProtocol(Protocol.UDP)
+                .withLayout(
+                        PatternLayout.newBuilder().withPattern("%d{ISO8601} %-5level %logger - %msg%n").build())
+                .setFacility(Facility.LOCAL1)
+                .build();
+    }
+
+    @Override
+    public String getTargetAppenderName() {
+        return "audit_for_" + domainName + "_syslog";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java
new file mode 100644
index 0000000..cffc8f7
--- /dev/null
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java
@@ -0,0 +1,86 @@
+/*
+ * 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.fit.core.reference;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.FileAppender;
+import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.core.logic.ConnectorLogic;
+import org.apache.syncope.core.logic.DefaultAuditAppender;
+import org.apache.syncope.core.logic.ResourceLogic;
+
+public class TestFileAuditAppender extends DefaultAuditAppender {
+
+    @Override
+    public Set<AuditLoggerName> getEvents() {
+        Set<AuditLoggerName> events = new HashSet<>();
+        events.add(
+                new AuditLoggerName(
+                        AuditElements.EventCategoryType.LOGIC,
+                        ResourceLogic.class.getSimpleName(),
+                        null,
+                        "create",
+                        AuditElements.Result.SUCCESS));
+        events.add(
+                new AuditLoggerName(
+                        AuditElements.EventCategoryType.LOGIC,
+                        ConnectorLogic.class.getSimpleName(),
+                        null,
+                        "update",
+                        AuditElements.Result.SUCCESS));
+        return events;
+    }
+
+    @Override
+    public void initTargetAppender() {
+        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+        // get log file path from existing file appender
+        RollingRandomAccessFileAppender mainFile =
+                (RollingRandomAccessFileAppender) ctx.getConfiguration().getAppender("mainFile");
+
+        String pathPrefix = mainFile == null
+                ? System.getProperty("user.dir") + StringUtils.replace("/target/log", "/", File.separator)
+                + File.separator
+                : StringUtils.replace(mainFile.getFileName(), "core.log", StringUtils.EMPTY);
+
+        targetAppender = FileAppender.newBuilder()
+                .withName(getTargetAppenderName())
+                .withAppend(true)
+                .withFileName(pathPrefix + getTargetAppenderName() + ".log")
+                .withLayout(
+                        PatternLayout.newBuilder()
+                                .withPattern("%d{HH:mm:ss.SSS} %-5level %logger - %msg%n")
+                                .build()).
+                build();
+    }
+
+    @Override
+    public String getTargetAppenderName() {
+        return "audit_for_" + domainName + "_norewrite_file";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java
new file mode 100644
index 0000000..932ce56
--- /dev/null
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java
@@ -0,0 +1,84 @@
+/*
+ * 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.fit.core.reference;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.FileAppender;
+import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.core.logic.DefaultRewriteAuditAppender;
+import org.apache.syncope.core.logic.ResourceLogic;
+
+public class TestFileRewriteAuditAppender extends DefaultRewriteAuditAppender {
+
+    @Override
+    public Set<AuditLoggerName> getEvents() {
+        Set<AuditLoggerName> events = new HashSet<>();
+        events.add(
+                new AuditLoggerName(
+                        AuditElements.EventCategoryType.LOGIC,
+                        ResourceLogic.class.getSimpleName(),
+                        null,
+                        "update",
+                        AuditElements.Result.SUCCESS));
+        return events;
+    }
+
+    @Override
+    public void initTargetAppender() {
+        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+        // get log file path from existing file appender
+        RollingRandomAccessFileAppender mainFile =
+                (RollingRandomAccessFileAppender) ctx.getConfiguration().getAppender("mainFile");
+
+        String pathPrefix = mainFile == null
+                ? System.getProperty("user.dir") + StringUtils.replace("/target/log", "/", File.separator)
+                + File.separator
+                : StringUtils.replace(mainFile.getFileName(), "core.log", StringUtils.EMPTY);
+
+        targetAppender = FileAppender.newBuilder()
+                .withName(getTargetAppenderName())
+                .withAppend(true)
+                .withFileName(pathPrefix + getTargetAppenderName() + ".log")
+                .withLayout(
+                        PatternLayout.newBuilder()
+                                .withPattern("%d{HH:mm:ss.SSS} %-5level %logger - %msg%n")
+                                .build())
+                .build();
+    }
+
+    @Override
+    public String getTargetAppenderName() {
+        return "audit_for_" + domainName + "_file";
+    }
+
+    @Override
+    public RewritePolicy getRewritePolicy() {
+        return TestRewritePolicy.createPolicy();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestRewritePolicy.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestRewritePolicy.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestRewritePolicy.java
new file mode 100644
index 0000000..641f203
--- /dev/null
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestRewritePolicy.java
@@ -0,0 +1,46 @@
+/*
+ * 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.fit.core.reference;
+
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.status.StatusLogger;
+
+@Plugin(name = "TestRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy",
+        printObject = true)
+public class TestRewritePolicy implements RewritePolicy {
+
+    protected static final StatusLogger LOGGER = StatusLogger.getLogger();
+
+    @Override
+    public LogEvent rewrite(final LogEvent event) {
+        return new Log4jLogEvent.Builder(event).setMessage(new SimpleMessage("This is a static test message")).build();
+    }
+
+    @PluginFactory
+    public static TestRewritePolicy createPolicy() {
+        return new TestRewritePolicy();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/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 5720785..9bc6ed6 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
@@ -24,17 +24,27 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
 import java.text.ParseException;
 import java.util.List;
+import java.util.Properties;
 import javax.ws.rs.core.Response;
 import javax.xml.ws.WebServiceException;
 import org.apache.commons.collections4.IterableUtils;
 import org.apache.commons.collections4.Predicate;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeClientException;
 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.to.ConnInstanceTO;
+import org.apache.syncope.common.lib.to.ConnPoolConfTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType;
@@ -43,11 +53,13 @@ import org.apache.syncope.common.lib.types.LoggerLevel;
 import org.apache.syncope.common.lib.types.LoggerType;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.rest.api.LoggerWrapper;
+import org.apache.syncope.core.logic.ConnectorLogic;
 import org.apache.syncope.core.logic.ReportLogic;
 import org.apache.syncope.core.logic.ResourceLogic;
 import org.apache.syncope.core.logic.GroupLogic;
 import org.apache.syncope.core.logic.UserLogic;
 import org.apache.syncope.fit.AbstractITCase;
+import org.junit.Assert;
 import org.junit.Test;
 
 public class LoggerITCase extends AbstractITCase {
@@ -270,4 +282,85 @@ public class LoggerITCase extends AbstractITCase {
         assertNotNull(userLogic);
         assertEquals(1, IterableUtils.frequency(userLogic.getEvents(), "create"));
     }
+
+    @Test
+    public void testCustomAuditAppender() throws IOException, InterruptedException {
+        InputStream propStream = null;
+        try {
+            Properties props = new Properties();
+            propStream = getClass().getResourceAsStream("/core-test.properties");
+            props.load(propStream);
+
+            final String auditFilePath = props.getProperty("test.log.dir") + File.separator
+                    + "audit_for_Master_file.log";
+            final String auditNoRewriteFilePath = props.getProperty("test.log.dir") + File.separator
+                    + "audit_for_Master_norewrite_file.log";
+            // 1. Enable audit for resource update -> catched by FileRewriteAuditAppender
+            AuditLoggerName auditLoggerResUpd = new AuditLoggerName(
+                    EventCategoryType.LOGIC,
+                    ResourceLogic.class.getSimpleName(),
+                    null,
+                    "update",
+                    AuditElements.Result.SUCCESS);
+
+            LoggerTO loggerTOUpd = new LoggerTO();
+            loggerTOUpd.setKey(auditLoggerResUpd.toLoggerName());
+            loggerTOUpd.setLevel(LoggerLevel.DEBUG);
+            loggerService.update(LoggerType.AUDIT, loggerTOUpd);
+            // 2. Enable audit for connector update -> NOT catched by FileRewriteAuditAppender
+            AuditLoggerName auditLoggerConnUpd = new AuditLoggerName(
+                    EventCategoryType.LOGIC,
+                    ConnectorLogic.class.getSimpleName(),
+                    null,
+                    "update",
+                    AuditElements.Result.SUCCESS);
+
+            LoggerTO loggerTOConnUpd = new LoggerTO();
+            loggerTOConnUpd.setKey(auditLoggerConnUpd.toLoggerName());
+            loggerTOConnUpd.setLevel(LoggerLevel.DEBUG);
+            loggerService.update(LoggerType.AUDIT, loggerTOConnUpd);
+
+            // 3. check that resource update is transformed and logged onto an audit file.
+            ResourceTO resource = resourceService.read(RESOURCE_NAME_CSV);
+            assertNotNull(resource);
+            resource.setPropagationPriority(100);
+            resourceService.update(resource);
+
+            ConnInstanceTO connector = connectorService.readByResource(RESOURCE_NAME_CSV, null);
+            assertNotNull(connector);
+            connector.setPoolConf(new ConnPoolConfTO());
+            connectorService.update(connector);
+
+            File auditTempFile = new File(auditFilePath);
+            // check audit_for_Master_file.log, it should contain only a static message
+            String auditLog = FileUtils.readFileToString(auditTempFile, "UTF-8");
+
+            Assert.assertTrue(StringUtils.contains(auditLog,
+                    "DEBUG Master.syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]"
+                    + " - This is a static test message"));
+            File auditNoRewriteTempFile = new File(auditNoRewriteFilePath);
+            // check audit_for_Master_file.log, it should contain only a static message
+            String auditLogNoRewrite = FileUtils.readFileToString(auditNoRewriteTempFile, "UTF-8");
+
+            Assert.assertFalse(StringUtils.contains(auditLogNoRewrite,
+                    "DEBUG Master.syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]"
+                    + " - This is a static test message"));
+
+            // clean audit_for_Master_file.log
+            FileUtils.writeStringToFile(auditTempFile, StringUtils.EMPTY, "UTF-8");
+            loggerService.delete(LoggerType.AUDIT, "syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]");
+
+            resource = resourceService.read(RESOURCE_NAME_CSV);
+            assertNotNull(resource);
+            resource.setPropagationPriority(200);
+            resourceService.update(resource);
+
+            // check that nothing has been written to audit_for_Master_file.log
+            assertTrue(StringUtils.isEmpty(FileUtils.readFileToString(auditTempFile, "UTF-8")));
+        } catch (IOException e) {
+            fail("Unable to read/write log files" + e.getMessage());
+        } finally {
+            IOUtils.closeQuietly(propStream);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/fit/core-reference/src/test/resources/core-test.properties
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/resources/core-test.properties b/fit/core-reference/src/test/resources/core-test.properties
index f4e4d32..2f523e9 100644
--- a/fit/core-reference/src/test/resources/core-test.properties
+++ b/fit/core-reference/src/test/resources/core-test.properties
@@ -16,3 +16,4 @@
 # under the License.
 test.csv.src=${project.build.directory}/test-classes/test.csv
 test.csv.dst=${test.csvdir.path}/test.csv
+test.log.dir=${log.directory}

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/src/main/asciidoc/reference-guide/concepts/audit.adoc
----------------------------------------------------------------------
diff --git a/src/main/asciidoc/reference-guide/concepts/audit.adoc b/src/main/asciidoc/reference-guide/concepts/audit.adoc
index 3b2b5d8..78c38d3 100644
--- a/src/main/asciidoc/reference-guide/concepts/audit.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/audit.adoc
@@ -19,9 +19,10 @@
 === Audit
 
 The audit feature allows to capture <<audit-events,events>> occurring within the <<core>> and to log relevant information
-about them as entries into the `SYNCOPEAUDIT` table of the internal storage.
+about them. By default events are logged as entries into the `SYNCOPEAUDIT` table of the internal storage,
+but can also be logged on some additional Log4j2 appenders defined through simple customization mechanisms.
 
-Once events are reported in the table above, they can be used as input for external tools.
+Once events are reported, they can be used as input for external tools.
 
 [TIP]
 ====
@@ -32,3 +33,48 @@ An example of how audit entries can be extracted for reporting is shown by the <
 
 The information provided for <<notification-events,notification events>> is also valid for audit events, including examples -
 except for the admin console <<console-configuration-audit,tooling>>, which is naturally distinct.
+
+==== Audit Customization
+
+As mentioned above, events are, basically, logged in a database table, but this behavior can be extended through 
+`AuditAppender` interface implementation which allows an user to define additional logging supports that we address 
+as appenders.
+
+Appender is a Log4j entity that allows to write whatever log message on different destinations (file, queues, syslog, 
+other appenders, etc.). Moreover it provides the ability to edit (rewrite) the message, that is flowing through appenders,
+in order to customize information.
+Audit customization relies on Log4j appenders. To implement a custom audit appender an user just needs to extend to basic 
+classes:
+
+. DefaultAuditAppender
+. DefaultRewriteAuditAppender
+
+The first is intended to add custom appender without any rewrtining of the message, the second allows message rewriting.
+
+What is needed to implement a well formed appender:
+ 
+. Events: a set of events to which the appender is bound. Appender will log only if one of those events occurs.
+. Target Appender: the Log4j appender that writes message somewhere. See 
+  https://github.com/andrea-patricelli/syncope/blob/2_0_X/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java[TestFileRewriteAuditAppender^] or 
+  https://github.com/andrea-patricelli/syncope/blob/2_0_X/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java[TestFileAuditAppender^].
+. Rewrite policy (if needed): in case of rewrite enabled a rewrite policy should be defined, by implementing Log4j 
+  `RewritePolicy` interface. Some examples are 
+  https://github.com/andrea-patricelli/syncope/blob/2_0_X/core/logic/src/main/java/org/apache/syncope/core/logic/PassThroughRewritePolicy.java[PassThroughRewritePolicy^] 
+  and https://github.com/andrea-patricelli/syncope/blob/2_0_X/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestRewritePolicy.java[TestRewritePolicy^].
+  If no rewrite policy is specified `PassThroughRewritePolicy` will be used.
+
+[TIP]
+====
+Be careful while assigning names to the appenders. The name of the target appender should be unique and should depend on
+the domain. 
+A best practice is to assign different names to the appenders in order to avoid names collisions and strange behavior of
+the logging framework.
+====
+
+===== How custom appenders work
+
+An appender is bound to specific events. While enabling audit on some event, if that event is "catched" also by the custom
+appender, it automatically activates. Once the audit is enabled the same audit message will be logged by the 
+default audit appender and all the extensions bound to those events. While disabling audit all audit extensions are 
+disabled.
+To enable an audit extension an user just needs to implement his custom `AuditAppender` in the sources, build application and deploy.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/dc4b83ef/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc
----------------------------------------------------------------------
diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc
index f73e360..da85daa 100644
--- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc
+++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/customization.adoc
@@ -694,3 +694,9 @@ Moreover, `defaultValues` do not overwrite any existing value.
 For example, the http://www.chorevolution.eu/[CHOReVOLUTION^] IdM - based on Apache Syncope - provides
 https://gitlab.ow2.org/chorevolution/syncope/tree/master/ext/choreography[an extension^]
 for managing via the <<core>> and visualizing via the <<admin-console-component>> the running choreography instances.
+
+[[audit-customization]]
+==== Audit Extensions
+
+<<audit>> by default, if enabled, logs on a specific database table, though this functionality could be extended to log 
+also on different supports (file, queue, syslog, etc.).


Mime
View raw message