From 9da5ad1e7f1e78f2d0f579a289a08129a981f9ee Mon Sep 17 00:00:00 2001 From: sjuarezgx Date: Tue, 12 Mar 2024 15:51:23 -0300 Subject: [PATCH 1/2] Add manual Otel instrumentation for Spans --- gxobservability/pom.xml | 73 +++++++ .../genexus/opentelemetry/GXSpanContext.java | 33 +++ .../genexus/opentelemetry/GXTraceContext.java | 21 ++ .../com/genexus/opentelemetry/OtelSpan.java | 186 ++++++++++++++++ .../com/genexus/opentelemetry/OtelTracer.java | 206 ++++++++++++++++++ pom.xml | 1 + 6 files changed, 520 insertions(+) create mode 100644 gxobservability/pom.xml create mode 100644 gxobservability/src/main/java/com/genexus/opentelemetry/GXSpanContext.java create mode 100644 gxobservability/src/main/java/com/genexus/opentelemetry/GXTraceContext.java create mode 100644 gxobservability/src/main/java/com/genexus/opentelemetry/OtelSpan.java create mode 100644 gxobservability/src/main/java/com/genexus/opentelemetry/OtelTracer.java diff --git a/gxobservability/pom.xml b/gxobservability/pom.xml new file mode 100644 index 000000000..2e6686594 --- /dev/null +++ b/gxobservability/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + + com.genexus + parent + ${revision}${changelist} + + + gxobservability + GeneXus Observability + + + + io.opentelemetry + opentelemetry-api + + + io.opentelemetry + opentelemetry-sdk-trace + + + io.opentelemetry + opentelemetry-exporter-otlp + + + io.opentelemetry + opentelemetry-sdk + + + io.opentelemetry + opentelemetry-semconv + 1.23.0-alpha + + + io.opentelemetry + opentelemetry-extension-annotations + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure + 1.23.0-alpha + + + + + + io.opentelemetry + opentelemetry-bom + 1.23.0 + pom + import + + + + + + gxobservability + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + \ No newline at end of file diff --git a/gxobservability/src/main/java/com/genexus/opentelemetry/GXSpanContext.java b/gxobservability/src/main/java/com/genexus/opentelemetry/GXSpanContext.java new file mode 100644 index 000000000..07c68f93b --- /dev/null +++ b/gxobservability/src/main/java/com/genexus/opentelemetry/GXSpanContext.java @@ -0,0 +1,33 @@ +package com.genexus.opentelemetry; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.context.Context; +public class GXSpanContext +{ + private io.opentelemetry.api.trace.SpanContext _spanContext; + + public io.opentelemetry.api.trace.SpanContext getSpanContext() + { + return _spanContext; + } + public GXSpanContext(io.opentelemetry.api.trace.SpanContext spanContext) + { + this._spanContext = spanContext; + } + public GXSpanContext() + { + _spanContext = Span.current().getSpanContext(); + } + public String traceId() + { + return _spanContext.getTraceId(); + } + public String spanId() + { + return _spanContext.getSpanId(); + } +} \ No newline at end of file diff --git a/gxobservability/src/main/java/com/genexus/opentelemetry/GXTraceContext.java b/gxobservability/src/main/java/com/genexus/opentelemetry/GXTraceContext.java new file mode 100644 index 000000000..6367b2938 --- /dev/null +++ b/gxobservability/src/main/java/com/genexus/opentelemetry/GXTraceContext.java @@ -0,0 +1,21 @@ +package com.genexus.opentelemetry; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.context.Context; + +public class GXTraceContext +{ + private Context context; + public GXTraceContext(io.opentelemetry.context.Context context) + { + this.context = context; + } + public Context getTraceContext() + { + return this.context; + } +} \ No newline at end of file diff --git a/gxobservability/src/main/java/com/genexus/opentelemetry/OtelSpan.java b/gxobservability/src/main/java/com/genexus/opentelemetry/OtelSpan.java new file mode 100644 index 000000000..9e1c2d928 --- /dev/null +++ b/gxobservability/src/main/java/com/genexus/opentelemetry/OtelSpan.java @@ -0,0 +1,186 @@ +package com.genexus.opentelemetry; + +import java.util.concurrent.atomic.AtomicReference; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.extension.annotations.SpanAttribute; +import io.opentelemetry.extension.annotations.WithSpan; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; +public class OtelSpan { + private Span span; + public enum SpanStatusCode + { + UNSET, + OK, + ERROR + } + public OtelSpan(Span span) + { + this.span=span; + } + public OtelSpan() + {} + //region EO Properties + public String getTraceId() + { + if (span != null) + return span.getSpanContext().getTraceId(); + return ""; + } + public String getSpanId() + { + if (span != null) + return span.getSpanContext().getSpanId(); + return ""; + } + public Boolean isRecording() + { + if (span != null) + return span.isRecording(); + return false; + } + public GXSpanContext getSpanContext() { + return new GXSpanContext(getSpanContext(span)); + } + //endregion + //region EO Methods + public void endSpan() + { + if (span!=null) + span.end(); + } + public GXTraceContext addBaggage(String key, String value) + { + return new GXTraceContext(addBaggageReturnContext(key, value)); + } + public String getBaggaeItem(String key,GXTraceContext gxTraceContext) + { + return getBaggaeItemInContext(gxTraceContext.getTraceContext(),key); + } + public GXTraceContext getGXTraceContext() + { + return new GXTraceContext(getContext()); + } + public void recordException(String message) + { + recordException(span,new Throwable(message)); + } + public void setStringAttribute(String key, String value) + { + if (span != null) + span.setAttribute(key,value); + } + public void setBooleanAttribute(String key, boolean value) + { + if (span != null) + span.setAttribute(key,value); + } + public void setDoubleAttribute(String key, double value) + { + if (span != null) + span.setAttribute(key,value); + } + public void setLongAttribute(String key, long value) + { + if (span != null) + span.setAttribute(key,value); + } + public void setStatus(Byte spanStatusCodeByte) + { + StatusCode statusCode = toStatusCode(spanStatusCodeByte); + if (span != null) + span.setStatus(statusCode); + } + public void setStatus(Byte spanStatusCodeByte, String message) + { + StatusCode statusCode = toStatusCode(spanStatusCodeByte); + if (span != null) + span.setStatus(statusCode, message); + } + //endregion + + //region Private methods + + private String getBaggaeItemInContext(Context context, String key) + { + AtomicReference value = new AtomicReference<>(""); + Baggage.fromContext(context).asMap().forEach((k, v) -> { + if (k.equals(key)) { + value.set(v.getValue()); + } + }); + if (value != null) + return value.get(); + return ""; + } + private Context addBaggageReturnContext(String key, String value) + { + Baggage baggage = Baggage.current().toBuilder().put(key,value).build(); + return baggage.storeInContext(getContext()); + } + private Context getContext() + { + if (span != null) + return Context.current().with(span); + return null; + } + private Context getContextCurrentSpan() + { + return Context.current(); + } + + private static void recordException(Span span, Throwable exc) { + if (span != null && exc != null) { + span.recordException(exc); + } + } + private io.opentelemetry.api.trace.SpanContext getSpanContext(Span span) + { + if (span != null) + return span.getSpanContext(); + return null; + } + private boolean isRecording(Span span) + { + if (span != null) + return span.isRecording(); + return false; + } + private Span current() + { + return Span.current(); + + } + private static StatusCode toStatusCode (Byte spanStatusCode){ + switch (spanStatusCode) { + case 0: + return StatusCode.UNSET; + case 1: + return StatusCode.OK; + case 2: + return StatusCode.ERROR; + } + return null; + } + private static SpanStatusCode fromStatusCode (StatusCode statusCode){ + switch (statusCode) { + case UNSET: + return SpanStatusCode.UNSET; + case OK: + return SpanStatusCode.OK; + case ERROR: + return SpanStatusCode.ERROR; + } + return null; + } + //endregion + +} \ No newline at end of file diff --git a/gxobservability/src/main/java/com/genexus/opentelemetry/OtelTracer.java b/gxobservability/src/main/java/com/genexus/opentelemetry/OtelTracer.java new file mode 100644 index 000000000..cd7442a96 --- /dev/null +++ b/gxobservability/src/main/java/com/genexus/opentelemetry/OtelTracer.java @@ -0,0 +1,206 @@ +package com.genexus.opentelemetry; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.extension.annotations.SpanAttribute; +import io.opentelemetry.extension.annotations.WithSpan; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import java.util.Iterator; +import java.util.regex.*; +public class OtelTracer { + private static final String OTEL_SERVICE_NAME = "OTEL_SERVICE_NAME"; + private static final String OTEL_SERVICE_VERSION = "OTEL_SERVICE_VERSION"; + private static final String OTEL_RESOURCE_ATTRIBUTES = "OTEL_RESOURCE_ATTRIBUTES"; + private static final String JAVA_INSTRUMENTATION_SCOPE_NAME = "JAVA_INSTRUMENTATION_SCOPE_NAME"; + private static final String JAVA_INSTRUMENTATION_SCOPE_VERSION = "JAVA_INSTRUMENTATION_SCOPE_VERSION"; + private static StringPair instrumentationScope=getInstrumentationScope(); + private static Tracer tracer=getTracer(instrumentationScope); + static class StringPair { + final String name; + final String version; + + StringPair(String name, String version) { + this.name = name; + this.version = version; + } + } + public enum SpanType + { + INTERNAL, + SERVER, + CLIENT, + PRODUCER, + CONSUMER + } + public OtelSpan createSpan(String displayName) + { + Span otelspan= createAndStartSpan(displayName); + return new OtelSpan(otelspan); + } + public OtelSpan createSpan(String displayName, Byte spanTypeByte) + { + io.opentelemetry.api.trace.SpanKind spanKind = toSpanKind(spanTypeByte); + Span otelspan = createAndStartSpan(displayName, spanKind); + return new OtelSpan(otelspan); + } + public OtelSpan createSpan(String displayName, GXTraceContext gxTraceContext, Byte spanTypeByte) + { + io.opentelemetry.api.trace.SpanKind spanKind = toSpanKind(spanTypeByte); + Span otelspan = createAndStartSpan(displayName,spanKind,gxTraceContext.getTraceContext()); + return new OtelSpan(otelspan); + } + public OtelSpan createSpan(String displayName, GXTraceContext gxTraceContext, Byte spanTypeByte, Iterator gxSpanContextIterator) + { + io.opentelemetry.api.trace.SpanKind spanKind = toSpanKind(spanTypeByte); + Span otelspan = createAndStartSpan(displayName,spanKind,gxTraceContext.getTraceContext(),gxSpanContextIterator); + return new OtelSpan(otelspan); + } + public static OtelSpan getCurrentSpan() + { + return new OtelSpan(Span.current()); + } + //region Private methods + private static StringPair getInstrumentationScope() + { + String name="GeneXus.Tracing"; + String version=""; + + String javaInstrumentationScopeName = System.getenv(JAVA_INSTRUMENTATION_SCOPE_NAME); + String javaInstrumentationScopeVersion = System.getenv(JAVA_INSTRUMENTATION_SCOPE_VERSION); + + if (javaInstrumentationScopeName!=null && !javaInstrumentationScopeName.trim().isEmpty()) + { + name = javaInstrumentationScopeName; + if (javaInstrumentationScopeVersion!=null && !javaInstrumentationScopeVersion.trim().isEmpty()) { + version = javaInstrumentationScopeVersion; + } + } + else + { + String serviceName = System.getenv(OTEL_SERVICE_NAME); + + if (serviceName==null || serviceName.trim().isEmpty()) { + String pattern = "(?:\\b\\w+\\b=\\w+)(?:,(?:\\b\\w+\\b=\\w+))*"; + Pattern regex = Pattern.compile(pattern); + Matcher matcher = regex.matcher(OTEL_RESOURCE_ATTRIBUTES); + + while (matcher.find()) { + String[] keyValue = matcher.group().split("="); + if (keyValue[0].equals("service.name")) { + serviceName = keyValue[1]; + break; + } + } + } + + String serviceVersion = System.getenv(OTEL_SERVICE_VERSION); + if (serviceVersion==null || serviceVersion.trim().isEmpty()) { + String pattern = "(?:\\b\\w+\\b=\\w+)(?:,(?:\\b\\w+\\b=\\w+))*"; + Pattern regex = Pattern.compile(pattern); + Matcher matcher = regex.matcher(OTEL_RESOURCE_ATTRIBUTES); + + while (matcher.find()) { + + String[] keyValue = matcher.group().split("="); + + if (keyValue[0].equals("service.version")) { + serviceVersion = keyValue[1]; + break; + } + } + } + + if (serviceName!=null && !serviceName.trim().isEmpty()) + name = serviceName; + if (serviceVersion!=null && !serviceVersion.trim().isEmpty()) + version = serviceVersion; + + } + return new StringPair(name,version); + } + + private static Tracer getTracer(StringPair instrumentationScope) + { + OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); + if (openTelemetry != null) + return openTelemetry.getTracer(instrumentationScope.name,instrumentationScope.version); + return null; + } + + private static Span createAndStartSpan(String displayName) + { + if (tracer != null) { + if (!displayName.isEmpty()) + return tracer.spanBuilder(displayName).startSpan(); + } + return null; + } + private static Span createAndStartSpan(String displayName, io.opentelemetry.api.trace.SpanKind spanKind) + { + if (tracer != null) { + if (!displayName.isEmpty() && spanKind != null) + return tracer.spanBuilder(displayName).setSpanKind(spanKind).startSpan(); + else if (spanKind == null) { + return tracer.spanBuilder(displayName).startSpan(); + } + } + return null; + } + private static Span createAndStartSpan(String displayName, io.opentelemetry.api.trace.SpanKind spanKind, Context context) + { + if (tracer != null) { + if (!displayName.isEmpty() && spanKind != null && context != null) + return tracer.spanBuilder(displayName).setSpanKind(spanKind).setParent(context).startSpan(); + else { + if (!displayName.isEmpty() && spanKind != null && context == null) + return tracer.spanBuilder(displayName).setSpanKind(spanKind).setNoParent().startSpan(); + } + } + return null; + } + private static Span createAndStartSpan(String displayName, io.opentelemetry.api.trace.SpanKind spanKind, Context context, Iterator gxSpanContexts) + { + if (tracer != null) { + SpanBuilder spanBuilder; + if (!displayName.isEmpty() && spanKind != null && context != null) { + spanBuilder = tracer.spanBuilder(displayName).setSpanKind(spanKind).setParent(context); + } else { + if (!displayName.isEmpty() && spanKind != null && context == null) + spanBuilder = tracer.spanBuilder(displayName).setSpanKind(spanKind).setNoParent(); + else + return null; + } + while (gxSpanContexts.hasNext()) { + if (spanBuilder != null) + spanBuilder.addLink(gxSpanContexts.next().getSpanContext()); + } + return spanBuilder.startSpan(); + } + return null; + } + + private static io.opentelemetry.api.trace.SpanKind toSpanKind (Byte spanTypeByte){ + switch (spanTypeByte) { + case 0: + return io.opentelemetry.api.trace.SpanKind.INTERNAL; + case 1: + return io.opentelemetry.api.trace.SpanKind.SERVER; + case 2: + return io.opentelemetry.api.trace.SpanKind.CLIENT; + case 3: + return io.opentelemetry.api.trace.SpanKind.PRODUCER; + case 4: + return io.opentelemetry.api.trace.SpanKind.CONSUMER; + } + return null; + } + //endregion + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 136921f62..6244fc826 100644 --- a/pom.xml +++ b/pom.xml @@ -113,6 +113,7 @@ gxcloudstorage-azureblob gxcloudstorage-ibmcos gxcloudstorage-tests + gxobservability From 38db579e887ec8a326d7bf17e06797dea88cd62e Mon Sep 17 00:00:00 2001 From: sjuarezgx Date: Sat, 16 Mar 2024 10:41:43 -0300 Subject: [PATCH 2/2] Fix pom settings. Update io.opentelemetry libraries. --- gxobservability/pom.xml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/gxobservability/pom.xml b/gxobservability/pom.xml index 2e6686594..9b48bd2b8 100644 --- a/gxobservability/pom.xml +++ b/gxobservability/pom.xml @@ -33,7 +33,7 @@ io.opentelemetry opentelemetry-semconv - 1.23.0-alpha + 1.30.1-alpha io.opentelemetry @@ -42,7 +42,7 @@ io.opentelemetry opentelemetry-sdk-extension-autoconfigure - 1.23.0-alpha + 1.36.0 @@ -59,15 +59,5 @@ gxobservability - - - org.apache.maven.plugins - maven-compiler-plugin - - 8 - 8 - - - \ No newline at end of file