またどこかのCTOになった人のブログ

またどこかのCTOになった人が書いてます。最近はCakePHP3とConoHaのネタが多いです。

CakePHP3でいつでも出力できるログ用コンポーネントを作ってみた。

2017年5月もそろそろ終わりです。
今の会社にいるのも残り1ヶ月になりました。
有給をどうやって消化するかがテーマです。


今回のネタはパクリです。
以前、どこかの偉い方が書いていたブログを見ながら処理を作った記憶があります。
そのブログをどこで見たのかを残しておらず、申し訳ないなと思いつつ書いてみます。


CakePHP3のログはdebug.logとerror.logの2パターンが、デフォルトとして準備されています。
debug.logの方はapp.phpデバッグモードによって出力されるされないが決まります。
CakePHP2で言うとこのデバッグレベルとかいうやつです。
CakePHP3ってtrue / falseの2パターンしかないんですかね? よく知りませんが。


で。
このログ、開発中だとあまり意識しない(個人的なアレ)のですが本番運用だとちょっと気をつけないといけません。
「障害発生した!」「エラーログを確認だ!」というような状況になった時、まぁエラーログ見るのですが「なんかよくわかんねーな」ということも多々あるんじゃないかと思います。
また「開発環境じゃ発生しなくて本番環境だけ発生する。どういうこと?」のようなこともゼロではないと思います。
多分。


そんな時に「よーし。本番環境にデバッグログ埋め込んで確認するぞ」と思ったとしても、通常本番運用する場合はデバッグモード解除しているのでdebug.logは出ないと思います。
じゃデバッグモードを戻してログを仕込むというのもアレです。
つまり「デバッグモード戻さずに任意のとこでデバッグログ出したい」のような時に使えるんじゃないかなと。

そもそものログ設定

自分はいつもこんな風にしています。
修正する箇所はapp.phpの中です。「LOG」とかで検索すると出てきます。

<?php
 'Log' => [
        'debug' => [
            'className' => 'Cake\Log\Engine\FileLog',
            'path' => LOGS,
            'file' => date('Ymd').'_debug', // 日付毎に出力するようファイル名変更
            'size' => '50MB',                         // 指定したサイズまでログ出力
            'rotate' => 30,                             // 残すログファイル数(らしいです)
            'levels' => ['notice', 'info', 'debug'],
            'url' => env('LOG_DEBUG_URL', null),
        ],
        'error' => [
            'className' => 'Cake\Log\Engine\FileLog',
            'path' => LOGS,
            'file' => date('Ymd').'_debug',
            'size' => '50MB',                         // 指定したサイズまでログ出力
            'rotate' => 30,                             // 残すログファイル数(らしいです)
            'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
            'url' => env('LOG_ERROR_URL', null),
        ],
    ],


こんな感じにしています。
上に「levels」というパラメータがありますが、要はエラーが出た時にエラーレベルがここに指定されたものであれば、ログ出力しますよということです。
デバッグモードを解除していると、notice、info、debugのログは出さない設定です。

errorログの方にlevelを移植してしまえばログ出力されます。
デバッグモード解除してても出るようになりますけど、ウザいと思います。
欲しいのは「要所要所でこの時だけ出したい!」というログ出力処理です。

ログ設定を作る

なのでログ設定を作ってしまいます。
一例としてシステムログとクリティカルログの設定をここに配列で追加します。

<?php
 'Log' => [
        'debug' => [
            // 上と同じ
        ],
        'error' => [
            // 上と同じ
        ],
        'system' => [
            'className' => 'Cake\Log\Engine\FileLog',
            'path' => LOGS,
            'file' => date('Ymd').'_system',
            'size' => '50MB',
            'rotate' => 30,
            'levels' => [],       // ここを空配列
            'scopes' => ['system'],    // systemというスコープを作ってみる
        ],
        'dead' => [
            'className' => 'Cake\Log\Engine\FileLog',
            'path' => LOGS,
            'file' => date('Ymd').'_dead',
            'size' => '50MB',
            'rotate' => 30,
            'levels' => [],  // ここを空配列
            'scopes' => ['dead'],    // deadというスコープを作ってみる
        ],
    ],


こんな風に書くと、「systemってスコープのログとdeadってスコープのログの場合は、指定したファイルにログ出力しますよ」という設定が出来上がります。
本当はもっと丁寧な解説だったはずなのですが、覚えてないので端折ります。ごめんなさい。
levelsを書かないのが大事だったはずです。

作ったログ設定を呼び出すコンポーネントを作成する

これは大したものじゃないのでサラっと。

<?php
  namespace App\Controller\Component;
  use Cake\Controller\Component;
  use Cake\Log\Log;     // これを忘れずに
  
/**
 * ログ出力コンポーネント
 */
class LogComponent extends Component {
  
  /**
   * システムログ出力処理
   * @param type $params
   */
  public function systemLog($params) {

    // ログ出力(systemスコープを指定)
    Log::info($params, ['scope' => ['system']]);
    return;
  }

  /**
   * エラーログ出力処理
   * @param type $params
   * @return type
   */
  public function deadLog($params) {

    // ログ出力(deadスコープを指定)
    Log::error($params, ['scope' => ['dead']]);
    return;
  }
}


何となくそれっぽいコンポーネントです。
「Log::」の後のinfoとかerrorとかはログに出て来ます。
「何でもいいんじゃないかな! ログさえ出てくれば」という精神で適当に書いてます。


引数に指定している$paramsは何が入っていても大丈夫です。
文字列だろうが、配列だろうが。
用途に応じて適したものを指定するといいと思います。


自分はこのコンポーネントをAppControllerの最初の方で宣言しておいて、使いたくなったら呼び出して使います。
宣言は他のコンポーネントと同じようにこんな感じで書いてます。

<?php
 namespace App\Controller;
 use Cake\Controller\Controller;
 use Cake\Event\Event;

 class AppController extends Controller {
  public function initialize() {
   $this->loadComponent('RequestHandler');
   $this->loadComponent('Flash');
   $this->loadComponent('Security');
   $this->loadComponent('Csrf');

   // 呼び出し
   $this->loadComponent('Log');

   // 以下略
  }
 }


使う時は普通のコンポーネントと同じです。

<?php
 // 出力したい内容(配列でとりあえず書いてみます)
 $putout = [
  '日付' => date('Y-m-d H:i:s'),
  '内容' => 'なんか致命的なエラーでた'
 ];

 // ログ出力
 $this->Log->deadLog($putout);


出力はlogsディレクトリ配下に、こんな感じで出ると思います。

 2017-05-28 18:47:38 Info: Array
 (
  [日付] => 2017-05-28 18:47:38
  [内容] => なんか致命的なエラーでた
 )


当然、AppControllerで呼ばずに個別のコントローラで呼んでも使えます。
自分の場合はめんどくさいのでこのような感じにしています。


mailスコープなんかを作って、メール送信時の宛先とか内容とかを入れたりすると
それだけでメール送信ログもできると思います。