NAME

Audit::DBI - Audit data changes in your code and store searchable log records in a database.

VERSION

Version 1.9.0

SYNOPSIS

use Audit::DBI;

# Create the audit object.
my $audit = Audit::DBI->new(
	database_handle => $dbh,
);

# Create the necessary tables.
$audit->create_tables();

# Record an audit event.
$audit->record(
	event               => $event,
	subject_type        => $subject_type,
	subject_id          => $subject_id,
	event_time          => $event_time,
	diff                => [ $old_structure, $new_structure ],
	search_data         => \%search_data,
	information         => \%information,
	affected_account_id => $account_id,
	file                => $file,
	line                => $line,
);

# Search audit events.
my $audit_events = $audit->review(
	[ search criteria ]
);

To see an example of a search interface for audit events recorded by Audit::DBI, check the examples/ directory of Audit::DBI::TT2.

FORCE OBJECT STRINGIFICATION

When data structures are dumped (for diffs or to store information), it is sometimes desirable to turn some of the objects into strings, for two reasons:

  • First, two output strings can be the same even if the objects aren't, which is common when working with floats.

  • Second, the string version is easier to read than a dump of the object's internal variables.

A good example of this is Math::Currency. To convert those objects to strings, you can use the following:

local $Audit::DBI::FORCE_OBJECT_STRINGIFICATION =
{
	'Math::Currency' => 'bstr',
};

METHODS

new()

Create a new Audit::DBI object.

my $audit = Audit::DBI->new(
	database_handle => $dbh,
);

Parameters:

  • database handle

    Mandatory, a DBI object.

  • memcache

    Optional, a Cache::Memcached or Cache::Memcached::Fast object to use for rate limiting. If not specified, rate-limiting functions will not be available.

record()

Record an audit event along with information on the context and data changed.

$audit->record(
	event               => $event,
	subject_type        => $subject_type,
	subject_id          => $subject_id,
	event_time          => $event_time,
	diff                =>
	[
		$old_structure,
		$new_structure,
	],
	search_data         => \%search_data,
	information         => \%information,
	affected_account_id => $account_id,
	file                => $file,
	line                => $line,
);

Required:

  • event

    The type of action performed (48 characters maximum).

  • subject_type

    Normally, the singular form of the name of a table, such as "object" or "account" or "order".

  • subject_id

    If subject_type is a table, the corresponding record ID.

Optional:

  • diff

    This automatically calculates the differences between the two data structures passed as values for this parameter, and makes a new structure recording those differences.

  • search_data

    A hashref of all the key/value pairs that we may want to be able search on later to find this type of event. You may pass either a scalar or an arrayref of multiple values for each key.

  • information

    Any other useful information (such as user input) to understand the context of this change.

  • account_affected

    The ID of the account to which the data affected during that event where linked to, if applicable.

  • event_time

    Unix timestamp of the time that the event occurred, the default being the current time.

  • file and line

    The filename and line number where the event occurred, the default being the immediate caller of Audit::DBI->record().

Notes:

  • If you want to delay the insertion of audit events (to group them, for performance), subclass Audit::DBI and add a custom insert_event() method.

  • You can specify a custom comparison function to use for comparing leaf nodes in the data structures passed to diff, with the following syntax.

    diff =>
    [
    		$old_structure,
    		$new_structure,
    		comparison_function => sub { ... },
    ]

    See diff_structures() in Audit::DBI::Utils for more information on how to write custom comparison functions.

review()

Return the logged audit events corresponding to the criteria passed as parameter.

