mmag

ハマったことメモなど

Ectoのカスタムロガー

github.com

3.0-rc.1がもっぱら話題のEctoですが、ロガーを追加する方法をたまたま見つけたので書いておきます。Ecto.Repouseするときにloggersっていうオプションを渡します。

defmodule MyApp.Repo do
  use Ecto.Repo,
    otp_app: :my_app,
    loggers: [
      {Ecto.LogEntry, :log, []},
      {MyApp.CustomLogger, :log, []}
    ]
end

loggersのデフォルト値は[{Ecto.LogEntry, :log, []}]なので、今の挙動を変えたくないときはこんな風に追加するとよさそ。試しにこんなlog/1で動かしてみると、

defmodule MyApp.CustomLogger do
  def log(entry) do
    IO.inspect entry, structs: false
  end
end
[debug] QUERY OK source="nippoes" db=8.3ms
SELECT n0."id", n0."date", n0."content", n0."user_id", n0."inserted_at", n0."updated_at" FROM "nippoes" AS n0 WHERE (n0."date" = $1) ORDER BY n0."inserted_at" DESC [{2018, 10, 17}]
%{
  __struct__: Ecto.LogEntry,
  ansi_color: :cyan,
  caller_pid: #PID<0.599.0>,
  connection_pid: nil,
  decode_time: 11000,
  params: [{2018, 10, 17}],
  query: "SELECT n0.\"id\", n0.\"date\", n0.\"content\", n0.\"user_id\", n0.\"inserted_at\", n0.\"updated_at\" FROM \"nippoes\" AS n0 WHERE (n0.\"date\" = $1) ORDER BY n0.\"inserted_at\" DESC",
  query_time: 8328995,
  queue_time: 68000,
  result: {:ok,
   %{
     __struct__: Postgrex.Result,
     columns: ["id", "date", "content", "user_id", "inserted_at", "updated_at"],
     command: :select,
     connection_id: 15165,
     num_rows: 0,
     rows: []
   }},
  source: "nippoes"
}

こんなログが出ました。引数はEcto.LogEntryの構造体ですね。query_timeナノ秒)を見て、時間かかってたらLogger.warnしたりとか良いんじゃないでしょうか。本番環境だとNewRelicだなんだありますけど、開発環境とかテストとかで。

ExUnitAssertMatchっていうパッケージを書いた

hex.pm

伝わる人には伝わる言い方をすると、https://github.com/r7kamura/rspec-json_matcherみたいなことがしたい。

例えばPhoenixのコントローラのテストとかで、レスポンスのJSONの形式をテストしたいときに

assert json["user"]["name"] == "John"
assert json["user"]["age"] == 28
assert json["user"]["orgs"][0]["name"] |> is_binary()

とか全部書くのダルいし、もうちょいどんなJSONを求めてるのかわかるようにしたかった。あとElixirのパッケージ最近書いてなくて、リハビリしたかったというのもモチベーションの1つ。最初はマクロで実装しようと考えて書き始めたけど、そのまま機能追加してメンテしていく自信がなくなったのでやめて、以下のようになりました。

alias ExUnitAssertMatch, as: Match

expected = Match.map(%{
  "user" => Match.map(%{
    "name" => Match.literal("John"),
    "age" => Match.literal(28),
    "orgs" => Match.list_of(
      Match.map(%{
        "name" => Match.binary(),
      })
    ),
  }),
})

Match.assert expected, json

気をつけた点としては

  • useさせない
  • Configつかわない

です。マクロやめたのでuseはまぁしないっしょというのはありつつ、他のパッケージのコードにuseが入ってると結構コード読みづらくなるなーという実感があるので、自分がつくるものではやらんようにしよーというもの。Configややこしい問題はhttps://elixirforum.com/t/proposal-moving-towards-discoverable-config-files/14302とかhttps://elixiroutlaws.com/1でも言われていて、徐々に解決されていきそうだけど避けるようにしている。今回のパッケージはテストのときのためのものなので大して関係ないけど、設定っぽいものは引数で渡せるようにしてます。ex_unit_assert_matchって名前は長いなと思ったけど、https://hex.pm/docs/publish#naming-your-packageに従ってこうしてます。

趣味プロジェクトで使いながら、色々追加してこうと思います。

Gettextってよくできてるんだな、という感想

Elixir経由でしか使ったこと無いけど、Wikipediaを読むとどれでも大体同じっぽい。

joe-noh.hatenablog.com

なんでよくできてると思ったかというと、仕事でRails書いててtranslation missingをチラホラ出してしまい「直してもまだ漏れがあるのかないのかわからんからコードから抽出してくれんか」と思ったところで、そういえばGettextはこれやってくれてたんだな、となったのでした。最初に触ったときはワンステップ多い印象でしたが、意味あったんすね。