Skip to content

Commit 6b261c1

Browse files
author
Yuriy Bezsonov
committed
feat(apps): add observability instrumentation with @observed metrics
1 parent 8757f1f commit 6b261c1

11 files changed

Lines changed: 167 additions & 123 deletions

File tree

apps/unicorn-store-spring-java25/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@
8686
<groupId>io.micrometer</groupId>
8787
<artifactId>micrometer-registry-prometheus</artifactId>
8888
</dependency>
89+
<dependency>
90+
<groupId>org.aspectj</groupId>
91+
<artifactId>aspectjweaver</artifactId>
92+
</dependency>
8993

9094
<!-- Serialization -->
9195
<dependency>

apps/unicorn-store-spring-java25/src/main/java/com/unicorn/store/config/MonitoringConfig.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import io.micrometer.core.instrument.MeterRegistry;
66
import io.micrometer.core.instrument.config.MeterFilter;
7+
import io.micrometer.observation.ObservationPredicate;
8+
import io.micrometer.observation.ObservationRegistry;
9+
import io.micrometer.observation.aop.ObservedAspect;
710
import jakarta.annotation.PostConstruct;
811
import org.springframework.beans.factory.annotation.Autowired;
12+
import org.springframework.context.annotation.Bean;
913
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.http.server.observation.ServerRequestObservationContext;
1015

