トップ 最新 追記

2018-11-11 [長年日記]

_ 手っ取り早くgcloudコマンドでGoogle Apps Scriptの出力を扱う準備

Logger APIはツラカッタ

Google Apps Script には Script Editor もあるし、デバッガもついているし、G Suite が使えれば無料で使えるし、Time-based Triggerもついてるし、かなりできるやつではあるんだが、真面目に使おうと思うといろいろやっかいな問題を抱えていた。

その一つがログ、特にちょっとでも大きめな、例えば1画面に収まらないデータを扱おうと思うと途端に面倒くさくなってしまう。

自分が気になっていた Logger API の問題は以下のようなものだ。

  • Object を食わせると謎の独自 Stringify を行う
  • 一つのログが大きすぎると勝手に端折ってしまう

結構しんどい。

時代はStackdriver Logging

で、かつては Spreadsheet に吐くとかいろいろ工夫がなされていたわけだが、今はありがたいことに標準で Stackdriver Logging に対応している。

Stackdriver Logging for Google Apps Script is now available | Google Cloud Blog

日本語の公式発表が見つからないので英語の方のリンクを貼っておく。

使い方

特に事前準備は必要なくて、コード上の記法が増えただけと言ってよい。

  • これまでもあった Logger.log() はそのまま Script Editor 上のログに
  • console.log() は Stackdriver Logging のログに

それぞれ送られる。普段 Google Apps Script は書いていないが JavaScript を書いているという人が何気なく使うと Stackdriver Logging の方へ送られるという寸法だ。*1

料金の話

基本的には無料で使える。

というのも、GCP プロジェクトとして課金アカウントに紐づけてなくても使える からだ。容量の話などを気にしている人もいたが、そもそも課金アカウントと紐付いていないと請求はされないわけで、業務でその辺の管理をしっかりやっていくぞという場合でもなければ気にする必要はないと思う。

でもWeb UIはダルい

本題はここから。

常に 20" 以上のモニタを広く使ってコード書いてる人にはあまり関係ないかもしれないが、13" クラスの画面と terminal が主戦場の人間にとっては全部 Web UI というのは暴力でしかないし、実際に取得できた JSON のデータを解釈してどう整形しようか考える段階では普通に手元のエディタで処理したいわけですよ。

そこで Stackdriver Logging のデータを gcloud logging read することにする。

gcloudコマンドの準備

Google Cloud SDK のインストール | Cloud SDK のドキュメント | Google Cloud

自分の mac にどうやって入れたかは忘れたけど、たぶん最新版を inteactive に入れたような気がする。*2

  1. gcloud コマンド入れる
  2. gcloud auth login
    • ブラウザでログイン
  3. OAuth2 の認可を明示

これで自分が owner になっているプロジェクトのログは自由に見れるので*3

実際にreadする

Command Line Interface | Stackdriver Logging | Google Cloud

にしたがって

gcloud logging read --project <projectId>

と打ってあげればログを terminal で閲覧できる。快適。

default formatは実はYAML ?

Stackdriver Logging に JavaScript Object を渡すといい具合に jsonPayload という形でログが残る。これは非常に便利な機能なんだけど、gcloud logging read で取得できるログはどうも標準では YAML 形式っぽい。

JSON のデータが取得できると思って目grep しているとスルーしてしまうので注意が必要。または

gcloud logging read --format json

すれば JSON でログを取得できるので、こっちの方が目の引っかかりはよいかも。*4

tailできない問題

もう一つ、gcloud logging サブコマンドでは tail -f のようなことはできない*5ので、そこら辺は --limit と組み合わせるなりする必要がある。

payloadだけクレ

jq使え。

jq

| jq '.[].jsonPayload'

みたいな感じ。

実は jq の出力はそのままだと正しい JSON にならない。もうちょっと頑張って正しく JSON にしたい場合は jq -cとawkでndjsonを本来のJSONにする - あーありがち(2018-11-13) をどうぞ。

