Description
Summary
In #3945 we added new zero-dependency functional utilities to read and parse environment variables. These new functions supersede the old class based environment service approach. We should refactor the Metrics package to use the new function based approach instead of the custom EnvironmentVariablesService
class.
Why is this needed?
The rationale for the introduction of these utilities as per #3945 is as follows:
[M]ost of the advantage to customers will come in the form of smaller utilities over time, since we'll be able to use these helpers across other Powertools for AWS utilities and replace the existing EnvironmentVariablesService class-based model which is extremely verbose and requires props drilling if you want to access env variables deep into an inheritance stack.
This change will help us to eventually remove the EnvironmentVariablesService
entirely from the code base. The Metrics package currently has its own extended version of the service that adds metrics-specific environment variable handling.
Which area does this relate to?
Metrics
Solution
Currently the Metrics package has its own EnvironmentVariablesService class that extends the common one:
import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons';
class EnvironmentVariablesService
extends CommonEnvironmentVariablesService
implements ConfigServiceInterface
{
private readonly namespaceVariable = 'POWERTOOLS_METRICS_NAMESPACE';
private readonly functionNameVariable = 'POWERTOOLS_METRICS_FUNCTION_NAME';
private readonly disabledVariable = 'POWERTOOLS_METRICS_DISABLED';
public getNamespace(): string {
return this.get(this.namespaceVariable);
}
public getFunctionName(): string {
return this.get(this.functionNameVariable);
}
public getMetricsDisabled(): boolean {
const value = this.get(this.disabledVariable);
if (this.isValueFalse(value)) return false;
if (this.isValueTrue(value)) return true;
if (this.isDevMode()) return true;
return false;
}
}
This service is used throughout the Metrics class in several places for reading environment variables multiple times.
Proposed refactor using new functional utilities:
Replace the service with a private #envConfig
object that is populated once during the setOptions
method:
import {
getStringFromEnv,
getBooleanFromEnv,
isDevMode,
getServiceName
} from '@aws-lambda-powertools/commons/utils/env';
class Metrics {
// Replace envVarsService with #envConfig using hash notation
readonly #envConfig: {
namespace: string;
functionName: string;
serviceName: string;
disabled: boolean;
devMode: boolean;
};
// In setOptions method, replace setEnvVarsService() call
private setOptions(options: MetricsOptions): void {
const {
customConfigService,
namespace,
serviceName,
singleMetric,
defaultDimensions,
functionName,
} = options;
this.#setEnvConfig(); // Replace this.setEnvVarsService();
this.setConsole();
this.setCustomConfigService(customConfigService);
this.setDisabled();
this.setNamespace(namespace);
this.setService(serviceName);
this.setDefaultDimensions(defaultDimensions);
this.setFunctionNameForColdStartMetric(functionName);
this.isSingleMetric = singleMetric || false;
return this;
}
// Replace setEnvVarsService() with setEnvConfig()
private setEnvConfig(): void {
this.#envConfig = {
namespace: getStringFromEnv({
key: 'POWERTOOLS_METRICS_NAMESPACE',
defaultValue: '',
}),
functionName: getStringFromEnv({
key: 'POWERTOOLS_METRICS_FUNCTION_NAME',
defaultValue: '',
}),
serviceName: getServiceName(),
disabled: this.#getMetricsDisabledFromEnv(),
devMode: isDevMode(),
};
}
private #getMetricsDisabledFromEnv(): boolean {
const disabledValue = getStringFromEnv({
key: 'POWERTOOLS_METRICS_DISABLED',
defaultValue: '',
});
if (disabledValue.toLowerCase() === 'false') return false;
if (disabledValue.toLowerCase() === 'true') return true;
return isDevMode();
}
// Update methods to use #envConfig instead of service calls
private setNamespace(namespace: string | undefined): void {
this.namespace = (namespace ||
this.getCustomConfigService()?.getNamespace() ||
this.#envConfig.namespace) as string;
}
protected setFunctionNameForColdStartMetric(functionName?: string): void {
const value = functionName?.trim() ?? this.#envConfig.functionName.trim();
if (value && value.length > 0) {
this.functionName = value;
}
}
private setService(service: string | undefined): void {
const targetService =
((service ||
this.getCustomConfigService()?.getServiceName() ||
this.#envConfig.serviceName) as string) ||
this.defaultServiceName;
// ...
}
private setDisabled(): void {
this.disabled = this.#envConfig.disabled;
}
private setConsole(): void {
if (!this.#envConfig.devMode) {
this.console = new Console({
stdout: process.stdout,
stderr: process.stderr,
});
} else {
this.console = console;
}
}
}
Files to be modified:
- Remove
packages/metrics/src/config/EnvironmentVariablesService.ts
entirely - Update
packages/metrics/src/Metrics.ts
to use the#envConfig
approach - Remove the
envVarsService
property and related methods (getEnvVarsService()
,setEnvVarsService()
)
Benefits of this approach:
- Environment variables are read only once during initialization (better performance)
- No repeated environment variable access throughout the object lifecycle
- Cleaner, more functional approach
- Eliminates the verbose class-based service pattern
- Makes environment configuration explicit and cached
- Consistent with existing
setOptions
pattern
This refactor would eliminate approximately 50 lines of class-based code and improve performance by avoiding repeated environment variable reads.
Acknowledgment
- This request meets Powertools for AWS Lambda (TypeScript) Tenets
- Should this be considered in other Powertools for AWS Lambda languages? i.e. Python, Java, and .NET
Future readers
Please react with 👍 and your use case to help us understand customer demand.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status