どこかのCTOだった人のブログ

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

CakePHP3のtmpディレクトリ配下のキャッシュをコンソールから全部消してみた。

小ネタです。

CakePHP3でプロジェクトを作成中、テーブル定義が変わった場合tmpディレクトリ配下のキャッシュを消さないといきなりエラーが出てきます。
すっかり忘れててドキドキすることもあります。
そんな時のコマンドです。


とりあえず、プロジェクトがあるディレクトリに移動します。
自分の場合はMAMPに構築しているので、この辺です。

cd /Application/MAMP/htdocs/sample-project

ここに移動するとbinディレクトリがありますので、以下のコマンドを叩きます。

bin/cake cache clear_all


するとキレイに消えます。
便利。

公式で紹介されてました。
こちらもご覧くださいませ。

Cache Shell

CakePHP3でドキュメントの有効期限切れに対応してみた。

CakePHP3に限った話ではないのですが。
例えばブラウザバックした際に「ドキュメントの有効期限切れ」とかいうエラーが出て、頭がイーッとすることがあります。
これの対応です。


発生する理由はPHPの初期設定だと、headerに「no-cashe」を送っているからです。
ですので、そこをごにょごにょすると対応完了できます。
今回はCakePHP3のプロジェクトから、その設定をする方法になります。


上で書いていますがheaderに送ってあげればそれで終わる話です。
あとは送るタイミングの話。
CakePHP3だとbeforeRender辺りで実行するといいと思います。
AppController.phpを開いて、該当箇所に埋め込みます。

<?php
 public function beforeRender(Event $event) {
  // バージョンによりますが、ここは最初から記載があると思います
  if (!array_key_exists('_serialize', $this->viewVars) &&
   in_array($this->response->type(), ['application/json', 'application/xml'])) {
    $this->set('_serialize', true);
   }
  }

  // 2017/06/22 パクり元のエンジニアから訂正が入ったのでここから修正
  // ドキュメントの有効期限切れ対応として、以下を追加します
  header("Cache-Control: no-cache, no-store, must-revalidate");
  header("Pragma: no-cache");
  // ここまで修正しました
 }

もし、ControllerやAction毎に切り分けたくなったら、AppControllerにprotectedでbool型の変数でも作成して上記の箇所にtrueの場合のみ実行するように修正。
あとはControllerなどで作成した変数をfalseに変えればいいかと思います。


今回は弊社エンジニアが作ってた処理をそのまま記事にしてみました。
すいません。今日もパクリです。

ブログタイトルを変えてみた。

7月から今の会社を辞め、新しい会社に移籍します。
CTOでもなくなるので、ブログタイトルを変えてみました。
CTOじゃなくなったのはもう少し前の話なんですが、それはまぁいいです。


正直、辞めることになった時に色々とお声掛け頂けまして。
その中には「すげー。こんなとこからお声が!」という会社もありました。
なのですが、出来上がっている会社というと語弊があるかもしれませんが、カタチになっていない会社でもう一度自分を試してみたいなという気持ちが強く、今回のお話しを受けさせて頂くことになりました。

今度の会社はエンジニアが1人もいない会社で、自分が1人目で唯一のエンジニアです。
Web系の会社ではあるのですが、今はWordpressを使ってごにょごにょみたいな業務をやっていて。
でも将来はポータルサイトを作りたい! という野望を持っている会社です。

何もないゼロのところからDB定義作ってサーバ構成考えて、プログラム書いて。
それを動かしてああでもない、こうでもないという模索を繰り返す日々が始まるんじゃないかと思います。
色々苦労もあるかもしれませんが、そこを含めても楽しそうだと期待しています。


ブログの内容は特に変わりません、多分。
という報告っぽい何かでした。

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

apacheのアクセスログから日付指定でアクセス数を解析してbotを遮断してみた。

