風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

レンタルサーバでlog4phpを使用するための覚書

log4php とは…

Apache Logging ServicesプロジェクトのPHP用・高機能なログフレームワーク(ログ出力ツール)。

Apache log4php™ is a versatile logging framework for PHP.

Apache log4php - Welcome - Apache log4php

出力先には画面やファイル等を指定でき、ログレベル等も使え、一定サイズでログを切り替えたりといった機能も持つ。

レンタルサーバへのインストール(PEAR経由)

PEARがインストールされていて、正常に使用できていることが前提。

log4php のインストール
  1. Channel Management で "pear.apache.org/log4php" を [Discover Channel]
  2. "pear.apache.org/log4php" チャンネルから、"Apache_log4php" をインストール

使用サンプル

log4php用設定ファイル(log4php.properties)
# log4php.properties: log4php用設定ファイル
# 参考: http://logging.apache.org/log4php/docs/configuration.html


# === アペンダ(appenders)定義
# log4php.appender.{appender_name}
# 参考: http://logging.apache.org/log4php/docs/appenders.html
#       http://logging.apache.org/log4php/docs/layouts/pattern.html

# --- 出力無し設定
log4php.appender.dev_null = LoggerAppenderNull

# --- 画面出力設定
log4php.appender.stdout = LoggerAppenderEcho
log4php.appender.stdout.layout = LoggerLayoutPattern
log4php.appender.stdout.layout.conversionPattern = "%date{Y-m-d H:i:s.u} %-14logger %-8level [%-15X{ADDR} %X{HOST}] %message%newline"

# --- ファイル出力設定(一定サイズでログを切替)
#   http://logging.apache.org/log4php/docs/appenders/rolling-file.html
log4php.appender.file = LoggerAppenderRollingFile
# ■ log4php.appender.{name}.file は絶対パスで指定する必要あり
#  参考: http://stackoverflow.com/questions/15666893/log4php-file-size-error
log4php.appender.file.file = /path_to/app.log
log4php.appender.file.append = true
log4php.appender.file.maxFileSize = 5MB
log4php.appender.file.maxBackupIndex = 5
log4php.appender.file.compress = false
log4php.appender.file.layout = LoggerLayoutPattern
log4php.appender.file.layout.conversionPattern = "%date{Y-m-d H:i:s.u} %-14logger %-8level [%-15X{ADDR} %X{HOST}] %message%newline"


# === ロガー(logger)定義
# 参考: http://logging.apache.org/log4php/docs/loggers.html

# --- root ロガー
# log4php.rootLogger = {log_level}, {appender_name}[, {appender_name} ...]
# ※ rootLogger は、全ての Logger::getLogger({logger_name}) の継承元となる
#   {log_level} → http://logging.apache.org/log4php/docs/introduction.html
#   {appender_name) → アペンダ定義で指定した名称(log4php.appender.{appender_name})
#   <?php $root_logger = Logger::getRootLogger(); // RootLogger 取得 ?>
log4php.rootLogger = FATAL, dev_null


# --- 名前付きロガー
# log4php.logger.{logger_name} = {log_level}, {appender_name}[, {appender_name} ...]
# ※ 設定名({logger_name}) をLogger::getLogger({logger_name})で指定すると、log4php.rootLogger の継承+指定した設定のログとなる
# ※ 本設定ファイル内で定義されていない{logger_name}を指定すると、root ロガーと等価になる
#   {logger_name} → Logger::getLogger({logger_name})で指定すると、log4php.rootLogger の継承+指定したアペンダのロガーが取得される
#   <?php $sample_logger = Logger::getLogger('sample'); // 名前を指定して Logger 取得 ?>
log4php.logger.DefaultLogger = WARN, file
log4php.logger.DebugLogger = DEBUG, stdout, file


注意点として、

サンプルPHPファイル(test_log.php)
<?php
require_once('log4php/Logger.php');

// === 設定ファイル読込
Logger::configure('log4php.properties');

