ぷぎがぽぎ このページをアンテナに追加 RSSフィード

2014-12-08

[][]monologでPHPのFatalエラーをログに残す

この記事はSymfony Advent Calendar 2014の9日目の記事です。

昨日は おかぽんさんの SymfonyのDIを利用してMail送信クラスをインテグレーションテストする:コード編 でした

Symfony のエラーハンドリングはとても優秀 (ただし2.4以降)

Symfony は monolog が標準でバンドルされており、2.4からはエラーハンドリングをFWでとても良い感じにやってくれます。

Symfony 2.4以降の devで Noticeエラー

たとえば、dev環境だと

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction()
    {
        echo $a;
        return $this->render('default/index.html.twig');
    }
}

のような未定義の変数を書いてしまった場合のようなNoticeのエラー時には次のようなデバッグ画面で教えてくれます。

f:id:brtRiver:20141209011711p:image

そして、app/logs/dev.logには

[2014-12-09 00:01:57] request.CRITICAL: Uncaught PHP Exception Symfony\Component\Debug\Exception\ContextErrorException: "Notice: Undefined variable: a" at /private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php line 15 {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\ContextErrorException: Notice: Undefined variable: a at /private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php:15)"} []

といった内容のログが残ります。便利です。

Symfony 2.4以降の devで Fatal エラー

また、Fatalエラーが起こるような次のような Syntax Error の場合でも

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction()
    {
        echo
        return $this->render('default/index.html.twig');
    }
}

次のようなデバッグ画面で教えてくれ、

f:id:brtRiver:20141209011713p:image

dev.logにも

[2014-12-09 00:05:52] request.CRITICAL: Uncaught PHP Exception Symfony\Component\Debug\Exception\FatalErrorException: "Parse Error: syntax error, unexpected 'return' (T_RETURN)" at /private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php line 16 {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalErrorException: Parse Error: syntax error, unexpected 'return' (T_RETURN) at /private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php:16)"} []

といったように教えてくれます。

Symfony 2.4以降の prodで Noticeエラー

ただ、面倒なのが本番(prod)環境のときです。まず、前提として php.ini で

display_errors = Off

と設定されていて、たとえば nginx で

server {

...

error_log /usr/local/var/log/nginx/project_error.log;

...

}

となっているとします。

(本番環境でdisplay_errors = On なんてことないですよね)

この状態でNoticeエラーを起こしてみるとエラーは表示されず、何事もないかのように処理がおこなわれました。

そして、logs/prod.log をみてもエラーは記録されていません。

その代わりに project_error.log 側にphp-fpmからのエラーが記録されました。

2014/12/09 00:10:33 [error] 1737#0: *27 FastCGI sent in stderr: "PHP message: PHP Notice: Undefined variable: a in /private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php on line 15" while reading response header from upstream, client: 127.0.0.1, server: sf.localhost, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9054", host: "sf.localhost:9191"

つまり、Noticeエラーは標準ではアプリケーションのログには出ないので忘れずに設定したエラー出力先を監視する必要があります。

Symfony 2.4以降の prod で Fatal エラー

また、fatalエラーを起こしてみます。本番環境なのでエラーページが表示されました。

f:id:brtRiver:20141209012331p:image

こちらは、アプリケーションのログ (prod.log)にも記録されています。

[2014-12-09 00:12:07] php.EMERGENCY: Fatal Parse Error: syntax error, unexpected 'return' (T_RETURN) {"type":1,"file":"/private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php","line":16,"level":32767,"stack":}

[2014-12-09 00:12:07] request.CRITICAL: Uncaught PHP Exception Symfony\Component\Debug\Exception\FatalErrorException: "Parse Error: syntax error, unexpected 'return' (T_RETURN)" at /private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php line 16 {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalErrorException: Parse Error: syntax error, unexpected 'return' (T_RETURN) at /private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php:16)"} []

monolog は メモリ不足のエラーもログに残してくれる

