mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-21 02:36:57 +08:00
fix: read ql runtime logs from syslog
This commit is contained in:
+51
-2
@@ -213,7 +213,56 @@ run_extra_shell() {
|
|||||||
|
|
||||||
## 查看青龙服务日志
|
## 查看青龙服务日志
|
||||||
show_service_logs() {
|
show_service_logs() {
|
||||||
pm2 logs qinglong "$@"
|
local lines="15"
|
||||||
|
local no_stream="false"
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--lines)
|
||||||
|
if [[ ! "${2:-}" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "ql log: --lines requires a non-negative integer" >&2
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
lines="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--lines=*)
|
||||||
|
lines="${1#--lines=}"
|
||||||
|
if [[ ! "$lines" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "ql log: --lines requires a non-negative integer" >&2
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--nostream)
|
||||||
|
no_stream="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ql log: unknown option: $1" >&2
|
||||||
|
return 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
local system_log_dir="$dir_data/syslog"
|
||||||
|
local latest_system_log
|
||||||
|
latest_system_log=$(ls -1t "$system_log_dir"/*.log* 2>/dev/null | head -n 1)
|
||||||
|
if [[ "$lines" != "0" ]] && [[ -f "$latest_system_log" ]]; then
|
||||||
|
tail -n "$lines" "$latest_system_log"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$no_stream" == "true" ]]; then
|
||||||
|
if [[ -z "$latest_system_log" ]]; then
|
||||||
|
echo "ql log: no system log found in $system_log_dir" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Winston writes the active runtime log by date. Follow the filename so
|
||||||
|
# size-based rotations are handled without involving the PM2 log files.
|
||||||
|
local current_system_log="$system_log_dir/$(date +%F).log"
|
||||||
|
tail -n 0 --retry -F "$current_system_log"
|
||||||
}
|
}
|
||||||
|
|
||||||
## 脚本用法
|
## 脚本用法
|
||||||
@@ -226,7 +275,7 @@ usage() {
|
|||||||
echo -e "5. $cmd_update rmlog <days> # 删除旧日志"
|
echo -e "5. $cmd_update rmlog <days> # 删除旧日志"
|
||||||
echo -e "6. $cmd_update bot # 启动tg-bot"
|
echo -e "6. $cmd_update bot # 启动tg-bot"
|
||||||
echo -e "7. $cmd_update check # 检测青龙环境并修复"
|
echo -e "7. $cmd_update check # 检测青龙环境并修复"
|
||||||
echo -e "8. $cmd_update log [pm2-log-options] # 查看青龙服务日志"
|
echo -e "8. $cmd_update log [--lines <n>] [--nostream] # 查看青龙运行日志"
|
||||||
echo -e "9. $cmd_update resetlet # 重置登录错误次数"
|
echo -e "9. $cmd_update resetlet # 重置登录错误次数"
|
||||||
echo -e "10. $cmd_update resettfa # 禁用两步登录"
|
echo -e "10. $cmd_update resettfa # 禁用两步登录"
|
||||||
echo -e "11. $cmd_update resetpwd # 修改登录密码"
|
echo -e "11. $cmd_update resetpwd # 修改登录密码"
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ function writeExecutable(filePath, content) {
|
|||||||
fs.writeFileSync(filePath, content, { mode: 0o755 });
|
fs.writeFileSync(filePath, content, { mode: 0o755 });
|
||||||
}
|
}
|
||||||
|
|
||||||
test('ql log scopes PM2 output to qinglong and forwards log options', () => {
|
function prepareFixture(name) {
|
||||||
const shellDir = path.join(fixtureRoot, 'shell');
|
const testRoot = path.join(fixtureRoot, name);
|
||||||
const binDir = path.join(fixtureRoot, 'bin');
|
const shellDir = path.join(testRoot, 'shell');
|
||||||
|
const binDir = path.join(testRoot, 'bin');
|
||||||
fs.mkdirSync(shellDir, { recursive: true });
|
fs.mkdirSync(shellDir, { recursive: true });
|
||||||
fs.mkdirSync(binDir, { recursive: true });
|
fs.mkdirSync(binDir, { recursive: true });
|
||||||
|
|
||||||
@@ -28,29 +29,100 @@ test('ql log scopes PM2 output to qinglong and forwards log options', () => {
|
|||||||
);
|
);
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(shellDir, 'share.sh'),
|
path.join(shellDir, 'share.sh'),
|
||||||
'load_ql_envs() { :; }\nimport_config() { :; }\n',
|
[
|
||||||
|
'dir_data="${QL_DATA_DIR:-$QL_DIR/data}"',
|
||||||
|
'load_ql_envs() { :; }',
|
||||||
|
'import_config() { :; }',
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
);
|
);
|
||||||
fs.writeFileSync(path.join(shellDir, 'api.sh'), '');
|
fs.writeFileSync(path.join(shellDir, 'api.sh'), '');
|
||||||
fs.writeFileSync(path.join(shellDir, 'env.sh'), '');
|
fs.writeFileSync(path.join(shellDir, 'env.sh'), '');
|
||||||
writeExecutable(
|
writeExecutable(
|
||||||
path.join(binDir, 'pm2'),
|
path.join(binDir, 'pm2'),
|
||||||
'#!/usr/bin/env bash\nprintf "%s\\n" "$@"\n',
|
'#!/usr/bin/env bash\necho "pm2 must not be called" >&2\nexit 99\n',
|
||||||
|
);
|
||||||
|
|
||||||
|
return { binDir, shellDir, testRoot };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('ql log reads the requested system log history without PM2', () => {
|
||||||
|
const { binDir, shellDir, testRoot } = prepareFixture('host');
|
||||||
|
const systemLogDir = path.join(testRoot, 'data/syslog');
|
||||||
|
fs.mkdirSync(systemLogDir, { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(systemLogDir, '2026-08-20.log'),
|
||||||
|
'system-one\nsystem-two\nsystem-three\n',
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = spawnSync(
|
const result = spawnSync(
|
||||||
'/bin/bash',
|
'/bin/bash',
|
||||||
[path.join(shellDir, 'update.sh'), 'log', '--lines', '25', '--nostream'],
|
[path.join(shellDir, 'update.sh'), 'log', '--lines', '2', '--nostream'],
|
||||||
{
|
{
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
PATH: `${binDir}:${process.env.PATH}`,
|
PATH: `${binDir}:${process.env.PATH}`,
|
||||||
QL_DIR: fixtureRoot,
|
QL_CONTAINER: 'false',
|
||||||
|
QL_DIR: testRoot,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(result.status, 0, result.stderr);
|
assert.equal(result.status, 0, result.stderr);
|
||||||
assert.equal(result.stdout, 'logs\nqinglong\n--lines\n25\n--nostream\n');
|
assert.equal(result.stdout, 'system-two\nsystem-three\n');
|
||||||
assert.equal(fs.existsSync(path.join(fixtureRoot, 'data/log/log')), false);
|
assert.equal(fs.existsSync(path.join(testRoot, 'data/log/log')), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ql log follows the current Winston system log without PM2', () => {
|
||||||
|
const { binDir, shellDir, testRoot } = prepareFixture('container');
|
||||||
|
const systemLogDir = path.join(testRoot, 'data/syslog');
|
||||||
|
const currentDate = spawnSync('date', ['+%F'], { encoding: 'utf8' }).stdout.trim();
|
||||||
|
const currentLog = path.join(systemLogDir, `${currentDate}.log`);
|
||||||
|
fs.mkdirSync(systemLogDir, { recursive: true });
|
||||||
|
fs.writeFileSync(currentLog, 'system-one\n');
|
||||||
|
writeExecutable(
|
||||||
|
path.join(binDir, 'tail'),
|
||||||
|
'#!/usr/bin/env bash\nprintf "tail"\nprintf " <%s>" "$@"\nprintf "\\n"\n',
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = spawnSync(
|
||||||
|
'/bin/bash',
|
||||||
|
[path.join(shellDir, 'update.sh'), 'log', '--lines', '0'],
|
||||||
|
{
|
||||||
|
encoding: 'utf8',
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PATH: `${binDir}:${process.env.PATH}`,
|
||||||
|
QL_CONTAINER: 'true',
|
||||||
|
QL_DIR: testRoot,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(result.status, 0, result.stderr);
|
||||||
|
assert.equal(result.stdout, `tail <-n> <0> <--retry> <-F> <${currentLog}>\n`);
|
||||||
|
assert.equal(fs.existsSync(path.join(testRoot, 'data/log/log')), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ql log reports when no persisted system log exists', () => {
|
||||||
|
const { binDir, shellDir, testRoot } = prepareFixture('container-nostream');
|
||||||
|
|
||||||
|
const result = spawnSync(
|
||||||
|
'/bin/bash',
|
||||||
|
[path.join(shellDir, 'update.sh'), 'log', '--nostream'],
|
||||||
|
{
|
||||||
|
encoding: 'utf8',
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PATH: `${binDir}:${process.env.PATH}`,
|
||||||
|
QL_CONTAINER: 'true',
|
||||||
|
QL_DIR: testRoot,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(result.status, 0);
|
||||||
|
assert.equal(result.stdout, '');
|
||||||
|
assert.match(result.stderr, /no system log found/);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user