Skip to content
This repository has been archived by the owner on Apr 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1019 from Shopify/ml-graphql-client-logger
Browse files Browse the repository at this point in the history
[GraphQL Client][Feature] Add logger feature and updated retry error messages
  • Loading branch information
melissaluu authored Oct 24, 2023
2 parents 29036ac + 541ed55 commit 34f4f57
Show file tree
Hide file tree
Showing 5 changed files with 432 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/famous-rivers-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopify/graphql-client": minor
---

Added a new `logging` functionality to the client. This feature enables client consumers to log `request/response` and `retry attempt` info if a logger function is provided at initialization. Also, the `retry` error messages were updated for clarity.
31 changes: 30 additions & 1 deletion packages/graphql-client/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GraphQL Client

The GraphQL Client is a generic GraphQL client that can be used directly to interact with a GraphQL API. Client users are expected to provide the full API URL and necessary headers.
The GraphQL Client can be used to interact with any Shopify's GraphQL APIs. Client users are expected to provide the full API URL and necessary headers.

## Initialization

Expand All @@ -24,6 +24,8 @@ const client = createGraphQLClient({
| url | `string` | The Storefront API URL |
| headers | `{[key: string]: string}` | Headers to be included in requests |
| retries? | `number` | The number of HTTP request retries if the request was abandoned or the server responded with a `Too Many Requests (429)` or `Service Unavailable (503)` response. Default value is `0`. Maximum value is `3`. |
| fetchAPI? | `(url: string, init?: {method?: string, headers?: HeaderInit, body?: string}) => Promise<Response>` | A replacement `fetch` function that will be used in all client network requests. By default, the client uses `window.fetch()`. |
| logger? | `(logContent: `[HTTPResponseLog](#httpresponselog)`\|`[HTTPRetryLog](#httpretrylog)`) => void` | A logger function that accepts [log content objects](#log-content-types). This logger will be called in certain conditions with contextual information. |

## Client properties

Expand Down Expand Up @@ -180,3 +182,30 @@ if (response.ok) {
const {errors, data, extensions} = await response.json();
}
```

## Log Content Types

### `HTTPResponseLog`

This log content is sent to the logger whenever a HTTP response is received by the client.

| Property | Type | Description |
| -------- | ------------------------ | ---------------------------------- |
| type | `LogType['HTTP-Response']` | The type of log content. Is always set to `HTTP-Response` |
| content | `{`[requestParams](#requestparams)`: [url, init?], response: Response}` | Contextual data regarding the request and received response |

### `HTTPRetryLog`

This log content is sent to the logger whenever the client attempts to retry HTTP requests.

| Property | Type | Description |
| -------- | ------------------------ | ---------------------------------- |
| type | `LogType['HTTP-Retry']` | The type of log content. Is always set to `HTTP-Retry` |
| content | `{`[requestParams](#requestparams)`: [url, init?], lastResponse?: Response, retryAttempt: number, maxRetries: number}` | Contextual data regarding the upcoming retry attempt. <br /><br/>`requestParams`: [parameters](#requestparams) used in the request<br/>`lastResponse`: previous response <br/> `retryAttempt`: the current retry attempt count <br/> `maxRetries`: the maximum number of retries |

### `RequestParams`

| Property | Type | Description |
| -------- | ------------------------ | ---------------------------------- |
| url | `string` | Requested URL |
| init? | `{method?: string, headers?: HeaderInit, body?: string}` | The request information |
53 changes: 43 additions & 10 deletions packages/graphql-client/src/graphql-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
CustomFetchAPI,
GraphQLClient,
ClientResponse,
LogContentTypes,
ClientConfig,
} from "./types";
import { getErrorMessage } from "./utilities";

Expand Down Expand Up @@ -66,23 +68,42 @@ export function createGraphQLClient<TClientOptions extends ClientOptions>({
url,
fetchAPI = fetch,
retries = 0,
logger,
}: TClientOptions): GraphQLClient {
validateRetries(retries);

const config = {
const config: ClientConfig = {
headers,
url,
retries,
};

const clientLogger = (logContent: LogContentTypes) => {
if (logger) {
logger(logContent);
}
};

const httpFetch = async (
params: Parameters<CustomFetchAPI>,
requestParams: Parameters<CustomFetchAPI>,
count: number,
maxTries: number
maxRetries: number
): ReturnType<GraphQLClient["fetch"]> => {
const nextCount = count + 1;
const maxTries = maxRetries + 1;
let response: Response | undefined;

try {
const response = await fetchAPI(...params);
response = await fetchAPI(...requestParams);

clientLogger({
type: "HTTP-Response",
content: {
requestParams,
response,
},
});

if (
!response.ok &&
RETRIABLE_STATUS_CODES.includes(response.status) &&
Expand All @@ -95,13 +116,26 @@ export function createGraphQLClient<TClientOptions extends ClientOptions>({
} catch (error) {
if (nextCount <= maxTries) {
await sleep(RETRY_WAIT_TIME);
return httpFetch(params, nextCount, maxTries);

clientLogger({
type: "HTTP-Retry",
content: {
requestParams,
lastResponse: response,
retryAttempt: count,
maxRetries,
},
});

return httpFetch(requestParams, nextCount, maxRetries);
}

throw new Error(
`${ERROR_PREFIX} Exceeded maximum number of ${maxTries} network tries. Last message - ${getErrorMessage(
error
)}`
`${ERROR_PREFIX}${
maxRetries > 0
? ` Attempted maximum number of ${maxRetries} network retries. Last message -`
: ""
} ${getErrorMessage(error)}`
);
}
};
Expand Down Expand Up @@ -132,9 +166,8 @@ export function createGraphQLClient<TClientOptions extends ClientOptions>({
body,
},
];
const maxTries = (overrideRetries ?? retries) + 1;

return httpFetch(fetchParams, 1, maxTries);
return httpFetch(fetchParams, 1, overrideRetries ?? retries);
};

const request: GraphQLClient["request"] = async (...props) => {
Expand Down
Loading

0 comments on commit 34f4f57

Please sign in to comment.