Google Apps Script (webapp) でログを記録する

Google Apps Script とログ

Google Apps Script (GAS) で、ログを書く時に長年 Logger.log を使っていた。 スクリプトを実行し、メニューの 表示ログ でログを表示できる。 ただし、このログは自分でスクリプトを実行した時のログが表示できるだけで、例えば webapp としてデプロイしている時のログを見ることはできなかった。

Stackdriver Logging の有効化

実行中のログを見るためには Stackdriver Logging というものを利用する。

メニューの 表示Stackdriver Logging をクリックする。

f:id:mallowlabs:20200507195455p:plain

紐付けができていない場合には以下のダイアログが表示されるので、 Google Cloud Platform でプロジェクトを作ってあげる必要がある。

f:id:mallowlabs:20200507195516p:plain

https://console.cloud.google.com/ にアクセスし、左上のプロジェクト名をクリックし、新しいプロジェクトをクリックする。

f:id:mallowlabs:20200507195537p:plain

適当にプロジェクト名を入力して、作成をクリックする。

f:id:mallowlabs:20200507195548p:plain

するとプロジェクトが作成されるので、プロジェクト番号 をメモする。

f:id:mallowlabs:20200507195602p:plain

GAS のエディタに戻り、メニューから リソースGoogle Platform プロジェクト をクリックする。

f:id:mallowlabs:20200507195616p:plain

プロジェクトを紐付けるダイアログが表示されるので、プロジェクト番号を入力して プロジェクトを設定 をクリックする。 OAuth 同意画面を設定する必要があると表示されるので、 こちら をクリックする。

f:id:mallowlabs:20200507195631p:plain

User Type は 外部 しか選択できないので、外部を選択し、作成 をクリックする。

f:id:mallowlabs:20200507195643p:plain

OAuth 同意画面の作成画面で アプリケーション名 だけ入れて 保存 をクリックする

f:id:mallowlabs:20200507195658p:plain

GAS の先程のダイアログに戻って、もう一度 プロジェクトを設定 をクリックすると、今度は紐付けできる。

f:id:mallowlabs:20200507195710p:plain

ログを出力する

ログを出力するときは Logger ではなく console を使う。

こんな感じのスクリプトを webapp として公開して、ブラウザでアクセスしてみる。

const doGet = (e) => {
  console.log(e);
  return ContentService.createTextOutput('ok');
}

ログを確認する

メニューの 表示Stackdriver Logging をクリックする。JSON がいい感じに展開されているのがわかる。

f:id:mallowlabs:20200507195724p:plain

console.log はログレベルが DEBUG になる。 console.warnconsole.infoconsole.error なんかが使える。

おまけ

clasp を使わずに GAS を作ると、 Google Apps Script のログに「展開」というボタンが表示されて、そこからログが見られるような気がする。

f:id:mallowlabs:20200507195738p:plain

clasp を使うとこれが表示されない。謎。 

参考 URL

Node-RED Google Assistant Bridge 使ってみた

はじめに

我が家では Node-RED と Node-RED Alexa Home Skill Bridge を使って、家の家電を操作している。 これの Google Home 版である Node-RED Google Assistant Bridge が、公開されたので設定してみた。

基本的には 更新ドキュメント の通りにしている。

Node-RED Google Assistant Bridge の設定

Node-RED Alexa Home Skill Bridge にアカウントを登録する。 その後、適当にデバイスを登録する。 ここでは「リビングの電気」というデバイスを登録している。

f:id:mallowlabs:20200321145432p:plain:w420

日本語使ったら「絵文字を使うのは関心しないよ」みたいなことを言われるけど無視する。

Google Group に参加する

Google Assistant で使っている Google アカウントで以下のグループに参加する。

https://groups.google.com/forum/#!forum/node-red-google-home-bridge

アカウントをリンクする

Node-RED Google Assistant Bridge に自分のアカウントを教えて上げる必要があるので、 Google Home アプリでアカウントを認証する。

アプリを開いたら左上の「+」ボタンをタップし、「デバイスのセットアップ」をタップする。

f:id:mallowlabs:20200321145543p:plain:w420

次に「セットアップ済みのデバイスのリンク」をタップする。

