[bug]マルチプロセスでlog4netのファイルローテーション時に一部ログが欠損して困った

福岡の香月です。

log4netのRollingFileAppenderを使って日付で出力ファイルが切り替わる設定をしていたところ、日付が変わって新しいファイルへの出力が始まると先頭に出力されるであろうログがなぜか出力されない現象に遭遇しました。
設定はこうです。
複数のプロセスで同じ設定を用いて、同じファイルに対してログ出力します。

[/crayon]

AppenderはRollingFileAppenderを指定しています。複数プロセスで使用するため、lockingModelはInterProcessLockを指定します。これによりファイルストリームを開きっぱなしにして、mutexで排他制御しながら末端にシークし書き込みを行うことで、複数プロセスから効率的にログ出力を行ってくれます。
複数プロセスから使う場合はこれ以外にもMinimalLockを指定できますが、書き込むたびにファイルのオープンクローズを実施するため速度が求められる場合は不向きでしょう。さらに複数プロセスからの出力が同時に走った場合、片方はオープンできないためログが出力されません。

この設定でビルドし、業務終わりにアプリを起動して一晩中実行させ、翌朝出社して確認しても日付が変わった後のログの内容が足りませんでした。
最初はlog4netの使い方が悪いのか、自分のプログラムにバグがあるのかさんざん悩んだんですがさっぱりわからない。トレース用に埋め込んだログも当然のように出ていないのでどうしたものかと思いました。

しかし、プロセスが1つの場合はちゃんと期待通りのログが出ていました。問題があるのは複数プロセスの場合だけだったのです。

そこでlog4netのロジックを確認することにしました。 幸いなことにソースはこちらから取得できます。
https://github.com/apache/logging-log4net
すると、ファイルが切り替わったあとは最後に書き込んだプロセスのログが先頭に来て、それ以前のログが破棄される作りになっていたのです。

少しずつソースを見てみましょう。今回使っているのはRollingFileAppenderです。日付が変わるときにファイルを切り替える部分は、RollOverTime()という関数であることがわかりました。こうなっています。
https://github.com/apache/logging-log4net/blob/master/src/Appender/RollingFileAppender.cs

[/crayon]

ふむふむ、SafeOpenFile()で新しいファイルをオープンしに行くわけね。そして第二引数がfalseと。そしてSaveOpenFile()の実態はベースクラスFileAppenderにありました。
https://github.com/apache/logging-log4net/blob/master/src/Appender/FileAppender.cs
ここから次の順番で呼び出されていきます。

  • RollingFileAppender.OpenFile()
  • FileAppender.OpenFile()
  • FileAppender.InterProcessLock.OpenFile()
  • FileAppender.LockingModelBase.CreateStream()
[/crayon]

ここでSafeOpenFile()の第二引数で指定されたfalseは、CreateStreamのappendに代入されていることがわかりました。この場合FileMode.Createが指定され、FileStreamインスタンスが作成されます。
ではこのFileMode.Createはどのような動きをするかというと、

オペレーティング システムが新しいファイルを作成することを指定します。ファイルが既に存在する場合は上書きされます。これには Write 許可が必要です。
FileMode.Create は、ファイルが存在しない場合は CreateNew を使用した要求、ファイルが存在する場合は Truncate を使用した要求と等価です。

https://docs.microsoft.com/ja-jp/dotnet/api/system.io.filemode?view=netcore-3.1

複数のプロセスでログ出力を行う場合、FileStreamを最後に作成するプロセスによりそれまでのプロセスが作成したログファイルの中身がTruncateされて、つまり削除されてしまうわけです。
発生している現象とも合致するのでこれが問題であることを確信しました。

対応するためにはRollingFileAppender.RollOverTime()から呼んでるSafeOpenFile()の第二引数をtrueに知ればよいわけです。実際に修正して確認してみると、見事欠損することなく必要なログが出力されていました。
めでたしめでたし。

なお、SafeOpenFile()はRollingFileAppender.RollOverSize()内からも呼び出しているのでここも忘れずにtrueに変更しましょう。

C#でWOL(Wake-on-LAN)

福岡の香月です。

コロナ対応で世の中はリモートワークが進んでいるようです。中程度スペックの中古PCもたくさん売れているようで、在庫が少なくなっているようですね。

