投稿ログの一部が勝手に削除される不具合 #42

Open
opened 2023-11-12 17:33:15 +09:00 by konkon-fox · 7 comments
Contributor

概要

投稿ログ/{$bbs}/LOG.cgiの一部が勝手に消えてしまう不具合を確認しました。{$bbs}は板名を表します。
不具合の修正方法と、それに伴う古い投稿ログの扱いについての問題提起です。

現象

/{$bbs}/LOG.cgiの行数が多い場合に、複数の投稿が短時間で行われると古い投稿ログの一部が消えてしまうことがある。

例えば、管理画面での投稿最大ログ保存件数は無記入(上限なし)の場合でも、LOG.cgiが約1万行から約4千行に減少しました。

原因の推察と修正案

/test/bbs-main.phpで投稿ログのファイルLOG.cgiを更新する際に、fileで開きfile_put_contentsで更新しているのが問題ではないかと考えています。該当部分(ca415d001d/test/bbs-main.php (L1127-L1136))
fopenflockfread更新fwriteflockfcloseのようにファイルロックを活用した処理にすると改善する可能性があります。私の開発環境ではこちらの方法で改善されましたが、実際に多人数がアクセスする本番環境ではどうなるか不明です。

修正コード案

$logFileHandle = fopen($LOGFILE, 'c+');
flock($logFileHandle, LOCK_EX);
$logData = fread($logFileHandle, filesize($LOGFILE));
$logDataArray = explode("\n", $logData);
array_unshift($logDataArray, $_POST['name']."<>".$_POST['mail']."<>".$DATE." ".$ID."<>".$_POST['comment']."<>".$_POST['title']."<>".$_POST['thread']."<>".$number."<>".$HOST."<>".$_SERVER['REMOTE_ADDR']."<>".$_SERVER['HTTP_USER_AGENT']."<>".htmlspecialchars($CH_UA, ENT_NOQUOTES, "UTF-8")."<>".htmlspecialchars($ACCEPT, ENT_NOQUOTES, "UTF-8")."<>".$WrtAgreementKey."<>".$LV."<>".$info);
if ($SETTING['LOG_LIMIT'] && count($logDataArray) > $SETTING['LOG_LIMIT'] + 100) {
  while (count($logDataArray) > $SETTING['LOG_LIMIT']) array_pop($logDataArray);
}
$logData = implode("\n", $logDataArray);
ftruncate($logFileHandle, 0);
rewind($logFileHandle);
fwrite($logFileHandle, $logData);
flock($logFileHandle, LOCK_UN);
fclose($logFileHandle);

修正に伴う古い投稿ログの扱い

この不具合が修正された場合、未設定だと投稿ログが無限に貯まってしまうことになり、別の不具合が発生する恐れがあります。
例えば、未設定の場合は上限なしではなく1万件までに制限する等の必要がありそうです。

まとめ

不具合発生については再現性がありますが、原因については推察止まりです。
不具合発生の原因究明及び修正コード案で改善が可能かどうかの確認をお手伝いいただきたいです。
また、別の修正案やご質問等があればお知らせください。