感想

なんでも揃ってるクラウドインフラの中に G Suite があるのマジで強い。でも Advanced Service オメーはダメだ。リファレンスをください。API とのマッピングどうなってるのかわっかんないよ。

Tags: Google

*1 実は自分が久しぶりに GAS を書いたら console.log() を使っているのに Script Editor のログが表示されずに悩んだ。

*2 自動化する場合は versioned archive を使うといいんだけど、今回の話題と違うので割愛。

*3 個人だとこんなもんで十分だけど、業務の場合は IAM Role の設定で悩んでください。

*4 JSON は手で書くのはつらいが、読む分にはそうでもない気がする。YAML は逆に小さいうちはとても書きやすいがでかくなると目で追うのがつらくなってくる。

*5 App Engine の log は可能


2018-11-13 [長年日記]

_ jq -cとawkでndjsonを本来のJSONにする

[追記] 予想通り、このスクリプトは不要でした!

| jq <filter> | jq -s

でイケます! ポイントは jq と jq -s を分けること! jq -s はレコード指向ではなく全体をまるっと扱うオプションなんだけど、おかげで filter の指定方法が変わってしまう。しかし「抽出、加工」と「整形」を分けてしまうことでその問題も解決できちゃう。

さすがっす…。


改めてまとめると、jq の出力だけに注目すると

default 謎の format
-c ndjson
-s いわゆるJSON

ただし、-s オプションは filter の動作も変わってしまうので、

jq <filter> | jq -s

で出力するとよい。


以下は要らんドヤりでっす!

jq は JSON の中から欲しいデータを抽出したりできる便利なコマンドなんだけど、そのまま出力すると NDJSON ( Newline Delimitted JSON ) のようなものになる。

{
  "key": "value"
}
{
  "key2": 2
}
{
  "key": null
}

このように Array を意味する "[" "]" もその区切りの "," もない形になってしまう。

ndjson

正確には1行1レコードじゃないので NDJSON とも呼べなくて、こういう微妙なフォーマットをデフォルトにされると他のツールと互換性がなくなってしまうので嬉しくない。

そこでいわゆる本来の JSON にしたい場合はちょっと工夫が必要になる。

※ もしかしたら jq 自身にそういうオプションがあるかもしれないので、その場合は以下のコードは無駄です。

jq には -c オプションがあるので、これを使って出力すると余計な改行などがカットされて正しく NDJSON になる。そのうえで、こんな awk スクリプトを用意して、

#! /usr/bin/awk -f

BEGIN {
    print "["
}

{
    if ( last_line ) { print last_line  "," }
    last_line = $0
}

END {
    print last_line
    print "]"
}

以下のように

| jq -c <filter> | awk -f nd2purejson.awk

みたいなことをして各行のお尻に "," を付加して [ と ] で挟んでやれば ok.

awk はこんな風に面倒なロジックを考えずにちょっとした工夫で加工が済んじゃうのがいいんだよねぇ。

今回の工夫は今読み込んだ行を出力するのではなく、さっき読み込んだ行を出力するようにタイミングをずらしてやると、最後の END で自動的に最終行だけ異なる出力にすることができるというもの。これでいわゆる「ケツカンマ」を避けて正しい JSON を作ることができる。

※ 逆に JSON は jq -c . だけで NDJSON になるよという話でもあるんだけど、さすがにこれだけで独立したエントリ書くのははばかられますな。

Tags: Awk JSON

2018-11-18 [長年日記]

_ 怠惰なRubyistがNginxをできるだけ楽に使うことを考えたメモ

Nginx 使うには使うけど細かい設定はしていない*1 Rubyist が Nginx だけで何かをする場合の設定をどうしたかのメモ。予め断っておくと、本当に今さらで内容も全然ない話。

単に開発環境でWebサーバが欲しい場合はLLでよい

