Railsのログを awslogs で Cloudwatch Logs に出力する

2017年08月28日
区分
Rails
報告者:
morishita

morishita です。 今回はいこレポでのログ出力について紹介します。

いこレポの動作環境

いこレポは ElasticBeanstalk を利用してアプリケーションサーバを稼働させています。 ElasticBeanstalk ではプラットフォームを選択できますが、 Multi Container Docker を利用しています。 この場合、実際にRailsが稼働するのは ECS上に起動された Docker コンテナとなります。

Dockerではコンテナ内に永続化するデータを保存しないことが基本ですが、ログも コンテナ削除時に消えていい情報ではないので何らか外に出す必要があります。

その方法としては次の方法があるかと思います。

案1. ホスト側のディレクトリをマウントして、そこにログを保存する
案2. Fluentd でログサーバに転送する
案3. Cloudwatch Logsに転送する

いこレポではECSインスタンスも基本イミュータブルに運用しており、デプロイ毎に新たなインスタンスを起動して、古いのは捨てているのでホスト外に出す仕組みがもう一段必要になります。よって案1は却下しました。

Fluentd を使うと柔軟なログ転送が可能でElasticSearchに送ったりもやりやすいのですが、S3に格納するにしてもログを一旦受けるログサーバを立てる必要があります。ログサーバを立てて運用する手間を惜しんで案2も今回は見送りました。

いこレポで採用したのは案3のClowdwatch logsに出力するという案3の方法。 それについて以下に述べます。

Dockerの設定

Dockerには ロギングドライバという仕組みがあり、標準出力に吐いたログを設定されたロギングドライバ経由でハンドリングすることができます。

ロギングドライバには fluentd 1syslog がありますが、Clowdwatch Logsに出すには awslogs を使います。 このログドライバは当然、ElasticBeanstalk (具体的にはECS)でも利用できます。

ElasticBeanstalk ではコンテナの定義を Dockerfilecocker-compose.yml ではなく、Dockerrun.aws.json というAWSの独自フォーマットのファイルに記述します。

次のように設定してやれば、awslogsロギングドライバを利用できます。
logConfiguration がロギングドライバの設定部分です。

{
  "AWSEBDockerrunVersion": 2,
  "containerDefinitions": [
    {
      "name": "rails",
      "image": "<DockerイメージのURL>",
      "essential": true,
      "memory": 1024,
      "entryPoint": ["<略>"],
      "logConfiguration": {
          "logDriver": "awslogs",
          "options": {
            "awslogs-group": "<ロググループ名>",
            "awslogs-region": "ap-northeast-1",
            "awslogs-stream-prefix": "production"
          }
      }
    }
  ]
}

logDriverawslogsを指定し、options でログの格納先となる Clowdwatch Logsのロググループを指定します。

ログを出力するためには設定以外に次の2点が必要となるので注意です。

  • 出力先のロググループ は予め作っておくこと
  • ElasticBeanstalk でインスタンスの稼働に利用されるロールがそのロググループへのアクセス権を持っていること

Railsの設定

さて、Docker側の設定ができれば、ログの出力先を標準出力にするだけで Clowdwatch Logs にログが保存されるようになります。

Rails5.1では production.rb にもともと次のように定義されており、RAILS_LOG_TO_STDOUT という環境変数があれば、標準出力にログが出力されるようになっています。

  if ENV["RAILS_LOG_TO_STDOUT"].present?
    logger           = ActiveSupport::Logger.new(STDOUT)
    logger.formatter = config.log_formatter
    config.logger    = ActiveSupport::TaggedLogging.new(logger)
  end

ElasticBeanstalk では管理コンソールから環境変数を設定できるのでそれを利用すれば、設定ファイルを変更することなくログを標準出力に切り替えられます。

ログ出力のフォーマット変更

Railsのログは 1リクエストに対して複数行で出力されます。 例えば次のように。