## 概要 投稿ログ`/{$bbs}/LOG.cgi`の一部が勝手に消えてしまう不具合を確認しました。{$bbs}は板名を表します。 不具合の修正方法と、それに伴う古い投稿ログの扱いについての問題提起です。 ## 現象 `/{$bbs}/LOG.cgi`の行数が多い場合に、複数の投稿が短時間で行われると古い投稿ログの一部が消えてしまうことがある。 例えば、管理画面での投稿最大ログ保存件数は無記入(上限なし)の場合でも、`LOG.cgi`が約1万行から約4千行に減少しました。 ## 原因の推察と修正案 `/test/bbs-main.php`で投稿ログのファイル`LOG.cgi`を更新する際に、`file`で開き`file_put_contents`で更新しているのが問題ではないかと考えています。該当部分(https://git.3chan.cc/stat2/delightly-v2fork/src/commit/ca415d001d2ae5df18edc5385b843b56fbcac88d/test/bbs-main.php#L1127-L1136) `fopen`→`flock`→`fread`→`更新`→`fwrite`→`flock`→`fclose`のようにファイルロックを活用した処理にすると改善する可能性があります。私の開発環境ではこちらの方法で改善されましたが、実際に多人数がアクセスする本番環境ではどうなるか不明です。 ### 修正コード案 ``` $logFileHandle = fopen($LOGFILE, 'c+'); flock($logFileHandle, LOCK_EX); $logData = fread($logFileHandle, filesize($LOGFILE)); $logDataArray = explode("\n", $logData); array_unshift($logDataArray, $_POST['name']."<>".$_POST['mail']."<>".$DATE." ".$ID."<>".$_POST['comment']."<>".$_POST['title']."<>".$_POST['thread']."<>".$number."<>".$HOST."<>".$_SERVER['REMOTE_ADDR']."<>".$_SERVER['HTTP_USER_AGENT']."<>".htmlspecialchars($CH_UA, ENT_NOQUOTES, "UTF-8")."<>".htmlspecialchars($ACCEPT, ENT_NOQUOTES, "UTF-8")."<>".$WrtAgreementKey."<>".$LV."<>".$info); if ($SETTING['LOG_LIMIT'] && count($logDataArray) > $SETTING['LOG_LIMIT'] + 100) { while (count($logDataArray) > $SETTING['LOG_LIMIT']) array_pop($logDataArray); } $logData = implode("\n", $logDataArray); ftruncate($logFileHandle, 0); rewind($logFileHandle); fwrite($logFileHandle, $logData); flock($logFileHandle, LOCK_UN); fclose($logFileHandle); ``` ## 修正に伴う古い投稿ログの扱い この不具合が修正された場合、未設定だと投稿ログが無限に貯まってしまうことになり、別の不具合が発生する恐れがあります。 例えば、未設定の場合は上限なしではなく1万件までに制限する等の必要がありそうです。 ## まとめ 不具合発生については再現性がありますが、原因については推察止まりです。 不具合発生の原因究明及び修正コード案で改善が可能かどうかの確認をお手伝いいただきたいです。 また、別の修正案やご質問等があればお知らせください。
First-time contributor

実を言うと、これは LOG.cgi に限った話ではありません。

flock() はアドバイザリーロックなので、アドバイザリーロックを取っている相手にしか効果がありません。
file_get_contents()file() にはアドバイザリーロックを取る機能がないため、排他制御が行われず、書き込んでいる途中のファイルを読み込んでしまうことが起こり得ます。

書き込み途中のデータを読み込み、加工して、もう一度書き込むことで、データを欠損させたり、壊してしまったりするわけです。

今回はおそらく…

  1. プロセスAが LOG.cgi に file_put_contents() で書き込む(ロックを取っているつもり)
  2. プロセスAは4,000件書き込む
  3. プロセスBが file() で4,000件分だけ読み込む(ロックを取らないので10,000件書き込むまで待ってくれない)
  4. プロセスAは残りの6,000件を書き込む
  5. プロセスBが LOG.cgi に file_put_contents() で4,001件書き込む
  6. →6,000件がなかったことになる

…という感じでしょう。

重要なことなのでもう一度書きますが、file_get_contents()file() にはアドバイザリーロックを取る機能がないため、排他制御が行われません

この少し上の箇所では $subjectfilefile_get_contents() で読み込んで file_put_contents(..., LOCK_EX) で書き込んでいます。
ここも実は排他制御が行われていません。
書き込みが同時に起きなかったり、ファイルサイズが小さいため問題にならないだけで、ファイルサイズが大きくなると壊れます。

delightly では同じ問題を抱えている箇所がたくさんあります。近いうちに file_get_contents() 撲滅運動をすることになるでしょう。

実を言うと、これは LOG.cgi に限った話ではありません。 `flock()` はアドバイザリーロックなので、アドバイザリーロックを取っている相手にしか効果がありません。 `file_get_contents()` や `file()` にはアドバイザリーロックを取る機能がないため、排他制御が行われず、書き込んでいる途中のファイルを読み込んでしまうことが起こり得ます。 書き込み途中のデータを読み込み、加工して、もう一度書き込むことで、データを欠損させたり、壊してしまったりするわけです。 今回はおそらく… 1. プロセスAが LOG.cgi に `file_put_contents()` で書き込む(ロックを取っているつもり) 1. プロセスAは4,000件書き込む 1. プロセスBが `file()` で4,000件分だけ読み込む(ロックを取らないので10,000件書き込むまで待ってくれない) 1. プロセスAは残りの6,000件を書き込む 1. プロセスBが LOG.cgi に `file_put_contents()` で4,001件書き込む 1. →6,000件がなかったことになる …という感じでしょう。 重要なことなのでもう一度書きますが、**`file_get_contents()` や `file()` にはアドバイザリーロックを取る機能がないため、排他制御が行われません**。 この少し上の箇所では `$subjectfile` を `file_get_contents()` で読み込んで `file_put_contents(..., LOCK_EX)` で書き込んでいます。 ここも実は排他制御が行われていません。 書き込みが同時に起きなかったり、ファイルサイズが小さいため問題にならないだけで、ファイルサイズが大きくなると壊れます。 delightly では同じ問題を抱えている箇所がたくさんあります。近いうちに `file_get_contents()` 撲滅運動をすることになるでしょう。
First-time contributor

