feat(syslog): 增加 WAL 与分 IP 接入诊断
- 每分钟保存 WAL 全局状态和分 IP 接收统计,历史保留 7 天 - 记录接收、WAL 落盘、Kafka ACK、过滤、背压及丢弃数量 - 增加 wal_max_pending_bytes 和 wal_admission_unavailable 指标 - 增加 TCP pending 当前值及进程内峰值统计 - 网络异常后同一 IP 恢复接收时打印恢复日志 - 提供当前状态及按日期查询诊断历史的接口 - 连接关闭时清理 TCP pending 计数并停止无效重试 - 增加相关单元测试和现场排查文档
This commit is contained in:
@@ -146,6 +146,30 @@ public class AppProperties {
|
||||
@Value("${syslog.status-log-interval-ms:60000}")
|
||||
private long syslogStatusLogIntervalMs;
|
||||
|
||||
// ========== 接入诊断历史 ==========
|
||||
/** 独立于 Kafka/数据库保存诊断快照,避免故障链路同时带走排障证据。 */
|
||||
@Value("${syslog.diagnostics.enabled:true}")
|
||||
private boolean syslogDiagnosticsEnabled;
|
||||
|
||||
/** 必须位于宿主机持久化卷;生产默认映射到 /home/syslog/logs/syslog-monitor。 */
|
||||
@Value("${syslog.diagnostics.directory:${SYSLOG_DIAGNOSTICS_DIRECTORY:/data/syslog-monitor}}")
|
||||
private String syslogDiagnosticsDirectory;
|
||||
|
||||
@Value("${syslog.diagnostics.snapshot-interval-ms:60000}")
|
||||
private long syslogDiagnosticsSnapshotIntervalMs;
|
||||
|
||||
/** 保留今天及此前 6 个自然日。 */
|
||||
@Value("${syslog.diagnostics.retention-days:7}")
|
||||
private int syslogDiagnosticsRetentionDays;
|
||||
|
||||
/** 防止伪造 UDP 来源产生无界 IP 基数;超出的来源统一汇总到 __OTHER__。 */
|
||||
@Value("${syslog.diagnostics.max-source-ips:10000}")
|
||||
private int syslogDiagnosticsMaxSourceIps;
|
||||
|
||||
/** 网络线程只执行 offer;队列满时绝不阻塞接入线程。 */
|
||||
@Value("${syslog.diagnostics.event-queue-capacity:10000}")
|
||||
private int syslogDiagnosticsEventQueueCapacity;
|
||||
|
||||
// ========== 旧 v1 原文归档精确导入(默认关闭) ==========
|
||||
@Value("${syslog.wal.legacy-import.enabled:false}")
|
||||
private boolean syslogWalLegacyImportEnabled;
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package com.haobang.diagnostics;
|
||||
|
||||
import com.haobang.wal.SyslogWalStatusProvider;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDate;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
/** 现场查询当前分 IP 计数,并按日期下载持久化诊断历史。 */
|
||||
@RestController
|
||||
@RequestMapping("/api/syslog/wal/diagnostics")
|
||||
public class SyslogDiagnosticsController {
|
||||
private final SyslogWalStatusProvider statusProvider;
|
||||
private final SyslogIpMetricsRegistry ipMetrics;
|
||||
private final SyslogDiagnosticsPersistence persistence;
|
||||
|
||||
public SyslogDiagnosticsController(SyslogWalStatusProvider statusProvider,
|
||||
SyslogIpMetricsRegistry ipMetrics,
|
||||
SyslogDiagnosticsPersistence persistence) {
|
||||
this.statusProvider = statusProvider;
|
||||
this.ipMetrics = ipMetrics;
|
||||
this.persistence = persistence;
|
||||
}
|
||||
|
||||
@GetMapping("/current")
|
||||
public Map<String, Object> current(
|
||||
@RequestParam(name = "source_ip", required = false) String sourceIp) {
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("persistence", persistence.status());
|
||||
value.put("global", statusProvider.status());
|
||||
List<Map<String, Object>> rows = ipMetrics.currentSnapshot();
|
||||
if (sourceIp != null && !sourceIp.trim().isEmpty()) {
|
||||
List<Map<String, Object>> filtered = new ArrayList<>();
|
||||
for (Map<String, Object> row : rows) {
|
||||
if (sourceIp.trim().equals(row.get("source_ip"))) filtered.add(row);
|
||||
}
|
||||
rows = filtered;
|
||||
}
|
||||
value.put("ip_stats", rows);
|
||||
return value;
|
||||
}
|
||||
|
||||
@GetMapping("/history")
|
||||
public ResponseEntity<Resource> history(
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date,
|
||||
@RequestParam(defaultValue = "snapshots") String type) {
|
||||
Path file;
|
||||
if ("snapshots".equals(type)) file = persistence.snapshotFile(date);
|
||||
else if ("ip".equals(type)) file = persistence.ipSnapshotFile(date);
|
||||
else if ("events".equals(type)) file = persistence.eventFile(date);
|
||||
else return ResponseEntity.badRequest().build();
|
||||
if (file == null || !Files.isRegularFile(file)) return ResponseEntity.notFound().build();
|
||||
Resource resource = new FileSystemResource(file.toFile());
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType("application/x-ndjson"))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"" + file.getFileName() + "\"")
|
||||
.body(resource);
|
||||
}
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
package com.haobang.diagnostics;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.haobang.config.AppProperties;
|
||||
import com.haobang.wal.SyslogWalStatusProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.DirectoryStream;
|
||||
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.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/** 将全局/分 IP 快照和网络异常事件写入独立 JSONL 文件。 */
|
||||
@Component
|
||||
public class SyslogDiagnosticsPersistence {
|
||||
private static final Logger log = LoggerFactory.getLogger(SyslogDiagnosticsPersistence.class);
|
||||
private static final ZoneId ZONE = ZoneId.of("Asia/Shanghai");
|
||||
private static final String SNAPSHOT_PREFIX = "syslog-status-";
|
||||
private static final String EVENT_PREFIX = "syslog-events-";
|
||||
private static final String IP_SNAPSHOT_PREFIX = "syslog-ip-status-";
|
||||
|
||||
private final AppProperties properties;
|
||||
private final SyslogIpMetricsRegistry ipMetrics;
|
||||
private final SyslogWalStatusProvider statusProvider;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final AtomicBoolean running = new AtomicBoolean();
|
||||
private final AtomicLong snapshotWriteFailures = new AtomicLong();
|
||||
private final AtomicLong eventWriteFailures = new AtomicLong();
|
||||
private final AtomicLong initializationFailures = new AtomicLong();
|
||||
private volatile String lastSnapshotTime;
|
||||
private volatile String lastEventTime;
|
||||
private volatile String lastWriteError;
|
||||
private volatile LocalDate lastCleanupDate;
|
||||
private Path rootDirectory;
|
||||
private Path snapshotDirectory;
|
||||
private Path eventDirectory;
|
||||
private Path ipSnapshotDirectory;
|
||||
private ScheduledExecutorService snapshotScheduler;
|
||||
private Thread eventWriterThread;
|
||||
|
||||
public SyslogDiagnosticsPersistence(AppProperties properties,
|
||||
SyslogIpMetricsRegistry ipMetrics,
|
||||
SyslogWalStatusProvider statusProvider,
|
||||
ObjectMapper objectMapper) {
|
||||
this.properties = properties;
|
||||
this.ipMetrics = ipMetrics;
|
||||
this.statusProvider = statusProvider;
|
||||
this.objectMapper = objectMapper.copy().deactivateDefaultTyping();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
if (!properties.isSyslogDiagnosticsEnabled()) {
|
||||
log.warn("Syslog 诊断历史已关闭");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
validateConfiguration();
|
||||
rootDirectory = Paths.get(properties.getSyslogDiagnosticsDirectory())
|
||||
.toAbsolutePath().normalize();
|
||||
snapshotDirectory = rootDirectory.resolve("snapshots");
|
||||
eventDirectory = rootDirectory.resolve("events");
|
||||
ipSnapshotDirectory = rootDirectory.resolve("ip-snapshots");
|
||||
Files.createDirectories(snapshotDirectory);
|
||||
Files.createDirectories(eventDirectory);
|
||||
Files.createDirectories(ipSnapshotDirectory);
|
||||
cleanupExpiredFiles(LocalDate.now(ZONE));
|
||||
} catch (Throwable failure) {
|
||||
initializationFailures.incrementAndGet();
|
||||
lastWriteError = failure.toString();
|
||||
log.error("Syslog 诊断历史初始化失败;接入链路继续运行,诊断持久化不可用: directory={}",
|
||||
properties.getSyslogDiagnosticsDirectory(), failure);
|
||||
return;
|
||||
}
|
||||
running.set(true);
|
||||
eventWriterThread = new Thread(this::eventWriterLoop, "syslog-diagnostics-event-writer");
|
||||
eventWriterThread.setDaemon(true);
|
||||
eventWriterThread.start();
|
||||
snapshotScheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
|
||||
Thread thread = new Thread(runnable, "syslog-diagnostics-snapshot-writer");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
snapshotScheduler.scheduleWithFixedDelay(this::writeSnapshotSafely,
|
||||
1000L, properties.getSyslogDiagnosticsSnapshotIntervalMs(), TimeUnit.MILLISECONDS);
|
||||
log.info("Syslog 诊断历史已启用: directory={}, snapshotIntervalMs={}, retentionDays={}, maxSourceIps={}",
|
||||
rootDirectory, properties.getSyslogDiagnosticsSnapshotIntervalMs(),
|
||||
properties.getSyslogDiagnosticsRetentionDays(), properties.getSyslogDiagnosticsMaxSourceIps());
|
||||
}
|
||||
|
||||
private void eventWriterLoop() {
|
||||
Map<String, Object> pending = null;
|
||||
while (running.get() || pending != null || hasQueuedEvent()) {
|
||||
try {
|
||||
if (pending == null) {
|
||||
pending = running.get()
|
||||
? ipMetrics.pollEvent(1000L, TimeUnit.MILLISECONDS)
|
||||
: ipMetrics.pollEvent();
|
||||
}
|
||||
if (pending != null) {
|
||||
writeEvent(pending);
|
||||
pending = null;
|
||||
}
|
||||
} catch (InterruptedException interrupted) {
|
||||
if (running.get()) Thread.currentThread().interrupt();
|
||||
} catch (Throwable failure) {
|
||||
recordFailure(false, failure);
|
||||
try { Thread.sleep(1000L); }
|
||||
catch (InterruptedException interrupted) {
|
||||
if (running.get()) Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasQueuedEvent() {
|
||||
return ipMetrics.getEventQueueSize() > 0;
|
||||
}
|
||||
|
||||
void writeSnapshotSafely() {
|
||||
if (!running.get()) return;
|
||||
try {
|
||||
ZonedDateTime now = ZonedDateTime.now(ZONE);
|
||||
Map<String, Object> snapshot = new LinkedHashMap<>();
|
||||
snapshot.put("record_type", "snapshot");
|
||||
snapshot.put("snapshot_time", now.toInstant().toString());
|
||||
snapshot.put("instance_id", ipMetrics.getInstanceId());
|
||||
snapshot.put("process_start_time", ipMetrics.getProcessStartTime().toString());
|
||||
snapshot.put("device_collect_id", properties.getAppServiceDeviceCollectId());
|
||||
snapshot.put("device_collect_name", properties.getAppServiceDeviceCollectName());
|
||||
snapshot.put("global", statusProvider.status());
|
||||
SyslogIpMetricsRegistry.SnapshotBatch batch = ipMetrics.snapshotWithDeltas();
|
||||
snapshot.put("ip_stats_count", batch.getRows().size());
|
||||
appendJson(snapshotFile(now.toLocalDate()), snapshot);
|
||||
appendIpSnapshots(ipSnapshotFile(now.toLocalDate()), now, batch.getRows());
|
||||
ipMetrics.commitSnapshot(batch);
|
||||
lastSnapshotTime = now.toInstant().toString();
|
||||
cleanupExpiredFiles(now.toLocalDate());
|
||||
} catch (Throwable failure) {
|
||||
recordFailure(true, failure);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeEvent(Map<String, Object> event) throws IOException {
|
||||
LocalDate date = LocalDate.now(ZONE);
|
||||
appendJson(eventFile(date), event);
|
||||
Object eventTime = event.get("event_time");
|
||||
lastEventTime = eventTime == null ? ZonedDateTime.now(ZONE).toInstant().toString()
|
||||
: String.valueOf(eventTime);
|
||||
}
|
||||
|
||||
private void appendJson(Path path, Map<String, Object> value) throws IOException {
|
||||
byte[] json = (objectMapper.writeValueAsString(value) + System.lineSeparator())
|
||||
.getBytes(StandardCharsets.UTF_8);
|
||||
Files.write(path, json, StandardOpenOption.CREATE, StandardOpenOption.WRITE,
|
||||
StandardOpenOption.APPEND);
|
||||
}
|
||||
|
||||
private void appendIpSnapshots(Path path, ZonedDateTime now,
|
||||
List<Map<String, Object>> rows) throws IOException {
|
||||
if (rows.isEmpty()) return;
|
||||
StringBuilder output = new StringBuilder(rows.size() * 512);
|
||||
for (Map<String, Object> row : rows) {
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("record_type", "ip_snapshot");
|
||||
record.put("snapshot_time", now.toInstant().toString());
|
||||
record.put("instance_id", ipMetrics.getInstanceId());
|
||||
record.put("process_start_time", ipMetrics.getProcessStartTime().toString());
|
||||
record.put("device_collect_id", properties.getAppServiceDeviceCollectId());
|
||||
record.put("device_collect_name", properties.getAppServiceDeviceCollectName());
|
||||
record.putAll(row);
|
||||
output.append(objectMapper.writeValueAsString(record)).append(System.lineSeparator());
|
||||
}
|
||||
Files.write(path, output.toString().getBytes(StandardCharsets.UTF_8),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
|
||||
}
|
||||
|
||||
private void cleanupExpiredFiles(LocalDate today) throws IOException {
|
||||
if (today.equals(lastCleanupDate)) return;
|
||||
LocalDate oldestRetained = today.minusDays(properties.getSyslogDiagnosticsRetentionDays() - 1L);
|
||||
cleanupDirectory(snapshotDirectory, SNAPSHOT_PREFIX, oldestRetained);
|
||||
cleanupDirectory(eventDirectory, EVENT_PREFIX, oldestRetained);
|
||||
cleanupDirectory(ipSnapshotDirectory, IP_SNAPSHOT_PREFIX, oldestRetained);
|
||||
lastCleanupDate = today;
|
||||
}
|
||||
|
||||
private void cleanupDirectory(Path directory, String prefix, LocalDate oldestRetained) throws IOException {
|
||||
try (DirectoryStream<Path> files = Files.newDirectoryStream(directory, prefix + "*.jsonl")) {
|
||||
for (Path file : files) {
|
||||
LocalDate fileDate = parseDate(file.getFileName().toString(), prefix);
|
||||
if (fileDate != null && fileDate.isBefore(oldestRetained)) Files.deleteIfExists(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate parseDate(String fileName, String prefix) {
|
||||
if (!fileName.startsWith(prefix) || !fileName.endsWith(".jsonl")) return null;
|
||||
String date = fileName.substring(prefix.length(), fileName.length() - ".jsonl".length());
|
||||
try { return LocalDate.parse(date); }
|
||||
catch (RuntimeException ignored) { return null; }
|
||||
}
|
||||
|
||||
private void recordFailure(boolean snapshot, Throwable failure) {
|
||||
long count = snapshot ? snapshotWriteFailures.incrementAndGet() : eventWriteFailures.incrementAndGet();
|
||||
lastWriteError = failure.toString();
|
||||
if (count == 1L || count % 100L == 0L) {
|
||||
log.error("Syslog 诊断{}写入失败: failures={}", snapshot ? "快照" : "事件", count, failure);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateConfiguration() {
|
||||
if (properties.getSyslogDiagnosticsSnapshotIntervalMs() <= 0L
|
||||
|| properties.getSyslogDiagnosticsRetentionDays() <= 0
|
||||
|| properties.getSyslogDiagnosticsMaxSourceIps() <= 0
|
||||
|| properties.getSyslogDiagnosticsEventQueueCapacity() <= 0) {
|
||||
throw new IllegalArgumentException("Syslog 诊断间隔、保留天数、IP 上限和事件队列容量必须大于 0");
|
||||
}
|
||||
if (properties.getSyslogDiagnosticsDirectory() == null
|
||||
|| properties.getSyslogDiagnosticsDirectory().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("syslog.diagnostics.directory 不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> status() {
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("enabled", properties.isSyslogDiagnosticsEnabled());
|
||||
value.put("active", running.get());
|
||||
value.put("directory", rootDirectory == null ? null : rootDirectory.toString());
|
||||
value.put("retention_days", properties.getSyslogDiagnosticsRetentionDays());
|
||||
value.put("snapshot_interval_ms", properties.getSyslogDiagnosticsSnapshotIntervalMs());
|
||||
value.put("last_snapshot_time", lastSnapshotTime);
|
||||
value.put("last_event_time", lastEventTime);
|
||||
value.put("snapshot_write_failures", snapshotWriteFailures.get());
|
||||
value.put("event_write_failures", eventWriteFailures.get());
|
||||
value.put("initialization_failures", initializationFailures.get());
|
||||
value.put("event_queue_dropped", ipMetrics.getEventQueueDropped());
|
||||
value.put("last_write_error", lastWriteError);
|
||||
return value;
|
||||
}
|
||||
|
||||
public Path snapshotFile(LocalDate date) {
|
||||
return snapshotDirectory == null ? null
|
||||
: snapshotDirectory.resolve(SNAPSHOT_PREFIX + date + ".jsonl");
|
||||
}
|
||||
|
||||
public Path eventFile(LocalDate date) {
|
||||
return eventDirectory == null ? null
|
||||
: eventDirectory.resolve(EVENT_PREFIX + date + ".jsonl");
|
||||
}
|
||||
|
||||
public Path ipSnapshotFile(LocalDate date) {
|
||||
return ipSnapshotDirectory == null ? null
|
||||
: ipSnapshotDirectory.resolve(IP_SNAPSHOT_PREFIX + date + ".jsonl");
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
if (!running.compareAndSet(true, false)) return;
|
||||
if (snapshotScheduler != null) snapshotScheduler.shutdownNow();
|
||||
if (eventWriterThread != null) {
|
||||
eventWriterThread.interrupt();
|
||||
try { eventWriterThread.join(5000L); }
|
||||
catch (InterruptedException interrupted) { Thread.currentThread().interrupt(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
+496
@@ -0,0 +1,496 @@
|
||||
package com.haobang.diagnostics;
|
||||
|
||||
import com.common.entity.DeviceDevice;
|
||||
import com.haobang.config.AppProperties;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
/**
|
||||
* 进程内、按来源 IP 聚合的接入诊断指标。
|
||||
*
|
||||
* <p>所有接入热路径只做无锁计数和非阻塞队列 offer。诊断文件 IO 由
|
||||
* {@link SyslogDiagnosticsPersistence} 的独立线程完成。</p>
|
||||
*/
|
||||
@Component
|
||||
public class SyslogIpMetricsRegistry {
|
||||
public static final String OVERFLOW_SOURCE_IP = "__OTHER__";
|
||||
|
||||
private final ConcurrentHashMap<String, IpMetrics> metrics = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, MetricValues> previousSnapshot = new ConcurrentHashMap<>();
|
||||
private final IpMetrics overflow = new IpMetrics(OVERFLOW_SOURCE_IP);
|
||||
private final ArrayBlockingQueue<Map<String, Object>> eventQueue;
|
||||
private final LongAdder eventQueueDropped = new LongAdder();
|
||||
private final AtomicLong tcpPendingFramesCurrent = new AtomicLong();
|
||||
private final AtomicLong tcpPendingFramesMax = new AtomicLong();
|
||||
private final int maxSourceIps;
|
||||
private final int deviceCollectId;
|
||||
private final String deviceCollectName;
|
||||
private final String instanceId = UUID.randomUUID().toString();
|
||||
private final Instant processStartTime = Instant.now();
|
||||
|
||||
public SyslogIpMetricsRegistry(AppProperties properties) {
|
||||
this.maxSourceIps = Math.max(1, properties.getSyslogDiagnosticsMaxSourceIps());
|
||||
this.deviceCollectId = properties.getAppServiceDeviceCollectId();
|
||||
this.deviceCollectName = properties.getAppServiceDeviceCollectName();
|
||||
this.eventQueue = new ArrayBlockingQueue<>(
|
||||
Math.max(1, properties.getSyslogDiagnosticsEventQueueCapacity()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录收到的报文;如果该 IP 此前发生过网络接入异常,返回本次恢复信息。
|
||||
*
|
||||
* <p>这里不判断设备是否离线,也不生成中断统计,只用于让接入层在异常后的第一条报文到达时
|
||||
* 打印一次恢复日志。</p>
|
||||
*/
|
||||
public NetworkRecovery recordNetworkReceived(String sourceIp, int sourcePort, String protocol) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, protocol);
|
||||
value.networkReceived.increment();
|
||||
long receivedAt = System.currentTimeMillis();
|
||||
value.lastReceiveTime.set(receivedAt);
|
||||
// __OTHER__ 会合并多个来源,不能用其中一个 IP 的异常推断另一个 IP 已恢复。
|
||||
if (value == overflow) return null;
|
||||
long exceptionAt = value.pendingRecoveryExceptionTime.getAndSet(0L);
|
||||
return exceptionAt <= 0L ? null : new NetworkRecovery(exceptionAt, receivedAt);
|
||||
}
|
||||
|
||||
public void recordWalAppended(String sourceIp, Integer sourcePort, String protocol) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort == null ? 0 : sourcePort, protocol);
|
||||
value.walAppended.increment();
|
||||
}
|
||||
|
||||
public void recordWalBackpressure(String sourceIp, int sourcePort, String protocol) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, protocol);
|
||||
value.walAdmissionBackpressure.increment();
|
||||
}
|
||||
|
||||
public void recordWalUnavailable(String sourceIp, Integer sourcePort, String protocol) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort == null ? 0 : sourcePort, protocol);
|
||||
value.walAdmissionUnavailable.increment();
|
||||
}
|
||||
|
||||
public void recordTcpPendingAdded(String sourceIp, int sourcePort) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, "TCP");
|
||||
long current = tcpPendingFramesCurrent.incrementAndGet();
|
||||
updateMax(tcpPendingFramesMax, current);
|
||||
long ipCurrent = value.tcpPendingFramesCurrent.incrementAndGet();
|
||||
updateMax(value.tcpPendingFramesMax, ipCurrent);
|
||||
}
|
||||
|
||||
public void recordTcpPendingRemoved(String sourceIp, int sourcePort) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, "TCP");
|
||||
decrementIfPositive(tcpPendingFramesCurrent);
|
||||
decrementIfPositive(value.tcpPendingFramesCurrent);
|
||||
}
|
||||
|
||||
public void recordKafkaAck(String sourceIp, int sourcePort, String protocol, DeviceDevice device) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, protocol);
|
||||
value.assignDevice(device);
|
||||
value.kafkaAcked.increment();
|
||||
}
|
||||
|
||||
public void recordInvalidDeviceDropped(String sourceIp, String protocol) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(0, protocol);
|
||||
value.invalidDeviceDropped.increment();
|
||||
}
|
||||
|
||||
public void recordDeviceLookupFailed(String sourceIp, String protocol) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(0, protocol);
|
||||
value.deviceLookupFailed.increment();
|
||||
}
|
||||
|
||||
public void recordOversizedDropped(String sourceIp, int sourcePort, String protocol, int chars) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, protocol);
|
||||
value.oversizedDropped.increment();
|
||||
enqueueEvent(event("OVERSIZED_DROPPED", sourceIp, sourcePort, protocol,
|
||||
"chars=" + chars, null, value));
|
||||
}
|
||||
|
||||
public void recordUdpDropped(String sourceIp, int sourcePort) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, "UDP");
|
||||
value.udpDropped.increment();
|
||||
}
|
||||
|
||||
public void recordTcpPause(String sourceIp, int sourcePort) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, "TCP");
|
||||
value.tcpPauseTotal.increment();
|
||||
}
|
||||
|
||||
public void recordDispatchRetry(String sourceIp, int sourcePort, String protocol) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, protocol);
|
||||
value.dispatchRetry.increment();
|
||||
}
|
||||
|
||||
public void recordTerminalDropped(String sourceIp, int sourcePort, String protocol,
|
||||
DeviceDevice device, String reason) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, protocol);
|
||||
value.assignDevice(device);
|
||||
value.dispatchTerminalDropped.increment();
|
||||
enqueueEvent(event("DISPATCH_TERMINAL_DROPPED", sourceIp, sourcePort, protocol,
|
||||
abbreviate(reason, 1024), null, value));
|
||||
}
|
||||
|
||||
public void recordNetworkException(String sourceIp, int sourcePort, String protocol,
|
||||
Throwable failure) {
|
||||
IpMetrics value = metric(sourceIp);
|
||||
value.touch(sourcePort, protocol);
|
||||
value.networkException.increment();
|
||||
long exceptionAt = System.currentTimeMillis();
|
||||
value.lastExceptionTime.set(exceptionAt);
|
||||
// 多次连续异常只保留第一次,恢复日志才能反映从首次发现异常到重新收到数据的间隔。
|
||||
if (value != overflow) value.pendingRecoveryExceptionTime.compareAndSet(0L, exceptionAt);
|
||||
boolean tcp = "TCP".equalsIgnoreCase(protocol);
|
||||
String type = classify(failure, tcp);
|
||||
if (tcp) {
|
||||
value.tcpNetworkException.increment();
|
||||
if ("TOO_LONG_FRAME".equals(type)) value.tcpTooLongFrame.increment();
|
||||
else if ("CONNECTION_RESET".equals(type)) value.tcpConnectionReset.increment();
|
||||
else value.tcpOtherException.increment();
|
||||
} else {
|
||||
value.udpNetworkException.increment();
|
||||
}
|
||||
Throwable root = rootCause(failure);
|
||||
enqueueEvent(event(type, sourceIp, sourcePort, protocol,
|
||||
root == null ? null : abbreviate(root.getMessage(), 1024),
|
||||
root == null ? null : root.getClass().getName(), value));
|
||||
}
|
||||
|
||||
/** 当前累计值;读取接口调用不会改变分钟增量基线。 */
|
||||
public List<Map<String, Object>> currentSnapshot() {
|
||||
return snapshot(false);
|
||||
}
|
||||
|
||||
/** 持久化线程专用;返回累计值并推进下一分钟的 delta 基线。 */
|
||||
public List<Map<String, Object>> snapshotAndAdvanceDeltas() {
|
||||
SnapshotBatch batch = snapshotWithDeltas();
|
||||
commitSnapshot(batch);
|
||||
return batch.getRows();
|
||||
}
|
||||
|
||||
/** 先捕获一致的 delta,只有两个诊断文件都写成功后才由持久化线程提交基线。 */
|
||||
public SnapshotBatch snapshotWithDeltas() {
|
||||
List<IpMetrics> values = sortedMetrics();
|
||||
List<Map<String, Object>> rows = new ArrayList<>(values.size());
|
||||
Map<String, MetricValues> baselines = new LinkedHashMap<>();
|
||||
for (IpMetrics value : values) {
|
||||
MetricValues current = value.values();
|
||||
rows.add(value.toMap(current, current.minus(previousSnapshot.get(value.sourceIp))));
|
||||
baselines.put(value.sourceIp, current);
|
||||
}
|
||||
return new SnapshotBatch(rows, baselines);
|
||||
}
|
||||
|
||||
public void commitSnapshot(SnapshotBatch batch) {
|
||||
if (batch != null) previousSnapshot.putAll(batch.baselines);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> snapshot(boolean advanceDeltas) {
|
||||
List<IpMetrics> values = sortedMetrics();
|
||||
List<Map<String, Object>> result = new ArrayList<>(values.size());
|
||||
for (IpMetrics value : values) {
|
||||
MetricValues current = value.values();
|
||||
MetricValues previous = previousSnapshot.get(value.sourceIp);
|
||||
result.add(value.toMap(current, advanceDeltas ? current.minus(previous) : null));
|
||||
if (advanceDeltas) previousSnapshot.put(value.sourceIp, current);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<IpMetrics> sortedMetrics() {
|
||||
List<IpMetrics> values = new ArrayList<>(metrics.values());
|
||||
if (!overflow.isEmpty()) values.add(overflow);
|
||||
Collections.sort(values, Comparator.comparing(value -> value.sourceIp));
|
||||
return values;
|
||||
}
|
||||
|
||||
public Map<String, Object> summary() {
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("instance_id", instanceId);
|
||||
value.put("process_start_time", processStartTime.toString());
|
||||
value.put("device_collect_id", deviceCollectId);
|
||||
value.put("device_collect_name", deviceCollectName);
|
||||
value.put("tracked_source_ips", metrics.size());
|
||||
value.put("max_source_ips", maxSourceIps);
|
||||
value.put("event_queue_size", eventQueue.size());
|
||||
value.put("event_queue_dropped", eventQueueDropped.sum());
|
||||
value.put("network_exception_total", sum(Metric.NETWORK_EXCEPTION));
|
||||
value.put("tcp_network_exception_total", sum(Metric.TCP_NETWORK_EXCEPTION));
|
||||
value.put("udp_network_exception_total", sum(Metric.UDP_NETWORK_EXCEPTION));
|
||||
value.put("tcp_connection_reset_total", sum(Metric.TCP_CONNECTION_RESET));
|
||||
value.put("tcp_too_long_frame_total", sum(Metric.TCP_TOO_LONG_FRAME));
|
||||
value.put("tcp_other_exception_total", sum(Metric.TCP_OTHER_EXCEPTION));
|
||||
value.put("tcp_pending_frames_current", tcpPendingFramesCurrent.get());
|
||||
value.put("tcp_pending_frames_max", tcpPendingFramesMax.get());
|
||||
return value;
|
||||
}
|
||||
|
||||
public Map<String, Object> pollEvent(long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return eventQueue.poll(timeout, unit);
|
||||
}
|
||||
|
||||
public Map<String, Object> pollEvent() { return eventQueue.poll(); }
|
||||
public String getInstanceId() { return instanceId; }
|
||||
public Instant getProcessStartTime() { return processStartTime; }
|
||||
public int getEventQueueSize() { return eventQueue.size(); }
|
||||
public long getEventQueueDropped() { return eventQueueDropped.sum(); }
|
||||
public long getTcpPendingFramesCurrent() { return tcpPendingFramesCurrent.get(); }
|
||||
public long getTcpPendingFramesMax() { return tcpPendingFramesMax.get(); }
|
||||
|
||||
public static final class NetworkRecovery {
|
||||
private final long exceptionAtEpochMillis;
|
||||
private final long recoveredAtEpochMillis;
|
||||
|
||||
private NetworkRecovery(long exceptionAtEpochMillis, long recoveredAtEpochMillis) {
|
||||
this.exceptionAtEpochMillis = exceptionAtEpochMillis;
|
||||
this.recoveredAtEpochMillis = recoveredAtEpochMillis;
|
||||
}
|
||||
|
||||
public Instant getExceptionAt() { return Instant.ofEpochMilli(exceptionAtEpochMillis); }
|
||||
public Instant getRecoveredAt() { return Instant.ofEpochMilli(recoveredAtEpochMillis); }
|
||||
public long getElapsedMillis() {
|
||||
return Math.max(0L, recoveredAtEpochMillis - exceptionAtEpochMillis);
|
||||
}
|
||||
}
|
||||
|
||||
private IpMetrics metric(String sourceIp) {
|
||||
String normalized = sourceIp == null || sourceIp.trim().isEmpty() ? "unknown" : sourceIp.trim();
|
||||
IpMetrics existing = metrics.get(normalized);
|
||||
if (existing != null) return existing;
|
||||
synchronized (metrics) {
|
||||
existing = metrics.get(normalized);
|
||||
if (existing != null) return existing;
|
||||
if (metrics.size() >= maxSourceIps) return overflow;
|
||||
IpMetrics created = new IpMetrics(normalized);
|
||||
metrics.put(normalized, created);
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
||||
private void enqueueEvent(Map<String, Object> event) {
|
||||
if (!eventQueue.offer(event)) eventQueueDropped.increment();
|
||||
}
|
||||
|
||||
private static void updateMax(AtomicLong maximum, long candidate) {
|
||||
long current = maximum.get();
|
||||
while (candidate > current && !maximum.compareAndSet(current, candidate)) {
|
||||
current = maximum.get();
|
||||
}
|
||||
}
|
||||
|
||||
private static void decrementIfPositive(AtomicLong value) {
|
||||
long current = value.get();
|
||||
while (current > 0L && !value.compareAndSet(current, current - 1L)) {
|
||||
current = value.get();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> event(String eventType, String sourceIp, int sourcePort,
|
||||
String protocol, String message, String exceptionClass,
|
||||
IpMetrics metrics) {
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("record_type", "event");
|
||||
value.put("event_time", Instant.now().toString());
|
||||
value.put("instance_id", instanceId);
|
||||
value.put("device_collect_id", deviceCollectId);
|
||||
value.put("device_collect_name", deviceCollectName);
|
||||
value.put("event_type", eventType);
|
||||
value.put("source_ip", sourceIp);
|
||||
value.put("source_port", sourcePort);
|
||||
value.put("protocol", protocol);
|
||||
value.put("device_id", metrics.deviceId.get() == 0 ? null : metrics.deviceId.get());
|
||||
value.put("device_name", metrics.deviceName);
|
||||
value.put("exception_class", exceptionClass);
|
||||
value.put("message", message);
|
||||
return value;
|
||||
}
|
||||
|
||||
private long sum(Metric metric) {
|
||||
long total = metric.value(overflow);
|
||||
for (IpMetrics value : metrics.values()) total += metric.value(value);
|
||||
return total;
|
||||
}
|
||||
|
||||
private static String classify(Throwable failure, boolean tcp) {
|
||||
if (!tcp) return "UDP_NETWORK_EXCEPTION";
|
||||
for (Throwable current = failure; current != null; current = current.getCause()) {
|
||||
if (current instanceof TooLongFrameException) return "TOO_LONG_FRAME";
|
||||
String text = String.valueOf(current.getMessage()).toLowerCase();
|
||||
if (text.contains("connection reset") || text.contains("broken pipe")
|
||||
|| text.contains("forcibly closed")) return "CONNECTION_RESET";
|
||||
}
|
||||
return "TCP_NETWORK_EXCEPTION";
|
||||
}
|
||||
|
||||
private static Throwable rootCause(Throwable failure) {
|
||||
Throwable current = failure;
|
||||
while (current != null && current.getCause() != null && current.getCause() != current) {
|
||||
current = current.getCause();
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private static String abbreviate(String value, int limit) {
|
||||
if (value == null || value.length() <= limit) return value;
|
||||
return value.substring(0, limit);
|
||||
}
|
||||
|
||||
private enum Metric {
|
||||
NETWORK_EXCEPTION { long value(IpMetrics value) { return value.networkException.sum(); } },
|
||||
TCP_NETWORK_EXCEPTION { long value(IpMetrics value) { return value.tcpNetworkException.sum(); } },
|
||||
UDP_NETWORK_EXCEPTION { long value(IpMetrics value) { return value.udpNetworkException.sum(); } },
|
||||
TCP_CONNECTION_RESET { long value(IpMetrics value) { return value.tcpConnectionReset.sum(); } },
|
||||
TCP_TOO_LONG_FRAME { long value(IpMetrics value) { return value.tcpTooLongFrame.sum(); } },
|
||||
TCP_OTHER_EXCEPTION { long value(IpMetrics value) { return value.tcpOtherException.sum(); } };
|
||||
abstract long value(IpMetrics value);
|
||||
}
|
||||
|
||||
public static final class SnapshotBatch {
|
||||
private final List<Map<String, Object>> rows;
|
||||
private final Map<String, MetricValues> baselines;
|
||||
|
||||
private SnapshotBatch(List<Map<String, Object>> rows,
|
||||
Map<String, MetricValues> baselines) {
|
||||
this.rows = rows;
|
||||
this.baselines = baselines;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getRows() { return rows; }
|
||||
}
|
||||
|
||||
private static final class IpMetrics {
|
||||
private final String sourceIp;
|
||||
private final Set<String> protocols = ConcurrentHashMap.newKeySet();
|
||||
private final AtomicInteger lastSourcePort = new AtomicInteger();
|
||||
private final AtomicInteger deviceId = new AtomicInteger();
|
||||
private volatile String deviceName;
|
||||
private final AtomicLong lastReceiveTime = new AtomicLong();
|
||||
private final AtomicLong lastExceptionTime = new AtomicLong();
|
||||
private final AtomicLong pendingRecoveryExceptionTime = new AtomicLong();
|
||||
private final AtomicLong tcpPendingFramesCurrent = new AtomicLong();
|
||||
private final AtomicLong tcpPendingFramesMax = new AtomicLong();
|
||||
private final LongAdder networkReceived = new LongAdder();
|
||||
private final LongAdder walAppended = new LongAdder();
|
||||
private final LongAdder walAdmissionBackpressure = new LongAdder();
|
||||
private final LongAdder walAdmissionUnavailable = new LongAdder();
|
||||
private final LongAdder kafkaAcked = new LongAdder();
|
||||
private final LongAdder invalidDeviceDropped = new LongAdder();
|
||||
private final LongAdder deviceLookupFailed = new LongAdder();
|
||||
private final LongAdder oversizedDropped = new LongAdder();
|
||||
private final LongAdder udpDropped = new LongAdder();
|
||||
private final LongAdder tcpPauseTotal = new LongAdder();
|
||||
private final LongAdder networkException = new LongAdder();
|
||||
private final LongAdder tcpNetworkException = new LongAdder();
|
||||
private final LongAdder udpNetworkException = new LongAdder();
|
||||
private final LongAdder tcpConnectionReset = new LongAdder();
|
||||
private final LongAdder tcpTooLongFrame = new LongAdder();
|
||||
private final LongAdder tcpOtherException = new LongAdder();
|
||||
private final LongAdder dispatchTerminalDropped = new LongAdder();
|
||||
private final LongAdder dispatchRetry = new LongAdder();
|
||||
|
||||
private IpMetrics(String sourceIp) { this.sourceIp = sourceIp; }
|
||||
|
||||
private void touch(int sourcePort, String protocol) {
|
||||
if (sourcePort > 0) lastSourcePort.set(sourcePort);
|
||||
if (protocol != null && !protocol.trim().isEmpty()) protocols.add(protocol);
|
||||
}
|
||||
|
||||
private void assignDevice(DeviceDevice device) {
|
||||
if (device == null) return;
|
||||
if (device.getId() != null) deviceId.set(device.getId());
|
||||
if (device.getName() != null) deviceName = device.getName();
|
||||
}
|
||||
|
||||
private MetricValues values() {
|
||||
return new MetricValues(networkReceived.sum(), walAppended.sum(),
|
||||
walAdmissionBackpressure.sum(), walAdmissionUnavailable.sum(), kafkaAcked.sum(),
|
||||
invalidDeviceDropped.sum(), deviceLookupFailed.sum(), oversizedDropped.sum(),
|
||||
udpDropped.sum(), tcpPauseTotal.sum(), networkException.sum(),
|
||||
tcpNetworkException.sum(), udpNetworkException.sum(),
|
||||
tcpConnectionReset.sum(), tcpTooLongFrame.sum(), tcpOtherException.sum(),
|
||||
dispatchTerminalDropped.sum(), dispatchRetry.sum());
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap(MetricValues total, MetricValues delta) {
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("source_ip", sourceIp);
|
||||
value.put("source_port", lastSourcePort.get());
|
||||
value.put("protocols", new ArrayList<>(protocols));
|
||||
value.put("device_id", deviceId.get() == 0 ? null : deviceId.get());
|
||||
value.put("device_name", deviceName);
|
||||
value.put("last_receive_time", instant(lastReceiveTime.get()));
|
||||
value.put("last_exception_time", instant(lastExceptionTime.get()));
|
||||
value.put("tcp_pending_frames_current", tcpPendingFramesCurrent.get());
|
||||
value.put("tcp_pending_frames_max", tcpPendingFramesMax.get());
|
||||
value.putAll(total.toMap());
|
||||
if (delta != null) value.put("delta", delta.toMap());
|
||||
return value;
|
||||
}
|
||||
|
||||
private boolean isEmpty() { return values().isZero(); }
|
||||
private static String instant(long epochMillis) {
|
||||
return epochMillis <= 0L ? null : Instant.ofEpochMilli(epochMillis).toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MetricValues {
|
||||
private final long[] values;
|
||||
|
||||
private MetricValues(long... values) { this.values = values; }
|
||||
|
||||
private MetricValues minus(MetricValues previous) {
|
||||
long[] result = new long[values.length];
|
||||
for (int index = 0; index < values.length; index++) {
|
||||
long old = previous == null ? 0L : previous.values[index];
|
||||
result[index] = Math.max(0L, values[index] - old);
|
||||
}
|
||||
return new MetricValues(result);
|
||||
}
|
||||
|
||||
private boolean isZero() {
|
||||
for (long value : values) if (value != 0L) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap() {
|
||||
String[] names = {"network_received", "wal_appended", "wal_admission_backpressure",
|
||||
"wal_admission_unavailable", "kafka_acked",
|
||||
"invalid_device_dropped", "device_lookup_failed", "oversized_dropped",
|
||||
"udp_dropped", "tcp_pause_total", "network_exception_total",
|
||||
"tcp_network_exception_total", "udp_network_exception_total",
|
||||
"tcp_connection_reset_total", "tcp_too_long_frame_total",
|
||||
"tcp_other_exception_total", "dispatch_terminal_dropped", "dispatch_retry"};
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
for (int index = 0; index < names.length; index++) result.put(names[index], values[index]);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-3
@@ -1,6 +1,7 @@
|
||||
package com.haobang.dispatch;
|
||||
|
||||
import com.haobang.config.AppProperties;
|
||||
import com.haobang.diagnostics.SyslogIpMetricsRegistry;
|
||||
import com.haobang.wal.SyslogWalRecord;
|
||||
import com.haobang.wal.SyslogWalService;
|
||||
import com.kafka.kafkaProducer;
|
||||
@@ -8,6 +9,7 @@ import com.netty.SyslogMessageHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
@@ -51,6 +53,7 @@ public class SyslogWalDispatcher {
|
||||
private final LongAdder retryCount = new LongAdder();
|
||||
private final LongAdder largeDispatched = new LongAdder();
|
||||
private final AtomicLong inflight = new AtomicLong();
|
||||
private SyslogIpMetricsRegistry ipMetrics;
|
||||
|
||||
private Semaphore permits;
|
||||
private ByteBudget byteBudget;
|
||||
@@ -71,6 +74,11 @@ public class SyslogWalDispatcher {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setIpMetrics(SyslogIpMetricsRegistry ipMetrics) {
|
||||
this.ipMetrics = ipMetrics;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() throws Exception {
|
||||
validateConfiguration();
|
||||
@@ -157,6 +165,10 @@ public class SyslogWalDispatcher {
|
||||
walService.recordTerminal(record, resolution.getType().name(), resolution.getReason());
|
||||
} else {
|
||||
terminalDropped.increment();
|
||||
if (ipMetrics != null) {
|
||||
ipMetrics.recordTerminalDropped(record.getSourceIp(), record.getSourcePort(),
|
||||
record.getProtocol(), resolution.getDevice(), resolution.getReason());
|
||||
}
|
||||
}
|
||||
terminalFiltered.increment();
|
||||
markComplete(record);
|
||||
@@ -200,6 +212,10 @@ public class SyslogWalDispatcher {
|
||||
.whenComplete((delivery, failure) -> {
|
||||
if (failure == null) {
|
||||
kafkaAcked.increment();
|
||||
if (ipMetrics != null) {
|
||||
ipMetrics.recordKafkaAck(record.getSourceIp(), record.getSourcePort(),
|
||||
record.getProtocol(), resolution.getDevice());
|
||||
}
|
||||
if (large) largeDispatched.increment();
|
||||
markComplete(record);
|
||||
} else {
|
||||
@@ -214,6 +230,9 @@ public class SyslogWalDispatcher {
|
||||
|
||||
private void scheduleRetry(SyslogWalRecord record, int attempt, String stage, Throwable failure) {
|
||||
retryCount.increment();
|
||||
if (ipMetrics != null) {
|
||||
ipMetrics.recordDispatchRetry(record.getSourceIp(), record.getSourcePort(), record.getProtocol());
|
||||
}
|
||||
long retries = retryCount.sum();
|
||||
if (attempt == 1 || retries % 100 == 0) {
|
||||
log.warn("WAL 事件投递失败,将保留原 event_id 重试: eventId={}, sequence={}, stage={}, "
|
||||
@@ -261,18 +280,21 @@ public class SyslogWalDispatcher {
|
||||
|
||||
private void logStatus() {
|
||||
if (!running.get()) return;
|
||||
log.info("Syslog WAL状态: networkReceived={}, appended={}, admissionOversized={}, walPendingApprox={}, walPendingBytes={}, walQueue={}/{}, durableSequence={}, "
|
||||
log.info("Syslog WAL状态: networkReceived={}, appended={}, admissionOversized={}, admissionUnavailable={}, "
|
||||
+ "walPendingApprox={}, walPendingBytes={}/{}, walQueue={}/{}, durableSequence={}, "
|
||||
+ "checkpointSequence={}, kafkaInflight={}, kafkaInflightBytes={}, kafkaAcked={}, largeDispatched={}, "
|
||||
+ "terminalRecordEnabled={}, terminalFiltered={}, terminalDropped={}, "
|
||||
+ "retry={}, tcpPauseTotal={}, udpDropped={}, deviceCacheSize={}, deviceCacheHits={}, "
|
||||
+ "retry={}, tcpPauseTotal={}, tcpPendingFrames={}/{}, udpDropped={}, deviceCacheSize={}, deviceCacheHits={}, "
|
||||
+ "deviceCacheMisses={}",
|
||||
SyslogMessageHandler.getNetworkReceivedCount(), walService.getAppendedCount(),
|
||||
walService.getOversizedCount(), walService.getPendingRecordsApprox(), walService.getPendingAppendBytes(),
|
||||
walService.getOversizedCount(), walService.getUnavailableCount(),
|
||||
walService.getPendingRecordsApprox(), walService.getPendingAppendBytes(), walService.getMaxPendingBytes(),
|
||||
walService.getQueueSize(), walService.getQueueCapacity(), walService.getDurableSequence(),
|
||||
walService.getCheckpointSequence(), inflight.get(), byteBudget == null ? 0L : byteBudget.used(),
|
||||
kafkaAcked.sum(), largeDispatched.sum(), properties.isSyslogDispatchTerminalRecordEnabled(),
|
||||
terminalFiltered.sum(), terminalDropped.sum(),
|
||||
retryCount.sum(), SyslogMessageHandler.getTcpPauseCount(),
|
||||
SyslogMessageHandler.getTcpPendingFramesCurrent(), SyslogMessageHandler.getTcpPendingFramesMax(),
|
||||
SyslogMessageHandler.getUdpDroppedCount(), deviceResolver.getCacheSize(),
|
||||
deviceResolver.getHitCount(), deviceResolver.getMissCount());
|
||||
}
|
||||
|
||||
+26
-1
@@ -3,6 +3,7 @@ package com.haobang.wal;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.haobang.config.AppProperties;
|
||||
import com.haobang.diagnostics.SyslogIpMetricsRegistry;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -70,12 +71,14 @@ public class SyslogWalService {
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Charset syslogCharset;
|
||||
private final ForceOperation forceOperation;
|
||||
private SyslogIpMetricsRegistry ipMetrics;
|
||||
private final PointTimeAllocator pointTimeAllocator = new PointTimeAllocator();
|
||||
private final AtomicBoolean initialized = new AtomicBoolean();
|
||||
private final AtomicLong nextSequence = new AtomicLong();
|
||||
private final AtomicLong durableSequence = new AtomicLong();
|
||||
private final LongAdder appended = new LongAdder();
|
||||
private final LongAdder admissionBackpressure = new LongAdder();
|
||||
private final LongAdder admissionUnavailable = new LongAdder();
|
||||
private final LongAdder admissionOversized = new LongAdder();
|
||||
private final AtomicLong pendingAppendBytes = new AtomicLong();
|
||||
private final LongAdder quarantined = new LongAdder();
|
||||
@@ -147,6 +150,12 @@ public class SyslogWalService {
|
||||
this.forceOperation = forceOperation;
|
||||
}
|
||||
|
||||
/** 测试可不注册诊断 Bean;生产环境由 Spring 注入。 */
|
||||
@Autowired(required = false)
|
||||
void setIpMetrics(SyslogIpMetricsRegistry ipMetrics) {
|
||||
this.ipMetrics = ipMetrics;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
if (!initialized.compareAndSet(false, true)) return;
|
||||
@@ -232,6 +241,11 @@ public class SyslogWalService {
|
||||
}
|
||||
refreshDiskState(false);
|
||||
if (!running || fatalFailure != null || diskState == DiskState.CRITICAL) {
|
||||
admissionUnavailable.increment();
|
||||
if (ipMetrics != null) {
|
||||
ipMetrics.recordWalUnavailable(event.getSourceIp(), event.getSourcePort(),
|
||||
event.getTransport());
|
||||
}
|
||||
return AppendAttempt.rejected(AdmissionStatus.UNAVAILABLE);
|
||||
}
|
||||
int rawBytes = event.getRawMessage().getBytes(event.getCharset()).length;
|
||||
@@ -250,7 +264,16 @@ public class SyslogWalService {
|
||||
admissionBackpressure.increment();
|
||||
return AppendAttempt.rejected(AdmissionStatus.BACKPRESSURE);
|
||||
}
|
||||
return AppendAttempt.accepted(pending.getDurableFuture());
|
||||
AppendAttempt accepted = AppendAttempt.accepted(pending.getDurableFuture());
|
||||
if (ipMetrics != null) {
|
||||
accepted.getDurableStage().whenComplete((receipt, failure) -> {
|
||||
if (failure == null) {
|
||||
ipMetrics.recordWalAppended(event.getSourceIp(), event.getSourcePort(),
|
||||
event.getTransport());
|
||||
}
|
||||
});
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
public boolean shouldPauseTcp() {
|
||||
@@ -281,8 +304,10 @@ public class SyslogWalService {
|
||||
public int getQueueCapacity() { return properties.getSyslogWalQueueCapacity(); }
|
||||
public long getAppendedCount() { return appended.sum(); }
|
||||
public long getBackpressureCount() { return admissionBackpressure.sum(); }
|
||||
public long getUnavailableCount() { return admissionUnavailable.sum(); }
|
||||
public long getOversizedCount() { return admissionOversized.sum(); }
|
||||
public long getPendingAppendBytes() { return pendingAppendBytes.get(); }
|
||||
public long getMaxPendingBytes() { return properties.getSyslogWalMaxPendingBytes(); }
|
||||
public long getQuarantinedCount() { return quarantined.sum(); }
|
||||
public long getTerminalRecordedCount() { return terminalRecorded.sum(); }
|
||||
public long getIngressPointTimeAssignedCount() { return pointTimeAllocator.getAssignedCount(); }
|
||||
|
||||
+4
-55
@@ -1,74 +1,23 @@
|
||||
package com.haobang.wal;
|
||||
|
||||
import com.haobang.dispatch.DeviceContextResolver;
|
||||
import com.haobang.dispatch.SyslogWalDispatcher;
|
||||
import com.netty.SyslogMessageHandler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** 运维只读状态接口;不提供跳过、清空或人工推进游标等危险操作。 */
|
||||
@RestController
|
||||
@RequestMapping("/api/syslog/wal")
|
||||
public class SyslogWalStatusController {
|
||||
private final SyslogWalService wal;
|
||||
private final SyslogWalDispatcher dispatcher;
|
||||
private final DeviceContextResolver resolver;
|
||||
private final SyslogWalStatusProvider statusProvider;
|
||||
|
||||
public SyslogWalStatusController(SyslogWalService wal, SyslogWalDispatcher dispatcher,
|
||||
DeviceContextResolver resolver) {
|
||||
this.wal = wal;
|
||||
this.dispatcher = dispatcher;
|
||||
this.resolver = resolver;
|
||||
public SyslogWalStatusController(SyslogWalStatusProvider statusProvider) {
|
||||
this.statusProvider = statusProvider;
|
||||
}
|
||||
|
||||
@GetMapping("/status")
|
||||
public Map<String, Object> status() {
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("network_received", SyslogMessageHandler.getNetworkReceivedCount());
|
||||
value.put("network_oversized_dropped", SyslogMessageHandler.getOversizedDroppedCount());
|
||||
value.put("network_invalid_device_dropped", SyslogMessageHandler.getInvalidDeviceDroppedCount());
|
||||
value.put("network_device_lookup_failed", SyslogMessageHandler.getDeviceLookupFailedCount());
|
||||
value.put("wal_appended", wal.getAppendedCount());
|
||||
value.put("wal_queue_size", wal.getQueueSize());
|
||||
value.put("wal_queue_capacity", wal.getQueueCapacity());
|
||||
value.put("wal_pending_bytes", wal.getPendingAppendBytes());
|
||||
value.put("wal_durable_sequence", wal.getDurableSequence());
|
||||
value.put("wal_checkpoint_sequence", wal.getCheckpointSequence());
|
||||
value.put("wal_pending_approx", wal.getPendingRecordsApprox());
|
||||
value.put("wal_admission_backpressure", wal.getBackpressureCount());
|
||||
value.put("wal_admission_oversized", wal.getOversizedCount());
|
||||
value.put("wal_terminal_recorded", wal.getTerminalRecordedCount());
|
||||
value.put("wal_quarantined", wal.getQuarantinedCount());
|
||||
Map<String, Long> pointTimeAssigned = new LinkedHashMap<>();
|
||||
pointTimeAssigned.put("ingress", wal.getIngressPointTimeAssignedCount());
|
||||
value.put("point_time_assigned_total", pointTimeAssigned);
|
||||
value.put("wal_disk_critical", wal.isDiskCritical());
|
||||
value.put("wal_disk_state", wal.getDiskState());
|
||||
value.put("wal_disk_total_bytes", wal.getDiskTotalBytes());
|
||||
value.put("wal_disk_usable_bytes", wal.getDiskUsableBytes());
|
||||
value.put("wal_disk_stop_threshold_bytes", wal.getDiskStopThresholdBytes());
|
||||
value.put("wal_fatal_error", wal.getFatalFailure() == null
|
||||
? null : wal.getFatalFailure().toString());
|
||||
value.put("dispatch_inflight", dispatcher.getInflight());
|
||||
value.put("dispatch_inflight_bytes", dispatcher.getInflightBytes());
|
||||
value.put("kafka_acked", dispatcher.getKafkaAcked());
|
||||
value.put("kafka_large_dispatched", dispatcher.getLargeDispatched());
|
||||
value.put("dispatch_terminal_record_enabled", dispatcher.isTerminalRecordEnabled());
|
||||
value.put("dispatch_terminal_filtered", dispatcher.getTerminalFiltered());
|
||||
value.put("dispatch_terminal_dropped", dispatcher.getTerminalDropped());
|
||||
value.put("dispatch_retry", dispatcher.getRetryCount());
|
||||
value.put("tcp_pause_total", SyslogMessageHandler.getTcpPauseCount());
|
||||
value.put("udp_dropped_total", SyslogMessageHandler.getUdpDroppedCount());
|
||||
value.put("device_cache_size", resolver.getCacheSize());
|
||||
value.put("device_cache_hits", resolver.getHitCount());
|
||||
value.put("device_cache_misses", resolver.getMissCount());
|
||||
value.put("device_ingress_snapshot_ready", resolver.isIngressSnapshotReady());
|
||||
value.put("device_ingress_snapshot_size", resolver.getIngressSnapshotSize());
|
||||
value.put("device_ingress_snapshot_refresh_failed", resolver.getIngressSnapshotRefreshFailedCount());
|
||||
return value;
|
||||
return statusProvider.status();
|
||||
}
|
||||
}
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.haobang.wal;
|
||||
|
||||
import com.haobang.diagnostics.SyslogIpMetricsRegistry;
|
||||
import com.haobang.dispatch.DeviceContextResolver;
|
||||
import com.haobang.dispatch.SyslogWalDispatcher;
|
||||
import com.netty.SyslogMessageHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** 生成 HTTP 状态与持久化快照共用的单一口径。 */
|
||||
@Component
|
||||
public class SyslogWalStatusProvider {
|
||||
private final SyslogWalService wal;
|
||||
private final SyslogWalDispatcher dispatcher;
|
||||
private final DeviceContextResolver resolver;
|
||||
private final SyslogIpMetricsRegistry ipMetrics;
|
||||
|
||||
public SyslogWalStatusProvider(SyslogWalService wal, SyslogWalDispatcher dispatcher,
|
||||
DeviceContextResolver resolver, SyslogIpMetricsRegistry ipMetrics) {
|
||||
this.wal = wal;
|
||||
this.dispatcher = dispatcher;
|
||||
this.resolver = resolver;
|
||||
this.ipMetrics = ipMetrics;
|
||||
}
|
||||
|
||||
public Map<String, Object> status() {
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("network_received", SyslogMessageHandler.getNetworkReceivedCount());
|
||||
value.put("network_oversized_dropped", SyslogMessageHandler.getOversizedDroppedCount());
|
||||
value.put("network_invalid_device_dropped", SyslogMessageHandler.getInvalidDeviceDroppedCount());
|
||||
value.put("network_device_lookup_failed", SyslogMessageHandler.getDeviceLookupFailedCount());
|
||||
value.put("wal_appended", wal.getAppendedCount());
|
||||
value.put("wal_queue_size", wal.getQueueSize());
|
||||
value.put("wal_queue_capacity", wal.getQueueCapacity());
|
||||
value.put("wal_pending_bytes", wal.getPendingAppendBytes());
|
||||
value.put("wal_max_pending_bytes", wal.getMaxPendingBytes());
|
||||
value.put("wal_durable_sequence", wal.getDurableSequence());
|
||||
value.put("wal_checkpoint_sequence", wal.getCheckpointSequence());
|
||||
value.put("wal_pending_approx", wal.getPendingRecordsApprox());
|
||||
value.put("wal_admission_backpressure", wal.getBackpressureCount());
|
||||
value.put("wal_admission_unavailable", wal.getUnavailableCount());
|
||||
value.put("wal_admission_oversized", wal.getOversizedCount());
|
||||
value.put("wal_terminal_recorded", wal.getTerminalRecordedCount());
|
||||
value.put("wal_quarantined", wal.getQuarantinedCount());
|
||||
Map<String, Long> pointTimeAssigned = new LinkedHashMap<>();
|
||||
pointTimeAssigned.put("ingress", wal.getIngressPointTimeAssignedCount());
|
||||
value.put("point_time_assigned_total", pointTimeAssigned);
|
||||
value.put("wal_disk_critical", wal.isDiskCritical());
|
||||
value.put("wal_disk_state", wal.getDiskState());
|
||||
value.put("wal_disk_total_bytes", wal.getDiskTotalBytes());
|
||||
value.put("wal_disk_usable_bytes", wal.getDiskUsableBytes());
|
||||
value.put("wal_disk_stop_threshold_bytes", wal.getDiskStopThresholdBytes());
|
||||
value.put("wal_fatal_error", wal.getFatalFailure() == null
|
||||
? null : wal.getFatalFailure().toString());
|
||||
value.put("dispatch_inflight", dispatcher.getInflight());
|
||||
value.put("dispatch_inflight_bytes", dispatcher.getInflightBytes());
|
||||
value.put("kafka_acked", dispatcher.getKafkaAcked());
|
||||
value.put("kafka_large_dispatched", dispatcher.getLargeDispatched());
|
||||
value.put("dispatch_terminal_record_enabled", dispatcher.isTerminalRecordEnabled());
|
||||
value.put("dispatch_terminal_filtered", dispatcher.getTerminalFiltered());
|
||||
value.put("dispatch_terminal_dropped", dispatcher.getTerminalDropped());
|
||||
value.put("dispatch_retry", dispatcher.getRetryCount());
|
||||
value.put("tcp_pause_total", SyslogMessageHandler.getTcpPauseCount());
|
||||
value.put("tcp_pending_frames_current", SyslogMessageHandler.getTcpPendingFramesCurrent());
|
||||
value.put("tcp_pending_frames_max", SyslogMessageHandler.getTcpPendingFramesMax());
|
||||
value.put("udp_dropped_total", SyslogMessageHandler.getUdpDroppedCount());
|
||||
value.put("device_cache_size", resolver.getCacheSize());
|
||||
value.put("device_cache_hits", resolver.getHitCount());
|
||||
value.put("device_cache_misses", resolver.getMissCount());
|
||||
value.put("device_ingress_snapshot_ready", resolver.isIngressSnapshotReady());
|
||||
value.put("device_ingress_snapshot_size", resolver.getIngressSnapshotSize());
|
||||
value.put("device_ingress_snapshot_refresh_failed", resolver.getIngressSnapshotRefreshFailedCount());
|
||||
value.put("diagnostics", ipMetrics.summary());
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+120
-6
@@ -1,12 +1,14 @@
|
||||
package com.netty;
|
||||
|
||||
import com.haobang.dispatch.DeviceContextResolver;
|
||||
import com.haobang.diagnostics.SyslogIpMetricsRegistry;
|
||||
import com.haobang.wal.SyslogIngressEvent;
|
||||
import com.haobang.wal.SyslogWalService;
|
||||
import com.kafka.kafkaProducer;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import io.netty.channel.socket.DatagramChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -19,6 +21,7 @@ import java.time.ZoneId;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
/**
|
||||
@@ -37,6 +40,8 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
private static final LongAdder OVERSIZED_DROPPED = new LongAdder();
|
||||
private static final LongAdder INVALID_DEVICE_DROPPED = new LongAdder();
|
||||
private static final LongAdder DEVICE_LOOKUP_FAILED = new LongAdder();
|
||||
private static final AtomicLong TCP_PENDING_FRAMES_CURRENT = new AtomicLong();
|
||||
private static final AtomicLong TCP_PENDING_FRAMES_MAX = new AtomicLong();
|
||||
|
||||
/** 每个 TCP Channel 独立 Handler,pending 只保存关闭 AUTO_READ 前已进入 Pipeline 的帧。 */
|
||||
private final Deque<SyslogIngressEvent> pendingTcp = new ArrayDeque<>();
|
||||
@@ -44,15 +49,22 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
private final SyslogWalService walService;
|
||||
private final DeviceContextResolver deviceResolver;
|
||||
private final boolean dropInvalidBeforeWal;
|
||||
private final SyslogIpMetricsRegistry ipMetrics;
|
||||
private boolean retryScheduled;
|
||||
private boolean autoReadPaused;
|
||||
|
||||
SyslogMessageHandler(Charset syslogCharset, SyslogWalService walService) {
|
||||
this(syslogCharset, walService, null, false);
|
||||
this(syslogCharset, walService, null, false, null);
|
||||
}
|
||||
|
||||
SyslogMessageHandler(Charset syslogCharset, SyslogWalService walService,
|
||||
DeviceContextResolver deviceResolver, boolean dropInvalidBeforeWal) {
|
||||
this(syslogCharset, walService, deviceResolver, dropInvalidBeforeWal, null);
|
||||
}
|
||||
|
||||
SyslogMessageHandler(Charset syslogCharset, SyslogWalService walService,
|
||||
DeviceContextResolver deviceResolver, boolean dropInvalidBeforeWal,
|
||||
SyslogIpMetricsRegistry ipMetrics) {
|
||||
this.syslogCharset = java.util.Objects.requireNonNull(syslogCharset, "syslogCharset");
|
||||
this.walService = java.util.Objects.requireNonNull(walService, "walService");
|
||||
if (dropInvalidBeforeWal && deviceResolver == null) {
|
||||
@@ -60,6 +72,7 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
}
|
||||
this.deviceResolver = deviceResolver;
|
||||
this.dropInvalidBeforeWal = dropInvalidBeforeWal;
|
||||
this.ipMetrics = ipMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,6 +106,16 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
}
|
||||
|
||||
NETWORK_RECEIVED.increment();
|
||||
if (ipMetrics != null) {
|
||||
SyslogIpMetricsRegistry.NetworkRecovery recovery =
|
||||
ipMetrics.recordNetworkReceived(sourceIp, sourcePort, protocol);
|
||||
if (recovery != null) {
|
||||
log.info("Syslog 网络接入异常后已恢复接收: sourceIp={}, sourcePort={}, protocol={}, "
|
||||
+ "exceptionTime={}, recoveryTime={}, elapsedMs={}",
|
||||
sourceIp, sourcePort, protocol, recovery.getExceptionAt(),
|
||||
recovery.getRecoveredAt(), recovery.getElapsedMillis());
|
||||
}
|
||||
}
|
||||
if (dropInvalidBeforeWal && shouldDropBeforeWal(sourceIp, protocol)) return;
|
||||
|
||||
Instant receivedInstant = Instant.now();
|
||||
@@ -114,7 +137,11 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
recordOversized(event, "UDP");
|
||||
return;
|
||||
}
|
||||
if (result == SyslogWalService.AdmissionResult.BACKPRESSURE && ipMetrics != null) {
|
||||
ipMetrics.recordWalBackpressure(event.getSourceIp(), event.getSourcePort(), "UDP");
|
||||
}
|
||||
UDP_DROPPED.increment();
|
||||
if (ipMetrics != null) ipMetrics.recordUdpDropped(event.getSourceIp(), event.getSourcePort());
|
||||
long dropped = UDP_DROPPED.sum();
|
||||
if (dropped == 1 || dropped % 100 == 0) {
|
||||
log.error("UDP Syslog 无法进入 WAL;UDP 不支持背压,已发生明确丢弃: eventId={}, sourceIp={}, "
|
||||
@@ -126,7 +153,7 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
private void acceptTcp(ChannelHandlerContext ctx, SyslogWalService wal, SyslogIngressEvent event) {
|
||||
// pending 非空时必须维持同一 TCP Channel 的原始顺序,不能让新帧越过旧帧。
|
||||
if (!pendingTcp.isEmpty()) {
|
||||
pendingTcp.addLast(event);
|
||||
addPendingTcp(event);
|
||||
pauseAndSchedule(ctx, wal);
|
||||
return;
|
||||
}
|
||||
@@ -137,7 +164,10 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
return;
|
||||
}
|
||||
if (result != SyslogWalService.AdmissionResult.ACCEPTED) {
|
||||
pendingTcp.addLast(event);
|
||||
if (result == SyslogWalService.AdmissionResult.BACKPRESSURE && ipMetrics != null) {
|
||||
ipMetrics.recordWalBackpressure(event.getSourceIp(), event.getSourcePort(), "TCP");
|
||||
}
|
||||
addPendingTcp(event);
|
||||
pauseAndSchedule(ctx, wal);
|
||||
return;
|
||||
}
|
||||
@@ -149,6 +179,12 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
ctx.channel().config().setAutoRead(false);
|
||||
autoReadPaused = true;
|
||||
TCP_PAUSE_COUNT.increment();
|
||||
if (ipMetrics != null) {
|
||||
InetSocketAddress remote = remoteAddress(ctx);
|
||||
ipMetrics.recordTcpPause(remote == null ? String.valueOf(ctx.channel().remoteAddress())
|
||||
: remote.getAddress().getHostAddress(),
|
||||
remote == null ? 0 : remote.getPort());
|
||||
}
|
||||
log.warn("TCP Syslog 已按 WAL 接入保护暂停读取: channel={}, pendingFrames={}, walQueue={}/{}, "
|
||||
+ "diskState={}, pauseTotal={}",
|
||||
ctx.channel().remoteAddress(), pendingTcp.size(), wal.getQueueSize(), wal.getQueueCapacity(),
|
||||
@@ -158,7 +194,7 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
}
|
||||
|
||||
private void scheduleDrain(ChannelHandlerContext ctx, SyslogWalService wal) {
|
||||
if (retryScheduled) return;
|
||||
if (retryScheduled || !ctx.channel().isActive()) return;
|
||||
retryScheduled = true;
|
||||
ctx.executor().schedule(() -> {
|
||||
retryScheduled = false;
|
||||
@@ -167,19 +203,27 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
}
|
||||
|
||||
private void drainPending(ChannelHandlerContext ctx, SyslogWalService wal) {
|
||||
if (!ctx.channel().isActive()) {
|
||||
clearPendingTcp();
|
||||
autoReadPaused = false;
|
||||
return;
|
||||
}
|
||||
while (!pendingTcp.isEmpty()) {
|
||||
SyslogIngressEvent event = pendingTcp.peekFirst();
|
||||
SyslogWalService.AdmissionResult result = wal.tryAppend(event);
|
||||
if (result == SyslogWalService.AdmissionResult.OVERSIZED) {
|
||||
pendingTcp.removeFirst();
|
||||
removeFirstPendingTcp();
|
||||
recordOversized(event, "TCP");
|
||||
continue;
|
||||
}
|
||||
if (result != SyslogWalService.AdmissionResult.ACCEPTED) {
|
||||
if (result == SyslogWalService.AdmissionResult.BACKPRESSURE && ipMetrics != null) {
|
||||
ipMetrics.recordWalBackpressure(event.getSourceIp(), event.getSourcePort(), "TCP");
|
||||
}
|
||||
scheduleDrain(ctx, wal);
|
||||
return;
|
||||
}
|
||||
pendingTcp.removeFirst();
|
||||
removeFirstPendingTcp();
|
||||
}
|
||||
if (wal.canResumeTcp()) {
|
||||
if (autoReadPaused && ctx.channel().isActive()) {
|
||||
@@ -196,33 +240,103 @@ public class SyslogMessageHandler extends SimpleChannelInboundHandler<Object> {
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
if (ipMetrics != null) {
|
||||
InetSocketAddress remote = remoteAddress(ctx);
|
||||
String protocol = ctx.channel() instanceof DatagramChannel ? "UDP" : "TCP";
|
||||
ipMetrics.recordNetworkException(
|
||||
remote == null ? String.valueOf(ctx.channel().remoteAddress())
|
||||
: remote.getAddress().getHostAddress(),
|
||||
remote == null ? 0 : remote.getPort(), protocol, cause);
|
||||
}
|
||||
log.error("Syslog 网络接入异常: channel={}", ctx.channel().remoteAddress(), cause);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
clearPendingTcp();
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
clearPendingTcp();
|
||||
super.handlerRemoved(ctx);
|
||||
}
|
||||
|
||||
public static long getNetworkReceivedCount() { return NETWORK_RECEIVED.sum(); }
|
||||
public static long getTcpPauseCount() { return TCP_PAUSE_COUNT.sum(); }
|
||||
public static long getUdpDroppedCount() { return UDP_DROPPED.sum(); }
|
||||
public static long getOversizedDroppedCount() { return OVERSIZED_DROPPED.sum(); }
|
||||
public static long getInvalidDeviceDroppedCount() { return INVALID_DEVICE_DROPPED.sum(); }
|
||||
public static long getDeviceLookupFailedCount() { return DEVICE_LOOKUP_FAILED.sum(); }
|
||||
public static long getTcpPendingFramesCurrent() { return TCP_PENDING_FRAMES_CURRENT.get(); }
|
||||
public static long getTcpPendingFramesMax() { return TCP_PENDING_FRAMES_MAX.get(); }
|
||||
|
||||
private void addPendingTcp(SyslogIngressEvent event) {
|
||||
pendingTcp.addLast(event);
|
||||
long current = TCP_PENDING_FRAMES_CURRENT.incrementAndGet();
|
||||
updateMax(TCP_PENDING_FRAMES_MAX, current);
|
||||
if (ipMetrics != null) {
|
||||
ipMetrics.recordTcpPendingAdded(event.getSourceIp(), event.getSourcePort());
|
||||
}
|
||||
}
|
||||
|
||||
private SyslogIngressEvent removeFirstPendingTcp() {
|
||||
SyslogIngressEvent event = pendingTcp.pollFirst();
|
||||
if (event == null) return null;
|
||||
decrementIfPositive(TCP_PENDING_FRAMES_CURRENT);
|
||||
if (ipMetrics != null) {
|
||||
ipMetrics.recordTcpPendingRemoved(event.getSourceIp(), event.getSourcePort());
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
private void clearPendingTcp() {
|
||||
while (!pendingTcp.isEmpty()) removeFirstPendingTcp();
|
||||
}
|
||||
|
||||
private static void updateMax(AtomicLong maximum, long candidate) {
|
||||
long current = maximum.get();
|
||||
while (candidate > current && !maximum.compareAndSet(current, candidate)) {
|
||||
current = maximum.get();
|
||||
}
|
||||
}
|
||||
|
||||
private static void decrementIfPositive(AtomicLong value) {
|
||||
long current = value.get();
|
||||
while (current > 0L && !value.compareAndSet(current, current - 1L)) {
|
||||
current = value.get();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldDropBeforeWal(String sourceIp, String protocol) {
|
||||
DeviceContextResolver.IngressStatus status = deviceResolver.resolveIngress(sourceIp, protocol);
|
||||
if (status == DeviceContextResolver.IngressStatus.REGISTERED) return false;
|
||||
if (status == DeviceContextResolver.IngressStatus.INVALID) {
|
||||
INVALID_DEVICE_DROPPED.increment();
|
||||
if (ipMetrics != null) ipMetrics.recordInvalidDeviceDropped(sourceIp, protocol);
|
||||
return true;
|
||||
}
|
||||
// 后台快照暂不可用时不访问数据库、不阻塞网络线程;保守进入 WAL 等待 Dispatcher 判断。
|
||||
DEVICE_LOOKUP_FAILED.increment();
|
||||
if (ipMetrics != null) ipMetrics.recordDeviceLookupFailed(sourceIp, protocol);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void recordOversized(SyslogIngressEvent event, String protocol) {
|
||||
OVERSIZED_DROPPED.increment();
|
||||
if (ipMetrics != null) {
|
||||
ipMetrics.recordOversizedDropped(event.getSourceIp(), event.getSourcePort(), protocol,
|
||||
event.getRawMessage().length());
|
||||
}
|
||||
log.error("Syslog 超过单条消息硬上限,已拒绝且不记录原文: eventId={}, protocol={}, sourceIp={}, chars={}, rejectedTotal={}",
|
||||
event.getEventId(), protocol, event.getSourceIp(), event.getRawMessage().length(),
|
||||
OVERSIZED_DROPPED.sum());
|
||||
}
|
||||
|
||||
private InetSocketAddress remoteAddress(ChannelHandlerContext ctx) {
|
||||
SocketAddress address = ctx.channel().remoteAddress();
|
||||
return address instanceof InetSocketAddress ? (InetSocketAddress) address : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.netty;
|
||||
|
||||
import com.haobang.config.AppProperties;
|
||||
import com.haobang.dispatch.DeviceContextResolver;
|
||||
import com.haobang.diagnostics.SyslogIpMetricsRegistry;
|
||||
import com.haobang.util.SpringContextUtil;
|
||||
import com.haobang.wal.SyslogWalService;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
@@ -43,6 +44,7 @@ public class SyslogServer {
|
||||
AppProperties properties = SpringContextUtil.getBean(AppProperties.class);
|
||||
SyslogWalService walService = SpringContextUtil.getBean(SyslogWalService.class);
|
||||
DeviceContextResolver deviceResolver = SpringContextUtil.getBean(DeviceContextResolver.class);
|
||||
SyslogIpMetricsRegistry ipMetrics = SpringContextUtil.getBean(SyslogIpMetricsRegistry.class);
|
||||
Charset charset = walService.getSyslogCharset();
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
@@ -53,7 +55,7 @@ public class SyslogServer {
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new SyslogTcpChannelInitializer(
|
||||
properties.getSyslogMaxFrameLength(), charset, walService, deviceResolver,
|
||||
!properties.isSyslogDispatchTerminalRecordEnabled()))
|
||||
!properties.isSyslogDispatchTerminalRecordEnabled(), ipMetrics))
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||
|
||||
@@ -71,6 +73,7 @@ public class SyslogServer {
|
||||
AppProperties properties = SpringContextUtil.getBean(AppProperties.class);
|
||||
SyslogWalService walService = SpringContextUtil.getBean(SyslogWalService.class);
|
||||
DeviceContextResolver deviceResolver = SpringContextUtil.getBean(DeviceContextResolver.class);
|
||||
SyslogIpMetricsRegistry ipMetrics = SpringContextUtil.getBean(SyslogIpMetricsRegistry.class);
|
||||
Charset charset = walService.getSyslogCharset();
|
||||
EventLoopGroup group = new NioEventLoopGroup();
|
||||
|
||||
@@ -79,7 +82,7 @@ public class SyslogServer {
|
||||
b.group(group)
|
||||
.channel(NioDatagramChannel.class)
|
||||
.handler(new SyslogUdpChannelInitializer(charset, walService, deviceResolver,
|
||||
!properties.isSyslogDispatchTerminalRecordEnabled()))
|
||||
!properties.isSyslogDispatchTerminalRecordEnabled(), ipMetrics))
|
||||
.option(ChannelOption.SO_BROADCAST, true);
|
||||
|
||||
ChannelFuture f = b.bind(udpPort).sync();
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import com.haobang.config.AppProperties;
|
||||
import com.haobang.dispatch.DeviceContextResolver;
|
||||
import com.haobang.diagnostics.SyslogIpMetricsRegistry;
|
||||
import com.haobang.wal.SyslogWalService;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
@@ -29,6 +30,7 @@ public class SyslogServerBoth {
|
||||
private final AppProperties properties;
|
||||
private final SyslogWalService walService;
|
||||
private final DeviceContextResolver deviceResolver;
|
||||
private final SyslogIpMetricsRegistry ipMetrics;
|
||||
private final Charset syslogCharset;
|
||||
private final int tcpPort;
|
||||
private final int udpPort;
|
||||
@@ -44,10 +46,12 @@ public class SyslogServerBoth {
|
||||
private final ExecutorService serverExecutor = Executors.newFixedThreadPool(2);
|
||||
|
||||
public SyslogServerBoth(AppProperties properties, SyslogWalService walService,
|
||||
DeviceContextResolver deviceResolver, Charset syslogCharset) {
|
||||
DeviceContextResolver deviceResolver, SyslogIpMetricsRegistry ipMetrics,
|
||||
Charset syslogCharset) {
|
||||
this.properties = java.util.Objects.requireNonNull(properties, "properties");
|
||||
this.walService = java.util.Objects.requireNonNull(walService, "walService");
|
||||
this.deviceResolver = java.util.Objects.requireNonNull(deviceResolver, "deviceResolver");
|
||||
this.ipMetrics = java.util.Objects.requireNonNull(ipMetrics, "ipMetrics");
|
||||
this.syslogCharset = java.util.Objects.requireNonNull(syslogCharset, "syslogCharset");
|
||||
this.tcpPort = properties.getSyslogTcpPort();
|
||||
this.udpPort = properties.getSyslogUdpPort();
|
||||
@@ -118,7 +122,7 @@ public class SyslogServerBoth {
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new SyslogTcpChannelInitializer(
|
||||
properties.getSyslogMaxFrameLength(), syslogCharset, walService, deviceResolver,
|
||||
!properties.isSyslogDispatchTerminalRecordEnabled()))
|
||||
!properties.isSyslogDispatchTerminalRecordEnabled(), ipMetrics))
|
||||
// [SYSLOG-ALARM][NINGRESS][已修复] backlog/接收缓冲配置化;实际生效上限仍受操作系统 somaxconn/rmem_max 约束。
|
||||
.option(ChannelOption.SO_BACKLOG, properties.getSyslogTcpBacklog())
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
@@ -141,7 +145,7 @@ public class SyslogServerBoth {
|
||||
b.group(udpGroup)
|
||||
.channel(NioDatagramChannel.class)
|
||||
.handler(new SyslogUdpChannelInitializer(syslogCharset, walService, deviceResolver,
|
||||
!properties.isSyslogDispatchTerminalRecordEnabled()))
|
||||
!properties.isSyslogDispatchTerminalRecordEnabled(), ipMetrics))
|
||||
// [SYSLOG-ALARM][NINGRESS][已修复] 显式配置 UDP 接收缓冲;UDP 仍无端到端确认,强可靠设备应改用 TCP/TLS 或 RELP。
|
||||
.option(ChannelOption.SO_BROADCAST, true)
|
||||
.option(ChannelOption.SO_RCVBUF, properties.getSyslogUdpReceiveBufferBytes());
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.netty;
|
||||
|
||||
import com.haobang.config.AppProperties;
|
||||
import com.haobang.dispatch.DeviceContextResolver;
|
||||
import com.haobang.diagnostics.SyslogIpMetricsRegistry;
|
||||
import com.haobang.dispatch.SyslogWalDispatcher;
|
||||
import com.haobang.wal.SyslogWalService;
|
||||
import org.slf4j.Logger;
|
||||
@@ -26,17 +27,20 @@ public class SyslogServerLifecycle implements SmartLifecycle {
|
||||
private final AppProperties properties;
|
||||
private final SyslogWalService walService;
|
||||
private final DeviceContextResolver deviceResolver;
|
||||
private final SyslogIpMetricsRegistry ipMetrics;
|
||||
@SuppressWarnings("unused")
|
||||
private final SyslogWalDispatcher dispatcher;
|
||||
private final AtomicBoolean running = new AtomicBoolean();
|
||||
private SyslogServerBoth server;
|
||||
|
||||
public SyslogServerLifecycle(AppProperties properties, SyslogWalService walService,
|
||||
SyslogWalDispatcher dispatcher, DeviceContextResolver deviceResolver) {
|
||||
SyslogWalDispatcher dispatcher, DeviceContextResolver deviceResolver,
|
||||
SyslogIpMetricsRegistry ipMetrics) {
|
||||
this.properties = properties;
|
||||
this.walService = walService;
|
||||
this.dispatcher = dispatcher;
|
||||
this.deviceResolver = deviceResolver;
|
||||
this.ipMetrics = ipMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,7 +48,7 @@ public class SyslogServerLifecycle implements SmartLifecycle {
|
||||
if (!running.compareAndSet(false, true)) return;
|
||||
try {
|
||||
Charset charset = walService.getSyslogCharset();
|
||||
server = new SyslogServerBoth(properties, walService, deviceResolver, charset);
|
||||
server = new SyslogServerBoth(properties, walService, deviceResolver, ipMetrics, charset);
|
||||
server.start();
|
||||
log.info("Syslog TCP/UDP 端口已在 WAL 和 Dispatcher 就绪后开放: charset={}", charset.name());
|
||||
} catch (Exception failure) {
|
||||
|
||||
+6
-2
@@ -2,6 +2,7 @@ package com.netty;
|
||||
|
||||
|
||||
import com.haobang.dispatch.DeviceContextResolver;
|
||||
import com.haobang.diagnostics.SyslogIpMetricsRegistry;
|
||||
import com.haobang.wal.SyslogWalService;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
@@ -21,15 +22,18 @@ public class SyslogTcpChannelInitializer extends ChannelInitializer<SocketChanne
|
||||
private final SyslogWalService walService;
|
||||
private final DeviceContextResolver deviceResolver;
|
||||
private final boolean dropInvalidBeforeWal;
|
||||
private final SyslogIpMetricsRegistry ipMetrics;
|
||||
|
||||
SyslogTcpChannelInitializer(int maxFrameLength, Charset syslogCharset, SyslogWalService walService,
|
||||
DeviceContextResolver deviceResolver, boolean dropInvalidBeforeWal) {
|
||||
DeviceContextResolver deviceResolver, boolean dropInvalidBeforeWal,
|
||||
SyslogIpMetricsRegistry ipMetrics) {
|
||||
if (maxFrameLength <= 0) throw new IllegalArgumentException("maxFrameLength must be positive");
|
||||
this.maxFrameLength = maxFrameLength;
|
||||
this.syslogCharset = java.util.Objects.requireNonNull(syslogCharset, "syslogCharset");
|
||||
this.walService = java.util.Objects.requireNonNull(walService, "walService");
|
||||
this.deviceResolver = java.util.Objects.requireNonNull(deviceResolver, "deviceResolver");
|
||||
this.dropInvalidBeforeWal = dropInvalidBeforeWal;
|
||||
this.ipMetrics = java.util.Objects.requireNonNull(ipMetrics, "ipMetrics");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,6 +51,6 @@ public class SyslogTcpChannelInitializer extends ChannelInitializer<SocketChanne
|
||||
|
||||
// 添加业务处理器
|
||||
pipeline.addLast("handler", new SyslogMessageHandler(
|
||||
syslogCharset, walService, deviceResolver, dropInvalidBeforeWal));
|
||||
syslogCharset, walService, deviceResolver, dropInvalidBeforeWal, ipMetrics));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -1,6 +1,7 @@
|
||||
package com.netty;
|
||||
|
||||
import com.haobang.dispatch.DeviceContextResolver;
|
||||
import com.haobang.diagnostics.SyslogIpMetricsRegistry;
|
||||
import com.haobang.wal.SyslogWalService;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
@@ -17,13 +18,16 @@ public class SyslogUdpChannelInitializer extends ChannelInitializer<DatagramChan
|
||||
private final SyslogWalService walService;
|
||||
private final DeviceContextResolver deviceResolver;
|
||||
private final boolean dropInvalidBeforeWal;
|
||||
private final SyslogIpMetricsRegistry ipMetrics;
|
||||
|
||||
SyslogUdpChannelInitializer(Charset syslogCharset, SyslogWalService walService,
|
||||
DeviceContextResolver deviceResolver, boolean dropInvalidBeforeWal) {
|
||||
DeviceContextResolver deviceResolver, boolean dropInvalidBeforeWal,
|
||||
SyslogIpMetricsRegistry ipMetrics) {
|
||||
this.syslogCharset = java.util.Objects.requireNonNull(syslogCharset, "syslogCharset");
|
||||
this.walService = java.util.Objects.requireNonNull(walService, "walService");
|
||||
this.deviceResolver = java.util.Objects.requireNonNull(deviceResolver, "deviceResolver");
|
||||
this.dropInvalidBeforeWal = dropInvalidBeforeWal;
|
||||
this.ipMetrics = java.util.Objects.requireNonNull(ipMetrics, "ipMetrics");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -36,6 +40,6 @@ public class SyslogUdpChannelInitializer extends ChannelInitializer<DatagramChan
|
||||
|
||||
// 添加业务处理器
|
||||
pipeline.addLast("handler", new SyslogMessageHandler(
|
||||
syslogCharset, walService, deviceResolver, dropInvalidBeforeWal));
|
||||
syslogCharset, walService, deviceResolver, dropInvalidBeforeWal, ipMetrics));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user