Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Migrating logging calls in plugins: Difference between revisions

From MoodleDocs
No edit summary
Line 1: Line 1:
This document is aimed to assist developers in replacing existing '''add_to_log()''' calls in the plugin with the events. This '''can''' be implemented in Moodle 2.6 and developers will be '''required''' to convert the existing code in 2.7.
This document is aimed to assist developers in replacing existing '''add_to_log()''' and '''events_trigger()''' calls in the plugin with the events. This '''can''' be implemented in Moodle 2.6 and developers will be '''required''' to convert the existing code in 2.7.


As a quick reminder: [[Event 2]] was introduced in Moodle 2.6, new [[Logging 2]] system is being introduced in Moodle 2.7. Function '''add_to_log()''' becomes deprecated, the existing log table is still present and existing data are intact. This original logging is now called '''legacy logging'''. The new and legacy logging may coexist in the same system, this is not recommended for performance reasons. When replacing the add_to_log() with triggering of an event developer also must ensure that he also generates an entry to the legacy log. It will only be used if legacy log is enabled since it may be enabled on systems that use too many custom reports relying on presence of log table and it would take time to migrate the reports.
As a quick reminder: [[Event 2]] was introduced in Moodle 2.6, new [[Logging 2]] system is being introduced in Moodle 2.7. Function '''add_to_log()''' becomes deprecated, the existing log table is still present and existing data are intact. This original logging is now called '''legacy logging'''. The new and legacy logging may coexist in the same system, this is not recommended for performance reasons. When replacing the add_to_log() with triggering of an event developer also must ensure that he also generates an entry to the legacy log. It will only be used if legacy log is enabled since it may be enabled on systems that use too many custom reports relying on presence of log table and it would take time to migrate the reports.
Line 42: Line 42:


     public function get_legacy_log_data() {
     public function get_legacy_log_data() {
        // Overwrite if you are migrating add_to_log() call.
         return array($this->courseid, 'PLUGINNAME', 'LOGACTION',
         return array($this->courseid, 'PLUGINNAME', 'LOGACTION',
             '...........',
             '...........',
             $this->objectid, $this->contextinstanceid);
             $this->objectid, $this->contextinstanceid);
    }
    public static function get_legacy_eventname() {
        // Overwrite ONLY if you are migrating events_trigger() call.
        return 'MYPLUGIN_OLD_EVENT_NAME';
    }
    protected function get_legacy_eventdata() {
        // Overwrite if you migrating events_trigger() call.
        $data = new \stdClass();
        $data->id = $this->objectid;
        $data->userid = $this->relateduserid;
        return $data;
     }
     }
}
}
Line 159: Line 173:


Most reporting tools will display aggregated logged information so those methods are not likely to be called often. They will only be used by detailed reports such as loglive. At the moment treat get_description() as internal very brief description of the performed action that is used for error recovery like any other system log. It is hardcoded in English but it is possible that in future versions of Moodle (2.8 or even later) it can start using language strings. Those methods can not make DB queries, access global variables, etc. They should return exactly the same result whenever they are called regardless of the environment. For example: course may be renamed, user may be deleted - '''do not retrieve course or user name but instead use their ids'''.
Most reporting tools will display aggregated logged information so those methods are not likely to be called often. They will only be used by detailed reports such as loglive. At the moment treat get_description() as internal very brief description of the performed action that is used for error recovery like any other system log. It is hardcoded in English but it is possible that in future versions of Moodle (2.8 or even later) it can start using language strings. Those methods can not make DB queries, access global variables, etc. They should return exactly the same result whenever they are called regardless of the environment. For example: course may be renamed, user may be deleted - '''do not retrieve course or user name but instead use their ids'''.
=== get_legacy_eventname() and get_legacy_eventdata() ===
Two functions that you need to overwrite if you upgrade events_trigger() calls and want other plugins to still be able to listen to your new events without upgrading their listeners.


=== can_view() ===
=== can_view() ===

Revision as of 06:45, 26 February 2014

This document is aimed to assist developers in replacing existing add_to_log() and events_trigger() calls in the plugin with the events. This can be implemented in Moodle 2.6 and developers will be required to convert the existing code in 2.7.