はい。最初にものすごく身も蓋もないことを書くけど、Nginx を使う必要はない。むしろ準備に手間が掛かる。Python の SimpleHTTPServer とか Node.js の http-server とか、そんなんでいい。

動的にサーバサイドを書く? Rackでいいでしょ。言語もWebサーバも選べない? なるほど、頑張ろう。

Nginxはreverse proxyのために入れる

単純な Web アプリのためにはそんなに proxy 必要ないけど、一つ一つのアプリをシンプルに保とうとするほど、proxy は便利に使える。ということで Nginx を使うときにはほぼ proxy 必須という縛りでよいと思う。

必要な時だけ動くコマンドとして使いたい

Nginxはコマンドライン引数だけで動かすもんじゃないので設定ファイルを書く。こんな感じ。もちろん daemon off だ

daemon off;

events {
}

http {
  include  mime.types;
  sendfile on;

  server {
    listen 3000;

    location / {
      ..
    }
  }
}

events section がないと怒られるけど、別に中身は要らない。mime.types は残念ながら必要で、これがないとブラウザが CSS とか無視してしまう。自分で作ってもいいし、適当に github から落としてきてもよい。

で、ここで大事なことの一つは log をファイルに紐付けないことで、

http {
  access_log /dev/stdout;
  error_log  /dev/stderr debug;
}

※ error_log の debug 指定はお好みで。

しといてあげないと起動した Nginx のログはどこかに設定されているデフォルトのファイルに吐き出されていて terminal 上で全然確認できない。daemon off とこの設定はセットと思ってよい。

面倒なのは絶対パス

で、実際に設定をするわけだけど、やっかいなのは Document Root で、本当は起動時に相対パスで指定できるのがいちばんお手軽なんだけど、残念ながらそういう機能は Nginx にはないっぽい。まぁ daemon として動作する場合には間違いなくフルパスが必要なので、仕方ないっちゃ仕方ない。

こういうやつね。

http {
  server {
    location / {
      root /path/to/root
    }
  }
}

で、じゃあどうするかというと、erb でワンクッション置いて埋め込むとよい。これは Heroku の Buildpack を参考にしていて、

#! /bin/sh

#
# Usage: start-nginx -c <config>
#

erb $2.erb > $2
nginx $@

という sh script を用意して、例えば

./start-nginx -c `pwd`/config/nginx.conf

のように動かす。

その nginx.conf.erb の中では

