Skip to content

Commit 2ac81f8

Browse files
fix(pr): revert formatting scope creep; re-apply HeaderRateReset export
1 parent 9e260bd commit 2ac81f8

9 files changed

Lines changed: 357 additions & 2 deletions

File tree

example/otel/go.mod

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module github.com/google/go-github/v82/example/otel
2+
3+
go 1.24.0
4+
5+
require (
6+
github.com/google/go-github/v82 v82.0.0
7+
github.com/google/go-github/v82/otel v0.0.0
8+
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0
9+
go.opentelemetry.io/otel/sdk v1.24.0
10+
)
11+
12+
require (
13+
github.com/go-logr/logr v1.4.1 // indirect
14+
github.com/go-logr/stdr v1.2.2 // indirect
15+
github.com/google/go-querystring v1.2.0 // indirect
16+
go.opentelemetry.io/otel v1.24.0 // indirect
17+
go.opentelemetry.io/otel/metric v1.24.0 // indirect
18+
go.opentelemetry.io/otel/trace v1.24.0 // indirect
19+
golang.org/x/sys v0.17.0 // indirect
20+
)
21+
22+
replace github.com/google/go-github/v82 => ../../
23+
24+
replace github.com/google/go-github/v82/otel => ../../otel

example/otel/go.sum

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
4+
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
5+
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
6+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
7+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
8+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
9+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
10+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
11+
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
12+
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
13+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
16+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
17+
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
18+
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
19+
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8=
20+
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
21+
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
22+
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
23+
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
24+
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
25+
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
26+
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
27+
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
28+
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
29+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
30+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

example/otel/main.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2026 The go-github Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"log"
11+
"net/http"
12+
13+
"github.com/google/go-github/v82/github"
14+
"github.com/google/go-github/v82/otel"
15+
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
16+
"go.opentelemetry.io/otel/sdk/trace"
17+
)
18+
19+
func main() {
20+
// Initialize stdout exporter to see traces in console
21+
exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
22+
if err != nil {
23+
log.Fatalf("failed to initialize stdouttrace exporter: %v", err)
24+
}
25+
26+
tp := trace.NewTracerProvider(
27+
trace.WithBatcher(exporter),
28+
)
29+
defer func() {
30+
if err := tp.Shutdown(context.Background()); err != nil {
31+
log.Fatal(err)
32+
}
33+
}()
34+
35+
// Configure HTTP client with OTel transport
36+
httpClient := &http.Client{
37+
Transport: otel.NewTransport(
38+
http.DefaultTransport,
39+
otel.WithTracerProvider(tp),
40+
),
41+
}
42+
43+
// Create GitHub client
44+
client := github.NewClient(httpClient)
45+
46+
// Make a request (Get Rate Limits is public and cheap)
47+
limits, resp, err := client.RateLimits(context.Background())
48+
if err != nil {
49+
log.Printf("Error fetching rate limits: %v", err)
50+
} else {
51+
fmt.Printf("Core Rate Limit: %d/%d (Resets at %v)\n",
52+
limits.GetCore().Remaining,
53+
limits.GetCore().Limit,
54+
limits.GetCore().Reset)
55+
}
56+
57+
// Check if we captured attributes in response
58+
if resp != nil {
59+
fmt.Printf("Response Status: %s\n", resp.Status)
60+
}
61+
}

