本文目录导读:

Shell脚本 - 保留关键堆栈行
#!/bin/bash
# 文件名: stack_filter.sh
# 过滤堆栈,保留特定包路径的行
filter_stack() {
local input_file="${1:-/dev/stdin}"
local packages="${2:-com.example,org.springframework}"
# 将包名转为grep模式
local pattern=$(echo "$packages" | tr ',' '|')
awk -v pattern="$pattern" '
BEGIN {
# 分割设置
FS="\t"
show_line = 0
}
# 保留异常类型和消息
/^[a-zA-Z]+\.[a-zA-Z]+.*Exception/ {
print $0
show_line = 1
next
}
# 保留匹配的关键包堆栈
$0 ~ pattern && /at / {
if (show_line) {
print $0
show_line = 0
} else {
print $0
}
next
}
# 保留"Caused by"行
/^Caused by:/ {
print $0
show_line = 1
next
}
# 保留堆栈结束标记
/^\.\.\. [0-9]+ more/ {
print $0
show_line = 0
}
' "$input_file"
}
# 使用示例
cat << 'EOF' | filter_stack "" "com.example"
java.lang.NullPointerException
at com.example.service.UserService.getUser(UserService.java:25)
at com.example.controller.UserController.findById(UserController.java:12)
at com.example.config.AopConfig.invoke(AopConfig.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
... 23 more
EOF
Python脚本 - 智能堆栈合并
#!/usr/bin/env python3
# 文件名: stack_minifier.py
import sys
import re
from collections import defaultdict
class StackMinifier:
def __init__(self, packages_to_keep=None):
"""
packages_to_keep: 需要保留的包路径列表,如 ['com.example', 'org.springframework']
"""
self.packages_to_keep = packages_to_keep or ['com.example', 'org.springframework']
self.patterns = [re.compile(f'^{pkg}') for pkg in self.packages_to_keep]
def process_stacktrace(self, text):
"""处理堆栈信息"""
lines = text.split('\n')
result = []
i = 0
while i < len(lines):
line = lines[i]
# 保留异常信息行
if re.match(r'^[a-zA-Z]+\.[a-zA-Z]+.*Exception', line):
result.append(line)
i += 1
continue
# 处理at行
if 'at ' in line:
frame_info = self._extract_frame_info(line)
if self._should_keep(frame_info['class']):
result.append(line)
# 检查是否有连续的堆栈行
j = i + 1
skipped = 0
while j < len(lines) and 'at ' in lines[j]:
next_frame = self._extract_frame_info(lines[j])
if self._should_keep(next_frame['class']):
result.append(lines[j])
else:
skipped += 1
j += 1
if skipped > 0:
result.append(f" ... {skipped} more [filtered]")
i = j
continue
# 保留Caused by行
elif line.startswith('Caused by:'):
result.append(line)
i += 1
return '\n'.join(result)
def _extract_frame_info(self, line):
"""提取堆栈帧信息"""
# 匹配格式: at com.example.Class.method(File.java:line)
match = re.search(r'at\s+([\w.]+)\.(\w+)\(([^:]+):(\d+)\)', line)
if match:
return {
'full_class': match.group(1),
'class': match.group(1).rsplit('.', 1)[0] if '.' in match.group(1) else '',
'method': match.group(2),
'file': match.group(3),
'line': match.group(4)
}
return {'full_class': '', 'class': '', 'method': '', 'file': '', 'line': ''}
def _should_keep(self, class_name):
"""判断是否应该保留该堆栈帧"""
for pattern in self.patterns:
if pattern.search(class_name):
return True
return False
def main():
# 从命令行参数读取需要保留的包
import argparse
parser = argparse.ArgumentParser(description='精简Java异常堆栈信息')
parser.add_argument('file', nargs='?', help='输入文件路径,默认从标准输入读取')
parser.add_argument('--packages', '-p',
default='com.example,org.springframework',
help='需要保留的包前缀,逗号分隔')
args = parser.parse_args()
# 读取输入
if args.file:
with open(args.file, 'r') as f:
input_text = f.read()
else:
input_text = sys.stdin.read()
# 处理堆栈
packages = args.packages.split(',')
minifier = StackMinifier(packages)
output = minifier.process_stacktrace(input_text)
print(output)
if __name__ == '__main__':
main()
Java工具 - 运行时精简
import java.util.*;
import java.util.stream.*;
import java.util.function.Predicate;
public class StackTraceSimplifier {
private final Set<String> packagesToKeep;
public StackTraceSimplifier(String... packages) {
this.packagesToKeep = new HashSet<>(Arrays.asList(packages));
}
/**
* 简化异常堆栈
*/
public String simplify(Throwable throwable) {
StringBuilder sb = new StringBuilder();
simplifyThrowable(throwable, sb, 0);
return sb.toString();
}
private void simplifyThrowable(Throwable throwable, StringBuilder sb, int depth) {
if (throwable == null || depth > 3) { // 限制chained exception深度
return;
}
// 添加异常信息
sb.append(throwable.getClass().getName())
.append(": ")
.append(throwable.getMessage())
.append("\n");
// 过滤堆栈
List<StackTraceElement> filteredStack = Arrays.stream(throwable.getStackTrace())
.filter(this::shouldKeep)
.collect(Collectors.toList());
// 获取被过滤的数量
long filteredCount = throwable.getStackTrace().length - filteredStack.size();
// 输出过滤后的堆栈
for (StackTraceElement element : filteredStack) {
sb.append("\tat ")
.append(element.getClassName())
.append(".")
.append(element.getMethodName())
.append("(")
.append(element.getFileName())
.append(":")
.append(element.getLineNumber())
.append(")\n");
}
if (filteredCount > 0) {
sb.append("\t... ").append(filteredCount).append(" more [filtered]\n");
}
// 处理cause
if (throwable.getCause() != null) {
sb.append("Caused by: ");
simplifyThrowable(throwable.getCause(), sb, depth + 1);
}
}
private boolean shouldKeep(StackTraceElement element) {
String className = element.getClassName();
return packagesToKeep.stream().anyMatch(className::startsWith);
}
// 使用示例
public static void main(String[] args) {
try {
// 模拟异常
throw new RuntimeException("Test exception",
new IllegalArgumentException("Root cause"));
} catch (Exception e) {
StackTraceSimplifier simplifier =
new StackTraceSimplifier("com.example", "org.springframework");
System.out.println(simplifier.simplify(e));
}
}
}
Node.js/JavaScript 版本
#!/usr/bin/env node
// 文件名: stack-minifier.js
class StackMinifier {
constructor(options = {}) {
this.modulesToKeep = options.modules || ['app/', 'src/'];
this.maxDepth = options.maxDepth || 20;
}
/**
* 精简Node.js异常堆栈
*/
simplifyError(error) {
if (!error || !error.stack) return '';
const lines = error.stack.split('\n');
const result = [];
// 保留错误消息行
result.push(lines[0]);
// 过滤堆栈行
let currentDepth = 0;
let skippedCount = 0;
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
if (currentDepth >= this.maxDepth) {
skippedCount++;
continue;
}
// 检查是否是内部模块
if (this.shouldKeep(line)) {
result.push(' ' + line);
currentDepth++;
} else {
skippedCount++;
}
}
if (skippedCount > 0) {
result.push(` ... ${skippedCount} frames were omitted`);
}
return result.join('\n');
}
shouldKeep(stackLine) {
// 过滤node_modules和原生模块
if (stackLine.includes('node_modules/')) return false;
if (stackLine.includes('(native)')) return false;
if (stackLine.includes('internal/')) return false;
// 保留指定模块
return this.modulesToKeep.some(module => stackLine.includes(module));
}
}
// 命令行工具
const fs = require('fs');
if (process.argv.length > 2) {
const inputFile = process.argv[2];
const content = fs.readFileSync(inputFile, 'utf-8');
const minifier = new StackMinifier({
modules: process.argv[3]?.split(',') || ['app/', 'src/']
});
// 假设内容中可能包含多个异常堆栈
const stacks = content.split(/(?=\w+Error:)/);
stacks.forEach(stack => {
if (stack.trim()) {
console.log(minifier.simplifyError({ stack: stack.trim() }));
}
});
} else {
// 从stdin读取
const chunks = [];
process.stdin.on('data', chunk => chunks.push(chunk));
process.stdin.on('end', () => {
const content = Buffer.concat(chunks).toString();
const minifier = new StackMinifier();
console.log(minifier.simplifyError({ stack: content }));
});
}
使用建议
- 生产环境日志处理:结合日志收集系统使用
- 开发调试:保留完整堆栈,仅在输出时精简
- 监控告警:只保留关键业务包(如com.example)的堆栈
- 集成到构建工具:作为post-processing步骤
这些脚本可以根据你的具体需求调整过滤规则和输出格式。