diff --git a/build.gradle b/build.gradle index 9ea5233c..bd28a04a 100644 --- a/build.gradle +++ b/build.gradle @@ -33,9 +33,12 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-core:2.11.1' implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.1' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.1' + implementation 'org.slf4j:slf4j-api:1.7.30' // Use JUnit test framework testImplementation 'junit:junit:4.13' testImplementation 'org.apache.commons:commons-lang3:3.10' testImplementation "org.mockito:mockito-core:2.+" + testImplementation "org.powermock:powermock-module-junit4:2.0.2" + testImplementation "org.powermock:powermock-api-mockito2:2.0.2" } diff --git a/examples/ErrorHandlingExample.java b/examples/ErrorHandlingExample.java index 0054d1c6..c343d8b0 100644 --- a/examples/ErrorHandlingExample.java +++ b/examples/ErrorHandlingExample.java @@ -3,9 +3,9 @@ import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.EMFLogger; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.CloudWatchLogsClientSink; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.ConsoleSink; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.MultiSink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.CloudWatchLogsClientSink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.ConsoleSink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.MultiSink; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.CloudwatchMetricCollection; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; diff --git a/examples/Examples.java b/examples/Examples.java index fb235030..397dee70 100644 --- a/examples/Examples.java +++ b/examples/Examples.java @@ -6,9 +6,9 @@ import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.EMFLogger; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.CloudWatchLogsClientSink; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.ConsoleSink; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.MultiSink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.CloudWatchLogsClientSink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.ConsoleSink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.MultiSink; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.Aggregation; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.CloudwatchMetricCollection; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; diff --git a/integ/software/amazon/awssdk/services/cloudwatchlogs/emf/IntegrationTestBase.java b/integ/software/amazon/awssdk/services/cloudwatchlogs/emf/IntegrationTestBase.java index 80be7096..7e985109 100644 --- a/integ/software/amazon/awssdk/services/cloudwatchlogs/emf/IntegrationTestBase.java +++ b/integ/software/amazon/awssdk/services/cloudwatchlogs/emf/IntegrationTestBase.java @@ -4,7 +4,7 @@ import software.amazon.awssdk.services.cloudwatch.CloudWatchClient; import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.EMFLogger; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.CloudWatchLogsClientSink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.CloudWatchLogsClientSink; import software.amazon.awssdk.services.cloudwatchlogs.emf.testutils.EMFTestUtilities; public class IntegrationTestBase { diff --git a/integ/software/amazon/awssdk/services/cloudwatchlogs/emf/LogItemSizeTest.java b/integ/software/amazon/awssdk/services/cloudwatchlogs/emf/LogItemSizeTest.java index 54c161bf..32812aaa 100644 --- a/integ/software/amazon/awssdk/services/cloudwatchlogs/emf/LogItemSizeTest.java +++ b/integ/software/amazon/awssdk/services/cloudwatchlogs/emf/LogItemSizeTest.java @@ -5,7 +5,7 @@ import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.CloudWatchLimits; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.EMFLogger; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.SinkUtilities; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.SinkUtilities; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.CloudwatchMetricCollection; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; import software.amazon.awssdk.services.cloudwatchlogs.model.GetLogEventsRequest; diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/Configuration.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/Configuration.java new file mode 100644 index 00000000..09b95778 --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/Configuration.java @@ -0,0 +1,59 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.config; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import software.amazon.awssdk.services.cloudwatchlogs.emf.environment.Environments; + +import java.util.Optional; + +@AllArgsConstructor +public class Configuration { + /** + * Whether or not internal logging should be enabled. + */ + @Getter @Setter + boolean debuggingLoggingEnabled; + + /** + * The name of the service to use in the default dimensions. + */ + @Getter @Setter + Optional serviceName; + + /** + * The type of the service to use in the default dimensions. + */ + @Getter @Setter + Optional serviceType; + + /** + * The LogGroup name to use. This is only used for the Cloudwatch Agent in agent-based environment. + */ + @Getter @Setter + Optional logGroupName; + + /** + * The LogStream name to use. This will be ignored when using the + * Lambda scope. + */ + @Getter @Setter + Optional logStreamName; + + /** + * The endpoint to use to connect to the CloudWatch Agent + */ + @Getter @Setter + Optional agentEndpoint; + + /** + * Environment override. This will short circuit auto-environment detection. + * Valid values include: + * - Local: no decoration and sends over stdout + * - Lambda: decorates logs with Lambda metadata and sends over stdout + * - Agent: no decoration and sends over TCP + * - EC2: decorates logs with EC2 metadata and sends over TCP + */ + @Getter @Setter + Environments environmentOverride; +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/ConfigurationKeys.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/ConfigurationKeys.java new file mode 100644 index 00000000..cb3f47da --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/ConfigurationKeys.java @@ -0,0 +1,14 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.config; + +public class ConfigurationKeys { + + public static final String ENV_VAR_PREFIX = "AWS_EMF"; + + public static final String ENABLE_DEBUG_LOGGING = "ENABLE_DEBUG_LOGGING"; + public static final String SERVICE_NAME = "SERVICE_NAME"; + public static final String SERVICE_TYPE = "SERVICE_TYPE"; + public static final String LOG_GROUP_NAME = "LOG_GROUP_NAME"; + public static final String LOG_STREAM_NAME = "LOG_STREAM_NAME"; + public static final String AGENT_ENDPOINT = "AGENT_ENDPOINT"; + public static final String ENVIRONMENT_OVERRIDE = "ENVIRONMENT"; +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/EnvironmentConfigurationProvider.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/EnvironmentConfigurationProvider.java new file mode 100644 index 00000000..001c7fe4 --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/EnvironmentConfigurationProvider.java @@ -0,0 +1,57 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.config; + +import software.amazon.awssdk.services.cloudwatchlogs.emf.environment.Environments; + +import java.util.Optional; + +/** + * Loads configuration from environment variables + */ +public class EnvironmentConfigurationProvider { + private static Configuration config; + + protected EnvironmentConfigurationProvider() {} + + public static Configuration getConfig() { + if (config == null) { + config = new Configuration( + getBoolEnvVar(ConfigurationKeys.ENABLE_DEBUG_LOGGING), + getEnvVar(ConfigurationKeys.SERVICE_NAME), + getEnvVar(ConfigurationKeys.SERVICE_TYPE), + getEnvVar(ConfigurationKeys.LOG_GROUP_NAME), + getEnvVar(ConfigurationKeys.LOG_STREAM_NAME), + getEnvVar(ConfigurationKeys.AGENT_ENDPOINT), + getEnvironmentOverride() + ); + } + return config; + } + + private static Optional getEnvVar(String key) { + String name = String.join("", ConfigurationKeys.ENV_VAR_PREFIX, "_", key); + return Optional.ofNullable(getEnv(name)); + } + + private static boolean getBoolEnvVar(String key) { + String name = String.join("", ConfigurationKeys.ENV_VAR_PREFIX, "_", key); + return Optional.ofNullable(getEnv(name)).map(str -> str.equalsIgnoreCase("true")).orElse(false); + } + + private static Environments getEnvironmentOverride() { + Optional environmentName = getEnvVar(ConfigurationKeys.ENVIRONMENT_OVERRIDE); + if (!environmentName.isPresent()) { + return Environments.Unknown; + } + + try { + return Environments.valueOf(environmentName.get()); + } catch (Exception e) { + return Environments.Unknown; + } + } + + private static String getEnv(String name) { + return SystemWrapper.getenv(name); + } + +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/SystemWrapper.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/SystemWrapper.java new file mode 100644 index 00000000..a42a5907 --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/SystemWrapper.java @@ -0,0 +1,7 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.config; + +class SystemWrapper { + static String getenv(String name) { + return System.getenv(name); + } +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/DefaultEnvironment.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/DefaultEnvironment.java new file mode 100644 index 00000000..085e08df --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/DefaultEnvironment.java @@ -0,0 +1,73 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.environment; + +import lombok.extern.slf4j.Slf4j; +import software.amazon.awssdk.services.cloudwatchlogs.emf.config.Configuration; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.AgentSink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.Endpoint; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.ISink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.SocketClientFactory; + + +@Slf4j +public class DefaultEnvironment implements Environment { + private Configuration config; + private ISink sink; + + public DefaultEnvironment(Configuration config) { + this.config = config; + } + + + @Override + public boolean probe() { + return true; + } + + @Override + public String getName() { + if (!config.getServiceName().isPresent()) { + log.info("Unknown ServiceName"); + return "Unknown"; + } + return config.getServiceName().get(); + } + + @Override + public String getType() { + if (!config.getServiceType().isPresent()) { + log.info("Unknown ServiceType"); + return "Unknown"; + } + return config.getServiceType().get(); + } + + public String getLogStreamName() { + return config.getLogStreamName().orElse(getName() + "-stream"); + } + + @Override + public String getLogGroupName() { + return config.getLogGroupName().orElse(getName() + "-metrics"); + } + + @Override + public void configureContext(MetricsContext context) { + // no-op + } + + @Override + public ISink getSink() { + if (sink == null) { + Endpoint endpoint; + if (!config.getAgentEndpoint().isPresent()) { + log.info("Endpoint is not defined. Using default: {}", Endpoint.DEFAULT_TCP_ENDPOINT); + endpoint = Endpoint.DEFAULT_TCP_ENDPOINT; + } else { + endpoint = Endpoint.fromURL(config.getAgentEndpoint().get()); + } + sink = new AgentSink(getLogGroupName(), getLogStreamName(), endpoint, new SocketClientFactory()); + } + return sink; + } +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/Environment.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/Environment.java new file mode 100644 index 00000000..a1d5abc8 --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/Environment.java @@ -0,0 +1,41 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.environment; + +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.ISink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; + +/** + * A runtime environment (e.g. Lambda, EKS, ECS, EC2) + */ +public interface Environment { + /** + * Determines whether or not we are executing in this environment + */ + boolean probe(); + + /** + * Get the environment name. This will be used to set the ServiceName dimension. + */ + String getName(); + + /** + * Get the environment type. This will be used to set the ServiceType dimension. + */ + String getType(); + + /** + * Get log group name. This will be used to set the LogGroup dimension. + */ + String getLogGroupName(); + + /** + * Configure the context with environment properties. + * + * @param context + */ + void configureContext(MetricsContext context); + + /** + * Create the appropriate sink for this environment. + */ + ISink getSink(); +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/EnvironmentProvider.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/EnvironmentProvider.java new file mode 100644 index 00000000..db8dfd73 --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/EnvironmentProvider.java @@ -0,0 +1,11 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.environment; + +import software.amazon.awssdk.services.cloudwatchlogs.emf.config.EnvironmentConfigurationProvider; + +public class EnvironmentProvider { + + //TODO: Support more environments + public Environment resolveEnvironment() { + return new DefaultEnvironment(EnvironmentConfigurationProvider.getConfig()); + } +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/Environments.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/Environments.java new file mode 100644 index 00000000..6642ef24 --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/Environments.java @@ -0,0 +1,5 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.environment; + +public enum Environments { + Local, Lambda, Agent, EC2, ECS, Unknown +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/EMFLogger.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/EMFLogger.java index 82b2797a..b3639ccd 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/EMFLogger.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/EMFLogger.java @@ -4,7 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NonNull; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.ISink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.ISink; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; import java.util.ArrayList; diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/FlushException.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/FlushException.java index 3d00f4c3..78626a52 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/FlushException.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/FlushException.java @@ -51,6 +51,10 @@ public FlushException(List failedLogItems, List unproces this.unprocessedLogItems = unprocessedLogItems; } + public FlushException(String message, Throwable cause) { + super(message, cause); + } + // Internal constructor for handling errors before all information is available protected FlushException(String message) { super(message); diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/MetricsLogger.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/MetricsLogger.java new file mode 100644 index 00000000..955c1032 --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/MetricsLogger.java @@ -0,0 +1,117 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.logger; + +import software.amazon.awssdk.services.cloudwatch.model.StandardUnit; +import software.amazon.awssdk.services.cloudwatchlogs.emf.environment.Environment; +import software.amazon.awssdk.services.cloudwatchlogs.emf.environment.EnvironmentProvider; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.DimensionSet; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.ISink; + +public class MetricsLogger { + private MetricsContext context; + private Environment environment; + + public MetricsLogger(EnvironmentProvider environmentProvider) { + this(environmentProvider, new MetricsContext()); + } + + public MetricsLogger(EnvironmentProvider environmentProvider, MetricsContext metricsContext) { + environment = environmentProvider.resolveEnvironment(); + context = metricsContext; + } + + /** + * Flushes the current context state to the configured sink. + * TODO: Support flush asynchronously + */ + public void flush() { + ISink sink = environment.getSink(); + configureContextForEnvironment(context, environment); + sink.accept(context); + context = context.createCopyWithContext(); + } + + /** + * Set a property on the published metrics. + * This is stored in the emitted log data and you are not + * charged for this data by CloudWatch Metrics. + * These values can be values that are useful for searching on, + * but have too high cardinality to emit as dimensions to + * CloudWatch Metrics. + * + * @param key Property name + * @param value Property value + */ + public MetricsLogger putProperty(String key, Object value) { + this.context.putProperty(key, value); + return this; + } + + /** + * Adds a dimension. + * This is generally a low cardinality key-value pair that is part of the metric identity. + * CloudWatch treats each unique combination of dimensions as a separate metric, even if the metrics have the same metric name. + * + * @param dimensions + * @see [CloudWatch Dimensions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Dimension) + */ + public MetricsLogger putDimensions(DimensionSet dimensions) { + context.putDimension(dimensions); + return this; + } + + /** + * Overwrite all dimensions on this MetricsLogger instance. + * + * @param dimensionSets + * @see [CloudWatch Dimensions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Dimension) + */ + public MetricsLogger setDimensions(DimensionSet ... dimensionSets) { + context.setDimensions(dimensionSets); + return this; + } + + /** + * Put a metric value. + * This value will be emitted to CloudWatch Metrics asyncronously and does not contribute to your + * account TPS limits. The value will also be available in your CloudWatch Logs + * @param key + * @param value + * @param unit + */ + public MetricsLogger putMetric(String key, double value, StandardUnit unit) { + this.context.putMetric(key, value, unit); + return this; + } + + /** + * Put a metric value. + * This value will be emitted to CloudWatch Metrics asyncronously and does not contribute to your + * account TPS limits. The value will also be available in your CloudWatch Logs + * @param key + * @param value + */ + public MetricsLogger putMetric(String key, double value) { + this.context.putMetric(key, value, StandardUnit.NONE); + return this; + } + + /** + * Set the CloudWatch namespace that metrics should be published to. + * @param namespace + */ + public MetricsLogger setNamespace(String namespace) { + this.context.setNamespace(namespace); + return this; + } + + private void configureContextForEnvironment(MetricsContext context, Environment environment) { + if (context.hasDefaultDimensions()) + return; + DimensionSet defaultDimension = new DimensionSet(); + defaultDimension.addDimension("LogGroup", environment.getLogGroupName()); + defaultDimension.addDimension("ServiceName", environment.getName()); + defaultDimension.addDimension("ServiceType", environment.getType()); + context.setDefaultDimensions(defaultDimension); + } +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/DimensionSet.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/DimensionSet.java index 7de1ff37..c04daa06 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/DimensionSet.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/DimensionSet.java @@ -138,4 +138,11 @@ public DimensionSet add(DimensionSet other) { public Set getDimensionKeys() { return dimensionRecords.keySet(); } + + /** + * Return the dimension value associated with a dimension key + */ + public String getDimensionValue(String key) { + return this.dimensionRecords.get(key); + } } diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/EMFLogItem.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/EMFLogItem.java index 2ea7a583..555091ad 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/EMFLogItem.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/EMFLogItem.java @@ -33,14 +33,7 @@ public EMFLogItem() { * @throws JsonProcessingException */ public String serializeMetrics() throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); - - if (EMFLogItem.isGlobalPrettyPrintJson()) { - objectMapper.enable(SerializationFeature.INDENT_OUTPUT); - } - - String ret = objectMapper.writeValueAsString(rootNode); - return ret; + return rootNode.serialize(); } /** diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/EmptyMetricsFilter.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/EmptyMetricsFilter.java new file mode 100644 index 00000000..50dbdffa --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/EmptyMetricsFilter.java @@ -0,0 +1,30 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.model; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.PropertyWriter; +import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; + +/** + * A Jackson property filter that filters out "_aws" metadata object if no metrics have been added + */ +class EmptyMetricsFilter extends SimpleBeanPropertyFilter { + + @Override + public void serializeAsField(Object pojo, JsonGenerator gen, SerializerProvider provider, PropertyWriter writer) throws Exception { + if (include(writer)) { + if (!writer.getName().equals("_aws")) { + writer.serializeAsField(pojo, gen, provider); + return; + } + Metadata metadata = ((RootNode) pojo).getAws(); + if (metadata.isEmpty()) { + return; + } + writer.serializeAsField(pojo, gen, provider); + } else if (!gen.canOmitFields()) { + writer.serializeAsOmittedField(pojo, gen, provider); + } + } + +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/Metadata.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/Metadata.java index 882005b8..478716e4 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/Metadata.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/Metadata.java @@ -1,5 +1,6 @@ package software.amazon.awssdk.services.cloudwatchlogs.emf.model; +import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -44,4 +45,14 @@ MetricDirective createMetricDirective() { cloudWatchMetrics.add(newMetricDirective); return newMetricDirective; } + + /** + * Test if there's any metric added. + * @return true if no metrics have been added, otherwise, false + */ + boolean isEmpty() { + return cloudWatchMetrics.isEmpty() || this.cloudWatchMetrics.stream() + .allMatch(MetricDirective::hasNoMetrics); + } + } diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/MetricDirective.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/MetricDirective.java index 422e09c8..dbe5a1af 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/MetricDirective.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/MetricDirective.java @@ -1,6 +1,7 @@ package software.amazon.awssdk.services.cloudwatchlogs.emf.model; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -24,10 +25,11 @@ class MetricDirective { @JsonProperty("Metrics") private List metrics = new ArrayList<>(); - @Setter + @Getter(AccessLevel.PROTECTED) private List dimensions = new ArrayList<>(); @Setter + @Getter(AccessLevel.PROTECTED) private DimensionSet defaultDimensions = new DimensionSet(); private boolean should_use_default_dimension = true; @@ -75,4 +77,12 @@ List getAllDimensions() { .map(dim -> defaultDimensions.add(dim)) .collect(Collectors.toList()); } + + /** + * Test if there's any metric added. + * @return true if no metrics have been added, otherwise, false + */ + boolean hasNoMetrics() { + return this.getMetrics().isEmpty(); + } } diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/MetricsContext.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/MetricsContext.java index 873bf89d..6661b5a1 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/MetricsContext.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/MetricsContext.java @@ -1,9 +1,12 @@ package software.amazon.awssdk.services.cloudwatchlogs.emf.model; +import com.fasterxml.jackson.core.JsonProcessingException; import lombok.Getter; import software.amazon.awssdk.services.cloudwatch.model.StandardUnit; import java.util.Arrays; +import java.util.List; +import java.util.Map; /** * Stores metrics and their associated properties and dimensions @@ -25,6 +28,23 @@ public MetricsContext(RootNode rootNode) { metricDirective = rootNode.getAws().createMetricDirective(); } + public MetricsContext( + String namespace, + Map properties, + List dimensionSets, + DimensionSet defaultDimensionSet + ) { + this(); + setNamespace(namespace); + setDefaultDimensions(defaultDimensionSet); + for (DimensionSet dimension: dimensionSets) { + putDimension(dimension); + } + for (Map.Entry entry: properties.entrySet()) { + putProperty(entry.getKey(), entry.getValue()); + } + } + /** * Update the namespace with the parameter * @param namespace The new namespace @@ -33,6 +53,13 @@ public void setNamespace(String namespace) { metricDirective.setNamespace(namespace); } + /** + * Return the namespace. If the namespace is not set, it would return a default value + */ + public String getNamespace() { + return metricDirective.getNamespace(); + } + /** * Update the dimensions * @param dimensionSets @@ -54,6 +81,21 @@ public void setDefaultDimensions(DimensionSet dimensionSet) { metricDirective.setDefaultDimensions(dimensionSet); } + /** + * Return the default dimension set + */ + public DimensionSet getDefaultDimensions() { + return metricDirective.getDefaultDimensions(); + } + + + /** + * Return true if there're default dimensions defined, otherwise, false + */ + public boolean hasDefaultDimensions() { + return getDefaultDimensions().getDimensionKeys().size() > 0; + } + /** * Add a metric measurement to the context. * Multiple calls using the same key will be stored as an @@ -108,7 +150,13 @@ public void putMetric(String key, double value) { */ public void putProperty(String name, Object value) { rootNode.putProperty(name, value); + } + /** + * Return the value of a property. + */ + public Object getProperty(String name) { + return rootNode.getProperties().get(name); } @@ -139,4 +187,32 @@ public void putDimension(DimensionSet dimensionSet) { public void putDimension(String dimension, String value) { metricDirective.putDimensionSet(DimensionSet.of(dimension, value)); } + + /** + * Return the list of dimensions that has been added, including default dimensions + */ + public List getDimensions() { + return metricDirective.getAllDimensions(); + } + + + /** + * Creates an independently flushable context. + */ + public MetricsContext createCopyWithContext() { + return new MetricsContext( + this.metricDirective.getNamespace(), + this.rootNode.getProperties(), + this.metricDirective.getDimensions(), + this.metricDirective.getDefaultDimensions() + ); + } + + /** + * Serialize the metrics in this context to a string. + * @throws JsonProcessingException + */ + public String serialize() throws JsonProcessingException { + return this.rootNode.serialize(); + } } diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/RootNode.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/RootNode.java index aff53d71..894fc2bc 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/RootNode.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/RootNode.java @@ -1,7 +1,11 @@ package software.amazon.awssdk.services.cloudwatchlogs.emf.model; import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import lombok.Getter; import java.util.ArrayList; @@ -12,12 +16,20 @@ /** * Represents the root of the EMF schema. */ +@JsonFilter("emptyMetricFilter") class RootNode { @Getter @JsonProperty("_aws") - private Metadata aws = new Metadata();; + private Metadata aws = new Metadata(); private Map properties = new HashMap<>(); private Map> metrics = new HashMap<>(); + private ObjectMapper objectMapper = new ObjectMapper(); + + RootNode() { + final SimpleFilterProvider filterProvider = new SimpleFilterProvider() + .addFilter("emptyMetricFilter", new EmptyMetricsFilter()); + objectMapper.setFilterProvider(filterProvider); + } public void putProperty(String key, Object value) { properties.put(key, value); @@ -65,4 +77,8 @@ Map getDimensions() { } return dimensions; } + + String serialize() throws JsonProcessingException { + return objectMapper.writeValueAsString(this); + } } diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/AgentSink.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/AgentSink.java new file mode 100644 index 00000000..7084f15b --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/AgentSink.java @@ -0,0 +1,44 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; + +import java.util.List; + + +@Slf4j +public class AgentSink implements ISink { + private final String logGroupName; + private final String logStreamName; + private final SocketClient client; + + public AgentSink(String logGroupName, String logStreamName, Endpoint endpoint, SocketClientFactory clientFactory) { + this.logGroupName = logGroupName; + this.logStreamName = logStreamName; + client = clientFactory.getClient(endpoint); + } + + @Override + public void accept(List logItems) throws FlushException { + //TODO Remove this method + } + + public void accept(MetricsContext context) { + if (logGroupName != null && !logGroupName.isEmpty()) { + context.putProperty("LogGroupName", logGroupName); + } + + if (logStreamName!= null && !logStreamName.isEmpty()) { + context.putProperty("LogStreamName", logStreamName); + } + + try { + client.sendMessage(context.serialize()); + } catch (JsonProcessingException e) { + log.error("Failed to serialize the metrics with the exception: ", e); + } + } +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSink.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSink.java similarity index 98% rename from src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSink.java rename to src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSink.java index 81185a1d..be268dad 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSink.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSink.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import com.fasterxml.jackson.core.JsonProcessingException; import lombok.Builder; @@ -12,6 +12,7 @@ import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.LogItemTooLargeException; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; import software.amazon.awssdk.services.cloudwatchlogs.model.CreateLogGroupRequest; import software.amazon.awssdk.services.cloudwatchlogs.model.CreateLogStreamRequest; import software.amazon.awssdk.services.cloudwatchlogs.model.DataAlreadyAcceptedException; @@ -98,6 +99,12 @@ public void accept(List logItems) throws FlushException { } } + @Override + public void accept(MetricsContext context) { + // TODO + } + + void putLogEvents( List logItems, diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/ConsoleSink.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ConsoleSink.java similarity index 67% rename from src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/ConsoleSink.java rename to src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ConsoleSink.java index d92bad93..e8c724db 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/ConsoleSink.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ConsoleSink.java @@ -1,9 +1,11 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import com.fasterxml.jackson.core.JsonProcessingException; import lombok.Builder; +import lombok.extern.slf4j.Slf4j; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; import java.util.Arrays; import java.util.List; @@ -11,6 +13,7 @@ /** * Write log items to the console in JSON format. */ +@Slf4j @Builder public class ConsoleSink implements ISink { @@ -26,4 +29,15 @@ public void accept(List logItems) throws FlushException { } } } + + @Override + public void accept(MetricsContext context) { + + try { + System.out.println(context.serialize()); + } catch (JsonProcessingException e) { + log.error("Failed to serialize a MetricsContext: ", e); + } + } + } diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/Endpoint.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/Endpoint.java new file mode 100644 index 00000000..07efdd5f --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/Endpoint.java @@ -0,0 +1,62 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + + +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.net.URI; +import java.net.URISyntaxException; + + +@Slf4j +@RequiredArgsConstructor +public class Endpoint { + + static public Endpoint DEFAULT_TCP_ENDPOINT = new Endpoint("0.0.0.0", 25888, Protocol.TCP); + + @Getter + @NonNull + private final String host; + + @Getter + private final int port; + + @Getter + @NonNull + private final Protocol protocol; + + public static Endpoint fromURL(String endpoint) { + URI parsedURI = null; + + try { + parsedURI= new URI(endpoint); + } catch (URISyntaxException ex) { + log.warn("Failed to parse the endpoint: {} ", endpoint); + return DEFAULT_TCP_ENDPOINT; + } + + if (parsedURI.getHost() == null || parsedURI.getPort() < 0 || parsedURI.getScheme() == null) { + return DEFAULT_TCP_ENDPOINT; + } + + Protocol protocol; + try { + protocol = Protocol.getProtocol(parsedURI.getScheme()); + } catch (IllegalArgumentException e) { + log.warn("Unsupported protocol: {}. Would use default endpoint: {}", parsedURI.getScheme(), DEFAULT_TCP_ENDPOINT); + return DEFAULT_TCP_ENDPOINT; + } + + return new Endpoint( + parsedURI.getHost(), + parsedURI.getPort(), + protocol + ); + } + + public String toString() { + return protocol.toString().toLowerCase() + "://" + host + ":" + port; + } +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/ISink.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ISink.java similarity index 64% rename from src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/ISink.java rename to src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ISink.java index 9ef6de86..a8396040 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/ISink.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ISink.java @@ -1,7 +1,8 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; import java.util.List; @@ -15,4 +16,9 @@ public interface ISink { * @throws FlushException */ void accept(List logItems) throws FlushException; + + /** + * accept MetricsContext to sink to CloudWatch. + */ + void accept(MetricsContext context); } diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/MultiSink.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/MultiSink.java similarity index 69% rename from src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/MultiSink.java rename to src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/MultiSink.java index ad275992..a8c00a2b 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/MultiSink.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/MultiSink.java @@ -1,10 +1,11 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import lombok.Builder; import lombok.NonNull; import lombok.Singular; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; import java.util.List; @@ -25,4 +26,11 @@ public void accept(List logItems) throws FlushException { sink.accept(logItems); } } + + @Override + public void accept(MetricsContext context) { + for (ISink sink : sinks) { + sink.accept(context); + } + } } diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/Protocol.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/Protocol.java new file mode 100644 index 00000000..7fbab75a --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/Protocol.java @@ -0,0 +1,14 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + +public enum Protocol { + TCP, UDP; + + public static Protocol getProtocol(String value) { + for (Protocol protocol: values()) { + if (protocol.toString().equalsIgnoreCase(value)) { + return protocol; + } + } + throw new IllegalArgumentException(); + } +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/SinkUtilities.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SinkUtilities.java similarity index 93% rename from src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/SinkUtilities.java rename to src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SinkUtilities.java index 2e4c81f6..98bfb2ef 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/SinkUtilities.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SinkUtilities.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.CloudWatchLimits; diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SocketClient.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SocketClient.java new file mode 100644 index 00000000..b01816b7 --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SocketClient.java @@ -0,0 +1,10 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + +public interface SocketClient { + + /** + * Send a message through the Socket Client + * @param message The message to be sent + */ + void sendMessage(String message); +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SocketClientFactory.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SocketClientFactory.java new file mode 100644 index 00000000..f6eefe36 --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SocketClientFactory.java @@ -0,0 +1,11 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + +public class SocketClientFactory { + public SocketClient getClient(Endpoint endpoint) { + if (endpoint.getProtocol() == Protocol.UDP) { + //TODO: Replace with UDP client + return null; + } + return new TCPClient(endpoint); + } +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/TCPClient.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/TCPClient.java new file mode 100644 index 00000000..daf8ee3e --- /dev/null +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/TCPClient.java @@ -0,0 +1,61 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; + +@Slf4j +public class TCPClient implements SocketClient { + + private final Socket socket; + private final Endpoint endpoint; + private boolean shouldConnect = true; + + public TCPClient(Endpoint endpoint) { + socket = createSocket(); + this.endpoint = endpoint; + } + + private void connect() { + try { + socket.connect(new InetSocketAddress(endpoint.getHost(), endpoint.getPort())); + shouldConnect = false; + } catch (Exception e) { + log.error("Failed to connect to the socket due to the exception: ", e); + shouldConnect = true; + } + } + + protected Socket createSocket() { + return new Socket(); + } + + @Override + public synchronized void sendMessage(String message) { + if (socket.isClosed() || shouldConnect) { + connect(); + } + + OutputStream os; + try{ + os = socket.getOutputStream(); + } catch (IOException e) { + log.error("Failed to open output stream: ", e); + connect(); + return; + } + + try { + os.write(message.getBytes()); + } catch (IOException e) { + log.error("Could not send write request due to IOException: ", e); + connect(); + } catch (Exception e) { + log.error("Could not send write request due to Exception: ", e); + connect(); + } + } +} diff --git a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/testutils/TestSink.java b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/testutils/TestSink.java index 554d2acf..929fb9df 100644 --- a/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/testutils/TestSink.java +++ b/src/main/java/software/amazon/awssdk/services/cloudwatchlogs/emf/testutils/TestSink.java @@ -2,7 +2,8 @@ import lombok.Getter; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.ISink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.ISink; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; import java.util.ArrayList; @@ -22,4 +23,9 @@ public class TestSink implements ISink { public void accept(List logItems) throws FlushException { seenLogItems.addAll(logItems); } + + @Override + public void accept(MetricsContext context) { + // no-op + } } diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/EnvironmentConfigurationProviderTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/EnvironmentConfigurationProviderTest.java new file mode 100644 index 00000000..a990dd9a --- /dev/null +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/config/EnvironmentConfigurationProviderTest.java @@ -0,0 +1,41 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.config; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import software.amazon.awssdk.services.cloudwatchlogs.emf.environment.Environments; + + +import static org.junit.Assert.*; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({SystemWrapper.class}) +public class EnvironmentConfigurationProviderTest { + + + @Test + public void getGetConfig() { + PowerMockito.mockStatic(SystemWrapper.class); + + putEnv("AWS_EMF_SERVICE_NAME", "TestServiceName"); + putEnv("AWS_EMF_SERVICE_TYPE", "TestServiceType"); + putEnv("AWS_EMF_LOG_GROUP_NAME", "TestLogGroup"); + putEnv("AWS_EMF_LOG_STREAM_NAME", "TestLogStream"); + putEnv("AWS_EMF_AGENT_ENDPOINT", "Endpoint"); + putEnv("AWS_EMF_ENVIRONMENT", "Agent"); + Configuration config = EnvironmentConfigurationProvider.getConfig(); + + assertEquals(config.getServiceName().get(), "TestServiceName"); + assertEquals(config.getServiceType().get(), "TestServiceType"); + assertEquals(config.getLogGroupName().get(), "TestLogGroup"); + assertEquals(config.getLogStreamName().get(), "TestLogStream"); + assertEquals(config.agentEndpoint.get(), "Endpoint"); + assertEquals(config.getEnvironmentOverride(), Environments.Agent); + } + + private void putEnv(String key, String value) { + PowerMockito.when(SystemWrapper.getenv(key)).thenReturn(value); + } +} diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/DefaultEnvironmentTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/DefaultEnvironmentTest.java new file mode 100644 index 00000000..8ca3fa42 --- /dev/null +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/environment/DefaultEnvironmentTest.java @@ -0,0 +1,78 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.environment; + +import org.junit.Before; +import org.junit.Test; +import software.amazon.awssdk.services.cloudwatchlogs.emf.config.Configuration; + +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultEnvironmentTest { + private DefaultEnvironment environment; + private Configuration configuration; + + @Before + public void setUp() { + configuration = mock(Configuration.class); + environment = new DefaultEnvironment(configuration); + } + + @Test + public void testGetName() { + String serviceName = "TestService"; + when(configuration.getServiceName()).thenReturn(Optional.of(serviceName)); + assertEquals(environment.getName(), serviceName); + } + + @Test + public void testGetNameWhenNotConfigured() { + when(configuration.getServiceName()).thenReturn(Optional.empty()); + assertEquals(environment.getName(), "Unknown"); + } + + @Test + public void testGetType() { + String serviceType= "TestServiceType"; + when(configuration.getServiceType()).thenReturn(Optional.of(serviceType)); + assertEquals(environment.getType(), serviceType); + } + + @Test + public void testGetTypeWhenNotConfigured() { + when(configuration.getServiceType()).thenReturn(Optional.empty()); + assertEquals(environment.getType(), "Unknown"); + } + + @Test + public void testGetLogStreamName() { + String logStream = "TestLogStream"; + when(configuration.getLogStreamName()).thenReturn(Optional.of(logStream)); + assertEquals(environment.getLogStreamName(), logStream); + } + + @Test + public void testGetLogStreamNameWhenNotConfigured() { + String serviceName = "TestService"; + when(configuration.getLogStreamName()).thenReturn(Optional.empty()); + when(configuration.getServiceName()).thenReturn(Optional.of(serviceName)); + assertEquals(environment.getLogStreamName(), serviceName + "-stream"); + } + + @Test + public void testGetLogGroupName() { + String logGroup = "TestLogGroup"; + when(configuration.getLogGroupName()).thenReturn(Optional.of(logGroup)); + assertEquals(environment.getLogGroupName(), logGroup); + } + + @Test + public void testGetLogLogNameWhenNotConfigured() { + String serviceName = "TestService"; + when(configuration.getLogGroupName()).thenReturn(Optional.empty()); + when(configuration.getServiceName()).thenReturn(Optional.of(serviceName)); + assertEquals(environment.getLogGroupName(), serviceName + "-metrics"); + } +} diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/EMFLoggerTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/EMFLoggerTest.java index 87e66cc4..bcd82949 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/EMFLoggerTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/EMFLoggerTest.java @@ -2,8 +2,8 @@ import org.junit.Test; import org.mockito.Mockito; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.ConsoleSink; -import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks.MultiSink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.ConsoleSink; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.MultiSink; import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; import software.amazon.awssdk.services.cloudwatchlogs.emf.testutils.EMFTestUtilities; import software.amazon.awssdk.services.cloudwatchlogs.emf.testutils.TestSink; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/MetricsLoggerTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/MetricsLoggerTest.java new file mode 100644 index 00000000..09139065 --- /dev/null +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/MetricsLoggerTest.java @@ -0,0 +1,146 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.logger; + +import org.junit.Before; +import org.junit.Test; +import software.amazon.awssdk.services.cloudwatchlogs.emf.config.Configuration; +import software.amazon.awssdk.services.cloudwatchlogs.emf.config.EnvironmentConfigurationProvider; +import software.amazon.awssdk.services.cloudwatchlogs.emf.environment.Environment; +import software.amazon.awssdk.services.cloudwatchlogs.emf.environment.EnvironmentProvider; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.DimensionSet; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; +import software.amazon.awssdk.services.cloudwatchlogs.emf.sinks.SinkShunt; + +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MetricsLoggerTest { + private MetricsLogger logger; + private EnvironmentProvider envProvider; + private SinkShunt sink; + private Environment environment; + + @Before + public void setUp() { + envProvider = mock(EnvironmentProvider.class); + environment = mock(Environment.class); + sink = new SinkShunt(); + + when(envProvider.resolveEnvironment()).thenReturn(environment); + when(environment.getSink()).thenReturn(sink); + logger = new MetricsLogger(envProvider); + } + + @Test + public void testPutProperty() { + String propertyName = "Property"; + String propertyValue = "PropValue"; + logger.putProperty(propertyName, propertyValue); + logger.flush(); + + assertEquals(sink.getContext().getProperty(propertyName), propertyValue); + } + + @Test + public void testPutDimension() { + String dimensionName = "dim"; + String dimensionValue = "dimValue"; + logger.putDimensions(DimensionSet.of(dimensionName, dimensionValue)); + logger.flush(); + + assertEquals(sink.getContext().getDimensions().size(), 1); + assertEquals(sink.getContext().getDimensions().get(0).getDimensionValue(dimensionName), dimensionValue); + } + + @Test + public void testOverrideDefaultDimensions() { + String dimensionName = "dim"; + String dimensionValue = "dimValue"; + String defaultDimName = "defaultDim"; + String defaultDimValue = "defaultDimValue"; + + MetricsContext metricsContext = new MetricsContext(); + metricsContext.setDefaultDimensions(DimensionSet.of(defaultDimName, defaultDimValue)); + metricsContext.setDimensions(DimensionSet.of(dimensionName, dimensionValue)); + logger = new MetricsLogger(envProvider, metricsContext); + logger.setDimensions(DimensionSet.of(dimensionName, dimensionValue)); + logger.flush(); + + assertEquals(sink.getContext().getDimensions().size(), 1); + assertEquals(sink.getContext().getDimensions().get(0).getDimensionValue(defaultDimName), null); + } + + @Test + public void testOverridePreviousDimensions() { + + String dimensionName = "dim"; + String dimensionValue = "dimValue"; + logger.putDimensions(DimensionSet.of("foo", "bar")); + logger.setDimensions(DimensionSet.of(dimensionName, dimensionValue)); + logger.flush(); + + assertEquals(sink.getContext().getDimensions().size(), 1); + assertEquals(sink.getContext().getDimensions().get(0).getDimensionKeys().size(), 1); + assertEquals(sink.getContext().getDimensions().get(0).getDimensionValue(dimensionName), dimensionValue); + } + + @Test + public void testSetNamespace() { + + String namespace = "testNamespace"; + logger.setNamespace(namespace); + logger.flush(); + + assertEquals(sink.getContext().getNamespace(), namespace); + } + + @Test + public void testFlushWithConfiguredServiceName() { + String serviceName = "TestServiceName"; + when(environment.getName()).thenReturn(serviceName); + logger.flush(); + + expectDimension("ServiceName", serviceName); + } + + @Test + public void testFlushWithConfiguredServiceType() { + String serviceType = "TestServiceType"; + when(environment.getType()).thenReturn(serviceType); + logger.flush(); + + expectDimension("ServiceType", serviceType); + } + + + @Test + public void testFlushWithConfiguredLogGroup() { + String logGroup = "MyLogGroup"; + when(environment.getLogGroupName()).thenReturn(logGroup); + logger.flush(); + + expectDimension("LogGroup", logGroup); + } + + @Test + public void testFlushWithDefaultDimensionDefined() { + MetricsContext metricsContext = new MetricsContext(); + metricsContext.setDefaultDimensions(DimensionSet.of("foo", "bar")); + logger = new MetricsLogger(envProvider, metricsContext); + String logGroup = "MyLogGroup"; + when(environment.getLogGroupName()).thenReturn(logGroup); + logger.flush(); + + expectDimension("foo", "bar"); + expectDimension("LogGroup", null); + } + + private void expectDimension(String dimension, String value) { + List dimensions = sink.getContext().getDimensions(); + assertEquals(dimensions.size(), 1); + assertEquals(dimensions.get(0).getDimensionValue(dimension), value); + } +} diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/RootNodeTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/RootNodeTest.java index 80a1f305..bf91de54 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/RootNodeTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/model/RootNodeTest.java @@ -88,7 +88,7 @@ public void testSerializeRootNode() throws JsonProcessingException { mc.putProperty("Property", "PropertyValue"); ObjectMapper objectMapper = new ObjectMapper(); - String emf_log = objectMapper.writeValueAsString(mc.getRootNode()); + String emf_log = mc.serialize(); Map emf_map = objectMapper.readValue(emf_log, new TypeReference>(){}); assertEquals(emf_map.keySet().size(), 5); @@ -101,4 +101,14 @@ public void testSerializeRootNode() throws JsonProcessingException { assertTrue(metadata.containsKey("Timestamp")); assertTrue(metadata.containsKey("CloudWatchMetrics")); } + + @Test + public void testSerializeRootNodeWithoutAnyMetrics() throws JsonProcessingException { + RootNode root = new RootNode(); + String property = "foo"; + String value = "bar"; + root.putProperty(property, value); + + assertEquals(root.serialize(), "{\"foo\":\"bar\"}"); + } } diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/AgentSinkTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/AgentSinkTest.java new file mode 100644 index 00000000..2fbdb6c3 --- /dev/null +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/AgentSinkTest.java @@ -0,0 +1,86 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Before; +import org.junit.Test; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; + +import java.util.Map; + +import static junit.framework.TestCase.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AgentSinkTest { + + private SocketClientFactory factory; + private TestClient client; + + class TestClient implements SocketClient { + + private String message; + + @Override + public void sendMessage(String message) { + this.message = message; + } + + public String getMessage() { + return this.message; + } + } + + @Before + public void setUp() { + factory = mock(SocketClientFactory.class); + + client = new TestClient(); + when(factory.getClient(any())).thenReturn(client); + } + + @Test + public void testAccept() throws JsonProcessingException { + String logGroupName = "TestLogGroup"; + String logStreamName = "TestLogStream"; + + MetricsContext mc = new MetricsContext(); + + AgentSink sink = new AgentSink( + logGroupName, + logStreamName, + Endpoint.DEFAULT_TCP_ENDPOINT, + factory + ); + + sink.accept(mc); + + ObjectMapper objectMapper = new ObjectMapper(); + Map emf_map = objectMapper.readValue(client.getMessage(), new TypeReference>(){}); + + + assertEquals(emf_map.get("LogGroupName"), logGroupName); + assertEquals(emf_map.get("LogStreamName"), logStreamName); + } + + @Test + public void testEmptyLogGroupName() throws JsonProcessingException { + String logGroupName = ""; + AgentSink sink = new AgentSink( + logGroupName, + null, + Endpoint.DEFAULT_TCP_ENDPOINT, + factory + ); + + sink.accept(new MetricsContext()); + ObjectMapper objectMapper = new ObjectMapper(); + Map emf_map = objectMapper.readValue(client.getMessage(), new TypeReference>(){}); + + assertFalse(emf_map.containsKey("LogGroupName")); + assertFalse(emf_map.containsKey("LogStreamName")); + + } +} diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkItemTooBigTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkItemTooBigTest.java similarity index 98% rename from src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkItemTooBigTest.java rename to src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkItemTooBigTest.java index d2f98efd..41dc1613 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkItemTooBigTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkItemTooBigTest.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import org.junit.Test; import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkNotInOrderTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkNotInOrderTest.java similarity index 95% rename from src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkNotInOrderTest.java rename to src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkNotInOrderTest.java index 2668bd61..65ff82db 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkNotInOrderTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkNotInOrderTest.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import org.junit.Test; import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkNumItemsBatchingTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkNumItemsBatchingTest.java similarity index 94% rename from src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkNumItemsBatchingTest.java rename to src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkNumItemsBatchingTest.java index 82709d31..ff19b865 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkNumItemsBatchingTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkNumItemsBatchingTest.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import org.junit.After; import org.junit.Before; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkSizeBatchingTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkSizeBatchingTest.java similarity index 95% rename from src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkSizeBatchingTest.java rename to src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkSizeBatchingTest.java index acd32f37..3b4424fc 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkSizeBatchingTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkSizeBatchingTest.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import org.junit.After; import org.junit.Before; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkTest.java similarity index 99% rename from src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkTest.java rename to src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkTest.java index 0c6497dd..b7eb1dd8 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkTest.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Test; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkTestBase.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkTestBase.java similarity index 99% rename from src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkTestBase.java rename to src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkTestBase.java index 3f20b102..257ea675 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkTestBase.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkTestBase.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import com.fasterxml.jackson.core.JsonProcessingException; import org.mockito.Mockito; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkTooOldTooNewTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkTooOldTooNewTest.java similarity index 98% rename from src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkTooOldTooNewTest.java rename to src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkTooOldTooNewTest.java index e6b9c38b..823cc40f 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/CloudWatchLogsClientSinkTooOldTooNewTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/CloudWatchLogsClientSinkTooOldTooNewTest.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Test; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/ConsoleSinkTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ConsoleSinkTest.java similarity index 95% rename from src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/ConsoleSinkTest.java rename to src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ConsoleSinkTest.java index 1c6e046d..a0701768 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/ConsoleSinkTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ConsoleSinkTest.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.Test; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/EndpointTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/EndpointTest.java new file mode 100644 index 00000000..968e61a4 --- /dev/null +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/EndpointTest.java @@ -0,0 +1,34 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class EndpointTest { + + @Test + public void testParseTCPEndpoint() { + String tcpEndpoint = "tcp://173.9.0.12:2580"; + Endpoint endpoint = Endpoint.fromURL(tcpEndpoint); + + assertEquals(endpoint.toString(), tcpEndpoint); + } + + @Test + public void testParseUDPEndpoint() { + String tcpEndpoint = "udp://173.9.0.12:2580"; + Endpoint endpoint = Endpoint.fromURL(tcpEndpoint); + + assertEquals(endpoint.toString(), tcpEndpoint); + } + + @Test + public void testReturnDefaultEndpointForInvalidURI() { + String unsupportedEndpoint = "http://173.9.0.12:2580"; + Endpoint endpoint = Endpoint.fromURL(unsupportedEndpoint); + Endpoint endpointFromEmptyString = Endpoint.fromURL(""); + + assertEquals(endpoint, Endpoint.DEFAULT_TCP_ENDPOINT); + assertEquals(endpointFromEmptyString, Endpoint.DEFAULT_TCP_ENDPOINT); + } +} diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/MultiSinkTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/MultiSinkTest.java similarity index 96% rename from src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/MultiSinkTest.java rename to src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/MultiSinkTest.java index 8b034d51..450b6915 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/MultiSinkTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/MultiSinkTest.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import org.junit.Test; import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ProtocolTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ProtocolTest.java new file mode 100644 index 00000000..8eb41be3 --- /dev/null +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/ProtocolTest.java @@ -0,0 +1,33 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ProtocolTest { + + @Test + public void testParseTCP() { + assertEquals(Protocol.getProtocol("TCP"), Protocol.TCP); + assertEquals(Protocol.getProtocol("tcp"), Protocol.TCP); + assertEquals(Protocol.getProtocol("Tcp"), Protocol.TCP); + } + + @Test + public void testParseUDP() { + assertEquals(Protocol.getProtocol("UDP"), Protocol.UDP); + assertEquals(Protocol.getProtocol("udp"), Protocol.UDP); + assertEquals(Protocol.getProtocol("Udp"), Protocol.UDP); + } + + @Test(expected = IllegalArgumentException.class) + public void testThrowExceptionForUnsupportedProtocol() { + Protocol.valueOf("http"); + } + + @Test(expected = IllegalArgumentException.class) + public void testThrowExceptionHttps() { + Protocol.valueOf("https"); + } + +} diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SinkShunt.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SinkShunt.java new file mode 100644 index 00000000..877032ca --- /dev/null +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SinkShunt.java @@ -0,0 +1,27 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + +import software.amazon.awssdk.services.cloudwatchlogs.emf.logger.FlushException; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.EMFLogItem; +import software.amazon.awssdk.services.cloudwatchlogs.emf.model.MetricsContext; + +import java.util.List; + +public class SinkShunt implements ISink { + + private MetricsContext context; + + @Override + public void accept(List logItems) throws FlushException { + + } + + @Override + public void accept(MetricsContext context) { + this.context = context; + } + + public MetricsContext getContext() { + return context; + } + +} diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/SinkUtilitiesTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SinkUtilitiesTest.java similarity index 75% rename from src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/SinkUtilitiesTest.java rename to src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SinkUtilitiesTest.java index 8baa9a85..79bd31b8 100644 --- a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/logger/sinks/SinkUtilitiesTest.java +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/SinkUtilitiesTest.java @@ -1,4 +1,4 @@ -package software.amazon.awssdk.services.cloudwatchlogs.emf.logger.sinks; +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; import org.junit.Test; diff --git a/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/TCPClientTest.java b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/TCPClientTest.java new file mode 100644 index 00000000..55691d7e --- /dev/null +++ b/src/test/java/software/amazon/awssdk/services/cloudwatchlogs/emf/sinks/TCPClientTest.java @@ -0,0 +1,35 @@ +package software.amazon.awssdk.services.cloudwatchlogs.emf.sinks; + +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.Socket; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class TCPClientTest { + + @Test + public void testSendMessage() throws IOException { + Socket socket = mock(Socket.class); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + when(socket.getOutputStream()).thenReturn(bos); + doNothing().when(socket).connect(any()); + Endpoint endpoint = Endpoint.DEFAULT_TCP_ENDPOINT; + + TCPClient client = new TCPClient(endpoint) { + @Override + protected Socket createSocket() { + return socket; + } + }; + + String message = "Test message"; + client.sendMessage(message); + + assertEquals(bos.toString(), message); + } +}