Grails Audit Logging Plugin

  • Authors : Shawn Hartsock
17 votes

4% of Grails users

Dependency :
compile ":audit-logging:0.5.4"

Issues

Summary

Installation

$ grails install-plugin audit-logging

That's it.

To set up a Domain Class so that it automatically has its changes logged in the AuditLog just add

static auditable = true

Description

Adds conventions so that when you put

static auditable = true

in a domain class, any changes to the class are automatically logged for you.

The Audit Logging plugin can add Hibernate Events based Audit Logging to a Grails project and it can also add support to domain models for hooking into the hibernate events system. Support for the following closures are added: onSave, onDelete, and onChange. The onChange closure can be used in two ways. First it can be used with no parameters or it can be used with two parameters, oldMap and newMap. The first parameter is a LinkedHashMap representing the old state of the object before the change was applied, the second parameter is a LinkedHashMap representing the state of the object after changes are applied.

NOTE: GORM Events hooks into the ''beforeInsert'' and the ''beforeUpdate'' events which work great for preventing updates but do not work well for ''Audit Logging'' where we would need critical information about the entity that is only available after these actions complete. I have chosen to prefix the handler names with "on" so that they do not conflict with other handler names in other existing plugins.

Compatibility issues

Users of Grails 1.2.x and below should use version 0.5.3 of this plugin. Users of Grails 1.3.x and above should use version 0.5.4 (or greater) of this plugin.

Installation

$ grails install-plugin audit-logging

Usage

You can use the grails-audit-logging plugin in several ways. First, in a domain class…

static auditable = true
enables audit logging using the introduced domain class AuditLogEvent which will record insert, update, and delete events. Update events will be logged in detail with the property name and the old and new values. If you'd like to get values logged on insert and delete events, add following in Config.groovy:
auditLog.verbose = true

Additionally you may use the optional event handlers.

Examples:

class Person {

static auditable = true Long id Long version

String firstName String middleName String lastName

String email

static constraints = { firstName(nullable:true,size:0..60) middleName(nullable:true,size:0..60) lastName(nullable:false,size:1..60) email(email:true) }

def onSave = { println "new person inserted" // may optionally refer to newState map } def onDelete = { println "person was deleted" // may optionally refer to oldState map } def onChange = { oldMap,newMap -> println "Person was changed ..." oldMap.each({ key, oldVal -> if(oldVal != newMap[key]) { println " * $key changed from $oldVal to " + newMap[key] } }) }//*/ }

Alternately you may choose to disable the audit logging and only use the event handlers. You would do this by specifying:
static auditable = [handlersOnly:true]
… with handlersOnly:true specified no AuditLogEvents will be persisted to the database and only the event handlers will be called.

Auditing Current User Information

As of version 0.5 additional configuration can be specified in the Config.groovy file of the project to help log the authenticated user for various security systems. For many security systems the defaults will work fine. To specify a property of the userPrincipal to be logged as the actor name (the person performing the action which triggered the event) in Config.groovy add these lines:

auditLog {
  actorClosure = { request, session ->
      session.user?.username
  } 
}
… if you prefer to log the user's username property.

Spring Security Core 1.0.1 Plugin

Based off of Jorge Aguilera's example, the Spring Security Plugin uses the springSecurityService.
auditLog {
   actorClosure = { request, session ->
       request.applicationContext.springSecurityService.principal?.username
   }
}

as there are some issues using Spring Security Core 1.1.2 Plugin: if You save data from an unprotected URL (configAttribute:IS_AUTHENTICATED_ANONYMOUSLY) the principal is a String-object not a Principal-object. to cope with this behaviour you should change above code to

auditLog {
	actorClosure = { request, session ->
		if (request.applicationContext.springSecurityService.principal instanceof java.lang.String){
			return request.applicationContext.springSecurityService.principal
		}
		def username = request.applicationContext.springSecurityService.principal?.username
		if (SpringSecurityUtils.isSwitched()){
			username = SpringSecurityUtils.switchedUserOriginalUsername+" AS "+username
		}
		return username
	}
}

don't forget to add

import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils

Acegi Plugin

Thanks to Jorge Aguilera for his example on how to integrate with the Acegi plugin:

auditLog {
   actorClosure = { request, session ->
       return request.applicationContext.authenticateService.principal()?.username
   }
}

If you are using a custom authentication system in your controller that puts the user data into the session you can set up the actorClosure to work with your security system instead.

CAS Authentication

For example if you are using a system such as CAS you can specify the CAS user attribute using a special configuration property to get the CAS user name. In Config.groovy just add the following lines to the top of the file:
import edu.yale.its.tp.cas.client.filter.CASFilter
auditLog { 
  actorClosure = { request, session ->
       session?.getAttribute(CASFilter.CAS_FILTER_USER)
  }
}
… and the audit_log table will have a record of which user and what controller triggered the hibernate event.

Shiro Plugin

With Shiro, add the following lines to use the currently logged in user's username:

auditLog {
  actorClosure = { request, session ->
     org.apache.shiro.SecurityUtils.getSubject()?.getPrincipal()
  }
}

Ignore List

It's possible to configure which properties get ignored for auditing.

The default ignore field list is: 'version','lastUpdated' (+) if you want to provide your own ignore list do so by specifying the ignore list like so:

static auditable = [ignore:['version','lastUpdated','myField']]

… if instead you really want to trigger on version and lastUpdated changes you may specify an empty ignore list … like so …

static auditable = [ignore:[]]

Transactional AuditLog events

In Config.groovy you may specify whether the Audit Log uses transactions or not. If set to true then the logger will begin and commit transactions around audit log save events. If set to false then the AuditLog will be persisted without a transaction wrapping its call to save. This setting should not be changed from defaults lightly as it can cause problems in integration testing.
auditLog {
  transactional = true 
}
You are only likely to want to change the defaults if you are working with a transactional database in test and production.

Continuous Integration Server

We are using a Jenkins CI server hosted at https://hartsock.ci.cloudbees.com//job/grails-audit-logging/