Skip to content

feat(client): add MetricsRecorder support to client middleware#2149

Open
vismayku wants to merge 1 commit into
smithy-lang:mainfrom
vismayku:feat/client-metrics
Open

feat(client): add MetricsRecorder support to client middleware#2149
vismayku wants to merge 1 commit into
smithy-lang:mainfrom
vismayku:feat/client-metrics

Conversation

@vismayku

Copy link
Copy Markdown
Contributor

Issue #, if available:

Description of changes:

Expose a call-site MetricsRecorder to client middleware. client.send(command, { recorder }) now forwards the recorder through Command.resolveMiddleware and MiddlewareStack.resolve as an optional invocationOptions third argument, so any middleware can read it. Adds the generic InvocationOptions type to @smithy/types (consuming the merged MetricsRecorder interface).
Backward compatible: the parameter is optional and existing two-argument middleware and send() calls are unaffected.

If one or more of the packages in the /packages directory has been modified, be sure yarn changeset add has been run and its output has
been committed and included in this pull request. See CONTRIBUTING.md.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@vismayku
vismayku requested a review from a team as a code owner July 15, 2026 01:44

@kuhe kuhe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the proposed public API for using a smithy client combined with a MetricsRecorder?

It is purely via client.send(args, { recorder }) and nowhere else? Why is there no client level config?

Comment thread packages/types/src/middleware.ts Outdated
*/
export interface InvocationOptions<Native> {
recorder?: MetricsRecorder<Native>;
}

@kuhe kuhe Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what else do you expect to add to InvocationOptions?

This type does not need to exist. Other comments explain why.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am adding this interface to basically add anything in future and we can avoid any signature changes. I'm fine switching to context for passing metrics recorder.

Comment thread packages/types/src/middleware.ts Outdated
/**
* Invocation-scoped options made visible to every middleware when a command is
* resolved. Carries per-call concerns supplied at the `client.send(command, options)`
* call site — currently a {@link MetricsRecorder} — so a middleware can read them

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* call site currently a {@link MetricsRecorder} so a middleware can read them
* call site, currently a {@link MetricsRecorder}, so a middleware can read them

Comment thread packages/types/src/middleware.ts Outdated
*
* @public
*/
export interface InvocationOptions<Native> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Native is too ambiguous as the type parameter name here at the consumer site.

It's ok in the type definition of MetricsRecorder, but here it should be NativeMetrics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack. Can switch to NativeMetrics

Comment thread packages/types/src/middleware.ts Outdated
next: InitializeHandler<Input, Output>,
context: HandlerExecutionContext,
invocationOptions?: InvocationOptions<Native>
): InitializeHandler<Input, Output>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no change should be made to the middleware stack interface.

I will explain the alternative in other comments.

httpLabelCommand(
args: HttpLabelCommandCommandInput,
options?: __HttpHandlerOptions
options?: __HttpHandlerOptions & __InvocationOptions<any>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated composed types are hard to read. And this is the most user-facing part of any service client API.

Introduce a new type called RequestOptions or InvocationOptions that extends (is superset of) __HttpHandlerOptions, and generate that here instead.

also,

what was the point of the Native type parameter if clients don't infer it at the public call site?

(request: FinalizeHandlerArguments<any>) => requestHandler.handle(request.request as HttpRequest, requestOptions),
handlerExecutionContext
handlerExecutionContext,
options

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add the recorder into the handlerExecutionContext[SMITHY_CONTEXT_KEY] instead of creating a new object for the request lifecycle.

the handler execution context is already matched to the request lifecycle.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack. Can switch to it.

import type {
AbsoluteLocation,
DeserializeHandler,
Handler,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file must not be changed for this feature

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack

@vismayku

Copy link
Copy Markdown
Contributor Author

What is the proposed public API for using a smithy client combined with a MetricsRecorder?

It is purely via client.send(args, { recorder }) and nowhere else? Why is there no client level config?

Yeah, Users basically passing the metric instance to the call. Open to suggestions if you want me to switch this as a metric middleware step while creating client.

@vismayku

Copy link
Copy Markdown
Contributor Author

Also, to add one more thing.
This PR just adds public api. I will have separate PR where I propose some common metric and actually using a separate metric to avoid metric clash with existing service metrics.

@vismayku
vismayku force-pushed the feat/client-metrics branch from dbe258a to 95cd771 Compare July 17, 2026 14:05
Expose a call-site MetricsRecorder to client middleware. client.send(command, { recorder }) now forwards the recorder through Command.resolveMiddleware and MiddlewareStack.resolve as an optional invocationOptions third argument, so any middleware can read it. Adds the generic InvocationOptions<Native> type to @smithy/types (consuming the merged MetricsRecorder<Native> interface). Backward compatible: the parameter is optional and existing two-argument middleware and send() calls are unaffected.
@vismayku
vismayku force-pushed the feat/client-metrics branch from 95cd771 to 8345c85 Compare July 17, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants