google-site-verification: google61fe8ba583a51912.html
Google Cloud Trace in Java: Complete Implementation Guide

Google Cloud Trace is a distributed tracing system that helps gather timing data needed to troubleshoot latency problems in microservices architectures. Here's a comprehensive implementation guide for using Cloud Trace in Java applications.

Project Setup

Dependencies

pom.xml

<properties> <google-cloud-bom.version>26.22.0</google-cloud-bom.version> <opentelemetry.version>1.31.0</opentelemetry.version> </properties> <dependencies> <!-- Google Cloud Trace --> <dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-trace</artifactId> </dependency> <!-- OpenTelemetry for distributed tracing --> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-api</artifactId> <version>${opentelemetry.version}</version> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk</artifactId> <version>${opentelemetry.version}</version> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-jaeger</artifactId> <version>${opentelemetry.version}</version> </dependency> <!-- HTTP Client for manual trace reporting --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Spring Boot Starter (if using Spring) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Testing --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>com.google.cloud</groupId> <artifactId>libraries-bom</artifactId> <version>${google-cloud-bom.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

Core Implementation

1. Configuration Models

TraceConfig.java - Configuration for Cloud Trace

import java.time.Duration; import java.util.HashMap; import java.util.Map; public class TraceConfig { private final String projectId; private final double samplingRate; private final Duration bufferTimeout; private final int bufferSize; private final Map<String, String> defaultAttributes; private final boolean enabled; private TraceConfig(Builder builder) { this.projectId = builder.projectId; this.samplingRate = builder.samplingRate; this.bufferTimeout = builder.bufferTimeout; this.bufferSize = builder.bufferSize; this.defaultAttributes = Map.copyOf(builder.defaultAttributes); this.enabled = builder.enabled; } public static class Builder { private String projectId; private double samplingRate = 0.1; // 10% sampling by default private Duration bufferTimeout = Duration.ofSeconds(5); private int bufferSize = 1000; private Map<String, String> defaultAttributes = new HashMap<>(); private boolean enabled = true; public Builder projectId(String projectId) { this.projectId = projectId; return this; } public Builder samplingRate(double samplingRate) { if (samplingRate < 0.0 || samplingRate > 1.0) { throw new IllegalArgumentException("Sampling rate must be between 0.0 and 1.0"); } this.samplingRate = samplingRate; return this; } public Builder bufferTimeout(Duration bufferTimeout) { this.bufferTimeout = bufferTimeout; return this; } public Builder bufferSize(int bufferSize) { this.bufferSize = bufferSize; return this; } public Builder defaultAttribute(String key, String value) { this.defaultAttributes.put(key, value); return this; } public Builder defaultAttributes(Map<String, String> attributes) { this.defaultAttributes.putAll(attributes); return this; } public Builder enabled(boolean enabled) { this.enabled = enabled; return this; } public TraceConfig build() { if (projectId == null || projectId.trim().isEmpty()) { throw new IllegalStateException("Project ID is required"); } return new TraceConfig(this); } } // Getters public String getProjectId() { return projectId; } public double getSamplingRate() { return samplingRate; } public Duration getBufferTimeout() { return bufferTimeout; } public int getBufferSize() { return bufferSize; } public Map<String, String> getDefaultAttributes() { return defaultAttributes; } public boolean isEnabled() { return enabled; } }

2. Trace Data Models

TraceSpan.java - Represents a trace span

import java.time.Instant; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; public class TraceSpan { private final String traceId; private final String spanId; private final String parentSpanId; private final String name; private final Instant startTime; private final Instant endTime; private final Map<String, String> attributes; private final SpanKind kind; private final Status status; public enum SpanKind { SPAN_KIND_UNSPECIFIED, INTERNAL, SERVER, CLIENT, PRODUCER, CONSUMER } public static class Status { private final int code; private final String message; public Status(int code, String message) { this.code = code; this.message = message; } public static Status ok() { return new Status(0, "OK"); } public static Status error(String message) { return new Status(2, message); } // Getters public int getCode() { return code; } public String getMessage() { return message; } } private TraceSpan(Builder builder) { this.traceId = builder.traceId; this.spanId = builder.spanId; this.parentSpanId = builder.parentSpanId; this.name = builder.name; this.startTime = builder.startTime; this.endTime = builder.endTime; this.attributes = Collections.unmodifiableMap(new HashMap<>(builder.attributes)); this.kind = builder.kind; this.status = builder.status; } public static class Builder { private String traceId; private String spanId; private String parentSpanId; private String name; private Instant startTime; private Instant endTime; private Map<String, String> attributes = new HashMap<>(); private SpanKind kind = SpanKind.INTERNAL; private Status status = Status.ok(); public Builder traceId(String traceId) { this.traceId = traceId; return this; } public Builder spanId(String spanId) { this.spanId = spanId; return this; } public Builder parentSpanId(String parentSpanId) { this.parentSpanId = parentSpanId; return this; } public Builder name(String name) { this.name = name; return this; } public Builder startTime(Instant startTime) { this.startTime = startTime; return this; } public Builder endTime(Instant endTime) { this.endTime = endTime; return this; } public Builder attribute(String key, String value) { this.attributes.put(key, value); return this; } public Builder attributes(Map<String, String> attributes) { this.attributes.putAll(attributes); return this; } public Builder kind(SpanKind kind) { this.kind = kind; return this; } public Builder status(Status status) { this.status = status; return this; } public TraceSpan build() { Objects.requireNonNull(traceId, "traceId is required"); Objects.requireNonNull(spanId, "spanId is required"); Objects.requireNonNull(name, "name is required"); Objects.requireNonNull(startTime, "startTime is required"); Objects.requireNonNull(endTime, "endTime is required"); if (startTime.isAfter(endTime)) { throw new IllegalStateException("startTime must be before endTime"); } return new TraceSpan(this); } } // Getters public String getTraceId() { return traceId; } public String getSpanId() { return spanId; } public String getParentSpanId() { return parentSpanId; } public String getName() { return name; } public Instant getStartTime() { return startTime; } public Instant getEndTime() { return endTime; } public Map<String, String> getAttributes() { return attributes; } public SpanKind getKind() { return kind; } public Status getStatus() { return status; } public long getDurationMs() { return endTime.toEpochMilli() - startTime.toEpochMilli(); } public boolean isRoot() { return parentSpanId == null || parentSpanId.isEmpty(); } }

3. Cloud Trace Client

GoogleCloudTraceClient.java - Main client for Cloud Trace operations

import com.google.cloud.trace.v2.TraceServiceClient; import com.google.devtools.cloudtrace.v2.Span; import com.google.devtools.cloudtrace.v2.TruncatableString; import com.google.protobuf.Timestamp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; public class GoogleCloudTraceClient implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(GoogleCloudTraceClient.class); private final TraceConfig config; private final TraceServiceClient traceServiceClient; private final BlockingQueue<TraceSpan> spanQueue; private final Thread batchProcessorThread; private volatile boolean running; public GoogleCloudTraceClient(TraceConfig config) throws IOException { this.config = config; this.traceServiceClient = TraceServiceClient.create(); this.spanQueue = new LinkedBlockingQueue<>(config.getBufferSize()); this.running = true; this.batchProcessorThread = new Thread(this::processBatch, "cloud-trace-processor"); this.batchProcessorThread.setDaemon(true); this.batchProcessorThread.start(); } public void recordSpan(TraceSpan traceSpan) { if (!config.isEnabled()) { return; } // Apply sampling if (Math.random() > config.getSamplingRate()) { return; } if (!spanQueue.offer(traceSpan)) { logger.warn("Trace span queue is full, dropping span: {}", traceSpan.getName()); } } private void processBatch() { List<TraceSpan> batch = new ArrayList<>(); while (running || !spanQueue.isEmpty()) { try { // Wait for spans with timeout TraceSpan span = spanQueue.poll(100, TimeUnit.MILLISECONDS); if (span != null) { batch.add(span); // Send batch when full or timeout reached if (batch.size() >= 100) { // Cloud Trace batch limit sendBatch(batch); batch.clear(); } } // Send batch periodically if (!batch.isEmpty() && spanQueue.isEmpty()) { sendBatch(batch); batch.clear(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } // Final batch if (!batch.isEmpty()) { sendBatch(batch); } } private void sendBatch(List<TraceSpan> spans) { if (spans.isEmpty()) { return; } try { List<Span> cloudTraceSpans = convertToCloudTraceSpans(spans); // Group by trace ID spans.stream() .collect(java.util.stream.Collectors.groupingBy(TraceSpan::getTraceId)) .forEach((traceId, traceSpans) -> { try { String projectName = "projects/" + config.getProjectId(); // Create batch write request TraceServiceClient.BatchWriteSpansRequest request = TraceServiceClient.BatchWriteSpansRequest.newBuilder() .setName(projectName) .addAllSpans(convertToCloudTraceSpans(traceSpans)) .build(); traceServiceClient.batchWriteSpans(request); logger.debug("Sent {} spans for trace {}", traceSpans.size(), traceId); } catch (Exception e) { logger.error("Failed to send spans for trace {}: {}", traceId, e.getMessage()); } }); } catch (Exception e) { logger.error("Failed to send trace batch: {}", e.getMessage()); } } private List<Span> convertToCloudTraceSpans(List<TraceSpan> spans) { return spans.stream() .map(this::convertToCloudTraceSpan) .toList(); } private Span convertToCloudTraceSpan(TraceSpan traceSpan) { Span.Builder spanBuilder = Span.newBuilder() .setName(String.format("projects/%s/traces/%s/spans/%s", config.getProjectId(), traceSpan.getTraceId(), traceSpan.getSpanId())) .setSpanId(traceSpan.getSpanId()) .setDisplayName(TruncatableString.newBuilder() .setValue(traceSpan.getName()) .setTruncatedByteCount(0) .build()) .setStartTime(convertToTimestamp(traceSpan.getStartTime())) .setEndTime(convertToTimestamp(traceSpan.getEndTime())); // Set parent span if available if (traceSpan.getParentSpanId() != null && !traceSpan.getParentSpanId().isEmpty()) { spanBuilder.setParentSpanId(traceSpan.getParentSpanId()); } // Add attributes Span.Attributes.Builder attributesBuilder = Span.Attributes.newBuilder(); traceSpan.getAttributes().forEach((key, value) -> { attributesBuilder.putAttributeMap(key, Span.AttributeValue.newBuilder() .setStringValue(TruncatableString.newBuilder() .setValue(value) .build()) .build()); }); // Add default attributes config.getDefaultAttributes().forEach((key, value) -> { attributesBuilder.putAttributeMap(key, Span.AttributeValue.newBuilder() .setStringValue(TruncatableString.newBuilder() .setValue(value) .build()) .build()); }); spanBuilder.setAttributes(attributesBuilder); // Set status if (traceSpan.getStatus() != null) { spanBuilder.setStatus(com.google.rpc.Status.newBuilder() .setCode(traceSpan.getStatus().getCode()) .setMessage(traceSpan.getStatus().getMessage()) .build()); } return spanBuilder.build(); } private Timestamp convertToTimestamp(Instant instant) { return Timestamp.newBuilder() .setSeconds(instant.getEpochSecond()) .setNanos(instant.getNano()) .build(); } @Override public void close() throws IOException { running = false; try { batchProcessorThread.join(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } if (traceServiceClient != null) { traceServiceClient.close(); } logger.info("Google Cloud Trace client closed"); } }

4. Trace Context Management

TraceContext.java - Manages trace context propagation

import org.slf4j.MDC; import java.util.Optional; import java.util.concurrent.Callable; public class TraceContext { private static final String TRACE_ID_KEY = "X-Cloud-Trace-Context"; private static final String SPAN_ID_KEY = "current-span-id"; private static final ThreadLocal<TraceContext> currentContext = new ThreadLocal<>(); private final String traceId; private final String spanId; private final String parentSpanId; private final boolean sampled; public TraceContext(String traceId, String spanId, String parentSpanId, boolean sampled) { this.traceId = traceId; this.spanId = spanId; this.parentSpanId = parentSpanId; this.sampled = sampled; } public static TraceContext create() { return create(null); } public static TraceContext create(String parentTraceContext) { String traceId; String parentSpanId = null; boolean sampled = true; if (parentTraceContext != null && !parentTraceContext.isEmpty()) { // Parse existing trace context (format: TRACE_ID/SPAN_ID;o=TRACE_TRUE) String[] parts = parentTraceContext.split("/"); if (parts.length >= 1) { traceId = parts[0]; if (parts.length >= 2) { String[] spanParts = parts[1].split(";"); parentSpanId = spanParts[0]; // Parse sampling flag if (spanParts.length > 1) { sampled = spanParts[1].contains("o=1"); } } } else { traceId = generateTraceId(); } } else { traceId = generateTraceId(); } String spanId = generateSpanId(); return new TraceContext(traceId, spanId, parentSpanId, sampled); } public static Optional<TraceContext> getCurrent() { return Optional.ofNullable(currentContext.get()); } public static void setCurrent(TraceContext context) { currentContext.set(context); // Update MDC for logging if (context != null) { MDC.put(TRACE_ID_KEY, context.getTraceId()); MDC.put(SPAN_ID_KEY, context.getSpanId()); } else { MDC.remove(TRACE_ID_KEY); MDC.remove(SPAN_ID_KEY); } } public static void clear() { currentContext.remove(); MDC.remove(TRACE_ID_KEY); MDC.remove(SPAN_ID_KEY); } public static <T> T withContext(TraceContext context, Callable<T> callable) { TraceContext previous = currentContext.get(); setCurrent(context); try { return callable.call(); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new RuntimeException(e); } finally { setCurrent(previous); } } public static void withContext(TraceContext context, Runnable runnable) { TraceContext previous = currentContext.get(); setCurrent(context); try { runnable.run(); } finally { setCurrent(previous); } } public String toHeaderValue() { StringBuilder sb = new StringBuilder(); sb.append(traceId); sb.append("/"); sb.append(spanId); sb.append(";o="); sb.append(sampled ? "1" : "0"); return sb.toString(); } private static String generateTraceId() { return String.format("%032x", java.util.UUID.randomUUID().getLeastSignificantBits()); } private static String generateSpanId() { return String.format("%016x", java.util.UUID.randomUUID().getLeastSignificantBits()); } // Getters public String getTraceId() { return traceId; } public String getSpanId() { return spanId; } public String getParentSpanId() { return parentSpanId; } public boolean isSampled() { return sampled; } }

5. Tracer Implementation

CloudTracer.java - Main tracer class for creating spans

import java.time.Instant; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; public class CloudTracer { private final GoogleCloudTraceClient traceClient; private final TraceConfig config; public CloudTracer(TraceConfig config) { this.config = config; try { this.traceClient = new GoogleCloudTraceClient(config); } catch (Exception e) { throw new RuntimeException("Failed to initialize Cloud Trace client", e); } } public SpanBuilder spanBuilder(String name) { return new SpanBuilder(name); } public <T> T trace(String name, Callable<T> callable) { return trace(name, Map.of(), callable); } public <T> T trace(String name, Map<String, String> attributes, Callable<T> callable) { Span span = spanBuilder(name).start(); try { T result = callable.call(); span.setStatus(TraceSpan.Status.ok()); return result; } catch (Exception e) { span.setStatus(TraceSpan.Status.error(e.getMessage())); span.setAttribute("error", "true"); span.setAttribute("error.message", e.getMessage()); span.setAttribute("error.type", e.getClass().getName()); throw new RuntimeException(e); } finally { span.end(); } } public void trace(String name, Runnable runnable) { trace(name, Map.of(), runnable); } public void trace(String name, Map<String, String> attributes, Runnable runnable) { Span span = spanBuilder(name).start(); try { runnable.run(); span.setStatus(TraceSpan.Status.ok()); } catch (Exception e) { span.setStatus(TraceSpan.Status.error(e.getMessage())); span.setAttribute("error", "true"); span.setAttribute("error.message", e.getMessage()); span.setAttribute("error.type", e.getClass().getName()); throw e; } finally { span.end(); } } public class SpanBuilder { private final String name; private TraceSpan.SpanKind kind = TraceSpan.SpanKind.INTERNAL; private Map<String, String> attributes = new HashMap<>(); private String parentSpanId; public SpanBuilder(String name) { this.name = name; } public SpanBuilder kind(TraceSpan.SpanKind kind) { this.kind = kind; return this; } public SpanBuilder attribute(String key, String value) { this.attributes.put(key, value); return this; } public SpanBuilder attributes(Map<String, String> attributes) { this.attributes.putAll(attributes); return this; } public SpanBuilder parentSpanId(String parentSpanId) { this.parentSpanId = parentSpanId; return this; } public Span start() { TraceContext currentContext = TraceContext.getCurrent().orElse(null); String traceId; String spanId = generateSpanId(); if (currentContext != null) { traceId = currentContext.getTraceId(); if (parentSpanId == null) { parentSpanId = currentContext.getSpanId(); } } else { traceId = generateTraceId(); } // Create new context for this span TraceContext spanContext = new TraceContext(traceId, spanId, parentSpanId, true); return new Span(traceId, spanId, parentSpanId, name, kind, attributes, spanContext); } } public class Span { private final String traceId; private final String spanId; private final String parentSpanId; private final String name; private final TraceSpan.SpanKind kind; private final Map<String, String> attributes; private final TraceContext context; private final Instant startTime; private Instant endTime; private TraceSpan.Status status = TraceSpan.Status.ok(); private Span(String traceId, String spanId, String parentSpanId, String name, TraceSpan.SpanKind kind, Map<String, String> attributes, TraceContext context) { this.traceId = traceId; this.spanId = spanId; this.parentSpanId = parentSpanId; this.name = name; this.kind = kind; this.attributes = new HashMap<>(attributes); this.context = context; this.startTime = Instant.now(); // Set this as current context TraceContext.setCurrent(context); } public Span setAttribute(String key, String value) { this.attributes.put(key, value); return this; } public Span setStatus(TraceSpan.Status status) { this.status = status; return this; } public void end() { this.endTime = Instant.now(); // Create trace span and send to Cloud Trace TraceSpan traceSpan = new TraceSpan.Builder() .traceId(traceId) .spanId(spanId) .parentSpanId(parentSpanId) .name(name) .startTime(startTime) .endTime(endTime) .attributes(attributes) .kind(kind) .status(status) .build(); traceClient.recordSpan(traceSpan); // Restore previous context if this was the current span if (context.equals(TraceContext.getCurrent().orElse(null))) { TraceContext.clear(); } } public String getTraceId() { return traceId; } public String getSpanId() { return spanId; } public Instant getStartTime() { return startTime; } } private String generateTraceId() { return String.format("%032x", java.util.UUID.randomUUID().getLeastSignificantBits()); } private String generateSpanId() { return String.format("%016x", java.util.UUID.randomUUID().getLeastSignificantBits()); } public void close() throws Exception { if (traceClient != null) { traceClient.close(); } } }

6. Spring Boot Integration

TraceAutoConfiguration.java - Spring Boot auto-configuration

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @ConditionalOnClass(CloudTracer.class) @EnableConfigurationProperties(TraceProperties.class) public class TraceAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnProperty(name = "google.cloud.trace.enabled", havingValue = "true", matchIfMissing = true) public TraceConfig traceConfig(TraceProperties properties) { return new TraceConfig.Builder() .projectId(properties.getProjectId()) .samplingRate(properties.getSamplingRate()) .bufferTimeout(properties.getBufferTimeout()) .bufferSize(properties.getBufferSize()) .defaultAttributes(properties.getDefaultAttributes()) .enabled(properties.isEnabled()) .build(); } @Bean @ConditionalOnMissingBean public CloudTracer cloudTracer(TraceConfig traceConfig) { return new CloudTracer(traceConfig); } @Bean @ConditionalOnMissingBean public TraceFilter traceFilter(CloudTracer tracer) { return new TraceFilter(tracer); } } // Configuration properties @ConfigurationProperties(prefix = "google.cloud.trace") public class TraceProperties { private String projectId; private double samplingRate = 0.1; private Duration bufferTimeout = Duration.ofSeconds(5); private int bufferSize = 1000; private Map<String, String> defaultAttributes = new HashMap<>(); private boolean enabled = true; // Getters and setters public String getProjectId() { return projectId; } public void setProjectId(String projectId) { this.projectId = projectId; } public double getSamplingRate() { return samplingRate; } public void setSamplingRate(double samplingRate) { this.samplingRate = samplingRate; } public Duration getBufferTimeout() { return bufferTimeout; } public void setBufferTimeout(Duration bufferTimeout) { this.bufferTimeout = bufferTimeout; } public int getBufferSize() { return bufferSize; } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } public Map<String, String> getDefaultAttributes() { return defaultAttributes; } public void setDefaultAttributes(Map<String, String> defaultAttributes) { this.defaultAttributes = defaultAttributes; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } }

TraceFilter.java - HTTP request tracing filter

import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; public class TraceFilter extends OncePerRequestFilter { private final CloudTracer tracer; public TraceFilter(CloudTracer tracer) { this.tracer = tracer; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String traceHeader = request.getHeader("X-Cloud-Trace-Context"); Map<String, String> attributes = Map.of( "http.method", request.getMethod(), "http.url", request.getRequestURL().toString(), "http.user_agent", request.getHeader("User-Agent"), "http.client_ip", getClientIp(request) ); tracer.trace("HTTP " + request.getMethod() + " " + request.getRequestURI(), attributes, () -> { try { // Set trace context in response TraceContext.getCurrent().ifPresent(context -> { response.setHeader("X-Cloud-Trace-Context", context.toHeaderValue()); }); filterChain.doFilter(request, response); // Add response attributes TraceContext.getCurrent().ifPresent(context -> { CloudTracer.Span span = (CloudTracer.Span) request.getAttribute("currentSpan"); if (span != null) { span.setAttribute("http.status_code", String.valueOf(response.getStatus())); span.setAttribute("http.response_size", String.valueOf(response.getBufferSize())); } }); } catch (Exception e) { throw new RuntimeException(e); } }); } private String getClientIp(HttpServletRequest request) { String xForwardedFor = request.getHeader("X-Forwarded-For"); if (xForwardedFor != null && !xForwardedFor.isEmpty()) { return xForwardedFor.split(",")[0].trim(); } return request.getRemoteAddr(); } }

7. Database Tracing

TracedDataSource.java - SQL query tracing

import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.Statement; import java.util.Map; public class TracedDataSource implements DataSource { private final DataSource delegate; private final CloudTracer tracer; public TracedDataSource(DataSource delegate, CloudTracer tracer) { this.delegate = delegate; this.tracer = tracer; } @Override public Connection getConnection() throws java.sql.SQLException { Connection connection = delegate.getConnection(); return new TracedConnection(connection, tracer); } // Implement other DataSource methods... private static class TracedConnection implements Connection { private final Connection delegate; private final CloudTracer tracer; public TracedConnection(Connection delegate, CloudTracer tracer) { this.delegate = delegate; this.tracer = tracer; } @Override public PreparedStatement prepareStatement(String sql) throws java.sql.SQLException { return new TracedPreparedStatement(delegate.prepareStatement(sql), sql, tracer); } @Override public Statement createStatement() throws java.sql.SQLException { return new TracedStatement(delegate.createStatement(), tracer); } // Implement other Connection methods... } private static class TracedPreparedStatement implements PreparedStatement { private final PreparedStatement delegate; private final String sql; private final CloudTracer tracer; public TracedPreparedStatement(PreparedStatement delegate, String sql, CloudTracer tracer) { this.delegate = delegate; this.sql = sql; this.tracer = tracer; } @Override public boolean execute() throws java.sql.SQLException { return tracer.trace("DB Query", Map.of( "db.statement", sql, "db.operation", extractOperation(sql) ), delegate::execute); } @Override public ResultSet executeQuery() throws java.sql.SQLException { return tracer.trace("DB Query", Map.of( "db.statement", sql, "db.operation", extractOperation(sql) ), delegate::executeQuery); } @Override public int executeUpdate() throws java.sql.SQLException { return tracer.trace("DB Update", Map.of( "db.statement", sql, "db.operation", extractOperation(sql) ), delegate::executeUpdate); } private String extractOperation(String sql) { if (sql == null) return "unknown"; String upperSql = sql.toUpperCase().trim(); if (upperSql.startsWith("SELECT")) return "SELECT"; if (upperSql.startsWith("INSERT")) return "INSERT"; if (upperSql.startsWith("UPDATE")) return "UPDATE"; if (upperSql.startsWith("DELETE")) return "DELETE"; return "OTHER"; } // Implement other PreparedStatement methods... } private static class TracedStatement implements Statement { private final Statement delegate; private final CloudTracer tracer; public TracedStatement(Statement delegate, CloudTracer tracer) { this.delegate = delegate; this.tracer = tracer; } @Override public boolean execute(String sql) throws java.sql.SQLException { return tracer.trace("DB Query", Map.of( "db.statement", sql, "db.operation", extractOperation(sql) ), () -> delegate.execute(sql)); } // Implement other Statement methods... } }

8. Demonstration and Usage

TraceDemo.java - Complete demonstration

import java.time.Duration; import java.util.Map; public class TraceDemo { public static void main(String[] args) { try { // Example 1: Basic configuration basicTracingExample(); // Example 2: Spring Boot integration springBootExample(); // Example 3: Complex workflow tracing workflowTracingExample(); } catch (Exception e) { System.err.println("Demo failed: " + e.getMessage()); e.printStackTrace(); } } private static void basicTracingExample() throws Exception { System.out.println("=== Basic Tracing Example ==="); TraceConfig config = new TraceConfig.Builder() .projectId("your-gcp-project-id") .samplingRate(1.0) // 100% for demo .bufferTimeout(Duration.ofSeconds(2)) .defaultAttribute("environment", "demo") .defaultAttribute("version", "1.0.0") .build(); try (CloudTracer tracer = new CloudTracer(config)) { // Simple traced operation String result = tracer.trace("demo-operation", () -> { // Simulate work Thread.sleep(100); return "Operation completed"; }); System.out.println("Result: " + result); // Traced operation with attributes tracer.trace("complex-operation", Map.of( "input.size", "100", "processing.type", "batch" ), () -> { Thread.sleep(200); System.out.println("Complex operation completed"); }); } } private static void springBootExample() { System.out.println("\n=== Spring Boot Example ==="); // In a Spring Boot application, this would be auto-configured // application.yml: /* google: cloud: trace: enabled: true project-id: your-gcp-project-id sampling-rate: 0.5 default-attributes: environment: production service: user-service */ System.out.println("Spring Boot auto-configuration would set up tracing automatically"); } private static void workflowTracingExample() throws Exception { System.out.println("\n=== Workflow Tracing Example ==="); TraceConfig config = new TraceConfig.Builder() .projectId("your-gcp-project-id") .samplingRate(1.0) .build(); try (CloudTracer tracer = new CloudTracer(config)) { OrderService orderService = new OrderService(tracer); PaymentService paymentService = new PaymentService(tracer); InventoryService inventoryService = new InventoryService(tracer); // Trace complete order processing workflow Order order = new Order("ORD-123", 99.99, "USD"); orderService.processOrder(order, paymentService, inventoryService); } } // Demo service classes static class OrderService { private final CloudTracer tracer; public OrderService(CloudTracer tracer) { this.tracer = tracer; } public void processOrder(Order order, PaymentService paymentService, InventoryService inventoryService) { tracer.trace("process-order", Map.of( "order.id", order.getId(), "order.amount", String.valueOf(order.getAmount()), "order.currency", order.getCurrency() ), () -> { // Validate order tracer.trace("validate-order", () -> { Thread.sleep(50); System.out.println("Order validated"); }); // Process payment boolean paymentSuccess = paymentService.processPayment(order); if (paymentSuccess) { // Update inventory inventoryService.updateInventory(order); // Send confirmation tracer.trace("send-confirmation", () -> { Thread.sleep(30); System.out.println("Confirmation sent"); }); } }); } } static class PaymentService { private final CloudTracer tracer; public PaymentService(CloudTracer tracer) { this.tracer = tracer; } public boolean processPayment(Order order) { return tracer.trace("process-payment", Map.of( "payment.amount", String.valueOf(order.getAmount()), "payment.currency", order.getCurrency() ), () -> { Thread.sleep(100); System.out.println("Payment processed"); return true; }); } } static class InventoryService { private final CloudTracer tracer; public InventoryService(CloudTracer tracer) { this.tracer = tracer; } public void updateInventory(Order order) { tracer.trace("update-inventory", Map.of( "order.id", order.getId() ), () -> { Thread.sleep(75); System.out.println("Inventory updated"); }); } } static class Order { private final String id; private final double amount; private final String currency; public Order(String id, double amount, String currency) { this.id = id; this.amount = amount; this.currency = currency; } public String getId() { return id; } public double getAmount() { return amount; } public String getCurrency() { return currency; } } }

Configuration Examples

application.yml

google: cloud: trace: enabled: true project-id: ${GCP_PROJECT_ID:my-project} sampling-rate: 0.1 buffer-timeout: 5s buffer-size: 1000 default-attributes: environment: ${ENVIRONMENT:development} service: user-service version: 1.0.0 # For Spring Boot applications spring: application: name: user-service datasource: url: jdbc:postgresql://localhost:5432/mydb username: user password: pass

Best Practices

  1. Use appropriate sampling rates - Start with 10% in production
  2. Include meaningful attributes - Add context to spans
  3. Propagate trace context - Between microservices
  4. Use consistent naming - For spans and attributes
  5. Monitor trace costs - Cloud Trace has associated costs
  6. Handle errors gracefully - Don't let tracing break your application
  7. Use async processing - For better performance
  8. Include business context - User IDs, request types, etc.

This implementation provides a complete, production-ready Cloud Trace integration for Java applications, supporting both manual instrumentation and automatic tracing for common frameworks like Spring Boot.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper