Audit Logging Plugin

22 votes

5% of Grails users

Dependency:
compile ":audit-logging:1.0.3"

 Documentation  Source  Issues

Summary

Automatically log change events for domain objects.The Audit Logging plugin additionally adds an instance hook to domain objects that allows you to hangAudit events off of them. The events include onSave, onChange, and onDelete.When called, the event handlers have access to oldObj and newObj definitions that will allow you to take action on what has changed.

Installation

Install as plugin dependency in BuildConfig.groovy (see above).

If you use Grails < 2.0, perform:

$ grails install-plugin audit-logging

Latest stable releases:

  • 1.0.1 (Grails 2.0 or above)
  • 0.5.5.3 (Grails 1.3 or above)
  • 0.5.3 (Grails 1.2 or below)

Description

The Audit Logging plugin adds Grails GORM Events (Hibernate Events when using version < 1.0.0) based Audit Logging capabilities to a Grails project and it also adds support to domain models for hooking into the Grails GORM 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 hook 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. We've 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. If you need to log collection changes, use version 0.5.5 or higher. If you use Grails >= 2.3 we recommend to use 1.0.0 or above.

Starting with version 1.0.0, this plugin is ORM mapper agnostic, so you can use it with the ORM mapper of your choice (Hibernate3, Hibernate4, MongoDB, etc.).

Usage

You can use the grails-audit-logging plugin in several ways.

1. Auditing of 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.

2. Auditing plus domain event handlers

You may use the optional event handlers in your Domain classes.

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] } }) }//*/ }

3. Use event handlers only (no auditing)

You may choose to disable the audit logging and only use the event handlers. You would do this by specifying in your domain class:

static auditable = [handlersOnly:true]

With "handlersOnly:true" specified, no AuditLogEvents will be persisted to the database and only the event handlers will be called.

Configuration

Auditing Current User Information

With 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, e.g. the username) add these lines to Config.groovy:
auditLog {
  actorClosure = { request, session ->
      session.user?.username
  } 
}

Spring Security Core Plugin 1.1.2 or higher

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 use the following Closure definition in Config.groovy:

Import statement:

import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
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
 }
}

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
  }
}

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
 }
}

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 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()
 }
}

Other security systems

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.

Property Ignore List

It's possible to configure which properties get ignored by auditing. The default ignore field list is: ['version','lastUpdated']. If you want to provide your own ignore list specify the ignore list like this:
static auditable = [ignore:['version','lastUpdated','myField']]

If instead you want to trigger on version and lastUpdated changes you may specify an empty ignore list:

static auditable = [ignore:[]]

Verbose mode

You can enable verbose mode. If enabled, column by column change logging in insert and delete events is enabled. Old- and new values are stored in detailed to the audit logging table. Enable verbose logging with:
auditLog {
  verbose = true
}
This setting is disabled by default.
Since Version 0.5.5, logging of associated objects is implemented.

Note: When enabling verbose audit logging, you may get errors if you explicitly flush the session. In this case, do not enable verbose logging. In version 1.0.1 of the plugin, additional closures will be available to disable logging or verbose mode in a code block.

Logging of associated objectIds

Since version 0.5.5, you can log the object-ids of associated objects. Logging will be performed in the format: "[id:<objId>]objDetails". You can enable id-logging with
auditLog {
  logIds = true
}

This setting is disabled by default.

Large value column mode

Since version 0.5.5, you can enable large value columns to log more details. If large value columns are enabled, the column limit is 65534 for oldValue and newValue. This setting is especially designed to work with "verbose" mode. If you enabled the large value column mode, you can set TRUNCATE_LENGTH to a value larger 255 and smaller 65535. Enable large value columns in Config.groovy with:
auditLog {
  largeValueColumnTypes = true // use large column db types for oldValue/newValue.
}
Whenever you change the largeValueColumnTypes flag, you need to update your database schema(s).

Property value masking

Since version 0.5.5, you can configure properties to mask on a per-Domain-Class base. If properties are defined as masked, their values are not stored into the audit log table if verbose mode is enabled. Instead, a mask of "**********" will be logged. By default, "password" properties are masked. You can mask property fields in domain classes like this:
static auditable = [mask:'password','otherField']

Verbose log truncation length

