diff --git a/.gitignore b/.gitignore index 661ab51d..f395d417 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,6 @@ out/ ### 로컬 환경변수 ### local.properties /logs -docker-compose.override.yml \ No newline at end of file + +docker-compose.override.yml +opentelemetry-javaagent.jar \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1e947b67..83ce59b6 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,7 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.8' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' + implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:2.16.0") implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation 'com.google.code.gson:gson:2.13.1' diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml new file mode 100644 index 00000000..1337a0b1 --- /dev/null +++ b/monitoring/docker-compose.yml @@ -0,0 +1,106 @@ +version: '3.8' + +services: + app: + image: eclipse-temurin:21-jre-alpine + container_name: my-spring-app2 + volumes: + - ../build/libs/coin-0.0.1-SNAPSHOT.jar:/app/coin-0.0.1-SNAPSHOT.jar + - /etc/localtime:/etc/localtime:ro + - ./opentelemetry-javaagent.jar:/app/opentelemetry-javaagent.jar + working_dir: /app + command: [ "java", "-jar", "coin-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=dev,mariadb-local,actuator,apm" ] + ports: + - "8080:8080" + env_file: + - ../docker/local.properties + environment: + - TZ=Asia/Seoul + - OTEL_SERVICE_NAME=my-spring-app + - OTEL_TRACES_EXPORTER=otlp + - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318 + - OTEL_LOGS_EXPORTER=none + - OTEL_METRICS_EXPORTER=none + - OTEL_INSTRUMENTATION_METHODS_ENABLED=true + - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005, -javaagent:/app/opentelemetry-javaagent.jar + depends_on: + mariadb: + condition: service_healthy + networks: + - app-network + - monitoring-net + + mariadb: + image: mariadb:latest + container_name: mariadb2 + ports: + - "3306:3306" + env_file: + - ../docker/local.properties + environment: + - TZ=Asia/Seoul + volumes: + - mariadb_data:/var/lib/mysql + - ../docker/mariadb/init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: [ "CMD", "healthcheck.sh", "--connect", "--innodb_initialized" ] + interval: 10s + timeout: 5s + retries: 10 + networks: + - app-network + - monitoring-net + + prometheus: + image: prom/prometheus:v2.53.0 + container_name: prometheus + volumes: + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.retention.time=30d' + ports: + - "9090:9090" + networks: + - monitoring-net + restart: unless-stopped + + grafana: + image: grafana/grafana:11.0.0 + container_name: grafana + volumes: + - grafana_data:/var/lib/grafana + - ./grafana/provisioning:/etc/grafana/provisioning + ports: + - "3000:3000" + networks: + - monitoring-net + restart: unless-stopped + depends_on: + - prometheus + - jaeger + + jaeger: + image: jaegertracing/all-in-one:latest + container_name: jaeger + ports: + - "16686:16686" + - "4317:4317" + - "4318:4318" + networks: + - monitoring-net + +volumes: + prometheus_data: {} + grafana_data: {} + mariadb_data: + driver: local + +networks: + app-network: + name: app-network + driver: bridge + monitoring-net: + name: monitoring-net + driver: bridge diff --git a/monitoring/grafana/provisioning/datsources/datasource.yml b/monitoring/grafana/provisioning/datsources/datasource.yml new file mode 100644 index 00000000..834801f3 --- /dev/null +++ b/monitoring/grafana/provisioning/datsources/datasource.yml @@ -0,0 +1,9 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + url: http://prometheus:9090 + access: proxy + isDefault: true + editable: true \ No newline at end of file diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml new file mode 100644 index 00000000..d1690101 --- /dev/null +++ b/monitoring/prometheus/prometheus.yml @@ -0,0 +1,11 @@ +global: + scrape_interval: 10s + +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ['prometheus:9090'] + - job_name: 'my-app' + static_configs: + - targets: [ 'app:8080' ] + metrics_path: /actuator/prometheus \ No newline at end of file diff --git a/src/main/java/com/cleanengine/coin/common/annotation/StartNewTrace.java b/src/main/java/com/cleanengine/coin/common/annotation/StartNewTrace.java new file mode 100644 index 00000000..1d94c1d7 --- /dev/null +++ b/src/main/java/com/cleanengine/coin/common/annotation/StartNewTrace.java @@ -0,0 +1,12 @@ +package com.cleanengine.coin.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface StartNewTrace { + String value() default ""; +} diff --git a/src/main/java/com/cleanengine/coin/common/annotation/StartNewTraceAspect.java b/src/main/java/com/cleanengine/coin/common/annotation/StartNewTraceAspect.java new file mode 100644 index 00000000..5c292600 --- /dev/null +++ b/src/main/java/com/cleanengine/coin/common/annotation/StartNewTraceAspect.java @@ -0,0 +1,43 @@ +package com.cleanengine.coin.common.annotation; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import java.lang.reflect.Method; + +@Aspect +@Component +@RequiredArgsConstructor +public class StartNewTraceAspect { + + @Around("@annotation(com.cleanengine.coin.common.annotation.StartNewTrace)") + public Object createNewTrace(ProceedingJoinPoint joinPoint) throws Throwable { + Tracer tracer = GlobalOpenTelemetry.getTracer("com.cleanengine.coin"); + + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + StartNewTrace newTraceAnnotation = method.getAnnotation(StartNewTrace.class); + + String spanName = newTraceAnnotation.value().isEmpty() ? + signature.getDeclaringType().getSimpleName() + "." + method.getName() : + newTraceAnnotation.value(); + + Span span = tracer.spanBuilder(spanName).setNoParent().startSpan(); + + try (Scope scope = span.makeCurrent()) { + return joinPoint.proceed(); + } catch (Exception e) { + span.recordException(e); + throw e; + } finally { + span.end(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/cleanengine/coin/realitybot/api/ApiScheduler.java b/src/main/java/com/cleanengine/coin/realitybot/api/ApiScheduler.java index e55a2059..9bf89e12 100644 --- a/src/main/java/com/cleanengine/coin/realitybot/api/ApiScheduler.java +++ b/src/main/java/com/cleanengine/coin/realitybot/api/ApiScheduler.java @@ -21,7 +21,6 @@ @Slf4j @Component -@WorkingServerProfile @RequiredArgsConstructor public class ApiScheduler { diff --git a/src/main/java/com/cleanengine/coin/realitybot/config/SchedulerConfig.java b/src/main/java/com/cleanengine/coin/realitybot/config/SchedulerConfig.java index 86aa2a3a..c5cf789f 100644 --- a/src/main/java/com/cleanengine/coin/realitybot/config/SchedulerConfig.java +++ b/src/main/java/com/cleanengine/coin/realitybot/config/SchedulerConfig.java @@ -1,5 +1,6 @@ package com.cleanengine.coin.realitybot.config; +import com.cleanengine.coin.common.annotation.WorkingServerProfile; import com.cleanengine.coin.realitybot.api.ApiScheduler; import com.cleanengine.coin.realitybot.api.UnitPriceRefresher; import org.springframework.beans.factory.annotation.Value; @@ -11,6 +12,7 @@ import java.time.Duration; @Configuration +@WorkingServerProfile @EnableScheduling //@RequiredArgsConstructor public class SchedulerConfig implements SchedulingConfigurer {