なぜログの出力形式をJSONにするのか

ログを出力形式をどうするか考えたとき、基本的にはJSONを推すことにしている。 その理由を整理してみた。

1. ツールを使ってパースしやすい

TableauやSplunkなどのBIツールを使って解析するのにJSON形式は都合が良いと聞いた。 残念ながら自分はBIツールを使って解析するようなことをしないのでこの恩恵がどの程度のものなのかは分からない。

2. 構造の規則性が視覚で分かるので理解しやすい

1行の文章でつらつらとログメッセージが出てきたときに、ぱっと見て分かった!と自分はならない自信がある。 継続してログを見続けるうちにメッセージの単語の位置を覚えて解析も早くなるみたいな熟練は求めてないので、誰もが分かりやすい形を目指したい。するとキーを見て値が想像しやすいJSONはベストかなと思った。 整理されたフォーマットは他の作業者にも共有しやすいと思う。

3. ログを再利用しやすい

JSONなのでそのままAPIで送受信できる。 ログをパースして必要な情報を取り出し、後続の処理に渡すことも可能。

上記の理由で、自分がログの出力形式を決定できるならJSONを推します。

pytestメモ

Udemyでお世話になっている酒井さんの講座の学習メモ。

www.udemy.com

テストしたい関数がある。

class Cal(object):
    def add_num_and_double(self, x, y):
        if type(x) is not int or type(y) is not int:
            raise ValueError
        result = x + y
        result *= 2
        return result

関数をテストするpytestファイルを作成する。

# pytestのファイル名は「test_」から始める
# 別ファイルの関数にアクセスするのでimportする
import calculation
import pytest

class TestCal(object):
    # テストを開始するときだけ実行
    @classmethod
    def setup_class(cls):
        print('start')
        cls.cal = calculation.Cal()

    # テストを終了するときだけ実行
    @classmethod
    def teardown_class(cls):
        print('end')
        del cls._cal

    # テスト関数を実行する度に実行
    def setup_method(self, method):
        print('method={}'.format(method.__name__))

    # テスト関数が終了する度に実行
    def teardown_method(self, method):
        print('method={}'.format(method.__name__))

    # テスト関数
    # 4が返るか確認
    def test_add_num_and_double(self):
        assert self.cal.add_num_and_double(1, 1) == 4

    # 例外テスト関数
    # 文字列を渡してValueErrorが返るか確認(返らなければエラー)
    def test_add_num_and_double_raise(self):
        with pytest.raises(ValueError):
            self.cal.add_num_and_double('1', '1')

    # スキップ
    @pytest.mark.skip(reason='Skip!')
    def test_add_num_and_double(self):
        assert self.cal.add_num_and_double(1, 1) == 4

    # 条件付きスキップ
    is_release = True
    @pytest.mark.skipif(is_release==True, reason='Skip!')
    def test_add_num_and_double(self):
        assert self.cal.add_num_and_double(1, 1) == 4

django メモ

下書きの肥やしになっていたdjangoの試した時のメモ。

環境

software version
django 2.2.1

アプリケーションファイル

  • views.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")
  • urls.py
from django.urls import path

from . import views

urlpatterns = [

urlpatterns = [
    path('',    # 設定した文字列がURLに表示される
         views.index,    
         name='index'),    # projectフォルダのurls.pyで指定する名前
]
]

モデルフィールド

リレーション先の特定のフィールドの値を取得する設定に苦戦した。

参考:

medium.com

stackoverflow.com

stackoverflow.com

k-mawa.hateblo.jp

docs.djangoproject.com

qiita.com

Django2.0から必須になったon_deleteの使い方 - Djangoの学習ができるチュートリアルサイトDjangoBrothers

トークン認証

simpleisbetterthancomplex.com

エラー対応

AttributeError

runserver実行時にエラーが発生した。

ubuntu@ip-10-0-1-180:~/django_rest_framework_test$ python3 manage.py runserver 0:8000
Watching for file changes with StatReloader
Performing system checks...

