Browse Source

aws cloudwatch功能

zhangzl 2 weeks ago
parent
commit
3a4d879096

+ 7 - 0
pom.xml

@@ -220,6 +220,13 @@
                 <artifactId>s3</artifactId>
                 <version>2.23.3</version>
             </dependency>
+            
+            <!-- AWS SDK for CloudWatch Logs -->
+            <dependency>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>cloudwatchlogs</artifactId>
+                <version>2.23.3</version>
+            </dependency>
 
             <!-- Lombok -->
             <dependency>

+ 14 - 14
ruoyi-admin/src/main/resources/application.yml

@@ -135,20 +135,20 @@ aws:
     # LocalStack端点URL
     endpoint-url: http://172.14.3.142:4566
   
-#  # AWS CloudWatch Logs配置
-#  cloudwatch:
-#    # AWS区域 (与SES、S3保持一致)
-#    region: us-east-1
-#    # AWS访问密钥ID (LocalStack使用任意值)
-#    access-key-id: test
-#    # AWS秘密访问密钥 (LocalStack使用任意值)
-#    secret-access-key: test
-#    # 日志组名称
-#    log-group: my-webApp-logs
-#    # 日志流名称前缀
-#    log-stream-prefix: ruoyi-app
-#    # LocalStack端点URL
-#    endpoint-url: http://172.14.3.142:4566
+ # AWS CloudWatch Logs配置
+  cloudwatch:
+   # AWS区域 (与SES、S3保持一致)
+   region: us-east-1
+   # AWS访问密钥ID (LocalStack使用任意值)
+   access-key-id: test
+   # AWS秘密访问密钥 (LocalStack使用任意值)
+   secret-access-key: test
+   # 日志组名称
+   log-group: my-webApp-logs
+   # 日志流名称前缀
+   log-stream-prefix: ruoyi-app
+   # LocalStack端点URL
+   endpoint-url: http://172.14.3.142:4566
 
 # MyBatis配置
 mybatis:

+ 6 - 0
ruoyi-common/pom.xml

@@ -143,6 +143,12 @@
             <artifactId>s3</artifactId>
         </dependency>
 
+        <!-- AWS SDK for CloudWatch Logs -->
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>cloudwatchlogs</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 122 - 0
ruoyi-common/src/main/java/com/ruoyi/common/aws/cloudwatch/AwsCloudWatchConfig.java

@@ -0,0 +1,122 @@
+package com.ruoyi.common.aws.cloudwatch;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient;
+import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClientBuilder;
+
+import java.net.URI;
+
+/**
+ * AWS CloudWatch Logs設定クラス
+ * 
+ * @author ruoyi
+ */
+@Configuration
+@ConfigurationProperties(prefix = "aws.cloudwatch")
+public class AwsCloudWatchConfig {
+    
+    /**
+     * AWSリージョン
+     */
+    private String region;
+    
+    /**
+     * AWSアクセスキーID
+     */
+    private String accessKeyId;
+    
+    /**
+     * AWSシークレットアクセスキー
+     */
+    private String secretAccessKey;
+    
+    /**
+     * ロググループ名
+     */
+    private String logGroup;
+    
+    /**
+     * ログストリームプレフィックス
+     */
+    private String logStreamPrefix;
+    
+    /**
+     * エンドポイントURL (LocalStackなどのローカル開発環境用)
+     */
+    private String endpointUrl;
+    
+    /**
+     * CloudWatch Logsクライアントを作成
+     * 
+     * @return CloudWatchLogsClientインスタンス
+     */
+    @Bean
+    public CloudWatchLogsClient cloudWatchLogsClient() {
+        AwsBasicCredentials awsCreds = AwsBasicCredentials.create(accessKeyId, secretAccessKey);
+        
+        CloudWatchLogsClientBuilder builder = CloudWatchLogsClient.builder()
+                .region(Region.of(region))
+                .credentialsProvider(StaticCredentialsProvider.create(awsCreds));
+        
+        // エンドポイントURLが設定されている場合はカスタムエンドポイントを使用(LocalStack用)
+        if (endpointUrl != null && !endpointUrl.trim().isEmpty()) {
+            builder.endpointOverride(URI.create(endpointUrl));
+        }
+        
+        return builder.build();
+    }
+    
+    // GetterとSetterメソッド
+    public String getRegion() {
+        return region;
+    }
+    
+    public void setRegion(String region) {
+        this.region = region;
+    }
+    
+    public String getAccessKeyId() {
+        return accessKeyId;
+    }
+    
+    public void setAccessKeyId(String accessKeyId) {
+        this.accessKeyId = accessKeyId;
+    }
+    
+    public String getSecretAccessKey() {
+        return secretAccessKey;
+    }
+    
+    public void setSecretAccessKey(String secretAccessKey) {
+        this.secretAccessKey = secretAccessKey;
+    }
+    
+    public String getLogGroup() {
+        return logGroup;
+    }
+    
+    public void setLogGroup(String logGroup) {
+        this.logGroup = logGroup;
+    }
+    
+    public String getLogStreamPrefix() {
+        return logStreamPrefix;
+    }
+    
+    public void setLogStreamPrefix(String logStreamPrefix) {
+        this.logStreamPrefix = logStreamPrefix;
+    }
+    
+    public String getEndpointUrl() {
+        return endpointUrl;
+    }
+    
+    public void setEndpointUrl(String endpointUrl) {
+        this.endpointUrl = endpointUrl;
+    }
+}

