Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document relay field logger #4809

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/relay-runtime/store/RelayStoreTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -1385,8 +1385,7 @@ export type RelayFieldLoggerEvent =
| RelayFieldPayloadErrorEvent;

/**
* A handler for events related to @required fields. Currently reports missing
* fields with either `action: LOG` or `action: THROW`.
* A handler for events related to field errors.
*/
export type RelayFieldLogger = (event: RelayFieldLoggerEvent) => void;

Expand Down
155 changes: 155 additions & 0 deletions website/docs/api-reference/relay-runtime/field-logger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@

Relay includes a number of features that allow for granular handling of field errors:

- [`@required`](../../guides/required-directive.md) provides declarative handling of field nullability
- [`@catch`](../../guides/catch-directive.md) allows you to explicitly handle field errors
- [`@throwOnFieldError`](../../guides/throw-on-field-error-directive.md) lets you treat field errors as exceptions
- [Relay Resolvers](../../guides/relay-resolvers/introduction.md) coerce thrown exceptions to null, matching the GraphQL spec

In each of these cases, field errors are handled by Relay. However, it can still be important to track that these errors are occurring in your app, and monitor or resolve them. To enable this, the Relay Environment can be configured with a `relayFieldLogger`. This logger is a function which is called with events each time Relay handles a field-level error.

Providing a field logger looks something like this:
```ts
import {Environment} from "relay-runtime";

const environment = new Environment({
relayFieldLogger: (event) => {
switch(event.kind) {
case "missing_expected_data.log":
// ...
break;
// ... handle other events
}
},
network: // ...
store: // ...
});
```

## Event Types

The Field Logger currently can receive the following events:

## Missing Expected Data Log

Data which Relay expected to be in the store (because it was requested by the parent query/mutation/subscription) was missing. This can happen due to graph relationship changes observed by other queries/mutations, or imperative updates that don't provide all needed data.

See [Graph Relationship Changes](../../debugging/why-null/#graph-relationship-change).

In this case Relay will render with the referenced field as `undefined`.

:::note
This may break with the type contract of Relay's generated types.
:::

To turn this into a hard error for a given fragment/query, you can use [`@throwOnFieldError`](../../guides/throw-on-field-error-directive/)

```ts
export type MissingExpectedDataLogEvent = {
+kind: 'missing_expected_data.log',
+owner: string,
+fieldPath: string,
};
```

## Missing Expected Data Throw

Data which Relay expected to be in the store (because it was requested by the parent query/mutation/subscription) was missing. This can happen due to graph relationship changes observed by other queries/mutations, or imperative updates that don't provide all needed data.

See [Graph Relationship Changes](../../debugging/why-null/#graph-relationship-change).


This event is called `.throw` because the missing data was encountered in a
query/fragment/mutation with [`@throwOnFieldError`](../../guides/throw-on-field-error-directive/)

Relay will throw immediately after logging this event. If you wish to
customize the error being thrown, you may throw your own error.

:::note
Only throw on this event if `handled` is false. Errors that have been handled by a `@catch` directive or by making a resolver null will have `handled: true` and should not trigger a throw.
:::

```ts
export type MissingExpectedDataThrowEvent = {
+kind: 'missing_expected_data.throw',
+owner: string,
+fieldPath: string,
+handled: boolean,
};
```

## Missing Required Field Log

A field was marked as [@required(action: LOG)](../../guides/required-directive.md#action) but was null or missing in the store.

```ts
export type MissingRequiredFieldLogEvent = {
+kind: 'missing_required_field.log',
+owner: string,
+fieldPath: string,
};
```

## Missing Required Field Throw

A field was marked as [@required(action: THROW)](../../guides/required-directive.md#action) but was null or missing in the* store.

Relay will throw immediately after logging this event. If you wish to customize the error being thrown, you may throw your own error.

:::note
Only throw on this event if `handled` is false. Errors that have been
handled by a `@catch` directive or by making a resolver null will have
`handled: true` and should not trigger a throw.
:::

```ts
export type MissingRequiredFieldThrowEvent = {
+kind: 'missing_required_field.throw',
+owner: string,
+fieldPath: string,
+handled: boolean,
};
```

## Relay Resolver Error


A [Relay Resolver](../../guides/relay-resolvers/introduction.md) that is currently being read threw a JavaScript error when it was last evaluated. By default, the value has been coerced to null and passed to the product code.

If [`@throwOnFieldError`](../../guides/throw-on-field-error-directive.md) was used on the parent query/fragment/mutation, you will also receive a runtime exception when the field is read.

:::note
Only throw on this event if `handled` is false. Errors that have been handled by a `@catch` directive or by making a resolver null will have `handled: true` and should not trigger a throw.

```ts
export type RelayResolverErrorEvent = {
+kind: 'relay_resolver.error',
+owner: string,
+fieldPath: string,
+error: Error,
+shouldThrow: boolean,
+handled: boolean,
};
```

## GraphQL Payload Field Error


A field being read by Relay was marked as being in an error state by the [GraphQL response](https://spec.graphql.org/October2021/#sec-Errors.Field-errors)

If the field's parent query/fragment/mutation was annotated with [`@throwOnFieldError`](../../guides/throw-on-field-error-directive.md) and no [`@catch`](../../guides/catch-directive.md) directive was used to catch the error, Relay will throw an error immediately after logging this event.

:::note
Only throw on this event if `handled` is false. Errors that have been handled by a `@catch` directive or by making a resolver null will have `handled: true` and should not trigger a throw.
:::

```ts
export type RelayFieldPayloadErrorEvent = {
+kind: 'relay_field_payload.error',
+owner: string,
+fieldPath: string,
+error: TRelayFieldError,
+shouldThrow: boolean,
+handled: boolean,
};
```
8 changes: 3 additions & 5 deletions website/docs/guides/catch-directive.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ If `name` contains an error - it would be provided in the response data on the
ok: false,
errors: [
{
message: "Couldn't get name",
path: ['viewer', 'name']
}
]
Expand Down Expand Up @@ -73,7 +72,6 @@ query MyQuery {
ok: false,
errors: [
{
message: "Couldn't get name",
path: ['viewer', 'name']
}
]
Expand Down Expand Up @@ -118,7 +116,7 @@ query MyQuery {
ok: false,
errors: [
{
message: "Relay: Missing @required value at path 'viewer.name' in 'MyQuery'.",
path: ["viewer", "name"],
}
]
}
Expand All @@ -139,7 +137,7 @@ it happens with an `@catch` as an ancestor, it will also be caught like so:
ok: false,
errors: [
{
message: "Relay: Missing data for one or more fields in MyQuery",
path: ["viewer", "name"],
}
]
}
Expand All @@ -151,7 +149,7 @@ it happens with an `@catch` as an ancestor, it will also be caught like so:
Using `@throwOnFieldError` enables fields to throw a JavaScript exception when a
field error occurs. By using `@catch` - you tell Relay that you don't want a
JavaScript exception in this case. Instead, you are requesting that the error be
proviced in the data object, with the same behaviors and rules as are listed
provided in the data object, with the same behaviors and rules as are listed
above (including bubbling to a parent field).

It is important to note that you can still use @catch without
Expand Down
10 changes: 1 addition & 9 deletions website/docs/guides/required-directive.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,7 @@ This field is expected to be null sometimes.

### `LOG` (recoverable)

This value is not expected to ever be null, but the component **can still render** if it is. If a field with `action: LOG` is null, the Relay environment logger will receive an event that looks like this:

```javascript
{
name: 'read.missing_required_field',
owner: string, // MyFragmentOrQueryName
fieldPath: string, // path.to.my.field
};
```
This value is not expected to ever be null, but the component **can still render** if it is. If a field with `action: LOG` is null, the [Relay field logger](../api-reference/relay-runtime/field-logger.md#missing-required-field-log) will receive a `missing_required_field.log` event.

### `THROW` (unrecoverable)

Expand Down
Loading