さて、LOG.cgi に限ると、解決する方法は複数あります。

まず、file()fopen() + flock() + fread() の読み込みに置き換えるパターン。
この場合、上記のように数千件のデータが消失することはなくなりますが、同時に書き込まれた場合は最悪1件のデータがなかったことになります。

次に konkon-fox さんの提示しているコードのようなパターン。
全体が flock() されているため、データがなかったことにされる可能性はありませんが、ずっと排他ロックしているためパフォーマンスが最悪になります。
実況にはあまり向かないように思います。

個人的にオススメしたいのは「ログの並び順を逆にする」パターンです。
現在の LOG.cgi は新しい順にソートされており、そのため毎回ファイル全体を書き込む必要があります。
これを古い順にする(つまり普通のdatと同じ順にする)と追記モードで書き込めばいいだけになります。
あとは test/operate/log.php でログを表示する前に array_reverse() するだけです。

ただし、並び順を逆にしたとしても縮小処理のときは排他ロックをする必要があります。なので…

  1. flock(LOCK_SH) で共有ロックを取って LOG.cgi に追記モードで書き込む
  2. 行数を確認する
  3. もし行数が基準値を超えていたら、flock(LOCK_EX) で排他ロックを取って縮小処理をする

という感じになると思われます。

さて、LOG.cgi に限ると、解決する方法は複数あります。 まず、`file()` を `fopen() + flock() + fread()` の読み込みに置き換えるパターン。 この場合、上記のように数千件のデータが消失することはなくなりますが、同時に書き込まれた場合は最悪1件のデータがなかったことになります。 次に konkon-fox さんの提示しているコードのようなパターン。 全体が `flock()` されているため、データがなかったことにされる可能性はありませんが、ずっと排他ロックしているためパフォーマンスが最悪になります。 実況にはあまり向かないように思います。 個人的にオススメしたいのは「ログの並び順を逆にする」パターンです。 現在の LOG.cgi は新しい順にソートされており、そのため毎回ファイル全体を書き込む必要があります。 これを古い順にする(つまり普通のdatと同じ順にする)と追記モードで書き込めばいいだけになります。 あとは test/operate/log.php でログを表示する前に `array_reverse()` するだけです。 ただし、並び順を逆にしたとしても縮小処理のときは排他ロックをする必要があります。なので… 1. `flock(LOCK_SH)` で共有ロックを取って LOG.cgi に追記モードで書き込む 1. 行数を確認する 1. もし行数が基準値を超えていたら、`flock(LOCK_EX)` で排他ロックを取って縮小処理をする という感じになると思われます。
Author
Contributor

ファイルのロックに関してあまり詳しくなかったので、なんとなくこんな状態なのだろうという認識を詳しく解説していただいて非常に助かります。
$subjectfile等の他の箇所でも排他制御が行われておらず問題なのではないかと危惧しておりましたがその通りのようですね。
同様の問題ということなのでLOG.cgiだけでなくファイルの更新処理はまとめて修正するのが良いのでしょうか。

LOG.cgiの修正方法に関してはご提案の「ログの並び順を古い順にして追記する方式」が理にかなっていると思います。

1.flock(LOCK_SH)で共有ロックを取って LOG.cgi に追記モードで書き込む

とありますが、共有ロック状態では書き込みが行えないので排他ロックを行う必要があると思うのですがどうなのでしょうか。

ファイルのロックに関してあまり詳しくなかったので、なんとなくこんな状態なのだろうという認識を詳しく解説していただいて非常に助かります。 `$subjectfile`等の他の箇所でも排他制御が行われておらず問題なのではないかと危惧しておりましたがその通りのようですね。 同様の問題ということなので`LOG.cgi`だけでなく**ファイルの更新処理**はまとめて修正するのが良いのでしょうか。 `LOG.cgi`の修正方法に関してはご提案の「ログの並び順を古い順にして追記する方式」が理にかなっていると思います。 >1.`flock(LOCK_SH)`で共有ロックを取って LOG.cgi に追記モードで書き込む とありますが、共有ロック状態では書き込みが行えないので排他ロックを行う必要があると思うのですがどうなのでしょうか。
First-time contributor