Started GET "/" for 172.18.0.4 at 2017-08-16 02:10:36 +0000
Processing by TopController#index as HTML
  Rendering top/index.html.slim within layouts/application
  Rendered top/index.html.slim within layouts/application (***.*ms)
  Rendered layouts/_header.html.slim (**.*ms)
  Rendered layouts/_footer.html.slim (**.*ms)
Completed 200 OK in ***ms (Views: ***.*ms | ActiveRecord: **.*ms)

Clowdwatch logsでは1行が1レコードとなってしまうので、どのレコードからどのレコードまでが一連のログなのか追う必要があり、非常に見づらくなります。 1行のJSONで出力すると1レコードとして一連のログを記録できる上に、パースして見やすくしてくれるので、JSONで出力するようにログフォーマットを定義するようにしました。

いこレポではログフォーマットに roidrage/lograge: An attempt to tame Rails’ default policy to log everything. を利用しています。

まず、production.rb に次の設定を追加します。

  config.lograge.enabled = true
  config.lograge.formatter = Lograge::Formatters::Json.new
  config.lograge.custom_payload do |controller|
    {
      host: controller.request.host,
      remote_ip: controller.request.remote_ip,
    }
  end
  config.lograge.custom_options = lambda do |event|
    exceptions = %w(controller action format id)
    {
      time: event.time,
      host: event.payload[:host],
      remote_ip: event.payload[:remote_ip],
      params: event.payload[:params].except(*exceptions),
      exception_object: event.payload[:exception_object],
      exception: event.payload[:exception],
      backtrace: event.payload[:exception_object].try(:backtrace),
    }
  end

config.lograge.formatter = Lograge::Formatters::Json.new でJSONフォーマッターを使うこと指定して、 config.lograge.custom_payload では 標準で出力されている項目以外を追加したい場合に 各アプリケーションの要求仕様に沿ってそれを追加するように実装します。

lograge.custom_options ではどの項目をどういうキーでJSONに出力するのかを実装します。ここで定義した、ハッシュがそのまま、JSONとしてログ出力されます。

rescue_from で404や例外をキャッチして、レスポンスをレンダリングしている場合、 これだけでは 例外情報がログに入りません。 そこでapplication_controller.rbappend_info_to_payload をオーバーライドします。こうしておけばバックトレースもログに出力されるようになります。

  def append_info_to_payload(payload)
    super
    if @exception.present?
      payload[:exception_object] ||= @exception
      payload[:exception] ||= [@exception.class, @exception.message]
    end
  end

config.lograge.custom_payload を実装せずに、append_info_to_payload のオーバーライドでやってしまってもいいかもしれない。

以上を実施して出力されるログの表示はこんな感じです。 ClowdwatchLog

とても見やすくなります。

最後に

おでかけ先探しに悩むパパ・ママを助けてくれるエンジニアを募集していますので、よろしくお願いします。

脚注

  1. じゃあ、 fluentd のドライバを使えば、ログサーバなしで fluentd で運用できたんじゃない?と思われるかもしれませんが、ロギングドライバがやってくれるのはプロトコルのペイロードに載せて転送してくれるところ。例えばS3に保存するにはどこかで一旦受けて、fluentdの S3アウトプットプラグインでS3に送ってやる必要があるのでログサーバの運用が必要となります。 

DelayedJobのバックグランド処理でサーバーが落ちた時にすること

2017年08月25日
区分
Rails
報告者:
endo

こんにちは、endoです。

今回はDelayedJob先生のバックグラウンドの処理が重くて、スワップが発生してサーバーがお亡くなりになりました。

対処として、サーバー再起動を行いました。

この時、サーバー再起動で処理は繰り返されないだろうと勘違いしていました。

弊社ではDelayedJobの設定でリトライをしないようにしております。

詳細はDelayed::Job で絶対にやっておいた方がいいたった1つの設定 をご確認ください。