そして、偉いのはメモリ不足時のエラーもちゃんと記録します。

[2014-12-09 00:16:23] php.EMERGENCY: Fatal Error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 100000000000000001 bytes) {"type":1,"file":"/private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php","line":15,"level":32767,"stack":}

[2014-12-09 00:16:23] request.CRITICAL: Uncaught PHP Exception Symfony\Component\Debug\Exception\OutOfMemoryException: "Error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 100000000000000001 bytes)" at /private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php line 15 {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\OutOfMemoryException: Error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 100000000000000001 bytes) at /private/var/www/sf-sample/src/AppBundle/Controller/DefaultController.php:15)"} []

なぜ偉いかというと、メモリ不足エラーが発生した時点からログに書きだそうとしても、既にメモリ不足状態なので書き出すことはできない可能性があります。が、monologはとりあえずログを書き出すメモリ分を確保しておいてから処理を行い、エラー時にはその確保したメモリを開放して書き込んでいるようです。

https://github.com/Seldaek/monolog/blob/master/src/Monolog/ErrorHandler.php#L94

<?php
...
    public function registerFatalHandler($level = null, $reservedMemorySize = 20)
    {
        register_shutdown_function(array($this, 'handleFatalError'));
        $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); // ここで事前に確保
        $this->fatalLevel = $level;
    }
...
    public function handleFatalError()
    {
        $this->reservedMemory = null; // エラーを書き込む前に領域開放
        $lastError = error_get_last();
        if ($lastError && in_array($lastError['type'], self::$fatalErrors)) {
            $this->logger->log(
                $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel,
                'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
                array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'])
            );
        }
    }

何を言ってるんだ。俺は LTSの 2.3 を使っているんだけど

そうです。LTSは 2.3 では挙動が異なります。その違いはエラー時に画面にデバッグ情報を出してくれないという点です。

Noticeエラーは画面には何もエラーがでずログには書き出されます。Fatalエラーは画面は真っ白に。これもログには書き出されます。

ログに残してくれるのはいいんですが、Fatalエラーで真っ白になるのは悲しいですね。

何を言ってるんだ。俺は monolog は使っているが Symfony を使ってないんだけど

おまけです。monologさえ使っていれば ErrorUnadler::register にロガーを登録しておけばPHPのFatalエラーもよしなにログに残してくれるようになります。(monolog >= 1.6)

<?php
require_once __DIR__.'/../vendor/autoload.php';

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$log = new Logger('name');
$log->pushHandler(new StreamHandler(__DIR__ . '/your.log', Logger::WARNING));
\Monolog\ErrorHandler::register($log); // UtilのErrorHandler::registerにロガーを登録

$a = str_repeat('1234567890', 10000000000000000); // メモリ不足エラーを意図的に発生させる

// add records to the log
$log->addWarning('Foo');
$log->addError('Bar');

するとログには

[2014-12-09 01:05:23] name.ALERT: Fatal Error (E_ERROR): Allowed memory size of 134217728 bytes exhausted (tried to allocate 100000000000000001 bytes) {"code":1,"message":"Allowed memory size of 134217728 bytes exhausted (tried to allocate 100000000000000001 bytes)","file":"/private/var/www/sf-sample/web/app.php","line":15} []

とメモリ不足のFatalエラーが記録されました。素敵ですね。

自前で同じようなことをやろうとするとほんと面倒で涙がでてくるので monolog さまさまです。

このmonolog の Utilの使い方については全くといっていいほど紹介してくれている記事がなかったので、この機会にまとめてみました。

PHPでアプリを作っている方のお役に立てれば幸いです。

明日は PHP Mentorsitemanさんです!お楽しみに!

2014-12-07

[][] ミニ四駆よりも熱い ミニッツレーサーのススメ #vgadvent2014

この記事はVOYAGE GROUP エンジニアブログ Advent Calendar 2014の7日目の記事です。

こんにちは、VOYAGE GROUPの Zucksスマホ広告配信サービス Zucks Ad Networkエンジニアしている 前田 @brtriver です。

