feat: enhance coding harness, model security and system features

This commit is contained in:
ageerle
2026-09-08 23:33:29 +08:00
parent d4ed2b8672
commit bcff4fd9e3
259 changed files with 15183 additions and 2713 deletions

View File

@@ -65,7 +65,12 @@ public class DefaultTraceStreamSpan implements TraceStreamSpan {
traceRecordService.finishNode(traceId, nodeId, status, errorMessage, outputPayload,
new Date(), System.currentTimeMillis() - startMillis);
} catch (Exception e) {
log.warn("结束 trace stream span 失败,traceId={}, nodeId={}", traceId, nodeId, e);
log.warn("trace_persistence operation=FINISH_STREAM_NODE status=FAILED traceId={} nodeId={} errorType={}",
traceId, nodeId, errorType(e));
}
}
private static String errorType(Throwable error) {
return error == null ? "unknown" : error.getClass().getName();
}
}

View File

@@ -37,9 +37,14 @@ public final class TracePayloadUtils {
if (throwable == null) {
return null;
}
int maxLength = properties == null ? 1000 : properties.getPayload().getMaxErrorLength();
String message = throwable.getClass().getSimpleName() + ": " + (throwable.getMessage() == null ? "" : throwable.getMessage());
return truncate(message, maxLength);
int maxLength = properties == null || properties.getPayload() == null
? 1000 : properties.getPayload().getMaxErrorLength();
// Trace payloads are durable, operator-visible records. Provider exception messages can
// carry prompts, credentials, SQL, file-system paths, or response bodies, so this boundary
// must never read or retain Throwable#getMessage(). The exception type is sufficient for
// classification and remains useful without persisting untrusted content.
String summary = "TRACE_FAILURE errorType=" + throwable.getClass().getName();
return truncate(summary, maxLength);
}
public static String toJson(Object value) {

View File

@@ -1,6 +1,10 @@
package org.ruoyi.common.trace.core;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
@@ -9,11 +13,14 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.ruoyi.common.trace.config.TraceProperties;
import org.ruoyi.common.trace.constant.TraceConstants;
import org.ruoyi.common.trace.service.TraceRecordService;
import org.slf4j.LoggerFactory;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -23,6 +30,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
@Tag("dev")
class DefaultTraceStreamSpanTest {
@Mock
@@ -45,17 +53,18 @@ class DefaultTraceStreamSpanTest {
}
@Test
void finishErrorShouldRecordTruncatedError() {
void finishErrorShouldRecordOnlyBoundedExceptionType() {
TraceProperties properties = new TraceProperties();
properties.getPayload().setMaxErrorLength(8);
properties.getPayload().setMaxErrorLength(200);
DefaultTraceStreamSpan span = new DefaultTraceStreamSpan(traceRecordService, properties, "trace-1", "node-1", System.currentTimeMillis());
span.finishError(new IllegalStateException("abcdef"));
span.finishError(new IllegalStateException("provider-credential-path-canary"));
ArgumentCaptor<String> errorCaptor = ArgumentCaptor.forClass(String.class);
verify(traceRecordService).finishNode(eq("trace-1"), eq("node-1"), eq(TraceConstants.STATUS_ERROR),
errorCaptor.capture(), isNull(), any(Date.class), anyLong());
assertEquals("IllegalS", errorCaptor.getValue());
assertEquals("TRACE_FAILURE errorType=java.lang.IllegalStateException", errorCaptor.getValue());
assertFalse(errorCaptor.getValue().contains("provider-credential-path-canary"));
}
@Test
@@ -72,12 +81,38 @@ class DefaultTraceStreamSpanTest {
@Test
void writeFailureShouldNotEscape() {
doThrow(new IllegalStateException("db down")).when(traceRecordService)
Logger logger = (Logger) LoggerFactory.getLogger(DefaultTraceStreamSpan.class);
ListAppender<ILoggingEvent> appender = attach(logger);
doThrow(new IllegalStateException("trace-storage-credential-canary")).when(traceRecordService)
.finishNode(eq("trace-1"), eq("node-1"), eq(TraceConstants.STATUS_SUCCESS), isNull(), isNull(), any(Date.class), anyLong());
DefaultTraceStreamSpan span = new DefaultTraceStreamSpan(traceRecordService, new TraceProperties(), "trace-1", "node-1", System.currentTimeMillis());
span.finishSuccess();
try {
span.finishSuccess();
assertNull(TraceContext.currentNodeId());
String logs = formattedLogs(appender);
assertNull(TraceContext.currentNodeId());
assertFalse(logs.contains("trace-storage-credential-canary"));
assertTrue(logs.contains("java.lang.IllegalStateException"));
} finally {
detach(logger, appender);
}
}
private static ListAppender<ILoggingEvent> attach(Logger logger) {
ListAppender<ILoggingEvent> appender = new ListAppender<>();
appender.start();
logger.addAppender(appender);
return appender;
}
private static void detach(Logger logger, ListAppender<ILoggingEvent> appender) {
logger.detachAppender(appender);
appender.stop();
}
private static String formattedLogs(ListAppender<ILoggingEvent> appender) {
return appender.list.stream().map(ILoggingEvent::getFormattedMessage)
.reduce("", (left, right) -> left + "\n" + right);
}
}

View File

@@ -1,13 +1,18 @@
package org.ruoyi.common.trace.util;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Tag;
import org.ruoyi.common.trace.config.TraceProperties;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("dev")
class TracePayloadUtilsTest {
@Test
@@ -25,4 +30,25 @@ class TracePayloadUtilsTest {
String json = TracePayloadUtils.toJson(Map.of("count", 2));
assertTrue(json.contains("\"count\":2"));
}
@Test
void errorShouldPersistOnlyTypeWithoutReadingThrowableMessage() {
AtomicBoolean messageRead = new AtomicBoolean();
Throwable failure = new IllegalStateException() {
@Override
public String getMessage() {
messageRead.set(true);
return "provider-credential-path-canary";
}
};
String summary = TracePayloadUtils.error(failure, new TraceProperties());
TraceProperties missingPayloadPolicy = new TraceProperties();
missingPayloadPolicy.setPayload(null);
assertFalse(messageRead.get());
assertEquals("TRACE_FAILURE errorType=" + failure.getClass().getName(), summary);
assertEquals(summary, TracePayloadUtils.error(failure, missingPayloadPolicy));
assertFalse(summary.contains("provider-credential-path-canary"));
}
}