OTOBANK Engineering Blog

オトバンクはコンテンツが大好きなエンジニアを募集しています!

Symfony Console のコマンド名を自動的に Monolog のログに出そう

この記事は Symfony Advent Calendar 2016 - Qiita 10日目の記事です。

みんな大好き Console のお話です。

CLI から Monolog Logger を使う

Symfony2 or 3 を使っているのであれば Console Component のお世話になっていると思います。

そしてほとんどの場合は便利なサービスを使いたいはずなので、 ContainerAwareCommand を使いますよね。

これを使えばサービスコンテナから Logger を取得し、Web と同じように CLI でもログを吐くことができます。 *1

例えば、下記のようなコマンドを定義し、

<?php

namespace AppBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class AppMyCommandCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this
            ->setName('app:my-command')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $logger = $this->getContainer()->get('logger');
        $logger->info('hello otobank');
    }
}

こういうコマンドを叩くと

$ ./bin/console app:my-command

var/logs/dev.log には下記のようなログが出力されます。

[2016-12-08 11:21:34] app.INFO: hello otobank [] []

OK. いいですね。

しかしこの var/logs/dev.log は Web でも使用されますし、別のコマンドを作ればそちらでも使用されることになるので、いろんなログが混ざり合い、とても見づらくなってしまいます。 *2

コマンド名がログに出力されれば、他と見分けがつくのでとても便利だと思いませんか?

といっても、毎回自分で

<?php
$logger->info('[my-command] hello otobank');

なんて書きたくないですよね。 *3

Monolog Processor を使う

そういった場合に有効なのが Monolog Processor です。

app/config/services.yml などに、ちょちょいとこいつを使うように定義してあげれば、

あら不思議、すべてのログに思いのままに付加情報をつけることができます。

デフォルトで用意されているプロセッサーは ここ にある限りだと思うのですが、

URL を出せる WebProcessor

プロセスIDを出せる ProcessIdProcessor はあっても、

コマンド名を出せるプロセッサーはありません。

それもそのはず、Monolog は Symfony Console のことなんて知らないですからね。

そうなってくると Symfony と Monolog を橋渡し(Bridge)する役目の ここ にあるかと思いきや、

残念、ちょっとそれらしきものはないわけです。

というわけで作りましょう。

Monolog Console Processor を作った

作ったものがこちらになります。 otobank/monolog-console-processor - Packagist

ソースコードは monolog-console-processor/ConsoleProcessor.php at 1.0.0 これだけ。

やってることは至極単純で、ConsoleEvents::COMMAND イベントを Subscribe して、コマンド名を保持しておき、ログが吐かれるタイミングで extra フィールドに保持しておいたコマンド名を付加するだけです。

使い方は monolog-console-processor/README.md に書いてあるとおりですが、

composer require otobank/monolog-console-processor

でインストールして、

app/config/services.yml などに

services:
    monolog.processor.console:
        class: Otobank\Monolog\Processor\ConsoleProcessor
        tags:
            - { name: monolog.processor }
            - { name: kernel.event_subscriber }

と書くだけ。

では再度コマンドを叩いてみましょう。

$ ./bin/console app:my-command

var/logs/dev.log には下記のようなログが出力されました。

[2016-12-09 17:55:26] app.INFO: hello otobank [] {"command_name":"app:my-command"}

いいですね。最高ですね。

おわりに

なんてことないプログラムですが、毎回書くことを考えれば便利なんじゃないかと思います。

だるいことはどんどんプログラムに任せよう!

そして空いた時間で龍が如く6をやろう!

はたまたオーディオブックを聴こう!

*1:バッチ処理ではなければわざわざ Logger を使わずに OutputInterface を使って標準出力に出せばいいと思うので、今回の記事はバッチ処理にコマンドを使う場合に有効な話です。とはいえ、サービス内で Logger を使用してロギングしている場合もあると思うので、そういう場合にも有効です。

