{"id":46095962,"url":"https://github.com/zalgonoise/spanner","last_synced_at":"2026-03-01T18:36:54.224Z","repository":{"id":64920042,"uuid":"579480288","full_name":"zalgonoise/spanner","owner":"zalgonoise","description":"A Tracer for Go applications","archived":false,"fork":false,"pushed_at":"2023-02-01T19:51:11.000Z","size":2820,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2023-08-12T04:28:04.977Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zalgonoise.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}},"created_at":"2022-12-17T20:46:45.000Z","updated_at":"2022-12-17T20:53:17.000Z","dependencies_parsed_at":"2023-02-17T10:16:06.836Z","dependency_job_id":null,"html_url":"https://github.com/zalgonoise/spanner","commit_stats":null,"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"purl":"pkg:github/zalgonoise/spanner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fspanner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fspanner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fspanner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fspanner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zalgonoise","download_url":"https://codeload.github.com/zalgonoise/spanner/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zalgonoise%2Fspanner/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29979120,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T16:35:47.903Z","status":"ssl_error","status_checked_at":"2026-03-01T16:35:44.899Z","response_time":124,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":[],"created_at":"2026-03-01T18:36:54.142Z","updated_at":"2026-03-01T18:36:54.206Z","avatar_url":"https://github.com/zalgonoise.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spanner\n\nA simple trace producer and exporter written in Go\n\n___________________\n\n\n## Overview\n\nAfter working with [OpenTelemetry's Tracer](https://github.com/open-telemetry/opentelemetry-go/tree/main/trace), I decided to implement a *simpler* tracer for my own applications. This tracer would output data in the same format as OpenTelemetry's, but have a smaller surface and more concise API. This would imply mimicking some of the structure, approach and API of OpenTelemetry's Tracer, while approaching the implementation in my own way (regardless). Basically, looking at the interfaces exposed by OpenTelemetry's Tracer and implementing my own Tracer from there.\n\nThis obviously resulted in an application with 6x more allocations than the original, mostly due to the approach when writing Span data. This was a perfect occasion to dive deeper into profiling Go applications using tools like `pprof`, and gradually improving the performance of my implementation. By the time the repo the repo was ported from `zalgonoise/x/spanner` to `zalgonoise/spanner`, it had decimated the amount of allocations as compared to the first implementation (however, still 25% more allocations than OpenTelemetry's). More information on benchmarks in its own section, below.\n\nThe trace output will have the same elements as found in OpenTelemetry's implementation, with a bit less metadata. Still, the overall structure described in [OpenTelemetry's Traces reference documentation](https://opentelemetry.io/docs/concepts/signals/traces/) is preserved, as seen in the example shared in it:\n\n```json\n{\n    \"name\": \"Hello-Greetings\",\n    \"context\": {\n        \"trace_id\": \"0x5b8aa5a2d2c872e8321cf37308d69df2\",\n        \"span_id\": \"0x5fb397be34d26b51\",\n    },\n    \"parent_id\": \"0x051581bf3cb55c13\",\n    \"start_time\": \"2022-04-29T18:52:58.114304Z\",\n    \"end_time\": \"2022-04-29T18:52:58.114435Z\",\n    \"attributes\": {\n        \"http.route\": \"some_route1\"\n    },\n    \"events\": [\n        {\n            \"name\": \"hey there!\",\n            \"timestamp\": \"2022-04-29T18:52:58.114561Z\",\n            \"attributes\": {\n                \"event_attributes\": 1\n            }\n        },\n        {\n            \"name\": \"bye now!\",\n            \"timestamp\": \"2022-04-29T22:52:58.114561Z\",\n            \"attributes\": {\n                \"event_attributes\": 1\n            }\n        }\n    ],\n}\n```\n\n## Installation \n\n\nTo fetch `spanner` as a Go library, use `go get` or `go install`:\n\n```\ngo get -u github.com/zalgonoise/spanner\n```\n\n```\ngo install github.com/zalgonoise/spanner@latest\n```\n\n...or, simply import it in your Go file and run `go mod tidy`:\n\n```go\nimport (\n    // (...)\n\n    \"github.com/zalgonoise/spanner\"\n)\n```\n\n## Features \n\n### Trace\n\nA Trace represents a single transaction in a system. It has a unique ID (16-byte-long, hex-encoded) that is present in all Spans spawned within this Trace. It also registers a Span's unique ID for reference when creating a new Span; as it will point to its parent Span.\n\nTraces are created when the `Tracer.Start()` method is called, and the input context does not have a Trace already.\n\n```go\ntype Trace interface {\n\t// ID returns the TraceID\n\tID() TraceID\n\t// Register sets the input pointer to a SpanID `s` as this Trace's reference parent_id\n\tRegister(s *SpanID)\n\t// Parent returns the parent SpanID, or nil if unset\n\tParent() *SpanID\n}\n```\n\n### Span\n\nA Span represents a single action within a transaction, in a system. It has a unique ID (8-byte-long, hex-encoded) and keeps track of the parent Span's ID, `nil` if it's the root Span.\n\nWhile `Tracer.Start()` kicks off a Span's beginning, it is the responsibility of the caller to end it with its `Span.End()` method, deferred if needed be.\n\nA Span may also store metadata besides a name and beginning / end timestamps. As covered below, a Span is also able to store key-value-pair attributes and events.\n\nLastly, a Span exposes methods of extracting both its SpanData and its EventData.\n\n```go\ntype Span interface {\n\t// Start sets the span to record\n\tStart()\n\t// End stops the span, returning the collected SpanData in the action\n\tEnd()\n\t// ID returns the SpanID of the Span\n\tID() SpanID\n\t// IsRecording returns a boolean on whether the Span is currently recording\n\tIsRecording() bool\n\t// SetName overwrites the Span's name field with the string `name`\n\tSetName(name string)\n\t// SetParent overwrites the Span's parent_id field with the SpanID `id`\n\tSetParent(span Span)\n\t// Add appends attributes (key-value pairs) to the Span\n\tAdd(attrs ...attr.Attr)\n\t// Attrs returns the Span's stored attributes\n\tAttrs() []attr.Attr\n\t// Replace will flush the Span's attributes and store the input attributes `attrs` in place\n\tReplace(attrs ...attr.Attr)\n\t// Event creates a new event within the Span\n\tEvent(name string, attrs ...attr.Attr)\n\t// Extract returns the current SpanData for the Span, regardless of its status\n\tExtract() SpanData\n\t// Events returns the events in the Span\n\tEvents() []EventData\n}\n```\n\n#### Span Attributes\n\nA Span stores key-value-pair attributes as metadata to the action in question. These key-value-pair attributes have a `string`-type key and `any`-type value, leveraging the [`zalgonoise/attr`](https://github.com/zalgonoise/attr) library.\n\n#### Span Events\n\nA Span also records events, which are one-shot entries with a `string` *name* and optionally any amount of attributes, leveraging the [`zalgonoise/attr`](https://github.com/zalgonoise/attr) library.\n\nSpan Events will store the Span Event name, attributes and also a timestamp of when the Event was recorded.\n\n### Tracer\n\nA Tracer creates a Traces and Spans within the input context, as well as setting Exporters to write the output SpanData.\n\nIt's main method `Tracer.Start()` reads the input context to create a Trace if it does not exist which is then stored in the context.\n\nIt also spawns a new Span with the input `string` *name*, that is returned to the caller.\n\nThe returned context will store the returned Span's ID, so that it is referenced when creating a new Span as a child. The input context, however, will not store the returned Span's ID, and when creating a new Span it will keep the previous parent Span's ID -- making it seem like the next call was done side-by-side with the parent (and not a child of it).\n\nIts `Tracer.To()` method sets the Span exporter to the input Exporter.\n\nIts `Tracer.Processor()` method returns the configured SpanProcessor.\n\n```go\ntype Tracer interface {\n\t// Start reuses the Trace in the input context `ctx`, or creates one if it doesn't exist. It also\n\t// creates the Span for the action, with string name `name`. Each call creates a new Span.\n\t//\n\t// After calling Start, the input context will still reference the parent Span's ID, nil if it's a new Trace.\n\t// The returned context will reference the returned Span's ID, to be used as the next call's parent.\n\t//\n\t// The returned Span is required, even if to defer its closure, with `defer s.End()`. The caller MUST close the\n\t// returned Span.\n\tStart(ctx context.Context, name string) (context.Context, Span)\n\t// To sets the Span exporter to Exporter `e`\n\tTo(e Exporter)\n\t// Processor returns the configured SpanProcessor in the Tracer\n\tProcessor() SpanProcessor\n}\n```\n\n### Processor\n\nA SpanProcessor will ingest the ended Spans when their `Span.End()` method is called, extract their SpanData, and push batches of SpanData to the configured Exporter. The SpanProcessor will be responsible of any post-recording processing that the Span needs, and it runs in a go routine.\n\n```go\ntype SpanProcessor interface {\n\t// Handle routes the input Span `span` to the SpanProcessor's Exporter\n\tHandle(span Span)\n\t// Shutdown gracefully stops the SpanProcessor, returning an error\n\tShutdown(ctx context.Context) error\n\t// Flush will force-push the existing SpanData in the SpanProcessor's batch into the\n\t// Exporter, even if not yet scheduled to do so\n\tFlush(ctx context.Context) error\n}\n```\n### Exporter\n\nAn Exporter will write a batch of SpanData to a certain output, as implemented in its `Exporter.Export()` method.\n\n```go\ntype Exporter interface {\n\t// Export pushes the input SpanData `spans` to its output, as a non-blocking\n\t// function\n\tExport(ctx context.Context, spans []SpanData) error\n\t// Shutdown gracefully terminates the Exporter\n\tShutdown(ctx context.Context) error\n}\n```\n\n### ID Generator\n\nAn ID Generator creates both Trace IDs and Span IDs, using a `crypto/rand` RNG. \n\nTraceIDs are 16-byte-long, hex-encoded values that implement the `fmt.Stringer` interface.\n\nSpanIDs are 8-byte-long, hex-encoded values that implement the `fmt.Stringer` interface.\n\n```go\ntype IDGenerator interface {\n\t// NewTraceID creates a new TraceID\n\tNewTraceID() TraceID\n\t// NewSpanID creates a new SpanID\n\tNewSpanID() SpanID\n}\n```\n\n## Disclaimer\n\nThis library does not aim to replace OpenTelemetry's Tracer implementation. It is about exploring an existing concept in order to work on improving the performance of a Go application. It just happens to be a Tracer. Don't replace your in-prod OpenTelemetry Tracer with this one.\n\nThis implementation is not more performant than OpenTelemetry's Tracer, as clarified in the Benchmark section below.\n\nThis approach tries to be a simple approach to the Span data structure exposed by OpenTelemetry's Tracer.\n\n## Benchmarks\n\n\u003e Tests are piped to [`prettybench`](https://github.com/cespare/prettybench) for a cleaner output of benchmark results.\n\nTo benchmark this implementation in comparison to OpenTelemetry's, the best route is to follow the [OpenTelemetry Go's Getting Started documentation](https://opentelemetry.io/docs/instrumentation/go/getting-started/), that describes a reasonable Fibonacci application with 3 different modules, that can be traced individually. As such, there are three implementations of that same logic in [`zalgonoise/x/benchmark/spanner`](https://github.com/zalgonoise/x/tree/master/benchmark/spanner):\n\n- [`zalgonoise/x/benchmark/spanner/core`](https://github.com/zalgonoise/x/tree/master/benchmark/spanner/core): contains the raw logic of the Fibonacci application, to benchmark with no Tracers involved.\n- [`zalgonoise/x/benchmark/spanner/optl`](https://github.com/zalgonoise/x/tree/master/benchmark/spanner/optl): contains the logic of the Fibonacci application wrapped with the OpenTelemetry tracer, exporting the Spans to standard-out.\n- [`zalgonoise/x/benchmark/spanner/self`](https://github.com/zalgonoise/x/tree/master/benchmark/spanner/self): contains the logic of the Fibonacci application wrapped with this tracer, exporting the Spans to standard-out. \n\n#### Core\n\nFor reference, note the benchmark output of the [`core`](https://github.com/zalgonoise/x/blob/master/benchmark/spanner/core/core_test.go#L19) implementation:\n\n```\n❯ go test -bench . -benchtime=10s -benchmem -cpuprofile /tmp/cpu.pprof -run BenchmarkRuntime | prettybench\n\ngoos: linux\ngoarch: amd64\npkg: github.com/zalgonoise/x/benchmark/spanner/core\ncpu: AMD Ryzen 3 PRO 3300U w/ Radeon Vega Mobile Gfx\nPASS\nbenchmark                iter      time/iter   bytes alloc        allocs\n---------                ----      ---------   -----------        ------\nBenchmarkRuntime-4   36876082   421.60 ns/op        8 B/op   1 allocs/op\nok      github.com/zalgonoise/x/benchmark/spanner/core  16.064s\n```\n\n\n#### OpenTelemetry\n\nOpenTelemetry's test instruments the application just like the official Getting Started guide suggests, but also involves flushing the accumulated Span data to the official standard-out exporter:\n\n```\n❯ go test -bench . -benchtime=10s -benchmem -cpuprofile /tmp/cpu.pprof -run BenchmarkRuntime | prettybench\ngoos: linux\ngoarch: amd64\npkg: github.com/zalgonoise/x/benchmark/spanner/optl\ncpu: AMD Ryzen 3 PRO 3300U w/ Radeon Vega Mobile Gfx\nPASS\nbenchmark              iter      time/iter   bytes alloc         allocs\n---------              ----      ---------   -----------         ------\nBenchmarkRuntime-4   283874    42.95 μs/op    12405 B/op   75 allocs/op\nok      github.com/zalgonoise/x/benchmark/spanner/optl  22.038s\n```\n\n#### `zalgonoise/spanner`\n\nThis repo's test instruments the application exactly the same way as OpenTelemetry, also exporting the Span data to standard-out:\n\n```\n❯ go test -bench . -benchtime=10s -benchmem -cpuprofile /tmp/cpu.pprof -run BenchmarkRuntime | prettybench\n\ngoos: linux\ngoarch: amd64\npkg: github.com/zalgonoise/x/benchmark/spanner/self\ncpu: AMD Ryzen 3 PRO 3300U w/ Radeon Vega Mobile Gfx\nPASS\nbenchmark              iter      time/iter   bytes alloc         allocs\n---------              ----      ---------   -----------         ------\nBenchmarkRuntime-4   169114    73.01 μs/op     6343 B/op   88 allocs/op\nok      github.com/zalgonoise/x/benchmark/spanner/self  13.237s\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzalgonoise%2Fspanner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzalgonoise%2Fspanner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzalgonoise%2Fspanner/lists"}