はじめに
前回の記事(XServer API で複数サーバーのディスク使用量を集中監視する)では、エックスサーバーの REST API(XServer API)を使って複数サーバーのディスク使用量を PHP で集中監視する仕組みを構築した。
その後、実運用を重ねる中で次の3つの課題が出てきた。
- 通知先の制御 — 運営者は全サーバーを把握したいが、クライアントには担当サーバーの情報だけを届けたい
- メール送信の信頼性 — 共有ホスティングの
mail()関数ではなく SMTP 認証で確実に送りたい - ログファイルの肥大化 — 毎時追記し続けると CSV が際限なく大きくなる
この記事ではこれら3つを解決するために追加した機能を紹介する。
通知グループ:受信者別のサーバー範囲制御
課題
監視対象サーバーが増えると、受け取りたい通知の範囲が受信者によって異なってくる。
- 保守サービス運営者 → 全サーバーのアラートとレポートを受け取りたい
- サービス利用企業(複数サイト担当) → 担当する複数サーバーの通知のみ
- 1サイトのみ管理の担当者 → そのサーバーの通知だけ
以前の実装は notification という単一の通知設定に全サーバーのアラートがまとめて送られていた。クライアントごとに通知先を分けるには設計の変更が必要だった。
設計:通知グループ配列
config.php に notification_groups 配列を追加し、グループごとに「対象サーバーの範囲」と「通知チャネル」を独立して定義する方式にした。
'notification_groups' => [
// 運営者:全サーバーを対象
[
'label' => '運営者(全体)',
'servers' => '*',
'channels' => [
'email' => ['enabled' => true, 'to' => 'admin@example.com', ...],
],
],
// クライアントA:担当サーバーのみ
[
'label' => 'クライアントA',
'servers' => ['server-b.xsrv.jp'],
'channels' => [
'email' => ['enabled' => true, 'to' => 'client-a@example.com', ...],
],
],
],
servers の指定は次の3パターン。
| 値 | 対象 | 用途例 |
|---|---|---|
'*' | 全監視対象 | 保守サービス運営者 |
['server-a.xsrv.jp', 'server-b.xsrv.jp'] | 指定した複数サーバー | 複数サイト担当クライアント |
['server-c.xsrv.jp'] | 指定した1サーバー | 1サイトのみの担当者 |
各グループは独立したチャネル設定を持つため、グループAはメール、グループBは Slack、といった組み合わせも可能だ。
実装:収集してからフィルタして送信
スクリプト(disk_check.php・disk_report.php)の処理を2フェーズに分けた。
フェーズ1:全サーバーのデータを収集
$results = []; // [servername => ['label', 'alert_msg', 'error']]
foreach ($config['servers'] as $server) {
try {
$api = new XServerApi($server['servername'], $server['api_key']);
$usage = $api->getUsage();
$row = $logger->append($server['servername'], $usage);
$alertMsg = ((float)$row['usage_pct'] >= $server['thresholds']['disk_usage_pct'])
? sprintf('[%s] 使用率 %s%% ※閾値超過', $server['label'], $row['usage_pct'])
: null;
$results[$server['servername']] = ['label' => $server['label'], 'alert_msg' => $alertMsg, 'error' => null];
} catch (Exception $e) {
$results[$server['servername']] = ['label' => $server['label'], 'alert_msg' => null, 'error' => $e->getMessage()];
}
}
フェーズ2:グループごとにフィルタして送信
foreach ($config['notification_groups'] as $group) {
$alerts = [];
$errors = [];
foreach ($results as $sname => $result) {
if (!serverInGroup($sname, $group['servers'])) continue;
if ($result['error']) $errors[] = $result['error'];
if ($result['alert_msg']) $alerts[] = $result['alert_msg'];
}
if (empty($alerts) && empty($errors)) continue; // このグループは正常
(new Notifier($group['channels']))->send('[XServer監視] アラート', implode("\n", $alerts));
}
function serverInGroup(string $servername, string|array $servers): bool
{
return $servers === '*' || (is_array($servers) && in_array($servername, $servers, true));
}
スクリプト側はグループが何件あっても変更不要で、グループの追加・削除は config.php だけで完結する。
メール送信方式の切り替え:mail() と SMTP 認証
課題
PHP の mail() 関数は共有ホスティングでも手軽に使えるが、送信元 IP や SPF 設定によっては迷惑メールフォルダに入ることがある。Gmail や自社の SMTP サーバーを経由して送信したい場面に対応するため、SMTP 認証での送信機能を追加した。
設計:driver による切り替え
config.php の email 設定に driver キーを追加し、値を変えるだけで切り替えられるようにした。
'email' => [
'enabled' => true,
'driver' => 'smtp', // 'mail'(デフォルト)または 'smtp'
'to' => 'admin@example.com',
'from' => 'monitor@your-server.xsrv.jp',
'smtp' => [
'host' => 'smtp.gmail.com',
'port' => 587,
'encryption' => 'tls', // 'tls'(STARTTLS)/ 'ssl'(SMTPS)/ 'none'
'username' => 'your@gmail.com',
'password' => 'app-password', // .env で管理
],
],
Notifier::sendEmail() がドライバーを判定して sendEmailMail() か sendEmailSmtp() を呼び分ける。公開インターフェースは変わらないため、呼び出し側のコードに変更はない。
実装:Composer 不要の socket ベース SMTP
外部ライブラリを使わず、PHP の stream_socket_client で直接 SMTP プロトコルを実装した。共有ホスティング環境でも Composer なしで動作する。
private function sendEmailSmtp(string $subject, string $body): void
{
$cfg = $this->config['email'];
$enc = $cfg['smtp']['encryption'] ?? 'tls';
// ssl: 最初から TLS 接続(SMTPS)、tls/none: 平文で接続
$uri = ($enc === 'ssl') ? "ssl://{$cfg['smtp']['host']}:{$cfg['smtp']['port']}"
: "tcp://{$cfg['smtp']['host']}:{$cfg['smtp']['port']}";
$sock = stream_socket_client($uri, $errno, $errstr, 30);
// ... 接続バナー確認 → EHLO 送信 ...
// STARTTLS の場合:平文接続後に TLS へアップグレード
if ($enc === 'tls') {
// STARTTLS コマンドを送信
stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
// TLS 確立後に EHLO を再送
}
// AUTH LOGIN で認証
// MAIL FROM / RCPT TO / DATA でメール送信
}
encryption の指定方法は次の通り。
| 値 | ポート | 動作 |
|---|---|---|
'tls' | 587 | 平文で接続後 STARTTLS で暗号化 |
'ssl' | 465 | 最初から SSL/TLS 接続(SMTPS) |
'none' | 任意 | 暗号化なし(社内リレー等) |
SMTP 認証情報は .env で管理し、config.php では Env::get('SMTP_PASSWORD') として参照するため、ソースコードにパスワードは含まれない。
CSV ログの保持とローテーション
課題
毎時1行ずつ追記し続けると、3サーバーで年間約26,000行・約2.4MB になる。数年運用すると無視できないサイズになり、またバックアップや分析の単位としても扱いにくい。
「古いデータを自動削除してファイルを小さく保ちたい」という要件と、「過去のデータも月別にアーカイブして残したい」という要件が同時に存在したため、2つの独立した機能として実装した。
設計:retention と rotation を分離
'data_retention_days' => 90, // アクティブファイルの保持日数(0 = 無制限)
'data_rotation' => [
'enabled' => false,
'period' => 'monthly', // 'daily', 'weekly', 'monthly', 'yearly'
'keep' => 12, // 保持するローテーションファイル数(0 = 無制限)
],
- retention:
{servername}.csv(アクティブファイル)から N日以上前の行を削除 - rotation:
append()のたびに{servername}-2026-04.csvのような期間別ファイルへも同時書き込み
両方を有効にすると次のような構成になる。
data/
├── your-server.xsrv.jp.csv ← アクティブ(直近90日・古い行は自動削除)
├── your-server.xsrv.jp-2026-03.csv ← 3月の全データ(ローテーション)
└── your-server.xsrv.jp-2026-04.csv ← 4月の全データ(書き込み中)
アクティブファイルで直近データをすばやく参照しながら、月別ファイルで長期トレンドを分析できる。
実装:必要なときだけ rewrite する retention
毎回ファイル全体を書き直すのは無駄が大きい。先頭行(最古)をチェックして期限切れの行があるときだけ rewrite する設計にした。
private function pruneOldRows(string $file): void
{
$lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (count($lines) < 2) return;
$cutoff = date('Y-m-d H:i:s', strtotime("-{$this->retentionDays} days"));
$firstData = str_getcsv($lines[1]);
// 先頭データ行が期限内 → 削除不要(rewrite しない)
if (($firstData[0] ?? '') >= $cutoff) return;
$header = $lines[0];
$retained = array_filter(
array_slice($lines, 1),
fn($line) => (str_getcsv($line)[0] ?? '') >= $cutoff
);
$fh = fopen($file, 'w');
fwrite($fh, $header . "\n");
foreach ($retained as $line) { fwrite($fh, $line . "\n"); }
fclose($fh);
}
rewrite が発生するのは最古行が保持期間を超えた瞬間だけなので、通常の実行ではオーバーヘッドはほぼゼロだ。
実装:ファクトリメソッドによる設定の集約
disk_check.php と disk_report.php の両方で CsvLogger を生成する処理が必要になるため、fromConfig() ファクトリメソッドを追加して config 配列から直接インスタンスを生成できるようにした。
// CsvLogger クラス
public static function fromConfig(array $config): self
{
$rot = $config['data_rotation'] ?? [];
$period = !empty($rot['enabled']) ? ($rot['period'] ?? 'monthly') : '';
return new self(
$config['data_dir'],
$config['data_retention_days'] ?? 0,
$period,
(int)($rot['keep'] ?? 0)
);
}
// 各スクリプト(変更前:3行 → 変更後:1行)
$logger = CsvLogger::fromConfig($config);
まとめ
今回追加した機能を整理する。
| 機能 | 実装内容 |
|---|---|
| 通知グループ | notification_groups で受信者ごとにサーバー範囲と通知先を独立設定 |
| メール送信方式 | driver: 'mail' / 'smtp' で切り替え。SMTP は Composer 不要の socket 実装 |
| データ保持(retention) | アクティブ CSV から N日以上前の行を自動削除。変化があるときのみ rewrite |
| データローテーション | 期間別 CSV に同時書き込み(月次など)。古いファイルを自動削除 |
いずれの機能も config.php の設定を変えるだけで有効化・無効化でき、スクリプト本体を変更する必要はない。設定ファイルに権限を集中させることで、運用中の変更ミスを減らせる。
XServer API(エックスサーバーの REST API)はサーバー・ドメイン・メール・cron など広い範囲をカバーしている。今後は SSL 証明書の期限監視や JSON エクスポートによる他システム連携なども検討している。


コメント