ファイルの更新処理はまとめて修正するのが良いのでしょうか。

細かい話をするといろいろあるんですが、排他制御が完全に壊れている現状よりはマシになるだろうと思います。

共有ロック状態では書き込みが行えないので排他ロックを行う必要があると思うのですがどうなのでしょうか。

書き込みを行えないわけではありません。
追記モードであれば書き込みはアトミック動作なので共有ロックを取る必要もありません。

ここで共有ロックを取っているのは縮小処理中に書き込みを行わないため、または書き込みが終わるまで縮小処理の実行を待つためです。

もちろん排他ロックでもいいのですが、 ここは性能上のボトルネックになるので共有ロックで済むならそうしたほうがいいかなと思いまして。

> ファイルの更新処理はまとめて修正するのが良いのでしょうか。 細かい話をするといろいろあるんですが、排他制御が完全に壊れている現状よりはマシになるだろうと思います。 > 共有ロック状態では書き込みが行えないので排他ロックを行う必要があると思うのですがどうなのでしょうか。 書き込みを行えないわけではありません。 追記モードであれば書き込みはアトミック動作なので共有ロックを取る必要もありません。 ここで共有ロックを取っているのは縮小処理中に書き込みを行わないため、または書き込みが終わるまで縮小処理の実行を待つためです。 もちろん排他ロックでもいいのですが、 ここは性能上のボトルネックになるので共有ロックで済むならそうしたほうがいいかなと思いまして。
Author
Contributor

今回はとりあえずLOG.cgi更新部分の修正のみを考えることにします。
alice2chさんのご助言を取り入れてコード案を再修正しました。よろしければ目を通してもらえるとありがたいです。
無限にログが増えることを防ぐために、ログ保存件数の最大値$LOG_LIMITを定義しています。

$LOG_LIMIT = 10000;
if ($SETTING['LOG_LIMIT'] !== '') {
    $LOG_LIMIT = min((int) $SETTING['LOG_LIMIT'], $LOG_LIMIT);
    if($LOG_LIMIT < 0) {
        $LOG_LIMIT = 0;
    }
}
$logFileHandle = fopen($LOGFILE, 'a+');
if(flock($logFileHandle, LOCK_SH)) {
    // 新規ログを追記
    $newLog = $_POST['name']."<>".$_POST['mail']."<>".$DATE." ".$ID."<>".$_POST['comment']."<>".$_POST['title']."<>".$_POST['thread']."<>".$number."<>".$HOST."<>".$_SERVER['REMOTE_ADDR']."<>".$_SERVER['HTTP_USER_AGENT']."<>".htmlspecialchars($CH_UA, ENT_NOQUOTES, "UTF-8")."<>".htmlspecialchars($ACCEPT, ENT_NOQUOTES, "UTF-8")."<>".$WrtAgreementKey."<>".$LV."<>".$info."\n";
    fwrite($logFileHandle, $newLog);
    // ログの行数確認
    rewind($logFileHandle);
    for($lineCount = 0; fgets($logFileHandle); $lineCount++);
    if ($lineCount > $LOG_LIMIT + 100) {
        // 古いログを削除
        if(flock($logFileHandle, LOCK_EX)) {
            rewind($logFileHandle);
            $logData = fread($logFileHandle, filesize($LOGFILE));
            $logDataLines = explode("\n", $logData);
            $logDataLines = array_slice($logDataLines, -($LOG_LIMIT + 1));
            $newLogData = implode("\n", $logDataLines);
            ftruncate($logFileHandle, 0);
            rewind($logFileHandle);
            fwrite($logFileHandle, $newLogData);
        }
    }
    // ロック解除
    flock($logFileHandle, LOCK_UN);
}
fclose($logFileHandle);
今回はとりあえず`LOG.cgi`更新部分の修正のみを考えることにします。 alice2chさんのご助言を取り入れてコード案を再修正しました。よろしければ目を通してもらえるとありがたいです。 無限にログが増えることを防ぐために、ログ保存件数の最大値`$LOG_LIMIT`を定義しています。 ``` $LOG_LIMIT = 10000; if ($SETTING['LOG_LIMIT'] !== '') { $LOG_LIMIT = min((int) $SETTING['LOG_LIMIT'], $LOG_LIMIT); if($LOG_LIMIT < 0) { $LOG_LIMIT = 0; } } $logFileHandle = fopen($LOGFILE, 'a+'); if(flock($logFileHandle, LOCK_SH)) { // 新規ログを追記 $newLog = $_POST['name']."<>".$_POST['mail']."<>".$DATE." ".$ID."<>".$_POST['comment']."<>".$_POST['title']."<>".$_POST['thread']."<>".$number."<>".$HOST."<>".$_SERVER['REMOTE_ADDR']."<>".$_SERVER['HTTP_USER_AGENT']."<>".htmlspecialchars($CH_UA, ENT_NOQUOTES, "UTF-8")."<>".htmlspecialchars($ACCEPT, ENT_NOQUOTES, "UTF-8")."<>".$WrtAgreementKey."<>".$LV."<>".$info."\n"; fwrite($logFileHandle, $newLog); // ログの行数確認 rewind($logFileHandle); for($lineCount = 0; fgets($logFileHandle); $lineCount++); if ($lineCount > $LOG_LIMIT + 100) { // 古いログを削除 if(flock($logFileHandle, LOCK_EX)) { rewind($logFileHandle); $logData = fread($logFileHandle, filesize($LOGFILE)); $logDataLines = explode("\n", $logData); $logDataLines = array_slice($logDataLines, -($LOG_LIMIT + 1)); $newLogData = implode("\n", $logDataLines); ftruncate($logFileHandle, 0); rewind($logFileHandle); fwrite($logFileHandle, $newLogData); } } // ロック解除 flock($logFileHandle, LOCK_UN); } fclose($logFileHandle); ```
First-time contributor