f:id:mallowlabs:20200321145604p:plain:w420

次に「NR-GAB」をタップする。 *[test]て書いてあるけど大丈夫か…?

f:id:mallowlabs:20200321145628p:plain:w420

Node-RED Alexa Home Skill Bridge の画面が開くので、 Node-RED Alexa Home Skill Bridge のアカウントで認証する。

f:id:mallowlabs:20200321145653p:plain:w420

トップページに「リビングの電気」が追加された。

f:id:mallowlabs:20200321145714p:plain:w420

Node-RED に node-red-google-home-bridge のインストール

まずはノードをインストールする。 我が家では Node-RED を IBM Cloud で動かしており、GUI からノードをインストールすることができないので、 package.json に追記する。

$ npm install node-red-contrib-googlehome

おまけ: IBM Cloud にデプロイする。

$ bx login
$ bx target -o 組織名 -s dev
$ bx cf push

Node-RED 側の設定

google home ノードを適当に置き、ダブルクリックする。

f:id:mallowlabs:20200321145801p:plain

「新規に google-home-conf を追加...」になっていることを確認し、鉛筆アイコンをクリックする。

f:id:mallowlabs:20200321145852p:plain:w420

Node-RED Google Assistant Bridge の ID とパスワードを渡すと、Device の一覧が取得できる。

f:id:mallowlabs:20200321150050p:plain:w420

適当にノードを配置する。

f:id:mallowlabs:20200321145815p:plain

google home ノードに接続している switch の中身は以下のような感じにした。

f:id:mallowlabs:20200321150212p:plain:w420

設定完了

この状態で「オッケーグーグル、リビングの電気をオフ」と言うと、リビングの電気が消えるようになった。 Alexa でも Google Home でも家電が操作できるようになってライフチェンジングになった!

Ruboty で Redash のデータを通知する ruboty-redash 作った

Ruboty で Redash のデータを通知したい

Redash で数値を可視化している環境の場合、その最新の値を Slack で通知したい。 Ruboty を使って bot を運用しているので、 Ruboty プラグインとして Redash の値を取ってくるものを作った。

ruboty-redash

github.com

使い方