As a quick reminder: Event 2 was introduced in Moodle 2.6, new Logging 2 system is being introduced in Moodle 2.7. Function add_to_log() becomes deprecated, the existing log table is still present and existing data are intact. This original logging is now called legacy logging. The new and legacy logging may coexist in the same system, this is not recommended for performance reasons. When replacing the add_to_log() with triggering of an event developer also must ensure that he also generates an entry to the legacy log. It will only be used if legacy log is enabled since it may be enabled on systems that use too many custom reports relying on presence of log table and it would take time to migrate the reports.

Quick guide

If you are replacing common add_to_log() calls such as "view" and "view all" in mod/XXX/view.php and mod/XXX/index.php, see below. Otherwise do the following:

Step 1. Choose a name for event

It should have syntax OBJECT_VERB, for example "entry_added", "work_submitted", etc. It does not need to have plugin name in it because it is obvious from the PHP class namespace. See Event 2 for more details on events names.

Define language string for the event name in YOURPLUGINDIR/lang/en/FULLPLUGINNAME.php :

$string['eventSOMETHINGHAPPENED] = 'SOMETHING has HAPPENED';

Step 2. Create event class

It must be located in YOURPLUGINDIR/classes/event/SOMETHING_HAPPENED.php , typical example:

namespace FULLPLUGINNAME\event;
defined('MOODLE_INTERNAL') || die();
class SOMETHING_HAPPENED extends \core\event\base {
    protected function init() {
        $this->data['crud'] = 'c'; // c(reate), r(ead), u(pdate), d(elete)
        $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
        $this->data['objecttable'] = '...';
    }

    public static function get_name() {
        return get_string('eventSOMETHINGHAPPENED', 'FULLPLUGINNAME');
    }

    public function get_description() {
        return "User {$this->userid} has ... ... ... with id {$this->objectid}.";
    }

    public function get_url() {
        return new \moodle_url('....', array());
    }

    public function get_legacy_log_data() {
        // Overwrite if you are migrating add_to_log() call.
        return array($this->courseid, 'PLUGINNAME', 'LOGACTION',
            '...........',
            $this->objectid, $this->contextinstanceid);
    }

    public static function get_legacy_eventname() {
        // Overwrite ONLY if you are migrating events_trigger() call.
        return 'MYPLUGIN_OLD_EVENT_NAME';
    }

    protected function get_legacy_eventdata() {
        // Overwrite if you migrating events_trigger() call.
        $data = new \stdClass();
        $data->id = $this->objectid;
        $data->userid = $this->relateduserid;
        return $data;
    }
}

Step 3. Trigger event instead of add_to_log()

Replace add_to_log() with triggering of event. This is a common example for event inside an activity module:

add_to_log($course->id, 'PLUGINNAME', 'LOGACTION', '...........', $objid, $cmid);

becomes:

$event = \FULLPLUGINNAME\event\SOMETHING_HAPPENED::create(array(
    'objectid' => $objid,
    'context' => context_module::instance($cmid)
));
$event->trigger();

Replacing 'view' event in the modules

This is usually found in mod/PLUGINNAME/view.php (or in lib function included from this file) and indicates that user viewed the module.

Step 1. Choosing the name

The name is already choosen - course_module_viewed - and the language string is defined in core.

Step 2. Defining class

YOURPLUGINDIR/classes/event/course_module_viewed.php :

namespace FULLPLUGINNAME\event;
defined('MOODLE_INTERNAL') || die();
class course_module_viewed extends \core\event\course_module_viewed {
    protected function init() {
        $this->data['objecttable'] = 'PLUGINNAME';
        parent::init();
    }
    // You might need to overwrite get_url() and get_legacy_log_data() if view mode needs to be stored as well.
}

Step 3. Triggering event

This example takes data from $PAGE object but you may as well substite with ids and objects that you fetched:

$event = \FULLPLUGINNAME\event\course_module_viewed::create(array(
    'objectid' => $PAGE->cm->instance,
    'context' => $PAGE->context,
));
$event->add_record_snapshot('course', $PAGE->course);
$event->add_record_snapshot($PAGE->cm->modname, $activityrecord); // You can use $PAGE->activityrecord if you have set it or skip this line if you don't have a record.
$event->trigger();

Replacing 'view all' event in the modules

This is usually found in mod/PLUGINNAME/index.php (or in lib function included from this file) and indicates that user viewed list of modules of this type in the course.

Step 1. Choosing the name

The name is already choosen - course_module_instance_list_viewed - and the language string is defined in core.

Step 2. Defining class

YOURPLUGINDIR/classes/event/course_module_instance_list_viewed.php :

namespace FULLPLUGINNAME\event;
defined('MOODLE_INTERNAL') || die();
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
}