お待たせしました。

追記モードのまま ftruncate() するのはなんというか居心地が悪いですね・・・。
試したところ動いていましたが 'c+' で開き直してもいい気がします。

fgets() をfor文で回してカウントするのは省メモリでいいですね。

$logData = fread($logFileHandle, filesize($LOGFILE));
$logDataLines = explode("\n", $logData);
$logDataLines = array_slice($logDataLines, -($LOG_LIMIT + 1));
$newLogData = implode("\n", $logDataLines);

逆にここは $logData$logDataLines$newLogData で元ファイルの約3倍の文字列が生成されています。
シンプルに fgets()fputs() を使う方が省メモリにできそう。

あとは $SETTING['LOG_LIMIT'] の最大値が本当に1万件でいいのか、という問題があります。
1万件は10スレ分でしかないので、余裕を見て10万件くらい欲しい場合も結構あるような・・・?
でも、多すぎても万が一の流出時が怖いかもしれませんからね。うーん・・・。

お待たせしました。 追記モードのまま `ftruncate()` するのはなんというか居心地が悪いですね・・・。 試したところ動いていましたが `'c+'` で開き直してもいい気がします。 `fgets()` をfor文で回してカウントするのは省メモリでいいですね。 ```php $logData = fread($logFileHandle, filesize($LOGFILE)); $logDataLines = explode("\n", $logData); $logDataLines = array_slice($logDataLines, -($LOG_LIMIT + 1)); $newLogData = implode("\n", $logDataLines); ``` 逆にここは `$logData` と `$logDataLines` と `$newLogData` で元ファイルの約3倍の文字列が生成されています。 シンプルに `fgets()` と `fputs()` を使う方が省メモリにできそう。 あとは `$SETTING['LOG_LIMIT']` の最大値が本当に1万件でいいのか、という問題があります。 1万件は10スレ分でしかないので、余裕を見て10万件くらい欲しい場合も結構あるような・・・? でも、多すぎても万が一の流出時が怖いかもしれませんからね。うーん・・・。
Author
Contributor

コードを再修正してみました。
ログの行数を確認して超過しているようならc+でファイルを開き直して縮小処理を行っています。
縮小処理後のfwriteによる書き込みについては、配列に格納されたログをforeachで行ごとに書き込む方法とimplodeで結合してから書き込む方法を考えましたが、メモリ使用量はあまり変わらず処理時間は後者が圧倒的に短かったので後者を採用しました。

$LOG_LIMIT = 10000については仮の値で10000を入れましたが、正直どれくらいの値が適切なのかよく分かりません。上限は間違いなく設定すべきだとは思うのですが…

