Skip to content

Commit 75dc5b1

Browse files
authored
feat(metrics): log directly to stdout (#1786)
* chore(commons): move isDevMode to commons * chore(logger): move isDev config out of logger to commons * feat(metrics): use own console object by default * tests(layers): fix unit tests
1 parent 73a56cc commit 75dc5b1

File tree

10 files changed

+218
-317
lines changed

10 files changed

+218
-317
lines changed

layers/tests/e2e/layerPublisher.test.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,14 @@ describe(`Layers E2E tests, publisher stack`, () => {
145145
it(
146146
'should have one info log related to coldstart metric',
147147
() => {
148-
const logs = invocationLogs.getFunctionLogs('INFO');
148+
const logs = invocationLogs.getFunctionLogs();
149+
const emfLogEntry = logs.find((log) =>
150+
log.match(
151+
/{"_aws":{"Timestamp":\d+,"CloudWatchMetrics":\[\{"Namespace":"\S+","Dimensions":\[\["service"\]\],"Metrics":\[\{"Name":"ColdStart","Unit":"Count"\}\]\}\]},"service":"\S+","ColdStart":1}/
152+
)
153+
);
149154

150-
expect(logs.length).toBe(1);
151-
expect(logs[0]).toContain('ColdStart');
155+
expect(emfLogEntry).toBeDefined();
152156
},
153157
TEST_CASE_TIMEOUT
154158
);

packages/commons/src/config/ConfigService.ts

+7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ abstract class ConfigService {
3535
*/
3636
public abstract getXrayTraceId(): string | undefined;
3737

38+
/**
39+
* It returns true if the `POWERTOOLS_DEV` environment variable is set to truthy value.
40+
*
41+
* @returns {boolean}
42+
*/
43+
public abstract isDevMode(): boolean;
44+
3845
/**
3946
* It returns true if the string value represents a boolean true value.
4047
*

packages/commons/src/config/EnvironmentVariablesService.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import { ConfigService } from './ConfigService';
1313
* @see https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime
1414
* @see https://docs.powertools.aws.dev/lambda/typescript/latest/#environment-variables
1515
*/
16-
class EnvironmentVariablesService extends ConfigService {
16+
class EnvironmentVariablesService implements ConfigService {
1717
/**
1818
* @see https://docs.powertools.aws.dev/lambda/typescript/latest/#environment-variables
1919
* @protected
2020
*/
21+
protected devModeVariable = 'POWERTOOLS_DEV';
2122
protected serviceNameVariable = 'POWERTOOLS_SERVICE_NAME';
2223
// Reserved environment variables
2324
private xRayTraceIdVariable = '_X_AMZN_TRACE_ID';
@@ -71,6 +72,15 @@ class EnvironmentVariablesService extends ConfigService {
7172
return xRayTraceData?.Sampled === '1';
7273
}
7374

75+
/**
76+
* It returns true if the `POWERTOOLS_DEV` environment variable is set to truthy value.
77+
*
78+
* @returns {boolean}
79+
*/
80+
public isDevMode(): boolean {
81+
return this.isValueTrue(this.get(this.devModeVariable));
82+
}
83+
7484
/**
7585
* It returns true if the string value represents a boolean true value.
7686
*

packages/commons/tests/unit/config/EnvironmentVariablesService.test.ts

+50
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,54 @@ describe('Class: EnvironmentVariablesService', () => {
163163
}
164164
);
165165
});
166+
167+
describe('Method: isDevMode', () => {
168+
test('it returns true if the environment variable POWERTOOLS_DEV is "true"', () => {
169+
// Prepare
170+
process.env.POWERTOOLS_DEV = 'true';
171+
const service = new EnvironmentVariablesService();
172+
173+
// Act
174+
const value = service.isDevMode();
175+
176+
// Assess
177+
expect(value).toEqual(true);
178+
});
179+
180+
test('it returns false if the environment variable POWERTOOLS_DEV is "false"', () => {
181+
// Prepare
182+
process.env.POWERTOOLS_DEV = 'false';
183+
const service = new EnvironmentVariablesService();
184+
185+
// Act
186+
const value = service.isDevMode();
187+
188+
// Assess
189+
expect(value).toEqual(false);
190+
});
191+
192+
test('it returns false if the environment variable POWERTOOLS_DEV is NOT set', () => {
193+
// Prepare
194+
process.env.POWERTOOLS_DEV = 'somethingsilly';
195+
const service = new EnvironmentVariablesService();
196+
197+
// Act
198+
const value = service.isDevMode();
199+
200+
// Assess
201+
expect(value).toEqual(false);
202+
});
203+
204+
test('it returns false if the environment variable POWERTOOLS_DEV is "somethingsilly"', () => {
205+
// Prepare
206+
process.env.POWERTOOLS_DEV = 'somethingsilly';
207+
const service = new EnvironmentVariablesService();
208+
209+
// Act
210+
const value = service.isDevMode();
211+
212+
// Assess
213+
expect(value).toEqual(false);
214+
});
215+
});
166216
});

packages/logger/src/config/EnvironmentVariablesService.ts

-12
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ class EnvironmentVariablesService
2222
// Reserved environment variables
2323
private awsRegionVariable = 'AWS_REGION';
2424
private currentEnvironmentVariable = 'ENVIRONMENT';
25-
private devModeVariable = 'POWERTOOLS_DEV';
2625
private functionNameVariable = 'AWS_LAMBDA_FUNCTION_NAME';
2726
private functionVersionVariable = 'AWS_LAMBDA_FUNCTION_VERSION';
2827
private logEventVariable = 'POWERTOOLS_LOGGER_LOG_EVENT';
@@ -107,17 +106,6 @@ class EnvironmentVariablesService
107106

108107
return value && value.length > 0 ? Number(value) : undefined;
109108
}
110-
111-
/**
112-
* It returns true if the POWERTOOLS_DEV environment variable is set to truthy value.
113-
*
114-
* @returns {boolean}
115-
*/
116-
public isDevMode(): boolean {
117-
const value = this.get(this.devModeVariable);
118-
119-
return this.isValueTrue(value);
120-
}
121109
}
122110

123111
export { EnvironmentVariablesService };

packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts

-50
Original file line numberDiff line numberDiff line change
@@ -152,54 +152,4 @@ describe('Class: EnvironmentVariablesService', () => {
152152
expect(value).toEqual(0.01);
153153
});
154154
});
155-
156-
describe('Method: isDevMode', () => {
157-
test('it returns true if the environment variable POWERTOOLS_DEV is "true"', () => {
158-
// Prepare
159-
process.env.POWERTOOLS_DEV = 'true';
160-
const service = new EnvironmentVariablesService();
161-
162-
// Act
163-
const value = service.isDevMode();
164-
165-
// Assess
166-
expect(value).toEqual(true);
167-
});
168-
169-
test('it returns false if the environment variable POWERTOOLS_DEV is "false"', () => {
170-
// Prepare
171-
process.env.POWERTOOLS_DEV = 'false';
172-
const service = new EnvironmentVariablesService();
173-
174-
// Act
175-
const value = service.isDevMode();
176-
177-
// Assess
178-
expect(value).toEqual(false);
179-
});
180-
181-
test('it returns false if the environment variable POWERTOOLS_DEV is NOT set', () => {
182-
// Prepare
183-
process.env.POWERTOOLS_DEV = 'somethingsilly';
184-
const service = new EnvironmentVariablesService();
185-
186-
// Act
187-
const value = service.isDevMode();
188-
189-
// Assess
190-
expect(value).toEqual(false);
191-
});
192-
193-
test('it returns false if the environment variable POWERTOOLS_DEV is "somethingsilly"', () => {
194-
// Prepare
195-
process.env.POWERTOOLS_DEV = 'somethingsilly';
196-
const service = new EnvironmentVariablesService();
197-
198-
// Act
199-
const value = service.isDevMode();
200-
201-
// Assess
202-
expect(value).toEqual(false);
203-
});
204-
});
205155
});

packages/metrics/src/Metrics.ts

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Callback, Context, Handler } from 'aws-lambda';
2+
import { Console } from 'node:console';
23
import { Utility } from '@aws-lambda-powertools/commons';
34
import type { MetricsInterface } from './MetricsInterface';
45
import {
@@ -109,6 +110,18 @@ import {
109110
* ```
110111
*/
111112
class Metrics extends Utility implements MetricsInterface {
113+
/**
114+
* Console instance used to print logs.
115+
*
116+
* In AWS Lambda, we create a new instance of the Console class so that we can have
117+
* full control over the output of the logs. In testing environments, we use the
118+
* default console instance.
119+
*
120+
* This property is initialized in the constructor in setOptions().
121+
*
122+
* @private
123+
*/
124+
private console!: Console;
112125
private customConfigService?: ConfigServiceInterface;
113126
private defaultDimensions: Dimensions = {};
114127
private dimensions: Dimensions = {};
@@ -388,7 +401,7 @@ class Metrics extends Utility implements MetricsInterface {
388401
);
389402
}
390403
const target = this.serializeMetrics();
391-
console.log(JSON.stringify(target));
404+
this.console.log(JSON.stringify(target));
392405
this.clearMetrics();
393406
this.clearDimensions();
394407
this.clearMetadata();
@@ -591,6 +604,24 @@ class Metrics extends Utility implements MetricsInterface {
591604
}
592605
}
593606

607+
/**
608+
* It initializes console property as an instance of the internal version of Console() class (PR #748)
609+
* or as the global node console if the `POWERTOOLS_DEV' env variable is set and has truthy value.
610+
*
611+
* @private
612+
* @returns {void}
613+
*/
614+
private setConsole(): void {
615+
if (!this.getEnvVarsService().isDevMode()) {
616+
this.console = new Console({
617+
stdout: process.stdout,
618+
stderr: process.stderr,
619+
});
620+
} else {
621+
this.console = console;
622+
}
623+
}
624+
594625
/**
595626
* Sets the custom config service to be used.
596627
*
@@ -640,6 +671,7 @@ class Metrics extends Utility implements MetricsInterface {
640671
} = options;
641672

642673
this.setEnvVarsService();
674+
this.setConsole();
643675
this.setCustomConfigService(customConfigService);
644676
this.setNamespace(namespace);
645677
this.setService(serviceName);

packages/metrics/src/config/ConfigServiceInterface.ts

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ interface ConfigServiceInterface {
22
get?(name: string): string;
33
getNamespace(): string;
44
getServiceName(): string;
5+
/**
6+
* It returns the value of the POWERTOOLS_DEV environment variable.
7+
*
8+
* @returns {boolean}
9+
*/
10+
isDevMode(): boolean;
511
}
612

713
export { ConfigServiceInterface };

packages/metrics/tests/unit/Metrics.test.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ import {
2424
EnvironmentVariablesService,
2525
} from '../../src/config';
2626

27+
jest.mock('node:console', () => ({
28+
...jest.requireActual('node:console'),
29+
Console: jest.fn().mockImplementation(() => ({
30+
log: jest.fn(),
31+
})),
32+
}));
33+
jest.spyOn(console, 'warn').mockImplementation(() => ({}));
2734
const mockDate = new Date(1466424490000);
2835
const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
2936
jest.spyOn(console, 'log').mockImplementation();
@@ -234,6 +241,9 @@ describe('Class: Metrics', () => {
234241
getServiceName(): string {
235242
return 'test-service';
236243
},
244+
isDevMode(): boolean {
245+
return false;
246+
},
237247
};
238248
const metricsOptions: MetricsOptions = {
239249
customConfigService: configService,
@@ -703,7 +713,7 @@ describe('Class: Metrics', () => {
703713
test('it should publish metrics when the array of values reaches the maximum size', () => {
704714
// Prepare
705715
const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE });
706-
const consoleSpy = jest.spyOn(console, 'log');
716+
const consoleSpy = jest.spyOn(metrics['console'], 'log');
707717
const metricName = 'test-metric';
708718

709719
// Act
@@ -1246,7 +1256,9 @@ describe('Class: Metrics', () => {
12461256
// Prepare
12471257
const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE });
12481258
metrics.addMetric('test-metric', MetricUnits.Count, 10);
1249-
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
1259+
const consoleLogSpy = jest
1260+
.spyOn(metrics['console'], 'log')
1261+
.mockImplementation();
12501262
const mockData: EmfOutput = {
12511263
_aws: {
12521264
Timestamp: mockDate.getTime(),
@@ -2183,4 +2195,15 @@ describe('Class: Metrics', () => {
21832195
);
21842196
});
21852197
});
2198+
2199+
describe('Feature: POWERTOOLS_DEV', () => {
2200+
it('uses the global console object when the environment variable is set', () => {
2201+
// Prepare
2202+
process.env.POWERTOOLS_DEV = 'true';
2203+
const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE });
2204+
2205+
// Act & Assess
2206+
expect(metrics['console']).toEqual(console);
2207+
});
2208+
});
21862209
});

0 commit comments

Comments
 (0)