Step 3. Triggering event

$event = \FULLPLUGINNAME\event\course_module_instance_list_viewed::create(array(
    'context' => context_course::instance($course->id)
));
$event->trigger();

What to include in the event

init() and create()

As you noticed in the examples above you can specify additional fields either by overwriting init() method of event or when calling create(). The first way is for properties that are always the same for this event, the second is for dynamic properties.

Usually you need to include fields:

  • context or contextid: required. If you want to hardcode system context, do so in init();
  • crud, edulevel: required, most often are specified in init();
  • objecttable and objectid: used by events that show the change in one record of one table, which will be the case for the most of the events. Since 'objecttable' is always the same it is usually specified in init();
  • relateduserid: only if it is easy to select a single user who is affected by this operation. For example, user who is being graded, user who receives the message, user who is being enrolled, etc. This is NOT a user who performs the action;
  • other: everything else that you may think is important about this event. This field will be serialised and stored by logger. Developers should put here only necessary information for functions get_description(), get_url() and get_legacy_logdata(). This should only contain array or scalar value, can not use objects

Example of information stored in 'other' can be found in event course_module_deleted:

Usually you don't need to include fields:

  • 'userid': user who performs the action, taken from $USER
  • 'courseid': course affected in the operation, it will be taken from context. It may not be specified at all for the events that are not related to a particular course.

See the full list of fields in Event_2#Information_contained_in_events

get_legacy_logdata()

Since this document is a transition guide from add_to_log() to the events, you will most likely need to overwrite this method. Return here a 5-element array which imitates the 5 arguments you used to pass to add_to_log() function. Since 2.7 the get_legacy_logdata() method will only be called if the legacy logging is enabled in legacy logging plugin.

get_description() and get_url()

Most reporting tools will display aggregated logged information so those methods are not likely to be called often. They will only be used by detailed reports such as loglive. At the moment treat get_description() as internal very brief description of the performed action that is used for error recovery like any other system log. It is hardcoded in English but it is possible that in future versions of Moodle (2.8 or even later) it can start using language strings. Those methods can not make DB queries, access global variables, etc. They should return exactly the same result whenever they are called regardless of the environment. For example: course may be renamed, user may be deleted - do not retrieve course or user name but instead use their ids.

get_legacy_eventname() and get_legacy_eventdata()

Two functions that you need to overwrite if you upgrade events_trigger() calls and want other plugins to still be able to listen to your new events without upgrading their listeners.

can_view()

Future of this function is not yet decided. It was intended as a callback allowing each event to determine whether the current user can see the logged event. But in reality checking various capabilities on the big amount of records is a very expensive process. At the moment events do not overwrite this function and it is not used. If you are interested in method future, watch/vote/comment on MDL-44107

add_record_snapshot()

Record snapshot can be added for any DB table related to the event. If it is added it must be exact record from the DB, with correct field types and without additional fields. Also developer must add a record snapshot when he deletes something from database. Record snapshots cannot be used from reports, it is intended for event observers only.

Record snapshots should be added only when you already have an object do not need to perform any additional DB queries to retrieve it. Otherwise omit it, as the record will be retrieved by get_record_snapshot() automatically and only if needed. For performance reasons the snapshots are not guaranteed to contain the exact state at the time of event triggering, it may be actually fetched at any time between the triggering of event and its observation.

Events DONTs

Do not put more information in 'other' than it is needed, for example full DB record for delete/create operation or list of all changed fields in edit operation. If observer is interested in those fields, it can request them by calling get_record_snapshot(). Never include large text fields in event data. Keep the log size reasonable.

Do NOT use $USER, $COURSE, $PAGE, and other global variables when overwriting get_* methods.

Do NOT call $this->get_record_snapshot() inside event class, if you need additional information for internal functions, add it to 'other'.

Do NOT use $this->context inside an event class. Remember that methods get_description() and get_url() may be called on events restored from log. It is possible that the original context does not exist any more. Instead use $this->contextid, $this->contextlevel, $this->contextinstanceid.

Validation and testing

You may notice that the most of existing events in Moodle also have function validate_data() . You can also add it for your own safety to ensure that you don't forget to define all required data when triggering event. Also we highly recommend to cover your events with unittests. Search for files with the names events_test.php in Moodle for examples.