// === MDC (_mapped diagnostic contexts_) 設定
// ※ log4php.appender.{appender_name}.layout.conversionPattern で、'%X{ADDR}' のようにして参照可能
// http://logging.apache.org/log4php/apidocs/class-LoggerMDC.html
LoggerMDC::put('ADDR', isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '-');
LoggerMDC::put('HOST', isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : '-');

// === ロガー取得
$default_logger = Logger::getLogger('DefaultLogger');
$debug_logger = Logger::getLogger('DebugLogger');
$separator_logger = Logger::getLogger('separator');
$separator_logger->addAppender($debug_logger->getAppender('stdout'));
$separator_logger->addAppender($debug_logger->getAppender('file'));
$separator_logger->setLevel(LoggerLevel::getLevelDebug());

// === テスト
$separator_logger->debug('1) DefaultLogger<br />');
$default_logger->trace('trace1<br />');
$default_logger->debug('debug1<br />');
$default_logger->info('info1<br />');
$default_logger->warn('warn1<br />');
$default_logger->error('error1<br />');
$default_logger->fatal('fatal1<br />');
$separator_logger->debug('==========<br />');

$separator_logger->debug('2) DebugLogger<br />');
$debug_logger->trace('trace2<br />');
$debug_logger->debug('debug2<br />');
$debug_logger->info('info2<br />');
$debug_logger->warn('warn2<br />');
$debug_logger->error('error2<br />');
$debug_logger->fatal('fatal2<br />');
$separator_logger->debug('==========<br />');

$separator_logger->debug('3) DefaultLogger (TRACE)<br />');
$default_logger->setLevel(LoggerLevel::getLevelTrace());
$default_logger->trace('trace3<br />');
$default_logger->debug('debug3<br />');
$default_logger->info('info3<br />');
$default_logger->warn('warn3<br />');
$default_logger->error('error3<br />');
$default_logger->fatal('fatal3<br />');
$separator_logger->debug('==========<br />');

$separator_logger->debug('4) DebugLogger (TRACE)<br />');
$debug_logger->setLevel(LoggerLevel::getLevelTrace());
$debug_logger->trace('trace4<br />');
$debug_logger->debug('debug4<br />');
$debug_logger->info('info4<br />');
$debug_logger->warn('warn4<br />');
$debug_logger->error('error4<br />');
$debug_logger->fatal('fatal4<br />');
$separator_logger->debug('==========<br />');

$separator_logger->debug('5) DefaultLogger (FATAL)<br />');
$default_logger->setLevel(LoggerLevel::getLevelFatal());
$default_logger->trace('trace5<br />');
$default_logger->debug('debug5<br />');
$default_logger->info('info5<br />');
$default_logger->warn('warn5<br />');
$default_logger->error('error5<br />');
$default_logger->fatal('fatal5<br />');
$separator_logger->debug('==========<br />');

