diff --git a/gxobservability/pom.xml b/gxobservability/pom.xml
new file mode 100644
index 000000000..9b48bd2b8
--- /dev/null
+++ b/gxobservability/pom.xml
@@ -0,0 +1,63 @@
+
+
+ 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.30.1-alpha
+
+
+ io.opentelemetry
+ opentelemetry-extension-annotations
+
+
+ io.opentelemetry
+ opentelemetry-sdk-extension-autoconfigure
+ 1.36.0
+
+
+
+
+
+ io.opentelemetry
+ opentelemetry-bom
+ 1.23.0
+ pom
+ import
+
+
+
+
+
+ gxobservability
+
+
\ 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 ca9bf9d3b..d206cd120 100644
--- a/pom.xml
+++ b/pom.xml
@@ -112,6 +112,7 @@
gxcloudstorage-azureblob
gxcloudstorage-ibmcos
gxcloudstorage-tests
+ gxobservability
gxcloudstorage-awss3-v2