Skip to content

Commit 025e364

Browse files
committed
Metrics support for Spring started
Signed-off-by: Hendrik Ebbers <[email protected]>
1 parent 554ebfc commit 025e364

File tree

9 files changed

+150
-17
lines changed

9 files changed

+150
-17
lines changed

hiero-enterprise-base/src/main/java/com/openelements/hiero/base/implementation/ProtocolLayerClientImpl.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@
2323
import com.hedera.hashgraph.sdk.Query;
2424
import com.hedera.hashgraph.sdk.SubscriptionHandle;
2525
import com.hedera.hashgraph.sdk.TokenAssociateTransaction;
26-
import com.hedera.hashgraph.sdk.TokenDissociateTransaction;
2726
import com.hedera.hashgraph.sdk.TokenBurnTransaction;
2827
import com.hedera.hashgraph.sdk.TokenCreateTransaction;
28+
import com.hedera.hashgraph.sdk.TokenDissociateTransaction;
2929
import com.hedera.hashgraph.sdk.TokenMintTransaction;
3030
import com.hedera.hashgraph.sdk.TopicCreateTransaction;
31-
import com.hedera.hashgraph.sdk.TopicUpdateTransaction;
3231
import com.hedera.hashgraph.sdk.TopicDeleteTransaction;
3332
import com.hedera.hashgraph.sdk.TopicMessageQuery;
3433
import com.hedera.hashgraph.sdk.TopicMessageSubmitTransaction;
34+
import com.hedera.hashgraph.sdk.TopicUpdateTransaction;
3535
import com.hedera.hashgraph.sdk.Transaction;
3636
import com.hedera.hashgraph.sdk.TransactionReceipt;
3737
import com.hedera.hashgraph.sdk.TransactionRecord;
@@ -41,6 +41,10 @@
4141
import com.openelements.hiero.base.HieroException;
4242
import com.openelements.hiero.base.data.Account;
4343
import com.openelements.hiero.base.data.ContractParam;
44+
import com.openelements.hiero.base.interceptors.ReceiveRecordInterceptor;
45+
import com.openelements.hiero.base.interceptors.ReceiveRecordInterceptor.ReceiveRecordHandler;
46+
import com.openelements.hiero.base.protocol.ProtocolLayerClient;
47+
import com.openelements.hiero.base.protocol.TransactionListener;
4448
import com.openelements.hiero.base.protocol.data.AccountBalanceRequest;
4549
import com.openelements.hiero.base.protocol.data.AccountBalanceResponse;
4650
import com.openelements.hiero.base.protocol.data.AccountCreateRequest;
@@ -65,34 +69,33 @@
6569
import com.openelements.hiero.base.protocol.data.FileInfoResponse;
6670
import com.openelements.hiero.base.protocol.data.FileUpdateRequest;
6771
import com.openelements.hiero.base.protocol.data.FileUpdateResult;
68-
import com.openelements.hiero.base.protocol.ProtocolLayerClient;
6972
import com.openelements.hiero.base.protocol.data.TokenAssociateRequest;
7073
import com.openelements.hiero.base.protocol.data.TokenAssociateResult;
71-
import com.openelements.hiero.base.protocol.data.TokenDissociateRequest;
72-
import com.openelements.hiero.base.protocol.data.TokenDissociateResult;
7374
import com.openelements.hiero.base.protocol.data.TokenBurnRequest;
7475
import com.openelements.hiero.base.protocol.data.TokenBurnResult;
7576
import com.openelements.hiero.base.protocol.data.TokenCreateRequest;
7677
import com.openelements.hiero.base.protocol.data.TokenCreateResult;
78+
import com.openelements.hiero.base.protocol.data.TokenDissociateRequest;
79+
import com.openelements.hiero.base.protocol.data.TokenDissociateResult;
7780
import com.openelements.hiero.base.protocol.data.TokenMintRequest;
7881
import com.openelements.hiero.base.protocol.data.TokenMintResult;
7982
import com.openelements.hiero.base.protocol.data.TokenTransferRequest;
8083
import com.openelements.hiero.base.protocol.data.TokenTransferResult;
8184
import com.openelements.hiero.base.protocol.data.TopicCreateRequest;
8285
import com.openelements.hiero.base.protocol.data.TopicCreateResult;
83-
import com.openelements.hiero.base.protocol.data.TopicUpdateRequest;
84-
import com.openelements.hiero.base.protocol.data.TopicUpdateResult;
8586
import com.openelements.hiero.base.protocol.data.TopicDeleteRequest;
8687
import com.openelements.hiero.base.protocol.data.TopicDeleteResult;
8788
import com.openelements.hiero.base.protocol.data.TopicMessageRequest;
8889
import com.openelements.hiero.base.protocol.data.TopicMessageResult;
8990
import com.openelements.hiero.base.protocol.data.TopicSubmitMessageRequest;
9091
import com.openelements.hiero.base.protocol.data.TopicSubmitMessageResult;
91-
import com.openelements.hiero.base.protocol.TransactionListener;
92+
import com.openelements.hiero.base.protocol.data.TopicUpdateRequest;
93+
import com.openelements.hiero.base.protocol.data.TopicUpdateResult;
9294
import com.openelements.hiero.base.protocol.data.TransactionType;
9395
import java.util.List;
9496
import java.util.Objects;
9597
import java.util.concurrent.CopyOnWriteArrayList;
98+
import java.util.concurrent.atomic.AtomicReference;
9699
import java.util.function.Consumer;
97100
import org.jspecify.annotations.NonNull;
98101
import org.slf4j.Logger;
@@ -108,11 +111,20 @@ public class ProtocolLayerClientImpl implements ProtocolLayerClient {
108111

109112
private final HieroContext hieroContext;
110113

114+
private final AtomicReference<ReceiveRecordInterceptor> recordInterceptor = new AtomicReference<>(
115+
ReceiveRecordInterceptor.DEFAULT_INTERCEPTOR);
116+
111117
public ProtocolLayerClientImpl(@NonNull final HieroContext hieroContext) {
112118
this.hieroContext = Objects.requireNonNull(hieroContext, "hieroContext must not be null");
113119
listeners = new CopyOnWriteArrayList<>();
114120
}
115121

122+
public void setRecordInterceptor(
123+
@NonNull final ReceiveRecordInterceptor recordInterceptor) {
124+
Objects.requireNonNull(recordInterceptor, "recordInterceptor must not be null");
125+
this.recordInterceptor.set(recordInterceptor);
126+
}
127+
116128
@Override
117129
public AccountBalanceResponse executeAccountBalanceQuery(@NonNull final AccountBalanceRequest request)
118130
throws HieroException {
@@ -618,7 +630,10 @@ private <T extends Transaction<T>> TransactionRecord executeTransactionAndWaitOn
618630
try {
619631
log.debug("Waiting for record of transaction '{}' of type {}", receipt.transactionId,
620632
transaction.getClass().getSimpleName());
621-
return receipt.transactionId.getRecord(hieroContext.getClient());
633+
634+
final ReceiveRecordHandler data = new ReceiveRecordHandler(transaction, receipt,
635+
r -> r.transactionId.getRecord(hieroContext.getClient()));
636+
return recordInterceptor.get().getRecordFor(data);
622637
} catch (final Exception e) {
623638
throw new HieroException("Failed to receive record of transaction '" + receipt.transactionId + "' of type "
624639
+ transaction.getClass(), e);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.openelements.hiero.base.interceptors;
2+
3+
import com.hedera.hashgraph.sdk.Transaction;
4+
import com.hedera.hashgraph.sdk.TransactionReceipt;
5+
import com.hedera.hashgraph.sdk.TransactionRecord;
6+
import java.util.Objects;
7+
import org.jspecify.annotations.NonNull;
8+
9+
@FunctionalInterface
10+
public interface ReceiveRecordInterceptor {
11+
12+
ReceiveRecordInterceptor DEFAULT_INTERCEPTOR = data -> data.handle();
13+
14+
@NonNull
15+
TransactionRecord getRecordFor(@NonNull ReceiveRecordHandler handler) throws Exception;
16+
17+
record ReceiveRecordHandler(@NonNull Transaction transaction, @NonNull TransactionReceipt receipt,
18+
@NonNull ReceiveRecordFunction function) {
19+
20+
public ReceiveRecordHandler {
21+
Objects.requireNonNull(transaction, "transaction must not be null");
22+
Objects.requireNonNull(receipt, "receipt must not be null");
23+
Objects.requireNonNull(function, "handler must not be null");
24+
}
25+
26+
@NonNull
27+
public TransactionRecord handle() throws Exception {
28+
return function.handle(receipt);
29+
}
30+
}
31+
32+
@FunctionalInterface
33+
interface ReceiveRecordFunction {
34+
@NonNull
35+
TransactionRecord handle(@NonNull TransactionReceipt receipt) throws Exception;
36+
}
37+
}

hiero-enterprise-base/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
exports com.openelements.hiero.base.implementation.data to com.openelements.hiero.base.test;
1010
exports com.openelements.hiero.base.config.implementation;
1111
exports com.openelements.hiero.base.protocol.data;
12+
exports com.openelements.hiero.base.interceptors to com.openelements.hiero.base.test;
1213

1314
uses com.openelements.hiero.base.config.NetworkSettingsProvider;
1415
provides com.openelements.hiero.base.config.NetworkSettingsProvider with com.openelements.hiero.base.config.hedera.HederaNetworkSettingsProvider;

hiero-enterprise-spring-sample/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,18 @@
2222
<groupId>org.springframework.boot</groupId>
2323
<artifactId>spring-boot-starter-web</artifactId>
2424
</dependency>
25+
<dependency>
26+
<groupId>org.springframework.boot</groupId>
27+
<artifactId>spring-boot-starter-actuator</artifactId>
28+
</dependency>
2529
<dependency>
2630
<groupId>${project.groupId}</groupId>
2731
<artifactId>hiero-enterprise-spring</artifactId>
2832
</dependency>
33+
<dependency>
34+
<groupId>io.micrometer</groupId>
35+
<artifactId>micrometer-registry-prometheus</artifactId>
36+
</dependency>
2937
<dependency>
3038
<groupId>io.grpc</groupId>
3139
<artifactId>grpc-okhttp</artifactId>

hiero-enterprise-spring-sample/src/main/java/com/openelements/hiero/spring/sample/Application.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package com.openelements.hiero.spring.sample;
22

33
import com.openelements.hiero.spring.EnableHiero;
4+
import io.micrometer.core.instrument.MeterRegistry;
5+
import org.springframework.beans.factory.annotation.Autowired;
46
import org.springframework.boot.SpringApplication;
57
import org.springframework.boot.autoconfigure.SpringBootApplication;
68

79
@SpringBootApplication
810
@EnableHiero
911
public class Application {
1012

13+
@Autowired
14+
MeterRegistry registry;
15+
1116
public static void main(String[] args) {
1217
SpringApplication.run(Application.class, args);
1318
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
spring.config.import=optional:file:.env[.properties]
2-
32
spring.hiero.accountId=${HEDERA_ACCOUNT_ID:0.0.123}
43
spring.hiero.privateKey=${HEDERA_PRIVATE_KEY}
5-
spring.hiero.network.name=${HEDERA_NETWORK:testnet}
6-
4+
spring.hiero.network.name=${HEDERA_NETWORK:hedera-testnet}
75
logging.level.com.openelements=DEBUG
86
logging.pattern.console=%d{HH:mm:ss.SSS} %logger{36} - %msg%n
7+
server.port=${SERVER_PORT:8080}
8+
management.endpoints.web.exposure.include=*

hiero-enterprise-spring/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
<artifactId>spring-boot-configuration-processor</artifactId>
4444
<optional>true</optional>
4545
</dependency>
46+
<dependency>
47+
<groupId>io.micrometer</groupId>
48+
<artifactId>micrometer-core</artifactId>
49+
<optional>true</optional>
50+
</dependency>
4651
<dependency>
4752
<groupId>${project.groupId}</groupId>
4853
<artifactId>hiero-enterprise-test</artifactId>

hiero-enterprise-spring/src/main/java/com/openelements/hiero/spring/implementation/HieroAutoConfiguration.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,41 @@
1414
import com.openelements.hiero.base.implementation.FungibleTokenClientImpl;
1515
import com.openelements.hiero.base.implementation.NetworkRepositoryImpl;
1616
import com.openelements.hiero.base.implementation.NftClientImpl;
17-
import com.openelements.hiero.base.implementation.TopicClientImpl;
1817
import com.openelements.hiero.base.implementation.NftRepositoryImpl;
19-
import com.openelements.hiero.base.implementation.TopicRepositoryImpl;
2018
import com.openelements.hiero.base.implementation.ProtocolLayerClientImpl;
2119
import com.openelements.hiero.base.implementation.SmartContractClientImpl;
2220
import com.openelements.hiero.base.implementation.TokenRepositoryImpl;
21+
import com.openelements.hiero.base.implementation.TopicClientImpl;
22+
import com.openelements.hiero.base.implementation.TopicRepositoryImpl;
2323
import com.openelements.hiero.base.implementation.TransactionRepositoryImpl;
24+
import com.openelements.hiero.base.interceptors.ReceiveRecordInterceptor;
2425
import com.openelements.hiero.base.mirrornode.AccountRepository;
2526
import com.openelements.hiero.base.mirrornode.MirrorNodeClient;
2627
import com.openelements.hiero.base.mirrornode.NetworkRepository;
2728
import com.openelements.hiero.base.mirrornode.NftRepository;
2829
import com.openelements.hiero.base.mirrornode.TokenRepository;
29-
import com.openelements.hiero.base.mirrornode.TransactionRepository;
3030
import com.openelements.hiero.base.mirrornode.TopicRepository;
31+
import com.openelements.hiero.base.mirrornode.TransactionRepository;
3132
import com.openelements.hiero.base.protocol.ProtocolLayerClient;
3233
import com.openelements.hiero.base.verification.ContractVerificationClient;
3334
import java.net.URI;
3435
import java.net.URL;
3536
import java.util.List;
3637
import org.slf4j.Logger;
3738
import org.slf4j.LoggerFactory;
39+
import org.springframework.beans.factory.annotation.Autowired;
3840
import org.springframework.boot.autoconfigure.AutoConfiguration;
41+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3942
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4043
import org.springframework.boot.context.properties.EnableConfigurationProperties;
4144
import org.springframework.context.annotation.Bean;
45+
import org.springframework.context.annotation.Import;
4246
import org.springframework.web.client.RestClient;
4347
import org.springframework.web.context.annotation.ApplicationScope;
4448

4549
@AutoConfiguration
4650
@EnableConfigurationProperties({HieroProperties.class, HieroNetworkProperties.class})
51+
@Import({MicrometerSupportConfig.class})
4752
public class HieroAutoConfiguration {
4853

4954
private static final Logger log = LoggerFactory.getLogger(HieroAutoConfiguration.class);
@@ -61,8 +66,13 @@ HieroContext hieroContext(final HieroConfig hieroConfig) {
6166
}
6267

6368
@Bean
64-
ProtocolLayerClient protocolLevelClient(final HieroContext hieroContext) {
65-
return new ProtocolLayerClientImpl(hieroContext);
69+
ProtocolLayerClient protocolLevelClient(final HieroContext hieroContext,
70+
@Autowired(required = false) final ReceiveRecordInterceptor interceptor) {
71+
ProtocolLayerClientImpl protocolLayerClient = new ProtocolLayerClientImpl(hieroContext);
72+
if (interceptor != null) {
73+
protocolLayerClient.setRecordInterceptor(interceptor);
74+
}
75+
return protocolLayerClient;
6676
}
6777

6878
@Bean
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.openelements.hiero.spring.implementation;
2+
3+
import com.hedera.hashgraph.sdk.ContractExecuteTransaction;
4+
import com.hedera.hashgraph.sdk.TransactionRecord;
5+
import com.openelements.hiero.base.interceptors.ReceiveRecordInterceptor;
6+
import io.micrometer.core.instrument.Counter;
7+
import io.micrometer.core.instrument.MeterRegistry;
8+
import io.micrometer.core.instrument.Tag;
9+
import io.micrometer.core.instrument.Timer;
10+
import java.util.HashSet;
11+
import java.util.Set;
12+
import org.jspecify.annotations.NonNull;
13+
import org.springframework.boot.autoconfigure.AutoConfiguration;
14+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
15+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
16+
import org.springframework.context.annotation.Bean;
17+
18+
@AutoConfiguration
19+
@ConditionalOnProperty(name = "spring.hiero.metrics.enabled", havingValue = "true", matchIfMissing = true)
20+
@ConditionalOnClass(name = "org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration")
21+
public class MicrometerSupportConfig {
22+
23+
public static final String TRANSACTION_TYPE_TAG = "hiero.transaction.record.type";
24+
public static final String CONTRACT_ID_TAG = "hiero.transaction.record.contractId";
25+
public static final String TIMER_NAME = "hiero.transaction.record.time";
26+
public static final String COUNTER_NAME = "hiero.transaction.record";
27+
28+
@Bean
29+
@NonNull
30+
public ReceiveRecordInterceptor interceptRecordReceive(@NonNull final MeterRegistry meterRegistry) {
31+
return handler -> {
32+
final String transactionType = handler.transaction().getClass().getSimpleName();
33+
final Set<Tag> tags = new HashSet<>();
34+
tags.add(Tag.of(TRANSACTION_TYPE_TAG, transactionType));
35+
if (handler.transaction() instanceof ContractExecuteTransaction contractExecuteTransaction) {
36+
tags.add(Tag.of(CONTRACT_ID_TAG,
37+
contractExecuteTransaction.getContractId().toString()));
38+
}
39+
final Timer timer = meterRegistry.timer(TIMER_NAME, tags);
40+
final Counter counter = meterRegistry.counter(COUNTER_NAME, tags);
41+
return timer.record(() -> {
42+
try {
43+
final TransactionRecord transactionRecord = handler.handle();
44+
counter.increment();
45+
return transactionRecord;
46+
} catch (Exception e) {
47+
throw new RuntimeException("Error in handling record interceptor", e);
48+
}
49+
});
50+
};
51+
}
52+
}

0 commit comments

Comments
 (0)