zuplo 6.71.2 → 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.
@@ -8,27 +8,25 @@ tags:
8
8
  - backends
9
9
  ---
10
10
 
11
- Zuplo fronts a GraphQL API like any other backend: requests pass through a
12
- gateway route with the policies you attach, and your Dev Portal documents the
13
- schema. This guide proxies the endpoint, reports failed operations to analytics,
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 Rewrite handler
19
- - [ ] Tag the route with the `x-graphql` extension so the Portal recognizes it
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
- - [ ] Publish a schema reference and playground with the `graphqlPlugin` for the
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
- Because GraphQL uses a single endpoint, the route uses the
29
- [URL Rewrite handler](../handlers/url-rewrite.mdx) every request goes to
30
- exactly the upstream URL — rather than URL Forward, which appends the incoming
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](#report-failed-operations-to-analytics) for error
48
- reporting. If `/graphql` is already taken, the Portal appends a short suffix
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
- ## Report failed operations to analytics
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 classification table and schema.
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
- :::caution{title="Beta"}
194
-
195
- The GraphQL plugin is in beta.
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
- 1. **Register the plugin**
202
-
203
- Import `graphqlPlugin` and add an instance per API. The `path` is where the
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
- </Stepper>
131
+ const graphqlPath = createPath("/graphql");
281
132
 
282
- ### Plugin options
283
-
284
- All options live under the instance's `options` key:
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
- | Option | Type | Description |
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
- [**Environments**](https://portal.zuplo.com/+/account/project/environments) tab
299
- in the Zuplo Portal — your build is listed there along with the URLs for both
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 allows you to collect and export
7
- telemetry data from your Zuplo API. The OpenTelemetry plugin supports tracing
8
- and logging. Metrics support is planned for future releases.
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 enables you to monitor performance, identify bottlenecks, and
15
- troubleshoot issues in your Zuplo API. The OpenTelemetry plugin automatically
16
- instruments your API to collect trace data. You can send trace data any
17
- OpenTelemetry service such as [Honeycomb](https://honeycomb.io),
18
- [Middleware](https://middleware.io/), [Dynatrace](https://dynatrace.com),
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
- ![Trace visualization](../../public/media/opentelemetry/image-1.png)
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 will
41
- be traced.
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
- ### Custom Tracing
45
+ ## Setup
52
46
 
53
- You can add custom tracing to your Zuplo API by using the OpenTelemetry API. The
54
- example below shows how to implement tracing in a custom policy.
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
- ```ts
57
- import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
58
- import { trace } from "@opentelemetry/api";
50
+ ### Send Traces to Zuplo
59
51
 
60
- export default async function policy(
61
- request: ZuploRequest,
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
- try {
69
- const results = await Promise.all([
70
- fetch("https://api.example.com/hello"),
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
- return request;
76
- } finally {
77
- span.end();
78
- }
79
- });
59
+ export function runtimeInit(runtime: RuntimeExtensions) {
60
+ runtime.addPlugin(new OpenTelemetryPlugin());
80
61
  }
81
62
  ```
82
63
 
83
- This will result in a span that has the following spans:
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
- Logging can be enabled by configuring the `logUrl` property in the OpenTelemetry
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
+ ![Trace visualization](../../public/media/opentelemetry/image-1.png)
120
67
 
121
- ```ts
122
- import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
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
- export default async function policy(
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
- After setting a custom property, all subsequent log messages will include that
135
- property.
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
- These logs will be exported to the configured log endpoint in OpenTelemetry
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
- ## Setup
82
+ ### Export to Your Own Backend
142
83
 
143
- Adding OpenTelemetry to your Zuplo API is done by adding the
144
- `OpenTelemetryPlugin` in the `zuplo.runtime.ts` file as shown below.
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
- ### Advanced Tracing Configuration
123
+ #### Sampling and Post-Processing
184
124
 
185
- The OpenTelemetry plugin supports additional configuration options for advanced
186
- use cases, including sampling and post-processing of spans.
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
- ### Tracing and Logging Configuration
162
+ #### Logs and Traces Together
223
163
 
224
- Logging is only enabled when specifically configured with its own endpoint using
225
- the `logUrl` property. When using both tracing and logging, use the top-level
226
- `traceUrl`, `logUrl`, and `headers` properties instead of the `exporter` object
227
- shown above. The plugin supports both configuration shapes, but they're mutually
228
- exclusive: `exporter` configures tracing only, while `traceUrl` and `logUrl`
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.2",
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.2",
23
- "@zuplo/core": "6.71.2",
24
- "@zuplo/runtime": "6.71.2",
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
  }