356 lines
12 KiB
Java
356 lines
12 KiB
Java
|
|
package com.common.util;
|
|||
|
|
|
|||
|
|
import java.time.LocalDateTime;
|
|||
|
|
import java.time.OffsetDateTime;
|
|||
|
|
import java.time.ZoneOffset;
|
|||
|
|
import java.time.format.DateTimeFormatter;
|
|||
|
|
import java.time.format.DateTimeParseException;
|
|||
|
|
import java.util.HashMap;
|
|||
|
|
import java.util.Map;
|
|||
|
|
import java.util.regex.Matcher;
|
|||
|
|
import java.util.regex.Pattern;
|
|||
|
|
import com.common.entity.SyslogMessage;
|
|||
|
|
import com.common.entity.RFC3164Message;
|
|||
|
|
import com.common.entity.RFC5424Message;
|
|||
|
|
import com.influx.SyslogToInfluxApp;
|
|||
|
|
import org.slf4j.Logger;
|
|||
|
|
import org.slf4j.LoggerFactory;
|
|||
|
|
import java.util.*;
|
|||
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
|||
|
|
import java.util.Locale;
|
|||
|
|
|
|||
|
|
public class SyslogParser {
|
|||
|
|
private static final Logger logger = LoggerFactory.getLogger(SyslogToInfluxApp.class);
|
|||
|
|
// RFC 5424 正则表达式
|
|||
|
|
private static final Pattern RFC5424_PATTERN = Pattern.compile(
|
|||
|
|
"^<(?<pri>\\d{1,3})>(?<version>\\d+) " +
|
|||
|
|
"(?<timestamp>\\S+) " +
|
|||
|
|
"(?<host>\\S+) " +
|
|||
|
|
"(?<app>\\S+) " +
|
|||
|
|
"(?<pid>\\S+) " +
|
|||
|
|
"(?<msgid>\\S+) " +
|
|||
|
|
"(?<sd>(?:-|\\[.*\\]))" +
|
|||
|
|
"(?: (?<message>.+))?$"
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// RFC 3164 正则表达式
|
|||
|
|
private static final Pattern RFC3164_PATTERN = Pattern.compile(
|
|||
|
|
"^<(?<pri>\\d{1,3})>" +
|
|||
|
|
"(?<timestamp>[A-Z][a-z]{2}\\s+\\d{1,2}\\s+\\d{2}:\\d{2}:\\d{2}) " +
|
|||
|
|
"(?<host>\\S+) " +
|
|||
|
|
"(?<content>.+)$"
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// RFC 3164 时间戳格式
|
|||
|
|
private static final DateTimeFormatter RFC3164_FORMATTER =
|
|||
|
|
DateTimeFormatter.ofPattern("MMM dd HH:mm:ss");
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析 Syslog 消息,自动检测格式
|
|||
|
|
*/
|
|||
|
|
public static SyslogMessage parse(String logLine) {
|
|||
|
|
if (logLine == null || logLine.trim().isEmpty()) {
|
|||
|
|
throw new IllegalArgumentException("Log line cannot be null or empty");
|
|||
|
|
}
|
|||
|
|
// 尝试解析为 RFC 5424 格式
|
|||
|
|
try {
|
|||
|
|
RFC5424Message msg5424 = parseRFC5424(logLine);
|
|||
|
|
if (msg5424 != null) {
|
|||
|
|
return msg5424;
|
|||
|
|
}
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
// 继续尝试 RFC 3164
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 尝试解析为 RFC 3164 格式
|
|||
|
|
try {
|
|||
|
|
RFC3164Message msg3164 = parseRFC3164(logLine);
|
|||
|
|
if (msg3164 != null) {
|
|||
|
|
return msg3164;
|
|||
|
|
}
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
// 解析失败
|
|||
|
|
}
|
|||
|
|
// 解析非标数据格式,提取字符【{】自后的内容
|
|||
|
|
try {
|
|||
|
|
String content = extractJsonStringNew(logLine);
|
|||
|
|
RFC5424Message msgClass =new RFC5424Message();
|
|||
|
|
if (content != null) {
|
|||
|
|
msgClass.setMessage(content.toString());
|
|||
|
|
return msgClass;
|
|||
|
|
}
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
// 解析失败
|
|||
|
|
}
|
|||
|
|
throw new IllegalArgumentException("Unable to parse syslog message: " + logLine);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析 RFC 5424 格式
|
|||
|
|
*/
|
|||
|
|
public static RFC5424Message parseRFC5424(String logLine) {
|
|||
|
|
Matcher matcher = RFC5424_PATTERN.matcher(logLine);
|
|||
|
|
if (!matcher.find()) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
RFC5424Message message = new RFC5424Message();
|
|||
|
|
// 解析优先级
|
|||
|
|
int pri = Integer.parseInt(matcher.group("pri"));
|
|||
|
|
message.setPriority(pri);
|
|||
|
|
|
|||
|
|
// 解析版本
|
|||
|
|
message.setVersion(Integer.parseInt(matcher.group("version")));
|
|||
|
|
|
|||
|
|
// 解析时间戳 (RFC 3339 格式)
|
|||
|
|
String timestampStr = matcher.group("timestamp");
|
|||
|
|
try {
|
|||
|
|
message.setTimestamp(OffsetDateTime.parse(timestampStr));
|
|||
|
|
} catch (DateTimeParseException e) {
|
|||
|
|
throw new IllegalArgumentException("Invalid RFC5424 timestamp: " + timestampStr, e);
|
|||
|
|
}
|
|||
|
|
// 解析其他字段
|
|||
|
|
message.setHostname(matcher.group("host"));
|
|||
|
|
message.setAppName(matcher.group("app"));
|
|||
|
|
message.setProcId(matcher.group("pid"));
|
|||
|
|
message.setMsgId(matcher.group("msgid"));
|
|||
|
|
|
|||
|
|
// 解析结构化数据
|
|||
|
|
String sd = matcher.group("sd");
|
|||
|
|
if (!"-".equals(sd)) {
|
|||
|
|
message.setStructuredData(parseStructuredData(sd));
|
|||
|
|
}
|
|||
|
|
// 解析消息内容
|
|||
|
|
String msg = matcher.group("message");
|
|||
|
|
if (msg != null) {
|
|||
|
|
message.setMessage(msg.trim());
|
|||
|
|
}
|
|||
|
|
return message;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析 RFC 3164 格式
|
|||
|
|
*/
|
|||
|
|
public static RFC3164Message parseRFC3164(String logLine) {
|
|||
|
|
Matcher matcher = RFC3164_PATTERN.matcher(logLine);
|
|||
|
|
if (!matcher.find()) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
RFC3164Message message = new RFC3164Message();
|
|||
|
|
|
|||
|
|
// 解析优先级
|
|||
|
|
int pri = Integer.parseInt(matcher.group("pri"));
|
|||
|
|
message.setPriority(pri);
|
|||
|
|
|
|||
|
|
// 解析时间戳 (需要特殊处理,因为没有年份)
|
|||
|
|
String timestampStr = matcher.group("timestamp");
|
|||
|
|
message.setTimestamp(parseRFC3164Timestamp(timestampStr));
|
|||
|
|
|
|||
|
|
// 解析主机名
|
|||
|
|
message.setHostname(matcher.group("host"));
|
|||
|
|
|
|||
|
|
// 解析内容和标签
|
|||
|
|
String content = matcher.group("content");
|
|||
|
|
parseRFC3164Content(content, message);
|
|||
|
|
|
|||
|
|
return message;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析 RFC 3164 时间戳
|
|||
|
|
*/
|
|||
|
|
private static OffsetDateTime parseRFC3164Timestamp(String timestampStr) {
|
|||
|
|
try {
|
|||
|
|
// 添加当前年份,因为 RFC3164 时间戳不包含年份
|
|||
|
|
String currentYear = String.valueOf(java.time.Year.now().getValue());
|
|||
|
|
String fullTimestamp = currentYear + " " + timestampStr;
|
|||
|
|
|
|||
|
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MMM dd HH:mm:ss", Locale.US);
|
|||
|
|
LocalDateTime localDateTime = LocalDateTime.parse(fullTimestamp, formatter);
|
|||
|
|
// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MMM dd HH:mm:ss", Locale.US);
|
|||
|
|
|
|||
|
|
// 假设为系统默认时区
|
|||
|
|
return localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(localDateTime));
|
|||
|
|
} catch (DateTimeParseException e) {
|
|||
|
|
throw new IllegalArgumentException("Invalid RFC3164 timestamp: " + timestampStr, e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析 RFC 3164 内容部分
|
|||
|
|
*/
|
|||
|
|
private static void parseRFC3164Content(String content, RFC3164Message message) {
|
|||
|
|
// 尝试匹配标签格式: app[pid]:
|
|||
|
|
Pattern tagPattern = Pattern.compile("^(?<tag>\\S+?)(?:\\[(?<pid>\\d+)\\])?:\\s*(?<message>.*)$");
|
|||
|
|
Matcher tagMatcher = tagPattern.matcher(content);
|
|||
|
|
|
|||
|
|
if (tagMatcher.find()) {
|
|||
|
|
message.setTag(tagMatcher.group("tag"));
|
|||
|
|
String pid = tagMatcher.group("pid");
|
|||
|
|
if (pid != null) {
|
|||
|
|
message.setProcId(pid);
|
|||
|
|
}
|
|||
|
|
message.setMessage(tagMatcher.group("message").trim());
|
|||
|
|
} else {
|
|||
|
|
// 如果没有标签格式,整个内容作为消息
|
|||
|
|
message.setTag("");
|
|||
|
|
message.setMessage(content.trim());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 对于 RFC3164,appName 通常与 tag 相同
|
|||
|
|
message.setAppName(message.getTag());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析结构化数据
|
|||
|
|
*/
|
|||
|
|
private static Map<String, Map<String, String>> parseStructuredData(String sd) {
|
|||
|
|
Map<String, Map<String, String>> structuredData = new HashMap<>();
|
|||
|
|
|
|||
|
|
// 简单的结构化数据解析,实际应用可能需要更复杂的解析
|
|||
|
|
Pattern sdPattern = Pattern.compile("\\[(?<id>[^\\]]+)\\]");
|
|||
|
|
Pattern paramPattern = Pattern.compile("(?<key>[^=\\s]+)=\"(?<value>[^\"]*)\"");
|
|||
|
|
|
|||
|
|
Matcher sdMatcher = sdPattern.matcher(sd);
|
|||
|
|
while (sdMatcher.find()) {
|
|||
|
|
String sdElement = sdMatcher.group("id");
|
|||
|
|
Map<String, String> params = new HashMap<>();
|
|||
|
|
|
|||
|
|
Matcher paramMatcher = paramPattern.matcher(sdElement);
|
|||
|
|
while (paramMatcher.find()) {
|
|||
|
|
params.put(paramMatcher.group("key"), paramMatcher.group("value"));
|
|||
|
|
}
|
|||
|
|
// 使用第一个参数作为键,或者生成一个键
|
|||
|
|
String key = params.containsKey("sdId") ? params.get("sdId") :
|
|||
|
|
"element" + structuredData.size();
|
|||
|
|
structuredData.put(key, params);
|
|||
|
|
}
|
|||
|
|
return structuredData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 提取 JSON 字符串(不解析)
|
|||
|
|
*/
|
|||
|
|
public static Optional<String> extractJsonString(String syslogMessage) {
|
|||
|
|
try {
|
|||
|
|
if (syslogMessage == null) return Optional.empty();
|
|||
|
|
|
|||
|
|
int jsonStart = syslogMessage.indexOf('{');
|
|||
|
|
if (jsonStart == -1) return Optional.empty();
|
|||
|
|
return Optional.of(syslogMessage.substring(jsonStart));
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
return Optional.empty();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 提取 JSON 字符串(不解析)
|
|||
|
|
*/
|
|||
|
|
public static String extractJsonStringNew (String syslogMessage) {
|
|||
|
|
try {
|
|||
|
|
if (syslogMessage == null) return "";
|
|||
|
|
|
|||
|
|
int jsonStart = syslogMessage.indexOf('{');
|
|||
|
|
if (jsonStart == -1) return "";
|
|||
|
|
|
|||
|
|
return syslogMessage.substring(jsonStart);
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
return "";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 截取字符串中第一个 ']' 之后的所有内容
|
|||
|
|
*
|
|||
|
|
* @param text 原始字符串
|
|||
|
|
* @return 第一个 ']' 之后的内容,如果没有 ']' 则返回空字符串
|
|||
|
|
*/
|
|||
|
|
public static String substringAfterFirstCloseBracket(String text) {
|
|||
|
|
if (text == null) {
|
|||
|
|
return "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int closeBracketIndex = text.indexOf(']');
|
|||
|
|
|
|||
|
|
if (closeBracketIndex == -1) {
|
|||
|
|
return ""; // 没有找到 ']',返回空字符串
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 返回 ']' 之后的内容(+1 是为了跳过 ']' 本身)
|
|||
|
|
return text.substring(closeBracketIndex + 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 截取字符串中第一个指定字符之前的所有内容
|
|||
|
|
*
|
|||
|
|
* @param text 原始字符串
|
|||
|
|
* @param delimiterChar 分隔字符
|
|||
|
|
* @return 分隔字符之前的内容
|
|||
|
|
*/
|
|||
|
|
public static String substringBeforeFirstChar(String text, char delimiterChar) {
|
|||
|
|
if (text == null) {
|
|||
|
|
return "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int delimiterIndex = text.indexOf(delimiterChar);
|
|||
|
|
|
|||
|
|
if (delimiterIndex == -1) {
|
|||
|
|
return text;
|
|||
|
|
}
|
|||
|
|
return text.substring(0, delimiterIndex+1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从文本中提取第一个 [] 包裹的键值对字符串并转换为 Map
|
|||
|
|
*
|
|||
|
|
* @param text 包含键值对的原始文本
|
|||
|
|
* @return 包含键值对的 Map
|
|||
|
|
* @throws IllegalArgumentException 如果格式不正确
|
|||
|
|
*/
|
|||
|
|
public static Map<String, String> parseKeyValuePairs(String text) {
|
|||
|
|
if (text == null || text.trim().isEmpty()) {
|
|||
|
|
throw new IllegalArgumentException("Input text cannot be null or empty");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查找第一个 [ 和对应的 ]
|
|||
|
|
int start = text.indexOf('[');
|
|||
|
|
int end = text.indexOf(']', start + 1);
|
|||
|
|
|
|||
|
|
if (start == -1 || end == -1) {
|
|||
|
|
throw new IllegalArgumentException("No valid [] pair found in text");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提取 [] 内的内容
|
|||
|
|
String content = text.substring(start + 1, end).trim();
|
|||
|
|
if (content.isEmpty()) {
|
|||
|
|
return new HashMap<>(); // 返回空Map
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 分割键值对
|
|||
|
|
String[] pairs = content.split("\\s+");
|
|||
|
|
Map<String, String> result = new HashMap<>();
|
|||
|
|
|
|||
|
|
for (String pair : pairs) {
|
|||
|
|
// 分割键和值
|
|||
|
|
int equalSign = pair.indexOf('=');
|
|||
|
|
if (equalSign == -1) {
|
|||
|
|
throw new IllegalArgumentException("Invalid key-value pair format: " + pair);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String key = pair.substring(0, equalSign).trim();
|
|||
|
|
String value = pair.substring(equalSign + 1).trim();
|
|||
|
|
|
|||
|
|
if (key.isEmpty()) {
|
|||
|
|
throw new IllegalArgumentException("Key cannot be empty in pair: " + pair);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result.put(key, value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|