Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/main/java/com/example/oteldemo/config/OtelConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.example.oteldemo.config;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Configuration
public class OtelConfig {
private static final Logger logger = LoggerFactory.getLogger(OtelConfig.class);

@Value("${otel.service.name:petclinic}")
private String serviceName;

@Value("${otel.exporter.otlp.endpoint:http://localhost:4317}")
private String endpoint;

@Bean
public OpenTelemetry openTelemetry() {
logger.info("Initializing OpenTelemetry with endpoint: {}", endpoint);

try {
Resource resource = Resource.getDefault()
.merge(Resource.create(Attributes.of(
ResourceAttributes.SERVICE_NAME, serviceName
)));

OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint(endpoint)
.build();

SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.setResource(resource)
.build();

OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder()
.setTracerProvider(sdkTracerProvider)
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
.build();

logger.info("OpenTelemetry initialization successful");
return openTelemetrySdk;
} catch (Exception e) {
logger.error("Failed to initialize OpenTelemetry", e);
// Fallback to no-op implementation
return OpenTelemetry.noop();
}
}
}
37 changes: 37 additions & 0 deletions src/main/java/com/example/oteldemo/health/OtelHealthIndicator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.example.oteldemo.health;

import io.opentelemetry.api.OpenTelemetry;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class OtelHealthIndicator implements HealthIndicator {
private final OpenTelemetry openTelemetry;

public OtelHealthIndicator(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}

@Override
public Health health() {
try {
// Check if OpenTelemetry is properly initialized
if (openTelemetry != null && openTelemetry.getTracerProvider() != null) {
return Health.up()
.withDetail("status", "Operational")
.withDetail("implementation", openTelemetry.getClass().getSimpleName())
.build();
} else {
return Health.down()
.withDetail("status", "Not initialized")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("status", "Error")
.withDetail("error", e.getMessage())
.build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.example.oteldemo.instrumentation;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Supplier;

@Component
public class OtelInstrumentationUtil {
private static final Logger logger = LoggerFactory.getLogger(OtelInstrumentationUtil.class);
private final Tracer tracer;

public OtelInstrumentationUtil(OpenTelemetry openTelemetry) {
this.tracer = openTelemetry.getTracer(getClass().getName());
}

/**
* Traces a callable operation with custom attributes
*/
public <T> T traceOperation(String operationName, Map<String, String> attributes, Callable<T> operation) {
Span span = createSpan(operationName, attributes);
try (Scope scope = span.makeCurrent()) {
T result = operation.call();
span.setStatus(StatusCode.OK);
return result;
} catch (Exception e) {
span.setStatus(StatusCode.ERROR, e.getMessage());
logger.error("Error in traced operation: {}", operationName, e);
throw new RuntimeException(e);
} finally {
span.end();
}
}

/**
* Traces a supplier operation
*/
public <T> T traceOperation(String operationName, Supplier<T> operation) {
return traceOperation(operationName, Map.of(), () -> operation.get());
}

/**
* Creates a new span with the given name and attributes
*/
public Span createSpan(String name, Map<String, String> attributes) {
Span span = tracer.spanBuilder(name)
.setSpanKind(SpanKind.INTERNAL)
.setParent(Context.current())
.startSpan();

attributes.forEach((key, value) -> span.setAttribute(key, value));
return span;
}

/**
* Gets the current span context
*/
public Span getCurrentSpan() {
return Span.current();
}

/**
* Adds an event to the current span
*/
public void addEvent(String eventName, Map<String, String> attributes) {
Span currentSpan = getCurrentSpan();
if (currentSpan != null) {
currentSpan.addEvent(eventName, attributes.entrySet().stream()
.collect(java.util.stream.Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue()
)));
}
}
}
9 changes: 9 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,12 @@ logging.level.org.springframework=INFO

# Maximum time static resources should be cached
spring.web.resources.cache.cachecontrol.max-age=12h

# OpenTelemetry Configuration
otel.service.name=${OTEL_SERVICE_NAME:petclinic}
otel.exporter.otlp.endpoint=${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4317}
otel.traces.sampler=${OTEL_TRACES_SAMPLER:parentbased_always_on}
otel.traces.sampler.arg=${OTEL_TRACES_SAMPLER_ARG:1.0}
otel.metric.export.interval=${OTEL_METRIC_EXPORT_INTERVAL:60000}
otel.propagators=${OTEL_PROPAGATORS:tracecontext,baggage}
otel.resource.attributes=${OTEL_RESOURCE_ATTRIBUTES:service.namespace=petclinic,service.instance.id=${random.uuid}}