+ 157 - 0
ruoyi-common/src/main/java/com/ruoyi/common/aws/cloudwatch/AwsCloudWatchLogsService.java

@@ -0,0 +1,157 @@
+package com.ruoyi.common.aws.cloudwatch;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient;
+import software.amazon.awssdk.services.cloudwatchlogs.model.*;
+
+import java.time.Instant;
+import java.util.UUID;
+
+/**
+ * AWS CloudWatch Logsサービスクラス
+ * 
+ * @author ruoyi
+ */
+@Slf4j
+@Service
+public class AwsCloudWatchLogsService {
+    
+    @Autowired
+    private AwsCloudWatchConfig awsCloudWatchConfig;
+    
+    @Autowired
+    private CloudWatchLogsClient cloudWatchLogsClient;
+    
+    /**
+     * ログをCloudWatch Logsに送信
+     * 
+     * @param message ログメッセージ
+     * @param level ログレベル(INFO, WARN, ERRORなど)
+     * @return 送信成功かどうか
+     */
+    public boolean sendLog(String message, String level) {
+        try {
+            String logGroup = awsCloudWatchConfig.getLogGroup();
+            String logStreamPrefix = awsCloudWatchConfig.getLogStreamPrefix();
+            
+            // ロググループが存在しない場合は作成
+            createLogGroupIfNotExists(logGroup);
+            
+            // ログストリーム名を生成(プレフィックス + タイムスタンプ + UUID)
+            String logStreamName = logStreamPrefix + "-" + 
+                    Instant.now().toEpochMilli() + "-" + 
+                    UUID.randomUUID().toString().substring(0, 8);
+            
+            // ログストリームを作成
+            createLogStream(logGroup, logStreamName);
+            
+            // ログイベントを送信
+            PutLogEventsRequest request = PutLogEventsRequest.builder()
+                    .logGroupName(logGroup)
+                    .logStreamName(logStreamName)
+                    .logEvents(InputLogEvent.builder()
+                            .message("[" + level + "] " + message)
+                            .timestamp(Instant.now().toEpochMilli())
+                            .build())
+                    .build();
+            
+            PutLogEventsResponse response = cloudWatchLogsClient.putLogEvents(request);
+            
+            log.info("CloudWatch Logsにログを送信しました: グループ={}, ストリーム={}, レベル={}", 
+                    logGroup, logStreamName, level);
+            
+            return true;
+        } catch (Exception e) {
+            log.error("CloudWatch Logsへのログ送信に失敗しました: {}", e.getMessage(), e);
+            return false;
+        }
+    }
+    
+    /**
+     * 情報ログを送信
+     * 
+     * @param message ログメッセージ
+     * @return 送信成功かどうか
+     */
+    public boolean sendInfoLog(String message) {
+        return sendLog(message, "INFO");
+    }
+    
+    /**
+     * 警告ログを送信
+     * 
+     * @param message ログメッセージ
+     * @return 送信成功かどうか
+     */
+    public boolean sendWarnLog(String message) {
+        return sendLog(message, "WARN");
+    }
+    
+    /**
+     * エラーログを送信
+     * 
+     * @param message ログメッセージ
+     * @return 送信成功かどうか
+     */
+    public boolean sendErrorLog(String message) {
+        return sendLog(message, "ERROR");
+    }
+    
+    /**
+     * ロググループが存在しない場合は作成
+     * 
+     * @param logGroupName ロググループ名
+     */
+    private void createLogGroupIfNotExists(String logGroupName) {
+        try {
+            // ロググループの存在確認
+            DescribeLogGroupsRequest describeRequest = DescribeLogGroupsRequest.builder()
+                    .logGroupNamePrefix(logGroupName)
+                    .build();
+            
+            DescribeLogGroupsResponse describeResponse = cloudWatchLogsClient.describeLogGroups(describeRequest);
+            
+            boolean exists = describeResponse.logGroups().stream()
+                    .anyMatch(group -> group.logGroupName().equals(logGroupName));
+            
+            if (!exists) {
+                // ロググループを作成
+                CreateLogGroupRequest createRequest = CreateLogGroupRequest.builder()
+                        .logGroupName(logGroupName)
+                        .build();
+                
+                cloudWatchLogsClient.createLogGroup(createRequest);
+                log.info("CloudWatch Logsグループを作成しました: {}", logGroupName);
+            }
+        } catch (ResourceAlreadyExistsException e) {
+            // 既に存在する場合は無視
+            log.debug("ロググループは既に存在します: {}", logGroupName);
+        } catch (Exception e) {
+            log.warn("ロググループの作成/確認に失敗しました: {}", e.getMessage());
+        }
+    }
+    
+    /**
+     * ログストリームを作成
+     * 
+     * @param logGroupName ロググループ名
+     * @param logStreamName ログストリーム名
+     */
+    private void createLogStream(String logGroupName, String logStreamName) {
+        try {
+            CreateLogStreamRequest request = CreateLogStreamRequest.builder()
+                    .logGroupName(logGroupName)
+                    .logStreamName(logStreamName)
+                    .build();
+            
+            cloudWatchLogsClient.createLogStream(request);
+        } catch (ResourceAlreadyExistsException e) {
+            // 既に存在する場合は無視
+            log.debug("ログストリームは既に存在します: {}/{}", logGroupName, logStreamName);
+        } catch (Exception e) {
+            log.warn("ログストリームの作成に失敗しました: {}", e.getMessage());
+        }
+    }
+}

