Skip to content

Commit 2ae4147

Browse files
Improve legibility of Golang context propagation docs. (#4313)
* Improve legibility of Golang context propagation docs * Fix tracing heading * Feedback from Lenny and Andrew * Add a backlink for tracing * Sync snippets with sample * Set up snipsync for context prop * Run snipsync * Fix Go index with new references * Update docs/develop/go/observability.mdx --------- Co-authored-by: Lenny Chen <55669665+lennessyy@users.noreply.github.com>
1 parent b904131 commit 2ae4147

6 files changed

Lines changed: 239 additions & 126 deletions

File tree

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
---
2+
id: context-propagation
3+
title: Context Propagation - Go SDK
4+
sidebar_label: Context Propagation
5+
toc_max_heading_level: 4
6+
keywords:
7+
- context propagation
8+
- context propagators
9+
- go sdk
10+
- headers
11+
- tracing
12+
tags:
13+
- Context Propagation
14+
- Go SDK
15+
- Temporal SDKs
16+
description: How to propagate custom key-value data across Workflow, Activity, and Child Workflow boundaries using the Temporal Go SDK.
17+
---
18+
19+
Context propagation lets you pass custom key-value data from a Client to Workflows, and from Workflows to Activities and Child Workflows, without threading it through every function signature. Common use cases include propagating tracing IDs, tenant IDs, auth tokens, or other request-scoped metadata.
20+
21+
{/* TODO: Link to /encyclopedia/context-propagation once that page lands */}
22+
23+
:::tip
24+
25+
If you want to propagate tracing context, check if there is a [built-in tracing interceptor](/develop/go/observability#tracing) for your library before building a custom context propagator.
26+
27+
:::
28+
29+
## How it works
30+
31+
1. **Register** a context propagator on the Client via `ContextPropagators` in [ClientOptions](https://pkg.go.dev/go.temporal.io/sdk/internal#ClientOptions)
32+
2. **Inject** - On outbound calls, the SDK calls `Inject` (from `context.Context`) or `InjectFromWorkflow` (from `workflow.Context`) to serialize values into Temporal headers
33+
3. **Extract** - On inbound calls, the SDK calls `Extract` (into `context.Context`) or `ExtractToWorkflow` (into `workflow.Context`) to deserialize headers back into the context
34+
4. **Access** - Your Workflow and Activity code reads values from the context as usual
35+
36+
## Implement a context propagator
37+
38+
A context propagator implements the [`ContextPropagator`](https://pkg.go.dev/go.temporal.io/sdk/workflow#ContextPropagator) interface:
39+
40+
```go
41+
type ContextPropagator interface {
42+
// Inject writes values from a Go context.Context into headers (Client/Activity side)
43+
Inject(context.Context, HeaderWriter) error
44+
// Extract reads headers into a Go context.Context (Client/Activity side)
45+
Extract(context.Context, HeaderReader) (context.Context, error)
46+
// InjectFromWorkflow writes values from a workflow.Context into headers
47+
InjectFromWorkflow(Context, HeaderWriter) error
48+
// ExtractToWorkflow reads headers into a workflow.Context
49+
ExtractToWorkflow(Context, HeaderReader) (Context, error)
50+
}
51+
```
52+
53+
There are two pairs of methods because Go uses `context.Context` in non-Workflow code (Client, Activities) and `workflow.Context` inside Workflows. You must implement all four methods for values to propagate across every boundary (Client → Workflow → Activity/Child Workflow).
54+
55+
Here is a propagator that carries a custom key-value pair from the Client to Workflows and Activities (from the [context propagation sample](https://github.com/temporalio/samples-go/tree/main/ctxpropagation)):
56+
57+
58+
<!--SNIPSTART samples-go-ctx-propagation-propagator-->
59+
[ctxpropagation/propagator.go](https://github.com/temporalio/samples-go/blob/main/ctxpropagation/propagator.go)
60+
```go
61+
type (
62+
// contextKey is an unexported type used as key for items stored in the
63+
// Context object
64+
contextKey struct{}
65+
66+
// propagator implements the custom context propagator
67+
propagator struct{}
68+
69+
// Values is a struct holding values
70+
Values struct {
71+
Key string `json:"key"`
72+
Value string `json:"value"`
73+
}
74+
)
75+
76+
// PropagateKey is the key used to store the value in the Context object
77+
var PropagateKey = contextKey{}
78+
79+
// HeaderKey is the key used by the propagator to pass values through the
80+
// Temporal server headers
81+
const HeaderKey = "custom-header"
82+
83+
// NewContextPropagator returns a context propagator that propagates a set of
84+
// string key-value pairs across a workflow
85+
func NewContextPropagator() workflow.ContextPropagator {
86+
return &propagator{}
87+
}
88+
89+
// Inject injects values from context into headers for propagation
90+
func (s *propagator) Inject(ctx context.Context, writer workflow.HeaderWriter) error {
91+
value := ctx.Value(PropagateKey)
92+
payload, err := converter.GetDefaultDataConverter().ToPayload(value)
93+
if err != nil {
94+
return err
95+
}
96+
writer.Set(HeaderKey, payload)
97+
return nil
98+
}
99+
100+
// InjectFromWorkflow injects values from context into headers for propagation
101+
func (s *propagator) InjectFromWorkflow(ctx workflow.Context, writer workflow.HeaderWriter) error {
102+
value := ctx.Value(PropagateKey)
103+
payload, err := converter.GetDefaultDataConverter().ToPayload(value)
104+
if err != nil {
105+
return err
106+
}
107+
writer.Set(HeaderKey, payload)
108+
return nil
109+
}
110+
111+
// Extract extracts values from headers and puts them into context
112+
func (s *propagator) Extract(ctx context.Context, reader workflow.HeaderReader) (context.Context, error) {
113+
if value, ok := reader.Get(HeaderKey); ok {
114+
var values Values
115+
if err := converter.GetDefaultDataConverter().FromPayload(value, &values); err != nil {
116+
return ctx, nil
117+
}
118+
ctx = context.WithValue(ctx, PropagateKey, values)
119+
}
120+
121+
return ctx, nil
122+
}
123+
```
124+
<!--SNIPEND-->
125+
126+
## Register the propagator and set context values
127+
128+
Register the propagator on the Client. Then set context values before starting a Workflow:
129+
130+
<!--SNIPSTART samples-go-ctx-propagation-starter-->
131+
[ctxpropagation/starter/main.go](https://github.com/temporalio/samples-go/blob/main/ctxpropagation/starter/main.go)
132+
```go
133+
// The client is a heavyweight object that should be created once per process.
134+
c, err := client.Dial(client.Options{
135+
HostPort: client.DefaultHostPort,
136+
Interceptors: []interceptor.ClientInterceptor{tracingInterceptor},
137+
ContextPropagators: []workflow.ContextPropagator{ctxpropagation.NewContextPropagator()},
138+
})
139+
if err != nil {
140+
log.Fatalln("Unable to create client", err)
141+
}
142+
defer c.Close()
143+
144+
workflowID := "ctx-propagation_" + uuid.New()
145+
workflowOptions := client.StartWorkflowOptions{
146+
ID: workflowID,
147+
TaskQueue: "ctx-propagation",
148+
}
149+
150+
ctx := context.Background()
151+
ctx = context.WithValue(ctx, ctxpropagation.PropagateKey, &ctxpropagation.Values{Key: "test", Value: "tested"})
152+
153+
we, err := c.ExecuteWorkflow(ctx, workflowOptions, ctxpropagation.CtxPropWorkflow)
154+
```
155+
<!--SNIPEND-->
156+
157+
You can also register context propagators through a [Plugin](/develop/plugins-guide) if you are building a reusable library.
158+
159+
## Access propagated values
160+
161+
In your Workflow, the propagated values are available on the `workflow.Context`. When the Workflow starts an Activity, the SDK automatically propagates the same values:
162+
163+
<!--SNIPSTART samples-go-ctx-propagation-workflow-->
164+
[ctxpropagation/workflow.go](https://github.com/temporalio/samples-go/blob/main/ctxpropagation/workflow.go)
165+
```go
166+
// CtxPropWorkflow workflow definition
167+
func CtxPropWorkflow(ctx workflow.Context) (err error) {
168+
ao := workflow.ActivityOptions{
169+
StartToCloseTimeout: 2 * time.Second, // such a short timeout to make sample fail over very fast
170+
}
171+
ctx = workflow.WithActivityOptions(ctx, ao)
172+
173+
if val := ctx.Value(PropagateKey); val != nil {
174+
vals := val.(Values)
175+
workflow.GetLogger(ctx).Info("custom context propagated to workflow", vals.Key, vals.Value)
176+
}
177+
178+
var values Values
179+
if err = workflow.ExecuteActivity(ctx, SampleActivity).Get(ctx, &values); err != nil {
180+
workflow.GetLogger(ctx).Error("Workflow failed.", "Error", err)
181+
return err
182+
}
183+
workflow.GetLogger(ctx).Info("context propagated to activity", values.Key, values.Value)
184+
workflow.GetLogger(ctx).Info("Workflow completed.")
185+
return nil
186+
}
187+
```
188+
<!--SNIPEND-->
189+
190+
<!--SNIPSTART samples-go-ctx-propagation-activity-->
191+
[ctxpropagation/activities.go](https://github.com/temporalio/samples-go/blob/main/ctxpropagation/activities.go)
192+
```go
193+
func SampleActivity(ctx context.Context) (*Values, error) {
194+
if val := ctx.Value(PropagateKey); val != nil {
195+
vals := val.(Values)
196+
return &vals, nil
197+
}
198+
return nil, nil
199+
}
200+
```
201+
<!--SNIPEND-->
202+
203+
You can configure multiple context propagators on a single Client, each responsible for its own set of keys.
204+
205+
## Context propagation over Nexus
206+
207+
Nexus does not use the `ContextPropagator` interface. It relies on a Temporal-agnostic protocol with its own header format (`nexus.Header`, a wrapper around `map[string]string`).
208+
209+
To propagate context over Nexus Operation calls, use interceptors to explicitly serialize and deserialize context into the Nexus header. See the [Nexus Context Propagation sample](https://github.com/temporalio/samples-go/tree/main/nexus-context-propagation).
210+
211+
## Further reading
212+
213+
- [Passing Context with Temporal](https://spiralscout.com/blog/passing-context-with-temporal) - A conceptual guide to middleware and walkthrough of building a context propagator in Go

docs/develop/go/core-application.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ The Temporal Go SDK has APIs to handle equivalent Go constructs:
401401
- `workflow.Selector` This is a replacement for the `select` statement.
402402
Learn more on the [Go SDK Selectors](https://legacy-documentation-sdks.temporal.io/go/selectors) page.
403403
- `workflow.Context` This is a replacement for `context.Context`.
404-
See [Tracing](/develop/go/observability#tracing-and-context-propagation) for more information about context propagation.
404+
See [Tracing](/develop/go/observability#tracing) for more information about context propagation.
405405

406406
## How to develop an Activity Definition in Go {#activity-definition}
407407

docs/develop/go/debugging.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ You can debug production Workflows using:
4747
- [Web UI](/web-ui)
4848
- [Temporal CLI](/cli)
4949
- [Replay](/develop/go/testing-suite#replay)
50-
- [Tracing](/develop/go/observability#tracing-and-context-propagation)
50+
- [Tracing](/develop/go/observability#tracing)
5151
- [Logging](/develop/go/observability#logging)
5252

5353
You can debug and tune Worker performance with metrics and the [Worker performance guide](/develop/worker-performance).

docs/develop/go/index.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,14 @@ Change Workflow Definitions without causing non-deterministic behavior in runnin
116116
Configure and use the Temporal Observability APIs.
117117

118118
- [How to emit metrics](/develop/go/observability#metrics)
119-
- [Tracing and Context Propagation](/develop/go/observability#tracing-and-context-propagation)
119+
- [Tracing](/develop/go/observability#tracing)
120120
- [How to log from a Workflow](/develop/go/observability#logging)
121121
- [How to use Visibility APIs](/develop/go/observability#visibility)
122122

123+
## [Context Propagation](/develop/go/context-propagation)
124+
125+
Propagate custom key-value data across Workflow, Activity, and Child Workflow boundaries using Temporal headers.
126+
123127
## [Debugging](/develop/go/debugging)
124128

125129
Explore various ways to debug your application.

0 commit comments

Comments
 (0)