Skip to content

Commit 3b100be

Browse files
committed
feat(metrics): add ability to pass custom logger
1 parent e922699 commit 3b100be

File tree

7 files changed

+70
-80
lines changed

7 files changed

+70
-80
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 {
@@ -159,6 +162,11 @@ class Metrics extends Utility implements MetricsInterface {
159162
*/
160163
private functionName?: string;
161164

165+
/**
166+
* Custom logger object to be used for emitting debug, warning, and error messages.
167+
*/
168+
#logger?: GenericLogger;
169+
162170
/**
163171
* Flag indicating if this is a single metric instance
164172
* @default false
@@ -192,6 +200,7 @@ class Metrics extends Utility implements MetricsInterface {
192200
super();
193201

194202
this.dimensions = {};
203+
this.#logger = options.logger;
195204
this.setOptions(options);
196205
}
197206

@@ -439,6 +448,13 @@ class Metrics extends Utility implements MetricsInterface {
439448
this.storedMetrics = {};
440449
}
441450

451+
/**
452+
* Check if there are stored metrics in the buffer.
453+
*/
454+
public hasStoredMetrics(): boolean {
455+
return Object.keys(this.storedMetrics).length > 0;
456+
}
457+
442458
/**
443459
* A class method decorator to automatically log metrics after the method returns or throws an error.
444460
*
@@ -539,12 +555,16 @@ class Metrics extends Utility implements MetricsInterface {
539555
* ```
540556
*/
541557
public publishStoredMetrics(): void {
542-
const hasMetrics = Object.keys(this.storedMetrics).length > 0;
558+
const hasMetrics = this.hasStoredMetrics();
543559
if (!this.shouldThrowOnEmptyMetrics && !hasMetrics) {
544-
console.warn(
560+
const message =
545561
'No application metrics to publish. The cold-start metric may be published if enabled. ' +
546-
'If application metrics should never be empty, consider using `throwOnEmptyMetrics`'
547-
);
562+
'If application metrics should never be empty, consider using `throwOnEmptyMetrics`';
563+
if (this.#logger?.warn) {
564+
this.#logger.warn(message);
565+
} else {
566+
this.console.warn(message);
567+
}
548568
}
549569
const emfOutput = this.serializeMetrics();
550570
hasMetrics && this.console.log(JSON.stringify(emfOutput));
@@ -584,7 +604,9 @@ class Metrics extends Utility implements MetricsInterface {
584604
}
585605

586606
if (!this.namespace)
587-
console.warn('Namespace should be defined, default used');
607+
(this.#logger?.warn || this.console.warn)(
608+
'Namespace should be defined, default used'
609+
);
588610

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

packages/metrics/src/types/Metrics.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { HandlerMethodDecorator } from '@aws-lambda-powertools/commons/types';
1+
import type {
2+
GenericLogger,
3+
HandlerMethodDecorator,
4+
} from '@aws-lambda-powertools/commons/types';
25
import type {
36
MetricResolution as MetricResolutions,
47
MetricUnit as MetricUnits,
@@ -57,6 +60,15 @@ type MetricsOptions = {
5760
* @see {@link MetricsInterface.setDefaultDimensions | `setDefaultDimensions()`}
5861
*/
5962
defaultDimensions?: Dimensions;
63+
/**
64+
* Logger object to be used for emitting debug, warning, and error messages.
65+
*
66+
* If not provided, debug messages will be suppressed, and warning and error messages will be sent to stdout.
67+
*
68+
* Note that EMF metrics are always sent directly to stdout, regardless of the logger
69+
* to avoid any potential side effects from using a custom logger.
70+
*/
71+
logger?: GenericLogger;
6072
};
6173

6274
/**

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)