Knowing when users log in and out of your WordPress site is valuable for both security auditing and business reporting. A sudden spike in failed logins may indicate a brute-force attack; unusual login times might reveal a compromised account; and membership sites often need accurate session data to enforce concurrent-login limits or bill by active usage. WordPress fires the wp_login action every time a user successfully authenticates and the wp_logout action when they end their session, giving you clean hooks to record this data without modifying core files. The simplest storage option is a custom database table, but for low-traffic sites you can get away with saving entries to the wp_usermeta table or even writing to a flat log file. If you already have a user-activity plugin installed, check whether it provides these hooks before adding custom code. For sites that need to redirect users after login based on their role, see the related guide on role-based login redirects. You can also combine login logging with fetching logged-in user data to enrich the log records with display names and email addresses. The example below stores login and logout events in a custom wp_usermeta key so no schema change is required, while a second snippet creates a lightweight custom table for high-volume sites.
Problem: You need to record when WordPress users log in and log out, either for security auditing or usage reporting, without installing a full activity-log plugin.
Solution: Add the following code to your functions.php file:
/**
* Log user login events to usermeta.
* Stores last 50 events as a JSON array.
*/
add_action( 'wp_login', 'helloadmin_log_login', 10, 2 );
function helloadmin_log_login( $user_login, $user ) {
$log = get_user_meta( $user->ID, '_helloadmin_login_log', true );
$log = is_array( $log ) ? $log : [];
$log[] = [
'action' => 'login',
'time' => current_time( 'mysql' ),
'ip' => sanitize_text_field( $_SERVER['REMOTE_ADDR'] ?? '' ),
];
// Keep only the last 50 entries
if ( count( $log ) > 50 ) {
$log = array_slice( $log, -50 );
}
update_user_meta( $user->ID, '_helloadmin_login_log', $log );
}
/**
* Log user logout events.
*/
add_action( 'wp_logout', 'helloadmin_log_logout' );
function helloadmin_log_logout() {
$user_id = get_current_user_id();
if ( ! $user_id ) {
return;
}
$log = get_user_meta( $user_id, '_helloadmin_login_log', true );
$log = is_array( $log ) ? $log : [];
$log[] = [
'action' => 'logout',
'time' => current_time( 'mysql' ),
'ip' => sanitize_text_field( $_SERVER['REMOTE_ADDR'] ?? '' ),
];
if ( count( $log ) > 50 ) {
$log = array_slice( $log, -50 );
}
update_user_meta( $user_id, '_helloadmin_login_log', $log );
}
/**
* Retrieve the login log for a given user.
*
* @param int $user_id
* @return array
*/
function helloadmin_get_login_log( $user_id ) {
return get_user_meta( $user_id, '_helloadmin_login_log', true ) ?: [];
}
NOTE: Storing IP addresses is subject to GDPR and similar privacy regulations in many jurisdictions. If you operate in the EU, either anonymise the IP (strip the last octet: preg_replace('/\.\d+$/', '.0', $ip)) or obtain explicit consent before collecting it. For high-traffic sites with many users, storing logs in wp_usermeta will bloat the meta table — create a dedicated wp_login_log table instead and schedule a cleanup cron to delete records older than 90 days using WP-Cron.