Skip to content

Commit

Permalink
feat(metrics): log directly to stdout (#1786)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dreamorosi authored Nov 10, 2023
1 parent 73a56cc commit 75dc5b1
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 317 deletions.
10 changes: 7 additions & 3 deletions layers/tests/e2e/layerPublisher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,14 @@ describe(`Layers E2E tests, publisher stack`, () => {
it(
'should have one info log related to coldstart metric',
() => {
const logs = invocationLogs.getFunctionLogs('INFO');
const logs = invocationLogs.getFunctionLogs();
const emfLogEntry = logs.find((log) =>
log.match(
/{"_aws":{"Timestamp":\d+,"CloudWatchMetrics":\[\{"Namespace":"\S+","Dimensions":\[\["service"\]\],"Metrics":\[\{"Name":"ColdStart","Unit":"Count"\}\]\}\]},"service":"\S+","ColdStart":1}/
)
);

expect(logs.length).toBe(1);
expect(logs[0]).toContain('ColdStart');
expect(emfLogEntry).toBeDefined();
},
TEST_CASE_TIMEOUT
);
Expand Down
7 changes: 7 additions & 0 deletions packages/commons/src/config/ConfigService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ abstract class ConfigService {
*/
public abstract getXrayTraceId(): string | undefined;

/**
* It returns true if the `POWERTOOLS_DEV` environment variable is set to truthy value.
*
* @returns {boolean}
*/
public abstract isDevMode(): boolean;

/**
* It returns true if the string value represents a boolean true value.
*
Expand Down
12 changes: 11 additions & 1 deletion packages/commons/src/config/EnvironmentVariablesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import { ConfigService } from './ConfigService';
* @see https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime
* @see https://docs.powertools.aws.dev/lambda/typescript/latest/#environment-variables
*/
class EnvironmentVariablesService extends ConfigService {
class EnvironmentVariablesService implements ConfigService {
/**
* @see https://docs.powertools.aws.dev/lambda/typescript/latest/#environment-variables
* @protected
*/
protected devModeVariable = 'POWERTOOLS_DEV';
protected serviceNameVariable = 'POWERTOOLS_SERVICE_NAME';
// Reserved environment variables
private xRayTraceIdVariable = '_X_AMZN_TRACE_ID';
Expand Down Expand Up @@ -71,6 +72,15 @@ class EnvironmentVariablesService extends ConfigService {
return xRayTraceData?.Sampled === '1';
}

/**
* It returns true if the `POWERTOOLS_DEV` environment variable is set to truthy value.
*
* @returns {boolean}
*/
public isDevMode(): boolean {
return this.isValueTrue(this.get(this.devModeVariable));
}

/**
* It returns true if the string value represents a boolean true value.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,54 @@ describe('Class: EnvironmentVariablesService', () => {
}
);
});

describe('Method: isDevMode', () => {
test('it returns true if the environment variable POWERTOOLS_DEV is "true"', () => {
// Prepare
process.env.POWERTOOLS_DEV = 'true';
const service = new EnvironmentVariablesService();

// Act
const value = service.isDevMode();

// Assess
expect(value).toEqual(true);
});

test('it returns false if the environment variable POWERTOOLS_DEV is "false"', () => {
// Prepare
process.env.POWERTOOLS_DEV = 'false';
const service = new EnvironmentVariablesService();

// Act
const value = service.isDevMode();

// Assess
expect(value).toEqual(false);
});

test('it returns false if the environment variable POWERTOOLS_DEV is NOT set', () => {
// Prepare
process.env.POWERTOOLS_DEV = 'somethingsilly';
const service = new EnvironmentVariablesService();

// Act
const value = service.isDevMode();

// Assess
expect(value).toEqual(false);
});

test('it returns false if the environment variable POWERTOOLS_DEV is "somethingsilly"', () => {
// Prepare
process.env.POWERTOOLS_DEV = 'somethingsilly';
const service = new EnvironmentVariablesService();

// Act
const value = service.isDevMode();

// Assess
expect(value).toEqual(false);
});
});
});
12 changes: 0 additions & 12 deletions packages/logger/src/config/EnvironmentVariablesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ class EnvironmentVariablesService
// Reserved environment variables
private awsRegionVariable = 'AWS_REGION';
private currentEnvironmentVariable = 'ENVIRONMENT';
private devModeVariable = 'POWERTOOLS_DEV';
private functionNameVariable = 'AWS_LAMBDA_FUNCTION_NAME';
private functionVersionVariable = 'AWS_LAMBDA_FUNCTION_VERSION';
private logEventVariable = 'POWERTOOLS_LOGGER_LOG_EVENT';
Expand Down Expand Up @@ -107,17 +106,6 @@ class EnvironmentVariablesService

return value && value.length > 0 ? Number(value) : undefined;
}

/**
* It returns true if the POWERTOOLS_DEV environment variable is set to truthy value.
*
* @returns {boolean}
*/
public isDevMode(): boolean {
const value = this.get(this.devModeVariable);

return this.isValueTrue(value);
}
}

export { EnvironmentVariablesService };
Original file line number Diff line number Diff line change
Expand Up @@ -152,54 +152,4 @@ describe('Class: EnvironmentVariablesService', () => {
expect(value).toEqual(0.01);
});
});

describe('Method: isDevMode', () => {
test('it returns true if the environment variable POWERTOOLS_DEV is "true"', () => {
// Prepare
process.env.POWERTOOLS_DEV = 'true';
const service = new EnvironmentVariablesService();

// Act
const value = service.isDevMode();

// Assess
expect(value).toEqual(true);
});

test('it returns false if the environment variable POWERTOOLS_DEV is "false"', () => {
// Prepare
process.env.POWERTOOLS_DEV = 'false';
const service = new EnvironmentVariablesService();

// Act
const value = service.isDevMode();

// Assess
expect(value).toEqual(false);
});

test('it returns false if the environment variable POWERTOOLS_DEV is NOT set', () => {
// Prepare
process.env.POWERTOOLS_DEV = 'somethingsilly';
const service = new EnvironmentVariablesService();

// Act
const value = service.isDevMode();

// Assess
expect(value).toEqual(false);
});

test('it returns false if the environment variable POWERTOOLS_DEV is "somethingsilly"', () => {
// Prepare
process.env.POWERTOOLS_DEV = 'somethingsilly';
const service = new EnvironmentVariablesService();

// Act
const value = service.isDevMode();

// Assess
expect(value).toEqual(false);
});
});
});
34 changes: 33 additions & 1 deletion packages/metrics/src/Metrics.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Callback, Context, Handler } from 'aws-lambda';
import { Console } from 'node:console';
import { Utility } from '@aws-lambda-powertools/commons';
import type { MetricsInterface } from './MetricsInterface';
import {
Expand Down Expand Up @@ -109,6 +110,18 @@ import {
* ```
*/
class Metrics extends Utility implements MetricsInterface {
/**
* Console instance used to print logs.
*
* In AWS Lambda, we create a new instance of the Console class so that we can have
* full control over the output of the logs. In testing environments, we use the
* default console instance.
*
* This property is initialized in the constructor in setOptions().
*
* @private
*/
private console!: Console;
private customConfigService?: ConfigServiceInterface;
private defaultDimensions: Dimensions = {};
private dimensions: Dimensions = {};
Expand Down Expand Up @@ -388,7 +401,7 @@ class Metrics extends Utility implements MetricsInterface {
);
}
const target = this.serializeMetrics();
console.log(JSON.stringify(target));
this.console.log(JSON.stringify(target));
this.clearMetrics();
this.clearDimensions();
this.clearMetadata();
Expand Down Expand Up @@ -591,6 +604,24 @@ class Metrics extends Utility implements MetricsInterface {
}
}

/**
* It initializes console property as an instance of the internal version of Console() class (PR #748)
* or as the global node console if the `POWERTOOLS_DEV' env variable is set and has truthy value.
*
* @private
* @returns {void}
*/
private setConsole(): void {
if (!this.getEnvVarsService().isDevMode()) {
this.console = new Console({
stdout: process.stdout,
stderr: process.stderr,
});
} else {
this.console = console;
}
}

/**
* Sets the custom config service to be used.
*
Expand Down Expand Up @@ -640,6 +671,7 @@ class Metrics extends Utility implements MetricsInterface {
} = options;

this.setEnvVarsService();
this.setConsole();
this.setCustomConfigService(customConfigService);
this.setNamespace(namespace);
this.setService(serviceName);
Expand Down
6 changes: 6 additions & 0 deletions packages/metrics/src/config/ConfigServiceInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ interface ConfigServiceInterface {
get?(name: string): string;
getNamespace(): string;
getServiceName(): string;
/**
* It returns the value of the POWERTOOLS_DEV environment variable.
*
* @returns {boolean}
*/
isDevMode(): boolean;
}

export { ConfigServiceInterface };
27 changes: 25 additions & 2 deletions packages/metrics/tests/unit/Metrics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ import {
EnvironmentVariablesService,
} from '../../src/config';

jest.mock('node:console', () => ({
...jest.requireActual('node:console'),
Console: jest.fn().mockImplementation(() => ({
log: jest.fn(),
})),
}));
jest.spyOn(console, 'warn').mockImplementation(() => ({}));
const mockDate = new Date(1466424490000);
const dateSpy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
jest.spyOn(console, 'log').mockImplementation();
Expand Down Expand Up @@ -234,6 +241,9 @@ describe('Class: Metrics', () => {
getServiceName(): string {
return 'test-service';
},
isDevMode(): boolean {
return false;
},
};
const metricsOptions: MetricsOptions = {
customConfigService: configService,
Expand Down Expand Up @@ -703,7 +713,7 @@ describe('Class: Metrics', () => {
test('it should publish metrics when the array of values reaches the maximum size', () => {
// Prepare
const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE });
const consoleSpy = jest.spyOn(console, 'log');
const consoleSpy = jest.spyOn(metrics['console'], 'log');
const metricName = 'test-metric';

// Act
Expand Down Expand Up @@ -1246,7 +1256,9 @@ describe('Class: Metrics', () => {
// Prepare
const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE });
metrics.addMetric('test-metric', MetricUnits.Count, 10);
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
const consoleLogSpy = jest
.spyOn(metrics['console'], 'log')
.mockImplementation();
const mockData: EmfOutput = {
_aws: {
Timestamp: mockDate.getTime(),
Expand Down Expand Up @@ -2183,4 +2195,15 @@ describe('Class: Metrics', () => {
);
});
});

describe('Feature: POWERTOOLS_DEV', () => {
it('uses the global console object when the environment variable is set', () => {
// Prepare
process.env.POWERTOOLS_DEV = 'true';
const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE });

// Act & Assess
expect(metrics['console']).toEqual(console);
});
});
});
Loading

0 comments on commit 75dc5b1

Please sign in to comment.