在有些场景下,需要解析Log4J的日志,以为己用。比如,根据关键字查询日志,用以定位问题等。就查询日志这个问题而论,通常的做法是登陆线上机器,grep一把日志目录,得到相关信息。这种做法有两个不好或者说不便捷的问题:首先要登陆线上机器,二来就是若有多台服务器,必须人肉的挨个儿查找,比较费时费力。
下面给出的解决方法是,分析log4j的配置文件,得到所有的Appender,然后再根据Appender中的日志路径、ConversionPattern等信息,去解析相应的日志文件,继而得到我们想要的信息,如Level,Logger、Message等信息。
闲话上说,直接上代码啦。
public final class ConversionRule {
private boolean followedByQuotedString;
private int beginIndex;
private int length;
private int minWidth = -1;
private int maxWidth = -1;
private String placeholderName;
private String modifier;
private Map<String, Object> properties = new HashMap<String, Object>();
public boolean isFollowedByQuotedString() {
return followedByQuotedString;
}
public void setFollowedByQuotedString(boolean followedByQuotedString) {
this.followedByQuotedString = followedByQuotedString;
}
public int getBeginIndex() {
return beginIndex;
}
public void setBeginIndex(int beginIndex) {
this.beginIndex = beginIndex;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getMinWidth() {
return minWidth;
}
public void setMinWidth(int minWidth) {
this.minWidth = minWidth;
}
public int getMaxWidth() {
return maxWidth;
}
public void setMaxWidth(int maxWidth) {
this.maxWidth = maxWidth;
}
public String getPlaceholderName() {
return placeholderName;
}
public void setPlaceholderName(String placeholderName) {
this.placeholderName = placeholderName;
}
public String getModifier() {
return modifier;
}
public void setModifier(String modifier) {
this.modifier = modifier;
}
public void putProperty(String key, Object value) {
properties.put(key, value);
}
@SuppressWarnings("unchecked")
public <T> T getProperty(String key, Class<T> clazz) {
return (T) properties.get(key);
}
@Override
public String toString() {
return "ConversionRule [modifier=" + modifier + ", placeholderName=" + placeholderName + "]";
}
public class ConversionRuleParser {
private static final Pattern EXTRACTION_PATTERN = Pattern.compile("%(-?(\\d+))?(\\.(\\d+))?([a-zA-Z])(\\{([^\\}]+)\\})?");
public static final String PROP_DATEFORMAT = "dateFormat";
protected Pattern getInternalPattern(String externalPattern) throws Exception {
List<ConversionRule> rules = extractRules(externalPattern);
return Pattern.compile(toRegexPattern(prepare(externalPattern), rules));
}
protected List<ConversionRule> extractRules(String externalPattern) throws Exception {
externalPattern = prepare(externalPattern);
Matcher m = EXTRACTION_PATTERN.matcher(externalPattern);
List<ConversionRule> ret = new ArrayList<ConversionRule>();
while (m.find()) {
String minWidthModifier = m.group(2);
String maxWidthModifier = m.group(4);
String conversionName = m.group(5);
String conversionModifier = m.group(7);
int minWidth = -1;
if ((minWidthModifier != null) && (minWidthModifier.length() > 0)) {
minWidth = Integer.parseInt(minWidthModifier);
}
int maxWidth = -1;
if ((maxWidthModifier != null) && (maxWidthModifier.length() > 0)) {
maxWidth = Integer.parseInt(maxWidthModifier);
}
ConversionRule rule = new ConversionRule();
rule.setBeginIndex(m.start());
rule.setLength(m.end() - m.start());
rule.setMaxWidth(maxWidth);
rule.setMinWidth(minWidth);
rule.setPlaceholderName(conversionName);
rule.setModifier(conversionModifier);
rewrite(rule);
ret.add(rule);
}
return ret;
}
public String prepare(String externalPattern) throws Exception {
if (!externalPattern.endsWith("%n")) {
return externalPattern;
}
// Pattern without %n
externalPattern = externalPattern.substring(0, externalPattern.length() - 2);
if (externalPattern.contains("%n")) {
throw new Exception("ConversionPattern不合法!");
}
return externalPattern;
}
private void rewrite(ConversionRule rule) throws Exception {
if (rule.getPlaceholderName().equals("d")) {
applyDefaults(rule);
if (rule.getModifier().equals("ABSOLUTE")) {
rule.setModifier("HH:mm:ss,SSS");
} else if (rule.getModifier().equals("DATE")) {
rule.setModifier("dd MMM yyyy HH:mm:ss,SSS");
} else if (rule.getModifier().equals("ISO8601")) {
rule.setModifier("yyyy-MM-dd HH:mm:ss,SSS");
}
try {
// Cache date format
rule.putProperty(PROP_DATEFORMAT, new SimpleDateFormat(rule.getModifier()));
} catch (IllegalArgumentException e) {
throw new Exception(e);
}
}
}
private void applyDefaults(ConversionRule rule) throws Exception {
if (rule.getModifier() == null) {
// ISO8601 is the default
rule.setModifier("ISO8601");
}
}
private String getRegexPatternForRule(ConversionRule rule) throws Exception {
if (rule.getPlaceholderName().equals("d")) {
// Pattern is dynamic
return "(" + RegexUtils.getRegexForSimpleDateFormat(rule.getModifier()) + ")";
} else if (rule.getPlaceholderName().equals("p")) {
String lnHint = RegexUtils.getLengthHint(rule);
if (lnHint.length() > 0) {
return "([ A-Z]" + lnHint + ")";
}
// Default: Length is limited by the levels available
return "([A-Z]{4,5})";
} else if (rule.getPlaceholderName().equals("c")) {
return "(.*" + RegexUtils.getLengthHint(rule) + RegexUtils.getLazySuffix(rule) + ")";
} else if (rule.getPlaceholderName().equals("t")) {
return "(.*" + RegexUtils.getLengthHint(rule) + RegexUtils.getLazySuffix(rule) + ")";
} else if (rule.getPlaceholderName().equals("m")) {
return "(.*" + RegexUtils.getLengthHint(rule) + RegexUtils.getLazySuffix(rule) + ")";
} else if (rule.getPlaceholderName().equals("F")) {
return "(.*" + RegexUtils.getLengthHint(rule) + RegexUtils.getLazySuffix(rule) + ")";
} else if (rule.getPlaceholderName().equals("C")) {
return "(.*" + RegexUtils.getLengthHint(rule) + RegexUtils.getLazySuffix(rule) + ")";
} else if (rule.getPlaceholderName().equals("M")) {
return "(.*" + RegexUtils.getLengthHint(rule) + RegexUtils.getLazySuffix(rule) + ")";
} else if (rule.getPlaceholderName().equals("L")) {
return "([0-9]*" + RegexUtils.getLengthHint(rule) + ")";
} else if (rule.getPlaceholderName().equals("x")) {
return "(.*" + RegexUtils.getLengthHint(rule) + RegexUtils.getLazySuffix(rule) + ")";
}
throw new Exception("无法找到对应的表达式描述!");
}
protected String toRegexPattern(String externalPattern, List<ConversionRule> rules) throws Exception {
// Determine whether rules are followed by quoted string, allowing use of special Regex lazy modifiers
int idx = 0;
ConversionRule prevRule = null;
for (ConversionRule rule : rules) {
if ((rule.getBeginIndex() > idx) && (prevRule != null)) {
// Previous rule is followed by a quoted string, allowing special regex flags
prevRule.setFollowedByQuotedString(true);
}
idx = rule.getBeginIndex();
idx += rule.getLength();
prevRule = rule;
}
if ((externalPattern.length() > idx) && (prevRule != null)) {
// Previous rule is followed by a quoted string, allowing special regex flags
prevRule.setFollowedByQuotedString(true);
}
// Build the internal Regex pattern
StringBuilder sb = new StringBuilder();
idx = 0;
for (ConversionRule rule : rules) {
if (rule.getBeginIndex() > idx) {
// Escape chars with special meaning
sb.append(Pattern.quote(externalPattern.substring(idx, rule.getBeginIndex())));
}
idx = rule.getBeginIndex();
String regex = this.getRegexPatternForRule(rule);
sb.append(regex);
idx += rule.getLength();
}
if (externalPattern.length() > idx) {
// Append suffix
sb.append(Pattern.quote(externalPattern.substring(idx)));
}
return sb.toString();
}
}
public class ConversionPatternParser {
private Map<String, Appender> appenderBag;
public static String CONVERSION_JUST_4TEST = "%d [] %-5p %c{2} - %m%n";
public ConversionPatternParser() {}
public void parseConfiguration(String configFilePath) throws Exception {
AppenderParser config = new AppenderParser();
config.parse(configFilePath);
setAppenderBag(config.getAppenderBag());
}
public String getConversionPattern(Appender appender) {
Layout layout = appender.getLayout();
if (layout instanceof PatternLayout) {
PatternLayout patternLayout = (PatternLayout) layout;
return patternLayout.getConversionPattern();
}
return null;
}
public void setAppenderBag(Map<String, Appender> appenderBag) {
this.appenderBag = appenderBag;
}
public Map<String, Appender> getAppenderBag() {
return appenderBag;
}
}
/**
* @author Philipp Nanz
*/
public class RegexUtils {
private static transient Logger logger = LoggerFactory.getLogger(RegexUtils.class);
/**
* Returns the Regex lazy suffix for the given rule.
* @param rule the conversion rule
* @return the Regex lazy suffix
*/
public static String getLazySuffix(ConversionRule rule) {
if (rule.isFollowedByQuotedString()) {
return "?";
} else {
return "";
}
}
/**
* Returns the Regex length hint for the given rule.
* @param rule the conversion rule
* @return the Regex length hint
*/
public static String getLengthHint(ConversionRule rule) {
if ((rule.getMaxWidth() > 0) && (rule.getMaxWidth() == rule.getMinWidth())) {
// Exact length specified
return "{" + rule.getMaxWidth() + "}";
} else if (rule.getMaxWidth() > 0) {
// Both min and max are specified
return "{" + Math.max(0, rule.getMinWidth()) + "," + rule.getMaxWidth() + "}"; //$NON-NLS-3$
} else if (rule.getMinWidth() > 0) {
// Only min is specified
return "{" + rule.getMinWidth() + ",}";
}
return "";
}
/**
* Converts a given <code>java.lang.SimpleDateFormat</code> pattern into
* a regular expression
* @param format the pattern
* @return the translated pattern
* @throws Exception if an error occurred
*/
public static String getRegexForSimpleDateFormat(String format) throws Exception {
RegexUtils utils = new RegexUtils();
return utils.doGetRegexForSimpleDateFormat(format);
}
private String doGetRegexForSimpleDateFormat(String format) throws Exception {
try {
new SimpleDateFormat(format);
} catch (Exception e) {
// Pattern is invalid
throw new Exception(e);
}
// Initialize
ReplacementContext ctx = new ReplacementContext();
ctx.setBits(new BitSet(format.length()));
ctx.setBuffer(new StringBuffer(format));
// Unquote
unquote(ctx);
// G - Era designator
replace(ctx, "G+", "[ADBC]{2}");
// y - Year
replace(ctx, "[y]{3,}", "\\d{4}");
replace(ctx, "[y]{2}", "\\d{2}");
replace(ctx, "y", "\\d{4}");
// M - Month in year
replace(ctx, "[M]{3,}", "[a-zA-Z]*");
replace(ctx, "[M]{2}", "\\d{2}");
replace(ctx, "M", "\\d{1,2}");
// w - Week in year
replace(ctx, "w+", "\\d{1,2}");
// W - Week in month
replace(ctx, "W+", "\\d");
// D - Day in year
replace(ctx, "D+", "\\d{1,3}");
// d - Day in month
replace(ctx, "d+", "\\d{1,2}");
// F - Day of week in month
replace(ctx, "F+", "\\d");
// E - Day in week
replace(ctx, "E+", "[a-zA-Z]*");
// a - Am/pm marker
replace(ctx, "a+", "[AMPM]{2}");
// H - Hour in day (0-23)
replace(ctx, "H+", "\\d{1,2}");
// k - Hour in day (1-24)
replace(ctx, "k+", "\\d{1,2}");
// K - Hour in am/pm (0-11)
replace(ctx, "K+", "\\d{1,2}");
// h - Hour in am/pm (1-12)
replace(ctx, "h+", "\\d{1,2}");
// m - Minute in hour
replace(ctx, "m+", "\\d{1,2}");
// s - Second in minute
replace(ctx, "s+", "\\d{1,2}");
// S - Millisecond
replace(ctx, "S+", "\\d{1,3}");
// z - Time zone
replace(ctx, "z+", "[a-zA-Z-+:0-9]*");
// Z - Time zone
replace(ctx, "Z+", "[-+]\\d{4}");
return ctx.getBuffer().toString();
}
private void unquote(ReplacementContext ctx) {
Pattern p = Pattern.compile("'[^']+'");
Matcher m = p.matcher(ctx.getBuffer().toString());
while (m.find()) {
logger.trace(ctx.toString());
// Match is valid
int offset = -2;
// Copy all bits after the match
for (int i = m.end(); i < ctx.getBuffer().length(); i++) {
ctx.getBits().set(i + offset, ctx.getBits().get(i));
}
for (int i = m.start(); i < m.end() + offset; i++) {
ctx.getBits().set(i);
}
ctx.getBuffer().replace(m.start(), m.start() + 1, "");
ctx.getBuffer().replace(m.end() - 2, m.end() - 1, "");
logger.trace(ctx.toString());
}
p = Pattern.compile("''");
m = p.matcher(ctx.getBuffer().toString());
while (m.find()) {
logger.trace(ctx.toString());
// Match is valid
int offset = -1;
// Copy all bits after the match
for (int i = m.end(); i < ctx.getBuffer().length(); i++) {
ctx.getBits().set(i + offset, ctx.getBits().get(i));
}
for (int i = m.start(); i < m.end() + offset; i++) {
ctx.getBits().set(i);
}
ctx.getBuffer().replace(m.start(), m.start() + 1, "");
logger.trace(ctx.toString());
}
}
private void replace(ReplacementContext ctx, String regex, String replacement) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(ctx.getBuffer().toString());
while (m.find()) {
logger.trace(regex);
logger.trace(ctx.toString());
int idx = ctx.getBits().nextSetBit(m.start());
if ((idx == -1) || (idx > m.end() - 1)) {
// Match is valid
int len = m.end() - m.start();
int offset = replacement.length() - len;
if (offset > 0) {
// Copy all bits after the match, in reverse order
for (int i = ctx.getBuffer().length() - 1; i > m.end(); i--) {
ctx.getBits().set(i + offset, ctx.getBits().get(i));
}
} else if (offset < 0) {
// Copy all bits after the match
for (int i = m.end(); i < ctx.getBuffer().length(); i++) {
ctx.getBits().set(i + offset, ctx.getBits().get(i));
}
}
for (int i = m.start(); i < m.end() + offset; i++) {
ctx.getBits().set(i);
}
ctx.getBuffer().replace(m.start(), m.end(), replacement);
logger.trace(ctx.toString());
}
}
}
private class ReplacementContext {
private BitSet bits;
private StringBuffer buffer;
/**
* @return the bits
*/
public BitSet getBits() {
return bits;
}
/**
* @param bits the bits to set
*/
public void setBits(BitSet bits) {
this.bits = bits;
}
/**
* @return the buffer
*/
public StringBuffer getBuffer() {
return buffer;
}
/**
* @param buffer the buffer to set
*/
public void setBuffer(StringBuffer buffer) {
this.buffer = buffer;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("ReplacementContext [bits=");
for (int i = 0; i < buffer.length(); i++) {
sb.append(bits.get(i) ? '1' : '0');
}
sb.append(", buffer=");
sb.append(buffer);
sb.append(']');
return sb.toString();
}
}
}
public class LogEntry {
private Map<String, Object> map = new HashMap<String, Object>();
public final <VT> void put(String key, VT value) {
map.put(key, value);
}
@SuppressWarnings("unchecked")
public final <VT> VT get(String key) {
return (VT) map.get(key);
}
public final <VT> boolean contains(String key) {
return map.containsKey(key);
}
public String toString() {
return map.toString();
}
}
public class LogFileParser {
public static final String FIELD_LOGGER = "logger";
public static final String FIELD_TIMESTAMP = "timestamp";
public static final String FIELD_LEVEL = "level";
public static final String FIELD_THREAD = "thread";
public static final String FIELD_MESSAGE = "message";
public static final String FIELD_CURRENT_LINE = "currLine";
public static final String FIELD_IS_HIT = "isHit";
public static final String FIELD_NDC = "ndc";
public static final String FIELD_THROWABLE = "throwable";
public static final String FIELD_LOC_FILENAME = "locFilename";
public static final String FIELD_LOC_CLASS = "locClass";
public static final String FIELD_LOC_METHOD = "locMethod";
public static final String FIELD_LOC_LINE = "locLine";
private static Logger logger = Logger.getLogger(LogFileParser.class);
private ConversionRuleParser conversionRuleParser;
public LogFileParser() {
conversionRuleParser = new ConversionRuleParser();
}
public List<LogEntry> parse(String fileName, String conversionPattern, String content, Integer upperLogNum, Integer lowerLogNum) throws Exception {
List<ConversionRule> extractRules = conversionRuleParser.extractRules(conversionPattern);
FileInputStream fis = new FileInputStream(new File(fileName));
LineIterator iter = IOUtils.lineIterator(fis, "GBK");
try {
List<LogEntry> logLines = iterateLogLines(conversionPattern, extractRules, iter, upperLogNum, lowerLogNum, content);
return logLines;
} finally {
LineIterator.closeQuietly(iter);
}
}
@SuppressWarnings("unchecked")
private List<LogEntry> iterateLogLines(String conversionPattern, List<ConversionRule> extractRules, LineIterator iter, Integer upperLogNum, Integer lowerLogNum, String content) throws Exception {
boolean flag = true;
List<LogEntry> result = new ArrayList<LogEntry>();
BoundedFifoBuffer upperLogEntries = null;
if (upperLogNum != null && upperLogNum > 0)
upperLogEntries = new BoundedFifoBuffer(upperLogNum);
BoundedFifoBuffer lowerLogEntries = null;
if (lowerLogNum != null && lowerLogNum > 0)
lowerLogEntries = new BoundedFifoBuffer(lowerLogNum);
LogEntry unfinishedEntry = null;
LogEntry currentEntry = fetchARecord(iter, conversionPattern, extractRules, unfinishedEntry);
while (currentEntry != null) {
String msg = currentEntry.get(FIELD_MESSAGE);
boolean isHit = msg.contains(content);
if (flag) {
if (isHit) {
//命中
flag = false;
if (upperLogEntries != null) {
result.addAll(upperLogEntries);
upperLogEntries.clear();
}
currentEntry.put(FIELD_IS_HIT, true);
result.add(currentEntry);
} else {
if (upperLogEntries != null) {
if (upperLogEntries.isFull()) {
upperLogEntries.remove();
}
upperLogEntries.add(currentEntry);
}
}
currentEntry = fetchARecord(iter, conversionPattern, extractRules, unfinishedEntry);
continue;
} else {
if (!isHit) {
if (lowerLogNum != 0) {
//未命中
if (lowerLogEntries != null) {
lowerLogEntries.add(currentEntry);
if (lowerLogEntries.isFull()) {
//转移Lower中的记录到LogList中
flag = true;
result.addAll(lowerLogEntries);
lowerLogEntries.clear();
}
}
} else {
flag = true;
}
} else {
if (lowerLogEntries != null) {
result.addAll(lowerLogEntries);
lowerLogEntries.clear();
}
currentEntry.put(FIELD_IS_HIT, true);
result.add(currentEntry);
}
currentEntry = fetchARecord(iter, conversionPattern, extractRules, unfinishedEntry);
continue;
}
}
return result;
}
private long lineNo = 1;
private LogEntry fetchARecord(LineIterator iter, String conversionPattern, List<ConversionRule> extractRules, LogEntry unfinishedEntry) throws Exception {
LogEntry currentEntry = null;
boolean found = true;
if (unfinishedEntry == null) {
found = false;
}
if (!iter.hasNext()) {
return null;
}
while (iter.hasNext()) {
// Error handling
String line = iter.nextLine();
while (StringUtils.isBlank(line) && iter.hasNext()) {
line = iter.nextLine();
}
Matcher m = conversionRuleParser.getInternalPattern(conversionPattern).matcher(line);
if (m.find()) {
//It's next entry, unfinished
if (found) {
currentEntry = unfinishedEntry;
unfinishedEntry = new LogEntry();
for (int i = 0; i < m.groupCount(); i++) {
try {
this.extractField(unfinishedEntry, extractRules.get(i), m.group(i + 1));
} catch (Exception e) {
// Mark for interruption
logger.warn(e);
}
}
currentEntry.put(FIELD_CURRENT_LINE, lineNo++);
return currentEntry;
} else {
unfinishedEntry = new LogEntry();
found = true;
for (int i = 0; i < m.groupCount(); i++) {
try {
this.extractField(unfinishedEntry, extractRules.get(i), m.group(i + 1));
} catch (Exception e) {
// Mark for interruption
logger.warn(e);
}
}
}
} else if (unfinishedEntry != null) {
String msg = unfinishedEntry.get(FIELD_MESSAGE);
msg += '\n' + line;
unfinishedEntry.put(FIELD_MESSAGE, msg);
}
}
if (unfinishedEntry != null) {
currentEntry = unfinishedEntry;
}
if (currentEntry != null)
currentEntry.put(FIELD_CURRENT_LINE, lineNo++);
return currentEntry;
}
private void extractField(LogEntry entry, ConversionRule rule, String val) throws Exception {
if (rule.getPlaceholderName().equals("d")) {
DateFormat df = rule.getProperty(ConversionRuleParser.PROP_DATEFORMAT, DateFormat.class);
entry.put(FIELD_TIMESTAMP, df.parse(val.trim()));
} else if (rule.getPlaceholderName().equals("p")) {
Level lvl = Level.toLevel(val.trim());
entry.put(FIELD_LEVEL, lvl);
} else if (rule.getPlaceholderName().equals("c")) {
entry.put(FIELD_LOGGER, val.trim());
} else if (rule.getPlaceholderName().equals("t")) {
entry.put(FIELD_THREAD, val.trim());
} else if (rule.getPlaceholderName().equals("m")) {
entry.put(FIELD_MESSAGE, val.trim());
} else if (rule.getPlaceholderName().equals("F")) {
entry.put(FIELD_LOC_FILENAME, val.trim());
} else if (rule.getPlaceholderName().equals("C")) {
entry.put(FIELD_LOC_CLASS, val.trim());
} else if (rule.getPlaceholderName().equals("M")) {
entry.put(FIELD_LOC_METHOD, val.trim());
} else if (rule.getPlaceholderName().equals("L")) {
entry.put(FIELD_LOC_LINE, val.trim());
} else if (rule.getPlaceholderName().equals("x")) {
entry.put(FIELD_NDC, val.trim());
} else {
throw new Exception("异常消息暂未设置");
}
}
}
LogFileParser的活动图:
public class AppenderParser extends DOMConfigurator {
private Map<String, Appender> appenderBag = new HashMap<String, Appender>();
private Document doc = null;
private DocumentBuilderFactory dbf;
private DocumentBuilder db = null;
public AppenderParser() {}
public void parse(String configFile) throws Exception {
doc = getDocument(configFile);
NodeList appenderList = doc.getElementsByTagName("appender");
for (int t = 0; t < appenderList.getLength(); t++) {
Node node = appenderList.item(t);
NamedNodeMap map = node.getAttributes();
Node attrNode = map.getNamedItem("name");
if (getAppenderBag().get(attrNode.getNodeValue()) == null) {
Appender appender = parseAppender((Element) node);
getAppenderBag().put(attrNode.getNodeValue(), appender);
}
}
}
private Document getDocument(String configFile) throws ParserConfigurationException, SAXException, IOException {
dbf = DocumentBuilderFactory.newInstance();
db = dbf.newDocumentBuilder();
return db.parse(new File(configFile));
}
public static void main(String[] args) throws Exception {
String configFile = "D:\\log4j.xml";
AppenderParser config = new AppenderParser();
config.parse(configFile);
System.out.println(SerializeUtil.serialize(config.getAppenderBag()));
}
public void setAppenderBag(Map<String, Appender> appenderBag) {
this.appenderBag = appenderBag;
}
public Map<String, Appender> getAppenderBag() {
return appenderBag;
}
}
@Component
public class LoggerSearch {
private AppenderParser appenderParser;
private LogFileParser logParser;
private Map<String, Appender> appenders;
private String configFilePath = "log4j.xml";
List<LogEntry> logEntries;
Map<String, List<LogEntry>> allLogEntries;
@PostConstruct
public void init() throws Exception {
appenderParser = new AppenderParser();
logParser = new LogFileParser();
appenderParser.parse(getConfigFilePath());
appenders = appenderParser.getAppenderBag();
allLogEntries = new HashMap<String, List<LogEntry>>();
}
public Map<String, List<LogEntry>> searchAll(String content, Integer upperLogNum, Integer lowerLogNum) throws Exception {
for (Appender appender : appenders.values()) {
if (appender instanceof FileAppender) {
FileAppender fileAppender = (FileAppender) appender;
if (appender instanceof DailyRollingFileAppender) {
Layout layout = fileAppender.getLayout();
if (layout instanceof PatternLayout) {
PatternLayout patternLayout = (PatternLayout) layout;
String conversionPattern = patternLayout.getConversionPattern();
String fileName = fileAppender.getFile();
logEntries = logParser.parse(fileName, conversionPattern, content, upperLogNum, lowerLogNum);
allLogEntries.put(new File(fileName).getName(), logEntries);
}
}
}
}
return allLogEntries;
}
public void setConfigFilePath(String configFilePath) {
this.configFilePath = configFilePath;
}
public String getConfigFilePath() {
return LoggerSearch.class.getClassLoader().getResource(configFilePath).getFile();
}
}
上述部分代码参考了LogSaw,其中仅实现了简单的功能,如果有需要可自行扩展。
另附上一个JMX查询接口:
@Component
@JmxClass
public class Log4jGrep {
@Resource
private LoggerSearch loggerSearch;
@JmxMethod
public String searchAll(String content, Integer upperLogNum, Integer lowerLogNum) {
try {
StringBuilder sb = new StringBuilder();
Map<String, List<LogEntry>> logEntries = loggerSearch.searchAll(content, upperLogNum, lowerLogNum);
String color = "#cccccc";
for (String logName : logEntries.keySet()) {
List<LogEntry> logs = logEntries.get(logName);
if (logs != null && logs.size() > 0) {
sb.append("<p style=\"\font-weight: bold;color: #FF0000;font-family: Geneva, Arial, Helvetica, sans-serif;font-size: x-large;\">" + logName + "</p>");
sb.append("<table height=\"100\" border=\"1\">");
sb.append("<tr>");
sb.append("<td width=\"45\">行号</td>");
sb.append("<td width=\"55\">等级</td>");
sb.append("<td width=\"88\">类名</td>");
sb.append("<td width=\"763\">信息</td>");
sb.append("</tr>");
for (LogEntry log : logs) {
if (log.get(LogFileParser.FIELD_IS_HIT) != null) {
color = "#cccccc";
} else {
color = "";
}
sb.append("<tr bgcolor=" + color + ">");
sb.append("<td>" + log.get(LogFileParser.FIELD_CURRENT_LINE) + "</td>");
sb.append("<td>" + log.get(LogFileParser.FIELD_LEVEL) + "</td>");
sb.append("<td>" + log.get(LogFileParser.FIELD_LOGGER) + "</td>");
String msg = log.get(LogFileParser.FIELD_MESSAGE);
msg = msg.replaceAll(content, "<span style=\"color: #FFFFFF;background: #FF0000;\">" + content + "</span>");
sb.append("<td style=\"word-wrap:break-word;\">" + msg + "</td>");
sb.append("</tr>");
}
sb.append("<table/>");
}
}
return sb.toString();
} catch (Exception e) {
return ExceptionUtils.getFullStackTrace(e);
}
}
}