Exception in thread django-main-thread:
Traceback (most recent call last):
  File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/utils/autoreload.py", line 54, in wrapper
    fn(*args, **kwargs)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/management/commands/runserver.py", line 117, in inner_run
    self.check(display_num_errors=True)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/management/base.py", line 390, in check
    include_deployment_checks=include_deployment_checks,
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/management/base.py", line 377, in _run_checks
    return checks.run_checks(**kwargs)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/checks/registry.py", line 72, in run_checks
    new_errors = check(app_configs=app_configs)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/checks/urls.py", line 13, in check_url_config
    return check_resolver(resolver)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/checks/urls.py", line 23, in check_resolver
    return check_method()
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/urls/resolvers.py", line 398, in check
    for pattern in self.url_patterns:
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/utils/functional.py", line 80, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/urls/resolvers.py", line 579, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/utils/functional.py", line 80, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/urls/resolvers.py", line 572, in urlconf_module
    return import_module(self.urlconf_name)
  File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/home/ubuntu/django_rest_framework_test/django_rest_framework_test/urls.py", line 20, in <module>
    from petstore.urls import router as petstore_router
  File "/home/ubuntu/django_rest_framework_test/petstore/urls.py", line 4, in <module>
    from .views import UserViewSet, PetViewSet
  File "/home/ubuntu/django_rest_framework_test/petstore/views.py", line 10, in <module>
    class UserViewSet(viewsets.ModelViewSet):
  File "/home/ubuntu/django_rest_framework_test/petstore/views.py", line 11, in UserViewSet
    queryset = User.Objects.all()
AttributeError: type object 'User' has no attribute 'Objects'

解決に必要な情報が末尾3行にある。

  File "/home/ubuntu/django_rest_framework_test/petstore/views.py", line 11, in UserViewSet
    queryset = User.Objects.all()
AttributeError: type object 'User' has no attribute 'Objects'

view.pyqueryset = User.Objects.all()を行ったが、オブジェクトUserObjectsは無いよ、という意味。 Objectsobjectsのタイポだったため修正して解決した。

You are trying to add a non-nullable field

複数のモデル間でリレーションを設定するため、models.py に新しいフィールドを追加。
migrate を実行するとYou are trying to add a non-nullable fieldのエラーを出力した。

ubuntu@ip-10-0-1-180:~/django_rest_framework_test$ python3 manage.py makemigrations
No changes detected
ubuntu@ip-10-0-1-180:~/django_rest_framework_test$ python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, petstore, sessions
Running migrations:
  No migrations to apply.
ubuntu@ip-10-0-1-180:~/django_rest_framework_test$ python3 manage.py makemigrations
You are trying to add a non-nullable field 'category' to pet without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option:
Please select a valid option:

新しいフィールドの追加が既に登録したデータにも行われるがフィールドが空を許可していないため、何か値を入れるためのアクションしてね~という内容。

対応の一つとして、models に null=Trueを設定し空の場合はnullが入るようにすることで回避できる。

class Pet(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True)

bad_value, referenced_table_name, referenced_column_name

リレーションを設定した項目が親のidの値を保持していたので、別のフィールド値を持たせたいと思い設定を変更した。 migrate 時にエラーが出力された。

ubuntu@ip-10-0-1-180:~/django_rest_framework_test$ python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, petstore, sessions
Running migrations:
  Applying petstore.0004_auto_20190514_1848...Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/management/__init__.py", line 375, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/management/base.py", line 323, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/management/base.py", line 364, in execute
    output = self.handle(*args, **options)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/management/base.py", line 83, in wrapped
    res = handle_func(*args, **kwargs)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/core/management/commands/migrate.py", line 234, in handle
    fake_initial=fake_initial,
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/db/migrations/executor.py", line 117, in migrate
    state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/db/migrations/executor.py", line 147, in _migrate_all_forwards
    state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/db/migrations/executor.py", line 247, in apply_migration
    migration_recorded = True
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/db/backends/sqlite3/schema.py", line 34, in __exit__
    self.connection.check_constraints()
  File "/home/ubuntu/.local/lib/python3.6/site-packages/django/db/backends/sqlite3/base.py", line 318, in check_constraints
    bad_value, referenced_table_name, referenced_column_name
django.db.utils.IntegrityError: The row in table 'petstore_pet' with primary key '1' has an invalid foreign key: petstore_pet.category contains a value '2' that does not have a corresponding value in petstore_category.name.
ubuntu@ip-10-0-1-180:~/django_rest_framework_test$

フィールドが対応しない形の値(変更前の値)を保持しているため、新しく保持しようとした値が入れられない。 エラーとなる値を削除することで回避可能。

シェルスクリプトをSSMのドキュメントに登録して実行する

目的

Lambdaを使ってEC2上でコマンド実行したい

実現案

Lambda --> SSM --> EC2

SSMが実行するアクションを「ドキュメント」として定義できる。
ドキュメントにはいくつかのタイプがあり、コマンドを使用できるRun Commandを使えば実現できそう。

やってみた

SSMのメニューでドキュメントを選択後、Create command or sessionを選択。

f:id:dafukui:20200129233740p:plain

