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); + } +}