またどこかでCTOっぽいことやってる人のブログ

フリーランスを経て、またどこかでCTOっぽいことをやってる人が書いてます。何か色々やってます。

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

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も似たような場所にあると思います。

と、覚書。

気がついたら2017年になっていた。

2017年になっていました。
自分は毎年元旦に「今年はこれを個人的に作ろう」という目標ぽいことを立てるタイプなのですが、2016年は目標未達成でした。
ですので「2016年にできなかったアレを2017年にローンチしよう」という目標にすることにしました。
アレというのはチマチマ作ってるアレです。

従事している業務の方はなかなか多忙を極めておりまして、なかなか時間が取れないのが悩み。
なのですが、業務は業務、これはこれという区分けで今年も何か作ってみようと思っています。

プロダクトを作るというのはなかなか大変で、いわゆるスタートアップですと「えいやっ!」ととりあえず作ってローンチ。
それを頑張って拡大させるというところもあると思います。弊社もどちらかというとそうです。
しかしながら、やっぱりサービスが大きくなっていくと「あの時、ちゃんと考えて作ればよかったな」と思うことが多々あります。
ローンチした時はよかったんです。
あの時はそれで正解だった、はずなんです。
が、気がついたら「やってしまった…」となることが最近増えてきました。

サービスが拡大している証拠でもあるので、そういう観点からだとアリなんですが過去の自分を戒めたい気持ちにもなります。
という経験をして、次に作るサービスであったり機能だったりなんかは「何か将来的にこんな展開になるかもしれないな」という心持ちで作るようになりました。

これを弊社では「エスパースキルを発動させて開発」みたいに呼んでいます。
今年も発動させまくりたいと思います。

CakePHP3でEntityからDate型のデータを日付計算して取得してみた。

日付の比較とか計算とかあまり好きではありません。
いつもグーグル先生にお申し立てをしてしまいます。
そんな自分が今回やったことはタイトルの通り。

■やりたかったこと
 Date型のデータの1日前のデータを取得したかった。

■やったこと
 Entityで計算した結果を返すようにした。

Entityのgetterでやりたかったんです。
実際に使う時はViewの方だったのですが、Viewにごちゃごちゃ書きたくなかったので。
結局、こんな風にしてみました。

<?php
 protected function _getPastSampleDate() {
    
  if (isset($this->_properties['sample_date'])) {
    return date('Y-m-d', strtotime('-1 day', strtotime($this->_properties['sample_date']->format('Y-m-d'))));
    } else {
        return '';
    }
 }


strtotime辺りはよくある日付操作と同じです。
ただフォーマットを掛けて、1日分マイナスしているだけです。
使う時は普通のEntityと同じです。

<?php
 if ($sampleObject->past_sample_date > date('Y-m-d')) {
   // 何か処理 
 }

これでやりたいことができました。


そう言えば。
CakePHP3で開発する時、View側でdatetimepickerを使うと日付表示フォーマットが様子おかしくなります。
犯人はfrozentimeなのですが、これを回避したい時はconfig\bootstrap.phpに以下を書くといいです。

 // 日付型フォーマットのデフォルト出力設定
 \Cake\I18n\Time::setToStringFormat('yyyy-MM-dd HH:mm:ss');
 \Cake\I18n\FrozenTime::setToStringFormat('yyyy-MM-dd HH:mm:ss');
 \Cake\I18n\Date::setToStringFormat('yyyy-MM-dd');
 \Cake\I18n\FrozenDate::setToStringFormat('yyyy-MM-dd');


\Cake\I18n~の部分はuseしてしまっても構いません。
今回は便宜的にベタっと書いておきました。

公式の方はJson出力のサンプルが書いてありますが、同じような感じです。
http://book.cakephp.org/3.0/ja/core-libraries/time.html#id3book.cakephp.org