From 184e95b59073b192c9e78838ed5241afc046e682 Mon Sep 17 00:00:00 2001 From: kusumachalasani Date: Fri, 12 Apr 2024 09:36:25 +0530 Subject: [PATCH 1/6] Add cloudwatch LogAppender Signed-off-by: kusumachalasani --- pom.xml | 6 + src/main/java/com/autotune/Autotune.java | 3 + .../operator/InitializeDeployment.java | 1 + .../operator/KruizeDeploymentInfo.java | 8 ++ .../autotune/utils/CloudWatchLogSender.java | 128 ++++++++++++++++++ .../com/autotune/utils/CustomAppender.java | 113 ++++++++++++++++ .../com/autotune/utils/KruizeConstants.java | 22 +++ 7 files changed, 281 insertions(+) create mode 100644 src/main/java/com/autotune/utils/CloudWatchLogSender.java create mode 100644 src/main/java/com/autotune/utils/CustomAppender.java diff --git a/pom.xml b/pom.xml index d5ac4e9ba..654101da1 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,12 @@ compile + + software.amazon.awssdk + cloudwatchlogs + 2.17.102 + + com.udojava EvalEx diff --git a/src/main/java/com/autotune/Autotune.java b/src/main/java/com/autotune/Autotune.java index 792020114..2dcd50cec 100644 --- a/src/main/java/com/autotune/Autotune.java +++ b/src/main/java/com/autotune/Autotune.java @@ -28,6 +28,7 @@ import com.autotune.operator.KruizeDeploymentInfo; import com.autotune.service.HealthService; import com.autotune.service.InitiateListener; +import com.autotune.utils.CloudWatchLogSender; import com.autotune.utils.KruizeConstants; import com.autotune.utils.MetricsConfig; import com.autotune.utils.ServerContext; @@ -100,6 +101,8 @@ public static void main(String[] args) { try { InitializeDeployment.setup_deployment_info(); + // Configure AWS CloudWatch + CloudWatchLogSender.configureCloudWatchLog(); // Read and execute the DDLs here executeDDLs(); // close the existing session factory before recreating diff --git a/src/main/java/com/autotune/operator/InitializeDeployment.java b/src/main/java/com/autotune/operator/InitializeDeployment.java index 51423ae23..5f4ad3428 100644 --- a/src/main/java/com/autotune/operator/InitializeDeployment.java +++ b/src/main/java/com/autotune/operator/InitializeDeployment.java @@ -43,6 +43,7 @@ private InitializeDeployment() { public static void setup_deployment_info() throws Exception, K8sTypeNotSupportedException, MonitoringAgentNotSupportedException, MonitoringAgentNotFoundException { setConfigValues(KruizeConstants.CONFIG_FILE, KruizeConstants.KRUIZE_CONFIG_ENV_NAME.class); setConfigValues(KruizeConstants.DBConstants.CONFIG_FILE, KruizeConstants.DATABASE_ENV_NAME.class); + setConfigValues(KruizeConstants.CloudWatchLogsConstants.CONFIG_FILE, KruizeConstants.CLOUDWATCH_LOGS_ENV_NAME.class); KruizeDeploymentInfo.setCluster_type(KruizeDeploymentInfo.cluster_type); KruizeDeploymentInfo.setKubernetesType(KruizeDeploymentInfo.k8s_type); KruizeDeploymentInfo.setAuth_type(KruizeDeploymentInfo.auth_type); diff --git a/src/main/java/com/autotune/operator/KruizeDeploymentInfo.java b/src/main/java/com/autotune/operator/KruizeDeploymentInfo.java index 132473319..8d8857d2b 100644 --- a/src/main/java/com/autotune/operator/KruizeDeploymentInfo.java +++ b/src/main/java/com/autotune/operator/KruizeDeploymentInfo.java @@ -65,6 +65,14 @@ public class KruizeDeploymentInfo { public static String database_admin_username; public static String database_admin_password; public static String database_ssl_mode; + + public static String cloudwatch_logs_access_key_id; + public static String cloudwatch_logs_secret_access_key; + public static String cloudwatch_logs_log_group; + public static String cloudwatch_logs_region; + public static String cloudwatch_logs_log_level; + public static String cloudwatch_logs_log_stream; + public static Boolean settings_save_to_db; public static String em_only_mode; public static Integer bulk_update_results_limit = 100; diff --git a/src/main/java/com/autotune/utils/CloudWatchLogSender.java b/src/main/java/com/autotune/utils/CloudWatchLogSender.java new file mode 100644 index 000000000..2d1dace5d --- /dev/null +++ b/src/main/java/com/autotune/utils/CloudWatchLogSender.java @@ -0,0 +1,128 @@ +package com.autotune.utils; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.builder.api.*; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +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.model.*; + +import java.util.List; + +import static com.autotune.operator.KruizeDeploymentInfo.*; + +public class CloudWatchLogSender { + private static final Logger LOGGER = LogManager.getLogger(CloudWatchLogSender.class); + + private CloudWatchLogSender() { + } + public static void configureCloudWatchLog() { + + // Check if access details are provided + if (cloudwatch_logs_access_key_id != null && !cloudwatch_logs_access_key_id.isEmpty() && cloudwatch_logs_secret_access_key != null && !cloudwatch_logs_secret_access_key.isEmpty() && cloudwatch_logs_region != null && !cloudwatch_logs_region.isEmpty()) { + try { + // Define default values for attributes if they are empty or null + String cw_logs_log_group = cloudwatch_logs_log_group == null || cloudwatch_logs_log_group.isEmpty() ? "kruize-logs" : cloudwatch_logs_log_group; + String cw_logs_log_stream = cloudwatch_logs_log_stream == null || cloudwatch_logs_log_stream.isEmpty() ? "kruize-stream" : cloudwatch_logs_log_stream; + String cw_logs_log_level = cloudwatch_logs_log_level == null || cloudwatch_logs_log_level.isEmpty() ? "INFO" : cloudwatch_logs_log_level; + + // Create a CloudWatchLogsClient with provided credentials and region + CloudWatchLogsClient logsClient = CloudWatchLogsClient.builder() + .region(Region.of(cloudwatch_logs_region)) + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(cloudwatch_logs_access_key_id, cloudwatch_logs_secret_access_key))) + .build(); + + // Check if log group exists, if not, create it + if (!logGroupExists(logsClient, cw_logs_log_group)) { + createLogGroup(logsClient, cw_logs_log_group); + } + + // Check if log stream exists, if not, create it + if (!logStreamExists(logsClient, cw_logs_log_group, cw_logs_log_stream)) { + createLogStream(logsClient, cw_logs_log_group, cw_logs_log_stream); + } + + // Create a configuration builder + ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + + // Define the console appender + AppenderComponentBuilder consoleAppenderBuilder = builder.newAppender("consoleLogger", "Console") + .addAttribute("target", "SYSTEM_OUT") + .add(builder.newLayout("PatternLayout") + .addAttribute("pattern", "%d{yyyy-MM-ddHH:mm:ss.SSS} %level [%t][%F(%L)]-%msg%n")); + builder.add(consoleAppenderBuilder); + + // Add a custom appender + AppenderComponentBuilder cloudWatchAppenderBuilder = builder.newAppender("CloudWatchAppender", "CustomAppender") + .addAttribute("logGroup", cw_logs_log_group) + .addAttribute("logStream", cw_logs_log_stream) + .addAttribute("region", cloudwatch_logs_region) + .addAttribute("awsAccessKeyId", cloudwatch_logs_access_key_id) + .addAttribute("awsSecretKey", cloudwatch_logs_secret_access_key) + .addAttribute("logLevel", cw_logs_log_level) + .add(builder.newLayout("PatternLayout") + .addAttribute("pattern", "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"));; + builder.add(cloudWatchAppenderBuilder); + // Configure the root logger to use both console appender and the new CloudWatch appender + RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.INFO) + .add(builder.newAppenderRef("consoleLogger")) + .add(builder.newAppenderRef("CloudWatchAppender")); + builder.add(rootLogger); + Configuration config = builder.build(); + // Update the current context with the new configuration + ctx.updateLoggers(config); + for (Appender appender : config.getAppenders().values()) { + appender.start(); + } + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } else { + LOGGER.info("AWS access details are not provided. Skipping sending logs to CloudWatch."); + } + } + + private static boolean logGroupExists(CloudWatchLogsClient logsClient, String logGroupName) { + DescribeLogGroupsRequest request = DescribeLogGroupsRequest.builder() + .logGroupNamePrefix(logGroupName) + .build(); + DescribeLogGroupsResponse response = logsClient.describeLogGroups(request); + List logGroups = response.logGroups(); + return logGroups.stream().anyMatch(group -> group.logGroupName().equals(logGroupName)); + } + + private static boolean logStreamExists(CloudWatchLogsClient logsClient, String logGroupName, String logStreamName) { + DescribeLogStreamsRequest request = DescribeLogStreamsRequest.builder() + .logGroupName(logGroupName) + .logStreamNamePrefix(logStreamName) + .build(); + DescribeLogStreamsResponse response = logsClient.describeLogStreams(request); + List logStreams = response.logStreams(); + return logStreams.stream().anyMatch(stream -> stream.logStreamName().equals(logStreamName)); + } + + private static void createLogGroup(CloudWatchLogsClient logsClient, String logGroupName) { + CreateLogGroupRequest request = CreateLogGroupRequest.builder() + .logGroupName(logGroupName) + .build(); + logsClient.createLogGroup(request); + LOGGER.info("Created log group: {}", logGroupName); + } + + private static void createLogStream(CloudWatchLogsClient logsClient, String logGroupName, String logStreamName) { + CreateLogStreamRequest request = CreateLogStreamRequest.builder() + .logGroupName(logGroupName) + .logStreamName(logStreamName) + .build(); + logsClient.createLogStream(request); + LOGGER.info("Created log stream: {} in log group: {}", logStreamName, logGroupName); + } +} \ No newline at end of file diff --git a/src/main/java/com/autotune/utils/CustomAppender.java b/src/main/java/com/autotune/utils/CustomAppender.java new file mode 100644 index 000000000..7a2f18b07 --- /dev/null +++ b/src/main/java/com/autotune/utils/CustomAppender.java @@ -0,0 +1,113 @@ +package com.autotune.utils; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.filter.AbstractFilter; +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.CloudWatchLogsAsyncClient; +import software.amazon.awssdk.services.cloudwatchlogs.model.InputLogEvent; +import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsRequest; +import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsResponse; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Plugin(name = "CustomAppender", category = "Core", elementType = Appender.ELEMENT_TYPE) +public class CustomAppender extends AbstractAppender { + + private final String awsAccessKeyId; + private final String logGroup; + private final String logStream; + private final String awsSecretKey; + private final String region; + + private String sequenceToken = null; + private final CloudWatchLogsAsyncClient logsClient; + private final Filter filter; + + protected CustomAppender(String name, Filter filter, Layout layout, String awsAccessKeyId, String logGroup, String logStream, String awsSecretKey, String region) { + super(name,filter, layout, false, null); + this.awsAccessKeyId = awsAccessKeyId; + this.logGroup = logGroup; + this.logStream = logStream; + this.awsSecretKey = awsSecretKey; + this.region = region; + this.filter = filter; + this.logsClient = CloudWatchLogsAsyncClient.builder() + .region(Region.of(region)) + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(awsAccessKeyId, awsSecretKey))) + .build(); + } + + @Override + public void start() { + super.start(); + } + + @Override + public void append(LogEvent event) { + String message = getLayout().toSerializable(event).toString(); + List logEvents = new ArrayList<>(); + logEvents.add(InputLogEvent.builder() + .timestamp(event.getTimeMillis()) + .message(message) + .build()); + + PutLogEventsRequest request = PutLogEventsRequest.builder() + .logGroupName(logGroup) + .logStreamName(logStream) + .logEvents(logEvents) + .sequenceToken(sequenceToken) + .build(); + + CompletableFuture futureResponse = logsClient.putLogEvents(request); + futureResponse.whenComplete((response, error) -> { + if (error != null) { + error.printStackTrace(); + } else { + sequenceToken = response.nextSequenceToken(); + } + }); + } + + @PluginFactory + public static CustomAppender createAppender( + @PluginAttribute("name") String name, + @PluginAttribute("logGroup") String logGroup, + @PluginAttribute("logStream") String logStream, + @PluginAttribute("awsAccessKeyId") String awsAccessKeyId, + @PluginAttribute("awsSecretKey") String awsSecretKey, + @PluginAttribute("region") String region, + @PluginElement("Layout") Layout layout, + @PluginAttribute("logLevel") String logLevel) { + Level level = Level.getLevel(logLevel); + Filter filter = new LogFilter(level); + return new CustomAppender(name, filter, layout, awsAccessKeyId, logGroup, logStream, awsSecretKey, region); + } + + private static class LogFilter extends AbstractFilter { + private final Level level; + protected LogFilter(Level level) { + super(Result.ACCEPT, Result.DENY); + this.level = level; + } + + @Override + public Result filter(LogEvent event) { + return event.getLevel().isMoreSpecificThan(level) ? onMatch : onMismatch; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/autotune/utils/KruizeConstants.java b/src/main/java/com/autotune/utils/KruizeConstants.java index ccf686fda..fac271211 100644 --- a/src/main/java/com/autotune/utils/KruizeConstants.java +++ b/src/main/java/com/autotune/utils/KruizeConstants.java @@ -377,6 +377,13 @@ private DBConstants() { } } + public static final class CloudWatchLogsConstants { + public static final String CONFIG_FILE = "DB_CONFIG_FILE"; + + private CloudWatchLogsConstants() { + } + } + public static final class DateFormats { public static final String STANDARD_JSON_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; public static final String DB_EXTRACTION_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; @@ -405,6 +412,21 @@ public static final class DATABASE_ENV_NAME { public static final String DATABASE_SSL_MODE = "database_sslmode"; } + /** + * In order to assign values to the static variables of KruizeDeploymentInfo + * using Java reflection, the class variables are utilized, and therefore, + * if any new variables are added, their corresponding declaration is necessary. + * Ref InitializeDeployment.setConfigValues(KruizeConstants.CONFIG_FILE, KruizeConstants.KRUIZE_CONFIG_ENV_NAME.class); + */ + public static final class CLOUDWATCH_LOGS_ENV_NAME { + public static final String CLOUDWATCH_LOGS_ACCESS_KEY_ID = "logging_cloudwatch_accessKeyId"; + public static final String CLOUDWATCH_LOGS_SECRET_ACCESS_KEY = "logging_cloudwatch_secretAccessKey"; + public static final String CLOUDWATCH_LOGS_LOG_GROUP = "logging_cloudwatch_logGroup"; + public static final String CLOUDWATCH_LOGS_REGION = "logging_cloudwatch_region"; + public static final String CLOUDWATCH_LOGS_LOG_STREAM = "logging_cloudwatch_logStream"; + public static final String CLOUDWATCH_LOGS_LOG_LEVEL = "logging_cloudwatch_logLevel"; + } + /** * In order to assign values to the static variables of KruizeDeploymentInfo * using Java reflection, the class variables are utilized, and therefore, From a6129c8c3f5fb1822c57d9c7d1451f155cad7117 Mon Sep 17 00:00:00 2001 From: kusumachalasani Date: Wed, 17 Apr 2024 19:58:21 +0530 Subject: [PATCH 2/6] Add cloudwatch LogAppender Signed-off-by: kusumachalasani --- src/main/java/com/autotune/Autotune.java | 7 +- .../operator/InitializeDeployment.java | 1 - ...LogSender.java => CloudWatchAppender.java} | 138 +++++++++++------- .../com/autotune/utils/CustomAppender.java | 113 -------------- .../com/autotune/utils/KruizeConstants.java | 29 +--- 5 files changed, 98 insertions(+), 190 deletions(-) rename src/main/java/com/autotune/utils/{CloudWatchLogSender.java => CloudWatchAppender.java} (52%) delete mode 100644 src/main/java/com/autotune/utils/CustomAppender.java diff --git a/src/main/java/com/autotune/Autotune.java b/src/main/java/com/autotune/Autotune.java index 2dcd50cec..2f7a1bfd0 100644 --- a/src/main/java/com/autotune/Autotune.java +++ b/src/main/java/com/autotune/Autotune.java @@ -28,10 +28,7 @@ import com.autotune.operator.KruizeDeploymentInfo; import com.autotune.service.HealthService; import com.autotune.service.InitiateListener; -import com.autotune.utils.CloudWatchLogSender; -import com.autotune.utils.KruizeConstants; -import com.autotune.utils.MetricsConfig; -import com.autotune.utils.ServerContext; +import com.autotune.utils.*; import com.autotune.utils.filter.KruizeCORSFilter; import io.prometheus.client.exporter.MetricsServlet; import io.prometheus.client.hotspot.DefaultExports; @@ -102,7 +99,7 @@ public static void main(String[] args) { try { InitializeDeployment.setup_deployment_info(); // Configure AWS CloudWatch - CloudWatchLogSender.configureCloudWatchLog(); + CloudWatchAppender.configureLoggerForCloudWatchLog(); // Read and execute the DDLs here executeDDLs(); // close the existing session factory before recreating diff --git a/src/main/java/com/autotune/operator/InitializeDeployment.java b/src/main/java/com/autotune/operator/InitializeDeployment.java index 5f4ad3428..51423ae23 100644 --- a/src/main/java/com/autotune/operator/InitializeDeployment.java +++ b/src/main/java/com/autotune/operator/InitializeDeployment.java @@ -43,7 +43,6 @@ private InitializeDeployment() { public static void setup_deployment_info() throws Exception, K8sTypeNotSupportedException, MonitoringAgentNotSupportedException, MonitoringAgentNotFoundException { setConfigValues(KruizeConstants.CONFIG_FILE, KruizeConstants.KRUIZE_CONFIG_ENV_NAME.class); setConfigValues(KruizeConstants.DBConstants.CONFIG_FILE, KruizeConstants.DATABASE_ENV_NAME.class); - setConfigValues(KruizeConstants.CloudWatchLogsConstants.CONFIG_FILE, KruizeConstants.CLOUDWATCH_LOGS_ENV_NAME.class); KruizeDeploymentInfo.setCluster_type(KruizeDeploymentInfo.cluster_type); KruizeDeploymentInfo.setKubernetesType(KruizeDeploymentInfo.k8s_type); KruizeDeploymentInfo.setAuth_type(KruizeDeploymentInfo.auth_type); diff --git a/src/main/java/com/autotune/utils/CloudWatchLogSender.java b/src/main/java/com/autotune/utils/CloudWatchAppender.java similarity index 52% rename from src/main/java/com/autotune/utils/CloudWatchLogSender.java rename to src/main/java/com/autotune/utils/CloudWatchAppender.java index 2d1dace5d..61cc79363 100644 --- a/src/main/java/com/autotune/utils/CloudWatchLogSender.java +++ b/src/main/java/com/autotune/utils/CloudWatchAppender.java @@ -1,87 +1,112 @@ package com.autotune.utils; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.builder.api.*; -import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.filter.AbstractFilter; +import org.apache.logging.log4j.core.layout.PatternLayout; 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.CloudWatchLogsAsyncClient; import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; import software.amazon.awssdk.services.cloudwatchlogs.model.*; +import java.util.ArrayList; import java.util.List; - +import java.util.concurrent.CompletableFuture; import static com.autotune.operator.KruizeDeploymentInfo.*; -public class CloudWatchLogSender { - private static final Logger LOGGER = LogManager.getLogger(CloudWatchLogSender.class); +public class CloudWatchAppender extends AbstractAppender { + private final String logGroupName; + private final String logStreamName; + private final CloudWatchLogsAsyncClient cloudWatchLogsClient; + private String sequenceToken = null; + + public CloudWatchAppender(String name, Filter filter, Layout layout, String logGroupName, String logStreamName, String region, String awsAccessKeyId, String awsSecretKey) { + super(name, filter, layout, false, null); + this.logGroupName = logGroupName; + this.logStreamName = logStreamName; + this.cloudWatchLogsClient = CloudWatchLogsAsyncClient.builder() + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(awsAccessKeyId, awsSecretKey))) + .region(Region.of(region)) + .build(); + } - private CloudWatchLogSender() { + @Override + public void start() { + super.start(); } - public static void configureCloudWatchLog() { - // Check if access details are provided + @Override + public void append(LogEvent event) { + String message = getLayout().toSerializable(event).toString(); + List logEvents = new ArrayList<>(); + logEvents.add(InputLogEvent.builder() + .timestamp(event.getTimeMillis()) + .message(message) + .build()); + + PutLogEventsRequest request = PutLogEventsRequest.builder() + .logGroupName(logGroupName) + .logStreamName(logStreamName) + .logEvents(logEvents) + .sequenceToken(sequenceToken) + .build(); + + CompletableFuture futureResponse = cloudWatchLogsClient.putLogEvents(request); + futureResponse.whenComplete((response, error) -> { + if (error != null) { + error.printStackTrace(); + } else { + sequenceToken = response.nextSequenceToken(); + } + }); + } + + public static void configureLoggerForCloudWatchLog() { if (cloudwatch_logs_access_key_id != null && !cloudwatch_logs_access_key_id.isEmpty() && cloudwatch_logs_secret_access_key != null && !cloudwatch_logs_secret_access_key.isEmpty() && cloudwatch_logs_region != null && !cloudwatch_logs_region.isEmpty()) { try { // Define default values for attributes if they are empty or null String cw_logs_log_group = cloudwatch_logs_log_group == null || cloudwatch_logs_log_group.isEmpty() ? "kruize-logs" : cloudwatch_logs_log_group; String cw_logs_log_stream = cloudwatch_logs_log_stream == null || cloudwatch_logs_log_stream.isEmpty() ? "kruize-stream" : cloudwatch_logs_log_stream; String cw_logs_log_level = cloudwatch_logs_log_level == null || cloudwatch_logs_log_level.isEmpty() ? "INFO" : cloudwatch_logs_log_level; + String cw_logs_log_level_uc = cw_logs_log_level.toUpperCase(); + - // Create a CloudWatchLogsClient with provided credentials and region CloudWatchLogsClient logsClient = CloudWatchLogsClient.builder() .region(Region.of(cloudwatch_logs_region)) .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(cloudwatch_logs_access_key_id, cloudwatch_logs_secret_access_key))) .build(); - // Check if log group exists, if not, create it if (!logGroupExists(logsClient, cw_logs_log_group)) { createLogGroup(logsClient, cw_logs_log_group); } - // Check if log stream exists, if not, create it if (!logStreamExists(logsClient, cw_logs_log_group, cw_logs_log_stream)) { createLogStream(logsClient, cw_logs_log_group, cw_logs_log_stream); } - // Create a configuration builder - ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); - LoggerContext ctx = (LoggerContext) LogManager.getContext(false); - - // Define the console appender - AppenderComponentBuilder consoleAppenderBuilder = builder.newAppender("consoleLogger", "Console") - .addAttribute("target", "SYSTEM_OUT") - .add(builder.newLayout("PatternLayout") - .addAttribute("pattern", "%d{yyyy-MM-ddHH:mm:ss.SSS} %level [%t][%F(%L)]-%msg%n")); - builder.add(consoleAppenderBuilder); - - // Add a custom appender - AppenderComponentBuilder cloudWatchAppenderBuilder = builder.newAppender("CloudWatchAppender", "CustomAppender") - .addAttribute("logGroup", cw_logs_log_group) - .addAttribute("logStream", cw_logs_log_stream) - .addAttribute("region", cloudwatch_logs_region) - .addAttribute("awsAccessKeyId", cloudwatch_logs_access_key_id) - .addAttribute("awsSecretKey", cloudwatch_logs_secret_access_key) - .addAttribute("logLevel", cw_logs_log_level) - .add(builder.newLayout("PatternLayout") - .addAttribute("pattern", "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"));; - builder.add(cloudWatchAppenderBuilder); - // Configure the root logger to use both console appender and the new CloudWatch appender - RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.INFO) - .add(builder.newAppenderRef("consoleLogger")) - .add(builder.newAppenderRef("CloudWatchAppender")); - builder.add(rootLogger); - Configuration config = builder.build(); - // Update the current context with the new configuration - ctx.updateLoggers(config); - for (Appender appender : config.getAppenders().values()) { - appender.start(); - } + LoggerContext context = (LoggerContext) LogManager.getContext(false); + Configuration config = context.getConfiguration(); + + Level level = Level.getLevel(cw_logs_log_level_uc); + Filter filter = new LogFilter(level); + Layout layout = PatternLayout.newBuilder().withPattern(KruizeConstants.Patterns.CLOUDWATCH_LOG_PATTERN).build(); + CloudWatchAppender appender = new CloudWatchAppender("cloudwatchRootAppender", filter, layout, cw_logs_log_group, cw_logs_log_stream, cloudwatch_logs_region,cloudwatch_logs_access_key_id,cloudwatch_logs_secret_access_key); + + appender.start(); + config.addAppender(appender); + // Adding CloudWatch Appender to "com.autotune" logger + LoggerConfig loggerConfig = config.getLoggerConfig("com.autotune"); + loggerConfig.addAppender(appender, level, filter); + context.updateLoggers(config); + } catch (Exception e) { LOGGER.error(e.getMessage()); } @@ -89,7 +114,6 @@ public static void configureCloudWatchLog() { LOGGER.info("AWS access details are not provided. Skipping sending logs to CloudWatch."); } } - private static boolean logGroupExists(CloudWatchLogsClient logsClient, String logGroupName) { DescribeLogGroupsRequest request = DescribeLogGroupsRequest.builder() .logGroupNamePrefix(logGroupName) @@ -125,4 +149,20 @@ private static void createLogStream(CloudWatchLogsClient logsClient, String logG logsClient.createLogStream(request); LOGGER.info("Created log stream: {} in log group: {}", logStreamName, logGroupName); } -} \ No newline at end of file + + + private static class LogFilter extends AbstractFilter { + private final Level level; + + protected LogFilter(Level level) { + super(Result.ACCEPT, Result.DENY); + this.level = level; + } + + @Override + public Result filter(LogEvent event) { + return event.getLevel().isMoreSpecificThan(level) ? onMatch : onMismatch; + } + + } +} diff --git a/src/main/java/com/autotune/utils/CustomAppender.java b/src/main/java/com/autotune/utils/CustomAppender.java deleted file mode 100644 index 7a2f18b07..000000000 --- a/src/main/java/com/autotune/utils/CustomAppender.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.autotune.utils; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.AbstractAppender; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.filter.AbstractFilter; -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.CloudWatchLogsAsyncClient; -import software.amazon.awssdk.services.cloudwatchlogs.model.InputLogEvent; -import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsRequest; -import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsResponse; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -@Plugin(name = "CustomAppender", category = "Core", elementType = Appender.ELEMENT_TYPE) -public class CustomAppender extends AbstractAppender { - - private final String awsAccessKeyId; - private final String logGroup; - private final String logStream; - private final String awsSecretKey; - private final String region; - - private String sequenceToken = null; - private final CloudWatchLogsAsyncClient logsClient; - private final Filter filter; - - protected CustomAppender(String name, Filter filter, Layout layout, String awsAccessKeyId, String logGroup, String logStream, String awsSecretKey, String region) { - super(name,filter, layout, false, null); - this.awsAccessKeyId = awsAccessKeyId; - this.logGroup = logGroup; - this.logStream = logStream; - this.awsSecretKey = awsSecretKey; - this.region = region; - this.filter = filter; - this.logsClient = CloudWatchLogsAsyncClient.builder() - .region(Region.of(region)) - .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(awsAccessKeyId, awsSecretKey))) - .build(); - } - - @Override - public void start() { - super.start(); - } - - @Override - public void append(LogEvent event) { - String message = getLayout().toSerializable(event).toString(); - List logEvents = new ArrayList<>(); - logEvents.add(InputLogEvent.builder() - .timestamp(event.getTimeMillis()) - .message(message) - .build()); - - PutLogEventsRequest request = PutLogEventsRequest.builder() - .logGroupName(logGroup) - .logStreamName(logStream) - .logEvents(logEvents) - .sequenceToken(sequenceToken) - .build(); - - CompletableFuture futureResponse = logsClient.putLogEvents(request); - futureResponse.whenComplete((response, error) -> { - if (error != null) { - error.printStackTrace(); - } else { - sequenceToken = response.nextSequenceToken(); - } - }); - } - - @PluginFactory - public static CustomAppender createAppender( - @PluginAttribute("name") String name, - @PluginAttribute("logGroup") String logGroup, - @PluginAttribute("logStream") String logStream, - @PluginAttribute("awsAccessKeyId") String awsAccessKeyId, - @PluginAttribute("awsSecretKey") String awsSecretKey, - @PluginAttribute("region") String region, - @PluginElement("Layout") Layout layout, - @PluginAttribute("logLevel") String logLevel) { - Level level = Level.getLevel(logLevel); - Filter filter = new LogFilter(level); - return new CustomAppender(name, filter, layout, awsAccessKeyId, logGroup, logStream, awsSecretKey, region); - } - - private static class LogFilter extends AbstractFilter { - private final Level level; - protected LogFilter(Level level) { - super(Result.ACCEPT, Result.DENY); - this.level = level; - } - - @Override - public Result filter(LogEvent event) { - return event.getLevel().isMoreSpecificThan(level) ? onMatch : onMismatch; - } - } - -} \ No newline at end of file diff --git a/src/main/java/com/autotune/utils/KruizeConstants.java b/src/main/java/com/autotune/utils/KruizeConstants.java index fac271211..94afc28d9 100644 --- a/src/main/java/com/autotune/utils/KruizeConstants.java +++ b/src/main/java/com/autotune/utils/KruizeConstants.java @@ -272,6 +272,7 @@ public static class Patterns { public static final String DURATION_PATTERN = "(\\d+)([a-zA-Z]+)"; public static final String WHITESPACE_PATTERN = "\\s"; public static final String QUERY_WITH_TIME_RANGE_PATTERN = ".*\\[(\\d+)([a-zA-Z]+)\\].*"; + public static final String CLOUDWATCH_LOG_PATTERN = "%d{yyyy-MM-ddHH:mm:ss.SSS} %level [%t][%F(%L)]-%msg%n"; private Patterns() { } @@ -377,13 +378,6 @@ private DBConstants() { } } - public static final class CloudWatchLogsConstants { - public static final String CONFIG_FILE = "DB_CONFIG_FILE"; - - private CloudWatchLogsConstants() { - } - } - public static final class DateFormats { public static final String STANDARD_JSON_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; public static final String DB_EXTRACTION_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; @@ -412,21 +406,6 @@ public static final class DATABASE_ENV_NAME { public static final String DATABASE_SSL_MODE = "database_sslmode"; } - /** - * In order to assign values to the static variables of KruizeDeploymentInfo - * using Java reflection, the class variables are utilized, and therefore, - * if any new variables are added, their corresponding declaration is necessary. - * Ref InitializeDeployment.setConfigValues(KruizeConstants.CONFIG_FILE, KruizeConstants.KRUIZE_CONFIG_ENV_NAME.class); - */ - public static final class CLOUDWATCH_LOGS_ENV_NAME { - public static final String CLOUDWATCH_LOGS_ACCESS_KEY_ID = "logging_cloudwatch_accessKeyId"; - public static final String CLOUDWATCH_LOGS_SECRET_ACCESS_KEY = "logging_cloudwatch_secretAccessKey"; - public static final String CLOUDWATCH_LOGS_LOG_GROUP = "logging_cloudwatch_logGroup"; - public static final String CLOUDWATCH_LOGS_REGION = "logging_cloudwatch_region"; - public static final String CLOUDWATCH_LOGS_LOG_STREAM = "logging_cloudwatch_logStream"; - public static final String CLOUDWATCH_LOGS_LOG_LEVEL = "logging_cloudwatch_logLevel"; - } - /** * In order to assign values to the static variables of KruizeDeploymentInfo * using Java reflection, the class variables are utilized, and therefore, @@ -457,6 +436,12 @@ public static final class KRUIZE_CONFIG_ENV_NAME { public static final String SETTINGS_HIBERNATE_SHOW_SQL = "hibernate_showsql"; public static final String SETTINGS_HIBERNATE_TIME_ZONE = "hibernate_timezone"; public static final String PLOTS = "plots"; + public static final String CLOUDWATCH_LOGS_ACCESS_KEY_ID = "cloudwatch_accessKeyId"; + public static final String CLOUDWATCH_LOGS_SECRET_ACCESS_KEY = "cloudwatch_secretAccessKey"; + public static final String CLOUDWATCH_LOGS_LOG_GROUP = "cloudwatch_logGroup"; + public static final String CLOUDWATCH_LOGS_REGION = "cloudwatch_region"; + public static final String CLOUDWATCH_LOGS_LOG_STREAM = "cloudwatch_logStream"; + public static final String CLOUDWATCH_LOGS_LOG_LEVEL = "cloudwatch_logLevel"; } public static final class RecommendationEngineConstants { From 246c9feeeedb0f3f00152fea693be96fbac7ef55 Mon Sep 17 00:00:00 2001 From: kusumachalasani Date: Wed, 17 Apr 2024 20:07:33 +0530 Subject: [PATCH 3/6] Add cloudwatch LogAppender Signed-off-by: kusumachalasani --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 654101da1..61b6445e0 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,7 @@ 6.1.7.Final 8.0.1.Final 1.9.9 + 2.17.102 @@ -91,7 +92,7 @@ software.amazon.awssdk cloudwatchlogs - 2.17.102 + ${awssdk-version} From 70147d18853354607a7ce2da1529f666b147e4bf Mon Sep 17 00:00:00 2001 From: kusumachalasani Date: Wed, 17 Apr 2024 20:49:47 +0530 Subject: [PATCH 4/6] Add cloudwatch config Signed-off-by: kusumachalasani --- .../BYODB-installation/minikube/kruize-crc-minikube.yaml | 8 ++++++++ .../openshift/kruize-crc-openshift.yaml | 8 ++++++++ .../minikube/kruize-crc-minikube.yaml | 8 ++++++++ .../openshift/kruize-crc-openshift.yaml | 8 ++++++++ 4 files changed, 32 insertions(+) diff --git a/manifests/crc/BYODB-installation/minikube/kruize-crc-minikube.yaml b/manifests/crc/BYODB-installation/minikube/kruize-crc-minikube.yaml index aecb83e1a..bb8417d2a 100644 --- a/manifests/crc/BYODB-installation/minikube/kruize-crc-minikube.yaml +++ b/manifests/crc/BYODB-installation/minikube/kruize-crc-minikube.yaml @@ -42,6 +42,14 @@ data: "hbm2ddlauto": "update", "showsql": "false", "timezone": "UTC" + }, + "cloudwatch": { + "accessKeyId": "", + "logGroup": "kruize-logs", + "logStream": "kruize-stream", + "region": "", + "secretAccessKey": "", + "logLevel": "WARN" } } --- diff --git a/manifests/crc/BYODB-installation/openshift/kruize-crc-openshift.yaml b/manifests/crc/BYODB-installation/openshift/kruize-crc-openshift.yaml index d5080dcdc..5efa793e0 100644 --- a/manifests/crc/BYODB-installation/openshift/kruize-crc-openshift.yaml +++ b/manifests/crc/BYODB-installation/openshift/kruize-crc-openshift.yaml @@ -42,6 +42,14 @@ data: "hbm2ddlauto": "none", "showsql": "false", "timezone": "UTC" + }, + "cloudwatch": { + "accessKeyId": "", + "logGroup": "kruize-logs", + "logStream": "kruize-stream", + "region": "", + "secretAccessKey": "", + "logLevel": "WARN" } } --- diff --git a/manifests/crc/default-db-included-installation/minikube/kruize-crc-minikube.yaml b/manifests/crc/default-db-included-installation/minikube/kruize-crc-minikube.yaml index f4f41af4b..a9f468386 100644 --- a/manifests/crc/default-db-included-installation/minikube/kruize-crc-minikube.yaml +++ b/manifests/crc/default-db-included-installation/minikube/kruize-crc-minikube.yaml @@ -121,6 +121,14 @@ data: "hbm2ddlauto": "none", "showsql": "false", "timezone": "UTC" + }, + "cloudwatch": { + "accessKeyId": "", + "logGroup": "kruize-logs", + "logStream": "kruize-stream", + "region": "", + "secretAccessKey": "", + "logLevel": "WARN" } } --- diff --git a/manifests/crc/default-db-included-installation/openshift/kruize-crc-openshift.yaml b/manifests/crc/default-db-included-installation/openshift/kruize-crc-openshift.yaml index 9529783c8..b4c935332 100644 --- a/manifests/crc/default-db-included-installation/openshift/kruize-crc-openshift.yaml +++ b/manifests/crc/default-db-included-installation/openshift/kruize-crc-openshift.yaml @@ -102,6 +102,14 @@ data: "hbm2ddlauto": "none", "showsql": "false", "timezone": "UTC" + }, + "cloudwatch": { + "accessKeyId": "", + "logGroup": "kruize-logs", + "logStream": "kruize-stream", + "region": "", + "secretAccessKey": "", + "logLevel": "WARN" } } --- From 76767494fb038a29063f847114396b814fddf0f9 Mon Sep 17 00:00:00 2001 From: kusumachalasani Date: Wed, 17 Apr 2024 23:21:28 +0530 Subject: [PATCH 5/6] Update cloudwatch logLevel Signed-off-by: kusumachalasani --- .../crc/BYODB-installation/minikube/kruize-crc-minikube.yaml | 2 +- .../crc/BYODB-installation/openshift/kruize-crc-openshift.yaml | 2 +- .../minikube/kruize-crc-minikube.yaml | 2 +- .../openshift/kruize-crc-openshift.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manifests/crc/BYODB-installation/minikube/kruize-crc-minikube.yaml b/manifests/crc/BYODB-installation/minikube/kruize-crc-minikube.yaml index bb8417d2a..a32334908 100644 --- a/manifests/crc/BYODB-installation/minikube/kruize-crc-minikube.yaml +++ b/manifests/crc/BYODB-installation/minikube/kruize-crc-minikube.yaml @@ -49,7 +49,7 @@ data: "logStream": "kruize-stream", "region": "", "secretAccessKey": "", - "logLevel": "WARN" + "logLevel": "INFO" } } --- diff --git a/manifests/crc/BYODB-installation/openshift/kruize-crc-openshift.yaml b/manifests/crc/BYODB-installation/openshift/kruize-crc-openshift.yaml index 5efa793e0..f51631887 100644 --- a/manifests/crc/BYODB-installation/openshift/kruize-crc-openshift.yaml +++ b/manifests/crc/BYODB-installation/openshift/kruize-crc-openshift.yaml @@ -49,7 +49,7 @@ data: "logStream": "kruize-stream", "region": "", "secretAccessKey": "", - "logLevel": "WARN" + "logLevel": "INFO" } } --- diff --git a/manifests/crc/default-db-included-installation/minikube/kruize-crc-minikube.yaml b/manifests/crc/default-db-included-installation/minikube/kruize-crc-minikube.yaml index 448f9ead8..8daae60cb 100644 --- a/manifests/crc/default-db-included-installation/minikube/kruize-crc-minikube.yaml +++ b/manifests/crc/default-db-included-installation/minikube/kruize-crc-minikube.yaml @@ -129,7 +129,7 @@ data: "logStream": "kruize-stream", "region": "", "secretAccessKey": "", - "logLevel": "WARN" + "logLevel": "INFO" }, "datasource": [ { diff --git a/manifests/crc/default-db-included-installation/openshift/kruize-crc-openshift.yaml b/manifests/crc/default-db-included-installation/openshift/kruize-crc-openshift.yaml index 247b6fcea..5046cae06 100644 --- a/manifests/crc/default-db-included-installation/openshift/kruize-crc-openshift.yaml +++ b/manifests/crc/default-db-included-installation/openshift/kruize-crc-openshift.yaml @@ -110,7 +110,7 @@ data: "logStream": "kruize-stream", "region": "", "secretAccessKey": "", - "logLevel": "WARN" + "logLevel": "INFO" }, "datasource": [ { From 1d2b0ba970cc44acc85476bea9fc09766720c2f5 Mon Sep 17 00:00:00 2001 From: kusumachalasani Date: Thu, 18 Apr 2024 12:59:53 +0530 Subject: [PATCH 6/6] Add cloudwatch config Signed-off-by: kusumachalasani --- design/KruizeConfiguration.md | 26 ++++++++++++++++++++++++++ design/KruizeConfigurationProcedure.md | 8 ++++++++ 2 files changed, 34 insertions(+) diff --git a/design/KruizeConfiguration.md b/design/KruizeConfiguration.md index 63c4fe2bb..5b83e7c2f 100644 --- a/design/KruizeConfiguration.md +++ b/design/KruizeConfiguration.md @@ -91,6 +91,32 @@ The following environment variables are set using the `kubectl apply` command wi - Description: Timezone configuration for Hibernate. - Value: "UTC" +## CloudWatch Configuration + +- **accessKeyId** + - Description: AWS account's access key ID. If not provided, CloudWatch logging is disabled. + - Value: "" + +- **secretAccessKey** + - Description: AWS account's secret access key. If not provided, CloudWatch logging is disabled. + - Value: "" + +- **region** + - Description: AWS region where CloudWatch logs are stored. If not provided, CloudWatch logging is disabled. + - Value: "" + +- **logGroup** + - Description: Name of the CloudWatch log group. Defaults to "kruize-logs". + - Value: "kruize-logs" + +- **logStream** + - Description: Name of the CloudWatch log stream within the log group. Defaults to "kruize-stream". + - Value: "kruize-stream" + +- **logLevel** + - Description: The minimum level of log events to send to CloudWatch. Defaults to "INFO". + - Value: "INFO" + ## Other Configuration - **deletepartitionsthreshold** diff --git a/design/KruizeConfigurationProcedure.md b/design/KruizeConfigurationProcedure.md index 71ba9b8ed..3b3a48d5e 100644 --- a/design/KruizeConfigurationProcedure.md +++ b/design/KruizeConfigurationProcedure.md @@ -48,6 +48,14 @@ data: "c3p0maxstatements": 50, "hbm2ddlauto": "update", "showsql": "true" + }, + "cloudwatch": { + "accessKeyId": "", + "secretAccessKey": "", + "region": "", + "logGroup": "kruize-logs", + "logStream": "kruize-stream", + "logLevel": "INFO" } } ```