Simple Audit Log using Grails and Hibernate event listeners
Providing following functionality 1. Loging Insert, Update, Delete of objects 2. Logging Login, Logout events 3. Logging Login Attempt failed event 4. Logging which screen is accessed 1. Create AuditLog domain.
class AuditLog {
    static transient escapeLogAction = true
    User user
    AuditLogType auditLogType // To find which type of action it is. like login, logout, screen access etc
    String value
    Date timeStamp
    String IPAddress
    Date dateCreated
    Date lastUpdated
    AuditLogSource source // To find what is the source of request. like web or system. Some times we run the cron jobs to insert the data. in that case it is "System"
    static constraints = {
        user (nullable: true)
        IPAddress (nullable: true)
        source (nullable: true)
    }
}
2. Create AuditLogType enum in src/groovy folder
enum AuditLogType {
    LOG_IN,
    LOG_OUT,
    FAILED_ATTEMPT,
    SCREEN_ACCESS,
    DATA_CHANGE
}
4. Create AuditLogSource enum in src/groovy folder
enum AuditLogSource {
   WEB,
   SYSTEM
}
3. Create AuditLogListener(src/groovy folder) to log insert/update/delete actions )
import org.hibernate.event.PostDeleteEvent
import org.hibernate.event.PostDeleteEventListener
import org.hibernate.event.PostInsertEvent
import org.hibernate.event.PostInsertEventListener
import org.hibernate.event.PostUpdateEvent
import org.hibernate.event.PostUpdateEventListener
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.context.request.RequestAttributes
import org.springframework.web.context.request.RequestContextHolder
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpSession
class AuditLogListener implements PostInsertEventListener, PostUpdateEventListener, PostDeleteEventListener {
    def springSecurityService
    @Override
    void onPostDelete(PostDeleteEvent postDeleteEvent) {
        logAction(postDeleteEvent.entity)
    }
    @Override
    void onPostInsert(PostInsertEvent postInsertEvent) {
        logAction(postInsertEvent.entity)
    }
    @Override
    void onPostUpdate(PostUpdateEvent postUpdateEvent) {
        logAction(postUpdateEvent.entity)
    }
    private void logAction(Object entity) {
 
  //declaring escapeAuditLog = true in a domain will skip the audit log for that domain.
        if (entity.metaClass.hasProperty(entity,'escapeAuditLog') && entity.'escapeAuditLog') {
            return
        }
        HttpServletRequest request = getHttpServletRequest()
        HttpSession session = request?.getSession()
        AuditLog auditLog = new AuditLog()
        auditLog.timeStamp = new Date()
        auditLog.actionType = ActionType.DATA_CHANGE
        auditLog.user = springSecurityService.currentUser
        auditLog.value = entity.getClass().getName()
        auditLog.IPAddress = getRemoteAddress(request)
        auditLog.source = request ? AuditLogSource.WEB : AuditLogSource.SYSTEM
        auditLog.withNewSession {
            auditLog.save(flush: true)
        }
    }
    private HttpServletRequest getHttpServletRequest() {
        RequestAttributes attribs = RequestContextHolder.getRequestAttributes()
        if (attribs instanceof NativeWebRequest) {
            HttpServletRequest request = (HttpServletRequest) ((NativeWebRequest) attribs).getNativeRequest()
            return request
        }
    }
 