*2:単純な解決方法として、ファイル名を分けるという方法もありますが、1つのアプリケーションログを構造化されたログとしてそのままFluent経由で送信していたのでログを分けたくなかったという経緯があります。

*3:これを書くことをいとわない人はプログラマーの三大美徳を思い出しましょう

2016年11月のどや会ごはん - 北海道直送 炭焼き酒場 36番倉庫 -

こんにちは! 愛子さまが15歳のお誕生日を迎えたと聞いて驚愕している@perokoです。
この間まで赤ちゃんじゃなかったっけ・・・???

さて、弊社は11月が期末なので、
お陰様を持ちまして、無事13期を迎えることができました。
今後ともオトバンクとオトバンク開発部をどうぞよろしくお願いいたします!

つまり、弊社は愛子さまのお誕生日とともに新しい期が始まるんだなあ。
5年目にして初めて気づきました。(どうでもいい)

ぼやっとしていると夢中で仕事をしていると時間が経つのは早いなあ〜。

f:id:peroko_tokyo:20161201153409j:plain

話は変わってどや会です。
オトバンク開発部では毎月末に月次の締め会として「どや会」を実施しています。

ビール片手に開催するこの会は、開発したものを"ドヤァ"したり、メンバー個人の近況を報告し合ったりして、チームビルディングに役立てようというものですが、参加者の感想としては「ご飯がおいしい」が9割なのではないかという噂もあります。

「どや会」の詳細についてはこちら engineering.otobank.co.jp

11月のどや会ごはん

11月のどや会ごはんは、東京の釧路「北海道直送 炭焼き酒場 36番倉庫」のお料理でした!
36番倉庫は何を食べても美味しい。そして安い。最高のお店です。

retty.me

f:id:peroko_tokyo:20161201144531j:plain 右上に写っている煮込みは世界で一番美味しい牛とうふ煮込み

f:id:peroko_tokyo:20161201144458j:plain 本当に分厚くておいしいハムカツ

f:id:peroko_tokyo:20161201144445j:plain 「弱くて甘いやつ」というオーダーをしたら、部長とデザイナーさんがおいしいやつを買ってきてくれました

f:id:peroko_tokyo:20161201144334j:plain 「他のエンジニアの作業環境が知りたい」という話が出たので、プロジェクターに作業環境を映し出して実演したり

36番倉庫、まじでおすすめです(宣伝)

テイクアウトはやっていない普通の居酒屋さんなんですが、今回はご無理をきいていただきました! 系列店(本郷・五反田・根津)も最高なので気になる方は是非行ってみてください〜😋

というわけで、11月もお疲れ様でした!
お正月休みまでもうちょっと!12月もがんばりましょ〜う!

Symfony2のコントローラについてまとめた(前半)

こんにちわ!@mrtryです。 最近、寒くなってきましたね。 最近は、ねとめしの「肉まんスープ」で温まっています。 すりごまとラー油をたくさん入れるのが好きです。

さて、「Symfony2入門」の4回目の記事です。 前回はルータについて説明しました。 今回は、コントローラについて説明します。 コントローラも理解すれば、Symfonyの基本はおさえた!と思って良いのではないでしょうか。


以下、今回の記事の目次になります。

  • コントローラとは
  • コントローラの生成方法

コントローラとは

コントローラとは、Requestオブジェクトから読み取った情報を元に、Responseオブジェクトを生成して返す処理のことを指します。 Responseオブジェクトは、HTTPのResponseに相当するのもので、レンダリングされたページやリダイレクト、画像などwebにアクセスして取得できる情報を任意に返すことができます。 フレームワークでの立ち位置については、以前書いた記事「Symfony2での処理の流れについてまとめた」を見ていただければ、と思います!

コントローラの生成方法

コントローラを作成する際は、基本的にapp/consoleに組み込まれている生成コマンドを利用して生成します。 今回は、実際に生成コマンドを利用して、symfonyのデモアプリケーションにコントローラを生成してみます。

デモアプリケーションのルートディレクトリにて、次のコマンドを実行してみましょう。

$ php bin/console generate:controller