my $results = $audit->review(
	ip_ranges   =>
	[
		{
			include => $boolean,
			begin   => $begin,
			end     => $end
		},
		...
	],
	subjects    =>
	[
		{
			include => $boolean,
			type    => $type1,
			ids     => \@id1,
		},
		{
			include => $boolean,
			type    => $type2,
			ids     => \@id2,
		},
		...
	],
	date_ranges =>
	[
		{
			include => $boolean,
			begin   => $begin,
			end     => $end
		},
		...
	],
	values      =>
	[
		{
			include => $boolean,
			name    => $name1,
			values  => \@value1,
		},
		{
			include => $boolean,
			name    => $name2,
			values  => \@value2,
		},
		...
	],
	events      =>
	[
		{
			include => $boolean,
			event   => $event,
		},
		...
	],
	logged_in   =>
	[
		{
			include    => $boolean,
			account_id => $account_id,
		},
		...
	],
	affected    =>
	[
		{
			include    => $boolean,
			account_id => $account_id,
		},
		...
	],
);

All the parameters are optional, but at least one of them is required due to the sheer volume of data this module tends to generate. If multiple parameters are passed, they are additive, i.e. use AND to combine themselves.

  • ip_ranges

    Allows restricting the search to ranges of IPs. Can be given in either dot-decimal notation (n.n.n.n) or as an integer.

  • events

    Allows searching on specific events.

  • subjects

    Allows to search on the subject types and subject IDs passed when calling record(). Multiple subject types can be passed, and for each subject type multiple IDs can be passed, hence the use of an arrayref of hashes for this parameter. Using

    [
    	{
    		type => $type1,
    		ids  => \@id1,
    	},
    	{
    		type => $type2,
    		ids  => \@id2,
    	}
    ]

    would translate into

    (subject_type = '[type1]' AND subject_id IN([ids1]) ) OR (subject_type = '[type2]' AND subject_id IN([ids2]) )

    for searching purposes.

  • date_ranges

    Allows restricting the search to specific date ranges.

  • values

    Searches on the key/values pairs initially passed via 'search_data' to record().

  • logged_in

    Searches on the ID of the account that was logged in at the time of the record() call.

  • affected

    Searches on the ID of the account that was linked to the data that changed at the time of the record() call.

Optional parameters that are not search criteria:

  • database_handle

    A specific database handle to use when searching for audit events. This allows the use of a separate reader database for example, to do expensive search queries. If this parameter is omitted, then the database handle specified when calling new() is used.

  • order_by

    An arrayref of fields and corresponding sort orders to use for sorting. By default, the audit events are sorted by ascending created date.

    order_by =>
    [
    	'created' => 'DESC',
    ]

create_tables()

Create the tables required to store audit events.

$audit->create_tables(
	drop_if_exist => $boolean,      #default 0
	database_type => $database_type #default SQLite
);

ACCESSORS

get_database_handle()

Return the database handle tied to the audit object.

my $database_handle = $audit->_get_database_handle();

get_memcache()

Return the database handle tied to the audit object.

my $memcache = $audit->get_memcache();

INTERNAL METHODS

get_cache()

Get a value from the cache.

my $value = $audit->get_cache( key => $key );

set_cache()

Set a value into the cache.

$audit->set_cache(
	key         => $key,
	value       => $value,
	expire_time => $expire_time,
);

insert_event()

Insert an audit event in the database.

my $audit_event = $audit->insert_event( \%data );

Important: note that this is an internal function that record() calls. You should be using record() instead. What you can do with this function is to subclass it if you need to extend/change how events are inserted, for example:

  • if you want to stash it into a register_cleanup() when you're making the all in Apache context (so that audit calls don't slow down the main request);

  • if you want to insert extra information.

BUGS

Please report any bugs or feature requests through the web interface at https://github.com/guillaumeaubert/Audit-DBI/issues/new. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Audit::DBI

You can also look for information at:

AUTHOR

Guillaume Aubert, <aubertg at cpan.org>.

CONTRIBUTORS

ACKNOWLEDGEMENTS

I originally developed this project for ThinkGeek (http://www.thinkgeek.com/). Thanks for allowing me to open-source it!

COPYRIGHT & LICENSE

Copyright 2010-2017 Guillaume Aubert.

This code is free software; you can redistribute it and/or modify it under the same terms as Perl 5 itself.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file for more details.