視界、夜中に再度実行されてまた、サーバーがお亡くなりになりました・・・

  1. サーバー再起動
  2. 8時間後にもう一度再実行←こいつの正体が意味わからない

設定を確認しました。

config/initializers/delayed_job_config.rb

Delayed::Worker.max_run_time = 8.hours ←こいつ

DBに書き込みをしていないので、DelayedJobが生きていると判断してくれて、再度実行するように気を利かせてくれました。

なので、DelayedJobで落ちるようなことがあったら、確実に失敗させましょう。

Delayed::Job.find(x).fail!←IDは適当に書き換えてください。

以上です。

いこレポ はじめました。

2017年08月14日
区分
ikorepo
報告者:
morishita

はじめまして。morishita です。 4月に入社以来、このブログに投稿する機会をうかがっていましたが、ついに初めての投稿です。

すでにサービスインから2週間ほど経っているのですが、いこレポ といういこーよの姉妹サイトをローンチさせました。
ざっくりどのようなサイトかというと、次のようなニュースサイトです。 パパ・ママエンジニアの皆さん、お子さんとのお出かけの参考にご活用ください。

ikorepo

【いこレポとは?】
お出かけの達人「お出かけコンシェルジュ」が、旅行で人気の観光名所、子連れで楽しめる遊び場・イベント、地元の日帰り穴場を掘り下げ、見どころやおすすめプランを提案します。

【いこレポの特徴】
お出かけコンシェルジュが、完全オリジナルの”子どもが喜ぶ遊び場・お出かけまとめ”記事を執筆します。お台場、伊豆などの人気エリアのまとめ記事を読んで週末のお出かけや旅行計画を立てたり、最新記事からお出かけのトレンド情報を探ったり、人気テーマ別記事から新しいお出かけ体験を見つけることができます。
(ニュースリリースより転載)

さて、このブログはTechブログなので、裏側の仕組みについて少しご紹介したいと思います。

私が入社したタイミングでサービスを始めることは決まっていましたが、設計はこれからという状況でした。自社サービスをやっている会社だと、新規機能開発は度々あっても全くの新規プロダクトの開発に最初から入れる機会は少なく、それだけでもラッキーなのに、どのように作るかについてもほとんど決めて良さそうだったので、やってみたいと思っていたことはほぼ思い通りつぎ込みました。

ソフトウェア構成

弊社でサーバサイドの開発というとRailsでというのが既定路線となっています。他の言語やフレームワークを採用する理由も特になかったですし、開発期間がそう長いわけではなかったので、今回もそのまま踏襲しました。

ということで、開発時の最新バージョンということで次の様な構成です。

  • Ruby 2.4
  • Rails 5.1
  • nginx
  • Vue.js


弊社的に新たな試みとしては、Vue.js の導入です。
いこレポに掲載する記事は自前のCMS機能で執筆されていますが、その部分をほぼ、Vue.js で書きました。React + Redux という選択肢も一応は考えたのですが、ちょっとしたアプリを書いてみて、いまいち私の肌に合わなかったので、ほぼ独断でVue.jsを採用しました。結果的には採用して良かったです。
webpacker によるビルドも、デフォルトで組み込まれている設定がよくできており、ハマる部分はありませんでした。

インフラ構成

ソフトウェア構成をオーソドックスに済ませた分、インフラでは色々、当社としては初めての構成を試みています。

まず最初に、サーバはDockerコンテナで動かそうと決めていました。
Dockerで動かす理由は、仕組みとして Dockerfile にサーバの構築手順がコード化されること、将来的にサーバの台数が増えても OS や基本パッケージの更新がアプリケーションのデプロイと同時にできてしまうので手間が少ないだろうということ、いまどき直接あれこれインストールしてサーバを作るのなんてしないよねいう思い込みです。
開発環境も docker-compose up だけで同じ構成のサーバがローカルで起動できるようにしています(流石にDBはMySQLですが)。

