How to log changes of ActiveRecords?

A simple and effective way to keep track what your users are doing within your application is to log their activities related to database modifications. You can log whenever a record was inserted, changed or deleted, and also when and by which user this was done. For a [CActiveRecord] Model you could use a behavior for this purpose. This way you will be able to add log functionality to ActiveRecords very easily.

First of all you have to create a table for the log-lines in the database. Here is an example (MySQL):

[sql]
CREATE TABLE ActiveRecordLog (
  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  description VARCHAR(255) NULL,
  action VARCHAR(20) NULL,
  model VARCHAR(45) NULL,
  idModel INTEGER UNSIGNED NULL,
  field VARCHAR(45) NULL,
  creationdate TIMESTAMP NOT NULL,
  userid VARCHAR(45) NULL,
  PRIMARY KEY(id)
)
TYPE=InnoDB;

The next step would be to create the corresponding Model using the YII Shell Tool:

model ActiveRecordLog

If you would like to have basic crud functionality created, you should also type:

crud ActiveRecordLog

To be able to log the changes we will use a behavior class. So you have to create one (i.e. ActiveRecordLogableBehavior) and store it somewhere in your application directory (i.e. \protected\behaviors). The behavior class should extend [CActiveRecordBehavior] as we want to work with ActiveRecords here.

class ActiveRecordLogableBehavior extends CActiveRecordBehavior
{
	private $_oldattributes = array();

	public function afterSave($event)
	{
		if (!$this->Owner->isNewRecord) {

			// new attributes
			$newattributes = $this->Owner->getAttributes();
			$oldattributes = $this->getOldAttributes();

			// compare old and new
            foreach ($newattributes as $name => $value) {
				if (!empty($oldattributes)) {
					$old = $oldattributes[$name];
				} else {
					$old = '';
				}

				if ($value != $old) {
					//$changes = $name . ' ('.$old.') => ('.$value.'), ';

					$log=new ActiveRecordLog;
					$log->description=	'User ' . Yii::app()->user->Name 
                                            . ' changed ' . $name . ' for ' 
                                            . get_class($this->Owner) 
                                            . '[' . $this->Owner->getPrimaryKey() .'].';
					$log->action=		'CHANGE';
					$log->model=		get_class($this->Owner);
					$log->idModel=		$this->Owner->getPrimaryKey();
					$log->field=		$name;
					$log->creationdate=	new CDbExpression('NOW()');
					$log->userid=		Yii::app()->user->id;
					$log->save();
				}
            }
		} else {
			$log=new ActiveRecordLog;
			$log->description=	'User ' . Yii::app()->user->Name 
                                    . ' created ' . get_class($this->Owner) 
                                    . '[' . $this->Owner->getPrimaryKey() .'].';
			$log->action=		'CREATE';
			$log->model=		get_class($this->Owner);
			$log->idModel=		$this->Owner->getPrimaryKey();
			$log->field=		'';
			$log->creationdate= new CDbExpression('NOW()');
			$log->userid=		Yii::app()->user->id;
			$log->save();
		}
	}

	public function afterDelete($event)
	{
		$log=new ActiveRecordLog;
		$log->description=	'User ' . Yii::app()->user->Name . ' deleted ' 
                                . get_class($this->Owner) 
                                . '[' . $this->Owner->getPrimaryKey() .'].';
		$log->action=		'DELETE';
		$log->model=		get_class($this->Owner);
		$log->idModel=		$this->Owner->getPrimaryKey();
		$log->field=		'';
		$log->creationdate= new CDbExpression('NOW()');
		$log->userid=		Yii::app()->user->id;
		$log->save();
	}

	public function afterFind($event)
	{
		// Save old values
		$this->setOldAttributes($this->Owner->getAttributes());
	}

	public function getOldAttributes()
	{
		return $this->_oldattributes;
	}

	public function setOldAttributes($value)
	{
		$this->_oldattributes=$value;
	}
}

The behavior class uses the ActiveRecordLog Model to store the log lines into the database. It will log a line each time a record is inserted or deleted. It will also log a line for each field which is changed.

In order to make an ActiveRecord Model use this behavior, you have to add the following code to the Model class:

public function behaviors()
{
    return array(
        // Classname => path to Class
        'ActiveRecordLogableBehavior'=>
            'application.behaviors.ActiveRecordLogableBehavior',
    );
}

Of course this simple example could be enhanced:

  • support for mult-column primary keys
  • savethe attributeLabels instead of the field names
  • make description customizable
  • and so on...
31 1
47 followers
Viewed: 74 374 times
Version: 1.1
Category: Tutorials
Tags: Logging
Written by: pfth
Last updated by: Yang He
Created on: Feb 13, 2009
Last updated: 12 years ago
Update Article

Revisions

View all history

Related Articles