Ruboty にプラグインを追加して、以下の2つの環境変数を追加する。

  • REDASH_ROOT … Ruboty に URL のルート (例: https://app.redash.io/)
  • REDASH_USER_APIKEY … Redash のユーザの API キー

あとはチャット上で以下のようなコマンドを実行する。

@ruboty redash show <クエリID> <ERB メッセージフォーマット>

<クエリ ID> は、Redash のクエリの画面を開いた時に URL に表示される ID のこと。

<ERB メッセージフォーマット> は、メッセージのフォーマットを ERB で指定する。 例えば

@ruboty redash show 11 昨日の生産性スコアは <%= data['rows'][-1]['productivity_pulse'] %> です。

のようになる。 この data のデータ構造の調べ方が少しややこしいので、以下で解説する。

data のデータ構造の調べ方

Redash で該当のクエリのページを Google Chrome を表示する。 Developer Tools の Network タブを開いて query_results でフィルタする。

f:id:mallowlabs:20200223144726p:plain

その返ってきた JSONPreviewquery_result 配下の data フィールドが data 変数に格納されるという仕組み。

query_result の値を見ていることからわかるように、最後の実行結果を取得するという仕組みになっている。 そのため、一度も実行されていないクエリは ruboty-redash で取得することはできない。 また、クエリの実行は Redash 側でスケジュール化しておいて常に実行されるようにしておかないと値は最新化されない。

まとめ

Ruboty で Redash の値を通知できるような仕組みを作った。 本文中の「生産性スコア」が気になった方は Redash で作る「じぶんダッシュボード」 というタイトルで発表した - mallowlabsの備忘録 の記事も参考にして欲しい。

Global Media Controls で Alexa SPA を操作する Chrome 拡張を作った

モチベーション

音楽を聴くときは Amazon Echo のマルチルームオーディオを使っている。 いろんな部屋で同じ曲が流れるのはいい経験だが、曲名を確認したり、曲をスキップしたいときに、声を出さないといけない。 そこで https://alexa.amazon.co.jp/spa/index.html#player (以下 Alexa SPA) を使うと、Web ブラウザから曲名を確認したり、曲をスキップできて便利なので、合わせて使っている。

最近 Chrome 79 で YouTube Music を聴いてたら、Chromeツールバーに音楽のツールバーが表示されるようになったことに気づいた。

f:id:mallowlabs:20200125174732p:plain

調べたら Global Media Controls という言うらしい。Chrome 79 時点では実験的な実装とされている。 これを Alexa SPA でも使いたいと思ったので、 Chrome 拡張で実装してみた

alexa-spa-global-media-controls

github.com

Chrome ストアには上げていないので、試してみたい場合はクローンして、Chrome に読み込んで欲しい。

拡張をインストールした状態で https://alexa.amazon.co.jp/spa/index.html#player にアクセスして、Alexa で音楽を流せばツールバーに以下のように表示される。

f:id:mallowlabs:20200125175129p:plain

実装の話

日本語のドキュメントが余りなかったのと、細かいところで結構ハマったので、ハマりどころをまとめる。 Chrome 79.0.3945.130 時点の話なので、今後改善する可能性は高い。

ツールバーに表示されない

基本的には MediaSession API を以下のように使うだけのはずである。

navigator.mediaSession.metadata = new MediaMetadata({
  title: '曲名',
  artist: 'アーティスト名',
  album: 'アルバム名',
  artwork: [
   { src:' アルバムアートの URL' }
  ]
});

しかし、これを呼び出してもツールバーにボタンが表示されない。 調べてみると実際のタブで video タグや audio タグで実際に音声が再生されている必要があるらしい。 しかも、 5秒以上の音声ファイルを再生している必要があるらしい。 Alexa SPA では実際の音は Amazon Echo から流れるため、タブで音楽を再生することはできない。 そこで、無音の ogg ファイルを用意して、 DOM の状態を見て、再生したり停止するように実装した。

アルバム名の表示位置が変

MediaSession API に素直に album 属性を渡すと、ドメインの横に表示される。 YouTube Music はどうしてるのか見てみたら artist 属性にアルバム名を含めるようにしていた。 なんか釈然としないが、似たような感じで実装した。

f:id:mallowlabs:20200125180157p:plain

navigator.mediaSession.metadata = new MediaMetadata({
  title: '曲名 - アルバム名',
  artist: 'アーティスト名',
  artwork: [
   { src:' アルバムアートの URL' }
  ]
});

アルバムアートが表示されない

アルバムアートの URL に画像の URL を渡しているにも関わらず、画像が表示されない。 どうやら sizes 属性を指定しなければならないらしい。

navigator.mediaSession.metadata = new MediaMetadata({
  title: '曲名 - アルバム名',
  artist: 'アーティスト名',
  artwork: [
   { sizes: '412x412', src:' アルバムアートの URL' }
  ]
});

artwork には type も指定できるが、こっちは不要らしい。

まとめ

Global Media Controls 個人的にはかなりアリなので、普及して欲しい。

参考 URL

Redash で作る「じぶんダッシュボード」 というタイトルで発表した

スライド

概要

Misoca 冬のLT大会 - connpass で私のダッシュボード事情について話してきた。

  • Heroku と Google Spreadsheets を使うことで運用することで、維持費無料でダッシュボードを作る方法
  • 運動量や睡眠、読書量や生産性とを可視化する方法

みたいな話を話せて楽しかった。

Misoca のコードレビューで教えてもらった RSpec マッチャまとめ

この記事は Misoca+弥生 Advent Calendar 2019 の7日目の記事です。

はじめに

Misoca に入社して1年とちょっとが経ちました。 Misoca は Ruby がメインの会社です。 私のキャリアはずっと Java だったので、特に RSpec の知識が貧弱で、RSpec は「expect って書くのは知ってる…」ぐらいの知識でした。 RSpec のマッチャは数も多く、いったいどこから勉強したらよいのか…というままテストコードを書いたため、コードレビューでよりよい書き方を教えてもらいつつやってきました。 そこで、同じ指摘をもらわないように自分用にまとめつつ、同じような境遇の人に少しでも役に立つように、Misoca のコードレビューで教えてもらった RSpec の書き方を紹介します。

注意事項として、以下で書き換え前と後のコードが出てきますが、どちらが良い悪いという意図はありません。 実際、指摘をもらっても書き換えることもあれば、メリットがあればそのままにすることもありました。

be_xxx

target が xxx? というメソッドを持っている場合、 be_xxx というマッチャが使えます。 例えば

expect(target.persisted?).to be_truthy

という example があったら

expect(target).to be_persisted

と書くことができます。

and

and マッチャをつなげることで expect(target) を何度も書かなくてすみます。 例えば

expect(target).to be_cool
expect(target).to be_beautiful

という example があったら

expect(target).to be_cool
  .and be_beautiful

と書くことできます。

have_attributes

have_attributes マッチャを使うことで複数の属性を持つことを一気にチェックできます。 例えば

expect(target.name).to eq('mallowlabs')
expect(target.company).to eq('Misoca Inc.')
expect(target.joined_at).to eq('2018-10-01')

という example があったら

expect(target).to have_attributes(
  name: 'mallowlabs',
  company: 'Misoca Inc.',
  joined_at: '2018-10-01'
)

と書くことができます。

all

all マッチャを使うことでコレクションの要素を一気にチェックできます。

targets.each do |target|
  expect(target).to be_ok
end

という example があったら

expect(targets).to all(be_ok)

と書くことできます。

satisfy

satisfy マッチャを使うことでブロックをマッチャに変換して all マッチャに渡すことができます。 例えば

targets.each do |target|
  expect(target).not_to be_cool
end

という example があったら

expect(targets).to all(satisfy { |t| !t.cool? })

と書くことができます。

contain_exactly

contain_exactly マッチャを使うことでコレクションの中に要素がすべて含まれているかを確認できます。 コレクションの長さを確認しつつ、要素が意図したものが入っているかのチェックする際に

array = ['mallowlabs']
expect(array.size).to eq(1)
expect(array.first).to eq('mallowlabs')

という example を書いていました。これは

array = ['mallowlabs']
expect(array).to contain_exactly('mallowlabs')

のように書けます。

他にも

いろんなマッチャを教えてもらいましたが、シンプルに説明できなかったので割愛します。

まとめ

洗練されていないコードを書いても dis られることなく、建設的な提案をしてくれる Misoca の開発メンバーのみんなに感謝です。

明日は id:ryotaway が「BrainF***処理系を作ってみます」という記事を買いてくれるそうです。楽しみですね。

RSpec の contain_exactly マッチャ と match_array マッチャ

contain_exactly マッチャ

`contain_exactly` matcher - Built in matchers - RSpec Expectations - RSpec - Relish

以下のようにコレクションに対して、順序は問わないがすべてが含まれているかをチェックすることができる。

expect([1, 2, 3]).to contain_exactly(2, 3, 1) # pass
expect([:a, :c, :b]).to contain_exactly(:a, :c) # fail

match_array マッチャ

ほぼ同じようなマッチャとして match_array というのもある。 これは引数に配列を取るので注意する。

expect([1, 2, 3]).to match_array [2, 3, 1] # pass
expect([:a, :c, :b]).to match_array [:a, :c]  # fail

ソースコードを読む

これらのマッチャの動作を理解するために、まずは contain_exactlyソースコードを読んでみた。

rspec-expectations/contain_exactly.rb at 99f9bcaff2a6f3d82f4e350e829eca6ab015694f · rspec/rspec-expectations · GitHub

def match(_expected, _actual)
  return false unless convert_actual_to_an_array
  match_when_sorted? || (extra_items.empty? && missing_items.empty?)
end

def match_when_sorted?
  values_match?(safe_sort(expected), safe_sort(actual))
end

引数を配列に変換して、ソートしたあとで、それぞれマッチするかをチェックしている。

一方 match_array は以下のような感じ。

https://github.com/rspec/rspec-expectations/blob/99f9bcaff2a6f3d82f4e350e829eca6ab015694f/lib/rspec/matchers.rb#L715

def match_array(items)
  contain_exactly(*items)
end

contain_exactly 呼んでた。 ということで contain_exactlymatch_array は引数に配列を取るかどうか以外は同じ挙動をすることがわかった。

まとめ

RSpec は奥が深い。