zuplo 6.71.3 → 6.71.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/articles/graphql.mdx +47 -202
- package/docs/articles/opentelemetry.mdx +176 -116
- package/package.json +4 -4
|
@@ -8,27 +8,25 @@ tags:
|
|
|
8
8
|
- backends
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
Zuplo
|
|
12
|
-
|
|
13
|
-
schema. This guide
|
|
14
|
-
and publishes a schema reference and playground.
|
|
11
|
+
Zuplo has rich support for GraphQL. Pass your requests through the gateway,
|
|
12
|
+
attach policies, track operations with analytics, and publish documentation for
|
|
13
|
+
your schema in the Dev Portal. This guide walks you through setting it up.
|
|
15
14
|
|
|
16
15
|
:::tip{title="TL;DR"}
|
|
17
16
|
|
|
18
|
-
- [ ] Proxy your GraphQL endpoint through a route with the URL
|
|
19
|
-
|
|
17
|
+
- [ ] Proxy your GraphQL endpoint through a POST `/graphql` route with the URL
|
|
18
|
+
Rewrite handler
|
|
19
|
+
- [ ] Tag the route with the `x-graphql` extension
|
|
20
20
|
- [ ] Surface failed operations with the `graphql-analytics-outbound` policy
|
|
21
|
-
- [ ]
|
|
22
|
-
Developer Portal
|
|
21
|
+
- [ ] Add the `graphqlPlugin` to the Developer Portal
|
|
23
22
|
|
|
24
23
|
:::
|
|
25
24
|
|
|
26
25
|
## Add a GraphQL endpoint to your gateway
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
path.
|
|
27
|
+
Set up the route with a [URL Rewrite handler](../handlers/url-rewrite.mdx) and
|
|
28
|
+
set the rewrite URL to your upstream GraphQL server (for example,
|
|
29
|
+
`https://api.example.com/graphql`).
|
|
32
30
|
|
|
33
31
|
### Add a new GraphQL route
|
|
34
32
|
|
|
@@ -44,10 +42,8 @@ path.
|
|
|
44
42
|
|
|
45
43
|
Click **Add** and select **GraphQL Endpoint**. This creates a `POST /graphql`
|
|
46
44
|
route preconfigured with the URL Rewrite handler, a demo upstream, and the
|
|
47
|
-
[GraphQL Analytics policy](
|
|
48
|
-
reporting.
|
|
49
|
-
(for example `/graphql-b891`) — edit the path to whatever you prefer. GraphQL
|
|
50
|
-
routes show a **GraphQL** badge in the route list instead of an HTTP method.
|
|
45
|
+
[GraphQL Analytics policy](../policies/graphql-analytics-outbound.mdx) for
|
|
46
|
+
error reporting. GraphQL routes show a **GraphQL** badge in the route list.
|
|
51
47
|
|
|
52
48
|
3. **Point it at your upstream**
|
|
53
49
|
|
|
@@ -111,78 +107,9 @@ disable introspection in production.
|
|
|
111
107
|
|
|
112
108
|
:::
|
|
113
109
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
Failed GraphQL operations return `200 OK` with an `errors[]` array in the body
|
|
117
|
-
(the Apollo Server and graphql-yoga convention). HTTP-level analytics read that
|
|
118
|
-
`200` as a success, so those failures never show up on the
|
|
119
|
-
[GraphQL analytics dashboard](../analytics/tabs/graphql.md).
|
|
120
|
-
|
|
121
|
-
The **GraphQL Analytics** policy closes that gap. It reads each response body,
|
|
122
|
-
counts the GraphQL errors, and classifies every one by its `extensions.code` —
|
|
123
|
-
`syntax`, `validation`, `auth`, `timeout`, or `resolver` — so failed operations
|
|
124
|
-
appear on the dashboard as failures, broken down by error class. The response
|
|
125
|
-
passes through to the client unchanged.
|
|
126
|
-
|
|
127
|
-
:::caution{title="Beta"}
|
|
128
|
-
|
|
129
|
-
The GraphQL Analytics policy is in beta. You can use it today, but it may change
|
|
130
|
-
in non-backward compatible ways before the final release.
|
|
131
|
-
|
|
132
|
-
:::
|
|
133
|
-
|
|
134
|
-
New GraphQL endpoints include this policy automatically — the
|
|
135
|
-
[Add a new GraphQL route](#add-a-new-graphql-route) flow attaches a
|
|
136
|
-
`graphql-analytics-outbound` policy to the route's `outbound` chain, so error
|
|
137
|
-
reporting works out of the box. If you instead
|
|
138
|
-
[marked an existing route as GraphQL](#mark-an-existing-route-as-graphql), add a
|
|
139
|
-
`graphql-analytics-outbound` policy and reference it in that route's `outbound`
|
|
140
|
-
list.
|
|
141
|
-
|
|
142
|
-
Tune the policy by editing its `options` in `config/policies.json`. Out of the
|
|
143
|
-
box it classifies the standard Apollo error codes — for example, it reports
|
|
144
|
-
`GRAPHQL_VALIDATION_FAILED` as `validation` and `UNAUTHENTICATED` as `auth` —
|
|
145
|
-
and falls back to `resolver` for anything it doesn't recognize:
|
|
146
|
-
|
|
147
|
-
```json title="config/policies.json"
|
|
148
|
-
{
|
|
149
|
-
"policies": [
|
|
150
|
-
{
|
|
151
|
-
"name": "graphql-analytics",
|
|
152
|
-
"policyType": "graphql-analytics-outbound",
|
|
153
|
-
"handler": {
|
|
154
|
-
"export": "GraphqlAnalyticsOutboundPolicy",
|
|
155
|
-
"module": "$import(@zuplo/runtime)",
|
|
156
|
-
"options": {
|
|
157
|
-
"errorCodeClassification": {
|
|
158
|
-
"RATE_LIMITED": "resolver",
|
|
159
|
-
"NOT_LOGGED_IN": "auth"
|
|
160
|
-
},
|
|
161
|
-
"defaultErrorClass": "resolver",
|
|
162
|
-
"logErrors": true
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
]
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
- `errorCodeClassification` — Maps custom `extensions.code` values your server
|
|
171
|
-
emits to an error class. Entries merge over, and win against, the built-in
|
|
172
|
-
Apollo code map. Defaults to none.
|
|
173
|
-
- `defaultErrorClass` — The class for an error whose code is missing or
|
|
174
|
-
unrecognized. One of `syntax`, `validation`, `auth`, `timeout`, or `resolver`.
|
|
175
|
-
Defaults to `resolver`.
|
|
176
|
-
- `logErrors` — When `true`, also writes a structured warning to the request log
|
|
177
|
-
— the message, code, and path of each error, capped at the first 10 — for
|
|
178
|
-
every errored response. Defaults to `false`.
|
|
179
|
-
- `maxResponseBytes` — The largest response body, in bytes, the policy inspects.
|
|
180
|
-
Larger responses pass through unscanned, so any errors they carry go
|
|
181
|
-
unreported. Defaults to `5242880` (5 MiB).
|
|
182
|
-
|
|
183
|
-
See the
|
|
110
|
+
To fine-tune analytics, see the
|
|
184
111
|
[GraphQL Analytics policy reference](../policies/graphql-analytics-outbound.mdx)
|
|
185
|
-
for the full
|
|
112
|
+
for the full set of options.
|
|
186
113
|
|
|
187
114
|
## Document the API in your Dev Portal
|
|
188
115
|
|
|
@@ -190,128 +117,46 @@ The `@zudoku/plugin-graphql` package renders a browsable type reference and a
|
|
|
190
117
|
playground in your Dev Portal, generated from your schema. Register one instance
|
|
191
118
|
per API.
|
|
192
119
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<Stepper>
|
|
120
|
+
Import `graphqlPlugin` and add an instance per API. The `path` is where the docs
|
|
121
|
+
mount, and `schema` points at your GraphQL API — either a live endpoint URL or a
|
|
122
|
+
path to a schema definition language (SDL) file. Define the `path` once with
|
|
123
|
+
`createPath` (a helper exported from `zudoku`) and reference the same value from
|
|
124
|
+
both the plugin and the navigation link, so the link can never point at a path
|
|
125
|
+
the plugin isn't mounted at:
|
|
200
126
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
docs mount, and `input` points at your GraphQL endpoint:
|
|
205
|
-
|
|
206
|
-
```tsx title="zudoku.config.tsx"
|
|
207
|
-
import { graphqlPlugin } from "@zudoku/plugin-graphql";
|
|
208
|
-
|
|
209
|
-
const config = {
|
|
210
|
-
plugins: [
|
|
211
|
-
graphqlPlugin({
|
|
212
|
-
type: "url",
|
|
213
|
-
input: "https://graphql.example.com/api",
|
|
214
|
-
path: "/graphql/ecommerce",
|
|
215
|
-
options: {
|
|
216
|
-
title: "E-Commerce GraphQL API",
|
|
217
|
-
description: "Products, orders, and customers.",
|
|
218
|
-
},
|
|
219
|
-
}),
|
|
220
|
-
],
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
export default config;
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
With `type: "url"`, the portal fetches the schema via introspection at build
|
|
227
|
-
time, and the playground sends operations to that same URL by default. Point
|
|
228
|
-
`input` at the gateway route from the first half of this guide so readers run
|
|
229
|
-
real queries through your gateway — or keep the playground separate from the
|
|
230
|
-
schema source with `options.playground.endpoint`.
|
|
231
|
-
|
|
232
|
-
Have a GraphQL schema definition language (SDL) file instead of a live
|
|
233
|
-
endpoint? Use `type: "file"` and set `input` to the file's path (for example
|
|
234
|
-
`./schema.graphql`). A file-based schema doesn't tell the plugin where your
|
|
235
|
-
API lives, so also set `options.playground.endpoint` — without it the
|
|
236
|
-
reference pages still render, but the playground asks for an endpoint before
|
|
237
|
-
it can run operations.
|
|
238
|
-
|
|
239
|
-
You can register the plugin more than once to document several schemas, each
|
|
240
|
-
with its own `path`.
|
|
241
|
-
|
|
242
|
-
:::note
|
|
243
|
-
|
|
244
|
-
New projects ship with `@zudoku/plugin-graphql` in `dependencies` already.
|
|
245
|
-
Older projects might need to add it to `docs/package.json`. The plugin
|
|
246
|
-
requires `zudoku` version `0.80.1` or newer.
|
|
247
|
-
[Update the Dev Portal](../dev-portal/updating.mdx) to get the latest
|
|
248
|
-
version.
|
|
249
|
-
|
|
250
|
-
:::
|
|
251
|
-
|
|
252
|
-
2. **Link it in the navigation**
|
|
253
|
-
|
|
254
|
-
Point a navigation link at the instance's `path`. Set `stack: true` so the
|
|
255
|
-
API's own pages render as a stacked sub-navigation instead of expanding
|
|
256
|
-
inline:
|
|
257
|
-
|
|
258
|
-
```tsx title="zudoku.config.tsx"
|
|
259
|
-
navigation: [
|
|
260
|
-
{
|
|
261
|
-
type: "category",
|
|
262
|
-
label: "Documentation",
|
|
263
|
-
items: [
|
|
264
|
-
{
|
|
265
|
-
type: "link",
|
|
266
|
-
label: "E-Commerce API",
|
|
267
|
-
to: "/graphql/ecommerce",
|
|
268
|
-
stack: true,
|
|
269
|
-
},
|
|
270
|
-
],
|
|
271
|
-
},
|
|
272
|
-
];
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
Prefer the API as its own top-level section instead? Drop the link at the top
|
|
276
|
-
level of `navigation` and leave off `stack`. See the
|
|
277
|
-
[navigation reference](../dev-portal/zudoku/configuration/navigation.mdx) for
|
|
278
|
-
all link, category, and stacking options.
|
|
127
|
+
```tsx title="zudoku.config.tsx"
|
|
128
|
+
import { graphqlPlugin } from "@zudoku/plugin-graphql";
|
|
129
|
+
import { createPath } from "zudoku";
|
|
279
130
|
|
|
280
|
-
|
|
131
|
+
const graphqlPath = createPath("/graphql");
|
|
281
132
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
133
|
+
const config = {
|
|
134
|
+
navigation: [
|
|
135
|
+
{
|
|
136
|
+
type: "link",
|
|
137
|
+
label: "GraphQL API",
|
|
138
|
+
to: graphqlPath,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
plugins: [
|
|
142
|
+
graphqlPlugin({
|
|
143
|
+
schema: "./schema.graphql", // Also accepts a URL, e.g. https://...
|
|
144
|
+
path: graphqlPath,
|
|
145
|
+
}),
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export default config;
|
|
150
|
+
```
|
|
285
151
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
| `title` | `string` | Display title for the API's overview page. |
|
|
289
|
-
| `description` | `string` | Short description shown on the overview page. |
|
|
290
|
-
| `showDeprecated` | `boolean` | Include deprecated fields and operations in the reference. |
|
|
291
|
-
| `playground.enabled` | `boolean` | Show the playground. Defaults to `true`. |
|
|
292
|
-
| `playground.endpoint` | `string` | URL the playground sends operations to. Defaults to `input` for `type: "url"`. File schemas have no default; the playground prompts for one until set. |
|
|
293
|
-
| `playground.headers` | `object` | Default headers the playground sends with each operation. |
|
|
152
|
+
You can register the plugin more than once to document several schemas, each
|
|
153
|
+
with its own `path`.
|
|
294
154
|
|
|
295
155
|
## Verify it worked
|
|
296
156
|
|
|
297
|
-
Open the
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
your gateway and your Dev Portal. Send a query through the gateway URL:
|
|
301
|
-
|
|
302
|
-
```bash
|
|
303
|
-
curl https://my-gateway.example.com/graphql \
|
|
304
|
-
-H "Content-Type: application/json" \
|
|
305
|
-
-d '{"query":"{ __typename }"}'
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
A working route returns a response from your upstream, such as
|
|
309
|
-
`{"data":{"__typename":"Query"}}`.
|
|
310
|
-
|
|
311
|
-
For the Dev Portal, open its URL from the **Environments** tab (or run
|
|
312
|
-
`npm run dev` in your `docs/` folder) and go to the plugin's `path` (for example
|
|
313
|
-
`/graphql/ecommerce`). The overview page lists the schema's types, and the
|
|
314
|
-
playground runs operations against your configured endpoint.
|
|
157
|
+
Open your Dev Portal, navigate to the GraphQL API, and run a query in the
|
|
158
|
+
playground. A successful response confirms the gateway is proxying requests and
|
|
159
|
+
the playground is pointed at the right endpoint.
|
|
315
160
|
|
|
316
161
|
## Next steps
|
|
317
162
|
|
|
@@ -3,28 +3,22 @@ title: Zuplo OpenTelemetry
|
|
|
3
3
|
sidebar_label: OpenTelemetry
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Zuplo ships with an OpenTelemetry plugin that
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
Zuplo ships with an OpenTelemetry plugin (`@zuplo/otel`) that instruments your
|
|
7
|
+
API and exports traces and logs in OpenTelemetry format. The quickest way to use
|
|
8
|
+
it is Zuplo's built-in tracing: add the plugin and your traces are stored by
|
|
9
|
+
Zuplo and shown in the portal's **Observability** tab, with no collector to run
|
|
10
|
+
or backend to host. You can also export to your own OpenTelemetry backend, or to
|
|
11
|
+
both at once.
|
|
9
12
|
|
|
10
13
|
<EnterpriseFeature name="OpenTelemetry" />
|
|
11
14
|
|
|
12
15
|
## Tracing
|
|
13
16
|
|
|
14
|
-
Tracing
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
[Jaeger](https://www.jaegertracing.io/), and
|
|
20
|
-
[many more](https://opentelemetry.io/ecosystem/).
|
|
21
|
-
|
|
22
|
-
With tracing enabled on your Zuplo API you will see timings for each request as
|
|
23
|
-
well as spans for plugins, handlers, and policies. The OpenTelemetry plugin
|
|
24
|
-
supports trace propagation (W3C headers by default) so you can trace requests
|
|
25
|
-
all the way from the client to your backend.
|
|
26
|
-
|
|
27
|
-

|
|
17
|
+
Tracing helps you monitor performance, identify bottlenecks, and troubleshoot
|
|
18
|
+
issues in your Zuplo API. The OpenTelemetry plugin automatically instruments
|
|
19
|
+
your API, so you get timings for each request along with spans for policies,
|
|
20
|
+
handlers, and subrequests. It supports trace propagation (W3C headers by
|
|
21
|
+
default), so you can follow a request from the client through to your backend.
|
|
28
22
|
|
|
29
23
|
### What's Traced?
|
|
30
24
|
|
|
@@ -34,114 +28,65 @@ By default, when the OpenTelemetry plugin is enabled, the following is traced:
|
|
|
34
28
|
process the request and send the response.
|
|
35
29
|
- Inbound Policies: The time taken to execute all inbound policies as well as
|
|
36
30
|
each policy is traced.
|
|
37
|
-
- Handler: The request handler is traced
|
|
31
|
+
- Handler: The request handler is traced.
|
|
38
32
|
- Outbound Policies: The time taken to execute all outbound policies as well as
|
|
39
33
|
each policy is traced.
|
|
40
|
-
- Subrequests: Any use of `fetch` within your custom policies or handlers
|
|
41
|
-
|
|
34
|
+
- Subrequests: Any use of `fetch` within your custom policies or handlers is
|
|
35
|
+
traced.
|
|
42
36
|
|
|
43
37
|
### Limitations
|
|
44
38
|
|
|
45
39
|
One important limitation to keep in mind is that the clock will only increment
|
|
46
40
|
when performing I/O operations (for example when calling `fetch`, using the
|
|
47
|
-
Cache APIs, etc.). This is a limitation imposed as a security measure due
|
|
41
|
+
Cache APIs, etc.). This is a limitation imposed as a security measure due to
|
|
48
42
|
Zuplo's serverless, multi-tenant architecture. In practice this shouldn't impact
|
|
49
|
-
your ability to trace as virtually any code that isn't I/O bound is fast.
|
|
43
|
+
your ability to trace, as virtually any code that isn't I/O bound is fast.
|
|
50
44
|
|
|
51
|
-
|
|
45
|
+
## Setup
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
Add the `OpenTelemetryPlugin` in your `zuplo.runtime.ts` file. Where it sends
|
|
48
|
+
data is up to you: Zuplo's built-in storage, your own backend, or both.
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
|
|
58
|
-
import { trace } from "@opentelemetry/api";
|
|
50
|
+
### Send Traces to Zuplo
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
context: ZuploContext,
|
|
63
|
-
) {
|
|
64
|
-
const tracer = trace.getTracer("my-tracer");
|
|
65
|
-
return tracer.startActiveSpan("my-span", async (span) => {
|
|
66
|
-
span.setAttribute("key", "value");
|
|
52
|
+
Add the plugin with no configuration. It sends traces to Zuplo and names the
|
|
53
|
+
service after your project:
|
|
67
54
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
fetch("https://api.example.com/world"),
|
|
72
|
-
]);
|
|
73
|
-
// ...
|
|
55
|
+
```ts title="zuplo.runtime.ts"
|
|
56
|
+
import { OpenTelemetryPlugin } from "@zuplo/otel";
|
|
57
|
+
import { RuntimeExtensions } from "@zuplo/runtime";
|
|
74
58
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
span.end();
|
|
78
|
-
}
|
|
79
|
-
});
|
|
59
|
+
export function runtimeInit(runtime: RuntimeExtensions) {
|
|
60
|
+
runtime.addPlugin(new OpenTelemetryPlugin());
|
|
80
61
|
}
|
|
81
62
|
```
|
|
82
63
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```txt
|
|
86
|
-
|--- my-policy
|
|
87
|
-
| |
|
|
88
|
-
| |--- my-span
|
|
89
|
-
| | |
|
|
90
|
-
| | |--- GET https://api.example.com/hello
|
|
91
|
-
| | |
|
|
92
|
-
| | |--- GET https://api.example.com/world
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Logging
|
|
64
|
+
Deploy your project and open the **Observability** tab to see traces.
|
|
96
65
|
|
|
97
|
-
|
|
98
|
-
plugin configuration, as shown in
|
|
99
|
-
[Tracing and Logging Configuration](#tracing-and-logging-configuration). When
|
|
100
|
-
enabled, logs will be exported to the specified endpoint in OpenTelemetry
|
|
101
|
-
format.
|
|
102
|
-
|
|
103
|
-
To add OpenTelemetry logs in your Zuplo handlers and policies, you can use the
|
|
104
|
-
`context.log` object as shown below:
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
|
|
108
|
-
|
|
109
|
-
export default async function policy(
|
|
110
|
-
request: ZuploRequest,
|
|
111
|
-
context: ZuploContext,
|
|
112
|
-
) {
|
|
113
|
-
context.log.info("Hello World");
|
|
114
|
-
return request;
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
You can also set additional custom log properties using
|
|
119
|
-
`context.log.setLogProperties!`:
|
|
66
|
+

|
|
120
67
|
|
|
121
|
-
|
|
122
|
-
|
|
68
|
+
Each trace is tagged with the account, project, deployment, and environment it
|
|
69
|
+
ran in, plus the request ID (the `zp-rid` value) that also appears on your
|
|
70
|
+
[logs](#logging), so you can move between a request's logs and its trace. How
|
|
71
|
+
long traces are kept depends on your plan.
|
|
123
72
|
|
|
124
|
-
|
|
125
|
-
request: ZuploRequest,
|
|
126
|
-
context: ZuploContext,
|
|
127
|
-
) {
|
|
128
|
-
context.log.setLogProperties!({ customProperty: "value" });
|
|
129
|
-
context.log.info("Hello World");
|
|
130
|
-
return request;
|
|
131
|
-
}
|
|
132
|
-
```
|
|
73
|
+
:::note
|
|
133
74
|
|
|
134
|
-
|
|
135
|
-
|
|
75
|
+
Traces reach Zuplo only from deployed environments. During local development
|
|
76
|
+
(`zuplo dev`) there is no Zuplo ingest to send to, so the plugin logs a startup
|
|
77
|
+
warning and skips the Zuplo destination. Any other destinations you configure
|
|
78
|
+
still run.
|
|
136
79
|
|
|
137
|
-
|
|
138
|
-
format. The log message will be in the `message` field and the custom properties
|
|
139
|
-
will be in the `attributes` field.
|
|
80
|
+
:::
|
|
140
81
|
|
|
141
|
-
|
|
82
|
+
### Export to Your Own Backend
|
|
142
83
|
|
|
143
|
-
|
|
144
|
-
|
|
84
|
+
To send traces to an OpenTelemetry service such as
|
|
85
|
+
[Honeycomb](https://honeycomb.io), [Middleware](https://middleware.io/),
|
|
86
|
+
[Dynatrace](https://dynatrace.com), [Jaeger](https://www.jaegertracing.io/), or
|
|
87
|
+
[others](https://opentelemetry.io/ecosystem/), configure an `exporter` with the
|
|
88
|
+
service's `url` and `headers`. It's common for providers to use a header for
|
|
89
|
+
authorization. Configuring an `exporter` sends traces there instead of Zuplo.
|
|
145
90
|
|
|
146
91
|
:::warning{title="OpenTelemetry Protocol"}
|
|
147
92
|
|
|
@@ -153,11 +98,6 @@ convert the JSON format to the format required by your service.
|
|
|
153
98
|
|
|
154
99
|
:::
|
|
155
100
|
|
|
156
|
-
### Basic Tracing Configuration
|
|
157
|
-
|
|
158
|
-
For most providers you will set values for `exporter.url` and
|
|
159
|
-
`exporter.headers`. It's common for providers to use a header for authorization.
|
|
160
|
-
|
|
161
101
|
```ts title="zuplo.runtime.ts"
|
|
162
102
|
import { OpenTelemetryPlugin } from "@zuplo/otel";
|
|
163
103
|
import { RuntimeExtensions, environment } from "@zuplo/runtime";
|
|
@@ -180,10 +120,10 @@ export function runtimeInit(runtime: RuntimeExtensions) {
|
|
|
180
120
|
}
|
|
181
121
|
```
|
|
182
122
|
|
|
183
|
-
|
|
123
|
+
#### Sampling and Post-Processing
|
|
184
124
|
|
|
185
|
-
The
|
|
186
|
-
|
|
125
|
+
The plugin supports additional options for advanced use cases, including head
|
|
126
|
+
sampling and post-processing of spans before export.
|
|
187
127
|
|
|
188
128
|
```ts title="zuplo.runtime.ts"
|
|
189
129
|
import { OpenTelemetryPlugin } from "@zuplo/otel";
|
|
@@ -219,14 +159,13 @@ export function runtimeInit(runtime: RuntimeExtensions) {
|
|
|
219
159
|
}
|
|
220
160
|
```
|
|
221
161
|
|
|
222
|
-
|
|
162
|
+
#### Logs and Traces Together
|
|
223
163
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
configure tracing and logging together with a shared set of `headers`.
|
|
164
|
+
To export logs as well as traces, use the top-level `traceUrl`, `logUrl`, and
|
|
165
|
+
`headers` properties instead of the `exporter` object. The plugin supports both
|
|
166
|
+
shapes, but they're mutually exclusive: `exporter` configures tracing only,
|
|
167
|
+
while `traceUrl` and `logUrl` configure tracing and logging together with a
|
|
168
|
+
shared set of `headers`. See [Logging](#logging) for how to emit log records.
|
|
230
169
|
|
|
231
170
|
```ts title="zuplo.runtime.ts"
|
|
232
171
|
import { OpenTelemetryPlugin } from "@zuplo/otel";
|
|
@@ -251,3 +190,124 @@ export function runtimeInit(runtime: RuntimeExtensions) {
|
|
|
251
190
|
);
|
|
252
191
|
}
|
|
253
192
|
```
|
|
193
|
+
|
|
194
|
+
### Send Traces to Zuplo and Your Own Backend
|
|
195
|
+
|
|
196
|
+
You aren't limited to one destination. To deliver traces to Zuplo and your own
|
|
197
|
+
backend at the same time, add a span processor for each. Zuplo is represented by
|
|
198
|
+
a `ZuploSpanExporter`. The processors batch and flush independently, so a slow
|
|
199
|
+
or failing backend doesn't hold up the other:
|
|
200
|
+
|
|
201
|
+
```ts title="zuplo.runtime.ts"
|
|
202
|
+
import {
|
|
203
|
+
OpenTelemetryPlugin,
|
|
204
|
+
OTLPSpanExporter,
|
|
205
|
+
BatchTraceSpanProcessor,
|
|
206
|
+
ZuploSpanExporter,
|
|
207
|
+
} from "@zuplo/otel";
|
|
208
|
+
import { RuntimeExtensions, environment } from "@zuplo/runtime";
|
|
209
|
+
|
|
210
|
+
export function runtimeInit(runtime: RuntimeExtensions) {
|
|
211
|
+
runtime.addPlugin(
|
|
212
|
+
new OpenTelemetryPlugin({
|
|
213
|
+
service: { name: "my-api" },
|
|
214
|
+
spanProcessors: [
|
|
215
|
+
new BatchTraceSpanProcessor(
|
|
216
|
+
new OTLPSpanExporter({
|
|
217
|
+
url: "https://otel-collector.example.com/v1/traces",
|
|
218
|
+
headers: { "api-key": environment.OTEL_API_KEY },
|
|
219
|
+
}),
|
|
220
|
+
),
|
|
221
|
+
new BatchTraceSpanProcessor(new ZuploSpanExporter()),
|
|
222
|
+
],
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Logging
|
|
229
|
+
|
|
230
|
+
The plugin can also export logs in OpenTelemetry format. Logs are sent to your
|
|
231
|
+
own endpoint configured with the `logUrl` property (see
|
|
232
|
+
[Logs and Traces Together](#logs-and-traces-together)); Zuplo's built-in storage
|
|
233
|
+
covers traces today, with managed logs and metrics planned for future releases.
|
|
234
|
+
|
|
235
|
+
To emit OpenTelemetry logs from your handlers and policies, use the
|
|
236
|
+
`context.log` object:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
|
|
240
|
+
|
|
241
|
+
export default async function policy(
|
|
242
|
+
request: ZuploRequest,
|
|
243
|
+
context: ZuploContext,
|
|
244
|
+
) {
|
|
245
|
+
context.log.info("Hello World");
|
|
246
|
+
return request;
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
You can also set additional custom log properties using
|
|
251
|
+
`context.log.setLogProperties!`:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
|
|
255
|
+
|
|
256
|
+
export default async function policy(
|
|
257
|
+
request: ZuploRequest,
|
|
258
|
+
context: ZuploContext,
|
|
259
|
+
) {
|
|
260
|
+
context.log.setLogProperties!({ customProperty: "value" });
|
|
261
|
+
context.log.info("Hello World");
|
|
262
|
+
return request;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
After setting a custom property, all subsequent log messages will include that
|
|
267
|
+
property. These logs are exported to the configured log endpoint in
|
|
268
|
+
OpenTelemetry format: the log message is in the `message` field and the custom
|
|
269
|
+
properties are in the `attributes` field.
|
|
270
|
+
|
|
271
|
+
## Custom Tracing
|
|
272
|
+
|
|
273
|
+
You can add custom tracing to your Zuplo API using the OpenTelemetry API. The
|
|
274
|
+
example below shows how to implement tracing in a custom policy.
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
|
|
278
|
+
import { trace } from "@opentelemetry/api";
|
|
279
|
+
|
|
280
|
+
export default async function policy(
|
|
281
|
+
request: ZuploRequest,
|
|
282
|
+
context: ZuploContext,
|
|
283
|
+
) {
|
|
284
|
+
const tracer = trace.getTracer("my-tracer");
|
|
285
|
+
return tracer.startActiveSpan("my-span", async (span) => {
|
|
286
|
+
span.setAttribute("key", "value");
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const results = await Promise.all([
|
|
290
|
+
fetch("https://api.example.com/hello"),
|
|
291
|
+
fetch("https://api.example.com/world"),
|
|
292
|
+
]);
|
|
293
|
+
// ...
|
|
294
|
+
|
|
295
|
+
return request;
|
|
296
|
+
} finally {
|
|
297
|
+
span.end();
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
This will result in the following spans:
|
|
304
|
+
|
|
305
|
+
```txt
|
|
306
|
+
|--- my-policy
|
|
307
|
+
| |
|
|
308
|
+
| |--- my-span
|
|
309
|
+
| | |
|
|
310
|
+
| | |--- GET https://api.example.com/hello
|
|
311
|
+
| | |
|
|
312
|
+
| | |--- GET https://api.example.com/world
|
|
313
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zuplo",
|
|
3
|
-
"version": "6.71.
|
|
3
|
+
"version": "6.71.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The programmable API Gateway",
|
|
6
6
|
"author": "Zuplo, Inc.",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"zuplo": "zuplo.js"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@zuplo/cli": "6.71.
|
|
23
|
-
"@zuplo/core": "6.71.
|
|
24
|
-
"@zuplo/runtime": "6.71.
|
|
22
|
+
"@zuplo/cli": "6.71.6",
|
|
23
|
+
"@zuplo/core": "6.71.6",
|
|
24
|
+
"@zuplo/runtime": "6.71.6",
|
|
25
25
|
"@zuplo/test": "1.4.0"
|
|
26
26
|
}
|
|
27
27
|
}
|