Skip to content

Commit 6b893e6

Browse files
committed
feat(metrics): add ability to pass custom logger
1 parent c119fc9 commit 6b893e6

File tree

7 files changed

+67
-79
lines changed

7 files changed

+67
-79
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// biome-ignore lint/suspicious/noExplicitAny: We intentionally use `any` here to represent any type of data and keep the logger is as flexible as possible.
2+
type Anything = any[];
3+
4+
/**
5+
* Interface for a generic logger object.
6+
*
7+
* This interface is used to define the shape of a logger object that can be passed to a Powertools for AWS utility.
8+
*
9+
* It can be an instance of Logger from Powertools for AWS, or any other logger that implements the same methods.
10+
*/
11+
export interface GenericLogger {
12+
trace?: (...content: Anything) => void;
13+
debug: (...content: Anything) => void;
14+
info: (...content: Anything) => void;
15+
warn: (...content: Anything) => void;
16+
error: (...content: Anything) => void;
17+
}

packages/commons/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type {
55
MiddlewareFn,
66
CleanupFunction,
77
} from './middy.js';
8+
export type { GenericLogger } from './GenericLogger.js';
89
export type { SdkClient, MiddlewareArgsLike } from './awsSdk.js';
910
export type {
1011
JSONPrimitive,

packages/metrics/src/Metrics.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { Console } from 'node:console';
22
import { Utility } from '@aws-lambda-powertools/commons';
3-
import type { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types';
3+
import type {
4+
GenericLogger,
5+
HandlerMethodDecorator,
6+
} from '@aws-lambda-powertools/commons/types';
47
import type { Callback, Context, Handler } from 'aws-lambda';
58
import { EnvironmentVariablesService } from './config/EnvironmentVariablesService.js';
69
import {
@@ -149,6 +152,11 @@ class Metrics extends Utility implements MetricsInterface {
149152
*/
150153
private functionName?: string;
151154

155+
/**
156+
* Custom logger object to be used for emitting debug, warning, and error messages.
157+
*/
158+
#logger?: GenericLogger;
159+
152160
/**
153161
* Flag indicating if this is a single metric instance
154162
* @default false
@@ -182,6 +190,7 @@ class Metrics extends Utility implements MetricsInterface {
182190
super();
183191

184192
this.dimensions = {};
193+
this.#logger = options.logger;
185194
this.setOptions(options);
186195
}
187196

@@ -347,6 +356,13 @@ class Metrics extends Utility implements MetricsInterface {
347356
this.storedMetrics = {};
348357
}
349358

359+
/**
360+
* Check if there are stored metrics in the buffer.
361+
*/
362+
public hasStoredMetrics(): boolean {
363+
return Object.keys(this.storedMetrics).length > 0;
364+
}
365+
350366
/**
351367
* A decorator automating coldstart capture, throw on empty metrics and publishing metrics on handler exit.
352368
*
@@ -428,12 +444,16 @@ class Metrics extends Utility implements MetricsInterface {
428444
* ```
429445
*/
430446
public publishStoredMetrics(): void {
431-
const hasMetrics = Object.keys(this.storedMetrics).length > 0;
447+
const hasMetrics = this.hasStoredMetrics();
432448
if (!this.shouldThrowOnEmptyMetrics && !hasMetrics) {
433-
console.warn(
449+
const message =
434450
'No application metrics to publish. The cold-start metric may be published if enabled. ' +
435-
'If application metrics should never be empty, consider using `throwOnEmptyMetrics`'
436-
);
451+
'If application metrics should never be empty, consider using `throwOnEmptyMetrics`';
452+
if (this.#logger?.warn) {
453+
this.#logger.warn(message);
454+
} else {
455+
this.console.warn(message);
456+
}
437457
}
438458
const emfOutput = this.serializeMetrics();
439459
hasMetrics && this.console.log(JSON.stringify(emfOutput));
@@ -471,7 +491,9 @@ class Metrics extends Utility implements MetricsInterface {
471491
}
472492

473493
if (!this.namespace)
474-
console.warn('Namespace should be defined, default used');
494+
(this.#logger?.warn || this.console.warn)(
495+
'Namespace should be defined, default used'
496+
);
475497

476498
// We reduce the stored metrics to a single object with the metric
477499
// name as the key and the value as the value.

packages/metrics/src/types/Metrics.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { GenericLogger } from '@aws-lambda-powertools/commons/types';
12
import type {
23
MetricResolution as MetricResolutionList,
34
MetricUnit as MetricUnitList,
@@ -12,6 +13,15 @@ type MetricsOptions = {
1213
serviceName?: string;
1314
singleMetric?: boolean;
1415
defaultDimensions?: Dimensions;
16+
/**
17+
* Logger object to be used for emitting debug, warning, and error messages.
18+
*
19+
* If not provided, debug messages will be suppressed, and warning and error messages will be sent to stdout.
20+
*
21+
* Note that EMF metrics are always sent directly to stdout, regardless of the logger
22+
* to avoid any potential side effects from using a custom logger.
23+
*/
24+
logger?: GenericLogger;
1525
};
1626

1727
type EmfOutput = Readonly<{

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* @group unit/metrics/class
55
*/
66
import type { LambdaInterface } from '@aws-lambda-powertools/commons/types';
7+
import { Logger } from '@aws-lambda-powertools/logger';
78
import context from '@aws-lambda-powertools/testing-utils/context';
89
import type { Context, Handler } from 'aws-lambda';
910
import { EnvironmentVariablesService } from '../../src/config/EnvironmentVariablesService.js';
@@ -27,6 +28,8 @@ jest.mock('node:console', () => ({
2728
...jest.requireActual('node:console'),
2829
Console: jest.fn().mockImplementation(() => ({
2930
log: jest.fn(),
31+
warn: jest.fn(),
32+
debug: jest.fn(),
3033
})),
3134
}));
3235
jest.spyOn(console, 'warn').mockImplementation(() => ({}));
@@ -1254,7 +1257,10 @@ describe('Class: Metrics', () => {
12541257
describe('Methods: publishStoredMetrics', () => {
12551258
test('it should log warning if no metrics are added & throwOnEmptyMetrics is false', () => {
12561259
// Prepare
1257-
const metrics: Metrics = new Metrics({ namespace: TEST_NAMESPACE });
1260+
const metrics: Metrics = new Metrics({
1261+
namespace: TEST_NAMESPACE,
1262+
logger: console,
1263+
});
12581264
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
12591265
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
12601266

@@ -1355,7 +1361,7 @@ describe('Class: Metrics', () => {
13551361
test('it should print warning, if no namespace provided in constructor or environment variable', () => {
13561362
// Prepare
13571363
process.env.POWERTOOLS_METRICS_NAMESPACE = '';
1358-
const metrics: Metrics = new Metrics();
1364+
const metrics: Metrics = new Metrics({ logger: console });
13591365
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
13601366

13611367
// Act

packages/metrics/tests/unit/middleware/middy.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ jest.mock('node:console', () => ({
1414
...jest.requireActual('node:console'),
1515
Console: jest.fn().mockImplementation(() => ({
1616
log: jest.fn(),
17+
warn: jest.fn(),
18+
debug: jest.fn(),
1719
})),
1820
}));
1921
jest.spyOn(console, 'warn').mockImplementation(() => ({}));
@@ -68,6 +70,7 @@ describe('Middy middleware', () => {
6870
const metrics = new Metrics({
6971
namespace: 'serverlessAirline',
7072
serviceName: 'orders',
73+
logger: console,
7174
});
7275
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
7376
const handler = middy(async (): Promise<void> => undefined).use(

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

Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
 (0)