 private String getRemoteAddress(HttpServletRequest request) {
        if (!request) return null
        String ipAddress = request.getHeader("X-Forwarded-For")  // X-Forwarded-For: clientIpAddress, proxy1, proxy2
        if (!ipAddress) {
            ipAddress = request.remoteAddr
        }
        return ipAddress.split(",")[0]
    }
}
5. Create LoggingSecurityEventListener(in src/groovy folder) to log the Login, Logout events 
import org.apache.log4j.Logger import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.ApplicationListener import org.springframework.security.authentication.event.AuthenticationSuccessEvent import org.springframework.security.core.Authentication import org.springframework.security.web.authentication.logout.LogoutHandler import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse class LoggingSecurityEventListener implements ApplicationListener6. Create LoginFailedAuthEventListener(in src/groovy folder) to log login failed attempts, LogoutHandler { private static final log = Logger.getLogger(this) def userService @Autowired HttpServletRequest httpServletRequest // Injected and limited to the current thread per usage void onApplicationEvent(AuthenticationSuccessEvent event) { event.authentication.with { def userName = principal.hasProperty('username')?.getProperty(principal) ?: principal log.info("Login success from ${getRemoteAddress(httpServletRequest)} using username $userName") logAction(userName, ActionType.LOG_IN, getRemoteAddress(httpServletRequest)) } } void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { authentication?.with { def username = principal.hasProperty('username')?.getProperty(principal) ?: principal logAction(username, ActionType.LOG_OUT, getRemoteAddress(request)) } } private void logAction(def userName, ActionType actionType, String ipAddress) { AuditLog auditLog = new AuditLog() auditLog.source = ActionLogSource.WEB auditLog.actionType = actionType auditLog.value = userName auditLog.timeStamp = new Date() auditLog.user = User.findByUserName(userName) // modify to get the logged in user auditLog.IPAddress = ipAddress auditLog.save(flush: true) } private String getRemoteAddress(HttpServletRequest request) { if (!request) return null String ipAddress = request.getHeader("X-Forwarded-For") // X-Forwarded-For: clientIpAddress, proxy1, proxy2 if (!ipAddress) { ipAddress = request.remoteAddr } return ipAddress.split(",")[0] } } 
import org.apache.log4j.Level import org.apache.log4j.Logger import org.springframework.context.ApplicationListener import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent class LoginFailedAuthEventListener implements ApplicationListener7. Register all these listners in resources.groovy{ private static final log = Logger.getLogger(this) def actionLogService void onApplicationEvent(AbstractAuthenticationFailureEvent event) { log.setLevel(Level.WARN) def userName = event.authentication.principal log.warn("Failed login attempt from $event.source.details.remoteAddress using username $userName") AuditLog auditLog = new AuditLog() auditLog.source = ActionLogSource.WEB auditLog.actionType = ActionType.FAILED_ATTEMPT auditLog.value = userName auditLog.timeStamp = new Date() auditLog.IPAddress = event.source.details.remoteAddress auditLog.save(flush: true) } } 
import AuditLogListener
import LoggingSecurityEventListener
import LoginFailedAuthEventListener
import org.codehaus.groovy.grails.orm.hibernate.HibernateEventListeners
// Place your Spring DSL code here
beans = {
    loginFailedAuthEventListener(LoginFailedAuthEventListener)
    securityEventListener(LoggingSecurityEventListener)
    auditLogListner(AuditLogListener) {
        springSecurityService = ref("springSecurityService")
    }
    hibernateEventListeners(HibernateEventListeners) {
          listenerMap = ['post-insert':auditLogListner,
                         'post-update':auditLogListner,
                         'post-delete':auditLogListner]
    }
}
8. Create BaseController(in controllers folder) to log Screen Access and extend this controller from every controller 
class BaseController {
    def springSecurityService
    def beforeInterceptor = {
        if (!request.xhr) {
            AuditLog auditLog = new AuditLog()
            auditLog.source = ActionLogSource.WEB
            auditLog.timeStamp = new Date()
            auditLog.actionType = ActionType.SCREEN_ACCESS
            auditLog.user = User.get(springSecurityService.principal.id)
            auditLog.IPAddress = getRemoteAddress(request)
            auditLog.value = actionUri
            auditLog.save(flush: true)
        }
        return true
    }
 
 private String getRemoteAddress(HttpServletRequest request) {
        if (!request) return null
        String ipAddress = request.getHeader("X-Forwarded-For")  // X-Forwarded-For: clientIpAddress, proxy1, proxy2
        if (!ipAddress) {
            ipAddress = request.remoteAddr
        }
        return ipAddress.split(",")[0]
    }
}
Categories: 
Groovy & Grails
