diff --git a/.github/workflows/temporal-go-native.yml b/.github/workflows/temporal-go-native.yml new file mode 100644 index 00000000..0e3bb3e7 --- /dev/null +++ b/.github/workflows/temporal-go-native.yml @@ -0,0 +1,39 @@ +name: Temporal Go native tracing + +on: + pull_request: + branches: [main] + paths: + - 'examples/frameworks/temporal-go/**' + - 'src/pages/docs/integrations/frameworks/temporal-go.mdx' + - 'src/lib/integrations.mjs' + - 'scripts/integrations.test.mjs' + - '.github/workflows/temporal-go-native.yml' + +permissions: + contents: read + +jobs: + otlp-contract: + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - go: '1.25.4' + sdk: 'v1.46.0' + - go: '1.26.0' + sdk: 'v1.50.0' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + cache-dependency-path: examples/frameworks/temporal-go/go.sum + - name: Test released SDK against loopback OTLP receiver + working-directory: examples/frameworks/temporal-go + run: | + go mod edit -go=${{ matrix.go }} -require=go.temporal.io/sdk@${{ matrix.sdk }} + go mod tidy + go test -race ./... diff --git a/examples/frameworks/temporal-go/README.md b/examples/frameworks/temporal-go/README.md new file mode 100644 index 00000000..df643ae9 --- /dev/null +++ b/examples/frameworks/temporal-go/README.md @@ -0,0 +1,7 @@ +# Temporal Go tracing example + +Runnable source for the [Temporal Go integration guide](../../../src/pages/docs/integrations/frameworks/temporal-go.mdx). The example uses Temporal's official OpenTelemetry extension, not a custom interceptor. It exports only traces. + +Requires Go 1.25.4 or later and a Temporal server. Set `FI_API_KEY`, `FI_SECRET_KEY`, and `FI_PROJECT_NAME` in both terminals. Set `TEMPORAL_ADDRESS` for a non-local cluster; the Temporal SDK defaults to localhost:7233. `FI_TRACES_URL` optionally replaces the default Future AGI traces URL. Then run `go run . worker` and `go run . starter Ada` in separate terminals. + +Run `go test ./...` to test the provider and workflow/activity span parentage against a loopback OTLP/HTTP receiver using synthetic headers; no account or Temporal server is needed. The activity returns a static greeting. Instrument real model and tool calls inside your activities, not workflow code. diff --git a/examples/frameworks/temporal-go/go.mod b/examples/frameworks/temporal-go/go.mod new file mode 100644 index 00000000..3b4b08f1 --- /dev/null +++ b/examples/frameworks/temporal-go/go.mod @@ -0,0 +1,47 @@ +module example.com/temporal-futureagi + +go 1.25.4 + +require ( + go.opentelemetry.io/otel v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 + go.opentelemetry.io/otel/sdk v1.43.0 + go.opentelemetry.io/proto/otlp v1.10.0 + go.temporal.io/sdk v1.46.0 + go.temporal.io/sdk/contrib/opentelemetry v0.8.1 + google.golang.org/protobuf v1.36.11 +) + +require ( + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/nexus-rpc/nexus-proto-annotations v0.1.0 // indirect + github.com/nexus-rpc/sdk-go v0.6.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/robfig/cron v1.2.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.11.1 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.temporal.io/api v1.63.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/grpc v1.82.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/examples/frameworks/temporal-go/go.sum b/examples/frameworks/temporal-go/go.sum new file mode 100644 index 00000000..0f528534 --- /dev/null +++ b/examples/frameworks/temporal-go/go.sum @@ -0,0 +1,134 @@ +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/nexus-rpc/nexus-proto-annotations v0.1.0 h1:2fELd+9sqUtNu6Fg//pw8YFsxOvp8vZ8hfP0nHhNI80= +github.com/nexus-rpc/nexus-proto-annotations v0.1.0/go.mod h1:n3UjF1bPCW8llR8tHvbxJ+27yPWrhpo8w/Yg1IOuY0Y= +github.com/nexus-rpc/sdk-go v0.6.0 h1:QRgnP2zTbxEbiyWG/aXH8uSC5LV/Mg1fqb19jb4DBlo= +github.com/nexus-rpc/sdk-go v0.6.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.temporal.io/api v1.63.0 h1:YZFOTA0/thRUIUC4qunAWdHhPh/IG4vy/+WjfEvT+ZE= +go.temporal.io/api v1.63.0/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q= +go.temporal.io/sdk v1.46.0 h1:zD2l907+4iVkLsnJZwFj/oIIjYsoqyjsHlKO/3tDKoU= +go.temporal.io/sdk v1.46.0/go.mod h1:x3v/9ImVh469kiHspoq1xgLdPnetbfuCAm+Y1+sUtIo= +go.temporal.io/sdk/contrib/opentelemetry v0.8.1 h1:wmQnxBWUsQQN6QihaEuUmsn8ZK6d+2G9oQF5bN4ObiY= +go.temporal.io/sdk/contrib/opentelemetry v0.8.1/go.mod h1:NnJgL/EwJIaWZVx4Vmb/qMh18a0fTu00VG/ojQ7tHPY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/frameworks/temporal-go/main.go b/examples/frameworks/temporal-go/main.go new file mode 100644 index 00000000..0c586f0b --- /dev/null +++ b/examples/frameworks/temporal-go/main.go @@ -0,0 +1,121 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.temporal.io/sdk/client" + temporalotel "go.temporal.io/sdk/contrib/opentelemetry" + "go.temporal.io/sdk/interceptor" + "go.temporal.io/sdk/worker" + "go.temporal.io/sdk/workflow" +) + +const queue = "futureagi-example" + +func provider(ctx context.Context, endpoint, apiKey, secretKey, project string) (*sdktrace.TracerProvider, error) { + if endpoint == "" || apiKey == "" || secretKey == "" || project == "" { + return nil, errors.New("FI_TRACES_URL, FI_API_KEY, FI_SECRET_KEY and FI_PROJECT_NAME are required") + } + exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(endpoint), otlptracehttp.WithHeaders(map[string]string{ + "X-Api-Key": apiKey, "X-Secret-Key": secretKey, + })) + if err != nil { + return nil, err + } + return sdktrace.NewTracerProvider( + sdktrace.WithResource(resource.NewWithAttributes("", attribute.String("project_name", project), attribute.String("project_type", "observe"), attribute.String("service.name", "temporal-go-example"))), + sdktrace.WithBatcher(exporter), + ), nil +} + +// Workflow code is deterministic. Put network calls and their instrumentation in activities. +func GreetingWorkflow(ctx workflow.Context, name string) (string, error) { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{StartToCloseTimeout: time.Minute}) + var result string + err := workflow.ExecuteActivity(ctx, GreetingActivity, name).Get(ctx, &result) + return result, err +} + +func GreetingActivity(ctx context.Context, name string) (string, error) { + // Replace this activity's body with your model/tool call and its Go instrumentor. + return "Hello, " + name, nil +} + +func run(ctx context.Context, mode, name string, p *sdktrace.TracerProvider) error { + tracing, err := temporalotel.NewTracingInterceptor(temporalotel.TracerOptions{Tracer: p.Tracer("temporal-sdk-go")}) + if err != nil { + return err + } + c, err := client.Dial(client.Options{ + HostPort: os.Getenv("TEMPORAL_ADDRESS"), + Interceptors: []interceptor.ClientInterceptor{tracing}, + }) + if err != nil { + return err + } + defer c.Close() + if mode == "worker" { + w := worker.New(c, queue, worker.Options{}) + w.RegisterWorkflow(GreetingWorkflow) + w.RegisterActivity(GreetingActivity) + return w.Run(worker.InterruptCh()) + } + if mode != "starter" { + return fmt.Errorf("mode must be worker or starter") + } + execution, err := c.ExecuteWorkflow(ctx, client.StartWorkflowOptions{ + ID: fmt.Sprintf("greeting-%d", time.Now().UnixNano()), TaskQueue: queue, + }, GreetingWorkflow, name) + if err != nil { + return err + } + var result string + if err := execution.Get(ctx, &result); err != nil { + return err + } + fmt.Println(result) + return nil +} + +func main() { + if err := execute(); err != nil { + log.Fatal(err) + } +} + +func execute() error { + ctx := context.Background() + endpoint := os.Getenv("FI_TRACES_URL") + if endpoint == "" { + endpoint = "https://api.futureagi.com/tracer/v1/traces" + } + p, err := provider(ctx, endpoint, os.Getenv("FI_API_KEY"), os.Getenv("FI_SECRET_KEY"), os.Getenv("FI_PROJECT_NAME")) + if err != nil { + return err + } + defer func() { + flushCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := p.Shutdown(flushCtx); err != nil { + log.Printf("tracing shutdown: %v", err) + } + }() + mode := "starter" + if len(os.Args) > 1 { + mode = os.Args[1] + } + name := "Ada" + if len(os.Args) > 2 { + name = os.Args[2] + } + return run(ctx, mode, name, p) +} diff --git a/examples/frameworks/temporal-go/main_test.go b/examples/frameworks/temporal-go/main_test.go new file mode 100644 index 00000000..ce17811a --- /dev/null +++ b/examples/frameworks/temporal-go/main_test.go @@ -0,0 +1,123 @@ +package main + +import ( + "context" + "encoding/hex" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + collectortrace "go.opentelemetry.io/proto/otlp/collector/trace/v1" + temporalotel "go.temporal.io/sdk/contrib/opentelemetry" + "go.temporal.io/sdk/interceptor" + "go.temporal.io/sdk/testsuite" + "go.temporal.io/sdk/worker" + "google.golang.org/protobuf/proto" +) + +// Exercise the official interceptor and a local OTLP/HTTP receiver; no Temporal server or real credentials. +func TestWorkflowExport(t *testing.T) { + received := make(chan *collectortrace.ExportTraceServiceRequest, 8) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/tracer/v1/traces" || r.Header.Get("X-Api-Key") != "synthetic-key" || r.Header.Get("X-Secret-Key") != "synthetic-secret" { + http.Error(w, "wrong OTLP route or headers", http.StatusBadRequest) + return + } + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + msg := new(collectortrace.ExportTraceServiceRequest) + if err := proto.Unmarshal(body, msg); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + received <- msg + w.Header().Set("Content-Type", "application/x-protobuf") + body, _ = proto.Marshal(&collectortrace.ExportTraceServiceResponse{}) + _, _ = w.Write(body) + })) + defer server.Close() + p, err := provider(context.Background(), server.URL+"/tracer/v1/traces", "synthetic-key", "synthetic-secret", "temporal-go-test") + if err != nil { + t.Fatal(err) + } + tracing, err := temporalotel.NewTracingInterceptor(temporalotel.TracerOptions{Tracer: p.Tracer("temporal-sdk-go")}) + if err != nil { + t.Fatal(err) + } + var suite testsuite.WorkflowTestSuite + env := suite.NewTestWorkflowEnvironment() + env.RegisterWorkflow(GreetingWorkflow) + env.RegisterActivity(GreetingActivity) + env.SetWorkerOptions(worker.Options{Interceptors: []interceptor.WorkerInterceptor{tracing}}) + env.ExecuteWorkflow(GreetingWorkflow, "Ada") + if err := env.GetWorkflowError(); err != nil { + t.Fatal(err) + } + var result string + if err := env.GetWorkflowResult(&result); err != nil || result != "Hello, Ada" { + t.Fatalf("result %q: %v", result, err) + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := p.Shutdown(ctx); err != nil { + t.Fatal(err) + } + var spans []string + var traceIDs = map[string]string{} + var spanIDs = map[string]string{} + var parentIDs = map[string]string{} + for { + select { + case req := <-received: + for _, rs := range req.ResourceSpans { + attrs := rs.Resource.GetAttributes() + hasProject := false + for _, a := range attrs { + if a.Key == "project_name" && a.Value.GetStringValue() == "temporal-go-test" { + hasProject = true + } + } + if !hasProject { + t.Fatal("project_name missing from OTLP resource") + } + for _, ss := range rs.ScopeSpans { + for _, span := range ss.Spans { + spans = append(spans, span.Name) + traceIDs[span.Name] = hex.EncodeToString(span.TraceId) + spanIDs[span.Name] = hex.EncodeToString(span.SpanId) + parentIDs[span.Name] = hex.EncodeToString(span.ParentSpanId) + if len(span.TraceId) != 16 { + t.Fatalf("invalid trace id: %s", span.Name) + } + } + } + } + default: + if !strings.Contains(strings.Join(spans, ","), "RunWorkflow:GreetingWorkflow") || !strings.Contains(strings.Join(spans, ","), "RunActivity:GreetingActivity") { + t.Fatalf("missing Temporal workflow/activity spans: %v", spans) + } + if len(spans) != 3 { + t.Fatalf("unexpected duplicate or missing spans: %v", spans) + } + if traceIDs["RunWorkflow:GreetingWorkflow"] != traceIDs["RunActivity:GreetingActivity"] || parentIDs["StartActivity:GreetingActivity"] != spanIDs["RunWorkflow:GreetingWorkflow"] || parentIDs["RunActivity:GreetingActivity"] != spanIDs["StartActivity:GreetingActivity"] { + t.Fatalf("activity is not child of workflow: traceIDs=%v spanIDs=%v parentIDs=%v", traceIDs, spanIDs, parentIDs) + } + return + } + } +} + +func TestMissingConfiguration(t *testing.T) { + if _, err := provider(context.Background(), "", "key", "secret", "project"); err == nil { + t.Fatal("accepted empty OTLP endpoint") + } + if _, err := provider(context.Background(), "http://localhost/tracer/v1/traces", "", "secret", "project"); err == nil { + t.Fatal("accepted empty API key") + } +} diff --git a/scripts/integrations.test.mjs b/scripts/integrations.test.mjs index 090d14b1..8b3cd30c 100644 --- a/scripts/integrations.test.mjs +++ b/scripts/integrations.test.mjs @@ -190,3 +190,12 @@ test('public prose and catalog labels contain no internal QA label or Alpha rele for (const item of [...integrations,...integrationCategories,...integrationAids]) assert.doesNotMatch(JSON.stringify(item),/\bnot\s+tested\b|\balpha\b/i); assert.equal(publicProse('Beta. `pkg==1.0.0-alpha`\n```python\nmodel="alpha"\n```\n'),'Beta. \n\n'); }); + +test('Temporal Go uses the official extension and authenticated traces-only OTLP export', () => { + const page = read('src/pages/docs/integrations/frameworks/temporal-go.mdx'); + const catalog = integrations.find(i => i.id === 'temporal-go'); + assert.equal(catalog?.method, 'OpenTelemetry'); + for (const term of ['go.temporal.io/sdk/contrib/opentelemetry@v0.8.1', 'v1.46.0', 'otel/exporters/otlp/otlptrace/otlptracehttp@v1.43.0', 'https://api.futureagi.com/tracer/v1/traces', 'X-Api-Key', 'X-Secret-Key', 'project_name', 'NewTracingInterceptor', 'RunActivity', 'provider.Shutdown']) assert.ok(page.includes(term), term); + assert.doesNotMatch(page, /AddOtlpMetricExporter|AddOtlpLogExporter|traceai-temporal-go|Live vendor verification/); + assert.match(read('src/pages/docs/integrations/frameworks/temporal.mdx'), /frameworks\/temporal-go/); +}); diff --git a/src/lib/integrations.mjs b/src/lib/integrations.mjs index 280fb359..49be592c 100644 --- a/src/lib/integrations.mjs +++ b/src/lib/integrations.mjs @@ -104,6 +104,7 @@ export const integrations = [ { id: 'ruby-llm', name: 'RubyLLM', href: '/docs/integrations/frameworks/ruby-llm', category: 'frameworks', summary: 'Trace RubyLLM calls with the third-party OTel gem.', method: 'OpenTelemetry', logo: 'ruby-llm', aliases: ['Ruby LLM', 'ruby_llm'] }, { id: 'swiftide', name: 'Swiftide', href: '/docs/integrations/frameworks/swiftide', category: 'frameworks', summary: 'Send typed Rust agent, model and tool spans through an OpenTelemetry Collector.', method: 'OpenTelemetry', logo: 'swiftide', aliases: ['Rust'] }, { id: 'temporal', name: 'Temporal', href: '/docs/integrations/frameworks/temporal', category: 'frameworks', summary: 'Follow Python workflows, activities and model calls.', method: 'OTel + traceAI', logo: 'temporal' }, + { id: 'temporal-go', name: 'Temporal Go', href: '/docs/integrations/frameworks/temporal-go', category: 'frameworks', summary: 'Trace Go workflows and activities with the official OTel interceptor.', method: 'OpenTelemetry', logo: 'temporal', aliases: ['Go Temporal'] }, { id: 'watsonx-orchestrate-adk', name: 'watsonx Orchestrate ADK', href: '/docs/integrations/frameworks/watsonx-orchestrate-adk', category: 'frameworks', summary: 'Forward Agentic SDK spans through a collector.', method: 'OTel collector', logo: 'watsonx', aliases: ['IBM', 'Agentic SDK'] }, { id: 'langfuse-sdk', name: 'Langfuse SDK', href: '/docs/sdk/tracing/langfuse-integration', category: 'frameworks', summary: 'Send new traces with Langfuse SDK instrumentation.', method: 'SDK tracing', logo: 'langfuse', sharedGuide: true }, diff --git a/src/pages/docs/integrations/frameworks/temporal-go.mdx b/src/pages/docs/integrations/frameworks/temporal-go.mdx new file mode 100644 index 00000000..01d32392 --- /dev/null +++ b/src/pages/docs/integrations/frameworks/temporal-go.mdx @@ -0,0 +1,96 @@ +--- +title: "Trace Temporal Go workflows" +description: "Send Temporal Go workflow and activity spans to Future AGI with Temporal's OpenTelemetry interceptor and an OTLP/HTTP exporter." +--- + +Temporal's [official Go OpenTelemetry extension](https://pkg.go.dev/go.temporal.io/sdk/contrib/opentelemetry) traces workflow and activity execution. Send those spans to Future AGI to follow an execution across the starter and worker processes. For the Python SDK, see [Temporal Python tracing](/docs/integrations/frameworks/temporal). + +The interceptor creates `StartWorkflow`, `RunWorkflow`, `StartActivity`, and `RunActivity` spans; it propagates the W3C trace context through Temporal headers. Model and tool spans belong in activities and require instrumentation for the library you use. This guide exports **traces only**, not Temporal metrics or logs. See the [runnable Go example](https://github.com/future-agi/docs/tree/main/examples/frameworks/temporal-go) for complete source and an OTLP receiver test. + + +Workflow IDs, activity names, baggage and exception messages may appear in exported traces. Keep credentials and personal data out of these values; remove or redact sensitive attributes before export if your application requires it. + + +## Set up + + + + + +In your application module, install the [independently versioned extension](https://github.com/temporalio/sdk-go/tree/main/contrib/opentelemetry): + +```bash +go get go.temporal.io/sdk@v1.46.0 go.temporal.io/sdk/contrib/opentelemetry@v0.8.1 +go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp@v1.43.0 +``` + +This example uses the extension's declared SDK dependency (`v1.46.0`). Use Go 1.25.4 or later; the latest SDK may require a newer Go toolchain. These modules can be updated independently. + + + + +Create an OTLP trace exporter and a tracer provider with a **resource** attribute named `project_name`. Put both Future AGI credentials in exporter request headers, not span attributes. The following is excerpted from [`main.go`](https://github.com/future-agi/docs/blob/main/examples/frameworks/temporal-go/main.go): + +```go +exporter, err := otlptracehttp.New(ctx, + otlptracehttp.WithEndpointURL("https://api.futureagi.com/tracer/v1/traces"), + otlptracehttp.WithHeaders(map[string]string{ + "X-Api-Key": os.Getenv("FI_API_KEY"), + "X-Secret-Key": os.Getenv("FI_SECRET_KEY"), + }), +) +if err != nil { return err } +provider := sdktrace.NewTracerProvider( + sdktrace.WithResource(resource.NewWithAttributes("", + attribute.String("project_name", os.Getenv("FI_PROJECT_NAME")), + attribute.String("project_type", "observe"), + attribute.String("service.name", "temporal-go-example"), + )), + sdktrace.WithBatcher(exporter), +) +defer provider.Shutdown(context.Background()) +``` + +Use the same project name in starter and worker. The example's `provider` function checks for missing keys and project name before connecting. Shut down the provider after the worker stops or the starter completes so batched spans are exported. + + + + +Pass the provider's tracer to the official extension and attach the interceptor to the Temporal client **in each process**: + +```go +tracing, err := temporalotel.NewTracingInterceptor( + temporalotel.TracerOptions{Tracer: provider.Tracer("temporal-sdk-go")}, +) +if err != nil { return err } +c, err := client.Dial(client.Options{ + HostPort: os.Getenv("TEMPORAL_ADDRESS"), + Interceptors: []interceptor.ClientInterceptor{tracing}, +}) +if err != nil { return err } +defer c.Close() +``` + +A worker created from this client inherits its interceptor; do not register the same interceptor on the worker again. The extension's default propagator handles W3C `traceparent` and baggage. When an activity also creates OTel spans, use this provider for its instrumentor so they join the activity trace. Keep network calls and model instrumentation out of replayed workflow code. + + + + +Start a [Temporal development server](https://docs.temporal.io/cli/server#start-dev) or set `TEMPORAL_ADDRESS` for your cluster. From the [example directory](https://github.com/future-agi/docs/tree/main/examples/frameworks/temporal-go): + +```bash +export FI_API_KEY=... FI_SECRET_KEY=... FI_PROJECT_NAME=temporal-go-demo +go run . worker # terminal 1 +go run . starter Ada # terminal 2, with the same FI_* values +``` + +The sample activity returns `Hello, Ada` without calling a model. Open **Observe** in the `temporal-go-demo` project to inspect the workflow and activity spans. Instrument the model client inside your own activity to add model spans under `RunActivity`. + + + + +## Troubleshooting + +- **No spans:** Check the exporter endpoint, both key headers, and the `project_name` **resource** attribute in each process. `service.name` does not select an Observe project. +- **Separate traces:** Configure the interceptor on the starter's client and the worker's client. A workflow started by a process without the interceptor cannot carry its parent trace context. +- **Duplicate spans:** Do not also attach the same tracing interceptor to the worker, or add a second exporter for the same provider. diff --git a/src/pages/docs/integrations/frameworks/temporal.mdx b/src/pages/docs/integrations/frameworks/temporal.mdx index 67a2d1f6..1a5d5aad 100644 --- a/src/pages/docs/integrations/frameworks/temporal.mdx +++ b/src/pages/docs/integrations/frameworks/temporal.mdx @@ -5,6 +5,8 @@ description: "Trace your own Temporal workflows and activities in Observe: Tempo ## About +For the Go SDK, see [Temporal Go tracing](/docs/integrations/frameworks/temporal-go). + [Temporal](https://temporal.io) runs your workflows as durable executions. It records every step in a history, retries a failed activity, and replays the history to rebuild a workflow after a worker restart or a cache eviction. The Temporal Python SDK ships `OpenTelemetryPlugin`, which creates spans for the workflow and its activities and carries the trace context between the process that starts a workflow, the workflow, and each activity. This page shows one workflow run as one trace in Observe: ```text