実行すると、コントローラ生成に必要な項目を対話式で入力していくようになります。

以下は、実際にAppBundleの中にDemoControllerを生成し、その中にindexActionを定義したときのログになります。

$ php bin/console generate:controller


  Welcome to the Symfony2 controller generator



Every page, and even sections of a page, are rendered by a controller.
This command helps you generate them easily.

First, you need to give the controller name you want to generate.
You must use the shortcut notation like AcmeBlogBundle:Post

Controller name: AppBundle:Demo

Determine the format to use for the routing.

Routing format (php, xml, yml, annotation) [annotation]:

Determine the format to use for templating.

Template format (twig, php) [twig]:

Instead of starting with a blank controller, you can add some actions now. An action
is a PHP function or method that executes, for example, when a given route is matched.
Actions should be suffixed by Action.

New action name (press <return> to stop adding actions): indexAction
Action route [/index]:
Template name (optional) [AppBundle:Demo:index.html.twig]:

New action name (press <return> to stop adding actions):


  Summary before generation


You are going to generate a "AppBundle:Demo" controller
using the "annotation" format for the routing and the "twig" format
for templating

Do you confirm generation [yes]?


  Controller generation


Generating the bundle code: OK


  Everything is OK! Now get to work :).

対話形式で入力する項目について

ログを見つつ、入力する項目について説明します。 実際に入力した項目は、以下の6項目です。

  • Controller name:
  • Routing format (php, xml, yml, annotation) [annotation]:
  • Template format (twig, php) [twig]:
  • New action name (press to stop adding actions):
  • Action route [/index]:
  • Template name (optional) [AppBundle:Demo:index.html.twig]:

Controller name:

コントローラを作成するバンドル先とコントローラクラス名を指定します。 アクション名を除いた論理コントローラ名で指定します。 今回は、AppBundleDemoControllerを作成したかったので、 AppBundle:Demoと入力しました。

Routing format (php, xml, yml, annotation) [annotation]:

ルーティングの設定フォーマットを指定します。 デフォルトでは、annotationが指定されています。 今回は、annotationを利用したかったので、そのままEnterで進めました。

Template format (twig, php) [twig]:

ビューで利用するテンプレートのフォーマットを指定します。 デフォルトではtwigが指定されています。 今回は、twigを利用するので、そのままEnterで進めました。

New action name (press to stop adding actions):

アクション名を指定します。 アクションとは、コントローラークラス内に定義されるコントローラの役割を持ったメソッドのことを指します。 xxxActionと入力すると、入力した命名でController name:で設定したコントローラクラスにアクションが定義されます。 今回は、indexActionという名前でActionを作成したかったので、indexActionと入力しました。

Action route [/index]:

設定したActionのマッチするルートを設定します。 デフォルトでは、Action名からActionを除いた文字列がルート名になります。 今回は/indexにマッチさせたかったので、そのままEnterで進めました。

Template name (optional) [AppBundle:Demo:index.html.twig]:

ビューのテンプレートの名前を指定します。 デフォルトでは論理コントローラ名の末尾にテンプレートフォーマットが追記されたものが、テンプレート名になります。 変更する必要も特になかったので、そのままEnterで進めました。


生成したコントローラの確認

一通り入力が済むと、AppBundleDemoControllerが生成されていることを確認できます。

$ ls src/AppBundle/Controller
Admin                  BlogController.php     DemoController.php     SecurityController.php

DemoControllerを見てみます。 ルーティングが/indexにマッチするようにAnnotationで設定され、 indexActionが定義されていることを確認できます。

$ cat src/AppBundle/Controller/DemoController.php

<?php

namespace AppBundle\Controller;

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

class DemoController extends Controller
{
    /**
     * @Route("/index")
     */
    public function indexAction()
    {
        return $this->render('AppBundle:Demo:index.html.twig', array(
            // ...
        ));
    }

}

