From ed178c6383826e31c4f75dd5e55b48d5b9141bbf Mon Sep 17 00:00:00 2001 From: Gang Li Date: Sat, 13 Jul 2024 09:29:00 +0800 Subject: [PATCH] Remove o11yphant dependencies --- .../client/IndyFoloAdminClientModule.java | 2 - core-java/pom.xml | 21 +- .../org/commonjava/indy/client/core/Indy.java | 20 -- .../indy/client/core/IndyClientHttp.java | 103 +----- .../client/core/helper/HttpResources.java | 12 +- .../core/inject/ClientMetricConfig.java | 31 -- .../client/core/inject/ClientMetricSet.java | 34 -- .../metric/ClientGoldenSignalsMetricSet.java | 48 --- ...ClientGoldenSignalsSpanFieldsInjector.java | 60 ---- .../core/metric/ClientMetricManager.java | 149 -------- .../core/metric/ClientMetricsProducer.java | 70 ---- .../metric/ClientMetricConstants.java | 14 +- .../core/o11y/metric/ClientMetricManager.java | 101 ++++++ .../core/{ => o11y}/metric/ClientMetrics.java | 90 +++-- .../metric/ClientTrafficClassifier.java | 23 +- .../o11y/metric/RequestContextHelper.java | 67 ++++ .../trace}/ClientTracerConfiguration.java | 98 +---- .../client/core/o11y/trace/NameUtils.java | 93 +++++ .../client/core/o11y/trace/OtelProvider.java | 181 ++++++++++ .../core/o11y/trace/SpanClosingResponse.java | 335 ++++++++++++++++++ .../client/core/o11y/trace/SpanProvider.java | 73 ++++ .../client/core/o11y/trace/SpanWrapper.java | 129 +++++++ .../core/o11y/trace/SpanningHttpFactory.java | 110 ++++++ .../client/core/o11y/trace/TraceManager.java | 86 +++++ .../core/o11y/trace/TracerHttpClient.java | 152 ++++++++ .../indy/client/core/util/ThreadContext.java | 277 +++++++++++++++ pom.xml | 47 +-- 27 files changed, 1704 insertions(+), 722 deletions(-) delete mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/inject/ClientMetricConfig.java delete mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/inject/ClientMetricSet.java delete mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientGoldenSignalsMetricSet.java delete mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientGoldenSignalsSpanFieldsInjector.java delete mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricManager.java delete mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricsProducer.java rename core-java/src/main/java/org/commonjava/indy/client/core/{ => o11y}/metric/ClientMetricConstants.java (76%) create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientMetricManager.java rename core-java/src/main/java/org/commonjava/indy/client/core/{ => o11y}/metric/ClientMetrics.java (54%) rename core-java/src/main/java/org/commonjava/indy/client/core/{ => o11y}/metric/ClientTrafficClassifier.java (74%) create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/RequestContextHelper.java rename core-java/src/main/java/org/commonjava/indy/client/core/{metric => o11y/trace}/ClientTracerConfiguration.java (58%) create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/NameUtils.java create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/OtelProvider.java create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanClosingResponse.java create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanProvider.java create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanWrapper.java create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanningHttpFactory.java create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/TraceManager.java create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/TracerHttpClient.java create mode 100644 core-java/src/main/java/org/commonjava/indy/client/core/util/ThreadContext.java diff --git a/addons-client/folo/client-java/src/main/java/org/commonjava/indy/folo/client/IndyFoloAdminClientModule.java b/addons-client/folo/client-java/src/main/java/org/commonjava/indy/folo/client/IndyFoloAdminClientModule.java index 99d454e..b70b167 100644 --- a/addons-client/folo/client-java/src/main/java/org/commonjava/indy/folo/client/IndyFoloAdminClientModule.java +++ b/addons-client/folo/client-java/src/main/java/org/commonjava/indy/folo/client/IndyFoloAdminClientModule.java @@ -144,8 +144,6 @@ public void clearTrackingRecord( final String trackingId, final StoreType type, public boolean sealTrackingRecord( String trackingId ) throws IndyClientException { - http.connect(); - HttpPost request = http.newRawPost( UrlUtils.buildUrl( http.getBaseUrl(), "/folo/admin", trackingId, "record" ) ); HttpResources resources = null; try diff --git a/core-java/pom.xml b/core-java/pom.xml index ff3359e..8bd4d9b 100644 --- a/core-java/pom.xml +++ b/core-java/pom.xml @@ -37,20 +37,20 @@ jhttpc - org.commonjava.util - o11yphant-trace-api + io.opentelemetry + opentelemetry-api - org.commonjava.util - o11yphant-trace-otel + io.opentelemetry + opentelemetry-sdk - org.commonjava.util - o11yphant-trace-helper-jhttpc + io.opentelemetry + opentelemetry-exporter-logging - org.commonjava.util - o11yphant-metrics-common + io.opentelemetry + opentelemetry-exporter-otlp org.apache.httpcomponents @@ -65,9 +65,8 @@ commons-io - org.commonjava.indy - indy-test-fixtures-core - test + org.apache.commons + commons-lang3 diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/Indy.java b/core-java/src/main/java/org/commonjava/indy/client/core/Indy.java index ba90769..f0921ed 100644 --- a/core-java/src/main/java/org/commonjava/indy/client/core/Indy.java +++ b/core-java/src/main/java/org/commonjava/indy/client/core/Indy.java @@ -22,8 +22,6 @@ import org.commonjava.indy.inject.IndyVersioningProvider; import org.commonjava.indy.model.core.io.IndyObjectMapper; import org.commonjava.indy.stats.IndyVersioning; -import org.commonjava.o11yphant.trace.TraceManager; -import org.commonjava.o11yphant.trace.TracerConfiguration; import org.commonjava.util.jhttpc.auth.PasswordManager; import org.commonjava.util.jhttpc.model.SiteConfig; @@ -213,10 +211,6 @@ public static final class Builder private Map mdcCopyMappings; - private TracerConfiguration existedTraceConfig; - - private TraceManager existedTraceManager; - private Builder() { } @@ -252,18 +246,6 @@ public Builder setObjectMapper( IndyObjectMapper objectMapper ) return this; } - public Builder setExistedTraceConfig( TracerConfiguration existedTraceConfig ) - { - this.existedTraceConfig = existedTraceConfig; - return this; - } - - public Builder setExistedTraceManager( TraceManager traceManager ) - { - this.existedTraceManager = traceManager; - return this; - } - public Builder setAuthenticator( IndyClientAuthenticator authenticator ) { this.authenticator = authenticator; @@ -291,8 +273,6 @@ public Indy build() .setApiVersion( indy.getApiVersion() ) .setLocation( this.location ) .setPasswordManager( this.passwordManager ) - .setExistedTraceConfig( this.existedTraceConfig ) - .setExistedTraceManager( this.existedTraceManager ) .setMdcCopyMappings( this.mdcCopyMappings ) .setObjectMapper( this.objectMapper ) .build(); diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/IndyClientHttp.java b/core-java/src/main/java/org/commonjava/indy/client/core/IndyClientHttp.java index 4eb5599..23aa055 100644 --- a/core-java/src/main/java/org/commonjava/indy/client/core/IndyClientHttp.java +++ b/core-java/src/main/java/org/commonjava/indy/client/core/IndyClientHttp.java @@ -32,7 +32,6 @@ import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.entity.InputStreamEntity; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; @@ -41,14 +40,12 @@ import org.apache.http.util.VersionInfo; import org.commonjava.indy.client.core.auth.IndyClientAuthenticator; import org.commonjava.indy.client.core.helper.HttpResources; -import org.commonjava.indy.client.core.metric.ClientMetricManager; -import org.commonjava.indy.client.core.metric.ClientMetrics; +import org.commonjava.indy.client.core.o11y.metric.ClientMetricManager; +import org.commonjava.indy.client.core.o11y.metric.ClientMetrics; +import org.commonjava.indy.client.core.o11y.trace.SpanningHttpFactory; import org.commonjava.indy.inject.IndyVersioningProvider; import org.commonjava.indy.model.core.ArtifactStore; import org.commonjava.indy.model.core.io.IndyObjectMapper; -import org.commonjava.o11yphant.jhttpc.SpanningHttpFactory; -import org.commonjava.o11yphant.trace.TraceManager; -import org.commonjava.o11yphant.trace.TracerConfiguration; import org.commonjava.util.jhttpc.HttpFactory; import org.commonjava.util.jhttpc.HttpFactoryIfc; import org.commonjava.util.jhttpc.JHttpCException; @@ -69,15 +66,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; import java.util.function.Supplier; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.commonjava.indy.IndyContentConstants.CHECK_CACHE_ONLY; import static org.commonjava.indy.client.core.helper.HttpResources.cleanupResources; import static org.commonjava.indy.client.core.helper.HttpResources.entityToString; -import static org.commonjava.indy.client.core.metric.ClientMetricConstants.HEADER_CLIENT_API; -import static org.commonjava.indy.client.core.metric.ClientMetricConstants.HEADER_CLIENT_TRACE_ID; import static org.commonjava.indy.client.core.util.UrlUtils.buildUrl; import static org.commonjava.indy.stats.IndyVersioning.HEADER_INDY_API_VERSION; @@ -121,7 +115,8 @@ public IndyClientHttp( final IndyClientAuthenticator authenticator, final IndyOb this( mapper, location, apiVersion ); this.mdcCopyMappings = mdcCopyMappings; metricManager = new ClientMetricManager( location ); - factory = new SpanningHttpFactory( new HttpFactory( authenticator ), metricManager.getTraceManager() ); + factory = new SpanningHttpFactory( new HttpFactory( authenticator ), + metricManager.getTraceManager().orElse( null ) ); } /** @@ -135,7 +130,8 @@ public IndyClientHttp( final PasswordManager passwordManager, final IndyObjectMa this( mapper, location, apiVersion ); metricManager = new ClientMetricManager( location ); - factory = new SpanningHttpFactory( new HttpFactory( passwordManager ), metricManager.getTraceManager() ); + factory = new SpanningHttpFactory( new HttpFactory( passwordManager ), + metricManager.getTraceManager().orElse( null ) ); } private IndyClientHttp( final IndyObjectMapper mapper, SiteConfig location, String apiVersion ) @@ -166,10 +162,6 @@ public static final class Builder private String apiVersion; - private TracerConfiguration existedTraceConfig; - - private TraceManager existedTraceManager; - private Map mdcCopyMappings; private Builder() @@ -206,18 +198,6 @@ public Builder setApiVersion( String apiVersion ) return this; } - public Builder setExistedTraceConfig( TracerConfiguration existedTraceConfig ) - { - this.existedTraceConfig = existedTraceConfig; - return this; - } - - public Builder setExistedTraceManager( TraceManager traceManager ) - { - this.existedTraceManager = traceManager; - return this; - } - public Builder setMdcCopyMappings( Map mdcCopyMappings ) { this.mdcCopyMappings = mdcCopyMappings; @@ -257,23 +237,8 @@ public IndyClientHttp build() factory = new HttpFactory( this.passwordManager ); } - ClientMetricManager metricManager; - if ( this.existedTraceManager != null ) - { - metricManager = new ClientMetricManager( this.existedTraceManager ); - } - else if ( this.existedTraceConfig != null ) - { - metricManager = new ClientMetricManager( this.existedTraceConfig ); - } - else - { - metricManager = new ClientMetricManager( location ); - } - - client.metricManager = metricManager; - client.factory = new SpanningHttpFactory( factory, metricManager.getTraceManager() ); - + client.metricManager = new ClientMetricManager( location ); + client.factory = new SpanningHttpFactory( factory, client.metricManager.getTraceManager().orElse( null ) ); return client; } } @@ -289,14 +254,6 @@ private void initUserAgent( final String apiVersion ) String.format( "Indy/%s (api: %s) via %s", indyVersion, apiVersion, hcUserAgent ) ); } - private String addClientTraceHeader() - { - String traceId = UUID.randomUUID().toString(); - addDefaultHeader( HEADER_CLIENT_TRACE_ID, traceId ); - addDefaultHeader( HEADER_CLIENT_API, String.valueOf( true ) ); - return traceId; - } - private void addApiVersionHeader( String apiVersion ) { if ( isNotBlank( apiVersion ) ) @@ -318,24 +275,6 @@ private void checkBaseUrl( String baseUrl ) } } - /** - * Not used since migration to jHTTPc library - */ - @Deprecated - public void connect( final HttpClientConnectionManager connectionManager ) - { - // NOP, now that we've moved to HttpFactory. - } - - /** - * Not used since migration to jHTTPc library - */ - @Deprecated - public synchronized void connect() - { - // NOP, now that we've moved to HttpFactory. - } - public Map head( final String path ) throws IndyClientException { @@ -347,7 +286,6 @@ public Map head( final String path, final int... responseCodes ) { HttpHead request = newJsonHead( buildUrl( baseUrl, path ) ); - connect(); CloseableHttpResponse response = null; CloseableHttpClient client = null; ClientMetrics metrics = metricManager.register( request ); @@ -401,8 +339,6 @@ public T get( final String path, final Class type ) HttpGet request = newJsonGet( buildUrl( baseUrl, path ) ); ClientMetrics metrics = metricManager.register( request ); - connect(); - CloseableHttpResponse response = null; CloseableHttpClient client = null; try @@ -450,7 +386,6 @@ public T get( final String path, final TypeReference typeRef ) HttpGet request = newJsonGet( buildUrl( baseUrl, path ) ); ClientMetrics metrics = metricManager.register( request ); - connect(); CloseableHttpResponse response = null; CloseableHttpClient client = null; try @@ -493,8 +428,6 @@ public HttpResources getRaw( final HttpGet req ) { ClientMetrics metrics = metricManager.register( req ); - connect(); - addLoggingMDCToHeaders( req ); CloseableHttpResponse response = null; try @@ -529,8 +462,6 @@ public HttpResources getRaw( final String path, final Map header final HttpGet req = newRawGet( buildUrl( baseUrl, path ) ); ClientMetrics metrics = metricManager.register( req ); - connect(); - CloseableHttpResponse response = null; try { @@ -570,8 +501,6 @@ public void putWithStream( final String path, final InputStream stream, final in final HttpPut put = newRawPut( buildUrl( baseUrl, path ) ); ClientMetrics metrics = metricManager.register( put ); - connect(); - addLoggingMDCToHeaders( put ); final CloseableHttpClient client = newClient(); CloseableHttpResponse response = null; @@ -625,7 +554,6 @@ public boolean put( final String path, final Object value, final int... response ClientMetrics metrics = metricManager.register( put ); checkRequestValue( value ); - connect(); CloseableHttpResponse response = null; CloseableHttpClient client = null; @@ -664,8 +592,6 @@ public HttpResources execute( HttpRequestBase request ) { ClientMetrics metrics = metricManager.register( request ); - connect(); - addLoggingMDCToHeaders( request ); CloseableHttpResponse response = null; try @@ -702,7 +628,6 @@ public HttpResources postRaw( final String path, Object value, final Map T postWithResponse( final String path, final Object value, final Clas checkRequestValue( value ); - connect(); - CloseableHttpResponse response = null; CloseableHttpClient client = null; try @@ -823,8 +746,6 @@ public T postWithResponse( final String path, final Object value, final Type checkRequestValue( value ); - connect(); - CloseableHttpResponse response = null; CloseableHttpClient client = null; try @@ -887,8 +808,6 @@ public void delete( final String path, final int... responseCodes ) HttpDelete delete = newDelete( buildUrl( baseUrl, path ) ); ClientMetrics metrics = metricManager.register( delete ); - connect(); - CloseableHttpResponse response = null; CloseableHttpClient client = null; try @@ -929,8 +848,6 @@ public void deleteWithChangelog( final String path, final String changelog, fina HttpDelete delete = newDelete( buildUrl( baseUrl, path ) ); ClientMetrics metrics = metricManager.register( delete ); - connect(); - CloseableHttpResponse response = null; CloseableHttpClient client = null; try @@ -984,8 +901,6 @@ public boolean exists( final String path, Supplier> querySup HttpHead request = newJsonHead( buildUrl( baseUrl, querySupplier, path ) ); ClientMetrics metrics = metricManager.register( request ); - connect(); - CloseableHttpResponse response = null; CloseableHttpClient client = null; try diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/helper/HttpResources.java b/core-java/src/main/java/org/commonjava/indy/client/core/helper/HttpResources.java index d51cd4d..5c910df 100644 --- a/core-java/src/main/java/org/commonjava/indy/client/core/helper/HttpResources.java +++ b/core-java/src/main/java/org/commonjava/indy/client/core/helper/HttpResources.java @@ -25,7 +25,7 @@ import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; -import org.commonjava.indy.client.core.metric.ClientMetrics; +import org.commonjava.indy.client.core.o11y.metric.ClientMetrics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +33,7 @@ import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import static org.apache.commons.io.IOUtils.closeQuietly; @@ -40,8 +41,8 @@ * Contains request, response, and client references, for passing raw data streams and the like back to the caller without losing track of the * resources that need to be closed when the caller is finished. *
- * NOTE: This class stores the response entity {@link InputStream}, and is NOT threadsafe! - * + * NOTE: This class stores the response entity {@link InputStream}, and is NOT thread safe! + * * @author jdcasey * */ @@ -55,7 +56,7 @@ public class HttpResources private final CloseableHttpClient client; - private ClientMetrics metrics; + private final ClientMetrics metrics; private InputStream responseEntityStream; @@ -130,7 +131,7 @@ public static String entityToString( final HttpResponse response ) stream = response.getEntity() .getContent(); - return IOUtils.toString( stream ); + return IOUtils.toString( stream, Charset.defaultCharset() ); } finally { @@ -161,7 +162,6 @@ public InputStream getResponseStream() @Override public void close() - throws IOException { Logger logger = LoggerFactory.getLogger( getClass() ); logger.debug( "Closing resources for: {}", request == null ? "UNKNOWN" : request.getRequestLine().getUri() ); diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/inject/ClientMetricConfig.java b/core-java/src/main/java/org/commonjava/indy/client/core/inject/ClientMetricConfig.java deleted file mode 100644 index 5902558..0000000 --- a/core-java/src/main/java/org/commonjava/indy/client/core/inject/ClientMetricConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-client) - * - * 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 org.commonjava.indy.client.core.inject; - -import javax.inject.Qualifier; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Qualifier -@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD } ) -@Retention( RetentionPolicy.RUNTIME ) -@Documented -public @interface ClientMetricConfig { - -} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/inject/ClientMetricSet.java b/core-java/src/main/java/org/commonjava/indy/client/core/inject/ClientMetricSet.java deleted file mode 100644 index b23fd09..0000000 --- a/core-java/src/main/java/org/commonjava/indy/client/core/inject/ClientMetricSet.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-client) - * - * 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 org.commonjava.indy.client.core.inject; - -import javax.inject.Qualifier; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Qualifier used to supply the client metric set. - */ -@Qualifier -@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD } ) -@Retention( RetentionPolicy.RUNTIME ) -@Documented -public @interface ClientMetricSet { - -} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientGoldenSignalsMetricSet.java b/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientGoldenSignalsMetricSet.java deleted file mode 100644 index 3aa4714..0000000 --- a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientGoldenSignalsMetricSet.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-client) - * - * 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 org.commonjava.indy.client.core.metric; - -import org.commonjava.indy.client.core.inject.ClientMetricSet; -import org.commonjava.o11yphant.metrics.sli.GoldenSignalsFunctionMetrics; -import org.commonjava.o11yphant.metrics.sli.GoldenSignalsMetricSet; - -import java.util.Arrays; -import java.util.Collection; - -import static org.commonjava.indy.client.core.metric.ClientMetricConstants.CLIENT_FUNCTIONS; - -@ClientMetricSet -public class ClientGoldenSignalsMetricSet - extends GoldenSignalsMetricSet { - - public ClientGoldenSignalsMetricSet() { - super(); - } - - @Override - protected Collection getFunctions() { - return Arrays.asList( CLIENT_FUNCTIONS ); - } - - public void clear() - { - getFunctionMetrics().clear(); - getFunctions().forEach( function -> { - getFunctionMetrics().put( function, new GoldenSignalsFunctionMetrics( function ) ); - } ); - } - -} \ No newline at end of file diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientGoldenSignalsSpanFieldsInjector.java b/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientGoldenSignalsSpanFieldsInjector.java deleted file mode 100644 index c4b7f31..0000000 --- a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientGoldenSignalsSpanFieldsInjector.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-client) - * - * 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 org.commonjava.indy.client.core.metric; - -import org.commonjava.o11yphant.metrics.api.Gauge; -import org.commonjava.o11yphant.metrics.api.Meter; -import org.commonjava.o11yphant.metrics.api.Metric; -import org.commonjava.o11yphant.metrics.api.Timer; -import org.commonjava.o11yphant.trace.spi.SpanFieldsInjector; -import org.commonjava.o11yphant.trace.spi.adapter.SpanAdapter; - -import java.util.Map; - -public class ClientGoldenSignalsSpanFieldsInjector - implements SpanFieldsInjector -{ - private ClientGoldenSignalsMetricSet goldenSignalsMetricSet; - - public ClientGoldenSignalsSpanFieldsInjector( ClientGoldenSignalsMetricSet goldenSignalsMetricSet ) - { - this.goldenSignalsMetricSet = goldenSignalsMetricSet; - } - - @Override - public void decorateSpanAtClose( SpanAdapter span ) - { - final Map metrics = goldenSignalsMetricSet.getMetrics(); - metrics.forEach( ( k, v ) -> { - Object value = null; - if ( v instanceof Gauge ) - { - value = ( (Gauge) v ).getValue(); - span.addField( "golden." + k, value ); - } - else if ( v instanceof Timer ) - { - value = ( (Timer) v ).getSnapshot().get95thPercentile(); - span.addField( "golden." + k + ".p95", value ); - } - else if ( v instanceof Meter ) - { - value = ( (Meter) v ).getOneMinuteRate(); - span.addField( "golden." + k + ".m1", value ); - } - } ); - } -} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricManager.java b/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricManager.java deleted file mode 100644 index 6e2fdf5..0000000 --- a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricManager.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-client) - * - * 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 org.commonjava.indy.client.core.metric; - -import org.apache.commons.lang3.StringUtils; -import org.apache.http.client.methods.HttpUriRequest; -import org.commonjava.indy.client.core.inject.ClientMetricSet; -import org.commonjava.o11yphant.otel.OtelConfiguration; -import org.commonjava.o11yphant.otel.OtelTracePlugin; -import org.commonjava.o11yphant.trace.SpanFieldsDecorator; -import org.commonjava.o11yphant.trace.TraceManager; -import org.commonjava.o11yphant.trace.TracerConfiguration; -import org.commonjava.o11yphant.trace.spi.O11yphantTracePlugin; -import org.commonjava.util.jhttpc.model.SiteConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import static org.apache.commons.lang3.StringUtils.isBlank; - -@SuppressWarnings( { "unused" } ) -public class ClientMetricManager -{ - - private final Logger logger = LoggerFactory.getLogger( getClass() ); - - private ClientTracerConfiguration configuration; - - private TraceManager traceManager; - - private final ClientTrafficClassifier classifier = new ClientTrafficClassifier(); - - @ClientMetricSet - private final ClientGoldenSignalsMetricSet metricSet = new ClientGoldenSignalsMetricSet(); - - public ClientMetricManager() - { - } - - public ClientMetricManager( SiteConfig siteConfig ) - { - buildConfig( siteConfig ); - buildTraceManager(); - } - - public ClientMetricManager( TracerConfiguration existedTraceConfig ) - { - buildTraceConfig( existedTraceConfig ); - buildTraceManager(); - } - - public ClientMetricManager( TraceManager traceManager ) - { - buildTraceConfig( traceManager.getConfig() ); - this.traceManager = traceManager; - } - - private void buildTraceConfig( TracerConfiguration existedTraceConfig ) - { - this.configuration = new ClientTracerConfiguration(); - this.configuration.setEnabled( existedTraceConfig.isEnabled() ); - this.configuration.setConsoleTransport( existedTraceConfig.isConsoleTransport() ); - this.configuration.setBaseSampleRate( existedTraceConfig.getBaseSampleRate() ); - this.configuration.setCpNames( existedTraceConfig.getCPNames() ); - this.configuration.setFields( existedTraceConfig.getFieldSet() ); - this.configuration.setEnvironmentMappings( existedTraceConfig.getEnvironmentMappings() ); - if ( existedTraceConfig instanceof OtelConfiguration ) - { - OtelConfiguration existedOtelConfig = (OtelConfiguration) existedTraceConfig; - this.configuration.setGrpcUri( existedOtelConfig.getGrpcEndpointUri() ); - this.configuration.setGrpcHeaders( existedOtelConfig.getGrpcHeaders() ); - this.configuration.setGrpcResources( existedOtelConfig.getResources() ); - } - } - - private void buildTraceManager() - { - if ( this.configuration.isEnabled() ) - { - O11yphantTracePlugin plugin = new OtelTracePlugin( configuration, configuration ); - if ( StringUtils.isNotBlank( configuration.getGrpcEndpointUri() ) ) - { - plugin = new OtelTracePlugin( configuration, configuration ); - } - this.traceManager = new TraceManager( plugin, new SpanFieldsDecorator( - Collections.singletonList( new ClientGoldenSignalsSpanFieldsInjector( metricSet ) ) ), - configuration ); - } - } - - public ClientMetrics register( HttpUriRequest request ) - { - logger.debug( "Client honey register: {}", request.getURI().getPath() ); - List functions = classifier.calculateClassifiers( request ); - - return new ClientMetrics( configuration.isEnabled(), request, functions, metricSet ); - } - - private void buildConfig( SiteConfig siteConfig ) - { - this.configuration = new ClientTracerConfiguration(); - this.configuration.setEnabled( siteConfig.isMetricEnabled() ); - this.configuration.setBaseSampleRate( siteConfig.getBaseSampleRate() ); - } - - private String getEndpointName( String method, String pathInfo ) - { - StringBuilder sb = new StringBuilder( method + "_" ); - String[] toks = pathInfo.split( "/" ); - for ( String s : toks ) - { - if ( isBlank( s ) || "api".equals( s ) ) - { - continue; - } - sb.append( s ); - if ( "admin".equals( s ) ) - { - sb.append( "_" ); - } - else - { - break; - } - } - return sb.toString(); - } - - public Optional getTraceManager() - { - return traceManager == null ? Optional.empty() : Optional.of( traceManager ); - } -} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricsProducer.java b/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricsProducer.java deleted file mode 100644 index 9dd1e2f..0000000 --- a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricsProducer.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-client) - * - * 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 org.commonjava.indy.client.core.metric; - -import org.commonjava.cdi.util.weft.config.DefaultWeftConfig; -import org.commonjava.cdi.util.weft.config.WeftConfig; -import org.commonjava.o11yphant.metrics.TrafficClassifier; -import org.commonjava.o11yphant.metrics.conf.DefaultMetricsConfig; -import org.commonjava.o11yphant.metrics.conf.MetricsConfig; -import org.commonjava.o11yphant.metrics.sli.GoldenSignalsMetricSet; -import org.commonjava.o11yphant.metrics.system.StoragePathProvider; - -import javax.enterprise.inject.Alternative; -import javax.enterprise.inject.Produces; - -/** - * This producer is used to provide the missing CDI deps for indy client metrics sets. Now all - * produces provided alternative ones, because it will break indy metrics providers. In future the - * indy-client libs will be extracted to a single lib, so then we will set these providers as default. - */ -public class ClientMetricsProducer -{ - @Produces - @Alternative - public TrafficClassifier getClientTrafficClassifier() - { - return new ClientTrafficClassifier(); - } - - @Produces - @Alternative - public GoldenSignalsMetricSet getClientMetricSet() - { - return new ClientGoldenSignalsMetricSet(); - } - - @Produces - @Alternative - public MetricsConfig getMetricsConfig() - { - return new DefaultMetricsConfig(); - } - - @Produces - @Alternative - public WeftConfig getWeftConfig() - { - return new DefaultWeftConfig(); - } - - @Produces - @Alternative - public StoragePathProvider getStoragePathProvider() - { - return () -> null; - } -} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricConstants.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientMetricConstants.java similarity index 76% rename from core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricConstants.java rename to core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientMetricConstants.java index 1b0c560..38ec718 100644 --- a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetricConstants.java +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientMetricConstants.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.commonjava.indy.client.core.metric; +package org.commonjava.indy.client.core.o11y.metric; -public class ClientMetricConstants { +public class ClientMetricConstants +{ public static final String CLIENT_FOLO_ADMIN = "client.folo.admin"; @@ -27,15 +28,10 @@ public class ClientMetricConstants { public static final String CLIENT_PROMOTE = "client.promote"; - public final static String HEADER_CLIENT_API = "Indy-Client-API"; - - public final static String HEADER_CLIENT_TRACE_ID = "Indy-Client-Trace-Id"; - - public final static String HEADER_CLIENT_SPAN_ID = "Indy-Client-Span-Id"; - public static final String[] CLIENT_FUNCTIONS = { CLIENT_FOLO_ADMIN, CLIENT_FOLO_CONTENT, CLIENT_REPO_MGMT, CLIENT_CONTENT, CLIENT_PROMOTE }; - private ClientMetricConstants() { + private ClientMetricConstants() + { } } diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientMetricManager.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientMetricManager.java new file mode 100644 index 0000000..482363d --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientMetricManager.java @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-client) + * + * 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 org.commonjava.indy.client.core.o11y.metric; + +import org.apache.http.client.methods.HttpUriRequest; +import org.commonjava.indy.client.core.o11y.trace.ClientTracerConfiguration; +import org.commonjava.indy.client.core.o11y.trace.TraceManager; +import org.commonjava.util.jhttpc.model.SiteConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Optional; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +public class ClientMetricManager +{ + + private final Logger logger = LoggerFactory.getLogger( getClass() ); + + private ClientTracerConfiguration configuration; + + private TraceManager traceManager; + + private final ClientTrafficClassifier classifier = new ClientTrafficClassifier(); + + public ClientMetricManager() + { + } + + public ClientMetricManager( SiteConfig siteConfig ) + { + this.configuration = new ClientTracerConfiguration(); + this.configuration.setEnabled( siteConfig.isMetricEnabled() ); + buildTraceManager(); + } + + public ClientMetricManager( ClientTracerConfiguration tracerConfig ) + { + this.configuration = tracerConfig; + buildTraceManager(); + } + + private void buildTraceManager() + { + if ( this.configuration.isEnabled() ) + { + this.traceManager = new TraceManager( configuration ); + } + } + + public ClientMetrics register( HttpUriRequest request ) + { + logger.debug( "Client honey register: {}", request.getURI().getPath() ); + List functions = classifier.calculateClassifiers( request ); + + return new ClientMetrics( configuration.isEnabled(), request, functions ); + } + + private String getEndpointName( String method, String pathInfo ) + { + StringBuilder sb = new StringBuilder( method + "_" ); + String[] toks = pathInfo.split( "/" ); + for ( String s : toks ) + { + if ( isBlank( s ) || "api".equals( s ) ) + { + continue; + } + sb.append( s ); + if ( "admin".equals( s ) ) + { + sb.append( "_" ); + } + else + { + break; + } + } + return sb.toString(); + } + + public Optional getTraceManager() + { + return Optional.ofNullable( traceManager ); + } +} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetrics.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientMetrics.java similarity index 54% rename from core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetrics.java rename to core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientMetrics.java index 903bbe9..c480ecf 100644 --- a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientMetrics.java +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientMetrics.java @@ -13,79 +13,76 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.commonjava.indy.client.core.metric; +package org.commonjava.indy.client.core.o11y.metric; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpUriRequest; -import org.commonjava.o11yphant.metrics.RequestContextHelper; -import org.commonjava.o11yphant.metrics.sli.GoldenSignalsFunctionMetrics; import org.slf4j.Logger; import java.io.Closeable; +import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Set; +import java.util.stream.Collectors; -import static org.commonjava.o11yphant.metrics.MetricsConstants.NANOS_PER_MILLISECOND; -import static org.commonjava.o11yphant.metrics.RequestContextConstants.REQUEST_LATENCY_MILLIS; -import static org.commonjava.o11yphant.metrics.RequestContextConstants.REQUEST_LATENCY_NS; -import static org.commonjava.o11yphant.metrics.RequestContextConstants.TRAFFIC_TYPE; -import static org.commonjava.o11yphant.trace.TraceManager.addFieldToActiveSpan; +import static org.commonjava.indy.client.core.o11y.trace.TraceManager.addFieldToCurrentSpan; import static org.slf4j.LoggerFactory.getLogger; public class ClientMetrics - extends ClientMetricManager implements Closeable + extends ClientMetricManager + implements Closeable { - private static final String ERROR = "request-error"; + private static final String REQUEST_ERROR = "request-error"; - private boolean enabled; + private static final String RESPONSE_ERROR = "response-error"; - private final HttpUriRequest request; + private static final double NANOS_PER_MILLISECOND = 1E6; + + private static final String REQUEST_LATENCY_NS = "request-latency-ns"; + + private static final String REQUEST_LATENCY_MILLIS = "latency_ms"; - private Collection functions; + private static final String TRAFFIC_TYPE = "traffic_type"; - private ClientGoldenSignalsMetricSet metricSet; + private final boolean enabled; + + private final HttpUriRequest request; + + private final Collection functions; private final Logger logger = getLogger( getClass().getName() ); private final long start; - private HttpResponse response; - private long end; - public ClientMetrics( boolean enabled, HttpUriRequest request, Collection functions, - ClientGoldenSignalsMetricSet metricSet ) + public ClientMetrics( boolean enabled, HttpUriRequest request, Collection functions ) { this.enabled = enabled; this.request = request; this.functions = functions; - this.metricSet = metricSet; this.start = System.nanoTime(); if ( enabled ) { logger.debug( "Client trace starting: {}", request.getURI().getPath() ); - functions.forEach( function -> metricSet.function( function ).ifPresent( GoldenSignalsFunctionMetrics::started ) ); - Set classifierTokens = new LinkedHashSet<>(); functions.forEach( function -> { String[] parts = function.split( "\\." ); - for ( int i = 0; i < parts.length - 1; i++ ) - { - classifierTokens.add( parts[i] ); - } + classifierTokens.addAll( Arrays.asList( parts ).subList( 0, parts.length - 1 ) ); } ); String classification = StringUtils.join( classifierTokens, "," ); RequestContextHelper.setContext( TRAFFIC_TYPE, classification ); - addFieldToActiveSpan( TRAFFIC_TYPE, classification ); + addFieldToCurrentSpan( TRAFFIC_TYPE, classification ); } } - public void registerErr( Object error ) { + public void registerErr( Object error ) + { if ( !enabled ) { return; @@ -94,18 +91,13 @@ public void registerErr( Object error ) { logger.debug( "Client trace registerErr: {}", request.getURI().getPath() ); if ( error instanceof Throwable ) { - StringBuilder sb = new StringBuilder(); - sb.append( error.getClass().getSimpleName() ); - sb.append( ": " ); - sb.append( ( (Throwable) error ).getMessage() ); - addFieldToActiveSpan( ERROR, sb ); + String errorMsg = error.getClass().getSimpleName() + ": " + ( (Throwable) error ).getMessage(); + addFieldToCurrentSpan( REQUEST_ERROR, errorMsg ); } else { - addFieldToActiveSpan( ERROR, error ); + addFieldToCurrentSpan( REQUEST_ERROR, error ); } - functions.forEach( function -> metricSet.function( function ) - .ifPresent( GoldenSignalsFunctionMetrics::error ) ); } public void registerEnd( HttpResponse response ) @@ -115,17 +107,21 @@ public void registerEnd( HttpResponse response ) return; } - this.response = response; - logger.debug( "Client trace registerEnd: {}", request.getURI().getPath() ); - boolean error = ( response != null && response.getStatusLine() != null ) && ( response.getStatusLine().getStatusCode() > 499 ); - - functions.forEach( function -> metricSet.function( function ).ifPresent( functionMetrics -> { - functionMetrics.latency( end - start ).call(); - if ( error ) { - functionMetrics.error(); + boolean error = response.getStatusLine() != null && response.getStatusLine().getStatusCode() > 499; + + functions.forEach( function -> { + if ( Arrays.stream( ClientMetricConstants.CLIENT_FUNCTIONS ) + .collect( Collectors.toSet() ) + .contains( function ) ) + { + addFieldToCurrentSpan( function + ".latency", String.valueOf( end - start ) ); + if ( error ) + { + addFieldToCurrentSpan( function + "." + RESPONSE_ERROR, error ); + } } - } ) ); + } ); } @Override @@ -142,12 +138,8 @@ public void close() RequestContextHelper.setContext( REQUEST_LATENCY_NS, String.valueOf( end - start ) ); RequestContextHelper.setContext( REQUEST_LATENCY_MILLIS, ( end - start ) / NANOS_PER_MILLISECOND ); - if ( metricSet.getFunctionMetrics().isEmpty() ) { - logger.trace( "Client trace metricSet is empty: {}", request.getURI().getPath() ); - return; - } String pathInfo = request.getURI().getPath(); - addFieldToActiveSpan( "path_info", pathInfo ); + addFieldToCurrentSpan( "path_info", pathInfo ); } } diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientTrafficClassifier.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientTrafficClassifier.java similarity index 74% rename from core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientTrafficClassifier.java rename to core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientTrafficClassifier.java index 1ae1f8e..d9637c7 100644 --- a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientTrafficClassifier.java +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/ClientTrafficClassifier.java @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.commonjava.indy.client.core.metric; +package org.commonjava.indy.client.core.o11y.metric; import org.apache.http.Header; import org.apache.http.client.methods.HttpUriRequest; -import org.commonjava.o11yphant.metrics.TrafficClassifier; -import javax.enterprise.inject.Alternative; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -30,21 +28,12 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.apache.commons.lang3.StringUtils.join; -import static org.commonjava.indy.client.core.metric.ClientMetricConstants.CLIENT_CONTENT; -import static org.commonjava.indy.client.core.metric.ClientMetricConstants.CLIENT_FOLO_ADMIN; -import static org.commonjava.indy.client.core.metric.ClientMetricConstants.CLIENT_FOLO_CONTENT; -import static org.commonjava.indy.client.core.metric.ClientMetricConstants.CLIENT_PROMOTE; -import static org.commonjava.indy.client.core.metric.ClientMetricConstants.CLIENT_REPO_MGMT; - -@Alternative public class ClientTrafficClassifier - implements TrafficClassifier { public static final Set FOLO_RECORD_ENDPOINTS = new HashSet<>( asList( "record", "report" ) ); - @Override public List classifyFunctions( String restPath, String method, Map headers ) { return calculateCachedFunctionClassifiers( restPath, method, headers ); } @@ -68,16 +57,16 @@ protected List calculateCachedFunctionClassifiers( String restPath, Stri String restPrefix = join( classifierParts, '/' ); if ( restPrefix.startsWith( "folo/admin/" ) && FOLO_RECORD_ENDPOINTS.contains( classifierParts[ 3 ] ) ) { - result = singletonList( CLIENT_FOLO_ADMIN ); + result = singletonList( ClientMetricConstants.CLIENT_FOLO_ADMIN ); } else if ( restPrefix.startsWith( "folo/track/" ) && classifierParts.length > 6 ) { - result = singletonList( CLIENT_FOLO_CONTENT ); + result = singletonList( ClientMetricConstants.CLIENT_FOLO_CONTENT ); } else if ( "admin".equals( classifierParts[ 0 ] ) && "stores".equals( classifierParts[ 1 ] ) && classifierParts.length > 2 ) { - result = singletonList( CLIENT_REPO_MGMT ); + result = singletonList( ClientMetricConstants.CLIENT_REPO_MGMT ); } else if ( ( "content".equals( classifierParts[ 0 ] ) && classifierParts.length > 5 ) ) { - result = singletonList( CLIENT_CONTENT ); + result = singletonList( ClientMetricConstants.CLIENT_CONTENT ); } else if ( restPrefix.startsWith( "promotion/paths/" ) ) { - result = singletonList( CLIENT_PROMOTE ); + result = singletonList( ClientMetricConstants.CLIENT_PROMOTE ); } } return result; diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/RequestContextHelper.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/RequestContextHelper.java new file mode 100644 index 0000000..458677a --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/metric/RequestContextHelper.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. + * + * 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 org.commonjava.indy.client.core.o11y.metric; + +import org.commonjava.indy.client.core.util.ThreadContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +/** + * The scope annotations (Thread, Header, MDC) tell where the constant is available/used. The static methods are used + * to manage contextual state in both MDC and ThreadContext. + */ +public class RequestContextHelper +{ + private static final Logger logger = LoggerFactory.getLogger( RequestContextHelper.class ); + + private static final String RAW_IO_WRITE_NANOS = "raw-io-write-nanos"; + + private static final String END_NANOS = "latency-end-nanos"; + + public static void setContext( final String key, final Object value ) + { + MDC.put( key, String.valueOf( value ) ); + ThreadContext ctx = ThreadContext.getContext( true ); + logger.trace( "Setting value: '{}' = '{}' in ThreadContext: {}", key, value, ctx ); + ctx.computeIfAbsent( key, k -> value ); + } + + public static T getContext( final String key, final T defaultValue ) + { + ThreadContext ctx = ThreadContext.getContext( false ); + if ( ctx != null ) + { + logger.trace( "Retrieving value for: '{}' from ThreadContext: {}", key, ctx ); + Object v = ctx.get( key ); + //noinspection unchecked + return v == null ? defaultValue : (T) v; + } + + return defaultValue; + } + + public static long getRequestEndNanos() + { + return getContext( END_NANOS, System.nanoTime() ); + } + + public static long getRawIoWriteNanos() + { + return getContext( RAW_IO_WRITE_NANOS, 0L ); + } + +} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientTracerConfiguration.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/ClientTracerConfiguration.java similarity index 58% rename from core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientTracerConfiguration.java rename to core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/ClientTracerConfiguration.java index a227dba..9a6d44a 100644 --- a/core-java/src/main/java/org/commonjava/indy/client/core/metric/ClientTracerConfiguration.java +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/ClientTracerConfiguration.java @@ -13,21 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.commonjava.indy.client.core.metric; +package org.commonjava.indy.client.core.o11y.trace; import org.apache.commons.lang3.StringUtils; -import org.commonjava.indy.client.core.inject.ClientMetricConfig; -import org.commonjava.o11yphant.otel.OtelConfiguration; -import org.commonjava.o11yphant.trace.TracerConfiguration; import java.util.HashMap; import java.util.Map; -import java.util.Set; -@SuppressWarnings( "unused" ) -@ClientMetricConfig public class ClientTracerConfiguration - implements TracerConfiguration, OtelConfiguration { private static final Integer DEFAULT_BASE_SAMPLE_RATE = 100; @@ -39,93 +32,37 @@ public class ClientTracerConfiguration private boolean consoleTransport; - private String writeKey; - - private String dataset; - - private Integer baseSampleRate; - - private final Map spanRates = new HashMap<>(); - - private Set fields; - - private String environmentMappings; - - private String cpNames; - private String grpcUri; private Map grpcHeaders = new HashMap<>(); private Map grpcResources = new HashMap<>(); - @Override - public Map getSpanRates() - { - return spanRates; - } - - @Override public boolean isEnabled() { return enabled; } - @Override public boolean isConsoleTransport() { return consoleTransport; } - @Override - public Integer getBaseSampleRate() - { - return baseSampleRate == null ? DEFAULT_BASE_SAMPLE_RATE : baseSampleRate; - } - - @Override - public Set getFieldSet() - { - return fields == null ? DEFAULT_FIELDS : fields; - } - - @Override - public String getEnvironmentMappings() - { - return environmentMappings; - } - - @Override - public String getCPNames() - { - return cpNames; - } - - @Override public Map getGrpcHeaders() { return grpcHeaders; } - @Override public Map getResources() { return grpcResources; } - @Override public String getServiceName() { return StringUtils.isBlank( serviceName ) ? DEFAULT_INDY_CLIENT_SERVICE_NAME : serviceName; } - @Override - public String getNodeId() - { - return null; - } - - @Override public String getGrpcEndpointUri() { return grpcUri == null ? DEFAULT_GRPC_URI : grpcUri; @@ -141,26 +78,6 @@ public void setEnabled( boolean enabled ) this.enabled = enabled; } - public void setBaseSampleRate( Integer baseSampleRate ) - { - this.baseSampleRate = baseSampleRate; - } - - public void setFields( Set fields ) - { - this.fields = fields; - } - - public void setEnvironmentMappings( String environmentMappings ) - { - this.environmentMappings = environmentMappings; - } - - public void setCpNames( String cpNames ) - { - this.cpNames = cpNames; - } - public void setGrpcUri( String grpcUri ) { this.grpcUri = grpcUri; @@ -180,4 +97,17 @@ public void setGrpcResources( Map grpcResources ) { this.grpcResources = grpcResources; } + + String DEFAULT_GRPC_URI = "http://localhost:55680"; + + public String getInstrumentationName() + { + return "org.commonjava.indy.client"; + } + + public String getInstrumentationVersion() + { + return "3.4.0"; + } + } diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/NameUtils.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/NameUtils.java new file mode 100644 index 0000000..0436aa3 --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/NameUtils.java @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. + * + * 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 org.commonjava.indy.client.core.o11y.trace; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +public class NameUtils +{ + private static final String DEFAULT = "default"; + + public static String name( Class klass, String... names ) + { + return name( klass.getSimpleName(), names ); + } + + public static String name( String name, String... names ) + { + StringBuilder builder = new StringBuilder(); + append( builder, name ); + if ( names != null ) + { + int var4 = names.length; + + for ( String s : names ) + { + append( builder, s ); + } + } + + return builder.toString(); + } + + private static void append( StringBuilder builder, String part ) + { + if ( part != null && !part.isEmpty() ) + { + if ( builder.length() > 0 ) + { + builder.append( '.' ); + } + + builder.append( part ); + } + + } + + /** + * Get default metric name. Experience has shown that we don't need to include all of the package details + * for each method metric we're gathering. The class names are unique enough to be useful without this. + * We need to migrate to an easier format: + * .. + */ + public static String getDefaultName( Class declaringClass, String method ) + { + return name( declaringClass.getSimpleName(), method ); + } + + /** + * Get the metric fullname with no default value. + * @param nameParts user specified name parts + */ + public static String getSupername( String nodePrefix, String... nameParts ) + { + return name( nodePrefix, nameParts ); + } + + /** + * Get the metric fullname. + * @param name user specified name + * @param defaultName 'class name + method name', not null. + */ + public static String getName( String nodePrefix, String name, String defaultName, String... suffix ) + { + if ( isBlank( name ) || name.equals( DEFAULT ) ) + { + name = defaultName; + } + return name( name( nodePrefix, name ), suffix ); + } +} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/OtelProvider.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/OtelProvider.java new file mode 100644 index 0000000..8a4c88c --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/OtelProvider.java @@ -0,0 +1,181 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. + * + * 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 org.commonjava.indy.client.core.o11y.trace; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.exporter.logging.LoggingSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import io.opentelemetry.sdk.trace.SpanProcessor; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +public class OtelProvider +{ + private static final Logger logger = LoggerFactory.getLogger( OtelProvider.class ); + + private SpanProvider spanProvider; + + private static volatile OpenTelemetry defaultOtel; + + private static final Object mutex = new Object(); + + private static void set( OpenTelemetry openTelemetry ) + { + synchronized ( mutex ) + { + if ( defaultOtel != null ) + { + logger.warn( "Note: Otel has been set up! Please check somewhere else if you have set it!" ); + } + defaultOtel = openTelemetry; + } + } + + public OtelProvider( ClientTracerConfiguration traceConfiguration, SpanExporter... exporters ) + { + if ( traceConfiguration.isEnabled() ) + { + OpenTelemetry otel = getOpenTelemetry( traceConfiguration, exporters ); + Tracer tracer = otel.getTracer( traceConfiguration.getInstrumentationName(), + traceConfiguration.getInstrumentationVersion() ); + + this.spanProvider = new SpanProvider( tracer ); + } + } + + public SpanProvider getSpanProvider() + { + return spanProvider; + } + + public void injectContext( BiConsumer consumer, SpanWrapper clientSpan ) + { + try (Scope scope = clientSpan.makeCurrent()) + { + defaultOtel.getPropagators() + .getTextMapPropagator() + .inject( Context.current(), consumer, ( biConsumer, t, u ) -> { + if ( biConsumer != null ) + { + biConsumer.accept( t, u ); + } + } ); + } + } + + public OpenTelemetry getOpenTelemetry( ClientTracerConfiguration traceConfiguration, SpanExporter... exporters ) + { + logger.debug( "Trace enabled with Otel trace plugin." ); + SpanExporter[] spanExporters = exporters; + //FIXME: This needs to be more exposed to configuration options, especially for endpoint and exporter formats. + if ( exporters == null || exporters.length < 1 ) + { + final String grpcEndpoint = traceConfiguration.getGrpcEndpointUri(); + logger.info( "Trace grpc endpoint is configured as: {}", grpcEndpoint ); + List exp = new ArrayList<>(); + if ( traceConfiguration.isConsoleTransport() ) + { + exp.add( LoggingSpanExporter.create() ); + } + + OtlpGrpcSpanExporterBuilder grpcExporterBuilder = OtlpGrpcSpanExporter.builder(); + grpcExporterBuilder.setEndpoint( grpcEndpoint ); + Map exporterHeaders = traceConfiguration.getGrpcHeaders(); + if ( exporterHeaders != null ) + { + exporterHeaders.forEach( grpcExporterBuilder::addHeader ); + } + + grpcExporterBuilder.build(); + exp.add( grpcExporterBuilder.build() ); + + spanExporters = exp.toArray( new SpanExporter[] {} ); + } + + SpanProcessor processor = BatchSpanProcessor.builder( SpanExporter.composite( spanExporters ) ).build(); + + SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder().addSpanProcessor( processor ); + + Map otelResources = traceConfiguration.getResources(); + if ( otelResources != null && !otelResources.isEmpty() ) + { + logger.debug( "Additional Trace Attributes for OTEL: {}", otelResources ); + AttributesBuilder builder = Attributes.builder(); + otelResources.forEach( builder::put ); + Resource resource = Resource.getDefault().merge( Resource.create( builder.build() ) ); + tracerProviderBuilder.setResource( resource ); + } + + SdkTracerProvider tracerProvider = tracerProviderBuilder.build(); + + OpenTelemetry otel; + try + { + otel = OpenTelemetrySdk.builder() + .setTracerProvider( tracerProvider ) + .setPropagators( + ContextPropagators.create( W3CTraceContextPropagator.getInstance() ) ) + .build(); + if ( getDefaultOpenTelemetry() == null ) + { + set( otel ); + } + else + { + logger.info( + "A default opentelemetry has been setup. You can call getDefaultOpenTelemetry() to get it." ); + } + logger.debug( "The OpenTelemetry instance has been setup successfully." ); + } + catch ( IllegalStateException e ) + { + logger.warn( "The OpenTelemetry instance has not been setup successfully. Error: {}", e.getMessage() ); + if ( getDefaultOpenTelemetry() != null ) + { + logger.warn( "Will use the default OpenTelemetry as it's setup somewhere." ); + return getDefaultOpenTelemetry(); + } + logger.warn( "Will use the global one." ); + otel = GlobalOpenTelemetry.get(); + } + return otel; + } + + public static OpenTelemetry getDefaultOpenTelemetry() + { + return defaultOtel; + } +} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanClosingResponse.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanClosingResponse.java new file mode 100644 index 0000000..c7f4701 --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanClosingResponse.java @@ -0,0 +1,335 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. + * + * 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 org.commonjava.indy.client.core.o11y.trace; + +import org.apache.http.Header; +import org.apache.http.HeaderIterator; +import org.apache.http.HttpEntity; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.params.HttpParams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Locale; +import java.util.Optional; + +public class SpanClosingResponse + implements CloseableHttpResponse +{ + private final CloseableHttpResponse delegate; + + private final SpanWrapper span; + + private final Logger logger = LoggerFactory.getLogger( getClass() ); + + public static final String LATENCY_TIMER_PAUSE_KEY = "add.latency_pause"; + + public SpanClosingResponse( CloseableHttpResponse delegate, SpanWrapper span ) + { + this.delegate = delegate; + this.span = span; + } + + @Override + public StatusLine getStatusLine() + { + return delegate.getStatusLine(); + } + + @Override + public void setStatusLine( StatusLine statusLine ) + { + delegate.setStatusLine( statusLine ); + } + + @Override + public void setStatusLine( ProtocolVersion protocolVersion, int i ) + { + delegate.setStatusLine( protocolVersion, i ); + } + + @Override + public void setStatusLine( ProtocolVersion protocolVersion, int i, String s ) + { + delegate.setStatusLine( protocolVersion, i, s ); + } + + @Override + public void setStatusCode( int i ) + throws IllegalStateException + { + delegate.setStatusCode( i ); + } + + @Override + public void setReasonPhrase( String s ) + throws IllegalStateException + { + delegate.setReasonPhrase( s ); + } + + @Override + public HttpEntity getEntity() + { + HttpEntity entity = delegate.getEntity(); + return entity == null ? null : new LatencyPauseEntity( entity, this.span ); + } + + @Override + public void setEntity( HttpEntity httpEntity ) + { + delegate.setEntity( httpEntity ); + } + + @Override + public Locale getLocale() + { + return delegate.getLocale(); + } + + @Override + public void setLocale( Locale locale ) + { + delegate.setLocale( locale ); + } + + @Override + public ProtocolVersion getProtocolVersion() + { + return delegate.getProtocolVersion(); + } + + @Override + public boolean containsHeader( String s ) + { + return delegate.containsHeader( s ); + } + + @Override + public Header[] getHeaders( String s ) + { + return delegate.getHeaders( s ); + } + + @Override + public Header getFirstHeader( String s ) + { + return delegate.getFirstHeader( s ); + } + + @Override + public Header getLastHeader( String s ) + { + return delegate.getLastHeader( s ); + } + + @Override + public Header[] getAllHeaders() + { + return delegate.getAllHeaders(); + } + + @Override + public void addHeader( Header header ) + { + delegate.addHeader( header ); + } + + @Override + public void addHeader( String s, String s1 ) + { + delegate.addHeader( s, s1 ); + } + + @Override + public void setHeader( Header header ) + { + delegate.setHeader( header ); + } + + @Override + public void setHeader( String s, String s1 ) + { + delegate.setHeader( s, s1 ); + } + + @Override + public void setHeaders( Header[] headers ) + { + delegate.setHeaders( headers ); + } + + @Override + public void removeHeader( Header header ) + { + delegate.removeHeader( header ); + } + + @Override + public void removeHeaders( String s ) + { + delegate.removeHeaders( s ); + } + + @Override + public HeaderIterator headerIterator() + { + return delegate.headerIterator(); + } + + @Override + public HeaderIterator headerIterator( String s ) + { + return delegate.headerIterator( s ); + } + + @Override + @Deprecated + public HttpParams getParams() + { + return delegate.getParams(); + } + + @Override + @Deprecated + public void setParams( HttpParams httpParams ) + { + delegate.setParams( httpParams ); + } + + @Override + public void close() + throws IOException + { + delegate.close(); + try + { + Optional.ofNullable( span ).ifPresent( SpanWrapper::close ); + } + catch ( Throwable t ) + { + final String errorMsg = "Failed to close http response span: " + t.getLocalizedMessage(); + logger.error( errorMsg, t ); + if ( t instanceof RuntimeException ) + { + throw (RuntimeException) t; + } + } + } + + private static class LatencyPauseEntity + implements HttpEntity + { + private final HttpEntity entity; + + private final SpanWrapper span; + + public LatencyPauseEntity( HttpEntity entity, SpanWrapper span ) + { + this.entity = entity; + this.span = span; + } + + @Override + public boolean isRepeatable() + { + return entity.isRepeatable(); + } + + @Override + public boolean isChunked() + { + return entity.isChunked(); + } + + @Override + public long getContentLength() + { + return entity.getContentLength(); + } + + @Override + public Header getContentType() + { + return entity.getContentType(); + } + + @Override + public Header getContentEncoding() + { + return entity.getContentEncoding(); + } + + @Override + public InputStream getContent() + throws IOException, IllegalStateException + { + return new LatencyPauseInputStream( entity.getContent(), span ); + } + + @Override + public void writeTo( OutputStream outputStream ) + throws IOException + { + entity.writeTo( outputStream ); + } + + @Override + public boolean isStreaming() + { + return entity.isStreaming(); + } + + @Override + @Deprecated + public void consumeContent() + throws IOException + { + entity.consumeContent(); + } + } + + private static class LatencyPauseInputStream + extends FilterInputStream + { + private final long start = System.nanoTime(); + + private final SpanWrapper span; + + public LatencyPauseInputStream( InputStream content, SpanWrapper span ) + { + super( content ); + this.span = span; + } + + @Override + public void close() + throws IOException + { + double elapsed = System.nanoTime() - start; + Optional.ofNullable( this.span ) + .ifPresent( s -> s.updateInProgressField( LATENCY_TIMER_PAUSE_KEY, elapsed ) ); + super.close(); + } + } +} + diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanProvider.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanProvider.java new file mode 100644 index 0000000..6610596 --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanProvider.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. + * + * 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 org.commonjava.indy.client.core.o11y.trace; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SpanProvider +{ + private final Logger logger = LoggerFactory.getLogger( this.getClass() ); + + private final Tracer tracer; + + @SuppressWarnings( "PMD" ) + public SpanProvider( Tracer tracer ) + { + this.tracer = tracer; + } + + public SpanWrapper startClientSpan( String spanName ) + { + return startClientSpan( spanName, null ); + } + + public SpanWrapper startClientSpan( String spanName, Context parentContext ) + { + SpanBuilder spanBuilder = tracer.spanBuilder( spanName ); + Context ctx = Context.current(); + if ( parentContext != null ) + { + ctx = parentContext; + spanBuilder.setParent( ctx ); + logger.trace( "The span {} is using a parent context {}", spanName, ctx ); + } + logger.trace( "Start a new client span {}", spanName ); + if ( ctx != null ) + { + logger.trace( "The span {} is using a parent context {}", spanName, ctx ); + spanBuilder.setParent( ctx ); + } + else + { + spanBuilder.setNoParent(); + } + + Span span = spanBuilder.setSpanKind( SpanKind.CLIENT ).startSpan(); + try (Scope ignored = span.makeCurrent()) + { + logger.trace( "span with id {} started in trace {}", span.getSpanContext().getSpanId(), + span.getSpanContext().getTraceId() ); + } + return new SpanWrapper( span ); + } +} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanWrapper.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanWrapper.java new file mode 100644 index 0000000..6207b9b --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanWrapper.java @@ -0,0 +1,129 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. + * + * 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 org.commonjava.indy.client.core.o11y.trace; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class SpanWrapper +{ + private final Logger logger = LoggerFactory.getLogger( this.getClass() ); + + private final Span span; + + private final Map inProgress = new HashMap<>(); + + private final Map attributes = new HashMap<>(); + + public SpanWrapper( Span span ) + { + this.span = span; + } + + public String getTraceId() + { + return span.getSpanContext().getTraceId(); + } + + public String getSpanId() + { + return span.getSpanContext().getSpanId(); + } + + public Map getFields() + { + return attributes; + } + + public void close() + { + attributes.forEach( ( k, v ) -> span.setAttribute( k, (String) v ) ); + inProgress.forEach( span::setAttribute ); + + SpanContext context = span.getSpanContext(); + logger.trace( "Closing span {} in trace {}", getSpanId(), getTraceId() ); + span.end(); + } + + public void setInProgressField( String key, Double value ) + { + inProgress.put( key, value ); + } + + public Double getInProgressField( String key, Double defValue ) + { + return inProgress.getOrDefault( key, defValue ); + } + + public synchronized void updateInProgressField( String key, Double value ) + { + Double mappedVal = inProgress.getOrDefault( key, 0.0 ); + mappedVal += value; + inProgress.put( key, mappedVal ); + } + + public void clearInProgressField( String key ) + { + inProgress.remove( key ); + } + + public Map getInProgressFields() + { + return inProgress; + } + + public Optional getSpanContext() + { + return Optional.ofNullable( Context.current().with( span ) ); + } + + public Scope makeCurrent() + { + return span.makeCurrent(); + } + + public String toString() + { + return span.toString(); + } + + public void addField( String name, T value ) + { + Logger logger = LoggerFactory.getLogger( TraceManager.class ); + + if ( logger.isTraceEnabled() ) + { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + logger.trace( "Adding field: {} with value: {} to span: {} from:\n {}\n {}", name, value, getSpanId(), + st[3], st[4] ); + } + + attributes.put( name, String.valueOf( value ) ); + } + + public static Optional current() + { + return Span.current() == null ? Optional.empty() : Optional.of( new SpanWrapper( Span.current() ) ); + } +} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanningHttpFactory.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanningHttpFactory.java new file mode 100644 index 0000000..625167a --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/SpanningHttpFactory.java @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. + * + * 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 org.commonjava.indy.client.core.o11y.trace; + +import org.apache.http.Header; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.client.CloseableHttpClient; +import org.commonjava.util.jhttpc.HttpFactory; +import org.commonjava.util.jhttpc.HttpFactoryIfc; +import org.commonjava.util.jhttpc.JHttpCException; +import org.commonjava.util.jhttpc.auth.PasswordManager; +import org.commonjava.util.jhttpc.model.SiteConfig; + +import java.io.IOException; +import java.util.List; + +public class SpanningHttpFactory + implements HttpFactoryIfc +{ + private final HttpFactory delegate; + + private final TraceManager traceManager; + + public SpanningHttpFactory( HttpFactory httpFactory, TraceManager traceManager ) + { + super(); + delegate = httpFactory; + this.traceManager = traceManager; + } + + public PasswordManager getPasswordManager() + { + return delegate.getPasswordManager(); + } + + @Override + public CloseableHttpClient createClient() + throws JHttpCException + { + return new TracerHttpClient( delegate.createClient(), traceManager ); + } + + @Override + public CloseableHttpClient createClient( SiteConfig location ) + throws JHttpCException + { + return new TracerHttpClient( delegate.createClient( location ), traceManager ); + } + + @Override + public CloseableHttpClient createClient( SiteConfig location, List
defaultHeaders ) + throws JHttpCException + { + return new TracerHttpClient( delegate.createClient( location, defaultHeaders ), traceManager ); + } + + @Override + public HttpClientContext createContext() + throws JHttpCException + { + return delegate.createContext(); + } + + @Override + public HttpClientContext createContext( SiteConfig location ) + throws JHttpCException + { + return delegate.createContext( location ); + } + + @Override + public void close() + throws IOException + { + delegate.close(); + } + + @Override + public boolean isShutdown() + { + return delegate.isShutdown(); + } + + @Override + public boolean shutdownNow() + { + return delegate.shutdownNow(); + } + + @Override + public boolean shutdownGracefully( long timeoutMillis ) + throws InterruptedException + { + return delegate.shutdownGracefully( timeoutMillis ); + } +} + diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/TraceManager.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/TraceManager.java new file mode 100644 index 0000000..e6f242a --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/TraceManager.java @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. + * + * 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 org.commonjava.indy.client.core.o11y.trace; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; +import java.util.Queue; +import java.util.function.BiConsumer; + +public final class TraceManager +{ + private static final ThreadLocal> ACTIVE_SPAN = new ThreadLocal<>(); + + // private static final String ACTIVE_SPAN_KEY = "active-trace-span"; + + private final OtelProvider otelProvider; + + private final ClientTracerConfiguration config; + + private final Logger logger = LoggerFactory.getLogger( getClass().getName() ); + + public TraceManager( ClientTracerConfiguration config ) + { + this.otelProvider = new OtelProvider( config ); + this.config = config; + } + + public Optional startClientRequestSpan( String spanName, BiConsumer spanInjector ) + { + if ( !config.isEnabled() ) + { + return Optional.empty(); + } + + SpanWrapper span = otelProvider.getSpanProvider().startClientSpan( spanName ); + if ( span != null ) + { + otelProvider.injectContext( spanInjector, span ); + logger.trace( "Started span: {}", span.getSpanId() ); + } + + return Optional.ofNullable( span ); + } + + public static void addFieldToCurrentSpan( String name, Object value ) + { + Logger logger = LoggerFactory.getLogger( TraceManager.class ); + Optional current = SpanWrapper.current(); + current.ifPresent( span -> { + if ( logger.isTraceEnabled() ) + { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + logger.trace( "Adding field: {} with value: {} to span: {} from:\n {}\n {}", name, value, + span.getSpanId(), st[3], st[4] ); + } + + span.addField( name, value ); + } ); + + if ( current.isEmpty() && logger.isTraceEnabled() ) + { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + logger.info( "NO ACTIVE SPAN for: {} from:\n {}\n {}", name, st[2], st[3] ); + } + } + + public ClientTracerConfiguration getConfig() + { + return this.config; + } +} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/TracerHttpClient.java b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/TracerHttpClient.java new file mode 100644 index 0000000..e238b7e --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/o11y/trace/TracerHttpClient.java @@ -0,0 +1,152 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. + * + * 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 org.commonjava.indy.client.core.o11y.trace; + +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URL; +import java.util.Optional; +import java.util.function.BiConsumer; + +public class TracerHttpClient + extends CloseableHttpClient +{ + private final CloseableHttpClient delegate; + + private final TraceManager traceManager; + + private final Logger logger = LoggerFactory.getLogger( getClass() ); + + public TracerHttpClient( CloseableHttpClient delegate, TraceManager traceManager ) + { + this.traceManager = traceManager; + this.delegate = delegate; + } + + @Override + protected CloseableHttpResponse doExecute( HttpHost target, HttpRequest request, HttpContext context ) + throws IOException + { + try + { + URL url = new URL( request.getRequestLine().getUri() ); + Optional span; + if ( traceManager != null ) + { + span = traceManager.startClientRequestSpan( + request.getRequestLine().getMethod() + "_" + url.getHost() + "_" + url.getPort(), + contextInjector( request ) ); + } + else + { + span = Optional.empty(); + } + + TraceManager.addFieldToCurrentSpan( "target-http-url", request.getRequestLine().getUri() ); + printHttpRequestHeaders( request ); + CloseableHttpResponse response = delegate.execute( target, request, context ); + printHttpResponseHeaders( response ); + if ( response != null ) + { + span.ifPresent( s -> s.addField( "target-http-status", response.getStatusLine().getStatusCode() ) ); + } + + return new SpanClosingResponse( response, span.orElse( null ) ); + } + catch ( IOException e ) + { + throw e; + } + catch ( Throwable t ) + { + final String errorMsg = "Failed to execute http request: " + t.getLocalizedMessage(); + logger.error( errorMsg, t ); + if ( t instanceof RuntimeException ) + { + throw (RuntimeException) t; + } + throw new RuntimeException( "Failed to execute: " + t.getMessage(), t ); + } + } + + private void printHttpRequestHeaders( HttpRequest request ) + { + if ( logger.isTraceEnabled() ) + { + Header[] headers = request.getAllHeaders(); + logger.trace( "========= Start print request headers for request {}: ====================", + request.getRequestLine() ); + for ( Header header : headers ) + { + logger.trace( "{} -> {}", header.getName(), header.getValue() ); + } + logger.trace( "========= Stop print request headers for request {}: ====================", + request.getRequestLine() ); + } + } + + private void printHttpResponseHeaders( HttpResponse response ) + { + if ( logger.isTraceEnabled() ) + { + Header[] headers = response.getAllHeaders(); + logger.trace( "========= Start print response headers for response {}: ====================", + response.getStatusLine() ); + for ( Header header : headers ) + { + logger.trace( "{} -> {}", header.getName(), header.getValue() ); + } + logger.trace( "========= Stop print response headers for response {}: ====================", + response.getStatusLine() ); + } + } + + public void close() + throws IOException + { + delegate.close(); + } + + @Override + @Deprecated + public HttpParams getParams() + { + return delegate.getParams(); + } + + @Override + @Deprecated + public ClientConnectionManager getConnectionManager() + { + return delegate.getConnectionManager(); + } + + private BiConsumer contextInjector( HttpRequest outbound ) + { + return outbound::setHeader; + } +} diff --git a/core-java/src/main/java/org/commonjava/indy/client/core/util/ThreadContext.java b/core-java/src/main/java/org/commonjava/indy/client/core/util/ThreadContext.java new file mode 100644 index 0000000..baea26e --- /dev/null +++ b/core-java/src/main/java/org/commonjava/indy/client/core/util/ThreadContext.java @@ -0,0 +1,277 @@ +/** + * Copyright (C) 2024 Red Hat, Inc. + * + * 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 org.commonjava.indy.client.core.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * (Copied from weft: https://github.com/Commonjava/weft) + * + * This {@link ThreadContext} keeps a count of the number of threads referencing it, and can run finalization logic + * when that number hits 0. + * + * Created by jdcasey on 1/3/17. + */ +public class ThreadContext + implements Map +{ + private static ThreadLocal THREAD_LOCAL = new ThreadLocal<>(); + + private final Map contextMap = new ConcurrentHashMap<>(); + + private Map mdcMap; // mapped diagnostic context + + private int refs = 1; + + private List> finalizers = new ArrayList<>(); + + public static ThreadContext getContext( boolean create ) + { + ThreadContext threadContext = THREAD_LOCAL.get(); + if ( threadContext == null && create ) + { + threadContext = new ThreadContext(); + THREAD_LOCAL.set( threadContext ); + } + + if ( threadContext != null ) + { + threadContext.mdcMap = MDC.getCopyOfContextMap(); + } + + return threadContext; + } + + public static ThreadContext setContext( ThreadContext ctx ) + { + ThreadContext oldCtx = swapContext( ctx ); + if ( ctx != null && ctx.mdcMap != null ) + { + MDC.setContextMap( ctx.mdcMap ); + } + return oldCtx; + } + + private static ThreadContext swapContext( final ThreadContext ctx ) + { + ThreadContext oldCtx = THREAD_LOCAL.get(); + if ( oldCtx != null ) + { + Logger logger = LoggerFactory.getLogger( ThreadContext.class ); + oldCtx.refs--; + logger.trace( "context refs: {}", oldCtx.refs ); + oldCtx.runFinalizersIfDone(); + } + + if ( ctx != null ) + { + THREAD_LOCAL.set( ctx ); + ctx.refs++; + } + else + { + THREAD_LOCAL.remove(); + } + + return oldCtx; + } + + /** + * Provide some finalizer logic to handle the scenario where the number of "live" threads referencing this context + * drops to 0. Before this happens, any contextual information in this ThreadContext may be needed by running threads, + * and it's not safe to clean up. However, since the context may contain {@link java.io.Closeable} instances and + * the like, it's important to have some point where they will be cleaned up. + * @since 1.5 + * @param finalizer + */ + public synchronized void registerFinalizer( Consumer finalizer ) + { + if ( !this.finalizers.contains( finalizer ) ) + { + Logger logger = LoggerFactory.getLogger( getClass() ); + logger.debug( "Registering finalizer: {} on ThreadContext: {}", finalizer, this ); + this.finalizers.add( finalizer ); + } + } + + /** + * If the thread reference count on this context drops to zero, run any finalization logic that might be registered. + */ + private synchronized void runFinalizersIfDone() + { + Logger logger = LoggerFactory.getLogger( getClass() ); + if ( refs < 1 && finalizers != null ) + { + logger.debug( "Running finalizers for ThreadContext: {}", this ); + finalizers.forEach( c -> { + if ( c != null ) + { + logger.debug( "Running finalizer: {} for ThreadContext: {}", c, this ); + + try + { + c.accept( this ); + } + catch ( Throwable t ) + { + logger.error( "Caught error while running finalizer: " + c + " on ThreadContext: " + this, t ); + } + + logger.trace( "Finalizer: {} done for ThreadContext: {}", c, this ); + } + } ); + } + } + + public static void clearContext() + { + swapContext( null ); + MDC.clear(); + } + + private ThreadContext() + { + } + + public int size() + { + return contextMap.size(); + } + + public boolean isEmpty() + { + return contextMap.isEmpty(); + } + + public void putAll( Map map ) + { + contextMap.putAll( map ); + } + + public Collection values() + { + return contextMap.values(); + } + + public Object merge( String key, Object value, BiFunction remappingFunction ) + { + return contextMap.merge( key, value, remappingFunction ); + } + + public Set keySet() + { + return contextMap.keySet(); + } + + public void forEach( BiConsumer action ) + { + contextMap.forEach( action ); + } + + public Object computeIfPresent( String key, BiFunction remappingFunction ) + { + return contextMap.computeIfPresent( key, remappingFunction ); + } + + public void clear() + { + contextMap.clear(); + } + + public boolean containsValue( Object o ) + { + return contextMap.containsValue( o ); + } + + public Object put( String s, Object o ) + { + return contextMap.put( s, o ); + } + + public void replaceAll( BiFunction function ) + { + contextMap.replaceAll( function ); + } + + public Object get( Object o ) + { + return contextMap.get( o ); + } + + public boolean containsKey( Object o ) + { + return contextMap.containsKey( o ); + } + + public Set> entrySet() + { + return contextMap.entrySet(); + } + + public boolean replace( String key, Object oldValue, Object newValue ) + { + return contextMap.replace( key, oldValue, newValue ); + } + + public Object computeIfAbsent( String key, Function mappingFunction ) + { + return contextMap.computeIfAbsent( key, mappingFunction ); + } + + public Object compute( String key, BiFunction remappingFunction ) + { + return contextMap.compute( key, remappingFunction ); + } + + public Object putIfAbsent( String key, Object value ) + { + return contextMap.putIfAbsent( key, value ); + } + + public Object remove( Object o ) + { + return contextMap.remove( o ); + } + + public Object getOrDefault( Object key, Object defaultValue ) + { + return contextMap.getOrDefault( key, defaultValue ); + } + + public boolean remove( Object key, Object value ) + { + return contextMap.remove( key, value ); + } + + public Object replace( String key, Object value ) + { + return contextMap.replace( key, value ); + } + +} diff --git a/pom.xml b/pom.xml index cc4cab4..aa93c86 100644 --- a/pom.xml +++ b/pom.xml @@ -52,19 +52,26 @@ 1.12 1.1.1 1.5 - 1.9.1 4.5.13 1.7.36 2.11.0 1.6.6 1.3.2 2.15.2 - 1.4 + 1.5.1 1.2.12 + 1.19.0 + + io.opentelemetry + opentelemetry-bom + ${otelVersion} + pom + import + org.commonjava.indy.service indy-model-core-java @@ -96,12 +103,6 @@ indy-client-core-java 3.4.0-SNAPSHOT - - org.commonjava.indy - indy-test-fixtures-core - 3.1.1 - test - org.commonjava.atlas atlas-identities @@ -128,26 +129,6 @@ jhttpc ${jhttpcVersion} - - org.commonjava.util - o11yphant-trace-api - ${o11yphantVersion} - - - org.commonjava.util - o11yphant-trace-otel - ${o11yphantVersion} - - - org.commonjava.util - o11yphant-trace-helper-jhttpc - ${o11yphantVersion} - - - org.commonjava.util - o11yphant-metrics-common - ${o11yphantVersion} - org.apache.httpcomponents httpclient @@ -215,12 +196,6 @@ commons-codec 1.15 - - javax.enterprise - cdi-api - 2.0 - provided - ch.qos.logback logback-core @@ -229,10 +204,6 @@ - - javax.enterprise - cdi-api - junit junit