The Audit Logging plugin can add simple 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 in a fashion that is similar in semantics to a database trigger. Support for the following closures are added: onSave, onDelete, and onChange which each give access to before and after states of the object so you may write business logic to handle specific types of changes to the object.
The Audit Logging Plugin provides tracking of database data changes on a row and column level this is known as change audit logging and should not be confused with security audit logging or with profiling. The development of this plugin was inspired by the needs of DBAs to be able to reconstruct data level changes over time. It does not address security or security auditing. For more information on this type of logging, see Audit Log and Hibernate Audit Logging.
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.
Conceptually the "on" event handler occurs in the middle of the event, this is not technically possible since Hibernate exists above the database layer, we are mimicking the semantic behavior inside in this plugin. If you have designed a solution with the use of triggers but either need the trigger to be portable to other databases or do not have DBA resources this plugin is for you.
It should be noted that any exceptions you throw from inside the triggers you write using this plugin will invalidate any session firing them. That means you should be careful to make as simple and easily debuggable triggers as possible using this plugin. You should in fact find ways to decouple the trigger from business logic as this is best practice no matter how you implement a trigger.
Installation
| Code Block |
|---|
$ grails install-plugin audit-logging |
Usage
You can use the grails-audit-logging plugin in several ways. First, in a domain class...
| Code Block |
|---|
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. Additionally you may use the optional event handlers which I refer to as "triggers".
Examples:
| Code Block |
|---|
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 triggers (event handlers). You would do this by specifying:
| Code Block |
|---|
static auditable = [handlersOnly:true] |
... with handlersOnly:true specified no AuditLogEvents will be persisted to the database and only the event handlers will be called.
As of version 0.3 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:
| Code Block |
|---|
auditLog {
actorKey = 'userPrincipal.name'
}
|
...or alternately...
| Code Block |
|---|
auditLog {
actorKey = 'userPrincipal.id'
}
|
... if you prefer to log the user's id.
If you are using a custom authentication system in your controller that puts the user data into the session you can set up the actorKey to work with this data instead...
In Config.groovy
| Code Block |
|---|
auditLog {
actorKey = 'session.username'
}
|
... or if your user is similar to the simple authentication user object in the tutorials...
in Config.groovy
| Code Block |
|---|
auditLog {
actorKey = 'session.user.name'
}
|
... finally 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:
| Code Block |
|---|
import edu.yale.its.tp.cas.client.filter.CASFilter
auditLog {
username = CASFilter.CAS_FILTER_USER
}
|
... and the audit_log table will have a record of which user and what controller triggered the hibernate event.
As of version 0.4 you may also specify columns to ignore...
| Code Block |
|---|
static auditable = [ignore:['version','lastUpdated','myField']] |
You may use this in concert with the other configuration parameters like so...
| Code Block |
|---|
static auditable = [handlersOnly:true,ignore:['version','lastUpdated','myField']] |
Changes to the fields 'version' and 'lastUpdated' will be ignored by default. If you wish to log these columns as well you may specify an ignore configuration that does not mention them. Like so...
| Code Block |
|---|
static auditable = [ignore:[]] |
... which means log everything.
Known Issues
Use of the event handlers in objects that are serialized in a WebFlow context can cause the exception:
| Code Block |
|---|
builder.ClosureInvokingAction Exception occured invoking flow action: null |
... mark the closure as a transient to avoid this...
| Code Block |
|---|
transient onChange = {
// ... code ...
}
|
Future Release Plan
User requested features include:
- better debugging and trace from execeptions thrown from inside triggers
- audit facade pattern option
- audit object model generator
- make master audit log optional
- decouple the triggers from audit logging so triggers can be used independently
- support for alternative audit log recording models
JIRA
| JIRA Issues | ||||
|---|---|---|---|---|
|