Detect startup after recent crashes
This commit is contained in:
parent
02a732c511
commit
91f1b62bc7
23 changed files with 650 additions and 101 deletions
109
ts/logging/uploadDebugLog.ts
Normal file
109
ts/logging/uploadDebugLog.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2018-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Response } from 'got';
|
||||
import { z } from 'zod';
|
||||
import FormData from 'form-data';
|
||||
import got from 'got';
|
||||
import { gzip } from 'zlib';
|
||||
import pify from 'pify';
|
||||
import { getUserAgent } from '../util/getUserAgent';
|
||||
import { maybeParseUrl } from '../util/url';
|
||||
import * as durations from '../util/durations';
|
||||
import type { LoggerType } from '../types/Logging';
|
||||
|
||||
const BASE_URL = 'https://debuglogs.org';
|
||||
|
||||
const UPLOAD_TIMEOUT = { request: durations.MINUTE };
|
||||
|
||||
const tokenBodySchema = z
|
||||
.object({
|
||||
fields: z.record(z.unknown()),
|
||||
url: z.string(),
|
||||
})
|
||||
.nonstrict();
|
||||
|
||||
const parseTokenBody = (
|
||||
rawBody: unknown
|
||||
): { fields: Record<string, unknown>; url: string } => {
|
||||
const body = tokenBodySchema.parse(rawBody);
|
||||
|
||||
const parsedUrl = maybeParseUrl(body.url);
|
||||
if (!parsedUrl) {
|
||||
throw new Error("Token body's URL was not a valid URL");
|
||||
}
|
||||
if (parsedUrl.protocol !== 'https:') {
|
||||
throw new Error("Token body's URL was not HTTPS");
|
||||
}
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
export type UploadOptionsType = Readonly<{
|
||||
content: string | Buffer | Uint8Array;
|
||||
appVersion: string;
|
||||
logger: LoggerType;
|
||||
extension?: string;
|
||||
contentType?: string;
|
||||
compress?: boolean;
|
||||
}>;
|
||||
|
||||
export const upload = async ({
|
||||
content,
|
||||
appVersion,
|
||||
logger,
|
||||
extension = 'gz',
|
||||
contentType = 'application/gzip',
|
||||
compress = true,
|
||||
}: UploadOptionsType): Promise<string> => {
|
||||
const headers = { 'User-Agent': getUserAgent(appVersion) };
|
||||
|
||||
const signedForm = await got.get(BASE_URL, {
|
||||
responseType: 'json',
|
||||
headers,
|
||||
timeout: UPLOAD_TIMEOUT,
|
||||
});
|
||||
const { fields, url } = parseTokenBody(signedForm.body);
|
||||
|
||||
const uploadKey = `${fields.key}.${extension}`;
|
||||
|
||||
const form = new FormData();
|
||||
// The API expects `key` to be the first field:
|
||||
form.append('key', uploadKey);
|
||||
Object.entries(fields)
|
||||
.filter(([key]) => key !== 'key')
|
||||
.forEach(([key, value]) => {
|
||||
form.append(key, value);
|
||||
});
|
||||
|
||||
const contentBuffer = compress
|
||||
? await pify(gzip)(Buffer.from(content))
|
||||
: Buffer.from(content);
|
||||
form.append('Content-Type', contentType);
|
||||
form.append('file', contentBuffer, {
|
||||
contentType,
|
||||
filename: `signal-desktop-debug-log-${appVersion}.txt.gz`,
|
||||
});
|
||||
|
||||
logger.info('Debug log upload starting...');
|
||||
try {
|
||||
const { statusCode, body } = await got.post(url, {
|
||||
headers,
|
||||
body: form,
|
||||
timeout: UPLOAD_TIMEOUT,
|
||||
});
|
||||
if (statusCode !== 204) {
|
||||
throw new Error(
|
||||
`Failed to upload to S3, got status ${statusCode}, body '${body}'`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
const response = error.response as Response<string>;
|
||||
throw new Error(
|
||||
`Got threw on upload to S3, got status ${response?.statusCode}, body '${response?.body}' `
|
||||
);
|
||||
}
|
||||
logger.info('Debug log upload complete.');
|
||||
|
||||
return `${BASE_URL}/${uploadKey}`;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue