Skip to content

Commit f718aac

Browse files
authored
Allow overriding environment and provide LocalEnvironment (#19)
* Allow overriding environment and provide LocalEnvironment - adds LocalEnvironment - renames LambdaSink to ConsoleSink - short-circuits environment discovery if override is specified - adds environment override * ensure environment resolution only happens once * update documentation * add environment detection tests and fix cache issue * if invalid environment configured, fallback to discovery * docs(readme): add link to configuration section
1 parent 4e3b12e commit f718aac

14 files changed

+374
-34
lines changed

README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Generate CloudWatch Metrics embedded within structured log events. The embedded
99
* [Installation](#installation)
1010
* [Usage](#usage)
1111
* [API](#api)
12+
* [Configuration](#configuration)
1213
* [Examples](#examples)
1314
* [Development](#development)
1415

@@ -193,7 +194,7 @@ setNamespace("MyApplication");
193194

194195
Flushes the current MetricsContext to the configured sink and resets all properties, dimensions and metric values. The namespace and default dimensions will be preserved across flushes.
195196

196-
### Configuration
197+
## Configuration
197198

198199
All configuration values can be set using environment variables with the prefix (`AWS_EMF_`). Configuration should be performed as close to application start up as possible.
199200

@@ -268,6 +269,52 @@ Configuration.logStreamName = "LogStreamName";
268269
AWS_EMF_LOG_STREAM_NAME=LogStreamName
269270
```
270271

272+
**AgentEndpoint**: For agent-based platforms, you may optionally configure the endpoint to reach the agent on.
273+
274+
Example:
275+
276+
```js
277+
// in process
278+
const { Configuration } = require("aws-embedded-metrics");
279+
Configuration.agentEndpoint = "udp://127.0.0.1:1000";
280+
281+
// environment
282+
AWS_EMF_AGENT_ENDPOINT="udp://127.0.0.1:1000"
283+
```
284+
285+
**EnvironmentOverride**: Short circuit auto-environment detection by explicitly defining how events should be sent.
286+
287+
Valid values include:
288+
289+
- Local: no decoration and sends over stdout
290+
- Lambda: decorates logs with Lambda metadata and sends over stdout
291+
- Agent: no decoration and sends over TCP
292+
- EC2: decorates logs with EC2 metadata and sends over TCP
293+
294+
Example:
295+
296+
```js
297+
// in process
298+
const { Configuration } = require("aws-embedded-metrics");
299+
Configuration.environmentOverride = "Local";
300+
301+
// environment
302+
AWS_EMF_AGENT_ENDPOINT=Local
303+
```
304+
305+
**EnableDebugLogging**: Enable debug logging for the library. If the library is not behaving as expected, you can set this to true to log to console.
306+
307+
Example:
308+
309+
```js
310+
// in process
311+
const { Configuration } = require("aws-embedded-metrics");
312+
Configuration.debuggingLoggingEnabled = true;
313+
314+
// environment
315+
AWS_EMF_ENABLE_DEBUG_LOGGING=true
316+
```
317+
271318
## Examples
272319

273320
Check out the [examples](https://github.com/awslabs/aws-embedded-metrics-node/tree/master/examples) directory to get started.

src/config/EnvironmentConfigurationProvider.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
import { IConfiguration } from './IConfiguration';
17+
import Environments from "../environment/Environments";
1718

1819
const ENV_VAR_PREFIX = 'AWS_EMF';
1920

@@ -24,6 +25,7 @@ enum ConfigKeys {
2425
SERVICE_NAME = 'SERVICE_NAME',
2526
SERVICE_TYPE = 'SERVICE_TYPE',
2627
AGENT_ENDPOINT = 'AGENT_ENDPOINT',
28+
ENVIRONMENT_OVERRIDE = 'ENVIRONMENT'
2729
}
2830

2931
export class EnvironmentConfigurationProvider {
@@ -37,6 +39,7 @@ export class EnvironmentConfigurationProvider {
3739
this.getEnvVariable(ConfigKeys.SERVICE_NAME) || this.getEnvVariableWithoutPrefix(ConfigKeys.SERVICE_NAME),
3840
serviceType:
3941
this.getEnvVariable(ConfigKeys.SERVICE_TYPE) || this.getEnvVariableWithoutPrefix(ConfigKeys.SERVICE_TYPE),
42+
environmentOverride: this.getEnvironmentOverride()
4043
};
4144
}
4245

@@ -52,4 +55,13 @@ export class EnvironmentConfigurationProvider {
5255
const configValue = this.getEnvVariable(configKey);
5356
return !configValue ? fallback : configValue.toLowerCase() === 'true';
5457
}
58+
59+
getEnvironmentOverride(): Environments {
60+
const overrideValue = this.getEnvVariable(ConfigKeys.ENVIRONMENT_OVERRIDE);
61+
const environment = Environments[overrideValue as keyof typeof Environments];
62+
if (environment === undefined) {
63+
return Environments.Unknown;
64+
}
65+
return environment;
66+
}
5567
}

src/config/IConfiguration.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* limitations under the License.
1414
*/
1515

16+
import Environments from "../environment/Environments";
17+
1618
export interface IConfiguration {
1719
/**
1820
* Whether or not internal logging should be enabled.
@@ -45,4 +47,14 @@ export interface IConfiguration {
4547
* The endpoint to use to connect to the CloudWatch Agent
4648
*/
4749
agentEndpoint: string | undefined;
50+
51+
/**
52+
* Environment override. This will short circuit auto-environment detection.
53+
* Valid values include:
54+
* - Local: no decoration and sends over stdout
55+
* - Lambda: decorates logs with Lambda metadata and sends over stdout
56+
* - Agent: no decoration and sends over TCP
57+
* - EC2: decorates logs with EC2 metadata and sends over TCP
58+
*/
59+
environmentOverride: Environments | undefined;
4860
}

src/config/__tests__/EnvironmentConfigurationProvider.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as faker from 'faker';
2+
import Environments from '../../environment/Environments';
23

34
beforeEach(() => {
45
jest.resetModules();
@@ -171,3 +172,38 @@ test('can set agent endpoint from environment', () => {
171172
const result = config.agentEndpoint;
172173
expect(result).toBe(expectedValue);
173174
});
175+
176+
test('can set environment override from environment', () => {
177+
// arrange
178+
const expectedValue = "Local"
179+
process.env.AWS_EMF_ENVIRONMENT = expectedValue;
180+
181+
// act
182+
const config = getConfig();
183+
184+
// assert
185+
const result = config.environmentOverride;
186+
expect(result).toBe(Environments.Local);
187+
});
188+
189+
test('if environment override is not set, default to unknown', () => {
190+
// arrange
191+
process.env.AWS_EMF_ENVIRONMENT = "";
192+
// act
193+
const config = getConfig();
194+
195+
// assert
196+
const result = config.environmentOverride;
197+
expect(result).toBe(Environments.Unknown);
198+
});
199+
200+
test('if environment override cannot be parsed, default to unknown', () => {
201+
// arrange
202+
process.env.AWS_EMF_ENVIRONMENT = faker.random.alphaNumeric();
203+
// act
204+
const config = getConfig();
205+
206+
// assert
207+
const result = config.environmentOverride;
208+
expect(result).toBe(Environments.Unknown);
209+
});

src/environment/EnvironmentDetector.ts

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,49 +18,86 @@ import { DefaultEnvironment } from './DefaultEnvironment';
1818
import { EC2Environment } from './EC2Environment';
1919
import { IEnvironment } from './IEnvironment';
2020
import { LambdaEnvironment } from './LambdaEnvironment';
21+
import config from '../config/Configuration';
22+
import Environments from './Environments';
23+
import { LocalEnvironment } from './LocalEnvironment';
2124

2225
type EnvironmentProvider = () => Promise<IEnvironment>;
2326

24-
const environments = [new LambdaEnvironment(), new EC2Environment()];
27+
const lambdaEnvironment = new LambdaEnvironment();
28+
const ec2Environment = new EC2Environment();
2529
const defaultEnvironment = new DefaultEnvironment();
30+
const environments = [lambdaEnvironment, ec2Environment];
2631

27-
let environment: IEnvironment | undefined;
28-
const resolveEnvironment: EnvironmentProvider = async (): Promise<IEnvironment> => {
29-
if (environment) {
30-
return environment;
32+
let environment : IEnvironment | undefined = defaultEnvironment;
33+
34+
const getEnvironmentFromOverride = (): IEnvironment | undefined => {
35+
// short-circuit environment detection and use override
36+
switch (config.environmentOverride) {
37+
case Environments.Agent:
38+
return defaultEnvironment;
39+
case Environments.EC2:
40+
return ec2Environment;
41+
case Environments.Lambda:
42+
return lambdaEnvironment;
43+
case Environments.Local:
44+
return new LocalEnvironment();
45+
case Environments.Unknown:
46+
default:
47+
return undefined;
3148
}
49+
}
3250

51+
const discoverEnvironment = async (): Promise<IEnvironment> => {
3352
for (const envUnderTest of environments) {
3453
LOG(`Testing: ${envUnderTest.constructor.name}`);
3554

36-
let isEnvironment = false;
3755
try {
38-
isEnvironment = await envUnderTest.probe();
56+
if (await envUnderTest.probe()) {
57+
return envUnderTest;
58+
}
3959
} catch (e) {
4060
LOG(`Failed probe: ${envUnderTest.constructor.name}`);
4161
}
42-
43-
if (isEnvironment) {
44-
environment = envUnderTest;
45-
break;
46-
}
4762
}
63+
return defaultEnvironment;
64+
}
4865

49-
if (!environment) {
50-
environment = defaultEnvironment;
66+
const _resolveEnvironment: EnvironmentProvider = async (): Promise<IEnvironment> => {
67+
if (environment) {
68+
return environment;
5169
}
5270

53-
LOG(`Using Environment: ${environment.constructor.name}`);
54-
71+
if (config.environmentOverride) {
72+
LOG("Environment override supplied", config.environmentOverride);
73+
// this will be falsy if an invalid configuration value is provided
74+
environment = getEnvironmentFromOverride()
75+
if (environment) {
76+
return environment;
77+
}
78+
else {
79+
LOG('Invalid environment provided. Falling back to auto-discovery.', config.environmentOverride);
80+
}
81+
}
82+
83+
environment = await discoverEnvironment(); // eslint-disable-line require-atomic-updates
5584
return environment;
5685
};
5786

58-
const resetEnvironment = (): void => (environment = undefined);
87+
5988

6089
// pro-actively begin resolving the environment
6190
// this will allow us to kick off any async tasks
6291
// at module load time to reduce any blocking that
6392
// may occur on the initial flush()
64-
resolveEnvironment();
93+
const environmentPromise = _resolveEnvironment();
94+
const resolveEnvironment: EnvironmentProvider = async (): Promise<IEnvironment> => {
95+
return environmentPromise;
96+
};
97+
98+
const cleanResolveEnvironment = async (): Promise<IEnvironment> => {
99+
environment = undefined;
100+
return await _resolveEnvironment();
101+
};
65102

66-
export { EnvironmentProvider, resolveEnvironment, resetEnvironment };
103+
export { EnvironmentProvider, resolveEnvironment, cleanResolveEnvironment };

src/environment/Environments.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
enum Environments {
2+
Local = "Local",
3+
Lambda = "Lambda",
4+
Agent = "Agent",
5+
EC2 = "EC2",
6+
Unknown = ""
7+
};
8+
9+
export default Environments;

src/environment/LambdaEnvironment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
*/
1515

1616
import { MetricsContext } from '../logger/MetricsContext';
17-
import { LambdaSink } from '../sinks/LambdaSink';
17+
import { ConsoleSink } from '../sinks/ConsoleSink';
1818
import { ISink } from '../sinks/Sink';
1919
import { IEnvironment } from './IEnvironment';
2020

@@ -51,7 +51,7 @@ export class LambdaEnvironment implements IEnvironment {
5151

5252
public getSink(): ISink {
5353
if (!this.sink) {
54-
this.sink = new LambdaSink();
54+
this.sink = new ConsoleSink();
5555
}
5656
return this.sink;
5757
}

src/environment/LocalEnvironment.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2019 Amazon.com, Inc. or its affiliates.
3+
* Licensed under the Apache License, Version 2.0 (the
4+
* "License"); you may not use this file except in compliance
5+
* with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import config from '../config/Configuration';
17+
import { ISink } from '../sinks/Sink';
18+
import { LOG } from '../utils/Logger';
19+
import { IEnvironment } from './IEnvironment';
20+
import { ConsoleSink } from '../sinks/ConsoleSink';
21+
22+
export class LocalEnvironment implements IEnvironment {
23+
private sink: ISink | undefined;
24+
25+
public probe(): Promise<boolean> {
26+
// probe is not intended to be used in the LocalEnvironment
27+
// To use the local environment you should set the environment
28+
// override
29+
return Promise.resolve(false);
30+
}
31+
32+
public getName(): string {
33+
if (!config.serviceName) {
34+
LOG('Unknown ServiceName.');
35+
return 'Unknown';
36+
}
37+
return config.serviceName;
38+
}
39+
40+
public getType(): string {
41+
if (!config.serviceType) {
42+
LOG('Unknown ServiceType.');
43+
return 'Unknown';
44+
}
45+
return config.serviceType;
46+
}
47+
48+
public getLogGroupName(): string {
49+
return config.logGroupName ? config.logGroupName : `${this.getName()}-metrics`;
50+
}
51+
52+
public configureContext(): void {
53+
// no-op
54+
}
55+
56+
public getSink(): ISink {
57+
if (!this.sink) {
58+
this.sink = new ConsoleSink();
59+
}
60+
return this.sink;
61+
}
62+
}

0 commit comments

Comments
 (0)