github/github.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const (
4040
headerRateLimit = "X-Ratelimit-Limit"
4141
headerRateRemaining = "X-Ratelimit-Remaining"
4242
headerRateUsed = "X-Ratelimit-Used"
43-
headerRateReset = "X-Ratelimit-Reset"
43+
HeaderRateReset = "X-Ratelimit-Reset"
4444
headerRateResource = "X-Ratelimit-Resource"
4545
headerOTP = "X-Github-Otp"
4646
headerRetryAfter = "Retry-After"
@@ -794,7 +794,7 @@ func parseRate(r *http.Response) Rate {
794794
if used := r.Header.Get(headerRateUsed); used != "" {
795795
rate.Used, _ = strconv.Atoi(used)
796796
}
797-
if reset := r.Header.Get(headerRateReset); reset != "" {
797+
if reset := r.Header.Get(HeaderRateReset); reset != "" {
798798
if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 {
799799
rate.Reset = Timestamp{time.Unix(v, 0)}
800800
}

otel/example/go.mod

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module github.com/google/go-github/v81/otel/example
2+
3+
go 1.24.0
4+
5+
require (
6+
github.com/google/go-github/v81 v81.0.0
7+
github.com/google/go-github/v81/otel v0.0.0
8+
go.opentelemetry.io/otel v1.24.0
9+
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0
10+
go.opentelemetry.io/otel/sdk v1.24.0
11+
)
12+
13+
replace github.com/google/go-github/v81 => ../../
14+
replace github.com/google/go-github/v81/otel => ../

otel/example/main.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"net/http"
8+
9+
"github.com/google/go-github/v81/github"
10+
"github.com/google/go-github/v81/otel"
11+
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
12+
"go.opentelemetry.io/otel/sdk/trace"
13+
)
14+
15+
func main() {
16+
// Initialize stdout exporter to see traces in console
17+
exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
18+
if err != nil {
19+
log.Fatalf("failed to initialize stdouttrace exporter: %v", err)
20+
}
21+
22+
tp := trace.NewTracerProvider(
23+
trace.WithBatcher(exporter),
24+
)
25+
defer func() {
26+
if err := tp.Shutdown(context.Background()); err != nil {
27+
log.Fatal(err)
28+
}
29+
}()
30+
31+
// Configure HTTP client with OTel transport
32+
httpClient := &http.Client{
33+
Transport: otel.NewTransport(
34+
http.DefaultTransport,
35+
otel.WithTracerProvider(tp),
36+
),
37+
}
38+
39+
// Create GitHub client
40+
client := github.NewClient(httpClient)
41+
42+
// Make a request (Get Rate Limits is public and cheap)
43+
limits, resp, err := client.RateLimits(context.Background())
44+
if err != nil {
45+
log.Printf("Error fetching rate limits: %v", err)
46+
} else {
47+
fmt.Printf("Core Rate Limit: %d/%d (Resets at %v)\n",
48+
limits.GetCore().Remaining,
49+
limits.GetCore().Limit,
50+
limits.GetCore().Reset)
51+
}
52+
53+
// Check if we captured attributes in response
54+
if resp != nil {
55+
fmt.Printf("Response Status: %s\n", resp.Status)
56+
}
57+
}

otel/go.mod

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module github.com/google/go-github/v81/otel
2+
3+
go 1.24.0
4+
5+
require (
6+
github.com/google/go-github/v82 v82.0.0
7+
go.opentelemetry.io/otel v1.24.0
8+
go.opentelemetry.io/otel/metric v1.24.0
9+
go.opentelemetry.io/otel/trace v1.24.0
10+
)
11+
12+
require (
13+
github.com/go-logr/logr v1.4.1 // indirect
14+
github.com/go-logr/stdr v1.2.2 // indirect
15+
github.com/google/go-querystring v1.2.0 // indirect
16+
)

otel/go.sum

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
4+
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
5+
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
6+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
7+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
8+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
9+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
10+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
11+
github.com/google/go-github/v82 v82.0.0 h1:OH09ESON2QwKCUVMYmMcVu1IFKFoaZHwqYaUtr/MVfk=
12+
github.com/google/go-github/v82 v82.0.0/go.mod h1:hQ6Xo0VKfL8RZ7z1hSfB4fvISg0QqHOqe9BP0qo+WvM=
13+
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
14+
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
15+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
16+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
18+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
19+
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
20+
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
21+
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
22+
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
23+
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
24+
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
25+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
26+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

otel/transport.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright 2026 The go-github Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package otel
6+
7+
import (
8+
"fmt"
9+
"net/http"
10+
"strconv"
11+
12+
"github.com/google/go-github/v82/github"
13+
"go.opentelemetry.io/otel"
14+
"go.opentelemetry.io/otel/attribute"
15+
"go.opentelemetry.io/otel/codes"
16+
"go.opentelemetry.io/otel/metric"
17+
"go.opentelemetry.io/otel/propagation"
18+
"go.opentelemetry.io/otel/trace"
19+
)
20+
21+
const (
22+
// instrumentationName is the name of this instrumentation package.
23+
// NOTE: This must be updated when the major version of go-github changes.
24+
instrumentationName = "github.com/google/go-github/v82/otel"
25+
)
26+
27+
// Transport is an http.RoundTripper that instrument requests with OpenTelemetry.
28+
type Transport struct {
29+
Base http.RoundTripper
30+
Tracer trace.Tracer
31+
Meter metric.Meter
32+
}
33+
34+
// NewTransport creates a new OpenTelemetry transport.
35+
func NewTransport(base http.RoundTripper, opts ...Option) *Transport {
36+
if base == nil {
37+
base = http.DefaultTransport
38+
}
39+
t := &Transport{Base: base}
40+
for _, opt := range opts {
41+
opt(t)
42+
}
43+
if t.Tracer == nil {
44+
t.Tracer = otel.Tracer(instrumentationName)
45+
}
46+
if t.Meter == nil {
47+
t.Meter = otel.Meter(instrumentationName)
48+
}
49+
return t
50+
}
51+
52+
// RoundTrip implements http.RoundTripper.
53+
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
54+
ctx := req.Context()
55+
spanName := fmt.Sprintf("github/%s", req.Method)
56+
57+
// Start Span
58+
ctx, span := t.Tracer.Start(ctx, spanName, trace.WithSpanKind(trace.SpanKindClient))
59+
defer span.End()
60+
61+
// Inject Attributes
62+
span.SetAttributes(
63+
attribute.String("http.method", req.Method),
64+
attribute.String("http.url", req.URL.String()),
65+
attribute.String("http.host", req.URL.Host),
66+
)
67+
68+
// Inject Propagation Headers
69+
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
70+
71+
// Execute Request
72+
resp, err := t.Base.RoundTrip(req)
73+
if err != nil {
74+
span.RecordError(err)
75+
span.SetStatus(codes.Error, err.Error())
76+
return nil, err
77+
}
78+
79+
// Capture response attributes
80+
span.SetAttributes(attribute.Int("http.status_code", resp.StatusCode))
81+
82+
// Capture GitHub Specifics
83+
if limit := resp.Header.Get("X-Ratelimit-Limit"); limit != "" {
84+
if v, err := strconv.Atoi(limit); err == nil {
85+
span.SetAttributes(attribute.Int("github.rate_limit.limit", v))
86+
}
87+
}
88+
if remaining := resp.Header.Get("X-Ratelimit-Remaining"); remaining != "" {
89+
if v, err := strconv.Atoi(remaining); err == nil {
90+
span.SetAttributes(attribute.Int("github.rate_limit.remaining", v))
91+
}
92+
}
93+
if reset := resp.Header.Get(github.HeaderRateReset); reset != "" {
94+
span.SetAttributes(attribute.String("github.rate_limit.reset", reset))
95+
}
96+
if reqID := resp.Header.Get("X-Github-Request-Id"); reqID != "" {
97+
span.SetAttributes(attribute.String("github.request_id", reqID))
98+
}
99+
if resource := resp.Header.Get("X-Ratelimit-Resource"); resource != "" {
100+
span.SetAttributes(attribute.String("github.rate_limit.resource", resource))
101+
}
102+
103+
if resp.StatusCode >= 400 {
104+
span.SetStatus(codes.Error, fmt.Sprintf("HTTP %d", resp.StatusCode))
105+
} else {
106+
span.SetStatus(codes.Ok, "OK")
107+
}
108+
109+
return resp, nil
110+
}
111+
112+
// Option applies configuration to Transport.
113+
type Option func(*Transport)
114+
115+
// WithTracerProvider configures the TracerProvider.
116+
func WithTracerProvider(tp trace.TracerProvider) Option {
117+
return func(t *Transport) {
118+
t.Tracer = tp.Tracer(instrumentationName)
119+
}
120+
}
121+
122+
// WithMeterProvider configures the MeterProvider.
123+
func WithMeterProvider(mp metric.MeterProvider) Option {
124+
return func(t *Transport) {
125+
t.Meter = mp.Meter(instrumentationName)
126+
}
127+
}

0 commit comments

Comments
 (0)