項目
名前 任意の名前
ターゲットタイプ /AWS::EC2::Instance
ドキュメントタイプ コマンドドキュメント
コンテンツ YAML

内容は以下で設定。

---
schemaVersion: "2.2"
description: "Command Document Example YAML Template"
mainSteps:
- action: "aws:runShellScript"
  name: "hostname"
  inputs:
    runCommand:
    - "hostname"
    - "ls -l /etc"

hostnamelsを試す内容です。
以上の内容で作成する。

自己所有で作成したドキュメントが確認できました。

f:id:dafukui:20200129234447p:plain

作成したドキュメントを実行すると以下の結果が得られました。

ip-10-0-1-188

total 1508

-rw-r--r-- 1 root root 5090 Feb 25 2016 DIR_COLORS

-rw-r--r-- 1 root root 5725 Feb 25 2016 DIR_COLORS.256color

-rw-r--r-- 1 root root 4669 Feb 25 2016 DIR_COLORS.lightbgcolor

-rw-r--r-- 1 root root 94 Aug 17 2017 GREP_COLORS

drwxr-xr-x 3 root root 4096 Nov 16 2018 NetworkManager

drwxr-xr-x 4 root root 4096 Nov 16 2018 X11

drwxr-xr-x 4 root root 4096 Nov 16 2018 acpi

-rw-r--r-- 1 root root 44 Jan 27 15:37 adjtime

-rw-r--r-- 1 root root 1512 Jan 12 2010 aliases

-rw-r----- 1 root smmsp 12288 Nov 16 2018 aliases.db

drwxr-xr-x 2 root root 12288 Jan 20 11:06 alternatives

drwxr-xr-x 3 root root 4096 Nov 16 2018 amazon

-rw------- 1 root root 541 Sep 28 2016 anacrontab

-rw-r--r-- 1 root root 148 Jan 6 2012 asound.conf

-rw-r--r-- 1 root root 1 Aug 16 2016 at.deny

drwxr-x--- 3 root root 4096 Nov 16 2018 audisp

drwxr-x--- 3 root root 4096 Nov 16 2018 audit

drwxr-xr-x 2 root root 4096 Jan 20 10:35 bash_completion.d

-rw-r--r-- 1 root root 2681 Sep 10 2014 bashrc

drwxr-xr-x 2 root root 4096 Jan 20 10:28 blkid

-rw-r--r-- 1 root root 905 Feb 28 2014 cgconfig.conf

-rw-r--r-- 1 root root 234 Feb 28 2014 cgrules.conf

-rw-r--r-- 1 root root 131 Feb 28 2014 cgsnapshot_blacklist.conf

drwxr-xr-x 2 root root 4096 Mar 17 2015 chkconfig.d

drwxr-xr-x 4 root root 4096 Nov 16 2018 cloud

drwxr-xr-x 2 root root 4096 Jan 20 10:48 containerd

drwxr-xr-x 2 root root 4096 Jan 27 13:52 cron.d

drwxr-xr-x 2 root root 4096 Nov 16 2018 cron.daily

-rw------- 1 root root 0 Sep 28 2016 cron.deny

drwxr-xr-x 2 root root 4096 Jan 6 2012 cron.hourly

drwxr-xr-x 2 root root 4096 Jan 6 2012 cron.monthly

drwxr-xr-x 2 root root 4096 Jan 6 2012 cron.weekly

-rw-r--r-- 1 root root 457 Jan 6 2012 crontab

-rw-r--r-- 1 root root 1602 Sep 10 2014 csh.cshrc

-rw-r--r-- 1 root root 794 Sep 10 2014 csh.login

drwxr-xr-x 4 root root 4096 Jan 20 10:27 dbus-1

drwxr-xr-x 2 root root 4096 Jan 20 10:27 default

drwxr-xr-x 2 root root 4096 Nov 16 2018 depmod.d

drwxr-x--- 4 root root 4096 Nov 16 2018 dhcp

drwxr-xr-x 2 root root 4096 Jan 20 10:48 docker

-rw-r--r-- 1 root root 519 May 6 2019 dracut.conf

drwxr-xr-x 2 root root 4096 Jan 20 10:27 dracut.conf.d

-rw-rw-r-- 1 root disk 0 Jan 5 2012 dumpdates

-rw-r--r-- 1 root root 119 Oct 1 2018 e2fsck.conf

---Output truncated---

hostnameコマンドとlsコマンドの結果が出てますね。

S3に出力するオプションを有効にすると、出力結果のファイルが作成されていました。

f:id:dafukui:20200129234929p:plain

Python の if not と or を組み合わせた時は条件に注意

Pythonで以下のようなif not 条件1 or 条件2を実行する。

