2026年5月20日
2026年5月20日
WordPressのユーザー活動ログを記録する方法(プラグインと自作コード)
複数の管理者・編集者がいるサイトでは、「誰が・いつ・何をしたか」の記録(活動ログ、監査ログ)があると、誤操作の特定や不正ログインの発見に役立ちます。WordPress 本体にはこの記録機能が無いため、プラグインを入れるか、フックを使って自分で記録します。
方法1: プラグインを使う
まずはプラグインを検討します。どちらも WordPress.org の公式ディレクトリで公開されている無料プラグインです(2026年11月時点)。
| プラグイン | スラッグ | 特徴 |
|---|---|---|
| WP Activity Log | wp-security-audit-log |
ログイン・ログアウト・ログイン失敗、投稿の変更、ユーザーの作成・権限変更、プラグイン・テーマの操作、設定の変更などを記録。無料版でも記録する項目は制限されず、ログの保存期間も設定できる。メール通知、レポート出力、外部データベースへの保存などは有料版 |
| Simple History | simple-history |
投稿・メディア・ユーザー・プラグイン・設定などの変更とログイン失敗を記録し、ダッシュボードのウィジェットや専用画面で見られる。IP アドレスは初期設定で一部を伏せて保存。保存期間は初期設定で 30 日 |
WP-CLI が使える場合は、次のようにインストールして有効化できます。管理画面の「プラグイン」→「新規追加」で名前を検索しても同じです。
# WP Activity Log
wp plugin install wp-security-audit-log --activate
# Simple History
wp plugin install simple-history --activate
どちらも、プラグインを有効にした後の操作から記録が始まります。有効にする前の操作は記録されません。
方法2: 自作のプラグインで記録する
記録したい内容が少ない場合や、プラグインを増やしたくない場合は、フックを使って専用のテーブルに記録できます。ここでは、次の操作を記録する小さなプラグインを作ります。
| 記録する操作 | 使うフック |
|---|---|
| ログイン | wp_login |
| ログアウト | wp_logout |
| ログイン失敗 | wp_login_failed |
| 投稿・固定ページの更新 | post_updated |
| プラグインの有効化・無効化 | activated_plugin / deactivated_plugin |
| ユーザーの権限グループの変更(ユーザー作成時の権限の設定も含む) | set_user_role |
テーブルの作成にはプラグインの有効化フック(register_activation_hook())を使います。このフックはプラグインのファイルでしか動かないため、functions.php ではなくプラグインとして作ります。
プラグインのファイルを作る
wp-content/plugins/my-activity-log/ フォルダーを作り、その中に my-activity-log.php として次の内容を保存します。
<?php
/*
* Plugin Name: My Activity Log
* Description: ログイン・投稿の更新などを専用テーブルに記録する。
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
const MY_ACTIVITY_LOG_KEEP_DAYS = 90; // ログを残す日数
function my_activity_log_table() {
global $wpdb;
return $wpdb->prefix . 'user_activity_log';
}
// 有効化したときにテーブルを作り、古いログを消す処理を毎日実行する予定を入れる
register_activation_hook( __FILE__, function () {
global $wpdb;
$table = my_activity_log_table();
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$table} (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
created_at datetime NOT NULL,
user_id bigint(20) unsigned NOT NULL DEFAULT 0,
user_login varchar(60) NOT NULL DEFAULT '',
event_type varchar(50) NOT NULL DEFAULT '',
object_id bigint(20) unsigned NOT NULL DEFAULT 0,
detail varchar(255) NOT NULL DEFAULT '',
ip_address varchar(45) NOT NULL DEFAULT '',
user_agent varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (id),
KEY user_id (user_id),
KEY created_at (created_at)
) {$charset_collate};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
if ( ! wp_next_scheduled( 'my_activity_log_cleanup' ) ) {
wp_schedule_event( time(), 'daily', 'my_activity_log_cleanup' );
}
} );
// 無効化したら予定を外す(テーブルとログは残す)
register_deactivation_hook( __FILE__, function () {
wp_clear_scheduled_hook( 'my_activity_log_cleanup' );
} );
// 1件記録する
function my_activity_log_write( $event_type, $user_id = 0, $user_login = '', $object_id = 0, $detail = '' ) {
global $wpdb;
$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
$ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
$wpdb->insert(
my_activity_log_table(),
array(
'created_at' => current_time( 'mysql', true ), // UTC で保存
'user_id' => (int) $user_id,
'user_login' => mb_substr( (string) $user_login, 0, 60 ),
'event_type' => $event_type,
'object_id' => (int) $object_id,
'detail' => mb_substr( (string) $detail, 0, 255 ),
'ip_address' => mb_substr( $ip, 0, 45 ),
'user_agent' => mb_substr( $ua, 0, 255 ),
),
array( '%s', '%d', '%s', '%s', '%d', '%s', '%s', '%s' )
);
}
// ログイン
add_action( 'wp_login', function ( $user_login, $user ) {
my_activity_log_write( 'login', $user->ID, $user_login );
}, 10, 2 );
// ログアウト(WordPress 5.5 以降はユーザー ID が渡される)
add_action( 'wp_logout', function ( $user_id ) {
$user = get_userdata( $user_id );
my_activity_log_write( 'logout', $user_id, $user ? $user->user_login : '' );
} );
// ログイン失敗(入力されたユーザー名を記録)
add_action( 'wp_login_failed', function ( $username ) {
my_activity_log_write( 'login_failed', 0, $username );
} );
// 投稿・固定ページの更新(リビジョンと自動保存は除く)
add_action( 'post_updated', function ( $post_id, $post_after, $post_before ) {
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
return;
}
$user = wp_get_current_user();
$detail = sprintf( '%s: %s -> %s', $post_after->post_type, $post_before->post_status, $post_after->post_status );
my_activity_log_write( 'post_updated', $user->ID, $user->user_login, $post_id, $detail );
}, 10, 3 );
// プラグインの有効化・無効化
add_action( 'activated_plugin', function ( $plugin ) {
$user = wp_get_current_user();
my_activity_log_write( 'plugin_activated', $user->ID, $user->user_login, 0, $plugin );
} );
add_action( 'deactivated_plugin', function ( $plugin ) {
$user = wp_get_current_user();
my_activity_log_write( 'plugin_deactivated', $user->ID, $user->user_login, 0, $plugin );
} );
// 権限グループの変更
add_action( 'set_user_role', function ( $user_id, $role, $old_roles ) {
$user = wp_get_current_user();
$detail = sprintf( 'user %d: %s -> %s', $user_id, implode( ',', (array) $old_roles ), $role );
my_activity_log_write( 'role_changed', $user->ID, $user->user_login, $user_id, $detail );
}, 10, 3 );
// 古いログを削除する
add_action( 'my_activity_log_cleanup', function () {
global $wpdb;
$table = my_activity_log_table();
$cutoff = gmdate( 'Y-m-d H:i:s', time() - MY_ACTIVITY_LOG_KEEP_DAYS * DAY_IN_SECONDS );
$wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE created_at < %s", $cutoff ) );
} );
保存したら、管理画面の「プラグイン」→「インストール済みプラグイン」で「My Activity Log」を有効化します。有効化したときにテーブル(接頭辞が wp_ なら wp_user_activity_log)が作られます。
コードのポイント
dbDelta()の書き方。dbDelta()は SQL の書式に厳しく、1行に1列、PRIMARY KEYの後は空白を2つ、インデックスはINDEXではなくKEYと書く必要があります。CREATE TABLE IF NOT EXISTSではなくCREATE TABLEと書きます(テーブルがあれば差分だけ反映されます)。- 日時は UTC で保存。
current_time( 'mysql', true )は UTC の日時を返します。表示するときに日本時間(+9 時間)に直します。 wp_login_failedで記録するユーザー名は、入力された文字列そのもの。 存在しないユーザー名やメールアドレスも記録されます。- IP アドレス。 Cloudflare やロードバランサーを通している場合、
REMOTE_ADDRはプロキシーの IP アドレスになります。その場合はサーバー側(Web サーバーの設定)で本来の接続元に置き換えてから記録してください。X-Forwarded-Forなどのヘッダーは利用者が自由に書き換えられるので、そのまま信用しません。 - プラグインの有効化の記録は、このプラグインが有効なときだけ。 このプラグイン自体を無効化した操作は、その時点ではまだコードが読み込まれているので記録されますが、その後の操作は記録されません。
記録を確認する
WP-CLI で新しい順に20件を表示します(接頭辞が wp_ でない場合はテーブル名を変えます)。
wp db query "SELECT id, CONVERT_TZ(created_at, '+00:00', '+09:00') AS jst, user_login, event_type, object_id, detail, ip_address FROM wp_user_activity_log ORDER BY id DESC LIMIT 20;"
ログイン失敗が多い IP アドレスを調べる例です。
wp db query "SELECT ip_address, COUNT(*) AS cnt FROM wp_user_activity_log WHERE event_type = 'login_failed' AND created_at > UTC_TIMESTAMP() - INTERVAL 1 DAY GROUP BY ip_address ORDER BY cnt DESC LIMIT 10;"
WP-CLI が使えない場合は、phpMyAdmin で同じ SQL を実行します。
不要になったとき
プラグインを無効化しても、テーブルとログは残ります。削除する場合は、データベースのバックアップを取ってから次を実行します。
wp db query "DROP TABLE wp_user_activity_log;"
注意すること
- 個人情報の扱い。 IP アドレスやユーザー名とひも付いたログは、個人情報保護法や GDPR の対象になる場合があります。記録する目的と保存期間を決め、プライバシーポリシーに記載してください。保存期間は上のコードでは 90 日です(
MY_ACTIVITY_LOG_KEEP_DAYS)。 - ログは同じデータベースにある。 管理者権限を奪われた場合、ログも消されたり書き換えられたりするおそれがあります。不正アクセスの調査にも使いたい場合は、Web サーバーのアクセスログを別に保管するか、外部にログを送れる仕組み(WP Activity Log の有料版のログ転送など)を検討します。
- 記録が多すぎるとデータベースが大きくなる。 ログイン失敗は攻撃を受けると大量に記録されます。ログインの総当たり対策も合わせて行ってください。
まとめ
- 手軽に始めるなら WP Activity Log か Simple History を使います。
- 自分で記録する場合は、
wp_login・wp_logout・wp_login_failed・post_updatedなどのフックで専用テーブルに書き込みます。テーブルの作成は有効化フックで行うため、プラグインとして作ります。 - 保存期間を決めて古いログを自動で消し、個人情報としての扱いをプライバシーポリシーに書きます。
ログイン画面の守り方は ブルートフォース攻撃への対策 も参考にしてください。