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;
+ }
+}