2021年12月27日月曜日
【Python】loggingでログをカスタマイズする【Flask】
狛ログをご覧のみなさん、はじめまして。
2021年にオフィス狛に入社いたしました、nago(なご)です。
技術に関するブログ記事を書くのは初めてですが、有益な情報を投稿できるように頑張ります。
よろしくおねがいします☺
さて、今回はPython(Flask)で、お試し開発をしていた時に苦戦したログ出力についてまとめたいと思います。
作ろうとしていたのは、外部のREST APIにリクエストを投げて、何かしらの結果を受け取り、画面に表示させる機能です。
まず、先輩社員が投稿してくださっていたこちらの記事を参考にして、AWS API GatewayでAPIのMockを作成しました。
正しい合言葉をidに設定してリクエストすると、ステータスコードが200となり、とってもタメになる情報を受け取れるAPIにしました。
合言葉を間違えていたらステータスコードが401となりエラーが返ってきます。
アプリケーションは以下のような構成で作成しました。
api-test/
├ static/
│ └ styles.css
├ templates/
│ └ api_test.html
├ views/
│ ├ __init__.py
│ └ api_test.py
├ __init__.py
├ app.py
├ get-pip.py
├ Pipfile
└ Pipfile.lock
コードはこちら。
・api_test.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>APIテスト</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body>
<form action="{{ url_for('api_test.call') }}" method="POST">
<input type="text" name="id">
<input type="submit" value="API CALL">
</form>
<input type="button" onclick="location.href='{{ url_for('api_test.show_api_test') }}'" value="RESET">
<p>{{ message }}</p>
</body>
・views/app.py
from flask import Flask, redirect, url_for
from views.api_test import api_test
app = Flask(__name__)
app.register_blueprint(api_test)
# 最初の画面へリダイレクト
@app.route("/")
def redirect_to_api_test():
return redirect(url_for("api_test.show_api_test"))
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
・api_test.py
import requests, json
from flask import Blueprint
from flask import render_template, request
api_test = Blueprint("api_test", __name__)
# 画面表示(初期 & リセット時)
@api_test.route("/")
def show_api_test():
message = ""
return render_template(
"api_test.html",
message=message
)
# APIリクエスト & レスポンス受け取り & 画面表示
@api_test.route("/call", methods=["POST"])
def call():
# APIGatewayでMock作成時に設定されたURL
url = "https://xxxx.amazonaws.com/api-test/my/info"
# クエリ文字列を作成
payload = { "id": request.form.get("id") }
# リクエストヘッダを作成
headers = { "Content-Type": "application/json" }
# リクエスト & レスポンス受け取り
response = requests.post(url, data=json.dumps(payload), headers=headers)
# レスポンスからメッセージを取り出す
message = response.json()["message"]
# 画面遷移 & メッセージ表示
return render_template(
"api_test.html",
message=message
)
これで、APIから返ってきたメッセージをそのまま画面に表示することができました。
次に、APIがコールされるたびに、そのリクエスト内容と結果のログを出力する仕組みを作ろうと思います。
Pythonでログを出力するには、Pythonで用意されているloggingというモジュールを使います。
今回は設定ファイルを既存のファイルとは別に作成して、ログの設定をしてみます。
私はプロジェクトルートに「config」という名前のディレクトリを作り、その中にログの設定ファイルを作りました。
また、ログファイルを出力したかったので、ログファイルを置くための「log」ディレクトリも作成しました。
以下が作成したディレクトリと設定ファイルです。
api-test/
├ config/
│ ├ __init__.py
│ └ logging_config.py
├ log/
├ static/
│ └ styles.css
├ templates/
│ └ api_test.html
├ views/
│ ├ __init__.py
│ └ api_test.py
├ __init__.py
├ app.py
├ get-pip.py
├ Pipfile
└ Pipfile.lock
※赤字が追加分・config/logging_config.py
from logging import DEBUG, INFO, config
import datetime
# ファイル名に入れる日付フォーマット
today = datetime.date.today().strftime("%Y-%m-%d");
config.dictConfig(
{
"version": 1,
# ログフォーマット設定
"formatters": {
"formatter": {
"format": "%(levelname)s %(asctime)s [%(module)s] %(message)s"
}
},
# ハンドラー設定
"handlers": {
# 標準出力ハンドラー
"streamHandler": {
"class": "logging.StreamHandler",
"formatter": "formatter",
"level": INFO
},
# ファイル出力ハンドラー
"fileHandler": {
"class": "logging.FileHandler",
"formatter": "formatter",
"level": INFO,
# ログファイルを出力したい場所のパスとファイル名を指定
"filename":f"xxxx/api-test/log/{today}_api-test.log",
"encoding": "utf-8"
}
},
# ロガー設定
"loggers": {
"basicLogger": {
"handlers": [
"streamHandler",
"fileHandler"
],
"level": INFO,
"propagate": 0
}
}
}
)
ログの設定は、logging.config.dictConfigに辞書(Dictionary型の設定値)を渡すか、
logging.config.fileConfigにconfigparser形式ファイルを渡すかで設定できますが、
私は今回はdictConfigで設定してみました。
ログを出力するには、まずLogger(ロガー)の取得が必要なのですが、そこにHandler(ハンドラー)を指定することで出力形式の指定をプラスできるようになっています。
また、出力フォーマットも設定したい場合は、ハンドラーに対してFormatter(フォーマッター)を指定します。
ロガー、ハンドラー、フォーマッターの名前は自由につけることが出来ます。
私はロガーは「basicLogger」を作り、そこに標準出力ハンドラーの「streamHandler」と、ファイル出力の「fileHandler」をくっつけました。フォーマッターは「formatter」にしました(そのまんま)
"varsion" : 1 ってなんぞやと思い調べてみたのですが、辞書スキーマのバージョンのことでした。
現在は1しかないので、1を入れておけばいいそうです。こちらはdictConfigに辞書を渡す場合は必須のため、入れないと怒られます。
さて、ようやくapi_test.pyで設定ファイルを読み込んで、ログ出力してみます。
・views/api_test.py
import requests, json
from logging import getLogger
from flask import Blueprint
from flask import render_template, request
from config import logging_config
api_test = Blueprint("api_test", __name__)
# ログ設定ファイル読込
logging_config
# ロガー取得
logger = getLogger("basicLogger")
# 画面初期表示
@api_test.route("/")
def show_api_test():
message = ""
return render_template(
"api_test.html",
message=message
)
# APIリクエスト & レスポンス受け取り & 画面表示
@api_test.route("/call", methods=["POST"])
def call():
# APIGatewayでMock作成時に設定されたURL
url = "https://xxxx.amazonaws.com/api-test/my/info"
# クエリ文字列を作成
payload = { "id": request.form.get("id") }
# リクエストヘッダを作成
headers = { "Content-Type": "application/json" }
# リクエストログ出力
logger.info("request : url = " + url + ", payload = " + str(payload))
# リクエスト & レスポンス受け取り
response = requests.post(url, data=json.dumps(payload), headers=headers)
# レスポンスからステータスコードとメッセージを取り出す
status_code = response.status_code
message = response.json()["message"]
# レスポンスログ出力
if status_code == 200:
logger.info("response : status_code = " + str(status_code) + ", message = " + message)
else:
logger.error("response : status_code = " + str(status_code) + ", message = " + message)
# 画面遷移 & メッセージ表示
return render_template(
"api_test.html",
message=message
)
※赤字は追加分・log/2021-12-15_api-test.log (2021/12/15にログが作成された場合のファイル名)
INFO 2021-12-15 14:23:53,701 [api_test] request : url = https://xxxx.amazonaws.com/api-test/my/info, payload = {'id': 'sushi'}
INFO 2021-12-15 14:23:53,778 [api_test] response : status_code = 200, message = お寿司はおいしい。猫はかわいい。ポメラニアンもかわいい。
INFO 2021-12-15 14:24:46,184 [api_test] request : url = https://xxxx.amazonaws.com/api-test/my/info, payload = {'id': ''}
ERROR 2021-12-15 14:24:46,254 [api_test] response : status_code = 401, message = 合言葉が違うようです・・・
正常パターンとエラーパターンを実行してみました。フォーマッターで設定した「%(levelname)s %(asctime)s [%(module)s] %(message)s」の通りに表示されています。
出力時にapi_test.pyで設定したメッセージが「%(message)s」に代入されている形です。
調べてみると、loggingは思ったより奥が深くびっくりしました。
覚えておくと出したいログを見やすく出すことができて便利だと思います。
今回はこれでおしまいです。
ここまでお読みいただきありがとうございました。
参考リファレンス:
・Logging HOWTO
・logging.config --- ロギングの環境設定
Angular・ngx-scannerでインカメラが使われるのを制御する方法。
こんにちは、オフィス狛 技術部のpinoです。
Angularで、バーコードやQRコード読み取りに使用するライブラリに、『ngx-scanner』があります。
テンプレートに<zxing-scanner></zxing-scanner>を埋め込むだけで、読み取り用のカメラを自動で起動してくれる便利なライブラリです。
当社のプロジェクトでも採用しているのですが、先日、通常なら背面のカメラが起動するところで、iOS12でのみインカメラが起動する事象が発生しました。
今回は、その時のトラブルシューティングとして、インカメラが使われるのを制御する方法について紹介します。
バージョン情報
- Angular:12.0.3
- ngx-scanner:3.0.1
なぜインカメラが起動する?
まず、インカメラが起動してしまう理由について見ていきます。
カメラが起動するまでの流れは、node_modules/@zxing/ngx-scanner/__ivy_ngcc__/fesm2015/zxing-ngx-scanner.jsで確認できます。
① ngAfterViewInit()
/**
* Executed after the view initialization.
*/
ngAfterViewInit() {
// makes torch availability information available to user
this.getCodeReader().isTorchAvailable.subscribe(x => this.torchCompatible.emit(x));
if (!this.autostart) {
console.warn('New feature \'autostart\' disabled, be careful. Permissions and devices recovery has to be run manually.');
// does the necessary configuration without autostarting
this.initAutostartOff();
return;
}
// configurates the component and starts the scanner
this.initAutostartOn();
}
this.autostartはtrue固定なのでif (!this.autostart)の分岐に入りません。
this.initAutostartOn()に続きます。
② initAutostartOn()
/**
* Initializes the component and starts the scanner.
* Permissions are asked to accomplish that.
*/
initAutostartOn() {
return __awaiter(this, void 0, void 0, function* () {
this.isAutostarting = true;
let hasPermission;
try {
// Asks for permission before enumerating devices so it can get all the device's info
hasPermission = yield this.askForPermission();
}
catch (e) {
console.error('Exception occurred while asking for permission:', e);
return;
}
// from this point, things gonna need permissions
if (hasPermission) {
const devices = yield this.updateVideoInputDevices();
this.autostartScanner([...devices]);
}
});
}
カメラアクセスの権限周りをチェックしています。
許可された状態だと、最終的にthis.autostartScanner([...devices])が実行されます。
③ autostartScanner()
/**
* Starts the scanner with the back camera otherwise take the last
* available device.
*/
autostartScanner(devices) {
const matcher = ({ label }) => /back|trás|rear|traseira|environment|ambiente/gi.test(label);
// select the rear camera by default, otherwise take the last camera.
const device = devices.find(matcher) || devices.pop();
if (!device) {
throw new Error('Impossible to autostart, no input devices available.');
}
this.device = device;
// @note when listening to this change, callback code will sometimes run before the previous line.
this.deviceChange.emit(device);
this.isAutostarting = false;
this.autostarted.next();
}
まず、カメラのlabelプロパティにback|trás|rear|traseira|environment|ambienteが含まれるかを見ていますね。
(調べてみたところ、ポルトガル語やスペイン語などで「後方」「環境」といった意味の単語が含まれているようです。)
devicesの中で上記に当てはまる最初のカメラか、当てはまるものがない場合はdevicesの最後尾のカメラが起動される仕組みになっています。
ここまでで、カメラが起動するまでの流れを追うことができました。
次は、iOS12と、比較用に背面カメラが起動するiOS15のカメラ情報を、デモページを使って確認してみます。
デモページでカメラアクセスを許可すると、使用できるカメラのリストが表示されます。
iOS12とiOS15で試した結果、以下のようになりました。
『〇〇カメラ』となっているのがlabelプロパティに当たり、並び順がそのままdevicesの要素の並びになっています。
どちらのOSも、labelにback|trás|rear|traseira|environment|ambienteは含まれていません。
その場合、配列の最後尾のカメラが起動されるわけですが、iOS15は背面カメラなのに対し、iOS12は前面側カメラが最後尾になっています。
iOS12でのみインカメラが起動するのは、ここに原因があったんですね。
デモのコードは、公式のGithubか下記からも確認できます。
ちなみに、iPhoneの設定から『iPhoneの使用言語』をEnglishに変更したところ、
-
前面カメラ ->
Front Camera -
背面カメラ ->
Back Camera
という風に文言が変わりました。
labelは使用言語に基づくようなので、使用言語を変更することで回避できる、ということになりますが、あまり現実的ではなさそうです...。
インカメラが使われるのを避けるには?
自動起動されるカメラを変更するのは、先ほど確認したような処理の流れ自体を変更しないといけないので、難しいです。
ただ、デモページで確認できるように、カメラの切り替えは可能になっています。
だとしたら、自動起動が終わった後に、背面カメラを起動するよう設定し直すのはどうでしょうか?
ということで、今度は「自動起動が完了したことを検知できるか」を確認してみます。
ngx-scannerでは、いくつかのイベントが用意されています。
今回使用しているVer.3.0.1では、以下のイベントが定義されていました。
-
autostarted -
autostarting -
torchCompatible -
scanSuccess -
scanFailure -
scanError -
scanComplete -
camerasFound -
camerasNotFound -
permissionResponse -
hasDevices -
deviceChange
名前からなんとなくどんなイベントか予想がつくかと思います。
例えば、「使用できるカメラが存在していない場合」と「読み取りに成功した場合」にイベントハンドラを設けたい時は、以下のようにします。
テンプレート側
<zxing-scanner
(camerasNotFound)="onCamerasNotFound()"
(scanSuccess)="onScanSuccess($event)">
</zxing-scanner>
ロジック側
onCamerasNotFound(): void {
console.log('cameras not found.')
}
onScanSuccess(result: string): void {
console.log('scan success:', result)
}
ここで、カメラが起動するまでの流れの最後の処理(autostartScanner())をもう一度見てみます。
/**
* Starts the scanner with the back camera otherwise take the last
* available device.
*/
autostartScanner(devices) {
const matcher = ({ label }) => /back|trás|rear|traseira|environment|ambiente/gi.test(label);
// select the rear camera by default, otherwise take the last camera.
const device = devices.find(matcher) || devices.pop();
if (!device) {
throw new Error('Impossible to autostart, no input devices available.');
}
this.device = device;
// @note when listening to this change, callback code will sometimes run before the previous line.
this.deviceChange.emit(device);
this.isAutostarting = false;
this.autostarted.next(); // <- ここ!
}
最後に、autostartedイベントが発火していますね。
これを、コンポーネント側で検知するようにしてあげればよさそうです。
テンプレート側
<zxing-scanner
[(device)]="currentDevice"
(camerasFound)="onCamerasFound($event)"
(autostarted)="onAutoStarted()">
</zxing-scanner>
ロジック側
availableDevices: MediaDeviceInfo[];
currentDevice: MediaDeviceInfo = null;
...
onCamerasFound(devices: MediaDeviceInfo[]): void {
// 使用できるカメラを取得
this.availableDevices = devices;
}
onAutoStarted(result: string): void {
const userAgent = navigator.userAgent.toLowerCase();
let iosVersionInfo = userAgent.match(/iphone os ([\d]+)_([\d]+)_([\d]+)/);
if (!iosVersionInfo) {
iosVersionInfo = userAgent.match(/iphone os ([\d]+)_([\d]+)/);
}
const iosVersion = iosVersionInfo?.[1];
if (iosVersion === '12') {
// iOS12の場合は、配列の最初のカメラを使用する
this.currentDevice = this.availableDevices?.[0];
}
}
実際に、iOS12の場合は背面カメラを起動するよう設定し直すサンプルにしてみました。
まず、camerasFoundで使用できるカメラの情報が渡ってきますので、変数に控えます。
テンプレート側にある[(device)]プロパティに使用したいカメラを設定してあげるとカメラが切り替えられるので、UserAgentからiOS12と判断できる場合は配列の最初のカメラを設定するようにしました。
iOS12ではない場合はライブラリ側で自動起動したカメラが設定されますので、ピンポイントな修正が可能です。
最新版(Ver.3.2.0)では、this.autostarted.next()はinitAutostartOn()内に処理が移っていました。
紹介した処理の流れは大きく変わっていないと思うので、お使いのバージョンと比較してみてください。
以上が、今回のトラブルシューティングの内容でした。
どなたかの参考になれば嬉しいです🦓
iOS15版のSafariから目立つようになったステータスバーの背景色を指定したい。
こんにちは、オフィス狛 デザイン部のSatoです。
iOS15版のSafariで以前私がHTMLなどを作成したWebシステムを確認したところ、ステータスバーの色が変わっていることに気づきました。
自動的に選ばれた色とヘッダーの色味が少し違う色でしたので、逆に浮いて見えて困っていました。
ステータスバーをWebシステムのメインカラーの色に合わせられたら違和感を感じないのにと考え、色を制御する方法を探していたところ、色を指定できるようなので忘れないよう備忘録も兼ねてブログに書いてみようと思います。
ステータスバーの背景色を指定する方法、それは「theme-color」というmetaタグを書くだけです。
headタグの中に下記を記述することで色を変えることができます。
<meta name="theme-color" content="任意の色">
任意の色の部分にカラーコードを指定するだけで、ステータスバーやアドレスバーの背景色が変わります。
<meta name="theme-color" content="#dcdcdc">をheadタグの中に記述しました。
「theme-color」を指定した場合も、明度の高いペールカラーのような色を指定した場合にはダークモードでは色が変わらないこともあるようですが、それはダークモードの仕様上仕方ないかと思います。
余談ですが、iOS15版のSafariから画面下部にアドレスバーが表示される関係だと思うのですが、要素が少ないページの背景色がアドレスバーの下には回り込まないで下に「theme-color」で指定した色が表示されてしまったりします。
上記のような状態になった場合は、全体を囲んでいるタグにheight: 100vh;を指定すると背景が下まで表示されるようになりました。(余白が空いてしまった例のCSS指定は height: 100%; にしていました)
簡単なことなのですが、少しつまづいてしまったので、似たような現象に悩まれた方は試してみてはいかがでしょうか?
2022/11/01日 height:100vhを設定するよりも綺麗に背景色設定する方法ができた為、新しくブログ記事を書きました!(この方法だと、余分なスクロールができません)
iOS15版のSafariはレイアウト含めさまざまな変更点があり、技術者はもちろん普通に使っているユーザーも覚えることが多くて大変ですね……。
今後もiOS版Safariの仕様変更には気をつけていきたいです。
2021年4月30日金曜日
【AWS】API Gatewayで超手軽にMockを作成する。
オフィス狛 技術部のyuckieeeです。
Androidアプリの開発中に動作確認をしようとしたとき、データ取得先のAPIが未完成だったり、特定のレスポンスやエラー(メンテナンス画面にするなど)を返すように制御するのが難しい...
など、もやもやすることがありました。
で、、それならシミュレータとかMockとか呼ばれてるものを自分で用意しちゃえばいいじゃん!?
と、思い立ち、AWSのAPI Gatewayが提供する機能を使用して作成してみました。
これが意外と使える子だったので、今回は一番シンプルに使える「統合Mock」というサービスの使い方を紹介します!
Step1:API GatewayにAPIを登録する
何はともあれ、APIの外枠を作らないことには始まりません。
AWSの管理コンソールからAPI Gateway画面を開き[API作成]をクリックして、APIを作成していきます!
最初に、作成するAPIタイプの選択画面が表示されますので"REST API"の[構築]を選択してください。
この値はアクセスURLに反映はされませんが、変更が出来ないようなので、ご注意ください。 これでAPIの新規作成は完了です。
Step2:リソースとメソッドを作成する
APIの作成が完了するとリソース管理画面に遷移します。この時点では、下図のとおり何も設定されていませんので、ここから設定を追加していきます。 作成開始前にイメージ共有しておいた方が分かりやすいと思うので、まずはMock化したいAPIの概要を決めておきます。
今回は以下のAPIレスポンスを代替する統合Mockを作ろうと思います。
GET / https://www.yuckieee.com/book/search
上記APIの下線部がAWS Gatewayが自動発行するパスで置き換わる部分です。
そのため、book以降のパス部分を作成対象として進めていきます。
まずは[アクション]メニューから[リソースの作成]を選択し、上記に沿ってリソースを定義します。
ここで"リソース"と言っているのがアクセスするURLのパスとなります。
リソースは、ディレクトリ 構造での管理となっており、最上位を
/(ルート)リソースと呼びます。このルートリソースに子リソースを追加していく形でリソースを作成していきます。
今回は
①
/(ルート)リソースにbook子リソースを追加②
bookリソースにsearch子リソースを追加の順でリソースを作成します。
リソースの作成後、レスポンス返却させたいリソース(=今回は
searchリソース)を選択し、[アクション]メニューから[メソッドの作成]を選択します。[メソッドの作成]クリック後、下図のように作成したいHTTPメソッドが選択可能になるため、今回は"GET"を選択し、右側の確定チェックボタンをクリックします。 確定チェッククリック後、メソッドのセットアップ画面が表示されます。
今回はAWSの統合Mock機能を使用するため、統合タイプから"Mock"を選択し[保存]をクリックします。
これでリソースとメソッドの作成は完了です。
Step3:マッピングテーブルを定義する
統合Mockが作成されると、下図のような定義画面が表示されます。それぞれ、私視点での概要を軽く説明すると下記のとおりです。
| タイプ | 説明 |
|---|---|
| メソッド リクエスト |
実際に受信したAPIリクエストに対して、入力値チェックやAPIキー指定などの設定が可能です。 |
| 統合 リクエスト |
統合Mockエンドポイントに引き渡す値の設定を行います。 統合Mockの場合、ここで設定された値が統合レスポンスに引き渡されます。 |
| 統合 レスポンス |
統合Mockエンドポイント(統合リクエスト)から受け取った値を元にレスポンスを定義します。ここでHTTPステータスやヘッダ、レスポンスBodyなどを設定します。 |
| メソッド レスポンス |
統合Mockの場合、レスポンスに必要なHTTPステータスをここで設定しておく必要があるようです。 |
色々触ってみながら試していただくのが良いですが、 最もシンプルに使いたい場合は、統合レスポンスのマッピングテーブルを作成するだけ出来ちゃいます!
ということで、[統合レスポンス]を開きます。
統合レスポンスでは、統合リクエストから受信するHTTPステータスごとにマッピングテンプレートの設定が可能です。 新規作成段階では、統合レスポンスがデフォルトで1つ用意されており、統合リクエストで指定されたHTTPステータスに対応する統合レスポンスが存在しない場合、 この統合レスポンスのマッピングテンプレートがレスポンスされます。
※ただし、API Gateway自体がエラーとなった場合を除きます。
今回マッピングテンプレートに指定したい内容は以下のとおりです。
[
{
"id": "1001",
"title": "オフィス狛ものがたり",
"Author": "yuckieee"
},
{
"id": "1002",
"title": "狛ちゃん冒険譚",
"Author": "yuckieee"
}
]
デフォルトの統合レスポンスを順番に展開し、マッピングテンプレートの
Content-Type:application/jsonにレスポンスしたい内容を入力します。入力完了後、右下の[保存]をクリックして保存します。
👉 統合リクエストと統合レスポンスのマッピングテンプレートの記述方法は
「おまけ:リクエストによってレスポンス内容を切り替える」の章を参照してください
マッピングテンプレートの設定は以上で完了です。
Step4:動作確認(テスト)を行う
これまでの設定が問題ないか、デプロイまでにテストできる機能がありますので、これを使って動作確認してみます。 リソースの管理画面左にある[テスト⚡️]をクリックしてください。 メソッドのテスト画面が表示されますので、画面中央下部の[⚡️テスト]クリックで、先ほど定義した内容でAPIが呼び出されます。 [⚡️テスト]クリック後、結果が画面右側に表示されます。この内容が実際にMockにアクセスした際に返却される値と同様の内容となっていますので、この段階でレスポンスに問題がないかを確認します。
もし、
Internal Errorなどが返却されている場合は、設定をもう一度見直すか、ログ領域を参照しエラー内容を確認してください。
Step5:デプロイ(外部公開)する
テストで動作に問題がないことを確認したあとは、実際にデプロイしてみます。[アクション]メニューの[APIのデプロイ]をクリックしてください。
APIのデプロイダイアログが表示されますので、デプロイするステージを新規作成します。
デプロイステージ名は、URLにも組み込まれますので分かりやすい名前にしておきましょう。
今回は
devとしておきます。問題なくデプロイが完了すると、新規作成したステージのエディター画面に遷移します。 遷移先の画面に呼び出し用のURLが記載されていますので、このURLを使用してMockにアクセスが可能になります。
Step6:実行確認する
最後にPOSTMANを使用してリクエストが可能かを確認してみます。POSTMANを起動し、該当のURLを指定して実行してみてください。下図のようにマッピングテンプレートに定義した値が想定通り返却されればOKです!
※APIクライアントツールが入っていない場合は、GETであればブラウザ直接指定でも確認可能です!
デプロイが出来ていない場合、下図のように
Missing Authentication Tokenエラーが返ってきますので再度デプロイしてみてください。
これでMockとして使用できるようになりました!あとは参照元のAndroidアプリ側URLをMockに切り替えてLet's テスト〜♪♬♩
おまけ:リクエストによってレスポンス内容を切り替える
テストをしていると、HTTPステータスやレスポンスBODYの値をリクエストによって切り替えたくなりますよね。私もそうでした!!!!(最終的にはLambdaを使用しましたが)簡単な切り替えであれば、統合Mock機能でも可能です。幾つか切り替え方法をご紹介しますので参考にしてみてください。
💡 マッピングテンプレート記述言語
マッピングテンプレートではApache Velocity Template Language (VTL)という言語が採用されているようです。 この言語仕様に沿って変数の定義やIF文、FOR文などの記述が可能です。今回は、これらを使ってリクエストによるレスポンス内容の切り替えを行っていきたいと思います。
なお、ここでは言語自体の説明を省きますので、詳細は公式リファレンスを参照ください。
パターン1:HTTPステータスコードを切り替える
① 統合リクエストのマッピングテンプレート編集HTTPステータスを切り替えたい場合は、統合リクエストのマッピングテンプレートを編集します。
デフォルトでは
{statusCode: 200}が指定されていますが、リクエスト値によって切り替えたい場合は以下の要領で設定します。👉 GETメソッド(クエリ文字使用)の場合
クエリ文字に"id"というパラメータを指定していたと仮定して記述しています。
例:
/book/search?id=1001
#set($id = $input.params("id"))
#if($id == "1001")
{
"statusCode": 200
}
#elseif($id == "1002")
{
"statusCode": 400
}
#else
{
"statusCode": 500
}
#end
👉 POSTメソッド(リクエストBODY使用)の場合
リクエストBODYにidというパラメータを指定していたと仮定して記述しています。
例:
{ "id": "1001" }
#set($id = $util.parseJson($input.body).id)
#if( $id == "1001")
{
"statusCode": 200
}
#elseif($id == "1002")
{
"statusCode": 400
}
#else
{
"statusCode": 500
}
#end
② メソッドレスポンスの追加
レスポンスのHTTPステータスにバリエーションを持たせたい場合、最初にメソッドレスポンスを追加する必要があります。メソッドレスポンス画面を開き[➕ レスポンスの追加]をクリックし、追加したいHTTPステータスコードを入力し、確定してください。
今回は400と500を追加してみました。
③ 統合レスポンスの追加
続いて、HTTPステータスごとの統合レスポンスを作成します。
デフォルトをHTTPステータス200で使用することにして、新しくHTTPステータス400と500用の統合レスポンスを作成します。統合レスポンス画面を開き[➕ 統合レスポンスの追加]をクリックして下記の要領で統合レスポンスを追加してください。
HTTPステータスの正規表現:
統合リクエストから引き渡されるStatusCodeのうち、対象とする値を指定します。
この値は正規表現での指定も可能なため、
4\d{2}と書くことでステータスコード400番台全てを対象とすることも出来ます。メソッドレスポンスのステータス:
②で作成したメソッドレスポンスを指定します。
※先にメソッドレスポンスを作成したのは、ここで指定させるためでした。
コンテンツの処理:
デフォルト値であるパススルーのままで!
この段階で一度[保存]をして開き直すと、マッピングテンプレートの追加が可能になりますので、適当なレスポンス内容を設定してみてください。
その後、HTTPステータス500についても同様に設定します。
全ての設定が完了したあとに、再度STEP4~5で紹介した手順を実施いただけば完了です...!
【補足】動作確認時のクエリ文字/リクエストBODYの指定について
リソースに指定されたHTTPメソッドがGETなどクエリ文字指定可能なものである場合、メソッドテスト画面にクエリ文字の入力エリアが表示されます。
同様にPOSTなど、リクエストBODY指定可能なメソッドの場合は、リクエスト本文の入力エリアが表示されます。(注:リクエスト本文は、GETメソッドの場合は非表示となります)
パターン2:レスポンスBODYを切り替える
HTTPステータスは200のままで、リクエスト内容によってレスポンス内容を変えたい!!!というパターンのご紹介です。
なお、クエリ文字を使用する場合は、パターン1のHTTPステータスでご紹介した
$input.params("id")が、統合レスポンスでも使用できるため説明を省略します。① 統合リクエストのマッピングテンプレート編集
この手順はリクエストBODYを統合レスポンスで使用したいときだけ使用します。
👉 POSTメソッド(リクエストBODY使用)の場合
デフォルトでは、リクエストBODYは統合レスポンスに渡されません。そのため、統合リクエストで明示的にオーバーライドしてあげる必要があります。
なお、
statusCodeは必ず統合レスポンスに送る必要があるため、削除しないようご注意ください。
#set($context.requestOverride.path.body = $input.body)
{
"statusCode": 200
}
② 統合レスポンスのマッピングテンプレート編集
👉 POSTメソッド(リクエストBODY使用)の場合
リクエストBODYにidというパラメータを指定していたと仮定して記述しています。
例:
{ "id": "1001" }
#set($body = $context.requestOverride.path.body)
#set($id = $util.parseJson($body).id)
#if($id == "1001")
{
"id": "1001",
"title": "オフィス狛ものがたり",
"Author": "yuckieee"
}
#elseif($id == "1002")
{
"id": "1002",
"title": "狛ちゃん冒険譚",
"Author": "yuckieee"
}
#else
{
"message":"該当の書籍情報が存在しません"
}
#end
なお、この辺りの話はAWS公式のデベロッパーガイドに記述されていますので、こちらも参考にしていただければと思います。
AWS公式:API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス
おまけ:APIGatewayの料金
今回使用したREST APIでは、受信したAPIコールと、転送データ量に対してのみ料金が発生します。
APIコール料金:
2021/4/13時点で、月間のリクエスト数が3億3,300万件まで100万リクエストにつき4.25USD(アジアパシフィック(東京))のようです。
※なお、キャッシュを利用する場合は、更に料金がかかりますが、テスト・開発で使用する分には必要のない機能かと思います。
データ転送料金:
2021/4/13時点で、10TBまで1GBあたり0.09USDのようです。
なお、対象はAPIGatewayから、インターネット方向のデータ(レスポンスデータ)のみが対象となります。
どちらもリクエスト数、転送データ量が多い場合は、レンジによって割引されます。
ただ、ある程度使い倒したとしても、テスト利用の範囲であれば数百円程度でしょうか。
Mockやシミュレータってどうすりゃいいのか...と、小1時間頭を抱えている工数分で解決できるなら、試してみるのも良さげです!
AWS公式:Amazon API Gateway 料金
まとめ
APIGatewayの統合Mock機能で手軽にMockやシミュレータが作成できるのが分かりました!次回は、APIGatewayをLambdaやS3と連携して一歩進んだMock作成をご紹介できればと思います!
2021年4月26日月曜日
express-requests-loggerでリクエストログの特定の項目をマスクする方法。
オフィス狛 技術部のJoeです。
担当しているプロジェクトで、Node.js と Express でAPIを作成して、
リクエストのログを出力しているのですが、
セキュリティの観点から、一部リクエストの値をマスクする必要がありました。
express-requests-loggerで、マスクする方法をまとめました。
【バージョン情報】
・node:v12.14.0
・express-requests-logger:v3.0.3
■express-requests-logger のインストール
express-requests-loggerをインストールします。npm install express-requests-logger
■ログ出力設定
app.jsにexpress-requests-loggerの設定を追加します。※loggerで指定している「httpLogger」ついては、こちらの記事を参考に設定いただければと思います
Node.js + Express + log4jsで、アクセスログとリクエストログを取る。
const audit = require('express-requests-logger');
// ログ設定
app.use(audit({
logger: httpLogger,
request: {
maskBody: ['password'],
},
response: {
maskBody: ['token'],
},
}));
上記のように設定しますと、リクエストとレスポンスの内容がログに出力されますが、
リクエストBodyの「password」と、レスポンスBodyの「token」の値が「XXXXX」にマスクされます。
【ログ出力結果】
[2021-04-16T11:04:56.407] [INFO] http - {
response: {
status_code: 200,
(中略)
body: '{"token":"XXXXX"}'
},
request: {
method: 'POST',
(中略)
body: '{"loginId":"test","password":"XXXXX"}'
},
'millis-timestamp': 1618538696407,
'utc-timestamp': '2021-04-16T02:04:56.407Z',
stage: 'end'
} Inbound Transaction
他にも、項目自体を除外するオプションなどもありますので、一部ご紹介します。
詳細は「express-requests-logger」のホームページをご覧ください。
express-request-logger
【オプション】
■doubleAudit
・true:リクエストを受けたタイミングで出力(リクエスト)、
レスポンスを送信したタイミングで出力(リクエストとレスポンス)します。
・false:レスポンスを送信したタイミングで出力(リクエストとレスポンス)します。
■excludeURLs
配列で指定した文字列が、リクエストURLの一部と一致する場合は、リクエストもレスポンスも出力しません。
【オプション(リクエスト)】
■audit
・true:リクエストを出力します。
・false:リクエストは出力しません。
■excludeBody
配列で指定した文字列と一致する項目は、bodyに出力しません。
※「*」を指定すると、bodyの全ての項目を出力しません。
■maskQuery
配列で指定した文字列と一致する項目は、queryにマスクして出力します。
■excludeHeaders
配列で指定した文字列と一致する項目は、headersに出力しません。
※「*」を指定すると、headersの全ての項目を出力しません。
■maskHeaders
配列で指定した文字列と一致する項目は、headersにマスクして出力します。
■maxBodyLength
bodyに出力される最大文字数を指定できます。
【オプション(レスポンス)】
レスポンスもリクエストと同様のオプションで、
Body、Headersの項目について、マスクや除外することができます。
少ない設定でマスクすることができますので、機会があればお試しください。
SQL Serverのトランザクションログについて(記録される内容、領域の解放、切り捨て)
オフィス狛 技術部のJoeです。
先日、担当していたシステムで、SQL Serverのトランザクションログが肥大化し、
ストレージが空き容量不足になる事象が発生しました。
その対応にあたり、SQL Serverのトランザクションログについて
調査する機会がありましたので、簡単ですがまとめてみました。
※下記の内容はSQL Server 2017で確認しています
■SQL Serverのトランザクションログには何が記録されるのか?
SQL Serverはデータベースに設定されている、復旧モデルによって異なります。※復旧モデルは下記で確認できます。
SELECT name, recovery_model_desc FROM sys.databases WHERE name = 'model';
・単純モデル
トランザクション開始からコミットまでが、トランザクションログに記録されます。
・完全モデル
全ての操作(DDL、DML)がトランザクションログに記録されます。
インデックスの操作など、大量のデータが読み込れると、大量のログが出力されます。
・一括ログモデル
完全モデルと似ていますが、一括操作(bcp、BULK INSERT など)の場合に、使用されるログ領域を縮小します。
インデックス操作(CREATE INDEX)も、最小ログ記録の対象(※)になるようです。
※下記の「最小ログ記録が可能な操作」を参照ください
トランザクション ログ (SQL Server)
■トランザクションログの領域は、いつ解放されるのか?領域を使い切るとどうなるのか?
こちらも「復旧モデル」によって異なります。・単純モデル
チェックポイント(トランザクションログファイルに書き込み完了のタイミング)で解放されます。
トランザクションログは既存の領域を自動的に再利用しますので、ログ領域の管理は基本的に不要です。
領域を再利用するので、トランザクションログをバックアップすることはできません。
・完全モデル、一括ログモデル
トランザクションログのバックアップで解放されます。
トランザクションログファイルは領域を使い切ると、自動拡張の設定をしていれば、
トランザクションログファイルの上限サイズ(※)まで、物理サイズを自動拡張します。
(もちろんストレージの空きが無ければ拡張できません)
※最大、2TBまで設定できます
トランザクションログファイルは一度拡張されると、
自動縮小のオプションを有効にしていない限り、
自動で物理サイズは縮小されませんので、ストレージは解放されません。
ストレージを解放するには、下記でトランザクションログファイルを縮小し、
物理ファイルサイズを減らすことで、ストレージの空き容量を増やすことができます。
use [データベース名]; -- トランザクションログ論理名を確認 SELECT name FROM sys.database_files where type = 1; -- トランザクションログファイルを縮小 DBCC SHRINKFILE( [トランザクションログ論理名] , [縮小後の容量(MB)]);※縮小後の容量の単位はMB(100を指定すると100MB)なので、ご注意ください
■トランザクションログを、手動で切り捨てる方法
ストレージの容量不足や、バックアップを取得する時間が無い場合などに、手動で切り捨てることができます。
※トランザクションログの使用状況は下記コマンドで確認できます。
DBCC SQLPERF('LOGSPACE');
【方法①】トランザクションログの削除
下記で削除できます。
注意点としては、削除前に一度、データベースの完全バックアップを取得している必要があります。
(取得してい場合はエラーになります)
BACKUP LOG [データベース名] TO DISK = N'NUL';
【方法②】データベースの復旧モデルを「単純モデル」に変更する
下記で変更できます。
注意点ですが、例えば完全モデルから単純モデルに変更する場合、
完全モデルのみで使用できる機能を使用していると、単純モデルへ変更できないようです。
※以前、AWS RDSで、マルチAZ「あり (ミラーリング)」が設定されている完全モデルのデータベースは、単純モデルに変更することができませんでした
ALTER DATABASE [データベース名] SET RECOVERY SIMPLE;
以上です。
トランザクションログで何か問題があった際に、お役に立てば幸いです。
2021年4月23日金曜日
2021年追加版、AdobeFontおすすめフォント三選。
こんにちは、オフィス狛 デザイン部のSatoです。
2021年4月8日に、AdobeFontsに日本語フォントが191も増えたそうです。(4月10日がフォントの日なので、そこに合わせてきたんですね)
既存のものも合わせると、AdobeFontsに日本語のフォントは436フォントもあるらしいです!(使いきれない!)
Adobe Fontsが大幅拡充!日本語フォントラインナップが強化されました
ラインナップを見ていると見覚えがある文字もチラチラ…。
今回はAdobeFontsの個人的推しフォントを3つご紹介しようと思います。
ABキリギリスは雑誌やお菓子パッケージでよく見るフォントです!
個人的には「え?あのキリギリスがAdobeFontsに!?」と驚きました!嬉しい〜!
ほぼ打ちっぱなしでも、良い感じになりますね
クラフト感や異国感を出したい時に使いたいフォントです。
漢字の収録が少ないですが、それでも素敵なフォントです。
ちなみに「きりぎりす」という、こちらのフォントのバランスを修正+収録文字が追加されたフォントもありますが、AdobeFontには入っていません!「キリギリスじゃ漢字が少ない!」と感じた方はこちらを買ってみてはいかがでしょうか?
文字の重なりがレトロでノスタルジーなフォントですね。
Googleで調べた所、アニメの字幕に使われていたらしいですね。
憧れの「スーボ」に似た印象を受ける文字なので一目惚れしていまいました。
平成初期くらいの雑誌や、本の見出しを思い出しませんか?
使い所は難しいですが、かわいい・ポップな印象を出したい時に使えそうですよね!
さらさらと小筆で書いたような手書き風フォントです。
女性っぽい字だと勝手に思っていたのですが、良寛という男のお坊さんが書いた臨書を元にしたフォント……との事です。
シンプルで細身なフォントなので筆文字!という迫力が少なくて色々な手書きフォントが必要な時に使えそうですね。
夏のお中元の広告や、ナチュラルなイメージにしたい時に使えそうだと思いました。
今年追加されたものではないですが、このフォントも紹介させてください(みなさん知ってますかね?)
丸くて抜けてる印象を受ける可愛いフォントなのにきちんと読みやすい!
萌え〜なアニメゲーム、などのかわいい系のサブカルチャーとの相性が良さそうですよね。

