+ 135 - 0
ruoyi-common/src/main/java/com/ruoyi/common/aws/cloudwatch/README.md

@@ -0,0 +1,135 @@
+# AWS CloudWatch Logs 使用指南
+
+## 概述
+
+本项目已集成AWS CloudWatch Logs功能,用于将应用日志集中存储到AWS CloudWatch服务中。支持LocalStack本地开发环境。
+
+## 配置说明
+
+在 `application.yml` 中配置CloudWatch Logs参数:
+
+```yaml
+aws:
+  cloudwatch:
+    region: us-east-1                    # AWS区域
+    access-key-id: test                  # 访问密钥ID
+    secret-access-key: test              # 秘密访问密钥
+    log-group: my-webApp-logs            # 日志组名称
+    log-stream-prefix: ruoyi-app         # 日志流前缀
+    endpoint-url: http://172.14.3.142:4566  # LocalStack端点URL(可选)
+```
+
+## 使用方法
+
+### 1. 基本使用
+
+在Spring Bean中注入 `AwsCloudWatchLogsService` 服务:
+
+```java
+@Autowired
+private AwsCloudWatchLogsService cloudWatchLogsService;
+```
+
+### 2. 发送不同级别的日志
+
+```java
+// 发送信息级别日志
+cloudWatchLogsService.sendInfoLog("用户登录成功 - 用户名: admin");
+
+// 发送警告级别日志
+cloudWatchLogsService.sendWarnLog("订单处理延迟 - 订单号: ORD20241128001");
+
+// 发送错误级别日志
+cloudWatchLogsService.sendErrorLog("数据库连接失败 - 服务: user-service");
+
+// 自定义日志级别
+cloudWatchLogsService.sendLog("自定义消息", "DEBUG");
+```
+
+### 3. 在现有服务中集成(以S3文件服务为例)
+
+```java
+@Service
+public class AwsS3FileService {
+    
+    @Autowired
+    private AwsCloudWatchLogsService cloudWatchLogsService;
+    
+    public void someMethod() {
+        try {
+            // 业务逻辑...
+            
+            // 发送成功日志到CloudWatch
+            cloudWatchLogsService.sendInfoLog("S3文件操作成功 - 文件名: " + fileName);
+            
+        } catch (Exception e) {
+            // 发送错误日志到CloudWatch
+            cloudWatchLogsService.sendErrorLog("S3文件操作失败 - 错误: " + e.getMessage());
+        }
+    }
+}
+```
+
+### 4. 测试示例
+
+使用 `CloudWatchLogsTest` 类进行测试:
+
+```java
+@Autowired
+private CloudWatchLogsTest cloudWatchLogsTest;
+
+// 测试基本功能
+cloudWatchLogsTest.testBasicLogging();
+
+// 测试业务日志
+cloudWatchLogsTest.testBusinessLogging();
+
+// 测试批量日志
+cloudWatchLogsTest.testBatchLogging();
+
+// 测试发送结果
+cloudWatchLogsTest.testLoggingWithResult();
+```
+
+## 功能特性
+
+1. **自动创建日志组和日志流**:如果指定的日志组不存在,系统会自动创建
+2. **支持LocalStack**:可通过配置endpoint-url使用本地开发环境
+3. **异常处理**:日志发送失败不会影响主业务流程
+4. **多种日志级别**:支持INFO、WARN、ERROR等标准日志级别
+5. **批量日志支持**:可连续发送多条日志
+
+## 日志格式
+
+发送到CloudWatch的日志格式为:
+```
+[LEVEL] 日志消息内容
+```
+
+例如:
+```
+[INFO] 用户登录成功 - 用户名: admin, IP: 192.168.1.100
+[ERROR] 数据库连接失败 - 服务: user-service, 错误: 连接超时
+```
+
+## 注意事项
+
+1. **网络连接**:确保能够访问AWS CloudWatch服务或LocalStack端点
+2. **权限配置**:确保AWS凭证具有CloudWatch Logs的写入权限
+3. **日志大小**:避免发送过大的日志内容(建议小于256KB)
+4. **错误处理**:日志发送失败时会记录到本地日志,不会中断业务逻辑
+5. **性能考虑**:在高频场景下,建议异步发送日志以避免性能影响
+
+## 故障排除
+
+1. **依赖问题**:确保 `cloudwatchlogs` 依赖已正确添加到pom.xml
+2. **配置错误**:检查application.yml中的CloudWatch配置是否正确
+3. **网络问题**:确认能够访问配置的endpoint-url
+4. **权限问题**:验证AWS凭证是否具有CloudWatch Logs的访问权限
+
+## 相关文件
+
+- `AwsCloudWatchConfig.java` - CloudWatch配置类
+- `AwsCloudWatchLogsService.java` - CloudWatch日志服务类
+- `CloudWatchLogsTest.java` - 使用示例测试类
+- `application.yml` - 配置文件

