Cloud Logging からログを NDJSON で取り出す
GCS に書き出しておいたり、BigQuery に sink したり
過去のものは 30 日間残る
UI や API では取り出せる
UI から export できるのはログ100件だけ
過去のログを取り出したい
できるだけ生のログの形で
そのまま再度 BigQuery に投入したりしたい
そのままとりだせるのは
gcloud logging read --format=json が一番正確(おそらく)
遅い
GB 単位だと日が暮れる
全体が Array で wrap されてしまうし、prettyprint されてしまう
のでそのまま BQ に入れられない
! gcloud も API 叩いてるはずなので API で代替したい gRPC で
こうする
並列に gcloud logging read を叩く
ほしい時間範囲を細かく切って
並列数を制御しつつ
出力される巨大な Array の JSON を Stream 処理しつつ NDJSON に変換する
以下のスクリプトで解決
BigQuery へ入れた
400万DL/week だけどリポジトリは archive されてるし case-insensitive で @types/jsonstream と型が合わない
Mac なら動くがめちゃだるいな
ln -s JSONStream jsonstream うんこ
code:rescue_logs.ts
import { spawn } from 'child_process';
import * as fs from 'fs';
import { default as PQueue } from 'p-queue';
import * as JSONStream from 'jsonstream';
import { parseISO, add, format, formatRFC3339, Duration } from 'date-fns';
// options
const concurrency = 3;
const projectId = '<YOUR_PROJECT_ID>';
const commonFilter = [
'resource.type=gae_app',
'logName="projects/<YOUR_PROJECT_ID>/logs/stdout"',
'jsonPayload.type="hogehoge"',
];
const since = '2020-03-28T00:00:00.000+09:00';
const until = '2020-03-28T00:05:00.000+09:00';
const minutes = 1;
function getLogEntries(range: Date[]): Promise<void> {
return new Promise((resolve, reject) => {
const query = [
timestamp >= "${formatRFC3339(range[0])}",
timestamp < "${formatRFC3339(range[1])}",
];
console.log('downloading logs:', query.join(' AND '));
const out = fs.createWriteStream(
./${format(range[0], 'yyyyMMdd_HHmm')}.json,
'utf8'
);
const proc = spawn('gcloud', [
--project=${projectId},
'logging',
'read',
'--format=json',
]);
proc.on('error', reject);
proc.stdout
.pipe(JSONStream.parse(true)) // strip array .pipe(JSONStream.stringify('', '\n', '')) // make NDJSON
.pipe(out);
out.on('finish', resolve);
});
}
function generateRanges(since: Date, until: Date, step: Duration): Date[][] {
const ranges = [];
let current = since;
while (current < until) {
const next = add(current, step);
ranges.push(r);
current = next;
}
return ranges;
}
(async () => {
const queue = new PQueue({ concurrency });
generateRanges(parseISO(since), parseISO(until), { minutes }).forEach(r =>
queue.add(() => getLogEntries(r).catch(console.error))
);
await queue.onIdle();
console.log('done');
})();