そして、AWSの便利サービスはロックインを恐れず積極的に活用することも最初に方針として決めました。 結果的に現時点で使っているAWSのサービスは次のとおりです。

  • ElasticBeanstalk (Multi Container Docker)
  • RDS Aurora
  • ClowdWatch Logs
  • SES
  • S3
  • ClowdFront
  • Route53
  • ECR (EC2 Container Registry)
  • CodeBuild
  • CodePipeline

AWS 上で Docker を動かす仕組みは何種類かあるのですが、ElasticBeanstalk を選択しました。理由は AWS 側でやってくれることが最も多いからです。 環境を作ると、アプリケーションを実際に動かす ECSインスタンスはもちろん、ALB+ターゲットグループ、オートスケーリング、デプロイとひと通りの仕組みを用意してくれます。 これに、CodePipeline と CodeBuild を組み合わせることで、 Github 上で PR をマージすると自動的にデプロイされるように構成しています。

また、これらのインフラ構築を出来る限りコード化することも目指しました。VPC のネットワークは Ansible で構築しています。アプリケーションサーバの構築については CodeBuild で Docker イメージのビルドを行うので、Dockerfile と CodeBuild のビルド定義ファイルである buildspec.yml でコード化ししており、それを稼働させる ElasticBeanstalk 環境も設定ファイルを用いて、eb コマンドで構築できるように定義しています。
部分部分でコード化の仕組みがバラバラで統一感がないのとCI環境の構築についてはコード化できていないのは今後の課題と思っています。

今後について

いこレポがネット上での存在感を高め、編集チームの皆さんが一生懸命書いた記事をできるだけ多くのパパ・ママに届けるために技術的にできることはどんどんやっていきたいと思っています。
今はまだ、記事数がそれほど多くないですが、増えてくると探せない問題が発生すると思うので、検索、レコメンデーションなども検討していきたいと思います。
また、本番を運用する環境についてはできていますが、開発バックエンドであるステージング環境、開発環境についてはいこーよに遅れを取っているのでこの改善も課題です。

アクトインディでは、いこレポを一緒にグロースさせていくエンジニアを待っています

HABTMのテーブル作成方法

2017年06月19日
区分
ruby
報告者:
endo

こんにちは、endoです。

今回は「HABTM」のテーブル作成の方法です。

「has_and__belongs_to_many」の多対多の関係性を頭文字で略語で表示しているものです。

発音はなんて発音されているのでしょうか。 自分は「ハブトゥム」って読んでいますが、どんな呼び方をしているのか気になります。 こんな呼び方があるよって方は、ぜひ教えてください!

さて、「HABTM」ですが、テーブル作成で便利に作成することができます。

rails g migration create_join_table_facility_prefecture facility prefecture

こんなファイルが出来上がります。

class CreateJoinTableFacilityPrefecture < ActiveRecord::Migration
  def change
    create_join_table :facilities, :prefectures do |t|
      # t.index [:facility_id, :prefecture_id]
      # t.index [:prefecture_id, :facility_id]
    end
  end
end

これでもいいのですが、foreign_keyに対して爪が甘いなと思います。

そこで、これを改造します。

class CreateJoinTableFacilityPrefecture < ActiveRecord::Migration
  def change
    create_join_table :facilities, :prefectures do |t|
      t.references :facility, index: true, foreign_key: true
      t.references :prefecture, index: true, foreign_key: true
      # t.index [:facility_id, :prefecture_id]
      # t.index [:prefecture_id, :facility_id]
    end
  end
end

なお、改造しなくてもrails5.1.1は下記のコマンドでいけます。

rails g migration create_join_table_facility_prefecture facility:references prefecture:references
class CreateJoinTableFacilityPrefecture < ActiveRecord::Migration[5.1]
  def change
    create_join_table :facilities, :prefectures do |t|
      t.references :facility, foreign_key: true
      t.references :prefecture, foreign_key: true
    end
  end
end

