OTOBANK Engineering Blog

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

オフィスで手巻き寿司&勝手丼♪開発チームで月次「締め会」はじめました!

こんにちは!デザイナーのぺろこです。

タイトル通りですが、開発チームで月次の締め会をはじめてみました!

OTOBANK開発チームでは、"イケてるリリースを自慢(どやぁ!)しあう会"という意味で、
締め会を「どや!会」という名前で呼んでいます。

「どや!会」とは

  • 月に1回、月末に開催
  • 開発メンバー全員でご飯を食べながら、今月のリリースをふりかえります
  • 1ヶ月の働きをねぎらうべく、毎回おいしいご飯を用意しています

「どや!会」のアジェンダ

  1. 乾杯
  2. 今月のリリース共有
  3. 雑談

「どや!会」は乾杯からはじまりますw
リリースの共有では、GithubのcloseされたPullRequest一覧と事前資料をベースに行います。
事前資料には自主的に書き込む項目があり、「これはイケてるっしょ!」というものを各自書き込み&発表(どやぁ)してもらいます。
発表のあとは、もぐもぐ・・・うまぁ・・・( ´ ▽ ` )

9月の「どや!会」ごはん

そろそろイクラが美味しい季節・・・ということで、CTOの@riafさんが大量のイクラを漬けてきてくれました!
北海道では自宅でイクラを漬けるのが当たり前だそうです。すごい。

f:id:peroko_tokyo:20161004005019j:plain みんなで手巻き寿司、楽しい!

f:id:peroko_tokyo:20161003193208j:plain どんなにかけてもイクラがなくならない・・・・しらすとの相性も抜群!!

f:id:peroko_tokyo:20161003193216j:plain 釧路のお酒もおいしい〜!

f:id:peroko_tokyo:20161003193245j:plain 釧路のご当地グルメ「スパカツ」は@symmt9302の手作り!

どや会のねらい

  1. 今月も頑張ったよね!と自分たちを労って、来月の活力にすること
  2. 良い仕事を共有して高め合うこと

オトバンクの場合は、自社サービスの開発がほとんど。
自社サービスの開発は、受託開発の仕事と比べると、

  • 「改善」という大きなタスクが消えることはない
  • 受注金額がないので、開発したものの価値が見えにくい

という点で、「達成感」や「区切り」みたいなものが作り出しにくいなーと感じます。

人間は機械じゃないので、モチベーションを維持するには何らかの仕組みが必要ですよね。
そこで、締め会という仕組みを取り入れてみた次第でした。

6月からはじめた試みなので、実は9月で4回目。明確に「ooがxxになった!」とは書きづらいのですが、さっそく雰囲気が変わったというか、"チーム感"のようなものが生まれてきている感じがしています。

いいことだな〜😸
来月の「どや!会」は、どんなメニューにしようかな。

Symfony2での処理の流れについてまとめた

こんにちは!@mrtryです。 最近、スモークチーズのオイル漬けを作りました! チーズを燻製して、オリーブオイルにつけるだけでですが、とても美味しく、ハイボールが進みます。 燻製してみたいなぁ〜と、考えている人には、おすすめです!

さて、「Symfony2入門」2回目の投稿です。 前回は、全体としての基本動作をざっくり説明しました。 今回は、RequestオブジェクトからResponseオブジェクトを生成するまでの処理をもう少し掘り下げて行きたいと思います。

全体の処理の流れ

公式ドキュメントのフローチャートを参考に全体の処理の流れを追ってみましょう。 全体の処理の流れ

Symfonyでは、すべてのリクエストの処理が以下の流れに従っています。

  1. URLへのアクセスをフロントコントローラで受ける
  2. フロントコントローラ(app.php)が受けたリクエストからRequestオブジェクトを生成する
  3. カーネルがリクエストがあったURLをルータに知らせる
  4. ルータがURLにマッチするコントローラなどをまとめた「情報」をカーネルに返す
  5. カーネルがルータから受け取った情報を元にコントローラを実行する
  6. コントローラがResponseオブジェクトを返す

データを処理をするのはコントローラ、どのコントローラかを選択するのはルータ、コントローラとルータに依頼をするのがカーネル、と役割が分かれています。 それぞれについて、役割を確認していきます。

カーネル

カーネルは、クライアントからのRequestオブジェクトを受け取り、コントローラにそれを渡し、Response オブジェクトに変換します。 コードでは、フロントコントローラ(app.php(app_dev.php))にて呼びだされています。 気になった方は、前回の記事の終わりの方を見てみてください

ルータ

「このURLにアクセスがあったら、このコントローラを呼び出す」というURLのパスとコントローラを関連付け(マッピング)を行っています。 マッピングの設定は、設定ファイルかコントローラのPHPファイルに書くことで設定できます。

例として、前回も参考にしたsymfonyのデモアプリケーションの設定ファイルを見てみます。 設定ファイルは、app/config/routing.ymlにあります。 http://127.0.0.1:8000/にアクセスがあった時、 default/homepage.html.twigを引数に、FrameworkBundleTemplateControllerにあるtemplateActionを実行するという設定が書かれています。

homepage:
    path: /{_locale}
    requirements:
        _locale: '%app_locales%'
    defaults:
        _controller: FrameworkBundle:Template:template
        template:    default/homepage.html.twig
        _locale:     '%locale%'

コントローラ

コントローラは、Requestオブジェクトから情報を取得し、Responseオブジェクトを生成する関数群です。 ページのレンダリングやリダイレクト、画像などwebにアクセスして取得できる情報を任意にResponseオブジェクトとして返すことができます。

こちらも、実際にコントローラを見てみましょう。 見るコントローラは、先ほどルータで設定されていた、FrameworkBundleにあるTemplateControllerです。 vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.phpに実物があります。 このコントローラでは、引数で受け取ったファイルをレンダリングして、Responseとして返すという処理をしています。

<?php
namespace Symfony\Bundle\FrameworkBundle\Controller;

use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\HttpFoundation\Response;

/**
 * TemplateController.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TemplateController implements ContainerAwareInterface
{
    use ContainerAwareTrait;

    /**
     * Renders a template.
     *
     * @param string    $template  The template name
     * @param int|null  $maxAge    Max age for client caching
     * @param int|null  $sharedAge Max age for shared (proxy) caching
     * @param bool|null $private   Whether or not caching should apply for client caches only
     *
     * @return Response A Response instance
     */
    public function templateAction($template, $maxAge = null, $sharedAge = null, $private = null)
    {
        /** @var $response \Symfony\Component\HttpFoundation\Response */
        $response = $this->container->get('templating')->renderResponse($template);

        if ($maxAge) {
            $response->setMaxAge($maxAge);
        }

        if ($sharedAge) {
            $response->setSharedMaxAge($sharedAge);
        }

        if ($private) {
            $response->setPrivate();
        } elseif ($private === false || (null === $private && ($maxAge || $sharedAge))) {
            $response->setPublic();
        }

        return $response;
    }

あわせて、実行時にレンダリングされるファイルを見てみましょう。 ルータの設定で、引数には、default/homepage.html.twigが設定されていました。 実際のファイルパスとしては、app/Resources/views/default/homepage.html.twigにあります。 http://127.0.0.1:8000/にアクセスされた際は、このファイルがレンダリングされて表示されます。

{% extends 'base.html.twig' %}

{% block body_id 'homepage' %}

{#
    the homepage is a special page which displays neither a header nor a footer.
    this is done with the 'trick' of defining empty Twig blocks without any content
#}
{% block header %}{% endblock %}
{% block footer %}{% endblock %}

{% block body %}
    <div class="page-header">
        <h1>{{ 'title.homepage'|trans|raw }}</h1>
    </div>

    <div class="row">
        <div class="col-sm-6">
            <div class="jumbotron">
                <p>
                    {{ 'help.browse_app'|trans|raw }}
                </p>
                <p>
                    <a class="btn btn-primary btn-lg" href="{{ path('blog_index') }}">
                        <i class="fa fa-users"></i> {{ 'action.browse_app'|trans }}
                    </a>
                </p>
            </div>
        </div>

        <div class="col-sm-6">
            <div class="jumbotron">
                <p>
                    {{ 'help.browse_admin'|trans|raw }}
                </p>
                <p>
                    <a class="btn btn-primary btn-lg" href="{{ path('admin_index') }}">
                        <i class="fa fa-lock"></i> {{ 'action.browse_admin'|trans }}
                    </a>
                </p>
            </div>
        </div>
    </div>
{% endblock %}

例: トップページが表示されるまでの処理の流れ

実際に、ビルドインサーバを立ち上げて、http://127.0.0.1:8000/にアクセスすると、homepage.html.twigがレンダリングされたものが表示されます。

f:id:symmt9302:20160816104405p:plain

アクセスしてから、ページが表示するまでの流れを追うと以下のようになります。

  1. ユーザがhttp://127.0.0.1:8000/にアクセスする
  2. リクエストフロントコントローラ(app.php)が受け、カーネルにRequestオブジェクトを渡す
  3. カーネル/にアクセスがあったと、ルータに知らせる
  4. ルータが、設定ファイルを元にTemplateController.phpのtemplateActionを実行すれば良いと情報をカーネルに知らせる
  5. カーネルが情報にあったコントローラのアクションを実行する
  6. アクションした結果、ページがレンダリングされ、その情報はResponseオブジェクトとして生成される
  7. Responseオブジェクトを返す
  8. ユーザがページを見る

おわりに

今回は、Symfonyの処理流れについて、書きました。 次回は、ルータの設定方法について書きたいと思います。

参考

ログレベルちゃんと使い分けてますか?

2回めましてこんばんわ。 @kalibora です。 焼き鳥のカシラは塩、シロはタレ派です。

さてさて、みなさまはプログラム中でログを吐くときのログレベルをどのように使い分けておりますでしょうか。

error 一択?

error info debug くらい?

そうだとして、それってどう使い分けていますか?

そしてそれをどのように気づき、どう対処していますか?

ログレベルの種類

まず、ログレベルはどのようなものが定義されているのか確認したいと思います。

PHP のデファクトスタンダートである PSR-3 で定義されているログレベルは下記の通りです。

ログレベル 説明
emergency System is unusable.
システムが使用不可能な状態
-
alert Action must be taken immediately.
直ちになんらかの対処の必要がある
Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.
完全にWebsiteがダウンした、データベースが使用不可能など。SMSで通知して起きる必要がある
critical Critical conditions.
危機的な状態
Application component unavailable, unexpected exception.
アプリケーションコンポーネントが使用不可能、予期しない例外。
error Runtime errors that do not require immediate action but should typically be logged and monitored.
直ちに対処する必要のない実行時エラーだが、通常はログに記録して監視すべき
-
warning Exceptional occurrences that are not errors.
エラーではない例外的な出来事
Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
廃止予定のAPI、中途半端なAPI、必ずしも間違っていないが望ましくないものの使用
notice Normal but significant events.
正常だが、重要な事象
-
info Interesting events.
興味深い事象
User logs in, SQL logs.
ユーザーのログインやSQLログ
debug Detailed debug information.
詳細なデバッグ情報
-

この定義をベースとして、サービスやシステム、チーム単位などで具体的にどう使うかや通知方法をあらかじめ決めておくと便利ですよね。

一例として

一例として弊社のあるサービスでは下記のように定義しています。

ログレベル 定義 使用例 本番環境でのロギング 通知方法
emergency 基本的に使わない - する Slackのalertチャンネルに通知
alert 1回発生したら必ずなにかしらの対応が必要なもの ここには使用例が書いてある する Slackのalertチャンネルに通知
critical 基本的には1回発生したら対応や調査が必要なもの ここには使用例が書いてある する Slackのerrorチャンネルに通知
error システムが原因で正常に処理ができないが、頻発しなければ未対応でもいいもの ここには使用例が書いてある する Slackのerrorチャンネルに通知
warning ユーザー起因で正常に処理ができなかったようなもの ここには使用例が書いてある する 通知しない
notice 正常に処理しているが、記録しておきたい重要なもの ここには使用例が書いてある する 通知しない
info そこまで重要じゃないが記録しておきたいもの ここには使用例が書いてある 一部する 通知しない
debug 開発のデバッグ時に必要な情報 ここには使用例が書いてある しない 通知しない

このように決めておけば、

ここでエラーが起きたら必ずデータのリカバリ作業が必要になるから alert だな。

とか、

ここでのエラーは問題だけど一時的な原因の可能性もあるし、再実行可能だから error だな。

とか、

正常なルートじゃないけど、システムには問題なくエンドユーザー起因だから warning だな。

などと判断できると思います。

そしてちゃんとレベルが定義できていれば、レベルごとに通知方法もわけられるので、 即対応が必要なものもすぐに気づくことができますよね。

ここで重要なのは定義をして各人での意識を合わせておく。ということで、この定義が正解だと言っているわけではないです。念のため。

おまけ: monolog の processor の話

ほとんどの phper のみなさんはロギングに Monolog を使っているかと思いますが、その際に Processor は使ってますでしょうか?

あらかじめちょっと定義・設定しておくだけでログに各種の付加情報を記録できる便利なやつです。

デフォルトでは

  • GitProcessor
    • git のブランチ名
  • IntrospectionProcessor
    • ファイル名, 行番号, クラス名, メソッド名
  • MemoryPeakUsageProcessor
    • メモリの最大使用量
  • MemoryUsageProcessor
    • メモリ使用量
  • ProcessIdProcessor
    • プロセスID(これがないとどのログが1つのリクエストなのか分からず混ざっちゃって大変ですね)
  • PsrLogMessageProcessor
  • TagProcessor.php
  • UidProcessor.php
  • WebProcessor.php
    • URL, IP, リファラーなど

が用意されています。

デフォルトで用意されていないもので必要になりそうなものとしては、ユーザーを特定するIDなどがあると思いますが、

そういったものも自前で Processor を定義することで簡単に付加できるので、Monolog を使っているのであれば是非使ってみるといいんじゃないでしょうか。

それではまた。