def sleep_in(weekday, vacation):
    if not weekday or vacation:
        return True
    else:
        return False

すると以下のような結果となる。

weekday vacation 実行結果
True True True
True False False
False True True
False False True

ここでvacationnotの判定になっていないことに疑問を感じた人向けの内容となるが、 これはnotweekdayのみに有効になっているためである。
判定を分解すると以下のようになる。

  • not 条件1
  • 条件2

もし条件2notで判定させたければ、if not 条件1 or not 条件2というように都度notを付けるようにする。

Elasticache比較

Elasticacheとは

インメモリデータベースのサービス。
インメモリデータベースはデータをメモリ上で持ち、ディスクにアクセスする必要性を除くことによって、最小限の応答時間を達成するように設計されている。
すべてのデータはメインメモリにのみ保存および管理されているので、処理やサーバー障害によって失われてしまうリスクがあるが、すべてのオペレーションをログに保存したりスナップショットを取得したりすることで、データを存続することができる。

ElasticacheではKey-Value型のオープンソースのNoSQLデータベースであるRedisと、Key-Value型のオープンソースのキャッシュシステムであるmemcachedを利用出来る。

Redisの特徴

  • リモートディクショナリサーバーの略
  • 高速に値をRead/WriteできるNoSQL
  • データベース、キャッシュ、メッセージブローカー、およびキューとして使用
  • 全てのデータ操作は排他的
  • メモリ内Key-Valueデータストア (データアクセスが高速)
  • メモリを使い果たしたら特定のルールに従って削除する
  • それでもメモリを確保できないときは全ての書込みをエラーにする
  • MASTER-SLAVEのレプリケーション構築が可能 (SLAVEは複数設定可能)
  • レプリケーションは非同期
  • 複数の操作を1回で実行できる
  • MULTIでトランザクション開始
  • 全て実行(EXEC)or全て未実行(DISCARD)
  • 幅広いユースケースに対して効果的
  • keyに対して有効期限を設定できる(SessionID, OneTimeToken)
  • ディスクI/Oを無効化できる
  • イベント駆動アーキテクチャ
  • シングルスレッドで動作 (複数の処理を並列で行えない)
  • CPUの1コアのみを使用 (複数コアを利用する場合は複数台のRedisを立ち上げ推奨)
  • 5つのデータ型が使用可能
    • 文字列型
    • リスト型
    • セット型
    • ソート済みセット型
    • ハッシュ型
  • プライマリ/レプリカアーキテクチャ

ユースケース

  • キャッシュ
  • チャット、メッセージング、キュー
  • ゲームのリーダーボード
  • セッションストア
  • リッチメディアストリーミング
  • 地理空間
  • Machine Learning
  • リアルタイム分析

Memcachedの特徴

  • シンプル
  • キャッシュやセッションストアとして役立ちます
  • 分散型
  • 新しいノードを追加することにより簡単にスケールアウトできます
  • マルチスレッド
  • 特定のノードにおいて複数のコアを利用可能
  • ディスクI/Oが発生しない

ユースケース

  • キャッシュ
  • 永続性が重要ではないセッションストア
  • ウェブ
  • モバイルアプリケーション
  • ゲーム
  • アドテック
  • eコマース

Redis 対 Memcached

どちらも持つ特徴

Redisのみ

  • データ構造のサポート
    • String に加えて、List、Set、Sorted Set、Hash、Bit Array、HyperLogLog をサポートしています。アプリケーションは、これらの柔軟なデータ構造を使用して、さまざまなユースケースをサポートできます。たとえば、Redis の Sorted Set を使用すると、ランク別にソートされたプレイヤーのリストを維持したゲームの順位表を簡単に実装できます。
  • スナップショット
    • アーカイブまたはリカバリーに使用できる特定の時点のスナップショットを使用してデータをディスクに保存できます。
  • レプリケーション
    • Redis プライマリの複数のレプリカを作成できます。これにより、データベースの読み取りを拡張し、可用性の高いクラスターを設定できます。
  • トランザクション
    • 独立したアトミック操作として一連のコマンドを実行できるトランザクションをサポートしています。
  • Pub/Sub
    • 高性能なチャットルーム、リアルタイムのコメントストリーム、ソーシャルメディアフィード、サーバー間通信に使用できるパターンマッチングを備えた Pub/Sub メッセージングをサポートしています。
  • Lua スクリプト
  • 地理空間のサポート
    • 大規模なリアルタイムの地理空間データを処理するための専用コマンドがあります。2 つの要素 (人や場所など) 間の距離を見つけたり、ある点から所定の距離内にあるすべての要素を見つけるなどの操作を実行できます。

Memcachedのみ

  • マルチスレッドであるため複数の処理コアを使用できる

ask-sdk V2 for node.js 開発メモ

ask-sdk も node.js も分からない状態からスキル開発を行いました。その時のメモです。
誰かの役に立つかもしれないのでまとめ方雑ですが載せます。

node.js

strict mode

コードの先頭に"use strict";と記述があるかもしれません。 これは通常よりも厳しくコードのチェックを行う宣言で、エラーではないけど落とし穴になりそうな内容をエラーとして扱います。

具体的には以下のように扱いが変わります。

  • 従来は受け入れていた一部のミスをエラーへ変換
    • 偶発的にグローバル変数を作成できないようにします
    • 代入文で暗黙的に失敗せずに例外が発生するようにします
    • 削除できないプロパティを削除しようとするとエラーが発生します
  • 変数の使用の単純化
    • with を禁止します
    • eval は新しい変数を周囲のスコープに広めません
    • 単純名の削除を禁止します
  • eval および arguments の単純化
    • eval および arguments という名前に対して言語構文でのバインドや代入を不可にします
    • 内部で作成された arguments オブジェクトのプロパティがエイリアスになりません
    • rguments.callee をサポートしません
  • JavaScript の "セキュア化"
    • this として関数に渡された値をオブジェクトへボクシングしません
    • ECMAScript の一般的な実装である拡張を通して JavaScript のスタックを "渡り歩く" ことができません
    • arguments は対応する関数の呼び出し時の変数にアクセスできません
  • 将来の ECMAScript への準備
    • 識別子 implements、interface、let、package、private、protected、public、static、yield を予約語にします
    • スクリプトのトップレベルまたは関数内にない function 文を禁止します

スキルに対してもこの機能を有効にするかどうかの判断が必要になります。

参考 - https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Strict_mode - https://www.sejuku.net/blog/58342

var

変数の宣言。関数のスコープで定義する。 最近(ES6)では殆ど使ってないっぽい。 基本は後述するletconstを使う。

let

変数の宣言。ブロックのスコープで定義する。 代入のし直しが可能。 再宣言ができない。エラーとなる。

const

変数の宣言。ブロックのスコープで定義する。 再代入による変更はできず、再宣言もできない。

startsWith

文字列が特定の文字列で始まるかどうかを判断する。 (英文字の)大文字・小文字を区別する。

parseInt

文字列を整数に変換する。

parseInt()関数は第1引数を文字列に変換し、解析したうえで、整数またはNaNを返します。戻り値は、NaNでなければ、第1引数のstringを第2引数radixの基数によって示す10進数の整数です。たとえば、radixが10なら10進数、8は8進数、16であれば16進数による変換を意味します。10以上の基数については、9より大きい数字はアルファベットで示されます。たとえば、16進数(基数16)ではAからFが用いられます。

indexOf

indexOf() メソッドは、呼び出す String オブジェクト中で、 fromIndex から検索を始め、指定された値が最初に現れたインデックスを返します。値が見つからない場合は -1 を返します。

例)

var paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';

var searchTerm = 'dog';
var indexOfFirst = paragraph.indexOf(searchTerm);

console.log('The index of the first "' + searchTerm + '" from the beginning is ' + indexOfFirst);
// expected output: "The index of the first "dog" from the beginning is 40"

console.log('The index of the 2nd "' + searchTerm + '" is ' + paragraph.indexOf(searchTerm, (indexOfFirst + 1)));
// expected output: "The index of the 2nd "dog" is 52"

IsMatch

値がMapオブジェクトとして分類されているかどうかを確認します。 https://www.npmjs.com/package/lodash.ismatch

Array.prototype.join()

join() メソッドは、配列 (または配列風オブジェクト) の全要素を順に連結した文字列を新たに作成して返します。区切り文字はカンマ、または指定された文字列です。

var elements = ['Fire', 'Wind', 'Rain'];

console.log(elements.join());
// expected output: "Fire,Wind,Rain"

console.log(elements.join(''));
// expected output: "FireWindRain"

console.log(elements.join('-'));
// expected output: "Fire-Wind-Rain"

... (ドット3つ)

ドットが3つ連続しているものをスプレッド構文と呼ぶ。 順番に値が取り出されて、個数分の要素が該当部分に入るような配列を作成できる。

var ary = [0, "A", false];
var str = "あいう";
var connectedAry = [...ary, ...str];
console.log(connectedAry);
/*
  [0, "A", false, "あ", "い", "う"]
*/

サンプル

audioData = [
  {
    title: 'Episode 139',
    url: 'test1.url',
  },
  {
    title: 'Episode 140',
    url: 'test2.url',
  },
];

const ary1 = audioData;
console.log(ary1);
// output
// [ { title: 'Episode 139', url: 'test1.url' },
//   { title: 'Episode 140', url: 'test2.url' } ]

const ary2 = [...audioData];
console.log(ary2);
// output
// [ { title: 'Episode 139', url: 'test1.url' },
//   { title: 'Episode 140', url: 'test2.url' } ]

console.log(audioData.keys());
// output
// Object [Array Iterator] {}

const ary3 = [...audioData.keys()];
console.log(ary3);
// output
// [ 0, 1 ]

Array.from()

配列型 (array-like) や反復型 (iterable) オブジェクトから、新しい Array インスタンスを生成します。

const str = "あいう";
const ary = Array.from(str);
console.log(ary);
// output
// [ 'あ', 'い', 'う' ]

サンプル

const aryfrom1 = [...Array("A","B","C")];
console.log(aryfrom1);
// output
// [ 'A', 'B', 'C' ]

const aryfrom2 = [...Array("A","B","C")].length;
console.log(aryfrom2);
// output
// 3

const aryfrom3 = [...Array(3)]
console.log(aryfrom3);
// output
// [ null, null, null ]

const aryfrom4 = [...Array("A","B","C")].length;
const aryfrom5 = [...Array(aryfrom4).keys()];
console.log(aryfrom5);
// output
// [ 0, 1, 2 ]

async

Node.jsのasyncは非同期処理を可読性の高いコードで実装できます。

非同期処理とはページが更新された際などに、更新前と更新後を比較して足りない部分だけをデータ通信する処理のことです。 この非同期処理の事をフロントエンド側の処理ではAjaxと呼ぶ事もあります。

スキルの場合はasyncを handler に設定して、awaitを handler 内の DynamoDB 等と通信する部分に設定すると非同期処理ができる。

== (等価演算子)

== は、文字列と数値の比較の場合、文字列を数値に変換してくれる。

数値と文字列を比較するとき、文字列は数値に変換されます。JavaScript は文字列の数値リテラルを Number 型の数値に変換しようと試みます。最初に、その文字列の数値リテラルから数学的な値を引き出します。次に、最も近い Number 型の値にこの値を丸めます。

=== (厳密等価演算子)

オペランド同士が、型を変換することなく厳密に等しいならば真を返します。

Object.prototype.hasOwnProperty()

オブジェクトが指定されたプロパティを持っているかどうかを示す真偽値を返します。

下記の例では、propという名前のプロパティをオブジェクトが含むか否かを確認しています。

    o = new Object();
    o.prop = 'exists';
    function changeO() {
      o.newprop = o.prop;
      delete o.prop;
    }
    o.hasOwnProperty('prop');   // returns true
    changeO();
    o.hasOwnProperty('prop');   // returns false

Object.getOwnPropertyNames()

与えられたオブジェクトで発見されたすべてのプロパティ (列挙可能・不可能を問わず) の配列を返します。

const object1 = {
  a: 1,
  b: 2,
  c: 3
};

console.log(Object.getOwnPropertyNames(object1));
// expected output: Array ["a", "b", "c"]

Array.length

length プロパティは配列の要素数を取得します。これは符号なし32bitの整数で、常に配列内インデックスの最大値よりも大きな数値になっています。

サンプル

var clothing = ['shoes', 'shirts', 'socks', 'sweaters'];

console.log(clothing.length);
// expected output: 4
var clothing = [{PrefectureName: "北海道", Romanization: "hokkaido", PrefecturalOfficeLocation: "札幌", PrefectureFlower: "ハマナス", PrefectureOrder: 1 },
                {PrefectureName: "鹿児島県", Romanization: "kagoshima", PrefecturalOfficeLocation: "鹿児島", PrefectureFlower: "ミヤマキリシマ", PrefectureOrder: 46 },
                {PrefectureName: "沖縄県", Romanization: "okinawa", PrefecturalOfficeLocation: "那覇", PrefectureFlower: "デイゴ", PrefectureOrder: 47 }
                ];

console.log(clothing.length);
// expected output: 3

Array.keys()

keys() メソッドは、配列の各インデックスのキーを含む、新しい Array Iterator オブジェクトを返します。

var array1 = ['a', 'b', 'c'];
var iterator = array1.keys();

for (let key of iterator) {
  console.log(key);
}

// output
// > 0
// > 1
// > 2

Object.keys()

Object.keys() メソッドは、指定されたオブジェクトが持つ names プロパティの配列を、通常のループで取得するのと同じ順序で返します。

const object1 = {
  a: 'somestring',
  b: 42,
  c: false
};

console.log(Object.keys(object1));
// expected output: Array ["a", "b", "c"]

for...of

オブジェクトの一覧をループで出力。

for...of 文は、iterableオブジェクトに対して反復的な処理をするループを作成します

let iterable = [10, 20, 30];

for (let value of iterable) {
  value += 1;
  console.log(value);
}
// output
// 11
// 21
// 31


let hoge = [{foo:10}, {bar:20}, {baz:30}];

for (let value of hoge) {
  console.log(value);
}
// output
// [object Object]
// [object Object]
// [object Object]

for (let value of hoge.keys()) {
  console.log(value);
}
// output
// 0
// 1
// 2

オブジェクトとプロパティ

以下はmyCarというオブジェクトの生成です。

let myCar = new Object();

以下のようにしてプロパティを定義することが可能です。

myCar.make = 'Ford';
myCar.model = 'Mustang';
myCar.year = 1969;

console.log(myCar)
// output
// {
//   make: "Ford" ,
//   model: "Mustang" ,
//   year: 1969
// }

未定義のプロパティはundefinedになります。

console.log(myCar.color)
// output
// undefined

オブジェクトのプロパティの名前には、あらゆる有効な JavaScript 文字列(空文字列を含む)か、文字列に変換できるものが使用できます。
しかしながら、JavaScript 識別子として有効ではないプロパティ名(例えば空白やダッシュを含んでいたり、数字で始まったりするプロパティ名)には、ブラケット(角括弧)表記法でのみアクセスできます。
この表記法はプロパティ名を動的に決める場合(プロパティ名が実行時に決まる場合)に便利です。

モジュール

moment

日時を取得するために使用。 https://momentjs.com/

ask-sdk V2

canHandle

Alexa SDK V2の、リクエストハンドラーとエラーハンドラーのインターフェースのメソッド。
SDKによって呼び出され、指定されたハンドラーが受け取ったリクエストやエラーを処理できるかどうかを判断します。

ex) HelloWorldIntentHandlerの処理を、HelloWorldIntentを受け取った時に呼び出す記述。

const HelloWorldIntentHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest' &&
           request.intent.name === 'HelloWorldIntent';
  },
  handle(handlerInput) {
    const speechText = 'こんにちは';

    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('Hello World', speechText)
      .getResponse();
  }
};

Playbackディレクティブ

PlaybackStopped

「アレクサ」と声をかけ、アレクサが待ち受け状態になったときに送信される。

PlaybackController

PlaybackControllerリクエストに応答する場合、AudioPlayerディレクティブでしか応答できません。
応答には、outputSpeech、card、repromptなどの標準のプロパティはいずれも含めることができません。
サポートされていないプロパティを含む応答を送信すると、エラーが発生します。
(次へ前へのボタンを押した動作には発話させられずシンプルカードの表示もできない)

LaunchIntent

スキルの呼び出し時にパラメーターも付与していたときは、LaunchIntentの処理をスキップして後続のパラメーターを処理するHandlerを呼び出す。
LaunchIntentに必要な処理を定義していると、スキップ時は意図しない状態でHandlerを呼び出すこともあるので注意。

Launch時にshouldEndSessionフラグをtrueにするとError retrieving device rendering.が返される?
https://github.com/alexa/skill-sample-nodejs-audio-player/issues/144

カスタムインテント

標準ビルトインインテントと同じフレーズを登録可能。
標準ビルトインインテントと競合するフレーズを登録していた場合、優先して処理される。
標準ビルトインインテントの方が幅広い範囲を網羅できるため、競合させない方が良い。
AudioPlayerを実行した後は受付できない。

標準ビルトインインテント

カスタムインテントより処理の優先度が低い。 インテントによってフレーズの拡張ができるもの、できないものがある。

AudioPlayerインタフェースを有効にすると、ビルトインインテントインテントに登録していなくても 受け付ける。

インテントのサンプル発話に登録できない言葉

仕様で登録できない言葉がある。

  • 終了
  • その他終了に近しい表現

Dialogモデル

slot値を満たせなかったときの発話をskill側で定義して対応可能。 MP3は流せない。

addRequestInterceptors

前処理(RequestInterceptor)を追加します。
LaunchRequestHandlerのcanHandleメソッド ⇒ 前処理 ⇒ LaunchRequestHandlerのhandleメソッド の順で処理。
インテント処理も呼び出すたびに実行される。

addResponseInterceptors

