Deno (Deploy)のログを New Relic に送り込む

Deno Deploy のログ保存期間は、有料版でも1週間。
無料版だと、1日分しか保存されない。

本運用するなら、別の場所へログを吐き出す仕組みは検討しておいた方がよいだろうと感じていました。
調べると New Relic は、デフォルトで30日分のログを保存してくれるようです。
有料プランで延長すれば120日まで延長でき。ライブアーカイブという機能なら7年分まで保存できるようです。

なんにせよ1週間よりは良さそうです。

こちらを試します。

参考

実装

準備

New Relic のアカウントを作成し、APIキーを取得します。
メニューからすぐには見つけにくく。

  • 左下のユーザーのアイコンをクリック
  • Administration をクリック
  • API Keys をクリック
  • Create Key で Ingest License を選択し、その他を任意の記述をしてキーを作成

という手順で操作できます。

とりあえず送る

new relic は、Node用であれば、アプリケーションの起動に引数を渡して動かすものがあります。
どうにかDenoでも使えないかと試してみましたが、いささか厳しい様子。
node-newrelic に Issues も立っています。

ですが、Deno Deploy では使えないなとも考え、早々にこの路線は諦めました。
で、Log API というのが提供されており、とりあえず送るだけなら以下のようにできます。

app.ts
1
2
3
4
5
6
7
8
fetch('https://log-api.newrelic.com/log/v1',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Api-Key': '[作成したキー]'
},
body: "{}"
})

このようにするとnew relic のログに時刻だけが記録されます。

さすがになのでメッセージを送ってみます。

app2.ts
1
2
3
4
5
6
7
8
9
10
fetch('https://log-api.newrelic.com/log/v1',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Api-Key': '[作成したキー]'
},
body: JSON.stringify({
message: "Hello from Deno"
})
)

console.log に割り込む

以前、Fresh のログ用ミドルウェアを作った際には、専用の関数を作って、それを使うようにしていました。
今回は、広くDenoからのログ出力をNew Relicへ送るようにしたいので、console.log の処理を割り込むようにします。

app3.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const orgLog = console.log;
console.log = (message: string) => {
fetch('https://log-api.newrelic.com/log/v1?Api-Key=',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Api-Key': '[作成したキー]'
},
body: JSON.stringify({
timestamp: Date.now(),
logtype: 'info',
message
})

})
orgLog(message)
}

console.log('Hello from Deno');

このようにすると、New Relicにもメッセージを送りつつ、console.log への出力が両立されるようになります。

複雑なオブジェクトも送る

と、ここまでうまい具合に動きますが、複雑なオブジェクトを送るとエラーになります。
例えば以下のRequestオブジェクトような。

app4.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const orgLog = console.log;

console.log = (message: string) => {
fetch('https://log-api.newrelic.com/log/v1?Api-Key=',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Api-Key': '[作成したキー]'
},
body: JSON.stringify({
timestamp: Date.now(),
logtype: 'info',
message
})
})
orgLog(message)
}

Deno.serve((req: Request) => {
console.log(req)

return new Response("Hello World")
});

実行してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ deno run -A --watch ./app4.ts
Watcher Process started.
Listening on http://localhost:8000/
error: Uncaught (in promise) AssertionError: Assertion failed.
orgLog(message)
^
at assert (ext:deno_console/01_console.js:184:11)
at getKeys (ext:deno_console/01_console.js:1268:7)
at formatRaw (ext:deno_console/01_console.js:745:14)
at formatValue (ext:deno_console/01_console.js:529:10)
at inspect (ext:deno_console/01_console.js:3417:10)
at Request.[Deno.privateCustomInspect] (ext:deno_fetch/23_request.js:514:12)
at formatValue (ext:deno_console/01_console.js:471:48)
at inspectArgs (ext:deno_console/01_console.js:3037:17)
at log (ext:deno_console/01_console.js:3106:7)
at console.log (file:///D:/development/test/test458-deno-new-relic/serve2.ts:17:3)
Watcher Process failed. Restarting on file change...

といった具合。

これの解決には、以下のように対応できます。

app5.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const logger={          // <= 直接の変数でconsole.logを保持するのをやめる
orgLog: console.log,
}

console.log = (message: string) => {
fetch('https://log-api.newrelic.com/log/v1',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Api-Key': '[作成したキー]'
},
body: JSON.stringify({
timestamp: Date.now(),
logtype: 'info',
message
})


})
logger.orgLog(message)
}

Deno.serve((req: Request) => {
console.log(req)

return new Response("Hello World")
});

いくつか事例を調べていくと、console.log の対比先を直接の変数ではなく、オブジェクトのメンバーで抱えさせておくものが多いようです。

これで問題ないかと思いきや、new relic にメッセージが空で送られるようになりました。

これは動作としては正しいものでした。例えば、JSON.stringify を通した Request オブジェクトは次のようになります。

1
2
3
$ deno
> JSON.stringify({message: new Request("https://example.com")})
'{"message":{}}'

なので、Deno.inspect を使って、オブジェクトを文字列化して送るようにします。

app6.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const logger={
orgLog: console.log,
}

console.log = (message: string) => {
fetch('https://log-api.newrelic.com/log/v1',{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Api-Key': '[作成したキー]'
},
body: JSON.stringify({
timestamp: Date.now(),
logtype: 'info',
message: Deno.inspect(message) // <= Deno.inspect で文字列化
})
})
logger.orgLog(message)
}

Deno.serve((req: Request) => {
console.log(req)

return new Response("Hello World")
});

new relic でのログ表示では、以下のように表示されます。

これで、コンソール出力と、new relic への送信が両立できるようになりました。

Deno Deploy でも動くのか

動く。

Playground で試してみましたが、問題なく動きました。

new relic のログ記載は以下の通り。

送り先ドメインが間違いなく Deno Deploy のものになっていることが確認できます。


というわけで、Deno Deploy のログ出力を new relic に送り込んでみました。
console.error などは、別の対応になるのでいくつかのメソッドを対応すれば実践投入できそうです。

では。