自宅作業する場合、会社のPCにリモートデスクトップでつないで作業することも多いと思いますが、気付いたらPCがシャットダウンしていることがあります。間違って電源切ったとか、夜中にWindows Updateが動いたとか。

そんな時はWOL(Wake-on-LAN)ですが、私はこれについて知識が無かったので調べがてらC#プログラムのWOLツールを作ってみました。プラットフォームは.Net Core 3.1です。
以下全文です。どん!

[/crayon]

特徴

  • マジックパケットを飛ばします。マジックパケットとは…
    • UDPで送信するパケット
    • 先頭6バイトに{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }
    • 続けて対象PCのmacアドレスを16回
    • 合わせて102バイトのパケットとなる。
  • exeと同じ名前のiniファイルを設定ファイルとして読み込む
  • 設定ファイルにて以下を設定
    • host=対象PCのIPアドレス
    • post=UDPポート(ルータが許可したポートであれば何でもOK)
    • macアドレス=対象PCのmacアドレス
  • エラーが発生した場合はコンソール上にエラーを表示

プログラムは案外簡単でびっくりしました。
それよりもWOLが使える環境の設定が大変そうですね。BIOS、ネットワークカード、Windowsの設定など、情報はぐぐればいろいろ出てきますので一度お試しあれ。

[Laravel]ビュー

福岡拠点の香月です。

今回はビューです。前回のルートとコントローラで呼び出されたURLに対してPHPでのプログラム処理ができるようになりました。
今回はここで実際にデータを集計して、結果を画面に表示したいと思います。この画面表示部分がMVCモデルのV(View)です。
Laravelの標準テンプレートエンジンはBladeとなっています。

それでは早速コントローラの実装です。
前回の実装ではLaravelのようこそ画面を呼び出していましたが、今回は”top”とう名前のテンプレートを使用して画面を表示するようにします。テンプレートファイルは後で作成します。
app/Http/Controllers/TopController.phpファイルを編集します。

モデルScoreに追加した2つのメソッドを呼び出して得点に関するデータを取得しています(ソースは後ほど)。$daysには試験実施日の一覧が、$scoresには試験の点数データが入ってきます。
$subjectsでは科目一覧を取得しています。
$studentsは配列として定義していて、続くforeachで点数データ列挙しながら二次元配列を作成します。一次元目に生徒名、二次元目に科目ID、その値として点数を設定しています。
最後にreturn view(‘top’)で画面を呼び出すと同時に、topテンプレート内で使用するデータを->with()で指定しています。(->with()の複数回の呼び出しは、一回の->with()の呼び出しに置き換えることができます。その場合、->with()の引数にはキー、バリューの配列を設定します)

実装の先頭で$request->input(‘day’)を使っています。コントローラメソッド(ここでいうindex())ではメソッドインジェクション(依存注入機能)を使ってRequestクラスのインスタンスを取得することができ、ユーザーリクエストの内容を利用できます。

次はScoreにメソッドを追加します。app\Score.phpを編集しましょう。

getDays()では試験日フィールドexam_dayをグルーピングして、日付の昇順に並び替えた結果を取得しています。exam_dayフィールドは時間まで入っているので、日付だけの書式に書き換えて配列に置き換えています。

getList()では引数で指定された試験日だけのデータを取得し、それに対してLEFT OUTER JOINで科目と生徒を連結した結果を返します。

さあ、ここまでくるとあとはビューを作成します。
resource/views/welcome.blade.phpをコピーしてresource/views/top.blade.phpを作成し、中身を編集します。
コントローラの最後で return view(‘top’) を呼び出していましたが、Laravelではこれでresource/viewsフォルダにあるtop.blade.phpの呼び出しと判断してくれます。

画面のタイトル「Score sheet」を表示します。
その下には試験日の一覧$daysを@foreachで列挙して表示します。各試験日はaタグでリンクを作成し、クリックするとその日の表示に切り替わるようにしています。ここでroute(‘top’)を呼び出していますが、前回route/web.phpファイル内でRoute::get(‘/’, ‘TopController@index’)->name(‘top’);と指定した名前ですね。これによりこのリンク先が。/ に設定されます。
最後にtableタグで点数表を表示します。縦に生徒軸、横に科目軸を設定して、各生徒の点数を表示するようにしています。それぞれ@foreachでループしながらテーブルを生成しています。

これで実装が完了しました。実際に画面を表示して見ましょう。
表示するデータはシーダーで用意したデータです。
こんな感じで画面が表示されます。