処理は特に自身で定義していませんが、render()メソッドを用いて、 src/AppBundle/Resources/views/Demo/index.html.twigをレンダリングし、 Responseオブジェクトとして返す処理を書かれています。

参照するviewを指定するときは、論理コントローラに似た記述で バンドル名:コントローラクラス名:ビューファイル名と記述すると、 src/バンドル名/Resources/views/コントローラクラス名/ビューファイル名 を参照することができます。

ルーティングの確認

consoleでdebug:routeすると、下から4番目にルーティングに登録されていることが確認できます。 ルーティング名は、指定しなければ論理コントローラ名の:_に置換したものがルーティング名になります。

$ bin/console debug:route
 -------------------------- ---------- -------- ------ ----------------------------------------
  Name                       Method     Scheme   Host   Path
 -------------------------- ---------- -------- ------ ----------------------------------------
  _wdt                       ANY        ANY      ANY    /_wdt/{token}
  _profiler_home             ANY        ANY      ANY    /_profiler/
  _profiler_search           ANY        ANY      ANY    /_profiler/search
  _profiler_search_bar       ANY        ANY      ANY    /_profiler/search_bar
  _profiler_info             ANY        ANY      ANY    /_profiler/info/{about}
  _profiler_phpinfo          ANY        ANY      ANY    /_profiler/phpinfo
  _profiler_search_results   ANY        ANY      ANY    /_profiler/{token}/search/results
  _profiler                  ANY        ANY      ANY    /_profiler/{token}
  _profiler_router           ANY        ANY      ANY    /_profiler/{token}/router
  _profiler_exception        ANY        ANY      ANY    /_profiler/{token}/exception
  _profiler_exception_css    ANY        ANY      ANY    /_profiler/{token}/exception.css
  _twig_error_test           ANY        ANY      ANY    /{_locale}/_error/{code}.{_format}
  admin_index                GET        ANY      ANY    /{_locale}/admin/post/
  admin_post_index           GET        ANY      ANY    /{_locale}/admin/post/
  admin_post_new             GET|POST   ANY      ANY    /{_locale}/admin/post/new
  admin_post_show            GET        ANY      ANY    /{_locale}/admin/post/{id}
  admin_post_edit            GET|POST   ANY      ANY    /{_locale}/admin/post/{id}/edit
  admin_post_delete          DELETE     ANY      ANY    /{_locale}/admin/post/{id}
  blog_index                 GET        ANY      ANY    /{_locale}/blog/
  blog_index_paginated       GET        ANY      ANY    /{_locale}/blog/page/{page}
  blog_post                  GET        ANY      ANY    /{_locale}/blog/posts/{slug}
  comment_new                POST       ANY      ANY    /{_locale}/blog/comment/{postSlug}/new
  app_demo_index             ANY        ANY      ANY    /{_locale}/index
  security_login             ANY        ANY      ANY    /{_locale}/login
  security_logout            ANY        ANY      ANY    /{_locale}/logout
  homepage                   ANY        ANY      ANY    /{_locale}
 -------------------------- ---------- -------- ------ ----------------------------------------

補足ですが、Pathの頭に/{_locale}がついているのは、app/config/routing.ymlにprefixが設定されているからです。

app/config/routing.yml (7行目~ )

app:
    resource: '@AppBundle/Controller/'
    type:     annotation
    # annotationで読み込んだルートには、
    # prefixでlocaleをつけるようにしてある
    prefix:   /{_locale}
    requirements:
        _locale: '%app_locales%'
    defaults:
        _locale: '%locale%'

ページの確認

/ja/indexへアクセスしてみると、Welcome to the Demo:index pageと表示されます。

f:id:symmt9302:20161102090911p:plain

おわりに

今回は、コントローラの役割についての紹介とコマンドを用いたコントローラ生成について紹介しました。 次回は、引き続いてコントローラについて、Responseオブジェクトを生成する際に利用するメソッドなどを紹介したいと思います。

また、この記事は、@mrtryの勉強の一環で書いています。 お気づきの点がありましたら、コメント等でご指摘いただければ幸いです!

参考