Skip to content

Commit

Permalink
Extend core module system to support metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
kaialang committed Aug 30, 2024
1 parent 94767a6 commit eda719a
Showing 1 changed file with 87 additions and 13 deletions.
100 changes: 87 additions & 13 deletions rfc/custom-modules-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,14 @@ type SubgraphResponse struct {
}

// Router Hooks
// The three hooks cover the entire router lifecycle,
// from entering to either successfully exiting or erroring out
// The input err of the post hooks is an object of core.errorType

type RouterRequestHook interface {
// OnRouterRequest is called when a request is made to the router and after all GraphQL information is available
// PreRouterRequest is called when a request is made to the router.
// Returning an error will result in a GraphQL error being returned to the client.
OnRouterRequest(ctx *core.RouterRequest, err error) error
PreRouterRequest(ctx *core.RouterRequest) error
}

type RouterResponseHook interface {
Expand All @@ -200,9 +203,9 @@ type RouterErrorHook interface {
// We will provide an easy way to share state between hooks.

type SubgraphRequestHook interface {
// OnSubgraphRequest is called when a subgraph request is made
// PreSubgraphRequest is called when a subgraph request is made
// Returning an error will result in a GraphQL error being returned to the client.
OnSubgraphRequest(ctx *core.SubgraphRequest, err error) error
PreSubgraphRequest(ctx *core.SubgraphRequest) error
}

type SubgraphResponseHook interface {
Expand Down Expand Up @@ -267,23 +270,41 @@ type AuthorizationHook interface {
}

// OperationHooks are called when an operation is parsed, normalized, or planned
// The input err of the post hooks is an object of core.errorType
type GraphQLOperationPreParseHook interface {
// PreOperationParse is called when an operation is entering parse
// Returning an error will result in a GraphQL error as a response from the router.
PreOperationParse(op *core.Operation) error
}

type GraphQLOperationPostParseHook interface {
// PostOperationParse is called when an operation is exiting parse
// Returning an error will result in a GraphQL error as a response from the router.
PostOperationParse(op *core.Operation, err error) error
}

type GraphQLOperationParseHook interface {
// OnOperationParse is called when an operation is parsed
type GraphQLOperationPreNormalizeHook interface {
// PreOperationNormalize is called when an operation is entering normalize
// Returning an error will result in a GraphQL error as a response from the router.
OnOperationParse(op *core.Operation, err error) error
PreOperationNormalize(op *core.Operation) error
}

type GraphQLOperationNormalizeHook interface {
// OnNormalize is called when an operation is normalized
type GraphQLOperationPostNormalizeHook interface {
// PostOperationNormalize is called when an operation is exiting normalize
// Returning an error will result in a GraphQL error as a response from the router.
OnOperationNormalize(op *core.Operation, err error) error
PostOperationNormalize(op *core.Operation, err error) error
}

type GraphQLOperationPlanHook interface {
// OnPlan is called when an operation is planned
type GraphQLOperationPrePlanHook interface {
// PreOperationPlan is called when an operation is entering plan
// Returning an error will result in a GraphQL error as a response from the router.
OnOperationPlan(op *core.Operation, err error) error
PreOperationPlan(op *core.Operation) error
}

type GraphQLOperationPostPlanHook interface {
// PostOperationPlan is called when an operation is exiting plan
// Returning an error will result in a GraphQL error as a response from the router.
PostOperationPlan(op *core.Operation, err error) error
}

// Module Hooks
Expand Down Expand Up @@ -332,6 +353,59 @@ The new module system is not backwards compatible with the old module system. Ex

__All examples are pseudocode and not tested, but they are as close as possible to the final implementation__

## Custom Metrics

This module allows custom metric client to record request latency and error type.

```go
type MyModule struct{}

// Ensure that MyModule implements the RouterPreRequestHook interface
var _ RouterPreRequestHook = (*MyModule)(nil)

func (m *MyModule) PreRouterRequest(req *core.RouterRequest, err error) error {
tags := map[string]string{
"operation_name": req.OperationName,
"operation_type": req.OperationType,
}
m.scope.Tagged(tags).Counter("request.calls").Inc(1)

req.Context = context.WithValue(req.Context, startTimeKey, time.Now())
return nil
}

func (m *MyModule) OnRouterResponse(res *core.RouterResponse, err error) error {
startTime, ok := res.Context.Value(startTimeKey).(time.Time)
if !ok {
return nil
}

tags := map[string]string{
"operation_name": req.OperationName,
"operation_type": req.OperationType,
}

latency := time.Since(startTime)
m.scope.Tagged(tags).Timer("request.latency").Record(latency)

return nil
}

func (m *MyModule) OnRouterError(res *core.RouterResponse, err error) error {
tags := map[string]string{
"operation_name": req.OperationName,
"operation_type": req.OperationType,
}

if typedErr, ok := err.(*core.ErrorType); ok {
tags["error_type"] := typedError.String()
}
m.scope.tagged(tags).Counter("request.error").Inc(1)

return nil
}
```

## Custom Telemetry

This module adds custom attributes to the OpenTelemetry span for each router request. Data can come from the request, the router configuration, or external sources. The example modifies the first span of the router but depending on the hook, the span is different.
Expand Down

0 comments on commit eda719a

Please sign in to comment.