Bladeテンプレートエンジンでは@foreach以外にも@if, @elseや@switch, @case, @breakなどの制御構文が使えます。

また、テンプレートの継承や@includeなど、他のテンプレートファイルと組み合わせて1つの画面を作成することができます。同じ内容を複数のファイルに書くと、後からの編集が大変ですね。その手間をなくすために積極的に活用しましょう。

[Laravel]ルートとコントローラー

福岡拠点の香月です。

今回はルートとコントローラーを追加します。

ブラウザでURLリンクをクリックしたときにはそれに対応した画面が表示されますが、Webサーバー内ではそのURLに対して表示するデータをモデルから取得し、そのデータをビューで整形してHTMLとして表示します。このモデルとビューの橋渡し役がコントローラーの役目となります。また、要求されたURLをどのコントローラーで処理させるかを指定したのがルート(ルーティング)です。

では早速ルートから指定していきます。laravelプロジェクトのrouteフォルダにweb.phpがあります。これを開いて次のように編集しましょう。

アプリケーションのトップURL(http://server/)が呼び出されたときに、TopControllerクラスのindexメソッドを呼び出すように指定しています。
->name(‘top’)でこのルートに対して名前を付けています。ここでつけた名前は他の場所でURIを指定するときに使うことができます。

次はTopControllerの作成です。シェルで次のコマンドを実行します。

するとapp/Http/Controllers/TopController.phpにファイルが作成されます。
クラスメソッドとしてindex()を追加してあげましょう。

route\web.php内で直接指定していたようこそ画面の表示を、コントローラーを介して表示するように変更しただけのものですが、このようにしておくことでビュー(ここではようこそ画面)で表示したいデータをphpプログラムで集めて、計算して、整形して表示することができるようになります。

上のphp artisan make:controllerコマンドではオプションは指定していませんでしたが、–model=[Model]や–resourceオプションを指定することで、リソースコントローラを作成することができます。
こうすることで作成されるコントローラ用ファイルにindex(), create(), store(), show(), edit(), update(), destroy()の名前が付いた空のメソッドが一緒に作成されます。例えばStudentモデル用のリソースコントローラを作成するにはこう書きます。

また、リソースコントローラを使う場合はroute/web.phpのルート指定を次のようにすることで、URIとアクションが自動的に割り付けられたルーティングが完成します。

Route::resource()の1行で次のルーティングが完成します。

動詞 URI 対応メソッド ルート名
GET /students index students.index
GET /students/create create students.create
POST /students store students.store
GET /students/{student} show students.show
GET /students/{student}/edit edit students.edit
PUT/PATCH /students/{student} update students.update
DELETE /students/{student} destroy students.destory

これはいわゆるRESTful APIとして設定されており、必要なURIが揃っているのでリソース(≒モデル)のCRUD(Create, Read, Update, Delete)管理がはかどるでしょう。

リソースコントローラの考え方は他のWebフレームワークでも同様に使われています。Ruby on Railsでは「rails generate scaffold …」コマンドが使えて同じようなルートを生成してくれますし、CakePHPでもルート指定時にまとめて設定してくれる方法があります。これらからLaravelに来た人にとってはわかりやすいですよね。

[Laravel]シーダー

お久しぶりです。福岡拠点の香月です。

今回はシーダーです。
作成したテーブルに初期データやテストデータをを投入するときに使います。

前回からテーブルを2つ「subjects」「students」を追加しています。

マイグレーションファイルでフィールドを定義して

マイグレーション

これでテーブルが追加されました。
ではここに初期データを投入しましょう。Subjectテーブルは科目、Studentテーブルは生徒、Scoreテーブルは点数です。
まずはシーダーファイルを作成するために、make:seederコマンドを実行します。

すると、Laravelプロジェクトフォルダのdatabase/seedsフォルダにファイルSubjectSeeder.php、StudentSeeder.php、ScoreSeeder.phpが作成されます。

まずはSubjectシーダー。
作成されたシーダークラスファイルには run() メソッドが用意されています。シーダーが呼び出されるとこのメソッドが実行されるようになっているため、ここにデータ投入用プログラムを記述します。
上にuse App\Subject; を追加して、Subject Eloquentモデルを使えるようにするのを忘れずに。

Subjectクラスのインスタンスを作成して、フィールドに値を設定し、save()メソッドを呼ぶことでレコードが追加されます。
このプログラムでは$seeds配列の1つ1つが1レコードとなるようなプログラムになっています。

続いてStudentシーダー。こちらはモデルファクトリを使って、フィールド値をランダム生成します。フィールド値には日本語を使いたいので、config\app.phpを次のように修正。

次にファクトリコード用のファイルを作成します。

作成したファイルdatabase\factories\StudentFactory.phpを次のようにします。

$factory->define()の第二引数のクロージャで、Studentレコード1件分の情報としてフィールド名(キー)と値(バリュー)の配列を返すようになっています。$faker->lastName、$faker->firstName部分がランダム生成の部分。これ以外にもcity、phoneNumber、wordなどいろいろ使えます。便利。誕生日は2011/04/02~2012/04/01までの1年間でランダムに生成されるようにしました。

そしてdatabase\seeds\StudentSeeder.phpです。

モデルファクトリを使って20個のエントリを簡単に作成できます。

database\seeds\ScoreSeeder.phpはこう。

作成したSubject、Studentの全レコードを取得し、ループで回しながら作成していきます。

プログラムができたらシーダーを実行しましょう。実行コマンドはdb:seedです。
実行するクラス名を–class=オプションで指定します。

これは1つずつ実行するコマンドとなりますが、まとめて実行したい場合は最初から用意されているDatabaseSeederクラスを利用するとよいでしょう。
database\seeds\DatabaseSeeder.phpファイル内の run() メソッドからまとめて処理したいクラスを指定して、

db:seedコマンドをオプションなしで実行します。

これでデータ投入が完了しました。

[Laravel]追加のマイグレーション

福岡拠点の香月です。

前回はDBのマイグレーションを行いました。
既にマイグレーション実行済みのテーブルに変更を行いたい場合、アプリケーションが稼働する前であればロールバックしてマイグレーションをやり直せばいいのですが、本稼働後のバージョンアップなどでデータを棄損することなくテーブルにフィールドを追加したいことがあります。この場合、フィールド追加用のマイグレーションを実施することになります。

まずは前回と同じようにマイグレーションファイルを作成しましょう。

これによりdatabase/migrations/(日付)alter_scores_table.phpが作成されますので、ここに追加したいフィールドを記載します。

up()メソッドにマイグレーション実施時のコードとして、scoreフィールドをexam_dayの後ろに追加するようにしています。
もちろんクロージャの中では必要なフィールドを1つだけではなく複数記述できます。文字列フィールド、timestampフィールドなど用途に合わせて追加しましょう。
カラム修飾子には->after()を使用しています。これ以外にもnullを許容する->nullable()、コメント文字列を指定する->comment()などいろいろ使えますよ。

down()メソッドにはロールバック実施時に実行されるコードを記述します。ここ忘れがちなので注意!

マイグレーションファイルを作成したら、マイグレーションを実施します。

マイグレーションが実施されていないファイルを対象に実施されます。同じファイルが2度3度と実施されることはないので安心してください。
実行後に反映されたことを確認しましょう。

 

[Laravel]モデルとマイグレーション

福岡拠点の香月です。

前回はLaravelのプロジェクトを作成しました。
今回はこれにDBへのアクセス設定と、モデルの追加及びDBのマイグレーションを行います。

DBへのアクセス設定はアプリケーションのルートフォルダにある.envファイルで行います。この.envファイルは.env.exampleを元に作成されます。
開発環境、検証環境、本番環境でそれぞれ異なる設定なのでその環境ごとのファイル.env.develop、.env.staging、.env.productionを作成することになるでしょう。リリース時にはリリース環境にあったファイルを.envにリネームします。
※Laravelでは.envはソース管理に含めないことがガイドラインに書いてあります。これはプロジェクトのルートフォルダにある.gitignoreを見ても明らかです。

さて .env を編集する前に、アプリケーションで使用する mysql のユーザーを作成します。また、使用するDBを作成します。

1行目でmysqlにrootユーザーでログインします。
3行目でアプリケーションで使用するデータベースを「scoresheet」という名前で作成します。
4行目ではデータベース使用するユーザーとして「ss_user@localhost」を作成しつつ、必要な権限設定を行います。
5行目では山椒の実のユーザーを作成します。
6行目で権限を反映します。

これでDBの準備が整いました。
.env を開くと、DBに関する設定を行う場所があるのでここに情報を記載します。

※本当の設定箇所は config/database.php です。しかし環境ごとに異なるなので.envに記載し、database.phpからは.envを参照するようになっています。

DBへのアクセス設定ができたので、ここからはモデルの作成とテーブルを追加する作業、マイグレーションです。アプリケーションのルートフォルダで

を実行すると モデルクラス用のファイルが作成されます。-mオプションでマイグレーションファイルも一緒に作成されるので、モデルを作成するときは指定しましょう。

app\Score.php
database/migrations/<日付_時間>_create_scores_table.php

モデル名を複数形の「スネークケース」にしたものが、テーブル名として使用されます。「Score」は「scores」になります。Laravelは内部に辞書をもっており、英単語を適切に複数形にしてくれます。ネイティブじゃない私にはありがたい機能です。
あとはこれを編集して必要なフィールドを追加します。

これとは別にLaravelではプロジェクト作成時にusers、password_resetsテーブル用のマイグレーションファイルが作成されています。認証用ですね。
これと今作成したマイグレーションファイルのすべてが次のコマンドでまとめて
DBに登録されます。

「php artisan migrate:status」でマイグレーション結果を確認できます。

※一度もマイグレーションを実行していない状態で実行するとエラーが出てしまいます。

「migrations」というテーブルが無い!と怒られるのですが、このテーブルは最初にマイグレーションを実行したときに作成されます。これを知らずにちょっとはまりました。

モデル及びテーブルがもっと必要な場合は「php artisan make:model xxx -m」を必要なだけ繰り返しましょう。最後に「php aritisan migrate」を忘れずに。

プログラムはこのモデルを使ってデータにアクセスしていきますよー。

[Python]拡張モジュールをWin Debug版DLLで試すとエラー

福岡拠点の香月です。

Windows環境で動作するPython3.6.6の拡張モジュールをC++で作りました。
開発ツールはVisual Studio 2015です。
早速作成した拡張モジュールのDebug版を使ってみようとPythonインタプリタを起動してモジュールロードしたんですが゙………

とこのようにエラーが発生してしまいました。インタプリタも強制終了しているようです。

これは拡張モジュールがDebug版なので、Pythonバイナリもデバッグ版を使う必要があります。python_d.exeがpython.exeと同じ場所にあるのでそれを使いましょう。
また、拡張モジュール名にも「_d」が必要です。Release版が「spam.pyd」ならDebug版では「spam_d.pyd」です。リネームで十分です。

このようにFatal Python Errorは発生せずに続行できます。
Debug版でのデバッグをあきらめて、Release版に無理やりデバッグ情報をつけてデバッグしていた方、是非これをお試しあれ。

これに気付く前にVisual Studio 2017のドキュメントで次のようなものを見つけていました。
https://docs.microsoft.com/ja-jp/visualstudio/python/working-with-c-cpp-python-in-visual-studio?view=vs-2017
以下抜粋

警告

デバッグ構成の場合でも [C/C++] > [コード生成] > [ランタイム ライブラリ] のオプションを常にマルチスレッド DLL (/MD) に設定します。これは、この設定がデバッグ以外の Python バイナリのビルドに使用されるためです。
マルチスレッド デバッグ DLL (/MDd) オプションを設定すると、デバッグ構成をビルドするときに、”C1189: Py_LIMITED_API は Py_DEBUG、Py_TRACE_REFS、Py_REF_DEBUG と互換性がありません” というエラーが表示されます。 さらに、ビルド エラーを避けるために Py_LIMITED_API を
削除すると、モジュールをインポートしようとしたときに Python がクラッシュします (後で説明しますが、クラッシュは DLL のPyModule_Create の呼び出し内で発生し、出力メッセージは “Fatal Python error: PyThreadState_Get: no current thread (Python 致命的エラー: PyThreadState_Get: 現在のスレッドがありません)” です)。
/MDd オプションは Python デバッグ バイナリ (python_d.exe など) のビルドに使われますが、拡張 DLL に対して選ぶと、やはり Py_LIMITED_API のビルド エラーになります。

でもこんなことする必要なく、python_d.exeを使うだけでOKですよー。

[Laravel]プロジェクトの作成

福岡拠点の香月です。

前回まででLaravel実行におけるインフラ整備が終わりました。
今回はLaravelプロジェクトの作成です。

環境は引き続きこちらで構築したCentOS上です。
Windowsのコマンドプロンプトを立ち上げて次のコマンドでCentOSを起動。

ターミナルソフトでローカルポート2222にsshでアクセスします。
以降の操作はターミナル上で行います。

実行ユーザーはvagrantです。実行はホームディレクトリで行いました
プロジェクトを作成するには次のコマンドを実行します。プロジェクト名は「scoresheet」とします。

以下が出力されます。

カレントディレクトリに「scoresheet」というフォルダが作成され、そこにLaravelの構成ファイルが作成されます。

このコマンドでは最新バージョンがインストールされますが、プロジェクトによってはバージョンを指定してインストールしなければならないことがあります。例えば5.1をインストールする場合はこう。

バージョンの3桁目は必ず*にしましょう。
インストールされたバージョンは次のコマンドで確認できます。作成されたディレクトリに移動して実行しましょう。

プロジェクトの作成コマンドとして本家サイトでは「# laravel new <プロジェクト名>」があります。しかしWindows環境では正しく動作しない報告が沢山あるようなので、「# composer create-project」コマンドで統一するのが良いようです。

次はWebサーバーの設定です。rootになって作成したディレクトリをWebサーバーのディレクトリの下に移動します。

/etc/httpdはvagrantユーザーには書き込み権限がないため、rootで作業します。

ユーザーvagrantで/etc/httpdに移動して直接「composer create-project」コマンドを実行すればいいのでは?とも考えますが、vagrantには/etc/httpdへの書き込み権限がないためLaraveのプロジェクトの種ファイル であるhttp://cabinet.laravel.com/latest.zip のダウンロードに失敗してしまいます。

続けてLaravelホームをWebのルートにするためにhttpd.conを編集します。

※厳密にはWebサーバーのディレクトリ以下に必ずしも移動させるひつようはありません。httpd.confの設定でLaravelディレクトリへのエイリアスを設定し、そこをDocumentRootに設定するのでもいいでしょう。

次にWebサーバーがLaravelのstorage、bootstrap/cacheディレクトリに書き込みできるようにパーミッションを調整します。

最後にWebサーバーを再起動します。

再起動が完了したらホストOS(Windows)上のブラウザからルートにアクセスします。
http://localhost:8080/
Laravelロゴが表示されれば完成です。

PHPカンファレンス福岡2018

先週末に開催されたPHPカンファレンス福岡に参加したのでその感想を簡単に。
https://phpcon.fukuoka.jp/2018/

PHPカンファレンス福岡は今年で4回目。
もともと東京で行われたPHPカンファレンスに参加できなかった人が「PHPカンファレンス福岡」とつぶやいたことから有志が集い福岡で開催が決定したというのが興味深いですね。
弊社はブロンズスポンサーとして出資しています。

朝から夕方まで開催されていて、主に初心者向けのセッションが多くありました。
また、MySQLやコンテナ技術などPHPに関係しないセッションも多く、Webアプリ全般技術勉強会みたいな感じでした。

「skaffold を使って Kubernetes してみた」

Kubernetes(くーべねてぃす)のセッションです。
オープンソースの「コンテナオーケストレーションシステム」で、Dockerが正式採用したのを皮切りに、AWS、MS Azureも次々に正式対応をしたということが説明されており、事実上の標準になったのではないか、ということでした。
今後コンテナを使って開発/勉強を行うときには合わせて使ったほうがよさそうです。

「0から始めるLaravel相談会」

匠の技を倣うならドキュメントを読め!
特にLaravelのドキュメントは良く書かれており、英文も単語を抜き出して読み進めればわかるはずだ!
みたいなセッションでした。
確かにオリジナルドキュメントは大事ですね。

「Testing Live!!!」

テストをやる人がどういった視点でテストをするのか?5分間の解説付きで実演していました。
アカウントやパスワードのテキスト入力枠にhtmlのタグ付き長文を入力する、表記ゆれが気になる~、などあるあるだけどついついおろそかにしがちなことをテスターの心情を交えながら説明されてて、面白かったです。

各セッションのスライドの多くは「Speaker Deck」にアップされています。
https://togetter.com/li/1237797

セッション以外にもスポンサー企業ブースが併設されていていました。
ノベリティ有り、コーヒーサービスありでゆっくり見て回れるようになっていました。

参加者全員に配られたバッグや先着で配られたTシャツもよかったです。