location / {
  root <%= File.absolute_path(File.dirname(__FILE__) + '/../<root>) %>;
}

みたいなことを書いておけばおっけー。

要は

相対パス → 絶対パス変換を nginx に渡す前のどこかでやる

ってだけ。sh script の中で erb に渡す前に変換してやれば変換のコードは減るけど、 $@ で丸ごと引数渡してる部分を書き換えてやらないといけないのが面倒でとりあえずこんな感じにしてある。

まとめ

登場人物は以下の通り

  • Nginx
  • ERB
  • erb を叩いたのち nginx を起動する sh script
  • nginx.conf.erb

で、全然お手軽じゃない。

でも portable にしておかないと他人と共有できない*2し、自分の手元でもうっかりプロジェクトの場所を動かしたら簡単に nginx.conf は壊れるものなので、この程度なら許容範囲かな?という辺りの面倒くささにしてある。

Tags: Web Ruby

*1 Apache 時代で直接サーバ管理するのをやめた

*2 「自分の環境しか考えてない」とか「各自がそれぞれ書き換えるのが当たり前でしょ?」と言わんばかりのベタ打ちのコードも稀によく見る


2018-11-22 [長年日記]

_ charset指定のないHTMLを扱う

NokogiriでHTMLを出力し直したら自動的にcharset指定が生えた

Nokogiri:HTML::Node#to_html でイケる。

Chromeはextentionで対応

Chrome はバージョン 55 からエンコーディング関係のメニューが消え、自動判別もしなくなった。いやいや、自動判別が遅くなるのは分かるけど、メニューくらい残せよ、と思うけど消えちゃったものは消えちゃったのだ。

Web サーバから配信している場合はサーバが charset 指定を付加していれば解釈できるが、local で直接開いている場合や Web サーバが charset を付加してくれない場合は Chrome ではこうした HTML を開くと正しく解釈できずに文字化けしてしまう。

そういう時は extension で対応するようだ。

https://chrome.google.com/webstore/detail/set-character-encoding/bpojelgakakmcfmjfilgdlmhefphglae

Firefox

バージョン 63 現在、特に困らない。


2018-11-23 [長年日記]

_ Middleman 4.2.1 + External Pipeline + Parcel 1.12環境を作れた

結構シンプルに作れたと思うので満足してる。

素材

静的サイトジェネレータ再び

Middleman ははるか昔、みんなが話題にする前に触っていたのだけれど、Asset Pipeline なんて許されない、みたいな雰囲気になった v4 からは距離を置いていた。Webpack とか生で触りたくないし。

しかし、いよいよもう一度静的サイトジェネレータを真面目に扱えた方がいいんじゃなかろうかという思うようになって*1、食わず嫌いだった Middleman v4 の External Pipeline をとりあえず使えるようになっておいた方がよさそうだよなーあー面倒くさいなーとか考えていたらふとそういえば2017年末に Parcel を試して*2「production では厳しいけど、お試しならいんじゃね?」という感触を抱いていたことを思い出したので組み合わせて試してみた。

External Pipeline向けassetsはMiddlemanの管轄外に置くべし

いくつか試したみたところ、以下のように Middleman の source/ ディレクトリとは別に置いた方がよさそうだということが分かった。

  • source/ とは別に assets/ を掘る
  • images/, stylesheets/, javascripts/ はその中に
  • これらの中は一切 Ruby で処理しないので erb は置かない

フライング情報も含めてできあがったディレクトリレイアウトはこんな感じ。

.tmp/dist/
source/
  layouts/
assets/
  images/
  javascripts/
  stylesheets/
build/

External Pipelineは外部のコマンドを叩き、結果がどこに生成されるかを指定すること

Middlemanの設定 config.rb としては以下のようになる。

activate :external_pipeline, {
  name:    :parcel,
  command: build? ? 'yarn build' : 'yarn watch',
  source:  '.tmp/dist'
}
..
configure  :build do
  activate :asset_hash
end

build? というのは middleman build コマンドが叩かれたか否かを判別しているらしい。

ここで source というのは middleman 標準の source ディレクトリと同じ意味に扱う場所のことで、呼ぶ出す外部コマンドに対しては

ここで言う source の場所に出力するように設定してあげないといけない。

activate :asset_hash は最後に middleman で source -> build 出力時にファイル名にいい具合に hash を付与しつつ HTML からの呼び出しにもそのファイル名を展開する機能を有効にするもの。要するに cache buster です。

Parcelの準備

  • parcel は zero config のままで ok
  • ただし build 時には HTML からではなく個々の asset の entry point を指定する形(画像は全部でよいと思う)
  • --out-dir をさっきの source と一致させる

コマンドライン引数を全部書くと、

yarn parcel --out-dir .tmp/dist \
            assets/images/**/* \
            assets/javascripts/*.js \
            assets/stylesheets/*.scss

みたいな感じ。

glob の **/* がどの環境でも使えるのかは未確認。とりあえず手元の zsh では動いている。こうしておくと JavaScript の component や Sass の partial などは import や @import からしか読み込まれないで済むようになる。

HTML から build させると parcel が自前でファイル名に hash を埋め込んでしまうので middleman から扱いにくくなるのと、そもそも middleman が処理する前の html.erb を parcel が処理できないため、あくまで個々の JavaScript や Sass の依存性解決とトランスパイルに利用するようにしてある。

※ あと、Middleman 標準だと *.css.scss が Sass ファイルの拡張子になるんだけど、Parcel では *.scss が拡張子になるなどの細かい違いはある。他にもありそう。

External Pipelineの考え方自体はすでに自分も昔からやってたものだと気づいた

分かる人にしか分からないが、この External Pipeline の考え方は Rails の Asset Pipeline 環境で precompile 設定を使うのに似ている。

例えば何かで build 済みの外部のライブラリを app/assets/ 以下に置く場合や、自分で Asset Pipeline の外に Babel などの環境を作っておいてその成果物を app/assets/ 以下に生成し、precompile で指定しておいて Asset Pipeline の世界に取り込む、みたいな感じ。

あくまで最終的な成果物を build して serve するのは Middleman であり Rails だが、その前の source は場所さえ分かっていれば出自は知らなくてよい、という扱い方。なんだ、External Pipeline はかつての自分が考えたことをもっと賢くしただけ*3だった。

できたリポジトリ

wtnabe/middleman-v4-example-with-ep-and-parcel

に置いた。

package.json に sass が追加されているけど、これは parcel の autoinstall で入ったもので、Sass 使わないなら不要。とは言え Web サイトをこさえる際に JavaScript だけ書ければあとは不要ということはまぁあり得ないので、動作確認を兼ねて入れてある。

参考

*1 理由は Decoupled すべきだと思うから

*2 その時の日記はなかった。Kanazawa.rb でライブで喋っただけでスライドもない。その後 production では Nuxt の方に突っ込むことになる。

*3 そこがポイントなんだけど


2018-11-24 [長年日記]

_ Middleman v4のExternal Pipelineてpreprocess全般に使えるのでは?

ちょっと試しに 4.2.1 でやってみたんだけど、

activate :external_pipeline, {
  name:    :dumb_command,
  command: 'echo "dumb command ..."',
  source:  ''
}

みたいな設定が可能ということが分かった。

つまり、

  • External Pipeline は名前をつけて外部コマンドを呼ぶだけなので、build 前になんでもさせられる
  • ただし、source 指定は必須なので空文字を入れておけば ok
    • nil はダメ

ということか。

外部の API からデータを取得するとかもこれでイケるんだな。Data Files を保存する何かを作ってしまえばなんでもできる。Headless CMS との連携とか。無理に内部構造を理解して Extension を作る必要もない。*1

これ、いいんじゃないか?

順番はどうなる?

複数の External Pipeline を指定した場合、

  • 実行する順番は activate した順番
  • ただし build 時以外は default ではスレッドで動くので external pipeline の 実行結果を得る順番は保証できない
    • disable_background_execution: true 指定を加えると制御可能

ということは

  1. 順番に依存する処理を watch 時に行わないのが基本
    • どうしてもという場合は disable_background_execution: true 指定を追加
    • その場合は hot reload / live reload 系はたぶん無理
  2. 順番に依存する処理はそれを起動する sh script などの方で制御する

といった工夫が必要になる。

ちなみにコードで言うとこの辺

class Middleman::Extensions::ExternalPipeline < ::Middleman::Extension
  self.supports_multiple_instances = true

  option :name, nil, 'The name of the pipeline', required: true
  option :command, nil, 'The command to initialize', required: true
  option :source, nil, 'Path to merge into sitemap', required: true
  option :latency, 0.25, 'Latency between refreshes of source'
  option :disable_background_execution, false, "Don't run the command in a separate background thread"
..
    if app.build? || options[:disable_background_execution]
      watch_command!(false)
..
  def watch_command!(async)
    @current_thread = ::Servolux::Child.new(
      command: options[:command],
      suspend: 2
    )

    @current_thread.start

    watch_thread = Thread.new do

*1 もちろん CMS 提供側としては gem で配布できることはアピールポイントになるのでちゃんと Extension 作った方がいいけど。