Skip to content

Commit 9c47362

Browse files
committed
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 9f7a9a2 commit 9c47362

File tree

9 files changed

+219
-226
lines changed

9 files changed

+219
-226
lines changed

Diff for: 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
);

Diff for: 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
*

Diff for: packages/commons/src/config/EnvironmentVariablesService.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import { ConfigService } from './ConfigService.js';
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
*

Diff for: packages/commons/tests/unit/EnvironmentVariablesService.test.ts

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

Diff for: packages/logger/src/config/EnvironmentVariablesService.ts

-1
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';

Diff for: 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 { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types';
45
import { EnvironmentVariablesService } from './config/EnvironmentVariablesService.js';
@@ -108,6 +109,18 @@ import {
108109
* ```
109110
*/
110111
class Metrics extends Utility implements MetricsInterface {
112+
/**
113+
* Console instance used to print logs.
114+
*
115+
* In AWS Lambda, we create a new instance of the Console class so that we can have
116+
* full control over the output of the logs. In testing environments, we use the
117+
* default console instance.
118+
*
119+
* This property is initialized in the constructor in setOptions().
120+
*
121+
* @private
122+
*/
123+
private console!: Console;
111124
private customConfigService?: ConfigServiceInterface;
112125
private defaultDimensions: Dimensions = {};
113126
private dimensions: Dimensions = {};
@@ -387,7 +400,7 @@ class Metrics extends Utility implements MetricsInterface {
387400
);
388401
}
389402
const target = this.serializeMetrics();
390-
console.log(JSON.stringify(target));
403+
this.console.log(JSON.stringify(target));
391404
this.clearMetrics();
392405
this.clearDimensions();
393406
this.clearMetadata();
@@ -590,6 +603,24 @@ class Metrics extends Utility implements MetricsInterface {
590603
}
591604
}
592605

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

641672
this.setEnvVarsService();
673+
this.setConsole();
642674
this.setCustomConfigService(customConfigService);
643675
this.setNamespace(namespace);
644676
this.setService(serviceName);

Diff for: packages/metrics/src/types/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 type { ConfigServiceInterface };

Diff for: packages/metrics/tests/unit/Metrics.test.ts

+27-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ import {
2323
import { setupDecoratorLambdaHandler } from '../helpers/metricsUtils.js';
2424
import { EnvironmentVariablesService } from '../../src/config/EnvironmentVariablesService.js';
2525

26+
jest.mock('node:console', () => ({
27+
...jest.requireActual('node:console'),
28+
Console: jest.fn().mockImplementation(() => ({
29+
log: jest.fn(),
30+
})),
31+
}));
32+
jest.spyOn(console, 'warn').mockImplementation(() => ({}));
2633
const mockDate = new Date(1466424490000);
2734
const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
2835
jest.spyOn(console, 'log').mockImplementation();
@@ -235,6 +242,9 @@ describe('Class: Metrics', () => {
235242
getServiceName(): string {
236243
return 'test-service';
237244
},
245+
isDevMode(): boolean {
246+
return false;
247+
},
238248
};
239249
const metricsOptions: MetricsOptions = {
240250
customConfigService: configService,
@@ -699,7 +709,7 @@ describe('Class: Metrics', () => {
699709
test('it should publish metrics when the array of values reaches the maximum size', () => {
700710
// Prepare
701711
const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE });
702-
const consoleSpy = jest.spyOn(console, 'log');
712+
const consoleSpy = jest.spyOn(metrics['console'], 'log');
703713
const metricName = 'test-metric';
704714

705715
// Act
@@ -1242,7 +1252,9 @@ describe('Class: Metrics', () => {
12421252
// Prepare
12431253
const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE });
12441254
metrics.addMetric('test-metric', MetricUnit.Count, 10);
1245-
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
1255+
const consoleLogSpy = jest
1256+
.spyOn(metrics['console'], 'log')
1257+
.mockImplementation();
12461258
const mockData: EmfOutput = {
12471259
_aws: {
12481260
Timestamp: mockDate.getTime(),
@@ -1512,7 +1524,7 @@ describe('Class: Metrics', () => {
15121524
});
15131525

15141526
// Act
1515-
metrics.addMetric(testMetric, MetricUnits.Count, 10);
1527+
metrics.addMetric(testMetric, MetricUnit.Count, 10);
15161528
metrics.addDimension('foo', 'baz');
15171529
const loggedData = metrics.serializeMetrics();
15181530

@@ -1531,7 +1543,7 @@ describe('Class: Metrics', () => {
15311543
Metrics: [
15321544
{
15331545
Name: testMetric,
1534-
Unit: MetricUnits.Count,
1546+
Unit: MetricUnit.Count,
15351547
},
15361548
],
15371549
Namespace: TEST_NAMESPACE,
@@ -2179,4 +2191,15 @@ describe('Class: Metrics', () => {
21792191
);
21802192
});
21812193
});
2194+
2195+
describe('Feature: POWERTOOLS_DEV', () => {
2196+
it('uses the global console object when the environment variable is set', () => {
2197+
// Prepare
2198+
process.env.POWERTOOLS_DEV = 'true';
2199+
const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE });
2200+
2201+
// Act & Assess
2202+
expect(metrics['console']).toEqual(console);
2203+
});
2204+
});
21822205
});

0 commit comments

Comments
 (0)