XML形式のlog4j設定(log4j.xml)をGrailsで使用する
Grailsではロギングの設定がConfig.groovyで設定できますが、なんらかの理由でXML形式の設定(log4j.xml)で行ないたいことがあるかもしれません。しかし、その場合log4j.xmlをどこに置くか、という問題があり得ます。
この問題を説明します。まずGrailsでlog4jのXML設定を使用するには、springの設定ファイルresouces.groovyで、以下のようにlog4jConfigurerを設定します。
GRAILS_PROJ/grails-app/conf/spring/resources.groovy
beans = { log4jConfigurer(org.springframework.beans.factory.config.MethodInvokingFactoryBean){ targetClass = "org.springframework.util.Log4jConfigurer" targetMethod = "initLogging" // arguments = "grails-app/conf/log4j.xml" // ★1 arguments = "classpath:log4j.xml" // ★2 } }
問題は、★1のようにカレントディレクトリ相対で指定すると、warにしてtomcatなどにデプロイした状態では、tomcatが走行しているときのカレントディレクトリ($CATARINA_BASE)からの相対になり、読み取ることができないということです。だからといって、絶対指定も一般にはできないでしょう。
この解決策ですが、まずlog4j.xmlはgrails-app/conf配下に置いた上で、(★2)のようにxmlがclasspath配下にあると指定します。run-appの際には、grails-app/confはクラスパスに入っていますのでこれで動作します。
ただし、warにしたときには、conf配下は特にはクラスパスにはいっているわけではないので(Config.groovyなどはコンパイルされてclasses配下に移動している)、warの作成時にgrails-app/conf/log4j.xmlを適切なclasspathの通っているところ、たとえば以下ではWAR中の$WEB-INF/classes/配下にコピーするように、BuildConfigに以下の設定を追加します。
GRAILS_PROJ/grails-app/conf/BuildConfig.groovyに追加。
grails.war.resources = { stagingDir, args -> println "$stagingDir, $args" copy(file: "grails-app/conf/log4j.xml", todir: "${stagingDir}/WEB-INF/classes/") }
なおログファイルをそもそもどこに置くか、という設定はlog4j.xmlに記入しておく必要があります。XML設定の場合、grailsのConfig.groovyで設定するように、developmentのときとprod/testのときに場所を切り替えたりということはたぶんできないので、適当に絶対パスで指定することになるでしょう。
ちなみに、Wiki - Log4j XML Pluginというプラグインもあり、XMLでの設定に対応する、markupbuilderっぽい記法で設定することができるようです。これを使えば、もっとうまくいくのかもしれませんが試してません。
(周知) 予告: JGGUG大忘年会LT大会と、LondonのG*なカンファレンス行ってきた報告!+合宿の報告もあるよ! #jggug
今週末は、JGGUG忘年会です。あしたビアバッシュのピザなどを予約しますんで、参加希望のかたは今日中にぜひどうぞ。
JGGUG大忘年会LT大会と、LondonのG*なカンファレンス行ってきた報告!+合宿の報告もあるよ! - 日本Grails/Groovyユーザーグループ | Doorkeeper
建物とフロアは同じですが、会議室が通常と違いますので注意を。
LTネタ考え中。
Grailsで、tomcatを起動するというだけの目的でファンクショナルテストを設定する方法
Grailsでコントローラの試験を、モックを使わず実際の通信を使って試験したいケースがある。たとえば、Rest APIを開発する場合、Rest APIはブラウザを使用しなくても簡単に呼び出せるものなので、ユニットテストがしたいです。もちろんGrailsにはコントローラをモックで試験する機能があるが、rest APIを呼び出せば済むものを、偽物ですます必然性があまりないと個人的には思う(もちろん試験実施時のtomcat立ち上げ下げのための実行時間を短縮したい、などの関心はありうるが)。
このためにはファンクショナルテストを実行する必要がある(unit/integrationではtomcatはたちあがらない設定になっている)。しかし、Grailsでは、ファンクショナルテストは単にtest/functional配下にテストコードを置くだけでは実行できず、一つなんでもいいからファンクショナルテスト用プラグインを導入しておく必要がある。
Gebの機能は特に使いたくないとしても、たとえばHttpClientなどでRESTの試験だけしたいという場合に、以下の手順でGebを形だけでも導入することで、ファンクショナルテストを行うことができるようになる。
(1) BuildConfig.groovyのdependenciesセクションに以下を追加
dependencies {
:
test "org.gebish:geb-spock:0.10.0"
}
(2) BuildConfig.groovyのpluginsセクションに以下を追加
plugins {
:
test ":geb:0.10.0"
}
(3) BuildConfig.groovyでfolkedモードをfalseに設定する
grails.project.fork = [ : test: false, // ← run: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false], war: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false], console: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256] ]
(4) テストコードをGRAILS_PROJECT/test/functional配下に作成。
ここでのテストコードは、Gebの機能を使う必要はなく、HttpClientなどを使用した通常のSpockコードで良い(もちろんGeb使っても良い)。
Grails/Groovyでのカバレッジ取得に関してのTIPS
プリミティブ最適化を抑制することでブランチカバレッジをましなものに
- Test Code Coverage Plugin(http://grails.org/plugin/code-coverage)
しかし、上記を使用した場合、分岐網羅(ブランチカバレッジ)は多くの場合、期待する値が取得できない。この理由の1つは、Groovy 1.8以降で導入されたプリミティブ最適化によって、型がプリミティブかどうかによっての条件分岐を行うコードがGroovyのコード生成器によってバイトコード上生成されているためである。Test Code Coverage Pluginはバイトコードレベルでカバレッジ情報を収集するので、ソース上に表われない暗黙の分岐をカバレッジ率の分母に計上してしまう。そしてその値は一般には100%にすることが困難である。
本来Groovyにおいて、プリミティブ最適化を抑止するためのコマンドラインオプション「--disableopt int」が存在する。しかし、GrailsではGroovyをコマンドラインから起動するわけではなくオプションが指定できない。
この問題の対処の一つは、この記事のリンク先にある、「renataogarcia/disableOptimizationsTransformation · GitHub」のコンパイル結果jarである
「DisableOptimizationsTransformation-0.1-SNAPSHOT.jar」をダウンロードし、クラスパスに通すことである。こうすればGrails上でのGroovyの最適化が抑制されるため、ブランチカバレッジが正確に測定できる可能性がたかまる(ただし、ブランチカバレッジが期待する値にならない理由はこれだけが原因とは限らないことに注意*1 )。
なお上記jarをクラスパスに通す方法として以下が考えられる。
どの方法でも良いが、「試験では最適化抑制をし、プロダクトコードでは最適化する」ということはリスクになるため、テスト時に常に抑制するのは怖い気がする(プロダクトコードでも最適化抑制するなら別だが)。なので、テストのとき常に、ではなく、カバレッジ判定のときだけ一時的な指定をするという意味で1でも良いかもしれない。
なお、上記の設定前後でgrails cleanを実行した方がよい。
Dockerを使ってGrails開発
Grails開発でdockerを使用するためのDockerfileを、備忘録兼ねて晒します。開発中に使うものです。運用用は、別のものになるでしょうかね。
onesysadmin/docker-grailsを元にして、Proxy設定を行い、いくつかの工夫をしております(プロキシ設定については、Dockerコンテナに透過的プロキシ設定とかをすれば不要なのかも)。
利点
Grails開発でDockerを使う利点は以下の通り。
- 準備が簡単。Grailsのインストールはもとより、gvmやJDKのインストールすらいらない。grails wrapperなどもいらないわけだ。
- 環境を汚さない。JDKのインストールをしなくてすむ、Postgresなどをコンテナに封入して連携する、などによる。
- 設定含めた開発時実行環境を開発メンバー間でシェアできる
- Dockerはlinuxなので、開発環境windows、実行環境linuxによる差異をなくせる。windowsの場合、boot2dockerなどを使って仮想マシン上でlinuxを動かすことになる(MacOSXではDocker 1.3のboot2dockerでホストのフォルダの共有とVOLUME対応(-v指定による共有)がされているそう]だが、まだ試していない。)。
Dockerfileの準備
まず、onesysadmin/docker-grailsをcloneするなりダウンロードするなりして、使用したいGrailsのバージョンのDockerfileを、Grailsプロジェクトのトップディレクトリにコピーします。
Dockerfileの修正
以下に修正点を示します。修正したDockerfile全体はこちらのgistに。
Proxyの設定
開発環境がファイアウォール背後でproxyの設定が必要になる場合は以下のように設定します。今回の場合、プロキシは以下において必要になります。
ENV PROXY_HOST 10.2.3.4 ENV PROXY_PORT 18080 ENV http_proxy http://${PROXY_HOST}:${PROXY_PORT}/ ENV https_proxy http://${PROXY_HOST}:${PROXY_PORT}/
ENTRYPOINT, EXPOSEの設定
ENTRYPOINT [ "/usr/local/bin/gvm-exec.sh", "grails", "-Dgrails.work.dir=/app/.grails" ] EXPOSE 8080
gvm-exec.shや後で出てくるgvm-wrapper.shは「onesysadmin/docker-grails」の親であるイメージ「onesysadmin/gvm」が入れてくれているスクリプトです。
RUN関係
RUN gvm-wrapper.sh install grails 2.4.4 && \ gvm-wrapper.sh flush archives && \ cd /app && \ gvm-exec.sh grails -Dgrails.work.dir=/app/.grails add-proxy client --host="${PROXY_HOST}" --port="${PROXY_PORT}" && \ gvm-exec.sh grails -Dgrails.work.dir=/app/.grails set-proxy client RUN mv /root/.grails/ProxySettings.groovy /root/.grails/ProxySettings.groovy.bak && \ sed -e "s/'http.nonProxyHosts':''/'http.nonProxyHosts':'localhost'/" /root/.grails/ProxySettings.groovy.bak > /root/.grails/ProxySettings.groovy
proxyの設定がポイント。interactiveにしたときでrun-appしてstop-appして再起動したときにportが所有されつづけてしまう問題(address already in useとなる)の回避のため、nonProxyにlocalhostを設定します。
GRAILS_PROJECT/grails-app/conf/BuildConfigの修正
grails.project.dependency.resolutionの「mavenLocal()」を以下のように修正します。
mavenLocal("/app/.m2")
コンテナのビルド
$ cd /path/to/grails/project $ docker build -t you/project .
DBコンテナとの連携
必要に応じて。以下はPostgresを前提とします。
Postgresのコンテナは、以下を参考に「orchardup/postgresql/」を使用します。
- https://registry.hub.docker.com/u/orchardup/postgresql/
- http://deeeet.com/writing/2014/03/20/docker-link-container/
以降、通常のDB接続の設定(ドライバ追加のためにBuildConfig.groovyのdependenciesに「 runtime 'postgresql:postgresql:9.1-901.jdbc4'」を追加するなど)を行なった上での設定です。
DataSource.groovyの修正
driverClassNameやDB認証などの修正
dataSource { pooled = true // jmxExport = true driverClassName = "org.postgresql.Driver" username = System.getenv("DB_ENV_POSTGRESQL_USER") password = System.getenv("DB_ENV_POSTGRESQL_PASS") }
JDBC URLの設定
必要な環境について、URLを設定します。
url = "jdbc:postgresql://${System.getenv('DB_PORT_5432_TCP_ADDR')}:${System.getenv('DB_PORT_5432_TCP_PORT')}/docker"
「docker」はorchardup/postgresqlが初期的にCREATEしてくれるデータベース名です。別の名前にしたいときは、orchardup/postgresqlの起動時にDB名を指定できるので、それにあわせたものを使えば良いでしょう。
DB連携したGarilsコンテナ実行(例)
$ docker run -d -p 5432:5432 -e POSTGRESQL_USER=docker -e POSTGRESQL_PASS=docker --name pg orchardup/postgresql:latest $ docker run -d -p 8080:8080 -v /path/to/grails/project:/app --link pg:db you/project:latest run-app
おまけ
ENTRYPOINTを指定しているので、bashなどを起動したい場合は、
$ docker run -it -v /path/to/grails/project:/app --link pg:db --entrypoint "bash" you/project:latest
こうかな。
まとめ
上記は、Grailsに限らず、開発時にdockerを使用する場合のパターンの一つになると思うが、ホストのディレクトリのマッピング(-v)を駆使してソース含めほとんどのファイルを外に置くコンセプトなので、単独実行するのと使用感も制約もあまりかわらない。しかし、このパターンだと、リモート利用、つまりdockerクライアントを実行するマシンとdockerデーモンが動作するマシンが別の場合には利用できない。つまりdockerデーモンを動かしているホストのlinuxマシンにログインできるアカウントがないと利用できない。dockerは単体でNFSや高速rsyncみたいなものを提供しないのが残念である。できればもっと開発用PaaSっぽく使えるのに!(ADDじゃ遅そうだし動的更新ができない)*2。
このパターンであれば、Eclipse/GGTSなどをdockerで実行するのもそんなに難しくはない。
*1:なお、grails.project.dependency.resolver = "maven"にしているのにもかかわらず、/root/.grails/ivy-cacheが作成され何かが入っている。理由不明
*2:将来的にはFUSEで対応されるかもしれないんだって。やったー。
PHPのようにGrailsを使う方法
GrailsアプリケーションではGSPのビューは以下の方法で表示できます(他にもあるかも)。
- コントローラーアクションの結果として表示
- renderで指定したビュー
- コントローラ名とアクション名から定まるデフォルトのビュー
- ビューを直接表示する
- URLMappingでURLパターンからビューを指定する
しかし、「ビューだけ」の、GSPの集合だけで機能するサイトは作れません。JSPで言うと、JSPだけのServlet無しのWebサイト、あるいはPHPの埋め込みページだけで構成するようなサイトが作れないのです。
そうなのはおそらく意図的であり、それがWeb開発の黎明期にアンチパターンとしてみなされてきたので排除されているのでしょう。上記のような制約「GSPはコントローラからの画面遷移を経て表示される」があることのメリットは、GSP内で使用できるデータの由来を明確化できること、そして、画面遷移のリンク情報をページの中にばらまくのではなく、コントローラのコードで指定することでしょう。
ただ、そうだとしても、以下のような開発手順をとりたいときがあります。
- 開発時に、まずHTMLでのモックアップがある(デザイナーや企画者が作成するなど)
- それをGSPに一括変換する
- 適宜以下を実施する
- FORMの部分をGSPタグ<g:form>でおきかえる
- レイアウトをSiteMeshでおきかえる
- 静的リソースはアセットにして…
現状のGrailsでは、上記の手順にはなりません。ページの持つ機能をコントローラ・アクションの階層に分割し、そしてコントローラアクションの結果としてのページ群に対応させ…、というトップダウンの機能設計をせざるを得ず、直接画面確認に行けないのです。
本記事では、この問題を解決するために、ページ間遷移について個別のコントローラ無しGSPの集合としてアプリを作る、つまり「PHPのようにGrailsを使う」方法を紹介します。
DispatchController
showPageというアクションを1つだけ持った以下のコントローラDispatchControllerを用意します。
package common class DispatchController { /** grails-app/view/pages配下に配置された、コントローラに所属しない * (コントローラのアクション名と一致していることを期待せず、リンク * をたどることやメニュー選択の結果として表示されることを想定して作 * 成されている)ページを表示する。 */ def showPage() { render view:"/pages/${params.pageName}" } }
URLMappingに以下を追加します。
"/pages/$pageName"(controller: "dispatch", action: "showPage") "/"{ controller = "dispatch" action = "showPage" pageName = "index" }
これで準備は終わり。あとは
配下に、たとえば
- <GRAILS_PROJ>/grails-app/views/pages/index.gsp
- <GRAILS_PROJ>/grails-app/views/pages/a.gsp
- <GRAILS_PROJ>/grails-app/views/pages/c.gsp
というGSP(htmlの拡張子をgspにリネーム)を置けば、
<a href="${request.contextPath}/pages/a">..</a>
というようなリンクで相互にGSPビューを表示できるようになります。views/pages/index.gspがトップページになります。
あもちろん、<g:link>とか使っても良いです。
まとめ
コントローラ・アクションの編成の方法論というのは、Grails初学者にとっては難しいところです。
Scaffoldではドメイン/アクションをベースにしたビューを生成してくれますが、Webサイトというものが一般には「DBテーブルエディタ」ではない以上、この構造は必ずしも一般的ではなく、参考にならないときもあります。
テーブル単位のCRUDに限らない、コントローラ・アクションの階層をどう編成すべきか。この問いに、なんとか解を出さないと、index.gsp以外の1ページを表示することすらままならないかもしれないわけです。デフォルトのindex.gspのようにページごとにURLMapping追加していってもいいんですが、面倒だし不毛ですよね。
多数のページから呼ばれ得る多数のコントローラの関係があるとき、むしろコントローラ/アクションの階層をページ集合とは独立した機能群として設計する方がやりやすいときもあるでしょう。
このPHPぽいアプローチならば、動くサイトを拡張しながら、コントローラを都度考えていくことができます。また、それとは別に、Grailsの初心者にとってのとっつきは、はるかに良くなると思います。
もちろん、この技法と通常のコントローラベースのアプローチを組合せても全く問題ありません。
問題もありえます。この方法だけで、大規模サイトとか業務システムのサイトの全体をつくるべきではないでしょう。単機能の1ページサイトでも不要でしょう。でも、ヘルプページとか、社長のご挨拶ページとか、サイトマップとか、そういうページを含むサイト全体を作るために、この技法を織り交ぜて適用するのが有用なことがあるでしょう。
ということで、選択肢の一つとして心にとめておいておくと便利なのではないかと思い、紹介させてもらいました。
ヒアドキュメントと複数行文字列について
「ヒアドキュメント」をなんで「ヒアドキュメント」っていうかを調べてみた。以下が参考ページ。
- http://programmers.stackexchange.com/questions/143918/why-is-it-called-a-here-document
- http://ja.wikipedia.org/wiki/%E3%83%86%E3%83%AC%E3%82%BF%E3%82%A4%E3%83%97%E7%AB%AF%E6%9C%AB#.22Here_is.22_.E3.82.AD.E3.83.BC
わかったこと
上記の内容が正しいとして、読み取ったことは以下のとおり。
- 「ヒアドキュメント」は「"Here is" document」から来ている。
- "Here is"は何からきているかというと、昔テレタイプ端末にあった「Here is」というキー。
- 「Here is」キーは何かというと、押すとあらかじめ端末ごとに設定できた20文字ぐらいの文字列をホストに送り返すキー。この用途は、例えば、その文字列に端末の識別IDなどを設定しておいて、1キーで「この端末はXXXだよ」とホストに送ること。
- さらに、ホストが送ったENQという制御文字を端末が受けとると、自動的にHere isキー登録文字列をホストに送信するように設定することも可能。ENQの送信は、ホスト側から、ログインしてるあんたの端末どれよ/あんた誰よ(操作者の名前をhere isキー文字列に登録しておいた場合か)、という情報の問合せをするのに用いられた。
ShellスクリプトにおけるHere documentの意味について
上記参考リンクには、それほど詳細には説明されていないので、ここからは推測も交えて、になる。
Shellスクリプトにおけるヒアドキュメントは、Shellが起動する、コマンドのプロセスに対して特定の文字列をパイプ(もしくはシングルタスクOSでは一時ファイル?)を通じて送りつける、という機能である。
cat <<EOT ... EOT
上では、プログラム中に書かれた固定文字列「...」の部分をShellが切り出し、そのデータをパイプ(やひょっとしたら一時ファイル)を通じてcatプロセスに送り込んでいる。これがホストに対してHere isキー登録文字列(=固定文字列)を送付している振舞に似ている。だからこの機能をHere (is) documentと名付けたのではなかろうか。
複数行を含むことができる文字列定数をヒアドキュメントと呼ぶことについて
時はながれて。
これを調べたのは、現代のプログラミング言語のいくつかにおいて、「改行文字を含むことができる」程度の機能をもった文字列定数の表記法を、「ヒアドキュメント」と呼ぶ場合があることにもともと違和感があったからである。
Shellスクリプトにおいては、任意の文字列を処理対象としてコマンドに渡すのにパイプやファイルを経由するしかない場合があるが、その処理を簡易に記述する機能を「ヒアドキュメント」と呼んだのは、当時において合理性があったように思われれる。(まさか、"Here is"キーがその後消滅することなんか、誰にも想像つかないし!)
また、PerlやRubyなどの言語における「ヒアドキュメント」機能は、その背景機構や元々の命名理由とは無関係にShellスクリプトとの表記上の類似性だけでそう呼んでいると推測できる。なぜなら、Shellにあった、起動したプロセスにパイプ繋いで送り込む、という様相が存在しないためである。このことを批判するつもりは別にないが、Shellスクリプトにおいて「ヒアドキュメント」という名称が背後機構をちゃんと説明するものであった、という利点は失うこととなっている。
しかし、PythonやGroovyなどの複数行文字列定数で使用する"""〜"""などには、表記上の類似性すらもないので、ヒアドキュメントと呼ぶ必要が全くないと思う*1。なので自分はそう呼ばないことにしている。なので「プログラミングGroovy」の本にもヒアドキュメントという用語を使うことは意図的に避け、確か複数行文字列定数と呼ぶように徹底したのであるよ。
(2014/12/24)
記憶をたどれば、昔のGroovy(Classic Groovy)には、「本当の(RubyやPerlの意味での)」ヒアドキュメントが実際にあったのですが、JSRに提案される段階で削除されました。ヒアドキュメントの廃止 - どうせ見苦しいですから (smile)。もし使いたい場合はかわりにトリプルクォートを使いましょう
この意味でも、Groovyの複数行文字列定数をヒアドキュメントと呼ぶのはまぎらわしい。
*1:改行を含んでいるとドキュメントっぽいから、ということが理由なら、バックスラッシュ('\')で行末エスケープした通常の文字列定数もヒアドキュメントと呼ぶべき。さらに「ヒア」の意味があるものすべてにヒアをつけるべき。ヒア整数、ヒア引数、ヒア関数…