issue自体は下記で上がっていました。

create_join_table should include indexes and foreign key contraints

取り込まれて使用できるようになっております。

foreign_key好きが突っ込むよな!って思ったので、嬉しかったです。

アクトインディではHABTMについて語りたいエンジニアを募集しております。

DroidKaigi2017に行ってきました。(2日目)

2017年03月17日
区分
android
報告者:
honda

こんにちは、hondaです。

DroidKaigi2017に行ってきた(1日目)

こちらではDroidKaigi2日目で聴講したセッションを簡単にまとめたいと思います。

2日目に見てきたセッション

ウェルカムトーク

DroidKaigi参加者には事前にアンケートを取っていてその結果がスライドで紹介されていました。 気になった項目を抜粋してみました。

・参加者の年齢

年齢 割合
20代  46%
30代  45%
40代  6%

私は30代の部類です。今後40代も増えてくるのでしょうか。

・事業分類

事業分類 割合
自社サービス  68.2%
受託開発  23.5%

受託開発ではなかな参加出来ないのでしょうか。弊社は自社サービスですが、参加させてもらえた事に感謝ですね。

・開発規模

開発規模 割合
3〜5名  36%
2名  25.8%
1名  24.7%
6〜9名  7.9%

1名〜2名での開発規模が5割行ってますね。職場でアプリエンジニアが少ないとこういったカンファレンスが本当にありがたいです。アプリ開発で6〜9名って逆に開発を回すのが大変そうなイメージなのですがどうやって回していっているのでしょう。

minSdkVersion

minSdkVersion 割合
4.1〜4.3  36%
4.0.x  33.7%
4.4.x  15.2%
5.0.x  12.4%

6,7はさすがにないですねー

DroidKaigi公式アプリ

  • 296PRs
  • 126issues
  • 68contributors

@konifarさん、ありがとうございました!アプリ助かりました。

Android ORMの選び方

Ormaが個人的に気になっていたので聴講しました。 なぜデータをローカルに保存するのか?どうやって保存するのか?そして、複数のORM(ActivitAndroid,greenDAO,Requery,SQLBrite & SQLDelight,Realm,Orma)を比較して、良し悪しを浮き彫りにするような内容でした。 プロダクトでRealmを使っている分、若干色眼鏡気味ではありますがはやり今のところはRealmなのかなと。 ただ、マイグレーションの煩雑さやequalToでの型安全の指摘は納得できるものでした。 Ormaは型安全に関しては解決されていて、Realmに比べて実装の軽さを感じました。 サービスや作るアプリの性質次第では採用してみたいですね。

未熟なチーム開発

弊社でもアプリチームは1年ちょっとでチームらしくなってきました。 さらに成熟させるヒントなどありそうだと思ったので聴講しました。 秩序づくりのところは大いに活かせそうでした。秩序とそのドキュメント化は面倒ですが大事ですね。 あとはよりテストブルな実装は大事。 実業務を進めながら新人教育をするのはなかなか難しい。。。

Kotlin + RxJava + Dagger2 + Orma + Retrofit で作るAndroidアプリ

より実践的な内容でいこーよアプリに活かせそうだったので聴講しました。 早口ではありましたが、DroidKaigiのアプリのコントリビュータをgithubから取得して表示するサンプルアプリを使っての説明でした。 とりあえず、RxJavaあたりから導入してみて、楽していきたい気持ちになりました。 Dagger2はテストとかも絡みそうなのでもうちょっと後から入れていこうかと。

4年続くアプリにおけるチーム開発

未熟なチーム開発と同じ理由で聴講しました。 UIファースト(UI駆動?)な開発スタイルでのバージョンごとでのアーキテクチャーの移り変わりと規約やドキュメントの整備やユーザインタビューでのアプリの質を高める話でした。 いこーよでもアーキテクチャーの見直しや規約、ドキュメントの整備は今まさに行っていることでとても参考になりました。 あと、目的別での部署を超えたチーム編成はとても興味深いものでした。