If you enabled verbose mode, you can configure the truncation length of detail information in the oldValue and newValue columns (Default is 255). Configure the TRUNCATE_LENGTH in Config.groovy:
auditLog {
  TRUNCATE_LENGTH = 400 // don't forget to enable largeValueColumnTypes if > 255!
  largeValueColumnTypes = true // use large column db types for oldValue/newValue.
}
When you set TRUNCATE_LENGTH to a value > 255 you must enable largeValueColumnTypes (see above). When you forgot to enable large value column mode while setting TRUNCATE_LENGTH > 255, a warning is logged during startup and the setting will be ignored (255 will be used instead).

Renaming the Audit-Log table name

Since version 0.5.5, you can specify the audit log table name. Default is "audit_log". Change the table name in Config.groovy with:
auditLog {
  tablename = "other_audit_log_name"
}

If you change the tablename, you need to update your database schema(s).

Disable Hibernate caching of AuditLogEvent

You can disable the caching of the AuditLogEvents by specifying:
auditLog {
  cacheDisabled = true
}

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.

Disable auditing by config

Since version 0.5.5.3, you can disable auditing by config. If you disable auditing, event handlers are still triggered but no changes are comitted to the audit log table. This can be used e.g. if you need to bootstrap many objects and want to programmatically disable auditing to not slow down the bootstrap process or if you want to audit log by Enviroment. With version >= 1.0.0 of the plugin, you can disable auditing on a per-datasource base as well.
auditLog {
  disabled = true 
}

Disabling in DataSource.groovy:

dataSource_foo {
    …
    auditLog.disabled = true
}

This setting is "false" by default (auditing is enabled).

nonVerboseDelete logging (since 1.0.1)

If verbose logging is enabled (see above), you can log deletes in a non-verbose manner. This means, only the delete event is logged, but not the properties the deleted object hold prior the deletion.
auditLog {
  nonVerboseDelete = true 
}
This setting is "false" by default (verbosity of deletes depend on the verbose setting).

log full domain class name (since 1.0.3)

By default, only the entity class name is logged. If you want to log the entity full name (including the package name), you can enable full logging.

logFullClassName = true
This setting is "false" by default (entity name is logged).

getAuditLogUri closure (with 1.0.4)

By default, the "uri" field is filled with the request uri which caused the action. You can define a closure "getAuditLogUri" on a per-domain object base to define what should be written to the AuditLog "uri" field.

class User {
  static auditable = true
  static belongsTo = [Client]

def getAuditLogUri = { clientId as String } }

You need to take special care how you obtain the "uri" data in the getAuditLogUri closure. It is recommended to not perform costly calls.

Example configuration

Example Config.groovy configuration with various settings as described above:
// AuditLog Plugin config
auditLog {
 verbose = true // verbosely log all changed values to db
 logIds = true  // log db-ids of associated objects.
 // Note: if you change next 2 properties, you must update your database schema!       
 tablename = 'my_audit' // table name for audit logs.     
 largeValueColumnTypes = true // use large column db types for oldValue/newValue.
 TRUNCATE_LENGTH = 1000
 cacheDisabled = true
 logFullClassName = true
 replacementPatterns = ["local.example.xyz.":""] // replace with empty string.
 actorClosure = { request, session ->
    // SpringSecurity Core 1.1.2
    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
 }
}

Version History

0.5.3
  • Grails <= 1.2
0.5.5
  • Grails >= 1.3
  • Added collections support
  • Added logIds config property
  • Possiblity to specify the auditLog tablename
  • Large auditlog "value" columns support
  • Property value masking support
0.5.5.3
  • Grails >= 1.3
  • Added disabled config property to disable audit logging by config
1.0.0
  • Grails >= 2.0.0
  • ORM agnostic implementation
  • Possibility to disable audit logging on a per-datasource base.
1.0.1
  • Grails >= 2.0.0
  • Propagate the object identifier to onSave() handlers
  • Added nonVerboseDelete config property
1.0.2
  • Grails >= 2.0.0
  • Fix GPAUDITLOGGING-63 (PersistedObjectVersion not logged in update events)
  • Fix GPAUDITLOGGING-56 (Home link does not point to home)
  • Fix GPAUDITLOGGING-66 (Pass exceptions to the caller in closures)
1.0.3
  • Grails >= 2.0.0
  • Fix GPAUDITLOGGING-64 (Duplicate log entries written per configured dataSource)
  • Fix GPAUDITLOGGING-63 (Support logging of full entity name - logFullClassName property)

Continuous Integration Server

We are using CI server hosted at https://travis-ci.org/robertoschwald/grails-audit-logging-plugin

Maintainers

Robert Oschwald, Elmar Kretzer, Aaron Long