+ 17 - 0
ruoyi-common/src/main/java/com/ruoyi/common/aws/s3/AwsS3FileService.java

@@ -1,6 +1,7 @@
 package com.ruoyi.common.aws.s3;
 
 import com.ruoyi.common.aws.AwsUtils;
+import com.ruoyi.common.aws.cloudwatch.AwsCloudWatchLogsService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +34,9 @@ public class AwsS3FileService {
     @Autowired
     private AwsS3Config awsS3Config;
     
+    @Autowired
+    private AwsCloudWatchLogsService cloudWatchLogsService;
+    
     /**
      * ファイルをS3にアップロード
      * 
@@ -241,6 +245,19 @@ public class AwsS3FileService {
             log.info(content);
             log.info("=== S3ファイル内容終了 ===");
             
+            // CloudWatch Logsにログを送信
+            try {
+                cloudWatchLogsService.sendInfoLog("S3ファイル読み取り成功 - ファイル名: " + fileName + 
+                        ", サイズ: " + bytesRead + " bytes, S3キー: " + s3Key);
+                
+                // ファイル内容が大きすぎない場合はCloudWatchにも送信
+                if (bytesRead < 1000) { // 1KB未満の場合のみ
+                    cloudWatchLogsService.sendInfoLog("S3ファイル内容: " + content);
+                }
+            } catch (Exception e) {
+                log.warn("CloudWatch Logsへのログ送信に失敗しました: {}", e.getMessage());
+            }
+            
             return S3FileReadResult.success(fileName, s3Key, bytesRead, "ファイル内容がバックエンドログに記録されました");
             
         } catch (Exception e) {