コマンドなしでぼくはAndroid開発できない話

コマンドで楽したい!という動機で聴講しました。

特に気に入ったコマンドを紹介します。

端末のワイヤレス接続

adb shell ip addr show wlan0 | grep 'inet ' | cut -d' ' -f6|cut -d/ -f1  ←端末のローカルIPを取り出す
adb tcpip 5555
adb connect "取り出したローカルIP":5555

条件としては端末とPCは同じWifi環境下にいる必要があります。 端末をUSBケーブルでPCとつなげて、上記コマンドを実行するとUSBケーブルが外れた状態でもコマンドを送ることが出来ます。

キーイベント送信

adb shell input keyevent KEYCODE_POWER ←電源キーを押す
adb shell input keyevent KEYCODE_SLEEP ←スクリーンをOFF

端末再起動

adb shell reboot 

Activity情報取得

adb shell dumpsys activity top

今表示しているActivitiyのクラスがわかる 今表示しているActivityのレイアウト構造がわかる!このアプリどういうレイアウトしてるんだろう?って思った時はレイアウト構造を丸裸に出来ます。

/data/data/packageName配管のファイルを見たい

adb shell run-as packageName

アプリの設定画面が見たい

shell am start -a android.settings.APPLICATION_DETAILS_SETTINGS -d package:"アプリのパッケージ名"

UIをカスタムしてあって、設定アプリが探しにくい端末だと使えます。

上記以外の使えるコマンドがここにまとめれてるそうです。

サンプルアプリをサクッと試したい

dryrun adbのコマンドではないですが、Githubに公開しているアプリをサクッと試すときにすごく重宝するツールです。

dryrun "GitHubリポジトリURL"

エンジニアが武器にするMaterial Design

まだMaterialDesignをものに出来ていないところがあるのでものにしたいために聴講しました。 ナノハさんではスピードも機能の1つと捉えてアニメーションもユーザに対するコンテンツの提供スピードを損ねないように実践されていて、とても興味深かったです。 その中でもアニメーションの改善で離脱率が半分に削減した話は特に興味深く登壇後やアフターパーティーでお話しを聞いてslackで弊社内のアプリチームに展開したところとても反響が大きかったです。 いこーよでも上手にMaterialDesignを活用して、ユーザに心地よくアプリを使っていただけるように改善していきたい。しよう。

テスト0から目指すクラッシュフリー率99%

テスト以外でも品質担保や不具合改善などの知見が得られそうだと思い聴講しました。 すごく当たり前のことですが、テストうんぬんの前のテスタブルにコードを書くというのが一番大切なんだなーと改めて感じました。 Activityからモデルを分離して、Interfaceを定義し、コンストラクタで依存するインスタンスを渡すようにするだけどすごくテスタブルでコードの見通しがよくなります。 あと、Fragment実装で一度は目にしたことがあるIllegalStateExceptionで悩んでる方はvultureを使うと幸せになれるようです。 最後にこの言葉は忘れないようにしよう。

まとめ

2日間ずっとお話しを聞き続けるっていうのは結構疲れました。 が、知見だらけの2日間はすごくエクサイティングで楽しかったです。 ずっと言い続けてますが、参加させてもらえたことに本当に感謝です。 この思いと知見をチームメンバーに伝えつつ、より良いプロダクトを作りたいですねー。 来年もぜひともDroidKaigiに参加したい! できれば、チームメンバー全員で参加して、お互い見れなかったセッションを現地で共有しあいたい!

以上、DroidKaigi2017に参加した感想でした。

 | 

技師部隊からの
お知らせ

【求人】エンジニア募集しています。

本頁の来客数
八十七万千百七十六名以上(計測停止中)

メンバー一覧

アクトインディ技師部隊員名簿

アクトインディ技師部元隊員

アクトインディへ

カテゴリー

アクトインディ

aaaa