{"id":22724689,"url":"https://github.com/mutablelogic/go-client","last_synced_at":"2026-04-12T08:03:25.466Z","repository":{"id":209133104,"uuid":"723033187","full_name":"mutablelogic/go-client","owner":"mutablelogic","description":"REST API Client for various services","archived":false,"fork":false,"pushed_at":"2026-03-27T17:41:52.000Z","size":7607,"stargazers_count":6,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-28T01:46:59.195Z","etag":null,"topics":["client","golang","http"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/mutablelogic/go-client","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mutablelogic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-11-24T14:17:24.000Z","updated_at":"2026-03-27T17:41:01.000Z","dependencies_parsed_at":"2023-11-25T10:24:17.183Z","dependency_job_id":"a5e2abc7-171d-4670-8be1-998a6cf7d0e5","html_url":"https://github.com/mutablelogic/go-client","commit_stats":null,"previous_names":["mutablelogic/go-client"],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/mutablelogic/go-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutablelogic%2Fgo-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutablelogic%2Fgo-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutablelogic%2Fgo-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutablelogic%2Fgo-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mutablelogic","download_url":"https://codeload.github.com/mutablelogic/go-client/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutablelogic%2Fgo-client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","golang","http"],"created_at":"2024-12-10T15:07:37.446Z","updated_at":"2026-04-12T08:03:25.459Z","avatar_url":"https://github.com/mutablelogic.png","language":"Go","readme":"# go-client\n\nThis repository contains a generic HTTP client which can be adapted to provide:\n\n* General HTTP methods for GET and POST of data\n* Ability to send and receive JSON, plaintext and XML data\n* Ability to send files and data of type `multipart/form-data`\n* Ability to send data of type `application/x-www-form-urlencoded`\n* Debugging capabilities to see the request and response data\n* Streaming text and JSON events\n* OpenTelemetry tracing for distributed observability\n\nAPI Documentation: \u003chttps://pkg.go.dev/github.com/mutablelogic/go-client\u003e\n\nThere are also some example clients which use this library:\n\n* [Bitwarden API Client](https://github.com/mutablelogic/go-client/tree/main/pkg/bitwarden)\n* [Home Assistant API Client](https://github.com/mutablelogic/go-client/tree/main/pkg/homeassistant)\n* [IPify Client](https://github.com/mutablelogic/go-client/tree/main/pkg/ipify)\n\nThere are also utility packages for working with multipart file uploads, transport middleware, and OpenTelemetry:\n\n* [OpenTelemetry Package](https://github.com/mutablelogic/go-client/tree/main/pkg/otel)\n* [Transport Middleware Package](https://github.com/mutablelogic/go-client/tree/main/pkg/transport)\n* [Multipart Package](https://github.com/mutablelogic/go-client/tree/main/pkg/multipart)\n\nCompatibility with go version 1.25 and above.\n\n## Basic Usage\n\nThe following example shows how to decode a response from a GET request\nto a JSON endpoint:\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"log\"\n\n    client \"github.com/mutablelogic/go-client\"\n)\n\nfunc main() {\n    // Create a new client\n    c, err := client.New(client.OptEndpoint(\"https://api.example.com/api/v1\"))\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    // Send a GET request, populating a struct with the response\n    var response struct {\n        Message string `json:\"message\"`\n    }\n    if err := c.Do(nil, \u0026response, client.OptPath(\"test\")); err != nil {\n        log.Fatal(err)\n    }\n\n    // Print the response\n    fmt.Println(response.Message)\n}\n```\n\nVarious options can be passed to the client `New` method to control its behaviour:\n\n* `OptEndpoint(value string)` sets the endpoint for all requests\n* `OptTimeout(value time.Duration)` sets the timeout on any request, which defaults to 30 seconds.\n    Timeouts can be ignored on a request-by-request basis using the `OptNoTimeout` option (see below).\n* `OptUserAgent(value string)` sets the user agent string on each API request.\n* `OptTrace(w io.Writer, verbose bool)` — **Deprecated.** Use `OptTransport` with `transport.NewLogging` instead.\n    See the Transport Middleware section below for details.\n* `OptStrict()` turns on strict content type checking on anything returned from the API.\n* `OptRateLimit(value float32)` sets the limit on number of requests per second and the API\n    will sleep to regulate the rate limit when exceeded.\n* `OptReqToken(value Token)` sets a request token for all client requests. This can be\n    overridden by the client for individual requests using `OptToken` (see below).\n* `OptSkipVerify()` skips TLS certificate domain verification.\n* `OptHeader(key, value string)` appends a custom header to each request.\n* `OptParent(v any)` attaches arbitrary context to the client. The stored value is accessible via the `Parent` field and is used by wrapper types that embed a `*Client` to store their own state.\n* `OptTransport(fn func(http.RoundTripper) http.RoundTripper)` inserts a transport middleware\n    that wraps every request made by this client. Multiple calls stack in order so the first\n    call becomes the outermost layer. Use this to plug in any `pkg/transport` middleware.\n* `OptTracer(tracer trace.Tracer)` sets an OpenTelemetry tracer for distributed tracing.\n    Span names default to `\"METHOD /path\"` format. See the OpenTelemetry section below for more details.\n\n## Redirect Handling\n\nThe client automatically follows HTTP redirects (3xx responses) for GET and HEAD requests, up to a maximum of 10 redirects. Unlike the default Go HTTP client behavior:\n\n* The HTTP method is preserved (HEAD stays HEAD, GET stays GET)\n* Request headers are preserved across redirects\n* For security, `Authorization` and `Cookie` headers are stripped when redirecting to a different host\n\nThis behavior ensures that redirects work correctly for APIs that use CDNs or load balancers with temporary redirects.\n\n## Usage with a payload\n\nThe first argument to the `Do` method is the payload to send to the server, when set.\nYou can create a payload using the following methods:\n\n* `client.NewRequest()` returns a new empty payload which defaults to GET.\n* `client.NewRequestEx(method, accept string)` returns a new empty payload with an explicit HTTP\n    method and accepted response content type.\n* `client.NewJSONRequest(payload any) (Payload, error)` returns a new POST request with a JSON payload\n    that accepts any response content type.\n* `client.NewJSONRequestEx(method string, payload any, accept string) (Payload, error)` is the\n    extended form that also sets the HTTP method and accepted response content type.\n* `client.NewMultipartRequest(payload any, accept string) (Payload, error)` returns a new request with\n    a Multipart Form data payload which defaults to POST.\n* `client.NewStreamingMultipartRequest(payload any, accept string) (Payload, error)` returns a new request\n    with a Multipart Form data payload that streams data rather than buffering in memory. Useful for\n    large file uploads. The returned payload implements `io.Closer` and is automatically closed\n    by the HTTP client after the request completes.\n* `client.NewFormRequest(payload any, accept string) (Payload, error)` returns a new request with a\n    Form data payload which defaults to POST.\n\nFor example,\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"log\"\n\n    client \"github.com/mutablelogic/go-client\"\n)\n\nfunc main() {\n    // Create a new client\n    c, err := client.New(client.OptEndpoint(\"https://api.example.com/api/v1\"))\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    // Send a POST request with JSON payload\n    var request struct {\n        Prompt string `json:\"prompt\"`\n    }\n    var response struct {\n        Reply string `json:\"reply\"`\n    }\n    request.Prompt = \"Hello, world!\"\n    payload, err := client.NewJSONRequest(request)\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := c.Do(payload, \u0026response, client.OptPath(\"test\")); err != nil {\n        log.Fatal(err)\n    }\n\n    // Print the response\n    fmt.Println(response.Reply)\n}\n```\n\nYou can also implement your own payload by implementing the `Payload` interface:\n\n```go\ntype Payload interface {\n  io.Reader\n\n  // The method to use to send the payload\n  Method() string\n\n  // The content type of the payload\n  Type() string\n\n  // The content type which is accepted as a response, or empty string if any\n  Accept() string\n}\n```\n\n## Request options\n\nThe signature of the `Do` method is as follows:\n\n```go\ntype Client interface {\n    // Perform request and wait for response\n    Do(in Payload, out any, opts ...RequestOpt) error\n\n    // Perform request and wait for response, with context for cancellation\n    DoWithContext(ctx context.Context, in Payload, out any, opts ...RequestOpt) error\n}\n```\n\nIf you pass a context to the `DoWithContext` method, then the request can be\ncancelled using the context in addition to the timeout. Various options can be passed to\nmodify each individual request when using the `Do` method:\n\n* `OptReqEndpoint(value string)` sets the endpoint for the request\n* `OptPath(value ...any)` appends path elements onto a request endpoint\n* `OptToken(value Token)` adds an authorization header (overrides the client OptReqToken option)\n* `OptQuery(value url.Values)` sets the query parameters to a request\n* `OptReqHeader(name, value string)` sets a custom header to the request\n* `OptNoTimeout()` disables the timeout on the request, which is useful for long running requests\n* `OptReqTransport(fn func(http.RoundTripper) http.RoundTripper)` inserts a transport middleware\n    for this single request only. Multiple calls stack in order; the first becomes the outermost.\n    The middleware is applied on a per-request copy of the client and does not affect other requests.\n* `OptTextStreamCallback(fn TextStreamCallback)` allows you to set a callback\n    function to process a streaming text response of type `text/event-stream`, where\n    `TextStreamCallback` is `func(TextStreamEvent) error`. See below for more details.\n* `OptJsonStreamCallback(fn JsonStreamCallback)` allows you to set a callback for JSON streaming\n    responses, where `JsonStreamCallback` is `func(json.RawMessage) error`. See below for more details.\n\n## Authentication\n\nThe authentication token can be set as follows:\n\n```go\npackage main\n\nimport (\n    \"log\"\n    \"os\"\n\n    client \"github.com/mutablelogic/go-client\"\n)\n\nfunc main() {\n    // Create a new client\n    c, err := client.New(\n        client.OptEndpoint(\"https://api.example.com/api/v1\"),\n        client.OptReqToken(client.Token{\n            Scheme: \"Bearer\",\n            Value:  os.Getenv(\"API_TOKEN\"),\n        }),\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    // Use the client...\n    _ = c\n}\n```\n\nYou can also set the token on a per-request basis using the `OptToken` option in call to the `Do` method.\n\n## Form submission\n\nYou can create a payload with form data:\n\n* `client.NewFormRequest(payload any, accept string) (Payload, error)` returns a new request with a Form\n    data payload which defaults to POST.\n* `client.NewMultipartRequest(payload any, accept string) (Payload, error)` returns a new request with\n    a Multipart Form data payload which defaults to POST. This is useful for file uploads.\n* `client.NewStreamingMultipartRequest(payload any, accept string) (Payload, error)` returns a new request\n    that streams the multipart data rather than buffering in memory. This is recommended for\n    large file uploads to avoid high memory usage. The returned payload implements `io.Closer`\n    and is automatically closed by the HTTP client after the request completes.\n\nThe payload should be a `struct` where the fields are converted to form tuples. File uploads require a field of type `multipart.File`. For example,\n\n```go\npackage main\n\nimport (\n    \"log\"\n    \"strings\"\n\n    client \"github.com/mutablelogic/go-client\"\n    multipart \"github.com/mutablelogic/go-client/pkg/multipart\"\n)\n\ntype FileUpload struct {\n    File multipart.File `json:\"file\"`\n}\n\nfunc main() {\n    // Create a new client\n    c, err := client.New(client.OptEndpoint(\"https://api.example.com/api/v1\"))\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    // Create a file upload request\n    request := FileUpload{\n        File: multipart.File{\n            Path: \"helloworld.txt\",\n            Body: strings.NewReader(\"Hello, world!\"),\n        },\n    }\n\n    // Upload a file\n    var response any\n    payload, err := client.NewMultipartRequest(request, \"*/*\")\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := c.Do(payload, \u0026response, client.OptPath(\"upload\")); err != nil {\n        log.Fatal(err)\n    }\n}\n```\n\n## Unmarshalling responses\n\nYou can implement your own unmarshalling of responses by implementing the `Unmarshaler` interface:\n\n```go\ntype Unmarshaler interface {\n  Unmarshal(header http.Header, r io.Reader) error\n}\n```\n\nThe first argument to the `Unmarshal` method is the HTTP header of the response, and the second\nargument is the body of the response. You can return one of the following error values from Unmarshal\nto indicate how the client should handle the response:\n\n* `nil` to indicate successful unmarshalling.\n* `httpresponse.ErrNotImplemented` (from github.com/mutablelogic/go-server/pkg/httpresponse) to fall back to the default unmarshaling behaviour.\n  In this case, the body will be unmarshaled based on the `Content-Type` header:\n  * `application/json` → any JSON-decodable type\n  * `application/xml` or `text/xml` → any XML-decodable type\n  * `text/plain` → `*string` (value set to the body text), `*[]byte` (raw bytes), or `io.Writer` (body copied into it)\n* Any other error to indicate a failure in unmarshaling.\n\n## Text Streaming Responses\n\nThe client implements a streaming text event callback which can be used to process a stream of text events,\nas per the [Mozilla specification](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events).\n\nIn order to process streamed events, pass the `OptTextStreamCallback()` option to the request\nwith a callback function, which should have the following signature:\n\n```go\nfunc Callback(event client.TextStreamEvent) error {\n    // Finish processing successfully\n    if event.Event == \"close\" {\n        return io.EOF\n    }\n\n    // Decode the data into a JSON object\n    var data map[string]any\n    if err := event.Json(\u0026data); err != nil {\n        return err\n    }\n\n    // Return success - continue streaming\n    return nil\n}\n```\n\nThe `TextStreamEvent` object has the following fields:\n\n* `Id string` — the event ID (`id:` field)\n* `Event string` — the event type (`event:` field; defaults to `\"message\"`)\n* `Data string` — the event data (`data:` fields joined with `\\n`)\n* `Retry time.Duration` — the server-requested reconnect delay (`retry:` field)\n\nIf you return an error of type `io.EOF` from the callback, then the stream will be closed.\nSimilarly, if you return any other error the stream will be closed and the error returned.\n\nUsually, you would pair this option with `OptNoTimeout` to prevent the request from timing out.\n\n### SSE Reconnect\n\nFor reconnect support, use `NewTextStream()` and `Decode()` directly rather than `OptTextStreamCallback`.\nAfter `Decode` returns, the decoder holds the last event ID and server-requested retry delay:\n\n```go\nstream := client.NewTextStream()\nif err := stream.Decode(r, callback); err != nil {\n    // reconnect: set Last-Event-ID header and wait\n    req.Header.Set(\"Last-Event-ID\", stream.LastEventID())\n    time.Sleep(stream.RetryDuration())\n}\n```\n\n## JSON Streaming\n\nThe client supports both one-way NDJSON response streaming and bi-directional NDJSON channels.\n\n### JSON Streaming Responses\n\nFor one-way JSON streaming responses, pass a callback function to the `OptJsonStreamCallback()` option.\nThe callback with signature `func(json.RawMessage) error` is called for each JSON object in the stream.\nDecode the raw frame into the concrete type you expect.\n\n```go\npackage main\n\nimport (\n    \"encoding/json\"\n    \"io\"\n    \"log\"\n    \"net/http\"\n\n    client \"github.com/mutablelogic/go-client\"\n)\n\ntype Event struct {\n    Value int `json:\"value\"`\n}\n\nfunc main() {\n    c, err := client.New(client.OptEndpoint(\"https://api.example.com\"))\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    err = c.Do(\n        client.NewRequestEx(http.MethodGet, \"application/ndjson\"),\n        nil,\n        client.OptPath(\"events\"),\n        client.OptNoTimeout(),\n        client.OptJsonStreamCallback(func(v json.RawMessage) error {\n            var event Event\n            if err := json.Unmarshal(v, \u0026event); err != nil {\n                return err\n            }\n            log.Printf(\"value=%d\", event.Value)\n            if event.Value \u003e= 100 {\n                return io.EOF\n            }\n            return nil\n        }),\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n}\n```\n\nYou can return an error from the callback to stop the stream and return the error, or return `io.EOF` to stop the stream\nimmediately and return success.\n\n### Bi-Directional JSON Streams\n\nUse `Client.Stream(ctx, callback, opts...)` to open a bi-directional NDJSON stream.\nThe callback receives a `JSONStream`, which lets you send newline-delimited JSON\nrequest frames with `Send` and receive response frames from the channel returned\nby `Recv`.\n\nReturning from the callback closes the stream. Canceling the context passed to\n`Client.Stream` also closes the stream. Blank response lines are treated as\nkeep-alive heartbeats and are delivered as `nil` frames on the receive channel.\n\nThe receive side starts immediately when the stream opens. Callbacks should keep\ndraining `Recv()` while the stream is active; if responses are not consumed and\nthe internal receive buffer fills, the stream is canceled to avoid a full-duplex\ndeadlock.\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"encoding/json\"\n    \"log\"\n    \"time\"\n\n    client \"github.com/mutablelogic/go-client\"\n)\n\ntype Reply struct {\n    Echo string `json:\"echo\"`\n}\n\nfunc main() {\n    ctx, cancel := context.WithCancel(context.Background())\n    defer cancel()\n\n    // Create a new client\n    c, err := client.New(client.OptEndpoint(\"https://api.example.com\"))\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    // Run the stream until the callback returns or the context is cancelled.\n    if err := c.Stream(ctx, func(ctx context.Context, stream client.JSONStream) error {\n        ticker := time.NewTicker(5 * time.Second)\n        defer ticker.Stop()\n\n        for {\n            select {\n            case \u003c-ticker.C:\n                frame := json.RawMessage(`{\"text\":\"status\"}`)\n                if err := stream.Send(frame); err != nil {\n                    return err\n                }\n            case frame, ok := \u003c-stream.Recv():\n                if !ok {\n                    return nil\n                }\n                if frame == nil {\n                    continue // keep-alive heartbeat\n                }\n\n                var reply Reply\n                if err := json.Unmarshal(frame, \u0026reply); err != nil {\n                    return err\n                }\n                log.Printf(\"reply=%s\", reply.Echo)\n            case \u003c-ctx.Done():\n                return ctx.Err()\n            }\n        }\n    }, client.OptPath(\"session\", \"1234\", \"channel\"), client.OptNoTimeout()); err != nil {\n        log.Fatal(err)\n    }\n}\n```\n\n## Transport Middleware\n\nThe `pkg/transport` package provides composable `http.RoundTripper` middleware. All middleware\nfollows the same constructor pattern `New*(... , parent http.RoundTripper)` and falls back to\n`http.DefaultTransport` when `parent` is nil. Middleware can be composed using `OptTransport`\n(client-wide) or `OptReqTransport` (per-request).\n\n### Logging Transport\n\n`transport.NewLogging` logs every request and response to an `io.Writer`. When `verbose` is true\nthe request and response bodies are also printed; `text/event-stream` and NDJSON streaming bodies\nare printed line-by-line as events arrive rather than buffered:\n\n```go\nimport (\n    client \"github.com/mutablelogic/go-client\"\n    \"github.com/mutablelogic/go-client/pkg/transport\"\n    \"os\"\n)\n\nc, err := client.New(\n    client.OptEndpoint(\"https://api.example.com\"),\n    client.OptTransport(func(next http.RoundTripper) http.RoundTripper {\n        return transport.NewLogging(os.Stderr, next, true)\n    }),\n)\n```\n\nThe convenience client option `OptTrace(w io.Writer, verbose bool)` (deprecated) is equivalent and internally\ncalls `transport.NewLogging`.\n\n### Recorder Transport\n\n`transport.NewRecorder` captures the HTTP status code and response headers of the most recent\nresponse. It is safe for concurrent use:\n\n```go\nvar rec *transport.Recorder\n\nc, err := client.New(\n    client.OptEndpoint(\"https://api.example.com\"),\n    client.OptTransport(func(next http.RoundTripper) http.RoundTripper {\n        rec = transport.NewRecorder(next)\n        return rec\n    }),\n)\n\n// After a request:\nfmt.Println(rec.StatusCode())     // e.g. 200\nfmt.Println(rec.Header())         // cloned http.Header map\nrec.Reset()                       // clear recorded values\n```\n\n### OTel Transport\n\n`transport.NewTransport` wraps an `http.RoundTripper` so that every hop produces an\nOpenTelemetry client span. See the OpenTelemetry section below for details.\n\n## OpenTelemetry\n\nThe `pkg/otel` package provides OpenTelemetry tracing utilities for both HTTP clients and servers.\n\n### Creating a Tracer Provider\n\nUse `otel.NewProvider` to create a tracer provider that exports spans to an OTLP endpoint:\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    \"net/http\"\n\n    client \"github.com/mutablelogic/go-client\"\n    \"github.com/mutablelogic/go-client/pkg/otel\"\n    \"github.com/mutablelogic/go-client/pkg/transport\"\n)\n\nfunc main() {\n    // Create a provider with an OTLP endpoint\n    // Supports http://, https://, grpc://, and grpcs:// schemes\n    provider, err := otel.NewProvider(\n        \"https://otel-collector.example.com:4318\",  // OTLP endpoint\n        \"api-key=your-api-key\",                     // Optional headers (comma-separated key=value pairs)\n        \"my-service\",                               // Service name\n        otel.Attr{Key: \"environment\", Value: \"production\"},  // Optional attributes\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer provider.Shutdown(context.Background())\n\n    // Get a tracer from the provider\n    tracer := provider.Tracer(\"my-service\")\n\n    // Use the tracer with go-client via OptTransport\n    c, err := client.New(\n        client.OptEndpoint(\"https://api.example.com\"),\n        client.OptTransport(func(next http.RoundTripper) http.RoundTripper {\n            return transport.NewTransport(tracer, next)\n        }),\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    // Use the client...\n    _ = c\n}\n```\n\n### HTTP Client Tracing\n\n`OptTracer` and `transport.NewTransport` both wrap the client's HTTP transport so\nthat **every** HTTP call produces a client span — including OAuth token refresh calls and each\nredirect hop. Span names default to `\"METHOD /path\"` format. Attributes captured per span:\n\n* HTTP method, URL, and host\n* Request and response body sizes\n* HTTP status codes\n* Error recording for failed requests\n\nPrefer composing via `OptTransport` so the transport layer stays explicit:\n\n```go\nimport (\n    client \"github.com/mutablelogic/go-client\"\n    \"github.com/mutablelogic/go-client/pkg/transport\"\n)\n\nc, err := client.New(\n    client.OptEndpoint(\"https://api.example.com\"),\n    client.OptTransport(func(next http.RoundTripper) http.RoundTripper {\n        return transport.NewTransport(tracer, next)\n    }),\n)\n```\n\nYou can also call `transport.NewTransport` directly on any `*http.Client`:\n\n```go\nhttpClient.Transport = transport.NewTransport(tracer, httpClient.Transport)\n```\n\n### HTTP Server Middleware\n\nUse `otel.HTTPHandler` or `otel.HTTPHandlerFunc` to add tracing to your HTTP server:\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    \"net/http\"\n\n    \"github.com/mutablelogic/go-client/pkg/otel\"\n)\n\nfunc main() {\n    // Create provider (see \"Creating a Tracer Provider\" above)\n    provider, err := otel.NewProvider(\"https://otel-collector.example.com:4318\", \"\", \"my-server\")\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer provider.Shutdown(context.Background())\n\n    tracer := provider.Tracer(\"my-server\")\n\n    // Wrap your handler with the middleware\n    handler := otel.HTTPHandler(tracer)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        w.Write([]byte(\"Hello, World!\"))\n    }))\n\n    // Or use HTTPHandlerFunc directly\n    handlerFunc := otel.HTTPHandlerFunc(tracer)(func(w http.ResponseWriter, r *http.Request) {\n        w.Write([]byte(\"Hello, World!\"))\n    })\n\n    http.Handle(\"/\", handler)\n    http.Handle(\"/func\", handlerFunc)\n    http.ListenAndServe(\":8080\", nil)\n}\n```\n\nThe middleware automatically:\n\n* Extracts trace context from incoming request headers (W3C Trace Context)\n* Creates server spans with HTTP method, URL, and host attributes\n* Captures response status codes\n* Marks spans as errors for 4xx and 5xx responses\n\n### Custom Spans\n\nUse `otel.StartSpan` to create custom spans in your application:\n\n```go\nctx, endSpan := otel.StartSpan(tracer, ctx, \"MyOperation\",\n    attribute.String(\"key\", \"value\"),\n)\n// Use a closure to capture the final value of err when the function returns.\n// defer endSpan(err) would capture err's value NOW (likely nil), not at return time.\ndefer func() { endSpan(err) }()\n\n// Your code here...\n```\n\n## License\n\nThis project is licensed under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for details, and the [NOTICE](NOTICE) file for copyright attribution.\n\n### What You Can Do\n\nUnder the Apache-2.0 license, you are free to:\n\n* **Use** — Use the software for any purpose, including commercial applications\n* **Modify** — Make changes to the source code\n* **Distribute** — Share the original or modified software\n* **Sublicense** — Grant rights to others under different terms\n* **Patent Use** — Use any patents held by contributors that cover this code\n\n**Requirements when redistributing:**\n\n* Include a copy of the Apache-2.0 license\n* State any significant changes you made\n* Preserve copyright, patent, trademark, and attribution notices\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmutablelogic%2Fgo-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmutablelogic%2Fgo-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmutablelogic%2Fgo-client/lists"}