* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import process from 'node:process';
import {
extractJscrashFaultlogNames,
filterFaultlogsByBundle,
parseFaultlogEntry,
selectWithinMaxAge,
sortFaultlogsByRecency,
} from './shared/jscrash-faultlogger.mjs';
import { resolveHdcOrThrow, runHdc, targetArgs } from './shared/hdc.mjs';
import { printKv, toErrorMessage } from './shared/utils.mjs';
function parseArgs(argv) {
const map = new Map();
const optionalKeys = new Set(['bundle-name', 'device-id']);
for (let i = 0; i < argv.length; i += 1) {
const t = argv[i];
if (!t.startsWith('--')) {
continue;
}
const key = t.slice(2);
const val = argv[i + 1];
if (!val || val.startsWith('--')) {
if (optionalKeys.has(key)) {
map.set(key, '');
continue;
}
throw new Error(`Missing value for --${key}`);
}
map.set(key, val);
i += 1;
}
return {
bundleName: map.get('bundle-name') ?? '',
deviceId: map.get('device-id') ?? '',
maxAgeMinutes: Number(map.get('max-age-minutes') ?? '30'),
limit: Number(map.get('limit') ?? '10'),
};
}
async function readFaultloggerViaHidumper(hdc, deviceId) {
const args = [
hdc,
...targetArgs(deviceId),
'shell',
'hidumper',
'-s',
'1201',
'-a',
'-p Faultlogger LogSuffixWithMs',
];
return runHdc(args);
}
async function readFaultloggerViaLs(hdc, deviceId) {
const remote = '/data/log/faultlog/faultlogger';
const args = [hdc, ...targetArgs(deviceId), 'shell', 'ls', '-1', remote];
return runHdc(args);
}
function formatCandidates(names, limit) {
return names.slice(0, limit).join('|');
}
function printProbeFailed(bundleName, nextAction) {
printKv({
status: 'probe_failed',
bundle_name: bundleName,
latest_faultlog: '',
latest_timestamp: '',
matched_count: '0',
candidates: '',
next_action: nextAction,
});
}
function printProbeNotFound(bundleName) {
printKv({
status: 'not_found',
bundle_name: bundleName,
latest_faultlog: '',
latest_timestamp: '',
matched_count: '0',
candidates: '',
next_action: 'No recent matching faultlog was found. You may collect hilog if you still need more runtime evidence, or reproduce and retry.',
});
}
function printProbeFound(bundleName, names, limit) {
const latest = names[0];
const ts = parseFaultlogEntry(latest).timestampMs;
printKv({
status: 'found',
bundle_name: bundleName,
latest_faultlog: latest,
latest_timestamp: ts === null ? '' : String(ts),
matched_count: String(names.length),
candidates: formatCandidates(names, limit),
next_action: `A recent faultlog is available. Fetch it with fetch-faultlog.mjs --faultlog-name "${latest}" if you want a cleaner crash anchor.`,
});
}
async function loadCandidateNames(hdc, deviceId, bundleName, maxAgeMinutes) {
const hidumper = await readFaultloggerViaHidumper(hdc, deviceId);
let combined = hidumper.exitCode === 0 ? hidumper.stdout : '';
const ls = await readFaultloggerViaLs(hdc, deviceId);
if (ls.exitCode === 0) {
combined = `${combined}\n${ls.stdout}`;
}
let names = extractJscrashFaultlogNames(combined);
names = filterFaultlogsByBundle(names, bundleName);
names = selectWithinMaxAge(names, maxAgeMinutes, Date.now());
return sortFaultlogsByRecency(names);
}
async function main() {
let bundleName = '';
try {
const args = parseArgs(process.argv.slice(2));
bundleName = args.bundleName;
const hdc = await resolveHdcOrThrow();
const names = await loadCandidateNames(hdc, args.deviceId, args.bundleName, args.maxAgeMinutes);
if (!names.length) {
printProbeNotFound(args.bundleName);
process.exit(0);
return;
}
printProbeFound(args.bundleName, names, args.limit);
process.exit(0);
} catch (err) {
printProbeFailed(bundleName, toErrorMessage(err));
process.exit(1);
}
}
await main();