去年は簡単にPHPをmakeしてみた だったので、「今年は何をmakeするのですか?」と聞かれましたが、ミニッツレーサーについて語りたいと思います。

ミニッツレーサーとは

今年はミニ四駆が再ブームというニュースもあったぐらい、大人にミニ四駆が流行りました。

自分自身は小さい時はミニ四駆にもハマりましたが、それよりもラジコンが大好きでした。

ミニッツレーサーは一言でいうと「ちっちゃいラジコン」です。ミニ四駆が 1/32 なのに比べて ミニッツは 1/27 なので少し大きいですが、感覚的にはミニ四駆と同じぐらいです。

一見は百聞にしかずということで、以下の紹介動画を見てください。

D

実際のレースの模様の動画(レースは2:50あたり)

D

こっちはバギー(しかも海外)

D

いわゆるトイラジではなく、ガチなラジコンの分野です。

ミニ四駆はTAMIYAですが、ミニッツは京商です。

ちなみに自分が小さいときに走らせていたラジコンは YOKOMOのスーパードッグファイターでした。

広坂雅美は神です。

なぜミニッツレーサーか

最初はミニ四駆の再ブームに押され、ミニ四駆をもう一度やろうかとも思ったのですが次のような理由でミニッツにしました。

かっこいい

今のミニ四駆は見れば見るほど自分が知ってたミニ四駆じゃなく、いろんなバーやポールがこれでもかというぐらい付いていてなにかが違う感。

ミニッツはオートスケールシリーズといって、塗装済みのかっこいいボディーが揃っている点。

ポールやダンパーなんて必要なく、余裕があるんだったらライトをつけることもできる。かっこいい。


専用のコースがいらない

また、走らせようとするとミニ四駆だとコースがないとやはり辛い。

その点ミニッツは適当な広さがあればコースづくりは簡単。仕事終わりにコース作って走らせていますが、ホース20m でOK

f:id:brtRiver:20141207115528j:image

ただし、本気でコースを買うとそれなりな値段になるので辛いです。

MINI-Z GRAND PRIX CIRCUIT

最後はドライビングテクニック

どちらかというと走らせるまでのセッティング決めを楽しむのがミニ四駆で、セッティングも重要ながらドライビングテクニックを磨かないとどんなに速いクルマでも勝てないのがミニッツです。

ミスを少なく集中しないと勝てません。最初のうちは運転中にケツに力が入りすぎて翌日筋肉痛になりました。そんな感じです。

ミニッツは本格的RCとしては安い

ミニッツは実売価格では 15,000円で全部込み(シャーシ、ボディー、プロポ)です。これは本格的なRCをやっている人からするととても安いです。

実際走行させるためには、単4が8本 (シャーシx4、プロポx4)必要なのは注意です。

大体1セットで 30分ぐらい走ってくれるので12本持っておけば十分楽しめます。

VOYAGE GROUP にはパンゲアがある

これが一番大きいと思います。本当は1/10のEPをやりたいんですが、都内だとコースがほとんど無いし公園で走らせるなんかは周りに迷惑掛けてしまいますし気楽に遊べません。

でも ミニッツはサイズも小さいですし、カーペットな床さえあればとりあえず走行を楽しめます。パンゲアの会議室はカーペットなので最低限のグリップはありますし、十分な広さも確保できます。

とはいえ、汚れなどでかなりスリッピーなので次はコース用のカーペットを購入したいなーと考えているところです。

もし、ミニッツを持ってる(買った)けど走らせる場所がないという方は声掛けてください。一緒に走らせましょう。

ちなみにまだヒヨッコなのでショップのコースで走らせたことはありません。。

ラップカウンターが欲しくなる

なんとなくコントロールできるようになってきたので、コースをラップタイムで競ったりしたくなってきます。

公式ではラップカウンターが販売されています。

ICタグラップカウンター

コースよりは安いとは言え、実売価格は3万近くしてしまいます...。

無いなら作ってみようということで、作ってみてますというのがこのブログの本題。

ICタグRFID

ラップタイムを測る方法は色々あると思いますが、要件としては

  • 複数台を識別してラップカウントとラップタイムを記録したい

です。

色々調べてみると、スマホアプリでカメラを使ったミニ四駆のラップタイマーなどはありましたが、これだと複数台を識別することはできません。

素直に公式サイトで販売されているラップカウンターの仕組みを調べてみます。

どうやら赤外線ではなくRFIDを利用しICタグを車に貼って、ディテクタで通過したことを検知し専用のアプリで管理している模様。

簡単にいうと、改札のパスモでピッてやるのと同じです。

ネットで調べたところ安いRFIDリーダーは 3,500円程度で売ってありました。

http://strawberry-linux.com/catalog/items?code=15080

USBタイプのリーダーで、USBキーボードとして認識されるのでmacでも普通に動作しました。

タグを認識すると「識別したタグを入力し最後にエンターを押す」という動作をします。まさにバーコードリーダーです。

ただ、このリーダーだとアンチコリジョン機能がないので、同時に2台が検知されたときにどちらも無視されるという痛い欠点がありますが、ICタグをそれぞれのシャーシに貼ることで個別に通過する限りは個別に管理できそうです。

ちなみに、リーダーやタグをもっと高性能のものにすると、こんなことができるようです。

(本当はこういうのをやりたかったけど更に高額になるので諦めた)

D

アンテナ制作はWIP

今回購入したリーダーは外部アンテナ端子をどうにかやれば作れるので、良い感じに検知してくれるディテクタを作れば勝てる!!!!1

ということでがんばっているんですが、まだ誠意作成中です。。。

どういう感じでがんばろうとしているかは次の動画みてもらえばわかるかと思います。

D

ガムテープの部分が外部アンテナ部分です。IC タグそれぞれに固有のIDが振られているので、リーダーが認識するとそれをキーボードから入力してくれます。


ラップカウンターアプリは Vue.js

あとはこのディテクタからの入力をテキストボックスで受ければできんじゃね?ってことで、

Vue.js でさくっと作ってみました。

本当は巷で話題のReactでやりたかったんですが、時間があるならミニッツをもっと走らせてたいとか年末にボヤロックがあるためバンド練習やらと忙しいためエンジニアインターン Treasure でも採用した慣れている Vue.js でやってしまいました。

去年のボヤロックの様子

興味がある方は twitter で #ajiting で参加表明してもらえれば。

話がそれましたが、完成したコードはこちら

https://github.com/brtriver/laptimer

最初にIDを通してエントリーします。そのあとに名前を登録しておけばその名前で読み上げてくれます。ブラウザが。


フリー走行モードとレースモードを用意していています。

フリー走行モードではひたすらラップタイムを読み上げてくれます。ブラウザが。

もしベストラップ更新した場合は「ベストラップ更新」も教えてくれます。ブラウザが。

D

レースモードでは指定された時間でラップタイムを測ってくれます。スタート時のカウントダウン付きです。ブラウザが。

D

名前を変更したらラップの履歴の名前も全部書き換えるとか、ラップタイムを履歴に追加し即座にviewにも反映させるとか

こういうものをさくっと作るには Vue.js はとてもマッチしている感じがしました。

それにしても、ブラウザの読み上げ機能すごいですね。漢字も普通に読んでしまう。

この程度だったらそれほど違和感もないですよね。

ただ、たまに突然しゃべらなくなることがありその制御方法がまだよくわからず...。

しばらくはラップタイマー作りで楽しめそうです。

まとめ

ミニッツレーサーを買ってパンゲアで一緒に走らせましょう。

皆さんの参加お待ちしておりますー。

明日は @chocopie116 で "どの会社も3ヶ月に1回必ずやるであろうアレについて書く予定です" とのことです。

アレってなんでしょね?!お楽しみに!!

2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |