Compute@Edge のデバッグツールとログについて
Compute@Edge をはじめるにあたって知っておくとよさそうなデバッグツールとログ周りのトピックについて記載します。
ローカルテスト
プロジェクトのルートディレクトリで fastly compute serve を実行するとローカルマシン上でテスト用の HTTP サーバが起動します。Rust の println!() や JS の console.log() の結果がコンソールに表示されるので手軽にプリントデバッグすることができます。
バックエンドサーバの設定
バックエンドリクエストの宛先はバックエンドサーバとして設定されている必要があり、ローカルテストの場合は fastly.toml にサーバの定義を記述します。バックエンドサーバはローカルマシン上で動作する別のサーバでもいいですし、外部のサーバでも構いません。
ローカルテストのバックエンドサーバの定義の例です。
[local_server]
[local_server.backends]
[local_server.backends.backend_a]
url = "http://127.0.0.1:8080/"
[local_server.backends.backend_b]
url = "https://www.example.com/"
ローカルテストではリクエストの Host ヘッダの値は指定しない限り 127.0.0.1:7676
になり、そのままバックエンドに転送するとサーバによってはエラーになります。 override_host を設定するとローカルの HTTP サーバからバックエンドにリクエストを送信するときに Host ヘッダを指定した値で上書きします。
[local_server]
[local_server.backends]
[local_server.backends.backend_a]
url = "http://127.0.0.1:8080/"
[local_server.backends.backend_b]
url = "https://www.example.com/"
+ override_host = "www.example.com"
ディクショナリ
Compute@Edge のコードから Edge Dictionary を参照することができますが、ローカルテストでもローカルのディクショナリを定義して同じコードで参照することができます。
ローカルのディクショナリは JSON ファイルに定義します。
{
"cat": "ニャオ",
"dog": "ワン"
}
fastly.toml のなかでディクショナリとして JSON ファイルを指定します。
[local_server]
[local_server.backends]
[local_server.backends.backend_a]
url = "http://127.0.0.1:8080/"
[local_server.dictionaries]
[local_server.dictionaries.animal_sound]
file = "animal_sound.json"
format = "json"
ディクショナリをルックアップするコードの例です。
Rust
src/main.rs
use fastly::{Error, Request, Response, Dictionary};
#[fastly::main]
fn main(_req: Request) -> Result<Response, Error> {
let dict = Dictionary::open("animal_sound");
match dict.get("dog") {
Some(text) => Ok(Response::from_body(text)),
_ => Ok(Response::from_body("Sound not found"))
}
}
JavaScript
src/index.js
addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));
async function handleRequest(event) {
const dict = new Dictionary("animal_sound");
const sound = dict.get("cat");
if (sound) {
return new Response(sound, {
status: 200,
});
}
return new Response("Sound not found", {
status: 200,
});
}
実行環境によって処理を書き分ける
環境変数 FASTLY_HOSTNAME にはローカルで実行されている場合 localhost
がセットされるので実行環境によって処理を書き分けるときに使うことができます。
let local = std::env::var("FASTLY_HOSTNAME").unwrap_or_default() == "localhost";
if local {
println!("I'm testing locally!");
}
const local = fastly.env.get("FASTLY_HOSTNAME") === "localhost";
if (local) {
console.log("I'm testing locally!");
}
ローカルテストの制約
ほとんどの機能をテストすることができるものの、ローカルテストは Fastly で動作するサービスの完全なレプリカではありません。例えばキャッシュの機能はありませんし、Geo ロケーションの情報取得には対応していません。ローカルテストの制約についてはこちらのドキュメントに記載されています。
ライブログテーリング
プロジェクトのルートディレクトリで fastly log-tail を実行すると Fastly にデプロイしたサービスの stdout, stderr へのアウトプットがリアルタイムにコンソールに配信されます。サードパーティのエンドポイントへ送信するように設定されているログはコンソールには配信されません。
ログテーリングは開発中のデバッグツールとして有用ですが、扱えるログのボリュームは限られています。プロダクション環境のログ等、ハイボリュームなログを取得する場合はログストリーミングをご利用ください。
ログテーリングの入力レートやバッファの制限についてはこちらに記載されています。
ログストリーミング
Compute@Edge からサードパーティのログ・ストレージサービスにログを送信することができます。ログの送信先となるエンドポイントの設定と、そのエンドポイントにログを送信するコードの例を紹介します。
ログのエンドポイントの設定
サービスの設定をクローンして編集可能なバージョンを作成し、ログのエンドポイントを設定します。この例では Papertrail を使っています。
fastly service-version clone --version=latest
fastly logging papertrail create --name=my_papertrail --placement=none --address=XXX.papertrailapp.com --port=XXXXX --version=latest
ここでは Fastly CLI を使ってエンドポイントを追加していますが、サービスの管理画面から設定することもできます。
設定に必要な情報は送信先のサービスによって異なります。こちらのリンクから対象のサービスのドキュメントを探してご確認ください。
(参考) fastly logging papertrail コマンドのオプションについて
Flag | Example | Description |
---|---|---|
--name | my_papertrail | エンドポイント名。コードの中でログの送信先を指定するときに使う |
--address | XXX.papertrailapp.com | Papertrail アカウントの Settings > Log Destinations から確認 |
--port | 12345 | Papertrail アカウントの Settings > Log Destinations から確認 |
--placement | none | Compute@Edge ログエンドポイントの場合は "none" |
設定したエンドポイントにログを送信する
設定したエンドポイントにログを送信するコードの例です。
Rust
src/main.rs
log, log-fastly を Cargo.toml に追加します。
cargo add log log-fastly
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding log v0.4.14 to dependencies
Adding log-fastly v0.8.0 to dependencies
エンドポイント名を使ってログの送信先を指定します。
use fastly::{Error, Request, Response, http::StatusCode};
#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
log_fastly::init_simple("my_papertrail", log::LevelFilter::Warn);
log::warn!("This will be written to my_papertrail...");
log::info!("...but this won't");
match req.get_path() {
"/panic" => panic!("oh no!"),
_ => Ok(Response::from_status(StatusCode::OK))
}
}
パニックしたときのログを同じエンドポイントに送信したい場合はこのようにします。
use fastly::{Error, Request, Response, http::StatusCode};
#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
log_fastly::init_simple("my_papertrail", log::LevelFilter::Warn);
log::warn!("This will be written to my_papertrail...");
log::info!("...but this won't");
+ fastly::log::set_panic_endpoint("my_papertrail").unwrap();
match req.get_path() {
"/panic" => panic!("oh no!"),
_ => Ok(Response::from_status(StatusCode::OK))
}
}
JavaScript
src/index.js
エンドポイント名を使ってログの送信先を指定します。
addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));
async function handleRequest(event) {
const logger = fastly.getLogger("my_papertrail");
logger.log("Hello, Papertrail");
return new Response("", {
status: 200,
});
}
ログすると有用かもしれない情報
環境変数
Name | Example | Description |
---|---|---|
FASTLY_CACHE_GENERATION | 1 | Purge all を実行するとカウントアップする |
FASTLY_CUSTOMER_ID | 1mXdot9pM8uPxdKyAHF4BG | |
FASTLY_HOSTNAME | cache-hnd18735 | リクエストを処理したサーバのホスト名。ローカルテスト時は "localhost" |
FASTLY_POP | HND | |
FASTLY_REGION | Asia | |
FASTLY_SERVICE_ID | 59cnYbyxkqxmqwdpxfj7Te | |
FASTLY_SERVICE_VERSION | 3 | |
FASTLY_TRACE_ID | f840590299c64e8485a2747b7f12fdf2 | クライアントリクエスト毎に割り振られる UUID |
環境変数の値を取得するコードの例。
let hostname = std::env::var("FASTLY_HOSTNAME").unwrap_or_default();
const hostname = fastly.env.get("FASTLY_HOSTNAME");
IPアドレス
クライアント IP を取得するコードの例
let ip = req.get_client_ip_addr().unwrap();
const ip = event.client.address;
IP をマスクして匿名化するコードの例。
Geolocation
取得できる情報は SDK のドキュメントから確認できます。
Geo 情報を取得するコードの例。ローカル実行時は Geo 情報をルックアップしないように条件をつけています。
Rust
この例では JSON 形式でログを出すことにします。 serde_json を Cargo.toml に追加します。
cargo add serde_json
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding serde_json v1.0.72 to dependencies
use fastly::{Error, Request, Response, http::StatusCode};
#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
let local = std::env::var("FASTLY_HOSTNAME").unwrap_or_default() == "localhost";
if !local {
let client_ip = req.get_client_ip_addr().unwrap();
let geo = fastly::geo::geo_lookup(client_ip).unwrap();
let client_data = serde_json::json!({
"ip": client_ip,
"as": geo.as_number(),
"city": geo.city(),
"country_code": geo.country_code(),
"conn_type": geo.conn_type(),
});
println!("{}", client_data);
}
Ok(Response::from_status(StatusCode::OK))
}
JavaScript
addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));
async function handleRequest(event) {
const local = fastly.env.get("FASTLY_HOSTNAME") === "localhost";
if (!local) {
const ip = event.client.address;
const geo = event.client.geo;
const clientData = JSON.stringify({
ip,
"as": geo.as_number,
"city": geo.city,
"country_code": geo.country_code,
"conn_type": geo.conn_type,
});
console.log(clientData);
}
return new Response("", {
status: 200,
});
}
Discussion