diff --git a/libs/java/server_common/pom.xml b/libs/java/server_common/pom.xml
index bd97821fc96..b912747272a 100644
--- a/libs/java/server_common/pom.xml
+++ b/libs/java/server_common/pom.xml
@@ -37,9 +37,50 @@
2.0.2
5.1.0
0.3
+ 1.40.0
+
+
+
+ io.opentelemetry
+ opentelemetry-bom
+ ${opentelemetry.version}
+ pom
+ import
+
+
+
+
+
+ io.opentelemetry
+ opentelemetry-sdk-extension-autoconfigure
+
+
+ io.opentelemetry
+ opentelemetry-exporter-otlp
+
+
+ io.opentelemetry
+ opentelemetry-sdk
+
+
+ io.opentelemetry
+ opentelemetry-sdk-common
+
+
+ io.opentelemetry
+ opentelemetry-api
+
+
+ io.opentelemetry
+ opentelemetry-sdk-metrics
+
+
+ io.opentelemetry
+ opentelemetry-exporter-logging
+
org.slf4j
slf4j-api
@@ -94,7 +135,7 @@
com.fasterxml.uuid
java-uuid-generator
${uuid.version}
-
+
${project.groupId}
athenz-auth-core
@@ -278,11 +319,11 @@
athenz-zms-core
${project.parent.version}
-
- com.yahoo.athenz
- athenz-zms-java-client
- ${project.parent.version}
-
+
+ com.yahoo.athenz
+ athenz-zms-java-client
+ ${project.parent.version}
+
org.eclipse.jetty
jetty-server
diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetric.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetric.java
new file mode 100644
index 00000000000..e26a562f765
--- /dev/null
+++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetric.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yahoo.athenz.common.metrics.impl;
+
+import com.yahoo.athenz.common.metrics.Metric;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+
+public class OpenTelemetryMetric implements Metric {
+ final Meter meter;
+ final Tracer tracer;
+
+ private static final String REQUEST_DOMAIN_NAME = "requestDomainName";
+ private static final String PRINCIPAL_DOMAIN_NAME = "principalDomainName";
+ private static final String HTTP_METHOD_NAME = "httpMethodName";
+ private static final String HTTP_STATUS = "httpStatus";
+ private static final String API_NAME = "apiName";
+
+ public OpenTelemetryMetric(OpenTelemetry openTelemetry) {
+ meter = openTelemetry.getMeter("meter");
+ tracer = openTelemetry.getTracer("tracer");
+ }
+
+ @Override
+ public void increment(String metric) {
+ LongCounter counter = meter.counterBuilder(metric).build();
+ counter.add(1);
+ }
+
+ @Override
+ public void increment(String metric, String requestDomainName) {
+ increment(metric, requestDomainName, 1);
+ }
+
+ @Override
+ public void increment(String metric, String requestDomainName, int count) {
+ LongCounter counter = meter.counterBuilder(metric).build();
+ Attributes attributes = Attributes.builder()
+ .put(REQUEST_DOMAIN_NAME, requestDomainName)
+ .build();
+ counter.add(count, attributes);
+ }
+
+ @Override
+ public void increment(String metric, String requestDomainName, String principalDomainName) {
+ increment(metric, requestDomainName, principalDomainName, 1);
+ }
+
+ @Override
+ public void increment(String metric, String requestDomainName, String principalDomainName, String httpMethod, int httpStatus, String apiName) {
+ LongCounter counter = meter.counterBuilder(metric).build();
+ Attributes attributes = Attributes.builder()
+ .put(REQUEST_DOMAIN_NAME, requestDomainName)
+ .put(PRINCIPAL_DOMAIN_NAME, principalDomainName)
+ .put(HTTP_METHOD_NAME, httpMethod)
+ .put(HTTP_STATUS, Integer.toString(httpStatus))
+ .put(API_NAME, apiName)
+ .build();
+ counter.add(1, attributes);
+ }
+
+ @Override
+ public void increment(String metric, String requestDomainName, String principalDomainName, int count) {
+ LongCounter counter = meter.counterBuilder(metric).build();
+ Attributes attributes = Attributes.builder()
+ .put(REQUEST_DOMAIN_NAME, requestDomainName)
+ .put(PRINCIPAL_DOMAIN_NAME, principalDomainName)
+ .build();
+ counter.add(count, attributes);
+ }
+
+ @Override
+ public Object startTiming(String metric, String requestDomainName) {
+ Span span = tracer.spanBuilder(metric).startSpan();
+ Context context = Context.current().with(span);
+ return new Timer(context, System.currentTimeMillis(), span);
+ }
+
+ @Override
+ public void stopTiming(Object timerMetric) {
+ //not necessary method
+ }
+
+ @Override
+ public void stopTiming(Object timerMetric, String requestDomainName, String principalDomainName) {
+ stopTiming(timerMetric, requestDomainName, principalDomainName, null, -1, null);
+ }
+
+ @Override
+ public void stopTiming(Object timerMetric, String requestDomainName, String principalDomainName,
+ String httpMethod, int httpStatus, String apiName) {
+ Timer timer = (Timer) timerMetric;
+ long duration = System.currentTimeMillis() - timer.start;
+ Span span = timer.getSpan();
+ span.setAttribute("duration", duration);
+ span.setAttribute(REQUEST_DOMAIN_NAME, requestDomainName);
+ span.setAttribute(PRINCIPAL_DOMAIN_NAME, principalDomainName);
+
+ if (httpMethod != null) {
+ span.setAttribute(HTTP_METHOD_NAME, httpMethod);
+ }
+ if (httpStatus != -1) {
+ span.setAttribute(HTTP_STATUS, Integer.toString(httpStatus));
+ }
+ if (apiName != null) {
+ span.setAttribute(API_NAME, apiName);
+ }
+ span.end();
+ }
+
+ @Override
+ public void flush() {
+ //doesn't require flushing
+ }
+
+ @Override
+ public void quit() {
+ //don't need to quit anything
+ }
+
+ static class Timer {
+ private final Context context;
+ private final long start;
+ private final Span span;
+ public Timer(Context context, long start, Span span) {
+ this.context = context;
+ this.start = start;
+ this.span = span;
+ }
+ public Span getSpan() {
+ return span;
+ }
+ }
+}
diff --git a/libs/java/server_common/src/main/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetricFactory.java b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetricFactory.java
new file mode 100644
index 00000000000..e9137f529bc
--- /dev/null
+++ b/libs/java/server_common/src/main/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetricFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yahoo.athenz.common.metrics.impl;
+
+import com.yahoo.athenz.common.metrics.Metric;
+import com.yahoo.athenz.common.metrics.MetricFactory;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
+
+/*
+ In order to use the otlp exporters you need to configure the environment variables.
+ You need to set the endpoint (OTEL_EXPORTER_OTLP_ENDPOINT) which is defaulted to
+ "http:://localhost:4317" and the attributes (OTEL_RESOURCE_ATTRIBUTES) which is defaulted
+ to "service.name=my-service." AutoConfiguredOpenTelemetrySdk automatically reads the
+ configuration and sets up the exporter.
+*/
+
+public class OpenTelemetryMetricFactory implements MetricFactory {
+ @Override
+ public Metric create() {
+ OpenTelemetry openTelemetry = initialize();
+ return new OpenTelemetryMetric(openTelemetry);
+ }
+
+ public OpenTelemetry initialize() {
+ return AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk();
+ }
+}
diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetricFactoryTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetricFactoryTest.java
new file mode 100644
index 00000000000..c08a3e311e8
--- /dev/null
+++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetricFactoryTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yahoo.athenz.common.metrics.impl;
+
+import static org.testng.Assert.*;
+import com.yahoo.athenz.common.metrics.Metric;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+public class OpenTelemetryMetricFactoryTest {
+ private OpenTelemetryMetricFactory factory;
+
+ @BeforeMethod
+ public void setUp() {
+ factory = new OpenTelemetryMetricFactory();
+ }
+
+ @Test
+ public void testCreate() {
+ Metric metric = factory.create();
+ assertNotNull(metric);
+ assertTrue(metric instanceof OpenTelemetryMetric);
+ }
+}
diff --git a/libs/java/server_common/src/test/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetricsTest.java b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetricsTest.java
new file mode 100644
index 00000000000..da544a444a7
--- /dev/null
+++ b/libs/java/server_common/src/test/java/com/yahoo/athenz/common/metrics/impl/OpenTelemetryMetricsTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yahoo.athenz.common.metrics.impl;
+
+import static org.mockito.Mockito.*;
+import static org.testng.Assert.*;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.metrics.LongCounter;
+import io.opentelemetry.api.metrics.LongCounterBuilder;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanBuilder;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.mockito.ArgumentCaptor;
+
+public class OpenTelemetryMetricsTest {
+ private Meter meter;
+ private Tracer tracer;
+ private LongCounter counter;
+ private Span span;
+ private OpenTelemetryMetric metric;
+
+ @BeforeMethod
+ public void setUp() {
+ meter = mock(Meter.class);
+ tracer = mock(Tracer.class);
+ counter = mock(LongCounter.class);
+ span = mock(Span.class);
+ OpenTelemetry openTelemetry = mock(OpenTelemetry.class);
+
+ LongCounterBuilder counterBuilder = mock(LongCounterBuilder.class);
+ when(meter.counterBuilder(anyString())).thenReturn(counterBuilder);
+ when(counterBuilder.build()).thenReturn(counter);
+
+ SpanBuilder spanBuilder = mock(SpanBuilder.class);
+ when(tracer.spanBuilder(anyString())).thenReturn(spanBuilder);
+ when(spanBuilder.startSpan()).thenReturn(span);
+
+ when(openTelemetry.getMeter("meter")).thenReturn(meter);
+ when(openTelemetry.getTracer("tracer")).thenReturn(tracer);
+
+ metric = new OpenTelemetryMetric(openTelemetry);
+ }
+
+ @Test
+ public void testIncrementMetric() {
+ metric.increment("testIncrement");
+ verify(counter).add(1L);
+ }
+
+ @Test
+ public void testIncrementMetricRequest() {
+ metric.increment("testMetric", "testRequestDomain");
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Attributes.class);
+ verify(counter).add(eq(1L), captor.capture());
+ Attributes attributes = captor.getValue();
+ assertEquals(attributes.get(AttributeKey.stringKey("requestDomainName")), "testRequestDomain");
+ }
+
+ @Test
+ public void testIncrementMetricRequestCount() {
+ metric.increment("testMetric", "testRequestDomain", 3);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Attributes.class);
+ verify(counter).add(eq(3L), captor.capture());
+ Attributes attributes = captor.getValue();
+ assertEquals(attributes.get(AttributeKey.stringKey("requestDomainName")), "testRequestDomain");
+ }
+
+ @Test
+ public void testIncrementMetricRequestPrincipal() {
+ metric.increment("testMetric", "testRequestDomain", "testPrincipalDomain");
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Attributes.class);
+ verify(counter).add(eq(1L), captor.capture());
+ Attributes attributes = captor.getValue();
+ assertEquals(attributes.get(AttributeKey.stringKey("requestDomainName")), "testRequestDomain");
+ assertEquals(attributes.get(AttributeKey.stringKey("principalDomainName")), "testPrincipalDomain");
+ }
+
+ @Test
+ public void testIncrementMetricRequestPrincipalCount() {
+ metric.increment("testMetric", "testRequestDomain",
+ "testPrincipalDomain", 5);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Attributes.class);
+ verify(counter).add(eq(5L), captor.capture());
+ Attributes attributes = captor.getValue();
+ assertEquals(attributes.get(AttributeKey.stringKey("requestDomainName")), "testRequestDomain");
+ assertEquals(attributes.get(AttributeKey.stringKey("principalDomainName")), "testPrincipalDomain");
+ }
+
+ @Test
+ public void testIncrementAllAttributes() {
+ metric.increment("testMetric", "testRequestDomain",
+ "testPrincipalDomain", "GET", 200, "testAPI");
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Attributes.class);
+ verify(counter).add(eq(1L), captor.capture());
+ Attributes attributes = captor.getValue();
+ assertEquals(attributes.get(AttributeKey.stringKey("requestDomainName")), "testRequestDomain");
+ assertEquals(attributes.get(AttributeKey.stringKey("principalDomainName")), "testPrincipalDomain");
+ assertEquals(attributes.get(AttributeKey.stringKey("httpMethodName")), "GET");
+ assertEquals(attributes.get(AttributeKey.stringKey("httpStatus")), "200");
+ assertEquals(attributes.get(AttributeKey.stringKey("apiName")), "testAPI");
+ }
+
+ @Test
+ public void testStartTiming() {
+ Object timerMetric = metric.startTiming("testMetric", "testRequestDomain");
+ assertNotNull(timerMetric);
+ assertTrue(timerMetric instanceof OpenTelemetryMetric.Timer);
+ OpenTelemetryMetric.Timer timer = (OpenTelemetryMetric.Timer) timerMetric;
+ assertEquals(span, timer.getSpan());
+ }
+
+ @Test
+ public void testStopTimingTimer() {
+ OpenTelemetryMetric.Timer timer = new OpenTelemetryMetric.Timer(Context.current(),
+ System.currentTimeMillis(), span);
+ metric.stopTiming(timer);
+ verifyNoInteractions(meter, tracer, counter, span);
+ }
+
+ @Test
+ public void testStopTimingTimerRequestPrincipal() {
+ OpenTelemetryMetric.Timer timer = new OpenTelemetryMetric.Timer(Context.current(),
+ System.currentTimeMillis(), span);
+ metric.stopTiming(timer, "testRequestDomain", "testPrincipalDomain");
+ verify(span).setAttribute("requestDomainName", "testRequestDomain");
+ verify(span).setAttribute("principalDomainName", "testPrincipalDomain");
+ verify(span).setAttribute(eq("duration"), anyLong());
+ verify(span).end();
+ }
+
+ @Test
+ public void testStopTimingAllAttributes() {
+ OpenTelemetryMetric.Timer timer = new OpenTelemetryMetric.Timer(Context.current(),
+ System.currentTimeMillis(), span);
+ metric.stopTiming(timer, "testRequestDomain",
+ "testPrincipalDomain", "GET", 200, "testAPI");
+ verify(span).setAttribute("requestDomainName", "testRequestDomain");
+ verify(span).setAttribute("principalDomainName", "testPrincipalDomain");
+ verify(span).setAttribute("httpMethodName", "GET");
+ verify(span).setAttribute("httpStatus", "200");
+ verify(span).setAttribute("apiName", "testAPI");
+ verify(span).setAttribute(eq("duration"), anyLong());
+ verify(span).end();
+ }
+
+
+ @Test
+ public void testFlush() {
+ metric.flush();
+ verifyNoInteractions(meter, tracer, counter, span);
+ }
+
+ @Test
+ public void testQuit() {
+ metric.quit();
+ verifyNoInteractions(meter, tracer, counter, span);
+ }
+}