From 56911b2b370cacd33387d609aee4c2696fd62b5f Mon Sep 17 00:00:00 2001 From: Zecheng Zhang Date: Sat, 18 Oct 2025 14:22:11 -0700 Subject: [PATCH] feat: add number of logs for trace --- .../java/com/example/StandaloneExample.java | 10 +- examples/logback-local-example/pom.xml | 2 +- examples/spring-boot-example/.env.example | 4 +- examples/spring-boot-example/pom.xml | 2 +- .../src/main/java/com/example/App.java | 10 +- .../main/java/com/example/TaskController.java | 1 + examples/standalone-example/pom.xml | 2 +- examples/tencent-log4j2-example/pom.xml | 2 +- .../java/com/example/StandaloneExample.java | 16 ++- examples/tencent-logback-example/pom.xml | 2 +- .../logger/log4j2/Log4j2TraceRootLogger.java | 16 +++ .../logback/LogbackTraceRootLogger.java | 34 +++--- .../SpanLogCounterCleanupProcessor.java | 39 +++++++ .../traceroot/sdk/tracer/TraceRootTracer.java | 3 + .../traceroot/sdk/utils/SpanLogCounter.java | 108 ++++++++++++++++++ 15 files changed, 212 insertions(+), 39 deletions(-) create mode 100644 src/main/java/ai/traceroot/sdk/tracer/SpanLogCounterCleanupProcessor.java create mode 100644 src/main/java/ai/traceroot/sdk/utils/SpanLogCounter.java diff --git a/examples/log4j2-local-example/src/main/java/com/example/StandaloneExample.java b/examples/log4j2-local-example/src/main/java/com/example/StandaloneExample.java index d4eb7c3..1657850 100644 --- a/examples/log4j2-local-example/src/main/java/com/example/StandaloneExample.java +++ b/examples/log4j2-local-example/src/main/java/com/example/StandaloneExample.java @@ -75,8 +75,14 @@ private static void initializeTraceRoot(Dotenv dotenv) { .enableSpanCloudExport(true) // Enable tracing export to OTLP endpoint .enableLogCloudExport(false) // Disable cloud export for logs (local file only) .logLevel(LogLevel.TRACE) - .otlpEndpoint(dotenv.get("TRACEROOT_OTLP_ENDPOINT", System.getenv("TRACEROOT_OTLP_ENDPOINT"))) // Get from .env or environment - .rootPath(dotenv.get("TRACEROOT_ROOT_PATH", System.getenv("TRACEROOT_ROOT_PATH"))) // Get from .env or environment + .otlpEndpoint( + dotenv.get( + "TRACEROOT_OTLP_ENDPOINT", + System.getenv("TRACEROOT_OTLP_ENDPOINT"))) // Get from .env or environment + .rootPath( + dotenv.get( + "TRACEROOT_ROOT_PATH", + System.getenv("TRACEROOT_ROOT_PATH"))) // Get from .env or environment .build(); // Initialize SDK (similar to Sentry.init(options)) diff --git a/examples/logback-local-example/pom.xml b/examples/logback-local-example/pom.xml index 2588c90..1edf067 100644 --- a/examples/logback-local-example/pom.xml +++ b/examples/logback-local-example/pom.xml @@ -24,7 +24,7 @@ ai.traceroot traceroot-sdk-java - 0.0.1-alpha.1 + 0.0.1-alpha.3 diff --git a/examples/spring-boot-example/.env.example b/examples/spring-boot-example/.env.example index 96f8154..12cf2be 100644 --- a/examples/spring-boot-example/.env.example +++ b/examples/spring-boot-example/.env.example @@ -1,5 +1,5 @@ # The token for the TraceRoot API -TRACEROOT_TOKEN=your_traceroot_token_here +TRACEROOT_TOKEN=traceroot-7c7c3b05e9ce300ca77fc4b0e72751cc # Enable cloud export of spans (default: `true`) TRACEROOT_ENABLE_SPAN_CLOUD_EXPORT=true @@ -8,4 +8,4 @@ TRACEROOT_ENABLE_SPAN_CLOUD_EXPORT=true TRACEROOT_ENABLE_LOG_CLOUD_EXPORT=true # Absolute path to your project root directory -TRACEROOT_ROOT_PATH=your_project_path_here +TRACEROOT_ROOT_PATH=/Users/zecheng/code/traceroot-sdk-java diff --git a/examples/spring-boot-example/pom.xml b/examples/spring-boot-example/pom.xml index 3d918ea..4261dd9 100644 --- a/examples/spring-boot-example/pom.xml +++ b/examples/spring-boot-example/pom.xml @@ -35,7 +35,7 @@ ai.traceroot traceroot-sdk-java - 0.0.1-alpha.1 + 0.0.1-alpha.3 diff --git a/examples/spring-boot-example/src/main/java/com/example/App.java b/examples/spring-boot-example/src/main/java/com/example/App.java index c239b80..29151b0 100644 --- a/examples/spring-boot-example/src/main/java/com/example/App.java +++ b/examples/spring-boot-example/src/main/java/com/example/App.java @@ -40,14 +40,8 @@ public void initializeTraceRoot() { .region("us-west-2") .enableSpanConsoleExport(false) .enableLogConsoleExport(true) - .enableSpanCloudExport( - dotenv.get( - "TRACEROOT_ENABLE_SPAN_CLOUD_EXPORT", - System.getenv("TRACEROOT_ENABLE_SPAN_CLOUD_EXPORT"))) - .enableLogCloudExport( - dotenv.get( - "TRACEROOT_ENABLE_LOG_CLOUD_EXPORT", - System.getenv("TRACEROOT_ENABLE_LOG_CLOUD_EXPORT"))) + .enableSpanCloudExport(true) + .enableLogCloudExport(true) .localMode(false) .logLevel(LogLevel.INFO) .tracerVerbose(true) diff --git a/examples/spring-boot-example/src/main/java/com/example/TaskController.java b/examples/spring-boot-example/src/main/java/com/example/TaskController.java index 0cb74d4..eef096a 100644 --- a/examples/spring-boot-example/src/main/java/com/example/TaskController.java +++ b/examples/spring-boot-example/src/main/java/com/example/TaskController.java @@ -127,6 +127,7 @@ public void delete(@PathVariable("id") Long id) { // Minimal error handling @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(TaskNotFound.class) + @Trace(spanName = "handle-task-not-found") public String notFound(TaskNotFound ex) { logger.error("Task not found error: {}", ex.getMessage()); return ex.getMessage(); diff --git a/examples/standalone-example/pom.xml b/examples/standalone-example/pom.xml index a000618..263e692 100644 --- a/examples/standalone-example/pom.xml +++ b/examples/standalone-example/pom.xml @@ -24,7 +24,7 @@ ai.traceroot traceroot-sdk-java - 0.0.1-alpha.1 + 0.0.1-alpha.3 diff --git a/examples/tencent-log4j2-example/pom.xml b/examples/tencent-log4j2-example/pom.xml index 5ab1e7f..871d62f 100644 --- a/examples/tencent-log4j2-example/pom.xml +++ b/examples/tencent-log4j2-example/pom.xml @@ -23,7 +23,7 @@ ai.traceroot traceroot-sdk-java - 0.0.1-alpha.1 + 0.0.1-alpha.3 diff --git a/examples/tencent-log4j2-example/src/main/java/com/example/StandaloneExample.java b/examples/tencent-log4j2-example/src/main/java/com/example/StandaloneExample.java index 070dd2a..d0e7fc2 100644 --- a/examples/tencent-log4j2-example/src/main/java/com/example/StandaloneExample.java +++ b/examples/tencent-log4j2-example/src/main/java/com/example/StandaloneExample.java @@ -71,18 +71,26 @@ private static void initializeTraceRoot(Dotenv dotenv) { .enableSpanCloudExport(true) // Enable for cloud export .enableLogCloudExport(true) // Enable for Tencent CLS export .logLevel(LogLevel.TRACE) - .rootPath(dotenv.get("TRACEROOT_ROOT_PATH", System.getenv("TRACEROOT_ROOT_PATH"))) // Get from .env or environment + .rootPath( + dotenv.get( + "TRACEROOT_ROOT_PATH", + System.getenv("TRACEROOT_ROOT_PATH"))) // Get from .env or environment .build(); // Configure Tencent Cloud credentials (required for Tencent provider) TencentCredentials tencentCredentials = new TencentCredentials(); tencentCredentials.setSecretId( - dotenv.get("TENCENT_SECRET_ID", System.getenv("TENCENT_SECRET_ID"))); // Get from .env or environment + dotenv.get( + "TENCENT_SECRET_ID", + System.getenv("TENCENT_SECRET_ID"))); // Get from .env or environment tencentCredentials.setSecretKey( - dotenv.get("TENCENT_SECRET_KEY", System.getenv("TENCENT_SECRET_KEY"))); // Get from .env or environment + dotenv.get( + "TENCENT_SECRET_KEY", + System.getenv("TENCENT_SECRET_KEY"))); // Get from .env or environment tencentCredentials.setRegion("ap-hongkong"); // Optional: defaults to ap-hongkong tencentCredentials.setLogset( - dotenv.get("TENCENT_LOGSET", System.getenv("TENCENT_LOGSET"))); // Get from .env or environment + dotenv.get( + "TENCENT_LOGSET", System.getenv("TENCENT_LOGSET"))); // Get from .env or environment tencentCredentials.setTraceToken( dotenv.get("TRACE_TOKEN", System.getenv("TRACE_TOKEN"))); // Get from .env or environment diff --git a/examples/tencent-logback-example/pom.xml b/examples/tencent-logback-example/pom.xml index a9f8277..66433a9 100644 --- a/examples/tencent-logback-example/pom.xml +++ b/examples/tencent-logback-example/pom.xml @@ -24,7 +24,7 @@ ai.traceroot traceroot-sdk-java - 0.0.1-alpha.1 + 0.0.1-alpha.3 diff --git a/src/main/java/ai/traceroot/sdk/logger/log4j2/Log4j2TraceRootLogger.java b/src/main/java/ai/traceroot/sdk/logger/log4j2/Log4j2TraceRootLogger.java index e745db0..26c8fec 100644 --- a/src/main/java/ai/traceroot/sdk/logger/log4j2/Log4j2TraceRootLogger.java +++ b/src/main/java/ai/traceroot/sdk/logger/log4j2/Log4j2TraceRootLogger.java @@ -4,6 +4,7 @@ import ai.traceroot.sdk.logger.TraceRootLoggerInterface; import ai.traceroot.sdk.types.LogLevel; import ai.traceroot.sdk.utils.LogAppenderUtils; +import ai.traceroot.sdk.utils.SpanLogCounter; import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -152,76 +153,91 @@ private static org.apache.logging.log4j.Level convertLogLevel(LogLevel logLevel) @Override public void trace(String message) { + SpanLogCounter.incrementTraceLogCount(); logWithTraceCorrelation(() -> logger.trace(message)); } @Override public void trace(String format, Object... args) { + SpanLogCounter.incrementTraceLogCount(); logWithTraceCorrelation(() -> logger.trace(format, args)); } @Override public void trace(String message, Throwable throwable) { + SpanLogCounter.incrementTraceLogCount(); logWithTraceCorrelation(() -> logger.trace(message, throwable)); } @Override public void debug(String message) { + SpanLogCounter.incrementDebugLogCount(); logWithTraceCorrelation(() -> logger.debug(message)); } @Override public void debug(String format, Object... args) { + SpanLogCounter.incrementDebugLogCount(); logWithTraceCorrelation(() -> logger.debug(format, args)); } @Override public void debug(String message, Throwable throwable) { + SpanLogCounter.incrementDebugLogCount(); logWithTraceCorrelation(() -> logger.debug(message, throwable)); } @Override public void info(String message) { + SpanLogCounter.incrementInfoLogCount(); logWithTraceCorrelation(() -> logger.info(message)); } @Override public void info(String format, Object... args) { + SpanLogCounter.incrementInfoLogCount(); logWithTraceCorrelation(() -> logger.info(format, args)); } @Override public void info(String message, Throwable throwable) { + SpanLogCounter.incrementInfoLogCount(); logWithTraceCorrelation(() -> logger.info(message, throwable)); } @Override public void warn(String message) { + SpanLogCounter.incrementWarnLogCount(); logWithTraceCorrelation(() -> logger.warn(message)); } @Override public void warn(String format, Object... args) { + SpanLogCounter.incrementWarnLogCount(); logWithTraceCorrelation(() -> logger.warn(format, args)); } @Override public void warn(String message, Throwable throwable) { + SpanLogCounter.incrementWarnLogCount(); logWithTraceCorrelation(() -> logger.warn(message, throwable)); } @Override public void error(String message) { + SpanLogCounter.incrementErrorLogCount(); logWithTraceCorrelation(() -> logger.error(message)); } @Override public void error(String format, Object... args) { + SpanLogCounter.incrementErrorLogCount(); logWithTraceCorrelation(() -> logger.error(format, args)); } @Override public void error(String message, Throwable throwable) { + SpanLogCounter.incrementErrorLogCount(); logWithTraceCorrelation(() -> logger.error(message, throwable)); } diff --git a/src/main/java/ai/traceroot/sdk/logger/logback/LogbackTraceRootLogger.java b/src/main/java/ai/traceroot/sdk/logger/logback/LogbackTraceRootLogger.java index da3e20a..815978d 100644 --- a/src/main/java/ai/traceroot/sdk/logger/logback/LogbackTraceRootLogger.java +++ b/src/main/java/ai/traceroot/sdk/logger/logback/LogbackTraceRootLogger.java @@ -5,6 +5,7 @@ import ai.traceroot.sdk.types.LogLevel; import ai.traceroot.sdk.utils.LogAppenderUtils; import ai.traceroot.sdk.utils.ProviderValidationUtils; +import ai.traceroot.sdk.utils.SpanLogCounter; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import java.util.Map; @@ -79,21 +80,6 @@ private static void setupJsonConsoleAppender(LoggerContext context, TraceRootCon try { Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME); - // Debug: print all appenders - System.out.println("[TraceRoot] Checking appenders..."); - java.util.Iterator> - debugIter = rootLogger.iteratorForAppenders(); - while (debugIter.hasNext()) { - ch.qos.logback.core.Appender app = - debugIter.next(); - System.out.println( - "[TraceRoot] Found appender: " - + app.getName() - + " (type: " - + app.getClass().getName() - + ")"); - } - // Iterate through existing appenders to wrap FILE appenders with TraceRoot format java.util.List> appendersToWrap = new java.util.ArrayList<>(); @@ -106,7 +92,6 @@ private static void setupJsonConsoleAppender(LoggerContext context, TraceRootCon // Wrap FILE appenders to add TraceRoot JSON format if ("FILE".equals(appender.getName()) && !(appender instanceof LogbackFileAppenderWrapper)) { - System.out.println("[TraceRoot] Will wrap FILE appender: " + appender.getName()); appendersToWrap.add(appender); } } @@ -114,7 +99,6 @@ private static void setupJsonConsoleAppender(LoggerContext context, TraceRootCon // Wrap the FILE appenders for (ch.qos.logback.core.Appender appender : appendersToWrap) { - System.out.println("[TraceRoot] Wrapping appender: " + appender.getName()); // Remove the original appender rootLogger.detachAppender(appender); @@ -127,7 +111,6 @@ private static void setupJsonConsoleAppender(LoggerContext context, TraceRootCon // Add the wrapped appender rootLogger.addAppender(wrapper); - System.out.println("[TraceRoot] Added wrapped FILE appender"); } } catch (Exception e) { @@ -200,76 +183,91 @@ private static ch.qos.logback.classic.Level convertLogLevel(LogLevel logLevel) { @Override public void trace(String message) { + SpanLogCounter.incrementTraceLogCount(); logWithTraceCorrelation(() -> logger.trace(message)); } @Override public void trace(String format, Object... args) { + SpanLogCounter.incrementTraceLogCount(); logWithTraceCorrelation(() -> logger.trace(format, args)); } @Override public void trace(String message, Throwable throwable) { + SpanLogCounter.incrementTraceLogCount(); logWithTraceCorrelation(() -> logger.trace(message, throwable)); } @Override public void debug(String message) { + SpanLogCounter.incrementDebugLogCount(); logWithTraceCorrelation(() -> logger.debug(message)); } @Override public void debug(String format, Object... args) { + SpanLogCounter.incrementDebugLogCount(); logWithTraceCorrelation(() -> logger.debug(format, args)); } @Override public void debug(String message, Throwable throwable) { + SpanLogCounter.incrementDebugLogCount(); logWithTraceCorrelation(() -> logger.debug(message, throwable)); } @Override public void info(String message) { + SpanLogCounter.incrementInfoLogCount(); logWithTraceCorrelation(() -> logger.info(message)); } @Override public void info(String format, Object... args) { + SpanLogCounter.incrementInfoLogCount(); logWithTraceCorrelation(() -> logger.info(format, args)); } @Override public void info(String message, Throwable throwable) { + SpanLogCounter.incrementInfoLogCount(); logWithTraceCorrelation(() -> logger.info(message, throwable)); } @Override public void warn(String message) { + SpanLogCounter.incrementWarnLogCount(); logWithTraceCorrelation(() -> logger.warn(message)); } @Override public void warn(String format, Object... args) { + SpanLogCounter.incrementWarnLogCount(); logWithTraceCorrelation(() -> logger.warn(format, args)); } @Override public void warn(String message, Throwable throwable) { + SpanLogCounter.incrementWarnLogCount(); logWithTraceCorrelation(() -> logger.warn(message, throwable)); } @Override public void error(String message) { + SpanLogCounter.incrementErrorLogCount(); logWithTraceCorrelation(() -> logger.error(message)); } @Override public void error(String format, Object... args) { + SpanLogCounter.incrementErrorLogCount(); logWithTraceCorrelation(() -> logger.error(format, args)); } @Override public void error(String message, Throwable throwable) { + SpanLogCounter.incrementErrorLogCount(); logWithTraceCorrelation(() -> logger.error(message, throwable)); } diff --git a/src/main/java/ai/traceroot/sdk/tracer/SpanLogCounterCleanupProcessor.java b/src/main/java/ai/traceroot/sdk/tracer/SpanLogCounterCleanupProcessor.java new file mode 100644 index 0000000..f658adc --- /dev/null +++ b/src/main/java/ai/traceroot/sdk/tracer/SpanLogCounterCleanupProcessor.java @@ -0,0 +1,39 @@ +package ai.traceroot.sdk.tracer; + +import ai.traceroot.sdk.utils.SpanLogCounter; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SpanProcessor; + +/** + * SpanProcessor that cleans up log counters when spans end + * + *

This prevents memory leaks in the SpanLogCounter's internal counter map by removing entries + * for spans that have finished. + */ +public class SpanLogCounterCleanupProcessor implements SpanProcessor { + + @Override + public void onStart(Context parentContext, ReadWriteSpan span) { + // Nothing to do on start + } + + @Override + public boolean isStartRequired() { + return false; + } + + @Override + public void onEnd(ReadableSpan span) { + // Clean up the counters for this span + if (span != null && span.getSpanContext().isValid()) { + SpanLogCounter.cleanupSpan(span.getSpanContext().getSpanId()); + } + } + + @Override + public boolean isEndRequired() { + return true; + } +} diff --git a/src/main/java/ai/traceroot/sdk/tracer/TraceRootTracer.java b/src/main/java/ai/traceroot/sdk/tracer/TraceRootTracer.java index 75a54c8..e2685fe 100644 --- a/src/main/java/ai/traceroot/sdk/tracer/TraceRootTracer.java +++ b/src/main/java/ai/traceroot/sdk/tracer/TraceRootTracer.java @@ -365,6 +365,9 @@ private void setupTracing() { } } + // Add cleanup processor to prevent memory leaks in SpanLogCounter + tracerProviderBuilder.addSpanProcessor(new SpanLogCounterCleanupProcessor()); + // Build tracer provider tracerProvider = tracerProviderBuilder.build(); diff --git a/src/main/java/ai/traceroot/sdk/utils/SpanLogCounter.java b/src/main/java/ai/traceroot/sdk/utils/SpanLogCounter.java new file mode 100644 index 0000000..94484cb --- /dev/null +++ b/src/main/java/ai/traceroot/sdk/utils/SpanLogCounter.java @@ -0,0 +1,108 @@ +package ai.traceroot.sdk.utils; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Utility class for incrementing log count attributes on the current span + * + *

This tracks the number of logs at each level (trace, debug, info, warn, error) for the current + * span, similar to the Python SDK implementation. + * + *

Since OpenTelemetry Java doesn't allow reading attribute values from spans, we maintain an + * internal counter map keyed by span ID. When a span ends, the counter is removed. + */ +public class SpanLogCounter { + + // Map of span ID to log counters for that span + private static final Map> spanCounters = new ConcurrentHashMap<>(); + + private SpanLogCounter() { + // Utility class + } + + /** Increment the trace log count for the current span */ + public static void incrementTraceLogCount() { + incrementLogCount("num_trace_logs"); + } + + /** Increment the debug log count for the current span */ + public static void incrementDebugLogCount() { + incrementLogCount("num_debug_logs"); + } + + /** Increment the info log count for the current span */ + public static void incrementInfoLogCount() { + incrementLogCount("num_info_logs"); + } + + /** Increment the warning log count for the current span */ + public static void incrementWarnLogCount() { + incrementLogCount("num_warning_logs"); + } + + /** Increment the error log count for the current span */ + public static void incrementErrorLogCount() { + incrementLogCount("num_error_logs"); + } + + /** + * Increment the log count attribute for the current span + * + * @param attributeName the name of the attribute to increment + */ + private static void incrementLogCount(String attributeName) { + try { + Span span = Span.current(); + if (span != null && span.isRecording()) { + SpanContext spanContext = span.getSpanContext(); + + // Only increment if we have a valid span (not invalid/noop span) + if (spanContext.isValid()) { + String spanId = spanContext.getSpanId(); + + // Get or create counter map for this span + Map counters = + spanCounters.computeIfAbsent(spanId, k -> new ConcurrentHashMap<>()); + + // Increment the counter + Long newCount = counters.merge(attributeName, 1L, Long::sum); + + // Set the attribute on the span + span.setAttribute(attributeName, newCount); + } + } + } catch (Exception e) { + // Don't let span attribute errors interfere with logging + // Silently ignore + } + } + + /** + * Clean up counters for a span (call this when a span ends) + * + * @param spanId the span ID to clean up + */ + public static void cleanupSpan(String spanId) { + if (spanId != null) { + spanCounters.remove(spanId); + } + } + + /** + * Get current count for testing purposes + * + * @param spanId the span ID + * @param attributeName the attribute name + * @return the current count, or 0 if not found + */ + static long getCount(String spanId, String attributeName) { + Map counters = spanCounters.get(spanId); + if (counters != null) { + return counters.getOrDefault(attributeName, 0L); + } + return 0L; + } +}