新增syslog-collector-api 采集模块,通过api 方式采集天眼、微步的设备日志。
This commit is contained in:
@@ -28,8 +28,7 @@ services:
|
||||
- SPRING_DATASOURCE_USERNAME=SYSDBA
|
||||
- SPRING_DATASOURCE_PASSWORD=caZ2TcmXNSW8L2Ap
|
||||
- SPRING_DATASOURCE_DRIVER_CLASS_NAME=dm.jdbc.driver.DmDriver
|
||||
- SPRING_DATASOURCE_HIKARI_SCHEMA=\"PUBLIC\"
|
||||
|
||||
- SPRING_DATASOURCE_HIKARI_SCHEMA=PUBLIC
|
||||
|
||||
# Redis配置
|
||||
- SPRING_REDIS_HOST=192.168.4.99
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ public class DeviceProcess {
|
||||
if(deviceList.size()>0)
|
||||
{
|
||||
deviceUnknownService.updateLastTime( deviceList.get(0).getId(),currentTime );
|
||||
System.out.println("更新未知设备的最后发现时间,IP:"+this.sourceIP);
|
||||
logger.info("更新未知设备的最后发现时间,IP:"+this.sourceIP);
|
||||
}
|
||||
else {
|
||||
//创建未知设备记录
|
||||
|
||||
@@ -5,6 +5,15 @@ import com.haobang.util.Sm4Util;
|
||||
import org.apache.kafka.clients.producer.*;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
@@ -20,157 +29,238 @@ public class kafkaProducer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(kafkaProducer.class);
|
||||
|
||||
// KafkaProducer 全局单例,线程安全,避免每次 send 重建连接
|
||||
private static volatile Producer<String, String> producer;
|
||||
// 缓存的 topic 名称(初始化时从配置读取一次,避免每次 send 都查 Spring)
|
||||
private static volatile String cachedTopic;
|
||||
// 缓存的 SM4 密钥
|
||||
private static volatile String cachedSm4Key;
|
||||
|
||||
private static final Object lock = new Object();
|
||||
|
||||
// ==================== 兜底备份配置 ====================
|
||||
/** 兜底文件存储根目录 */
|
||||
private static final String FALLBACK_DIR = "/app/syslog-fallback/";
|
||||
/** 兜底文件日期格式 */
|
||||
private static final DateTimeFormatter FALLBACK_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
/** 兜底写入最大重试次数 */
|
||||
private static final int FALLBACK_MAX_RETRIES = 3;
|
||||
/** 兜底写入重试间隔(毫秒) */
|
||||
private static final long FALLBACK_RETRY_INTERVAL_MS = 100;
|
||||
|
||||
static {
|
||||
// 注册 JVM 关闭钩子,确保 Producer 正常 flush + close
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> closeProducer()));
|
||||
}
|
||||
|
||||
private static AppProperties getProps() {
|
||||
return SpringContextUtil.getBean(AppProperties.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局 KafkaProducer 单例(双重检查锁定)
|
||||
*/
|
||||
private static Producer<String, String> getProducer() {
|
||||
if (producer == null) {
|
||||
synchronized (lock) {
|
||||
if (producer == null) {
|
||||
AppProperties appProps = getProps();
|
||||
Properties props = new Properties();
|
||||
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, appProps.getKafkaProducerBootstrap());
|
||||
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
props.put(ProducerConfig.ACKS_CONFIG, "all");
|
||||
props.put(ProducerConfig.RETRIES_CONFIG, 3);
|
||||
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
|
||||
// 生产者缓冲区大小,减少网络 IO 次数
|
||||
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
|
||||
props.put(ProducerConfig.LINGER_MS_CONFIG, 5);
|
||||
|
||||
producer = new KafkaProducer<>(props);
|
||||
cachedTopic = appProps.getKafkaProducerTopic();
|
||||
cachedSm4Key = appProps.getSm4Key();
|
||||
logger.info("KafkaProducer 单例初始化完成, topic={}, brokers={}",
|
||||
cachedTopic, appProps.getKafkaProducerBootstrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
return producer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优雅关闭 KafkaProducer
|
||||
*/
|
||||
public static void closeProducer() {
|
||||
if (producer != null) {
|
||||
synchronized (lock) {
|
||||
if (producer != null) {
|
||||
try {
|
||||
logger.info("正在关闭 KafkaProducer...");
|
||||
producer.flush();
|
||||
producer.close();
|
||||
logger.info("KafkaProducer 已关闭");
|
||||
} catch (Exception e) {
|
||||
logger.error("关闭 KafkaProducer 异常", e);
|
||||
}
|
||||
producer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
AppProperties appProps = getProps();
|
||||
// 配置生产者属性
|
||||
Properties props = new Properties();
|
||||
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, appProps.getKafkaProducerBootstrap());
|
||||
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
|
||||
// 可选配置:提高可靠性
|
||||
props.put(ProducerConfig.ACKS_CONFIG, "all");
|
||||
props.put(ProducerConfig.RETRIES_CONFIG, 3);
|
||||
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
|
||||
|
||||
// 创建生产者实例
|
||||
Producer<String, String> producer = new KafkaProducer<>(props);
|
||||
|
||||
try {
|
||||
// 发送10条消息
|
||||
//for (int i = 0; i < 1; i++) {
|
||||
String key = "key-" + "1";
|
||||
String value = "message-" + "1" + " at " + System.currentTimeMillis();
|
||||
|
||||
// 创建生产者记录
|
||||
ProducerRecord<String, String> record =
|
||||
new ProducerRecord<>(appProps.getKafkaProducerTopic(), key, value);
|
||||
|
||||
// 发送消息(异步方式)
|
||||
producer.send(record, new Callback() {
|
||||
@Override
|
||||
public void onCompletion(RecordMetadata metadata, Exception exception) {
|
||||
if (exception == null) {
|
||||
System.out.println("消息发送成功: " +
|
||||
"主题=" + metadata.topic() +
|
||||
", 分区=" + metadata.partition() +
|
||||
", 偏移量=" + metadata.offset());
|
||||
} else {
|
||||
System.err.println("消息发送失败: " + exception.getMessage());
|
||||
}
|
||||
try (Producer<String, String> producer = new KafkaProducer<>(props)) {
|
||||
String key = "key-" + "1";
|
||||
String value = "message-" + "1" + " at " + System.currentTimeMillis();
|
||||
ProducerRecord<String, String> record =
|
||||
new ProducerRecord<>(appProps.getKafkaProducerTopic(), key, value);
|
||||
producer.send(record, new Callback() {
|
||||
@Override
|
||||
public void onCompletion(RecordMetadata metadata, Exception exception) {
|
||||
if (exception == null) {
|
||||
System.out.println("消息发送成功: " +
|
||||
"主题=" + metadata.topic() +
|
||||
", 分区=" + metadata.partition() +
|
||||
", 偏移量=" + metadata.offset());
|
||||
} else {
|
||||
System.err.println("消息发送失败: " + exception.getMessage());
|
||||
}
|
||||
});
|
||||
// 同步发送方式(如果需要)
|
||||
// RecordMetadata metadata = producer.send(record).get();
|
||||
// System.out.println("同步发送成功: " + metadata.offset());
|
||||
// }
|
||||
}
|
||||
});
|
||||
producer.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
// 关闭生产者
|
||||
producer.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* * syslog 文本消息
|
||||
* * @param strSyslog
|
||||
* syslog 文本消息推送到 Kafka(复用全局单例 Producer)
|
||||
* @param deviceDevice 设备信息
|
||||
* @param strSyslog 原始 syslog 消息
|
||||
* @param strReceiveTime 接收时间
|
||||
*/
|
||||
|
||||
public static void messagePush(DeviceDevice deviceDevice, String strSyslog,String strReceiveTime)
|
||||
{
|
||||
AppProperties appProps = getProps();
|
||||
// 配置生产者属性
|
||||
Properties props = new Properties();
|
||||
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, appProps.getKafkaProducerBootstrap());
|
||||
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
|
||||
// 可选配置:提高可靠性
|
||||
props.put(ProducerConfig.ACKS_CONFIG, "all");
|
||||
props.put(ProducerConfig.RETRIES_CONFIG, 3);
|
||||
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
|
||||
|
||||
// 创建生产者实例
|
||||
Producer<String, String> producer = new KafkaProducer<>(props);
|
||||
public static void messagePush(DeviceDevice deviceDevice, String strSyslog, String strReceiveTime) {
|
||||
Producer<String, String> p = getProducer();
|
||||
try {
|
||||
String key = "key-" + System.currentTimeMillis();
|
||||
String value = DeviceInfoUtil.getFullLogString(deviceDevice, strSyslog, strReceiveTime);
|
||||
|
||||
String key = "key-" + System.currentTimeMillis();
|
||||
//String value = DeviceInfoUtil.getFullLogString(strSyslog);
|
||||
//采用动态获取Syslog 请求的设备信息
|
||||
String value = DeviceInfoUtil.getFullLogString(deviceDevice,strSyslog,strReceiveTime);
|
||||
// SM4 加密
|
||||
String sm4Message = Sm4Util.encryptCbc(value, cachedSm4Key);
|
||||
|
||||
//Message进行SM4加密写入kafka
|
||||
String Sm4message= Sm4Util.encryptCbc(value, appProps.getSm4Key());
|
||||
//System.out.println("Sm4message:"+Sm4message);
|
||||
ProducerRecord<String, String> record =
|
||||
new ProducerRecord<>(cachedTopic, key, sm4Message);
|
||||
|
||||
// 创建生产者记录
|
||||
ProducerRecord<String, String> record =
|
||||
new ProducerRecord<>(appProps.getKafkaProducerTopic(), key, Sm4message);
|
||||
|
||||
// 发送消息(异步方式)
|
||||
producer.send(record, new Callback() {
|
||||
@Override
|
||||
public void onCompletion(RecordMetadata metadata, Exception exception) {
|
||||
if (exception == null) {
|
||||
System.out.println("消息发送成功: " +
|
||||
"主题=" + metadata.topic() +
|
||||
", 分区=" + metadata.partition() +
|
||||
", 偏移量=" + metadata.offset());
|
||||
} else {
|
||||
System.err.println("消息发送失败: " + exception.getMessage());
|
||||
logger.error("消息发送失败: " + exception.getMessage());
|
||||
}
|
||||
// 异步发送 + 失败回调兜底:闭包捕获 value(原始明文),失败时写入本地文件
|
||||
p.send(record, new Callback() {
|
||||
@Override
|
||||
public void onCompletion(RecordMetadata metadata, Exception exception) {
|
||||
if (exception == null) {
|
||||
logger.debug("消息发送成功: 主题={}, 分区={}, 偏移量={}",
|
||||
metadata.topic(), metadata.partition(), metadata.offset());
|
||||
} else {
|
||||
logger.error("消息发送失败, 开始兜底写入本地文件: topic={}, 异常={}",
|
||||
cachedTopic, exception.getMessage());
|
||||
fallbackToLocal(value);
|
||||
}
|
||||
});
|
||||
|
||||
// 同步发送方式(如果需要)
|
||||
// RecordMetadata metadata = producer.send(record).get();
|
||||
// System.out.println("同步发送成功: " + metadata.offset());
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
// 关闭生产者
|
||||
producer.close();
|
||||
// 加密/组包阶段异常,同样兜底
|
||||
logger.error("messagePush 预处理异常, 开始兜底写入本地文件", e);
|
||||
try {
|
||||
String value = DeviceInfoUtil.getFullLogString(deviceDevice, strSyslog, strReceiveTime);
|
||||
fallbackToLocal(value);
|
||||
} catch (Exception ex) {
|
||||
logger.error("兜底写入时组包也失败, 消息已丢失", ex);
|
||||
}
|
||||
}
|
||||
// 注意:不再 close,Producer 是全局单例,由 shutdown hook 关闭
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成json字符串,补充设备配置信息
|
||||
* @param strSyslog
|
||||
* @return
|
||||
*/
|
||||
private static String getLogJsonString(String strSyslog)
|
||||
{
|
||||
private static String getLogJsonString(String strSyslog) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 创建设备配置信息MAP
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
AppProperties appProps = getProps();
|
||||
map.put("device_id", appProps.getAppServiceDeviceId());
|
||||
map.put("device_name", appProps.getAppServiceDeviceName());
|
||||
map.put("vendor", appProps.getAppServiceVendor());
|
||||
map.put("data_type", appProps.getAppServiceDataType());
|
||||
// 添加syslogMessage
|
||||
map.put("syslogMessage", strSyslog);
|
||||
try {
|
||||
//生成json消息字符串
|
||||
String json = objectMapper.writeValueAsString(map);
|
||||
return json;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
logger.error("kafkaProducer getLogJsonString()生成json字符串 异常: syslog: "+ strSyslog);
|
||||
logger.error("Exception: : "+ ex.getMessage());
|
||||
return objectMapper.writeValueAsString(map);
|
||||
} catch (Exception ex) {
|
||||
logger.error("kafkaProducer getLogJsonString() 生成json字符串异常, syslog: {}", strSyslog);
|
||||
logger.error("Exception: {}", ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== 兜底备份 ====================
|
||||
|
||||
/**
|
||||
* 将原始消息写入本地兜底文件(按日期分文件,追加写入)
|
||||
* <p>
|
||||
* 文件路径:/data/syslog-fallback/kafka-failed-yyyyMMdd.log
|
||||
* </p>
|
||||
* @param message 原始明文消息(完整日志字符串)
|
||||
*/
|
||||
private static void fallbackToLocal(String message) {
|
||||
if (message == null || message.isEmpty()) {
|
||||
logger.warn("兜底消息为空,跳过写入");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < FALLBACK_MAX_RETRIES; i++) {
|
||||
try {
|
||||
String dateStr = LocalDate.now().format(FALLBACK_DATE_FORMAT);
|
||||
String fileName = "kafka-failed-" + dateStr + ".log";
|
||||
Path filePath = Paths.get(FALLBACK_DIR, fileName);
|
||||
|
||||
// 确保目录存在
|
||||
File dir = new File(FALLBACK_DIR);
|
||||
if (!dir.exists()) {
|
||||
boolean created = dir.mkdirs();
|
||||
if (!created) {
|
||||
logger.error("创建兜底目录失败: {}", FALLBACK_DIR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 追加写入,每条一行
|
||||
String line = message + "\n";
|
||||
Files.write(filePath, line.getBytes(StandardCharsets.UTF_8),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||
|
||||
logger.debug("消息已兜底写入文件: {}", fileName);
|
||||
return;
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("兜底文件写入失败(第{}次重试): {}", i + 1, e.getMessage());
|
||||
if (i < FALLBACK_MAX_RETRIES - 1) {
|
||||
try {
|
||||
Thread.sleep(FALLBACK_RETRY_INTERVAL_MS);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.error("兜底写入彻底失败,消息丢失!消息长度={}", message.length());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ server.error.include-binding-errors=always
|
||||
# Syslog Server Configuration
|
||||
syslog.tcp.port=514
|
||||
syslog.udp.port=514
|
||||
syslog.max.frame.length=2621440
|
||||
syslog.buffer.size=10000
|
||||
syslog.max.frame.length=262144
|
||||
syslog.buffer.size=1000
|
||||
# syslog 消息字符编码,国内安全设备普遍使用 GBK,如需 UTF-8 改为 syslog.charset=UTF-8
|
||||
syslog.charset=UTF-8
|
||||
syslog.sm4.generateKey=f79548ab6fa8a304fc0115e17230358a
|
||||
@@ -23,7 +23,7 @@ app.service.product_name=diting
|
||||
app.service.data_type=json
|
||||
#采集探针ID
|
||||
app.service.device_collect_id=${DEVICE_COLLECT_ID:1}
|
||||
app.service.version=${APP_SERVICE_VER:V1.0.0-20260527}
|
||||
app.service.version=${APP_SERVICE_VER:V1.0.0-20260811}
|
||||
app.service.device_collect_name=${DEVICE_COLLECT_NAME:DevCollect-01}
|
||||
|
||||
# kafka Configuration
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>logs/syslog-serve-dm.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 每天滚动 -->
|
||||
<fileNamePattern>logs/syslog-serve-dm.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<!-- 保留的日志文件的最大天数 -->
|
||||
<maxHistory>1</maxHistory>
|
||||
<!-- 每小时滚动 -->
|
||||
<fileNamePattern>logs/syslog-serve-dm.%d{yyyy-MM-dd-HH}.log</fileNamePattern>
|
||||
<!-- 保留的日志文件的最大小时数 -->
|
||||
<maxHistory>24</maxHistory>
|
||||
<!-- 所有归档日志文件的总大小上限 -->
|
||||
<totalSizeCap>15GB</totalSizeCap>
|
||||
<!-- 启动时是否清理过期日志 -->
|
||||
|
||||
Reference in New Issue
Block a user