$separator_logger->debug('6) DebugLogger (FATAL)<br />');
$debug_logger->setLevel(LoggerLevel::getLevelFatal());
$debug_logger->trace('trace6<br />');
$debug_logger->debug('debug6<br />');
$debug_logger->info('info6<br />');
$debug_logger->warn('warn6<br />');
$debug_logger->error('error6<br />');
$debug_logger->fatal('fatal6<br />');
$separator_logger->debug('==========<br />');
結果(画面出力)
2014-09-21 23:38:50.836 separator DEBUG [xxx.xxx.xxx.xxx -] 1) DefaultLogger
2014-09-21 23:38:50.838 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
2014-09-21 23:38:50.838 separator DEBUG [xxx.xxx.xxx.xxx -] 2) DebugLogger
2014-09-21 23:38:50.838 DebugLogger DEBUG [xxx.xxx.xxx.xxx -] debug2
2014-09-21 23:38:50.839 DebugLogger INFO [xxx.xxx.xxx.xxx -] info2
2014-09-21 23:38:50.839 DebugLogger WARN [xxx.xxx.xxx.xxx -] warn2
2014-09-21 23:38:50.839 DebugLogger ERROR [xxx.xxx.xxx.xxx -] error2
2014-09-21 23:38:50.840 DebugLogger FATAL [xxx.xxx.xxx.xxx -] fatal2
2014-09-21 23:38:50.840 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
2014-09-21 23:38:50.840 separator DEBUG [xxx.xxx.xxx.xxx -] 3) DefaultLogger (TRACE)
2014-09-21 23:38:50.842 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
2014-09-21 23:38:50.842 separator DEBUG [xxx.xxx.xxx.xxx -] 4) DebugLogger (TRACE)
2014-09-21 23:38:50.842 DebugLogger TRACE [xxx.xxx.xxx.xxx -] trace4
2014-09-21 23:38:50.843 DebugLogger DEBUG [xxx.xxx.xxx.xxx -] debug4
2014-09-21 23:38:50.843 DebugLogger INFO [xxx.xxx.xxx.xxx -] info4
2014-09-21 23:38:50.843 DebugLogger WARN [xxx.xxx.xxx.xxx -] warn4
2014-09-21 23:38:50.844 DebugLogger ERROR [xxx.xxx.xxx.xxx -] error4
2014-09-21 23:38:50.844 DebugLogger FATAL [xxx.xxx.xxx.xxx -] fatal4
2014-09-21 23:38:50.844 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
2014-09-21 23:38:50.845 separator DEBUG [xxx.xxx.xxx.xxx -] 5) DefaultLogger (FATAL)
2014-09-21 23:38:50.845 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
2014-09-21 23:38:50.846 separator DEBUG [xxx.xxx.xxx.xxx -] 6) DebugLogger (FATAL)
2014-09-21 23:38:50.846 DebugLogger FATAL [xxx.xxx.xxx.xxx -] fatal6
2014-09-21 23:38:50.846 separator DEBUG [xxx.xxx.xxx.xxx -] ==========
結果(ログファイル:app.log)
2014-09-21 23:38:50.836 separator      DEBUG    [xxx.xxx.xxx.xxx -] 1) DefaultLogger<br />
2014-09-21 23:38:50.837 DefaultLogger  WARN     [xxx.xxx.xxx.xxx -] warn1<br />
2014-09-21 23:38:50.837 DefaultLogger  ERROR    [xxx.xxx.xxx.xxx -] error1<br />
2014-09-21 23:38:50.837 DefaultLogger  FATAL    [xxx.xxx.xxx.xxx -] fatal1<br />
2014-09-21 23:38:50.838 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />
2014-09-21 23:38:50.838 separator      DEBUG    [xxx.xxx.xxx.xxx -] 2) DebugLogger<br />
2014-09-21 23:38:50.838 DebugLogger    DEBUG    [xxx.xxx.xxx.xxx -] debug2<br />
2014-09-21 23:38:50.839 DebugLogger    INFO     [xxx.xxx.xxx.xxx -] info2<br />
2014-09-21 23:38:50.839 DebugLogger    WARN     [xxx.xxx.xxx.xxx -] warn2<br />
2014-09-21 23:38:50.839 DebugLogger    ERROR    [xxx.xxx.xxx.xxx -] error2<br />
2014-09-21 23:38:50.840 DebugLogger    FATAL    [xxx.xxx.xxx.xxx -] fatal2<br />
2014-09-21 23:38:50.840 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />
2014-09-21 23:38:50.840 separator      DEBUG    [xxx.xxx.xxx.xxx -] 3) DefaultLogger (TRACE)<br />
2014-09-21 23:38:50.841 DefaultLogger  TRACE    [xxx.xxx.xxx.xxx -] trace3<br />
2014-09-21 23:38:50.841 DefaultLogger  DEBUG    [xxx.xxx.xxx.xxx -] debug3<br />
2014-09-21 23:38:50.841 DefaultLogger  INFO     [xxx.xxx.xxx.xxx -] info3<br />
2014-09-21 23:38:50.841 DefaultLogger  WARN     [xxx.xxx.xxx.xxx -] warn3<br />
2014-09-21 23:38:50.841 DefaultLogger  ERROR    [xxx.xxx.xxx.xxx -] error3<br />
2014-09-21 23:38:50.842 DefaultLogger  FATAL    [xxx.xxx.xxx.xxx -] fatal3<br />
2014-09-21 23:38:50.842 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />
2014-09-21 23:38:50.842 separator      DEBUG    [xxx.xxx.xxx.xxx -] 4) DebugLogger (TRACE)<br />
2014-09-21 23:38:50.842 DebugLogger    TRACE    [xxx.xxx.xxx.xxx -] trace4<br />
2014-09-21 23:38:50.843 DebugLogger    DEBUG    [xxx.xxx.xxx.xxx -] debug4<br />
2014-09-21 23:38:50.843 DebugLogger    INFO     [xxx.xxx.xxx.xxx -] info4<br />
2014-09-21 23:38:50.843 DebugLogger    WARN     [xxx.xxx.xxx.xxx -] warn4<br />
2014-09-21 23:38:50.844 DebugLogger    ERROR    [xxx.xxx.xxx.xxx -] error4<br />
2014-09-21 23:38:50.844 DebugLogger    FATAL    [xxx.xxx.xxx.xxx -] fatal4<br />
2014-09-21 23:38:50.844 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />
2014-09-21 23:38:50.845 separator      DEBUG    [xxx.xxx.xxx.xxx -] 5) DefaultLogger (FATAL)<br />
2014-09-21 23:38:50.845 DefaultLogger  FATAL    [xxx.xxx.xxx.xxx -] fatal5<br />
2014-09-21 23:38:50.845 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />
2014-09-21 23:38:50.846 separator      DEBUG    [xxx.xxx.xxx.xxx -] 6) DebugLogger (FATAL)<br />
2014-09-21 23:38:50.846 DebugLogger    FATAL    [xxx.xxx.xxx.xxx -] fatal6<br />
2014-09-21 23:38:50.846 separator      DEBUG    [xxx.xxx.xxx.xxx -] ==========<br />

