M5StickC Plus とマイクモジュールでインターホンの呼び出し音が鳴ったら Discord に通知を送るようにした
宅急便等でインターホンが鳴っても別の部屋で作業をしていると気づかないことがあったので Discord に通知を送るようにしました。
以下解説
参考
大体ここから拝借しました
機器
- 家に転がっていた M5StickC Plus
- マイクモジュール
ソースコード
ここでしか C++ 書いたことないからやっつけレベル
解説
これは何
マイクモジュールが一定以上の音を検出すると Discord に post する仕組みです。
インターホンの呼び出し 1 回で何度も Discord に通知がいくと邪魔なので、一度 Discord に通知してから 120 秒間は通知しないようにしています。
冒頭の M5Stick のディスプレイに写っているもの
- 左上
- 右上
- Discord 通知禁止期間の残り秒数
- 真ん中の緑の線
- 音の波形。マイクが音を検出するとギザギザする。サンプルコードを動かした時のやつをそのままにしてる。
- 左下
- ローカル IP アドレス。これも参考にしたソースコードそのままの状態にしている。
まとめ
インターホンの音を検知して Discord へ post することで宅急便に気付きやすくなるようにしました。
本記事執筆時点でまだ運用 2 日目なのでまだ改善の余地がありそうですが今の所想定通りに動いています。
M5StickC Plus と MH-Z19C センサで CO2 濃度を可視化してみた
この記事は Rakuten Rakuma Advent Calendar 2022 の 17 日目の記事です。
マイコン初心者が CO2 センサの MH-Z19C で計測した値を M5StickC Plus で表示するまでの内容となります。
この手の記事は検索すると割と出てくるものですが、前提や諸条件が自分の手元の環境と微妙に違ったりしてそれなりに苦労したので誰かの助けになればと思い記事にしました。
きっかけ
かねてからラズパイに興味はあったものの今まで手をつけてこなかったのですが、ちょうどプラモが完成したタイミングでアドベントカレンダーをやりますという話を聞いたのでいい機会だしラズパイで遊んで記事書くかと軽い気持ちで手を上げました。
HG ゲシュペンスト完成 pic.twitter.com/PE3m951ZFV
— カニカニカニバル (@ka_nipan) 2022年11月4日
ラズパイ手に入らん
いざラズパイ買おうとするとどこも品切れ。。。
ポチれるサイトを見つけてポチって見たら後々メールで入荷予定は 1 年後とのこと。
ラズパイ買えるじゃんと思ってポチったら入荷予定は来年の9月ってメール来た…
— カニカニカニバル (@ka_nipan) 2022年11月6日
M5Stick なるものを見つけた
元々家の二酸化炭素濃度計測したいなーと思っていてそれをラズパイでやってみたいと思ってたのでそれっぽいワードで色々検索していたら M5StickC Plus と MH-Z19C で CO2 濃度を測ってみたという記事に流れ着いたのでこれ幸いと飛びついたわけです。
環境
CO2 濃度を表示するまでに苦労して色々なサイトに書いてあることを試行錯誤したので最終的な環境を書き残します
開発環境
必要なブツ
M5StickC Plus
MH-Z19C
これ系のブツはフェイク品が結構あるらしいですがここのサイトで買えば良いらしいです
ピン配列交換ケーブル
M5StickC Plus と MH-Z19C をつなぐやつ
ソースコード
ソースはほぼ こちらのサイト をまるっと使わせていただきましたが一応 github に上げておきました。
色々あってできた
CO2 センサーから数値取ってこれた!
— カニカニカニバル (@ka_nipan) 2022年12月6日
苦労したわ pic.twitter.com/hHla95ZiAX
CO2 センサから値を取ってきて表示するまでできたのでとりあえずのゴールは出来ました。
追記
ここまで書いた後 Twitter 経由で CO2 HAT の作成者様からアドバイスいただきました。
CO2 HAT とは
当初 MH-Z19C と M5StickC Plus をつなぐものとして CO2 HAT なるものを使っていました。
ただ合体させるだけで良いのでめっちゃ楽だしケーブルで繋ぐよりスッキリします。
しかし、使い方が書かれたブログ の通りに進めていくと VSCode 上で M5STACK DEVICE のファイルリストが見えるらしいのですが何も見えず途中で挫折し PlatformIO 拡張機能を使う方法にシフトしました。
その後アドバイスいただけたおかげで CO2 HAT でも CO2 濃度を表示させることができました。こちらオススメです。
あ、いけました!
— カニカニカニバル (@ka_nipan) 2022年12月13日
ありがとうございます!! pic.twitter.com/cSVNsnq5mr
先ほど紹介したソースコードからピン番号が変わります。
このピン番号というか回路図あたりが全然理解できてない初心者丸出し具合が出てます。。。
#define RX_PIN 36 // Rx pin which the MHZ19 Tx pin is attached to #define TX_PIN 0 // Tx pin which the MHZ19 Rx pin is attached to
devise ソースコードリーディング
何年か前にも devise 関連の gem の中身見て、再び中身を見る機会があったけどすっかり挙動を忘れてたので大まかな流れをメモっておく
完全に自分用のメモ
古の盟約により devise-token_authenticatable
を使用している
- application_controller - authenticate_user! - devise helper_method authenticate_user! - warden authenticate! - warden proxy _perform_authentication - warden proxy _retrieve_scope_and_opts - warden proxy user - warden session_serializer.fetch return nil - warden hooks after_failed_fetch - warden proxy set_user - warden proxy _run_stragegies_for - warden proxy _fetch_stragegy strategies.each - warden proxy stragety.performed? - devise-token_authenticatable stragety valid? (stragety.valid?) - devise strategies authenticatable valid? - devise authenticatable valid_for_params_auth? - devise authenticatable stragety params_authenticatable? - devise authenticatable model params_authenticatable? - devise authenticatable stragety valid_params_request? - devise authenticatable stragety valid_params? - devise authenticatable stragety with_authentication_hash - warden base _run!(strategy._run!) - devise-token_authenticatable stragety authenticate! - devise-token_authencatable model fird_for_token_authentication - devise-token_authencatable model find_for_authentication - devise-token_authencatable model find_first_by_auth_conditions - devise strategies authenticatable validate - devise model authenticatable valid_for_authentication? - devise-token_authencatable model after_token_authentication (only definition) - warden base success! - warden proxy set_user
基本的な流れは
strategies.each do |strategy| strategy._run! end
をやってる
stragegy は mail & pass 認証だったり token 認証だったりしてるやつ
gem の中で params とかが使われているけど、application_controller とかで使う params ではなく warden の params メソッドを使ってる
devise-token_authenticatable-1.0.2/lib/devise/token_authenticatable/strategy.rb
def params_auth_hash if params[scope].kind_of?(Hash) && params[scope].has_key?(authentication_keys.first) params[scope] else params end end
warden-1.2.7/lib/warden/mixins/common.rb
def params request.params end
ドリコムを支えるデータ分析基盤がTD+AWSに移行した話
はじめに
これは ドリコムAdventCalendar の7日目です
6日目は、keiichironaganoさんによる iTunes 使用許諾更新のとき一旦キャンセルしてほしい話 です
【その2】ドリコム Advent Calendar 2015 もあります
自己紹介
去年の ドリコムを支えるデータ分析基盤 に引き続き、今年もドリコムのデータ分析基盤を担当しています。
分析基盤をTreasure Dataに移行
オンプレ環境の Hadoop からTreasure Data に移行しました。
また、ジョブ管理ツールやBIツールといったサーバーもAmazon EC2 に移行しており、 徐々にオンプレ環境を離れつつあります。
背景
オンプレ環境で Hadoop を運用して3年も経つと考えなければならないのが HW の寿命です。
さてどうしようかとなった時に、ほぼ迷いなく外部サービスに移行するという選択を取りました。
Treasure Data、BigQuery、Amazon Redshift のようなサービスがある中で HW の交換作業をしてまでこのままオンプレで運用し続けるメリットはあまり感じられなかったためです。
サービス選定
候補として上で挙げた Treasure Data、BigQuery、Amazon Redshift の3サービスを実際に触ってみて最終的に Treasure Data を使うことに決めました。
Redshift はちょっと触っただけなのですが、実際に触ってみた所感の比較です。 ※ 2015.1 時点
Treasuredata presto | BigQuery | Amazon Redshift | |
---|---|---|---|
リソースのスケール | フルマネージ | フルマネージ | ユーザがノード変更 |
同時実行 | 4〜 | 20 | 5〜 |
課金 | 固定額。お高め | データサイズ+クエリ数+クエリスキャンサイズ+etc 。全体的にお安い。データ抽出サイズに気をつけないとウン百万の請求も | 固定額+etc |
パフォーマンス | 速い (件数依存、対象が数十億ならHiveにする) | 件数に関係なく速い | 速い (件数に依存) |
テーブル(分散) 設計 | 不要 | 不要 | 必要 |
スキーマレス | ○ | 固定 (json構造ならレス) | 固定 (json構造ならレス) |
データ投入 | 実質入れ放題 | 制約有り (回避方法あり) | S3経由 (大量データのインポートが遅い) |
サポート | ◎ | △ | ○ |
総評 | バランスが良い。専任で人をつけなくても安定した価格、性能で運用できそう | パフォーマンスを保つため、制約が多いので注意が必要 (制約条件が突然変わったら爆死する可能性)。節約したい時、件数が多くてどうしようもなくなった時に使えるかも | 安価で始められるDWH。大規模になるほどチューニングが必要になってくる。 スモールスタート。データマート向き。変更に弱い。 |
他にも比較する部分が色々あるのですが、あんまり細かくなってもしょうがないので、ざっくりとした所感で書きました。
また、弊社の分析基盤としては職人をできるだけ作らないような体制を目指しているので一番運用が楽で安定しそうな Tresure Data を使うことになりました。
新データ分析基盤全体図
大きなところは Hadoop が Treasure Data に変わり、AWS のサーバーを徐々に使い始めているというところで、それ以外のところは去年の ドリコムを支えるデータ分析基盤 とほぼ変わっていません。
DBのスナップショットはS3へ
Hadoop の時は、アプリのログもDBから取ってきたスナップショット(tsv) もHDFS に置いていたのですが、Treasure Data にはデータ件数の制限があるので、無闇に全部 TD に置くということはせずに TD 上で集計が必要なもの以外は S3 に置くことにしています。
また、ログもTD上には直近x日まで保存して、古いものはS3に逃がすということもログ件数調整の一環としてやっています。
闇のスクレイピング技術は今も健在
去年のエントリの闇スクレピングは今も元気に動いています。。。地味に便利なのが憎らしい。。。
まとめ
・安定した運用、職人芸を少しでも減らすために Treasure Data を分析基盤に
・特定の条件下であれば、BigQuery、Redshift はあり
・金を出した分だけ楽ができる
8日目は @sazae657 さんです
ドリコムを支えるデータ分析基盤
はじめに
これは ドリコムAdventCalendar の4日目です
3日目は、@arihh さんによる 3年くらいお菓子神社運営してきた です
自己紹介
ドリコムに新卒で入社し、Android開発、BtoBtoC のwebサービス開発を経て、現在は弊社アプリのログ収集から集計、可視化、その他周辺ツールといった分析基盤の面倒を見ています
本日はそのデータ基盤の話を書きます
データ分析基盤全体図
弊社では Hadoop をオンプレで運用していて、そこにログや分析用のデータを置いています
メリット
運用コストが安い
Treasure Data、Big Query、Amazon Redshift 等の外部サービスを使うよりは安く済みます
自由度が高い
各サービスには容量をはじめ色々と制限があったり、こちらの要求仕様にマッチしない部分が少なからずありますが、自前の場合その辺は融通がききやすいです
分析する人が生データにアクセスしやすい
後述しますが、分析チームの人が R言語 で処理しやすいように、Hadoop にデータを集積しています
なので、基本的に HDFS には gzip 圧縮した tsv ファイル等を置いていて、SequenceFile は置いていません
分析用のサーバーに Fuse で mount しているので通常のファイルシステムのように扱えます
デメリット
メンテナンスが辛い
HDDに障害があったり、運用年数が経ってくると新しいものに入れ替えたり、リカバリ作業をする必要があります
また、HadoopやOSのバージョンアップをしようと思ったら、NameNode や DataNode 全台、その他すべて含めると結構な台数になってくるので精神がガリガリ削られていきます
HDFS全体の容量とかブロックの数とか気にしてあげないといけない点も結構あります
データ取得・保存
ログ送信
ログの送信は fluentd を使用しています
鉄板ですね
図では端折ってますが、web → log collector → Hadoop のように一旦ログを集約してから Hadoop に送信するようにしています
テスト環境のログ排除
テスト環境のログが Hadoop に送られてしまうと集計値がくるってしまうので、ホスト名に test, staging のような文字列が入っている場合 Hadoop に送信しないようにしています
ログ送信成否の確認
webサーバー追加したけど設定ミスでログ送れてませんでしたー!等を突き止めやすくするために、web → collector に送る時にホスト名を送るようにしています
事前処理
送られてきたログに対して、集計に必要な付加情報を加える処理をしています
ここでフォーマットのチェックも行っていて、エラーとなったログは集計対象から除外されます
集計は hive を使用しており、事前処理が終わったらロードされます
スナップショット取得
アプリのDBにSQLを投げて、その結果をファイルとしてHDFSに保存しています
DBのフェイルオーバー対策
DBにクエリを投げる際、SLAVE に投げるわけですが、DBがフェイルオーバーするとSLAVEのIPアドレスも変わってしまいます
そこで、SLAVE のIP情報を管理用のサーバーのDBに入れておき、アプリのDBがフェイルオーバーすると管理用DBにあるIPも書き換わるという仕組みが用意してあります
この仕組みにより常にSLAVEへとクエリを投げることが可能になっています
データの持ち方としてはこんな感じ
CREATE TABLE IF NOT EXISTS `slavedb` ( `app_key` varchar(64) NOT NULL , `host_name` varchar(64) NOT NULL , `slave_ipaddress` varchar(64) NOT NULL , `db_name` varchar(64) NOT NULL , `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`app_key`,`host_name`,`db_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
帯域への配慮
結構な数のクエリを投げたり、結果がでかいものがあったりで何も考えずに一気に流すとネットワークの帯域を食いつぶす可能性があった (実際やばかった) ので、
一度に流すクエリの量を抑える仕組みを作って安全に配慮してます
クエリの管理
アプリのDBへ投げるクエリは ActiveAdmin で管理しています
また、ここで管理されているクエリは管理画面から Explain を実行してその結果を見ることができるように手を加えています
クエリのシンタックスエラーのチェックもできて地味に便利
バックアップ
ログ、スナップショットファイルは Hadoop に取り込まれると同時にバックアップサーバにコピーされます
集計
前述のとおり集計はみんな大好き hive を使っています
presto はテスト的に使ってみたりしてます
集計したデータは可視化のため MySQL へ
集計単位
- hourly
- dayily
- monthly
ジョブ数
集計以外の全ジョブ含め約600個
可視化
有料BIツールを使っていますが、パフォーマンス、アカウント制限等々の理由で結局BIツール自作しました
黒背景にするとなんかカッコよく見えますね!
分析チーム
弊社にはアプリの分析を行う分析専門のチームがいて、
ハイスペックな分析用サーバ数台に Hadoop を Fuse マウントして自由に Hadoop 上のファイルを使えるような環境を用意しています
基本的に R で分析を行い、彼らの定期ジョブは分析サーバーで Jenkis で実行しています
少し前まで cron を使っていましたが、さすがに管理が辛くなってきました
闇スクレイピング
ログの集計値の正当性を担保するために、プラットフォームの管理画面で見ることができる数字(売上等)を取ってこなければいけません
(ちなみにあまりにも誤差があると、ログと管理画面からDLできる課金明細をつき合せていくことになります...)
しかし、プラットフォームによってサイトの作りがまちまちです
単純にスクレイピング用ライブラリではすべてを解決できませんでした (そのへんのAPIは提供されていない)
そこで色々もがいた結果、以下のような環境が出来上がりました
使用しているもの
- Xwindow
- firefox
- Grease moneky
Grease monkey とは firefox のプラグインのひとつで、特定のURLに対して任意のコード (javascript) を実行できます
仕組み
- Linux に作った GUI 環境から指定のURLに firefox でアクセスする
- グリモンで目的のページまでたどり着いたら、そのページをテキストファイルとしてダウンロードします
- そのテキストファイルから正規表現で必要な部分を抜いてDBに入れます
もはやスクレイピングでもなんでもないですね
最高に力技ですが、この方法でURLの末尾にランダムなハッシュがつくサイトも非同期処理をするサイトでもなんでも抜いてこれるようになりました!
サンプルコード (雰囲気だけ感じてもらえれば...)
// ==UserScript== // @name kani_scraper // @namespace imperial_cross // @include https://developer.hogee.jp/ // @include https://developer.hogee.jp/* // ==/UserScript== (function() { if(location.href == "https://developer.hogee.jp/"){ document.getElementsByName("login_id")[0].value = login_id; document.getElementsByName("password")[0].value = password; document.getElementsByName("form")[0].submit(); }else if(location.href == "https://developer.hogee.jp/home"){ // http://d.hatena.ne.jp/a_bicky/20110718/1311027391 参照 writeHtmlFile = function () { function writeToLocal (filename, content){ var ua = navigator.userAgent.toLowerCase(); try { if (ua.indexOf('firefox') != -1) { // Firefox //filename = (ua.indexOf('windows') != -1 ? 'C:\\tmp\\' : '/tmp/') + filename; if (ua.indexOf('windows') != -1){ filename = "C:\\tmp\\" + filename; } else { filename = path + filename; } unsafeWindow.netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); // ファイルコンポーネントの取得+ローカルファイル操作用のインターフェイスの取得; var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); file.initWithPath(filename); var fileStream = Components .classes['@mozilla.org/network/file-output-stream;1'] .createInstance(Components.interfaces.nsIFileOutputStream); // ファイルが存在しない場合は664の権限で新規作成して書き込み権限で開く // cf. https://developer.mozilla.org/en/NsIFileOutputStream // http://www.oxymoronical.com/experiments/apidocs/interface/nsIFileOutputStream; fileStream.init(file, 0x02 | 0x08, // 0x01: 読み取り専用, 0x02: 書き込み, 0x03: 読み書き, 0x08: 新規作成, 0x10: 追記 0664, // mode 0 // 第4引数は現在サポートしていないとか ); // cf. http://www.oxymoronical.com/experiments/apidocs/interface/nsIConverterOutputStream var converterStream = Components .classes['@mozilla.org/intl/converter-output-stream;1'] .createInstance(Components.interfaces.nsIConverterOutputStream); converterStream.init(fileStream, 'UTF-8', content.length, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); converterStream.writeString(content); converterStream.close(); fileStream.close(); //alert('書き込みが完了しました!'); } else if (ua.indexOf('chrome') != -1) { // Google Chrome // 起動オプションに --unlimited-quota-for-files --allow-file-access-from-files をつける必要あり function errorCallback(e) { alert('Error: ' + e.code); } function fsCallback(fs) { fs.root.getFile(filename, {create: true}, function(fileEntry) { fileEntry.createWriter(function(fileWriter) { fileWriter.onwriteend = function(e) { alert('書き込みが完了しました!'); }; fileWriter.onerror = function(e) { alert('Failed: ' + e); }; var bb = new WebKitBlobBuilder(); bb.append(content); var output = bb.getBlob('text/plain'); fileWriter.write(output); }, errorCallback); }, errorCallback); } // 現時点ではたぶん第1引数はPERSISTENTもTEMPORARYディレクトリ名が異なるだけだし、 // 第2引数は極端な話0でもOK webkitRequestFileSystem(PERSISTENT, 1024, fsCallback, errorCallback); } else if (ua.indexOf('msie')) { // MS IE filename = 'C:\\tmp\\' + filename; // インターネットオプションで「スクリプトを実行しても安全だとマークされていない // ActiveX コントロールの初期化とスクリプトの実行(セキュリティで保護されていない)」 // を有効にする必要あり var fso = new ActiveXObject('Scripting.FileSystemObject'); // ファイルを新規作成して書き込みモードで開く (文字コードはUTF-16) // cf. http://msdn.microsoft.com/ja-jp/library/cc428044.aspx // http://msdn.microsoft.com/ja-jp/library/cc428042.aspx var file = fso.OpenTextFile(filename, 2, // 1: 読み取り専用, 2: 書き込み, 8: 追記 true, // ファイルが存在しなければ新規作成するかどうか -1 // -2: OSのデフォルト文字コード, -1: UTF-16, 0: ASCII ); file.Write(content); file.Close(); alert('書き込みが完了しました!'); /* * ADODB.Stream を使う場合(レジストリをいじっても何故か書き込めない・・・) */ // var adodbStream = new ActiveXObject('ADODB.Stream'); // adodbStream.type = 2; // テキストファイル(バイナリは1) // adodbStream.charset = 'UTF-8'; // adodbStream.open(filename); // adodbStream.writeText(content); // adodbStream.saveToFile(filename, 2); // 上書き保存(1だと新規作成のみが対象) // adodbStream.close(); } else { alert('エラー: ローカルファイルへの書き込み方がわかりません・・・'); } } catch (e) { alert('Error: ' + e); } } writeToLocal('test1.txt', "hogehoge"); } } setTimeout( writeHtmlFile, 20000);//20秒後にページを閉じる })();
闇っぽい雰囲気だけ感じてください
Hadoop まわりの運用
Hadoop、hive での運用で気を使っている点や以前困っていた点を紹介します
細かいファイルは集約する
HDFS のデフォルトブロックサイズは 64MB と大きく、小さいファイルをたくさん置くのはあまり効率が良くありません
サイズの小さいものは1日単位などに集約すると結構ブロック数を減らせます
簡単に再集計できる仕組み
再集計をかけるタイミングが必ず存在します。しかも意外に高い頻度で
ジョブスケジューラで簡単にできればそれがベストですが、
なんらかの事情やそんな機能がなかった場合は、再集計するコードを書いておくと幸せになります
hive の json カラムには気をつけろ
hive には json 型があり、スキーマレスっぽい感覚で使えます
しかし使えるからと言って何でもつっこんで、1行なのに画面いっぱいの json で埋まるみたいなことをされるとさすがに hive さんも落ちます気を付けてください
まとめ
次は id:GUSSAN さんです。
-- 追記 --
HDFS 上のファイル集約のくだりですが、ご指摘を頂いたのでほそくすると、
容量を節約したいからではなく、NameNode のメモリが節約できるということです
あと、mapタスクの数を減り mapの起動コストも減ることになります (こっちは失念してました!)
カニでもできるRailsでアクセスログ実装
こんにちはこんにちは
Rails 4 でさくっとアクセスログ出そうと思ったけど、思ったよりさくっといかなかったので実装方法を残しておく
ログの項目
これくらいの簡単な内容
- 時間
- ユーザー名
- リクエストURI
- USER AGENT
独自ログの設定
まず、Railsのログじゃなくて独自のログを出したかったので、
config/environment.rb
# Load the Rails application. require File.expand_path('../application', __FILE__) class AccessLogger < ::Logger class NoHeaderLogDevice < ::Logger::LogDevice def add_log_header(file) end end class AccessFormatter < ::Logger::Formatter def call(severity, timestamp, progname, msg) "#{msg}\n" end end def initialize(logdev, shift_age = 0, shift_size = 1048576) super(nil, shift_age, shift_size) @formatter = AccessFormatter.new if logdev @logdev = NoHeaderLogDevice.new( logdev, :shift_age => shift_age, :shift_size => shift_size ) end end end # Initialize the Rails application. Kaniapp::Application.initialize!
解説
ソースの中身を解説すると、
ログファイル作成時に出る邪魔なヘッダー(# Logfile created on ...)を抹殺
class NoHeaderLogDevice < ::Logger::LogDevice
def add_log_header(file)
end
end
ログフォーマットを指定
class AccessFormatter < ::Logger::Formatter
def call(severity, timestamp, progname, msg)
"#{msg}\n"
end
end
config/environments/production.rb に
config.access_logger = AccessLogger.new('log/skv_access.log')
を追加
呼び出し
ApplicationController から
before_filter { access_log if Rails.env.production? }
private
def access_log
return if session['warden.user.user.key'].nil?
@user = User.find_by_id(session['warden.user.user.key'][0][0])
return if @user.nil?
now = Time.now.to_s.gsub(/ \+0900/,'')
request_uri = URI.unescape(request.env['REQUEST_URI'])
remote_ip = request.env['HTTP_X_FORWARDED_FOR'] || request.remote_ip
user_agent = request.env['HTTP_USER_AGENT']
row = "#{now}\t#{@user.username}\t#{request_uri}\t#{remote_ip}\t#{user_agent}"
Kaniapp::Application.config.access_logger.info(row)
end
このやり方で君だけの最強のアクセスログを作ろう!!