$LOG_LIMIT = 10000;
if ($SETTING['LOG_LIMIT'] !== '') {
    $LOG_LIMIT = min((int) $SETTING['LOG_LIMIT'], $LOG_LIMIT);
    if($LOG_LIMIT < 0) {
        $LOG_LIMIT = 0;
    }
}
$logFileHandle = fopen($LOGFILE, 'a+');
if(flock($logFileHandle, LOCK_SH)) {
    // 新規ログを追記
    $newLog = $_POST['name']."<>".$_POST['mail']."<>".$DATE." ".$ID."<>".$_POST['comment']."<>".$_POST['title']."<>".$_POST['thread']."<>".$number."<>".$HOST."<>".$_SERVER['REMOTE_ADDR']."<>".$_SERVER['HTTP_USER_AGENT']."<>".htmlspecialchars($CH_UA, ENT_NOQUOTES, "UTF-8")."<>".htmlspecialchars($ACCEPT, ENT_NOQUOTES, "UTF-8")."<>".$WrtAgreementKey."<>".$LV."<>".$info."\n";
    fwrite($logFileHandle, $newLog);
    // ログの行数確認
    rewind($logFileHandle);
    for($lineCount = 0; fgets($logFileHandle); $lineCount++);
    if ($lineCount > $LOG_LIMIT + 100) {
        // ログ縮小処理用にファイルを開き直す
        fclose($logFileHandle);
        $logFileHandle = fopen($LOGFILE, 'c+');
        // 古いログを削除
        if(flock($logFileHandle, LOCK_EX)) {
            $logLines = [];
            while(($logLine = fgets($logFileHandle)) !== false) {
                $logLines[] = $logLine;
            }
            $logLines = array_slice($logLines, $lineCount - $LOG_LIMIT);
            ftruncate($logFileHandle, 0);
            rewind($logFileHandle);
            fwrite($logFileHandle, implode('', $logLines));
        }
    }
}
fclose($logFileHandle);

コードを再修正してみました。 ログの行数を確認して超過しているようなら`c+`でファイルを開き直して縮小処理を行っています。 縮小処理後の`fwrite`による書き込みについては、配列に格納されたログを`foreach`で行ごとに書き込む方法と`implode`で結合してから書き込む方法を考えましたが、メモリ使用量はあまり変わらず処理時間は後者が圧倒的に短かったので後者を採用しました。 `$LOG_LIMIT = 10000`については仮の値で10000を入れましたが、正直どれくらいの値が適切なのかよく分かりません。上限は間違いなく設定すべきだとは思うのですが… ``` $LOG_LIMIT = 10000; if ($SETTING['LOG_LIMIT'] !== '') { $LOG_LIMIT = min((int) $SETTING['LOG_LIMIT'], $LOG_LIMIT); if($LOG_LIMIT < 0) { $LOG_LIMIT = 0; } } $logFileHandle = fopen($LOGFILE, 'a+'); if(flock($logFileHandle, LOCK_SH)) { // 新規ログを追記 $newLog = $_POST['name']."<>".$_POST['mail']."<>".$DATE." ".$ID."<>".$_POST['comment']."<>".$_POST['title']."<>".$_POST['thread']."<>".$number."<>".$HOST."<>".$_SERVER['REMOTE_ADDR']."<>".$_SERVER['HTTP_USER_AGENT']."<>".htmlspecialchars($CH_UA, ENT_NOQUOTES, "UTF-8")."<>".htmlspecialchars($ACCEPT, ENT_NOQUOTES, "UTF-8")."<>".$WrtAgreementKey."<>".$LV."<>".$info."\n"; fwrite($logFileHandle, $newLog); // ログの行数確認 rewind($logFileHandle); for($lineCount = 0; fgets($logFileHandle); $lineCount++); if ($lineCount > $LOG_LIMIT + 100) { // ログ縮小処理用にファイルを開き直す fclose($logFileHandle); $logFileHandle = fopen($LOGFILE, 'c+'); // 古いログを削除 if(flock($logFileHandle, LOCK_EX)) { $logLines = []; while(($logLine = fgets($logFileHandle)) !== false) { $logLines[] = $logLine; } $logLines = array_slice($logLines, $lineCount - $LOG_LIMIT); ftruncate($logFileHandle, 0); rewind($logFileHandle); fwrite($logFileHandle, implode('', $logLines)); } } } fclose($logFileHandle); ```
Sign in to join this conversation.
No description provided.