Last updated by hartsock 2 years ago
$ 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
Last updated by frissner 5 months ago
Adds conventions so that when you put
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…
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.
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
}
return request.applicationContext.springSecurityService.principal?.username
}
}
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/
Last updated by hartsock 1 year ago
What is Audit Logging?
In this context we are referring to "change audit logging" in the hibernate community this is called Audit Logging see: https://www.hibernate.org/318.html which discusses the interceptor model for Audit Logging in Hibernate. This is the same pattern that the Audit Logging plugin uses internally. Naturally you can roll your own using these same documents.
I am using security plugin X/Y/Z how do I log the username?
This is the question most frequently asked of me. There are so many security systems I can't possibly deal with them all.
As of version 0.5 I have added a feature that lets you specify your own block of code to figure out who is logged in. Your closure will be passed the request and the session objects and you may calculate the logged in (or not) user yourself. Whatever you return from the closure will have "toString" called on it if it is not null.
In the Config.groovy file add the following lines to suit your security plugin.
Acegi:
auditLog {
actorClosure = { request, session ->
return request?.applicationContext.authenticateService.principal()?.username
}
}CAS:
import edu.yale.its.tp.cas.client.filter.CASFilter
auditLog {
actorClosure = { request, session ->
session?.getAttribute(CASFilter.CAS_FILTER_USER)
}
}Custom:
auditLog {
actorClosure = { request, session ->
session?.user?.username
}
}
(note: if there is no request or no session such as during BootStrap these may be null)
As people send in examples I will include them here.
What is the purpose of this plugin? What about Hibernate's transparent write behind which causes events to coalesce?
The Audit Logging plugin sits on top of the Hibernate Event system also used by GORM. It intercepts the same events as GORM but you should be aware that Hibernate uses a technique called "transparent write behind" to minimize the effects of network latency. This means writes are sometimes "coalesced". For example, if a field is updated twice during one transaction then only one update statement will be sent to the database.
I deemed it was outside the scope of the Audit Logging plugin to attempt to deal with coalesced events. Instead, the Audit Log will mirror the effected changes to the database since I cannot anticipate what the reason for the coalesce was and may include security or business logic preventing the change from being written.
If you have a need for tracking the
actions of users then this is not the plugin you are looking for. This plugin is a change audit system.
How do I get the history of an Object?
At present you will need to run something like:
AuditLogEvent.findAllByClassNameAndPersistedObjectId(objectInstance.getClass().getName(),objectInstance.id)
… where the objectInstance is an instance of your Domain class that you wish to get events about.
Do I need to do anything to use the AuditLogEvent domain class in my project?
Grails will inject the domain class from the plugin automatically into your Grails project. You don't need to do anything special. Just start using the AuditLogEvent class as if it were any other domain class in your project.
How do I import AuditLogEvent into my GSP/Controller?
import org.codehaus.groovy.grails.plugins.orm.auditable.AuditLogEvent
Why is the AuditLogEvent class non-relational?
If we related an AuditLogEvent back to your class in some way we would introduce foreign keys which would introduce additional burden to your transactions. In addition due to
http://jira.codehaus.org/browse/GRAILSPLUGINS-391 I was forced to use a separate Hibernate session for audit logging to avoid conflicts with GORM transactions. In addition it is difficult to accomodate every data model that might be created. For these reasons Audit Log data is persisted non-relationally and flattened.
Future development will create an Audit Facade for more relationally consistent audit logs:
http://jira.codehaus.org/browse/GRAILSPLUGINS-1496Why did you create the Audit Logging plugin?
I needed to track and assign blame for changes to a database mapped by GORM. I did not want to complicate the GORM domain by adding the audit logging code to each domain class. It is certainly possible to use GORM to roll your own Audit Logging but I did not wish to clutter my domain model by adding cut and paste audit logging code to each class.
Why did you add the onChange handler to GORM?
I had a project that needed to track not only when an object changed but which fields in the object changed. I needed to trigger business rules only when the variable "foo" changed from the value true to the value false and never when any other values changed.
I would have to either write an SQL Trigger (in PL/SQL for example) or I would have to write something complex to track when the actual event I was interested in had happened. This seemed overly complex when I could simply check the values when they changed using the Hibernate event life cycle.
How do I invoke logging from my actorClosure?
In order to log messages from your actorClosure you must use the closure's delegate. The delegate at run-time will have a logging object injected by Grails. Due to a bug I cannot inject a logging object directly into your closure's context so as a work around you must do you logging like this:
In the Config.groovy file
auditLog {
actorClosure = { request, session ->
delegate.log.info "my actor closure log message"
session.user?.username
}
}What databases is the Audit Logging Plugin compatible with?
As of version 0.5 the Grails Audit Logging plugin it should be compatible with all the databases Hibernate is. Explicitly the plugin has been tested with
Oracle 9i & 10g, MySQL 4.x & 5.0, PostgreSQL 8.x, As well as Grails' internal database.
Versions of the Grails Audit Logging Plugin before version 0.4.1 are known to have problems on transactional databases due to the fact that the AuditLog was persisted by the same database session as the GORM object. This meant that any additional inserts would invalidate the transactions and cause the database to roll back. This problem was fixed in 0.5 by allowing the AuditLog to use its own independent transactions.
What does auditLog.verbose = false in Config.groovy do?
As of version 0.5.1 the AuditLog is verbose by default since this is the least surprising behavior. The verbose AuditLog produces one entry per column on each table logged. Verbose set to false produces only one log indicating "stuff changed" but not precisely which stuff. You may wish to make your audit log shorter if you do not particularly care about each column's values only that someone acted upon a row.
How do I log shorter/longer values in my event log?
As of version 0.5.2 you may alter the size of the old and new value field logged in the AuditEventLog. Every value is stored as a string and your database's default string length dictates the length of the field values we can store. To change this you must manually alter your audit_log_event Database table and then alter the auditLog.TRUNCATE_LENGTH in Config.groovy to reflect this change.
In Config.groovy set
auditLog {
// This can be any value you can set your Database table's old_value and new_value columns to.
TRUNCATE_LENGTH = 128 // set the max length of data that can be logged.
}WARNING: Do not set the value of TRUNCATE_LENGTH beyond the largest string value your database can handle for both the old_value and new_value columns.
How do I tell Audit Logging to IGNORE a property?
Instead of setting your static auditable property to just 'true' you may set it to a Map object that contains properties to configure the Audit Log of the GORM object. One setting you can use is the 'ignore' setting. List out any properties of the object you do not want logged.
static auditable = [ignore:['version','lastUpdated','myField']]
Last updated by admin 2 years ago