1116
import java.io.File;
1217
import java.io.IOException;
@@ -28,6 +33,19 @@ public class MonitoringConfig {
2833
@Autowired
2934
private MeterRegistry meterRegistry;
3035

36+
// Enables @Observed annotation processing for Prometheus metrics (unicorn.create, unicorn.publish, etc.)
37+
@Bean
38+
ObservedAspect observedAspect(ObservationRegistry registry) {
39+
return new ObservedAspect(registry);
40+
}
41+
42+
// Filters actuator endpoints from observations to reduce metric/trace noise
43+
@Bean
44+
ObservationPredicate noActuatorTraces() {
45+
return (name, context) -> !(context instanceof ServerRequestObservationContext ctx &&
46+
ctx.getCarrier().getRequestURI().startsWith("/actuator"));
47+
}
48+
3149
@PostConstruct
3250
public void configureMeterRegistry() {
3351
String clusterType = System.getenv("ECS_CONTAINER_METADATA_URI_V4") != null ? "ecs" : "eks";

apps/unicorn-store-spring-java25/src/main/java/com/unicorn/store/data/UnicornPublisher.crac

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
55
import com.unicorn.store.model.Unicorn;
66
import com.unicorn.store.model.UnicornEventType;
77

8+
import io.micrometer.observation.annotation.Observed;
89
import jakarta.annotation.PostConstruct;
910
import org.crac.Context;
1011
import org.crac.Resource;
@@ -23,7 +24,7 @@ import software.amazon.awssdk.services.eventbridge.model.PutEventsResponse;
2324
import java.util.concurrent.CompletableFuture;
2425

2526
@Service
26-
public final class UnicornPublisher implements Resource {
27+
public class UnicornPublisher implements Resource {
2728

2829
private final ObjectMapper objectMapper;
2930

@@ -41,6 +42,7 @@ public final class UnicornPublisher implements Resource {
4142
Core.getGlobalContext().register(this);
4243
}
4344

45+
@Observed(name = "unicorn.publish")
4446
public CompletableFuture<PutEventsResponse> publish(Unicorn unicorn, UnicornEventType unicornEventType) {
4547
try {
4648
var unicornJson = objectMapper.writeValueAsString(unicorn);

apps/unicorn-store-spring-java25/src/main/java/com/unicorn/store/data/UnicornPublisher.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.unicorn.store.model.Unicorn;
66
import com.unicorn.store.model.UnicornEventType;
77

8+
import io.micrometer.observation.annotation.Observed;
89
import jakarta.annotation.PostConstruct;
910

1011
import org.slf4j.Logger;
@@ -20,7 +21,7 @@
2021
import java.util.concurrent.CompletableFuture;
2122

2223
@Service
23-
public final class UnicornPublisher {
24+
public class UnicornPublisher {
2425

2526
private final ObjectMapper objectMapper;
2627

@@ -37,6 +38,7 @@ public void init() {
3738
createClient();
3839
}
3940

41+
@Observed(name = "unicorn.publish")
4042
public CompletableFuture<PutEventsResponse> publish(Unicorn unicorn, UnicornEventType unicornEventType) {
4143
try {
4244
var unicornJson = objectMapper.writeValueAsString(unicorn);

apps/unicorn-store-spring-java25/src/main/java/com/unicorn/store/service/UnicornService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.unicorn.store.exceptions.ResourceNotFoundException;
77
import com.unicorn.store.model.Unicorn;
88
import com.unicorn.store.model.UnicornEventType;
9+
import io.micrometer.observation.annotation.Observed;
910
import org.slf4j.Logger;
1011
import org.slf4j.LoggerFactory;
1112
import org.springframework.stereotype.Service;
@@ -25,6 +26,7 @@ public UnicornService(UnicornRepository unicornRepository, UnicornPublisher unic
2526
this.unicornPublisher = unicornPublisher;
2627
}
2728

29+
@Observed(name = "unicorn.create")
2830
@Transactional
2931
public Unicorn createUnicorn(Unicorn unicorn) {
3032
// Access request ID from Scoped Value (JEP 506) - no parameter passing needed
@@ -67,6 +69,7 @@ public List<Unicorn> createUnicorns(List<Unicorn> unicorns) {
6769
.toList();
6870
}
6971

72+
@Observed(name = "unicorn.update")
7073
@Transactional
7174
public Unicorn updateUnicorn(Unicorn unicorn, String unicornId) {
7275
String requestId = RequestContext.REQUEST_ID.orElse("no-request-id");
@@ -84,6 +87,7 @@ public Unicorn updateUnicorn(Unicorn unicorn, String unicornId) {
8487
return savedUnicorn;
8588
}
8689

90+
@Observed(name = "unicorn.get")
8791
public Unicorn getUnicorn(String unicornId) {
8892
String requestId = RequestContext.REQUEST_ID.orElse("no-request-id");
8993
logger.debug("[{}] Retrieving unicorn with ID: {}", requestId, unicornId);
@@ -92,6 +96,7 @@ public Unicorn getUnicorn(String unicornId) {
9296
"Unicorn not found with ID: " + unicornId));
9397
}
9498

99+
@Observed(name = "unicorn.delete")
95100
@Transactional
96101
public void deleteUnicorn(String unicornId) {
97102
String requestId = RequestContext.REQUEST_ID.orElse("no-request-id");

infra/cdk/src/main/java/sample/com/constructs/Unicorn.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,9 @@ private void createEcsRoles(UnicornProps props) {
236236
.description("ECS task role for application runtime permissions")
237237
.build();
238238

239-
// X-Ray tracing
240-
ecsTaskRole.addToPolicy(PolicyStatement.Builder.create()
241-
.effect(Effect.ALLOW)
242-
.actions(List.of("xray:PutTraceSegments"))
243-
.resources(List.of("*"))
244-
.build());
239+
// CloudWatch Agent and X-Ray for observability
240+
ecsTaskRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy"));
241+
ecsTaskRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("AWSXrayWriteOnlyAccess"));
245242

246243
// EventBridge access
247244
eventBus.grantPutEventsTo(ecsTaskRole);

infra/cfn/base-stack.yaml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,16 @@ Resources:
676676
Fn::GetAtt:
677677
- IdeInstanceLauncherFunction803C5A2A
678678
- Arn
679+
SecurityGroupIds:
680+
Fn::Join:
681+
- ""
682+
- - Fn::GetAtt:
683+
- IdeSecurityGroup73B02454
684+
- GroupId
685+
- ","
686+
- Fn::GetAtt:
687+
- IdeInternalSecurityGroupB0A5D76B
688+
- GroupId
679689
ImageId:
680690
Ref: SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61x8664C96584B6F00A464EAD1953AFF4B05118Parameter
681691
UserData:
@@ -827,16 +837,6 @@ Resources:
827837
- - Ref: VpcPublicSubnet1Subnet8E8DEDC0
828838
- ","
829839
- Ref: VpcPublicSubnet2SubnetA811849C
830-
SecurityGroupIds:
831-
Fn::Join:
832-
- ""
833-
- - Fn::GetAtt:
834-
- IdeSecurityGroup73B02454
835-
- GroupId
836-
- ","
837-
- Fn::GetAtt:
838-
- IdeInternalSecurityGroupB0A5D76B
839-
- GroupId
840840
UpdateReplacePolicy: Delete
841841
DeletionPolicy: Delete
842842
IdeEipAssociationDFF81215:

infra/cfn/java-ai-agents-stack.yaml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -784,19 +784,8 @@ Resources:
784784
- Fn::GetAtt:
785785
- IdeInternalSecurityGroupB0A5D76B
786786
- GroupId
787-
SubnetIds:
788-
Fn::Join:
789-
- ""
790-
- - Ref: VpcPublicSubnet1Subnet8E8DEDC0
791-
- ","
792-
- Ref: VpcPublicSubnet2SubnetA811849C
793-
VolumeSize: "50"
794-
IamInstanceProfileArn:
795-
Fn::GetAtt:
796-
- IdeInstanceProfile61B92038
797-
- Arn
798-
InstanceName: ide
799-
InstanceTypes: m6a.xlarge,m7a.xlarge
787+
ImageId:
788+
Ref: SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61x8664C96584B6F00A464EAD1953AFF4B05118Parameter
800789
UserData:
801790
Fn::Base64:
802791
Fn::Join:
@@ -933,8 +922,19 @@ Resources:
933922
"
934923
exit 1
935924
fi
936-
ImageId:
937-
Ref: SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61x8664C96584B6F00A464EAD1953AFF4B05118Parameter
925+
InstanceTypes: m6a.xlarge,m7a.xlarge
926+
InstanceName: ide
927+
IamInstanceProfileArn:
928+
Fn::GetAtt:
929+
- IdeInstanceProfile61B92038
930+
- Arn
931+
VolumeSize: "50"
932+
SubnetIds:
933+
Fn::Join:
934+
- ""
935+
- - Ref: VpcPublicSubnet1Subnet8E8DEDC0
936+
- ","
937+
- Ref: VpcPublicSubnet2SubnetA811849C
938938
UpdateReplacePolicy: Delete
939939
DeletionPolicy: Delete
940940
IdeEipAssociationDFF81215:

infra/cfn/java-on-amazon-eks-stack.yaml

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -794,13 +794,31 @@ Resources:
794794
Fn::GetAtt:
795795
- IdeInstanceLauncherFunction803C5A2A
796796
- Arn
797-
VolumeSize: "50"
797+
InstanceTypes: m6a.xlarge,m7a.xlarge
798+
InstanceName: ide
798799
IamInstanceProfileArn:
799800
Fn::GetAtt:
800801
- IdeInstanceProfile61B92038
801802
- Arn
802-
InstanceName: ide
803-
InstanceTypes: m6a.xlarge,m7a.xlarge
803+
VolumeSize: "50"
804+
SubnetIds:
805+
Fn::Join:
806+
- ""
807+
- - Ref: VpcPublicSubnet1Subnet8E8DEDC0
808+
- ","
809+
- Ref: VpcPublicSubnet2SubnetA811849C
810+
SecurityGroupIds:
811+
Fn::Join:
812+
- ""
813+
- - Fn::GetAtt:
814+
- IdeSecurityGroup73B02454
815+
- GroupId
816+
- ","
817+
- Fn::GetAtt:
818+
- IdeInternalSecurityGroupB0A5D76B
819+
- GroupId
820+
ImageId:
821+
Ref: SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61x8664C96584B6F00A464EAD1953AFF4B05118Parameter
804822
UserData:
805823
Fn::Base64:
806824
Fn::Join:
@@ -937,24 +955,6 @@ Resources:
937955
"
938956
exit 1
939957
fi
940-
ImageId:
941-
Ref: SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61x8664C96584B6F00A464EAD1953AFF4B05118Parameter
942-
SecurityGroupIds:
943-
Fn::Join:
944-
- ""
945-
- - Fn::GetAtt:
946-
- IdeSecurityGroup73B02454
947-
- GroupId
948-
- ","
949-
- Fn::GetAtt:
950-
- IdeInternalSecurityGroupB0A5D76B
951-
- GroupId
952-
SubnetIds:
953-
Fn::Join:
954-
- ""
955-
- - Ref: VpcPublicSubnet1Subnet8E8DEDC0
956-
- ","
957-
- Ref: VpcPublicSubnet2SubnetA811849C
958958
UpdateReplacePolicy: Delete
959959
DeletionPolicy: Delete
960960
IdeEipAssociationDFF81215:
@@ -1333,12 +1333,12 @@ Resources:
13331333
Environment:
13341334
ComputeType: BUILD_GENERAL1_MEDIUM
13351335
EnvironmentVariables:
1336-
- Name: TEMPLATE_TYPE
1337-
Type: PLAINTEXT
1338-
Value: java-on-amazon-eks
13391336
- Name: GIT_BRANCH
13401337
Type: PLAINTEXT
13411338
Value: new-ws-infra
1339+
- Name: TEMPLATE_TYPE
1340+
Type: PLAINTEXT
1341+
Value: java-on-amazon-eks
13421342
Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
13431343
ImagePullCredentialsType: CODEBUILD
13441344
PrivilegedMode: false
@@ -1580,13 +1580,13 @@ Resources:
15801580
Fn::GetAtt:
15811581
- CodeBuildStartLambdaFunction8349284F
15821582
- Arn
1583-
ContentHash: "1767378094194"
1583+
ContentHash: "1767519160697"
1584+
ProjectName:
1585+
Ref: CodeBuildProjectA0FF5539
15841586
CodeBuildIamRoleArn:
15851587
Fn::GetAtt:
15861588
- CodeBuildRoleE9A44575
15871589
- Arn
1588-
ProjectName:
1589-
Ref: CodeBuildProjectA0FF5539
15901590
DependsOn:
15911591
- CodeBuildCompleteRuleAllowEventRuleWorkshopStackCodeBuildReportLambdaFunctionD77C60919E0B0C89
15921592
- CodeBuildCompleteRuleEE9277E8
@@ -1944,7 +1944,7 @@ Resources:
19441944
- Ref: AWS::AccountId
19451945
- "-"
19461946
- Ref: AWS::Region
1947-
- "-20260102192134"
1947+
- "-20260104103241"
19481948
PublicAccessBlockConfiguration:
19491949
BlockPublicAcls: true
19501950
BlockPublicPolicy: true
@@ -2211,12 +2211,12 @@ Resources:
22112211
}
22122212
Environment:
22132213
Variables:
2214-
S3_THREAD_DUMPS_PREFIX: thread-dumps/
22152214
S3_BUCKET_NAME:
22162215
Ref: WorkshopBucketFD5BC43F
2216+
S3_THREAD_DUMPS_PREFIX: thread-dumps/
2217+
SECRET_NAME: workshop-ide-password
22172218
EKS_CLUSTER_NAME:
22182219
Ref: EksClusterB2BDED5B
2219-
SECRET_NAME: workshop-ide-password
22202220
FunctionName: workshop-thread-dump-lambda
22212221
Handler: index.lambda_handler
22222222
MemorySize: 512
@@ -2521,16 +2521,24 @@ Resources:
25212521
Service: ecs-tasks.amazonaws.com
25222522
Version: "2012-10-17"
25232523
Description: ECS task role for application runtime permissions
2524+
ManagedPolicyArns:
2525+
- Fn::Join:
2526+
- ""
2527+
- - "arn:"
2528+
- Ref: AWS::Partition
2529+
- :iam::aws:policy/CloudWatchAgentServerPolicy
2530+
- Fn::Join:
2531+
- ""
2532+
- - "arn:"
2533+
- Ref: AWS::Partition
2534+
- :iam::aws:policy/AWSXrayWriteOnlyAccess
25242535
Path: /service-role/
25252536
RoleName: unicornstore-ecs-task-role
25262537
UnicornUnicornStoreEcsTaskRoleDefaultPolicy477138EA:
25272538
Type: AWS::IAM::Policy
25282539
Properties:
25292540
PolicyDocument:
25302541
Statement:
2531-
- Action: xray:PutTraceSegments
2532-
Effect: Allow
2533-
Resource: "*"
25342542
- Action: events:PutEvents
25352543
Effect: Allow
25362544
Resource:

0 commit comments

Comments
 (0)