Skip to content

Commit 2713006

Browse files
PaulJSchellenbergPaul Schellenberg
and
Paul Schellenberg
authored
Allow STDOUT overrides for AgentBasedEnvironments (#147)
* Allow for STDOUT overrides when using an AgentBasedEnvironment * replace double space indents with 4 space indents * read WRITE_TO_STDOUT env var to the Configuration object * add AWS_EMF_ prefix to the stdout docs * add blump in README for how the new environment variable will work * run spotlessApply --------- Co-authored-by: Paul Schellenberg <[email protected]>
1 parent 6b97f76 commit 2713006

File tree

8 files changed

+167
-20
lines changed

8 files changed

+167
-20
lines changed

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,24 @@ config.setAgentEndpoint("udp://127.0.0.1:1000");
373373
AWS_EMF_AGENT_ENDPOINT="udp://127.0.0.1:1000"
374374
```
375375

376+
**WriteToStdout**: For agent-based platforms, setting this configuration to `true` will make the `MetricsLogger` write to `stdout` rather than sending them to the agent. The default value for this configuration is `false`. This configuration has no effect for non-agent-based platforms.
377+
378+
If an `EnvironmentOverride` is provided, this configuration will apply to the overriden environment if the environment is an agent-based platform
379+
380+
Example:
381+
382+
```java
383+
// in process
384+
import software.amazon.cloudwatchlogs.emf.config.Configuration;
385+
import software.amazon.cloudwatchlogs.emf.config.EnvironmentConfigurationProvider;
386+
387+
Configuration config = EnvironmentConfigurationProvider.getConfig();
388+
config.setShouldWriteToStdout(true);
389+
390+
// environment
391+
AWS_EMF_WRITE_TO_STDOUT="true"
392+
```
393+
376394
## Thread-safety
377395

378396
### Internal Synchronization

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ With Docker images, using the `awslogs` log driver will send your container logs
3838
## ECS and Fargate
3939

4040
With ECS and Fargate, you can use the `awsfirelens` (recommended) or `awslogs` log driver to have your logs sent to CloudWatch Logs on your behalf. After configuring the options for your preferred log driver, you may write your EMF logs to STDOUT and they will be processed.
41+
To write your EMF logs to STDOUT, set the environment variable `AWS_EMF_WRITE_TO_STDOUT=true`
4142

4243
[`awsfirelens` documentation](https://github.com/aws/amazon-cloudwatch-logs-for-fluent-bit)
4344

src/main/java/software/amazon/cloudwatchlogs/emf/config/Configuration.java

+6
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public class Configuration {
5959
/** Queue length for asynchronous sinks. */
6060
@Setter @Getter int asyncBufferSize = Constants.DEFAULT_ASYNC_BUFFER_SIZE;
6161

62+
@Setter private boolean shouldWriteToStdout;
63+
6264
public Optional<String> getServiceName() {
6365
return getStringOptional(serviceName);
6466
}
@@ -92,4 +94,8 @@ private Optional<String> getStringOptional(String value) {
9294
}
9395
return Optional.of(value);
9496
}
97+
98+
public boolean shouldWriteToStdout() {
99+
return shouldWriteToStdout;
100+
}
95101
}

src/main/java/software/amazon/cloudwatchlogs/emf/config/ConfigurationKeys.java

+1
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ public class ConfigurationKeys {
2828
public static final String AGENT_ENDPOINT = "AGENT_ENDPOINT";
2929
public static final String ENVIRONMENT_OVERRIDE = "ENVIRONMENT";
3030
public static final String ASYNC_BUFFER_SIZE = "ASYNC_BUFFER_SIZE";
31+
public static final String WRITE_TO_STDOUT = "WRITE_TO_STDOUT";
3132
}

src/main/java/software/amazon/cloudwatchlogs/emf/config/EnvironmentConfigurationProvider.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ static Configuration createConfig() {
4242
getEnvVar(ConfigurationKeys.AGENT_ENDPOINT),
4343
getEnvironmentOverride(),
4444
getIntOrDefault(
45-
ConfigurationKeys.ASYNC_BUFFER_SIZE, Constants.DEFAULT_ASYNC_BUFFER_SIZE));
45+
ConfigurationKeys.ASYNC_BUFFER_SIZE, Constants.DEFAULT_ASYNC_BUFFER_SIZE),
46+
Boolean.parseBoolean(getEnvVar(ConfigurationKeys.WRITE_TO_STDOUT)));
4647
}
4748

4849
private static Environments getEnvironmentOverride() {

src/main/java/software/amazon/cloudwatchlogs/emf/environment/AgentBasedEnvironment.java

+24-19
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import software.amazon.cloudwatchlogs.emf.Constants;
2222
import software.amazon.cloudwatchlogs.emf.config.Configuration;
2323
import software.amazon.cloudwatchlogs.emf.sinks.AgentSink;
24+
import software.amazon.cloudwatchlogs.emf.sinks.ConsoleSink;
2425
import software.amazon.cloudwatchlogs.emf.sinks.Endpoint;
2526
import software.amazon.cloudwatchlogs.emf.sinks.ISink;
2627
import software.amazon.cloudwatchlogs.emf.sinks.SocketClientFactory;
@@ -67,27 +68,31 @@ public String getLogStreamName() {
6768
@Override
6869
public ISink getSink() {
6970
if (sink == null) {
70-
Endpoint endpoint;
71-
if (config.getAgentEndpoint().isPresent()) {
72-
endpoint = Endpoint.fromURL(config.getAgentEndpoint().get());
71+
if (config.shouldWriteToStdout()) {
72+
sink = new ConsoleSink();
7373
} else {
74-
log.info(
75-
"Endpoint is not defined. Using default: {}",
76-
Endpoint.DEFAULT_TCP_ENDPOINT);
77-
endpoint = Endpoint.DEFAULT_TCP_ENDPOINT;
74+
Endpoint endpoint;
75+
if (config.getAgentEndpoint().isPresent()) {
76+
endpoint = Endpoint.fromURL(config.getAgentEndpoint().get());
77+
} else {
78+
log.info(
79+
"Endpoint is not defined. Using default: {}",
80+
Endpoint.DEFAULT_TCP_ENDPOINT);
81+
endpoint = Endpoint.DEFAULT_TCP_ENDPOINT;
82+
}
83+
sink =
84+
new AgentSink(
85+
getLogGroupName(),
86+
getLogStreamName(),
87+
endpoint,
88+
new SocketClientFactory(),
89+
config.getAsyncBufferSize(),
90+
() ->
91+
new FibonacciRetryStrategy(
92+
Constants.MIN_BACKOFF_MILLIS,
93+
Constants.MAX_BACKOFF_MILLIS,
94+
Constants.MAX_BACKOFF_JITTER));
7895
}
79-
sink =
80-
new AgentSink(
81-
getLogGroupName(),
82-
getLogStreamName(),
83-
endpoint,
84-
new SocketClientFactory(),
85-
config.getAsyncBufferSize(),
86-
() ->
87-
new FibonacciRetryStrategy(
88-
Constants.MIN_BACKOFF_MILLIS,
89-
Constants.MAX_BACKOFF_MILLIS,
90-
Constants.MAX_BACKOFF_JITTER));
9196
}
9297
return sink;
9398
}

src/test/java/software/amazon/cloudwatchlogs/emf/config/EnvironmentConfigurationProviderTest.java

+12
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public void getGetConfig() {
4040
putEnv("AWS_EMF_AGENT_ENDPOINT", "Endpoint");
4141
putEnv("AWS_EMF_ENVIRONMENT", "Agent");
4242
putEnv("AWS_EMF_ASYNC_BUFFER_SIZE", "9999");
43+
putEnv("AWS_EMF_WRITE_TO_STDOUT", "true");
4344

4445
Configuration config = EnvironmentConfigurationProvider.createConfig();
4546

@@ -50,6 +51,7 @@ public void getGetConfig() {
5051
assertEquals("Endpoint", config.getAgentEndpoint().get());
5152
assertEquals(Environments.Agent, config.getEnvironmentOverride());
5253
assertEquals(9999, config.getAsyncBufferSize());
54+
assertTrue(config.shouldWriteToStdout());
5355
}
5456

5557
@Test
@@ -59,10 +61,20 @@ public void invalidEnvironmentValuesFallbackToExpectedDefaults() {
5961

6062
// act
6163
putEnv("AWS_EMF_ASYNC_BUFFER_SIZE", "NaN");
64+
putEnv("AWS_EMF_WRITE_TO_STDOUT", "notABool");
6265

6366
// assert
6467
Configuration config = EnvironmentConfigurationProvider.createConfig();
6568
assertEquals(100, config.getAsyncBufferSize());
69+
assertFalse(config.shouldWriteToStdout());
70+
}
71+
72+
@Test
73+
public void emptyEnvironmentValuesFallbackToExpectedDefaults() {
74+
// assert
75+
Configuration config = EnvironmentConfigurationProvider.createConfig();
76+
assertEquals(100, config.getAsyncBufferSize());
77+
assertFalse(config.shouldWriteToStdout());
6678
}
6779

6880
private void putEnv(String key, String value) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package software.amazon.cloudwatchlogs.emf.environment;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.powermock.api.mockito.PowerMockito.mock;
5+
6+
import org.junit.Before;
7+
import org.junit.Test;
8+
import org.junit.runner.RunWith;
9+
import org.powermock.api.mockito.PowerMockito;
10+
import org.powermock.core.classloader.annotations.PrepareForTest;
11+
import org.powermock.modules.junit4.PowerMockRunner;
12+
import software.amazon.cloudwatchlogs.emf.config.Configuration;
13+
import software.amazon.cloudwatchlogs.emf.config.SystemWrapper;
14+
import software.amazon.cloudwatchlogs.emf.model.MetricsContext;
15+
import software.amazon.cloudwatchlogs.emf.sinks.AgentSink;
16+
import software.amazon.cloudwatchlogs.emf.sinks.ConsoleSink;
17+
import software.amazon.cloudwatchlogs.emf.sinks.Endpoint;
18+
import software.amazon.cloudwatchlogs.emf.sinks.ISink;
19+
20+
@RunWith(PowerMockRunner.class)
21+
@PrepareForTest({SystemWrapper.class, AgentBasedEnvironment.class})
22+
public class AgentBasedEnvironmentTest {
23+
public static class AgentBasedEnvironmentTestImplementation extends AgentBasedEnvironment {
24+
protected AgentBasedEnvironmentTestImplementation(Configuration config) {
25+
super(config);
26+
}
27+
28+
@Override
29+
public boolean probe() {
30+
return false;
31+
}
32+
33+
@Override
34+
public String getType() {
35+
return null;
36+
}
37+
38+
@Override
39+
public void configureContext(MetricsContext context) {}
40+
}
41+
42+
private Configuration configuration;
43+
44+
@Before
45+
public void setup() {
46+
this.configuration = new Configuration();
47+
}
48+
49+
@Test
50+
public void testGetSinkWithDefaultEndpoint() throws Exception {
51+
AgentSink mockedSink = mock(AgentSink.class);
52+
PowerMockito.whenNew(AgentSink.class)
53+
.withAnyArguments()
54+
.then(
55+
invocation -> {
56+
Endpoint endpoint = invocation.getArgument(2);
57+
assertEquals(Endpoint.DEFAULT_TCP_ENDPOINT, endpoint);
58+
return mockedSink;
59+
});
60+
61+
AgentBasedEnvironment env = new AgentBasedEnvironmentTestImplementation(configuration);
62+
ISink sink = env.getSink();
63+
64+
assertEquals(mockedSink, sink);
65+
}
66+
67+
@Test
68+
public void testGetSinkWithConfiguredEndpoint() throws Exception {
69+
String endpointUrl = "http://configured-endpoint:1234";
70+
configuration.setAgentEndpoint(endpointUrl);
71+
AgentSink mockedSink = mock(AgentSink.class);
72+
PowerMockito.whenNew(AgentSink.class)
73+
.withAnyArguments()
74+
.then(
75+
invocation -> {
76+
Endpoint endpoint = invocation.getArgument(2);
77+
assertEquals(Endpoint.fromURL(endpointUrl), endpoint);
78+
return mockedSink;
79+
});
80+
81+
AgentBasedEnvironment env = new AgentBasedEnvironmentTestImplementation(configuration);
82+
ISink sink = env.getSink();
83+
84+
assertEquals(mockedSink, sink);
85+
}
86+
87+
@Test
88+
public void testGetSinkOverrideToStdOut() {
89+
configuration.setShouldWriteToStdout(true);
90+
91+
AgentBasedEnvironment env = new AgentBasedEnvironmentTestImplementation(configuration);
92+
ISink sink = env.getSink();
93+
94+
assertEquals(ConsoleSink.class, sink.getClass());
95+
}
96+
97+
@Test
98+
public void testGetSinkOverrideToStdOutFailFastOnImproperOverride() throws Exception {
99+
configuration.setShouldWriteToStdout(false);
100+
101+
testGetSinkWithDefaultEndpoint();
102+
}
103+
}

0 commit comments

Comments
 (0)