後処理(ResponseInterceptor)を追加します。 handleメソッド ⇒ 後処理 ⇒ 返事。 エラーインテントに振り分けられた場合は実行されていない様子。 正常時はインテント処理を呼び出すたびに実行される。

SSML audioタグ

  • 5つ以上のaudioタグを埋め込んだlambda関数を実行するとスキルの起動に失敗する。
  • 音声再生が240秒を超える音声を再生するとスキルが正しく応答しないというエラーを報告してスキルが終了する。(オーディオタグの再生時に音声の長さを把握しているように見受けられる)
  • 音声再生中でもリプロンプトの応対可能。

SSML breakタグ

連続して複数使用できる?

スキルの待ち受け時間

  • アレクサはユーザからの返答(リプロンプト)を7~8秒待つ
  • その後、リプロンプト処理を行い再度7~8秒待つ
  • 応答がなければスキルを終了する
  • リプロンプト回数は変更できない

アレクサ(スキル) からユーザーへの返答時間

8秒。

プログレッシブ応答により、応答に使える総時間が変わるわけではありません。ユーザーがスキルを呼び出すと、スキルは約8秒以内に完全な応答を返す必要があります。スキルは完全な応答だけでなく、プログレッシブ応答の処理についてもこの時間内に終了する必要があります。 プログレッシブ応答の送信手順

https://developer.amazon.com/ja/docs/custom-skills/send-the-user-a-progressive-response.html

attributes の管理

async/await を使ったattributesの操作を行うときは処理の順序を意識しないと変数が空のままなので注意。

永続アトリビュートのキー

DynamoDB側ではAlexaデバイスにログインしているuserIdをプライマリパーティションキーとする。

ダイアログモード

Dialogインターフェースは、スキルとユーザーとの間のマルチターンの会話を管理するためのディレクティブを提供します。ユーザーのリクエストに応えるために必要な情報をユーザーにたずねるときに使用できます

AudioPlayerインタフェース

metadata の設定

  • addAudioPlayerPlayDirective は6つ目の引数に metadata パラメータを付与することで、画面付き端末にタイトル情報や画像を表示できる。
  • SDKのバージョンが古いと metadata を取得しない。(Lambda のfactテンプレートで設定するSDKは古いバージョンのため注意すること。自分はこれでハマった。)
  • 画像の表示はキャッシュを利用して行う。キャッシュはtokenの値に対応しており、同じtokenを使いまわすと意図しない画像を表示する可能性がある。

特定のインテントに該当しないインテントの処理

ErrorHandlerで処理する。

リクエストタイプ

PlaybackController.PlayCommandIssued

ユーザーが再生を開始また再開するためにインテントで「再生」または「再開」ボタンを使用した場合に送信されます。

System.ExceptionEncountered

PlaybackControllerリクエストに対する応答が原因でエラーが発生した場合、System.ExceptionEncounteredリクエストがスキルに送信されます。応答に含まれるディレクティブはすべて無視されます。

発話が一致しない場合にフォールバックを提供する

ユーザーの音声入力がスキルの他のインテントとまったく一致しない場合、AMAZON.FallbackIntent(英語を使用するロケールとドイツ語で利用可能)がトリガーされます。

https://developer.amazon.com/ja/docs/custom-skills/standard-built-in-intents.html#fallback

現状は日本では使用できないため、登録したサンプルと似ない発話もカスタムインテントで処理されることは仕様。

エラー

Unsupported Directive

AudioPlayer is currently an unsupported namespace. Check the device log for more information.

AlexaシミュレータでAudioPlayerによる音声再生をしようとすると発生。 AlexaシミュレータがAudioPlayerインタフェースの再生に対応していないため実機のechoを使う必要がある。

RequestHandlerChain not found!

条件に合致するHandlerが無いと発生する。 スキルのI/O入力とLambdaで定義しているHandlerの条件を確認し、意図しないパラメーターを保持していないか確認する。

Cannot read property 'trim' of undefined

returnで返す値に未定義のものがあると発生する。 repromptの定義忘れとかありませんか?

Task timed out after 3.00 seconds

Lambdaがタイムアウトしている。 Lambdaのタイムアウト時間を調整してあげよう。

Unable to find a suitable request handler.

不明。

DynamoDB

DynamoDB.DocumentClient

SDK

DynamoDBはテーブル作成時にプライマリキーとして以下の2つのキーを登録できる。

DynamoDBからGetを行うときはプライマリキーを指定する。 パーティションキーのみを定義した場合はパーティションキーのみの指定で良いが、 ソートキーも定義した場合は、パーティションキーおよびソートキーの指定が必要になる。