「Jコミ」改め「絶版マンガ図書館」の初期不具合等

本日、Jコミが「絶版マンガ図書館」としてリニューアルされましたが、大幅な改定を行ったためか、ざっと見たところでも不具合等が目につきましたので、覚書を兼ねて。
まぁ、徐々に改善されていくのだと思いますが(初回の分は、Jコミ情報室!さん及び赤松氏に報告済み。その後は随時お問い合わせ等でも報告)。
→対応されたもの・新たに見つけたもの等を随時更新。

Web 版

不具合
No. 不具合内容 対応 備考
1 マイページの本棚で「本棚から削除」が出来ない。 削除できるようになった(2014/07/12)。 一旦消えたように見えても、ページをリロードすると削除できていない。
サーバとの通信が行われていないように見える。
2 シリーズ作品において、巻数順にソートされていないものがある。
作品一覧や、作品詳細ページのシリーズ作品の案内、RSS等
ソートされるようになった模様(2014/07/13)。
→作品一覧については、公開日順に表示されるようになった(2014/07/28)。
例:ハイスクール!奇面組 スクリーンショット
全10巻以上の長編
3 作品詳細ページの「著作者の作品案内」に何も表示されない。 「著作者の作品案内」欄がなくなった(2014/07/12)。
4 http://r18.zeppan.com/ の右下のランキングが表示されない。 表示されるようになった(2014/07/12)。 500エラーが発生している模様。
5 http://zeppan.com/ の右下のランキングが総合ランキングしかない。 一般・成人向け共に「総合ランキング」のみに統一された(2014/07/12)。 R18の方は7日間ランキング・30日間ランキングもある→無くなった(2014/07/12)。
→一般/R18共に、7日間ランキング・30日間ランキングが表示されるようになった(2014/08)。
6 「作品一覧」などで、画面を下にスクロールした際に継ぎ足された作品については、「本棚に追加」「本棚から削除」ができない。 継ぎ足された作品に関しても、追加・削除共にできるようになった(2014/07/14)。
7 マイページの本棚にXSS脆弱性有り。 報告分に関しては対策された(2014/07/13)。 2014/07/13に「お問い合わせ」から報告。
8 「作品一覧」などで、最初に表示されたり、継ぎ足されたりする作品数が50個だったりたまに10個だったりで一定しない。 改善されたように思える(2014/07/14)。 2014/07/14に「お問い合わせ」から報告。
9 マンガ検索フォームにXSS脆弱性有り。 報告分に関しては対策された(2014/07/15)。 2014/07/14に「お問い合わせ」から報告。
前日までは存在しない脆弱性だったはず、改修過程で入り込んだ模様。
10 タイトル50音順一覧に出てこない作品がある。 報告分に関しては対策された模様(2014/07/15)。 2014/07/15に「お問い合わせ」から報告。
ら行を最後までスクロールしても、「LoVe/EGOISTIC」(著作者 ぐりすぐり)が表示されない。
11 リニューアル後に掲載された作品で、著作者名が表示されないものがある。 対応された模様(2014/07/15)。 蓬萊学園の冒険!!(未報告、JComi_Updateのツイートで気づいてはいたが、そういうものだと思っていた)→2014/07/15対応済み。
おしえて♡お姉さん (2014/07/15、ツイートで報告)→2014/07/15対応済み。
その後も発生している(データベースへの登録ミスか?)。
12 旧Jコミの「Jコミで印刷できるってよHD」のPDFが(再)ダウンロードできない。 対応された模様(対応日不明、2014/07/25以前)。 503エラーでアクセス不可(2014/07/18)。
2014/07/18に「お問い合わせ」から報告。
2014/07/19時点の回答:サイトリニューアルに伴い、一時的に閉鎖中、近日中に再オープンとのこと(「現在、このページはメンテナンスを行っております。後日改めてアクセス下さい。ご迷惑をおかけてしておりますが、よろしくお願い申し上げます。」表示になった)。
13 新着公開作品等の作品一覧が、公開日順に表示されていない。 公開日順に表示されるようになった(2014/07/28)。
ただし、この影響で同一シリーズ内の順番は巻数順ではなくなった模様()。
作品番号の降順(ただし、シリーズ作品内では巻数順)で表示されているようだが、アップロード作品も公開作品と同じ番号体系になっているため、作品が許諾されたタイミングによっては表示順が後になってしまう模様。