とある日、突然の高アクセスにサーバが悲鳴をあげました。
個人的には「アクセスたくさんきたぜ」とちょっと喜んでしまうのですが、そうも言ってられません。
とりあえずapacheアクセスログを見ると、どうもbingのbotがきている模様。
何という節操の無さなのか。

こんな時「めんどくさいからアクセス多いIPアドレス弾く」ことをよくやります。
そのためにはどのIPアドレスからのアクセスが多いのかな? というのを調べる必要があります。
以下に書くコマンドは弊社のインフラエンジニアが教えてくれたのですが、とても便利です。

下準備

  1. apacheaccess.logを別名でコピる
  1. 対象日を決める
  1. 以下のコマンドを実行する
  grep "対象日" apachelog.txt | awk '{gsub(/"[^"]+"/, "\"***\"", $0); gsub(/\[/, "", $0);  print$0; }' | awk -F'[ ,]' '{print $13}' | sort | uniq -c > checkip.txt

対象日の部分はログの出力の仕方で変わるかもしれません。
「21/May/2017」みたいなイメージですが、この辺は環境に応じてaccess.logを見てください。

最後のcheckip.txtは結果をリダイレクトでファイル出力しているだけです。
出力されたファイルにはIPアドレスとアクセス回数が出ていると思います(多分)
あとは出力したファイルをExcelでスペース区切りだかタブ区切りだかでファイルを読み込んで
アクセス回数の列に対してソートをかければ、アクセス回数が多いIPアドレスがわかるかと思います。

偉そうに書いてますが、自分で使う時は日付変えてコピペして実行しているだけです。
インフラできる人って本当に尊敬します。

余談なんですが、アクセス回数が多いIPアドレスは一応どこからのアクセスか調べるのが吉です。
以前、とりあえず片っ端から遮断したら、その中にシステムを死ぬほど触ってたクライアントがいたことがあったので。

そんなに触らなくてもいいじゃないとちょっとだけ思いました。

CakePHP3で画像登録しようとしたら怒られた。

久しぶりの更新を。
何かしていたかと言われたら特に何もしていないのですが、7月から新しい職場で働くことになったりしました。
となると、このブログのタイトルもどうなんだろうと思うのですが、それはそれということにしておきます。

今回は恥ずかしいネタです。

・やりたかったこと
 フォームにfile属性で画像を指定してバイナリデータで保存したい

・発生したこと
 Unexpected field 〜
 Missing field 〜
 と怒られた

前にもこんなことがあったなと10分ぐらい悩んでいたんですが、CSRFが有効になってる時のアレでした。
原因はフォームの書き方です。パラメータが足りなかったので怒られていました。
CSRF有効にしているのでフォームヘルパーで書くところまではよかったのですが。
こんな時は以下のパラメータが多分抜けているので追加すればOKです。

<?php
   <?php echo $this->Form->create(null, [
          'type' => 'post', 
          'enctype' => 'multipart/form-data',    // 多分これがない
          'url' => [
                  'controller' => 'users', 
                  'action' => 'exec'
          ]
    ]); ?>

    // フォームを形成する要素などなど

    <?php echo $this->Form->end(); ?>

CSRFを解除する前に確認するといいんじゃないかと思います。

mysql workbenchで「cannot close sql ide while being busy」とか出てきて困った。

ローカル環境のDBなんですが、テーブルにALTER文をかけたら警告っぽいのが出まして。
その後からテーブル内容などが見れなくなりました。
で、mysql workbenchの左下辺りを見ると「cannot close sql ide while being busy」の文字。
何かよくわかりませんが、何かが悪さしているようでした。

グーグル先生にお申し立てをしてみると、以下のページがヒットしました。

MySQL Bugs: #83238: mysqlworkbench won't close Cannot Close SQL IDE while being busy

キャッシュが悪さしているようだったので、上記ページの通りにキャッシュを削除してみたら直りました。
Mac環境で記載されていますが、Windowsも似たような場所にあると思います。

と、覚書。