- 総作品数が一覧で表示される作品数と一致しない。 2014/07/14に「お問い合わせ」から報告。
■一般向け作品
総作品数:1,475
新着公開作品:1416件
最新アップロード作品:54件
※5件少ない。
■成人向け作品
総作品数:272
新着公開作品:261件
最新アップロード作品:11件
※こちらは一致。

要望等
No. 現状 要望 対応 備考
1 Jコミではシリーズ作品をまとめたページがあったが、これがなくなり、一覧表示などでも1巻ずつ表示される。
このため、特に全10巻以上の長編等では非常に見通しが悪いように感じられる。
一覧・検索表示では、シリーズ作品はまとめてひとつとして欲しい(好みの問題かもしれないが…)。 赤松氏:「ニコ動のように、同じ作品でも画質の違うバージョンが複数上がってくる可能性があるため、巻ごとの管理になりました。 」 現状でも、作品詳細ページには、シリーズ作品がまとめて表示はされる。
2 「本棚に追加」が単巻ずつしかできない。 シリーズものについてはまとめて追加したい。
3 既存のJコミ作品ページ等へのリンクが全て絶版マンガ図書館のトップページにリダイレクトされてしまう。 既存のJコミの各作品へのリンクは、当該作品のページへリダイレクトして欲しい。 各作品の旧リンクがリダイレクトされるようになった(2014/07/12)。 作者の方等にも負担になっている。(参考
4 Jコミで有志の入力による作品データが存在したが、これがなくなっている。
旧作品ページのキャッシュで、「>> 作品データを見る」をクリックしたときに出てきたもの。
既存の作品データについては予めWikiに反映しておくなどの配慮が欲しい。 既存の有志の方のモチベーションを下げてしまう。(参考
赤松氏:「絶版マンガ図書館では、wikipediaに準じた様式で書いて欲しい希望はあります。編集ツールもwikipedia準拠になっています。IPアドレスなども残ります。これまでとは結構違いますのでお気を付け下さい。しばらく様子見することをお薦め致します。 」
5 サムネイルをクリックしても作品詳細ページに移行しない(マウスオーバ→作品詳細ページへをクリックする手順が必要)。 サムネイルクリックでも作品詳細ページへ移行して欲しい。 対応された(2014/07/15?)。 要望として認識はされており、いずれ直されるとのこと(2014/07/14・参考

Androidアプリ

使用機種:WX04K(Android 4.1.1)、旧Jコミ用JComi Viewer+はアンインストール済み。

不具合
No. 不具合内容 対応 備考
1 DLのためにログインしようとすると、「ページの読み込み中…」が断続的に表示される等して、ログイン入力ができず、ダウンロードできない。 ログイン&ダウンロードが正常に出来るようになった(2014/07/12)。 2014/07/15現在、まだたまに同様の現象が発生してログインできないこともある。サーバ側の負荷などの問題なのか?
2 R18の方で、DLを選択しているのに、(「ページの読み込み中…」が断続的に表示される等した後で、リダイレクトされて(?))PC版の画面が表示されて正常動作しない。 ログイン&ダウンロードが正常に出来るようになった(2014/07/12)。 同上。
3 ある決まったURLにアクセスすると、ログインしていなくても作品のZIPファイル等が直接ダウンロードできてしまう。 認証されていない状態だと404が返されるようになった(2014/12/24?)。 PC上などからもダウンロード可能(ただし、ZIPファイルにはパスワードがかけられている)。

RHEL6.3で、HTTP GET時に5分以上受信データがないとだんまりになる

現象

とあるレンタルサーバ(telnetやsshは未サポート)上のデータをローカル(RHEL6.3 サーバ)上に定期的にバックアップを取る必要があり、ファイル数が多くFTPだと時間がかかって仕方がないので、

  1. レンタルサーバ上のPHPスクリプトを呼び出し、tar コマンドにより全ファイルをアーカイブ。
  2. アーカイブした tar ファイルを FTP でダウンロード。

という方法を取っていたのだが、ある時点から、正常にバックアップが取れなくなってしまった。


調べてみたところ、

  • 1. で、HTTP GET Request 後に、一定時間(5分)以上受信データが無い状態が続くと、HTTP クライアントがその後のデータを受信しないままフリーズしてしまう。

状態であることがわかった。


ちなみに、HTTP クライアントには wget を使用していたが、

  • 同一バージョンの wget を使用しても、個人持ちの CentOS 6.5 上ではフリーズせずに問題なく完了。
  • 当該 RHEL6.3 サーバ上では、wget 以外の方法であっても、同様の現象が発生してしまう。

RHEL6.3 サーバ上で使用している socket 関連の共有ライブラリ(あるいはその設定)に問題があるのであろうところまでしかわかっていない。


どなたか、このような場合の対策をご存じの方がおられたら、教えてほしい。
もっとも、5分以上もデータが無い状態が続くと、そもそも他のレンタルサーバとかだと Apache とかのタイムアウトの方でひっかかってしまう気がしなくもないので、どちらにしてもレンタルサーバ側の処理も見直す必要があるのだろうけれども。

再現方法

次のようなPHPスクリプトをレンタルサーバ上に設置し、

/test/wait.php
<?php
$WAIT_MIN = 5;  //  <=4:OK, >5:NG

if (isset($_GET['wait']) && is_numeric($_GET['wait'])) $WAIT_MIN = intval($_GET['wait']);

$WAIT_SEC = $WAIT_MIN * 60;
$WAIT_UNIT_SEC = 10;
$WAIT_COUNT = (int) ($WAIT_SEC / $WAIT_UNIT_SEC);

set_time_limit($WAIT_SEC * 2);

function    echo_flush($str) {
    echo($str);
    ob_flush();
    flush();
}   //  end of flush_output()

ob_start();
header("Content-Type: text/plain; charset=utf-8");
ob_end_flush(); // バッファフラッシュ&バッファリングをOFFに

echo_flush("wait {$WAIT_MIN} minutes ({$WAIT_SEC} seconds) ...\n");

for ($ci=0; $ci < $WAIT_COUNT; $ci++) {
    sleep($WAIT_UNIT_SEC);
    //↓の行を有効にして、10秒毎にデータを送信するようにすればクライアント側でフリーズしない
    //echo_flush(sprintf("%5d sec.\n", $WAIT_UNIT_SEC*(1+$ci)));
}
echo_flush("done.\n");

exit(0);

// ■ end of file


RHEL6.3 サーバ上で wget を実行すると、

$ wget "http://example.com/test/wait.php?wait=4" -q -O -
wait 4 minutes (240 seconds) ...
done.

のように、4分までは問題なく完了するのに、

$ wget "http://example.com/test/wait.php?wait=5" -q -O -
wait 5 minutes (300 seconds) ...

5分からは(wget側でタイムアウトするまで)だんまりになる。


また、telnet で試しても、

$ telnet example.com 80
Trying xxx.xxx.xxx.xxx...
Connected to example.com.
Escape character is '^]'.
GET /test/wait.php?wait=4 HTTP/1.0
User-Agent: telnet
Host: example.com

HTTP/1.1 200 OK
Date: Sun, 15 Jun 2014 00:00:00 GMT
Server: Apache
Connection: close
Content-Type: text/plain; charset=utf-8

wait 4 minutes (240 seconds) ...
done.
Connection closed by foreign host.

のように、4分までは問題なく完了するのに、

$ telnet example.com 80
# : (中略)
GET /test/wait.php?wait=5 HTTP/1.0
# : (中略)
wait 5 minutes (300 seconds) ...

5分からは、この状態でだんまりになる。

暫定対策

レンタルサーバ側のPHPスクリプトで、次のような関数を使って tar コマンドをバックグランドで呼び出した後、処理が終わるまで一定時間毎にポーリングし、何らかのデータを出力するようにしている。

<?php
function    exec_nowait($cmdline, &$outfile, &$errfile) {
    $outfile = tempnam('./', 'OUT_');
    $errfile = tempnam('./', 'ERR_');
    $cmdline = "{$cmdline} >{$outfile} 2>{$errfile} & echo \$!";
    $pid = null;
    exec($cmdline, $output, $rcode);
    foreach ($output as $line) if (($pid = trim($line)) !== '') break;
    return $pid;
}   //  end of exec_nowait()

$pid = exec_nowait("tar cvf backup.tar /path/to/target", $outfile, $errfile);
while ($pid) {
    sleep(10);
    if (/*バックグラウンド処理のチェックを行い、終了していたら*/) break;
    echo(date("Y-m-d H:i:s") . "\n"); // データ出力
    ob_flush();
    flush();
}
// この辺で後処理を入れる
unlink($outfile);
unlink($errfile);


バックグラウンド処理の終了チェックは、psコマンドが使える場合は、

<?php
function    get_proc_dict() {
    $proc_dict = array();
    $cmd = "/bin/ps -A -o pid= -o comm=";
    $fp = popen($cmd, "r");
    while (($line=fgets($fp))!==false) {
        $line = trim($line);
        $parts = explode(' ', $line, 2);
        $proc_dict[$parts[0]] = $parts[1];
    }
    pclose($fp);
    return $proc_dict;
}   //  end of get_proc_dict()

function    get_proc_name($pid) {
    $proc_dict = get_proc_dict();
    return isset($proc_dict[$pid]) ? $proc_dict[$pid] : null;
}   //  end of get_proc_name()

// while ループ内のチェック部分で、if (!get_proc_name($pid)) break;

のような関数を用意して使うのがよさそう。


ps コマンドが存在しない場合は…tar の標準出力($outfile)をチェックして、例えばサイズが変わらなくなったら終了、とか。

32ビット環境だと2GBを超えるファイルサイズが正常に取れないのか…

PHP_INT_MAX = 2147483647 = 0x7FFFFFFF となっている環境だと、

注意: PHP の数値型は符号付整数であり、 多くのプラットフォームでは 32 ビットの整数を取るため、 ファイルシステム関数の中には 2GB より大きなファイルについては期待とは違う値を返すものがあります。

PHP: filesize - Manual

のように、filesize() の返り値が保証されないということを今更ながらに気付く。
64ビット環境でテストしていたから、32ビット環境で動かしてみてしばらく悩んでしまった。

やむを得ず、代用関数を考えて見る

<?php
function    get_filesize($filepath) {
    $filesize = '';
    for (;;) {
        if (!file_exists($filepath)) break;
        exec("/usr/bin/wc -c < {$filepath}", $output, $return_var);
        foreach ($output as $line) if (($filesize = trim($line)) !== '') break;
        break;
    }
    return $filesize;
}   //  end of get_filesize()

"wc -c"の結果を取得して、文字列で返しているだけ。ファイルが存在しない場合等は ""(空文字列) が返る。


BCMathが有効な環境であれば、使えるかな?

【近傍ツイート検索】(twDisplayVicinity):Google Chrome拡張機能版を公開

2019年7月のTwitterデザイン変更に伴い、動作しなくなっております。
現状では実装方法が思いつかず、また調査の時間も取れないため、対応の見通しが立っておりません。悪しからずご了承ください。

なお、2019年8月現在、「GoodTwitter」(Chrome版Firefox版)を使用して旧デザインに戻すことで、従来通り利用することは可能なようです。

2019年9月14日リリースのバージョン0.3.0.0より、新UIにも対応しました。
なお、ユーザースクリプト版は新UIでは動作しませんので、Chrome 拡張機能版もしくはFirefoxアドオン版をご使用ください。

"twDisplayVicinity" is no longer working, due to the Twitter design change in July 2019.
I cannot conceive the implementation method and the survey time cannot be taken, so at present there is no expectation of correspondence. Please understand.

In addition, if you use "GoodTwitter"(Chrome extensionFirefox addon), you can use it as usual (as of August 2019).

New UI has been supported since version 0.3.0.0 released on September 14, 2019.
The user script version does not work with the new UI, so please use the Chrome extension or the Firefox add-on.


【近傍ツイート検索】特定ツイート前後のタイムラインを表示するユーザースクリプト

の、Google Chrome専用版(拡張機能)を公開

本体スクリプトはユーザースクリプト版と同一であるため機能的な違いはないが、表示オプション等が設定画面から変更可能になっている。



インストールの前に

既にユーザースクリプト版がインストールされている場合、Google Chromeの拡張機能(chrome://extensions/)を開き、

twDisplayVicinityを削除。
Tampermonkey等の拡張機能経由でインストールしている場合は、それぞれの拡張機能のアンインストール機能で削除。

インストール方法

  1. 近傍ツイート検索 - Chrome ウェブストアからインストール開始。

    ダイアログ右上の方にある[+ 無料]ボタンをクリック。
    これ、初見だとインストール用ボタンだと判りづらいと思うのだけれど、自分だけ?

     

  2. 『新しい拡張を機能の確認』ダイアログが表示されたら、

    [追加]ボタンをクリック。

オプション設定

  1. Google Chromeの拡張機能(chrome://extensions/)を開き、twDisplayVicinityの

    “オプション”リンクをクリック。

     

  2. オプション画面が開いたら、お好みに応じて各種オプションを変更。

使い方

Twitterの個別ツイート、もしくは、タイムラインを開き、名前/日付の右側に表示される“近傍”リンクをクリックすると、


別タブ/ウィンドウで当該ツイート前後のその人のツイートが表示される。
当該ツイートが画面に出現するまで自動でスクロール。


デフォルトではその人のタイムライン上から検索するが、この場合は最大で(最新発言から数えて)3200件までしか遡れない。
上記方法でツイートが表示されない場合、Twitterの検索機能の結果表示へと切り替わる。
また、(デフォルトでは)[Shift]キーを押しながら“近傍”リンクをクリックすることで、最初からTwitterの検索機能の結果表示から探すこともできる(ただしRTは表示されず、精度も落ちる精度は落ち、表示されないツイートも出てくる)。