Documentation
¶
Overview ¶
Package mesh implements the Benzene Mesh design (the main repo's docs/specification/mesh.md, originally extracted from this package's earlier docs/design/mesh.md): a service's self-description (Descriptor) derived from its live Registry (what it provides, §2) and its live OutboundRegistry (what it consumes, §2.3) - including per-topic request/response JSON Schemas derived at startup from the registered types, and the contract hash that makes drift detectable (schema.go) - a reserved-topic interception middleware that serves that descriptor, and a trace middleware (trace.go) that turns every pipeline invocation into a semantic TraceEvent handed to an Exporter - either the zero-setup LogExporter (exporter.go) or the batching PushExporter (push.go) that feeds a collector over the mesh:* wire topics (wire.go), with span propagation for cross-service trace joins (span.go). The meshd package implements the collector side, where the declared Descriptor - not trace parentage - is the producer/consumer graph's sole source (mesh.md §4); traces there are an observed, additive signal for liveness and drift (§4.2), never for graph membership.
Every feed this package provides is independent and optional, and unavailability degrades the mesh rather than the service. A deployment that provisions only the trace feed - for example, when the descriptor endpoint is withheld pending a security review - still yields a reduced mesh (live stats and flows, no catalog entries), and a descriptor-only deployment yields the reverse. Concretely: Describe with a nil Registry or a nil OutboundRegistry returns a descriptor without that half of the catalog and records the missing feed in Degraded; TraceMiddleware with a nil Exporter is a pass-through; and a panicking or failing exporter never affects the invocation it observed.
Index ¶
- Constants
- func ClassifyIssue(status, exceptionType string) string
- func IssueFingerprint(service, topic, version, classification, discriminator string) string
- func IssueMiddleware(info ServiceInfo, exporter IssueRecorder) benzene.Middleware
- func Middleware(descriptor Descriptor, aliases ...string) benzene.Middleware
- func RegisterOutbound[TReq, TRes any](r *OutboundRegistry, topic benzene.Topic) error
- func SpecHandler(descriptor Descriptor) http.Handler
- func TraceMiddleware(info ServiceInfo, exporter Exporter) benzene.Middleware
- func WithTraceContext(next client.Sender) client.Sender
- type Descriptor
- type Exporter
- type Heartbeat
- type Issue
- type IssueBatch
- type IssueOccurrence
- type IssueRecorder
- type LogExporter
- type OutboundRegistry
- type Placement
- type PushExporter
- type PushExporterOptions
- type PushIssueExporter
- type PushIssueExporterOptions
- type Sender
- type ServiceInfo
- type Span
- type TopicDescriptor
- type TraceBatch
- type TraceEvent
Constants ¶
const ( TopicRegister = "benzene:mesh:register" TopicHeartbeat = "benzene:mesh:heartbeat" TopicTraces = "benzene:mesh:traces" TopicIssues = "benzene:mesh:issues" TopicQueryFleet = "benzene:mesh:query:fleet" TopicQueryService = "benzene:mesh:query:service" TopicQueryTopic = "benzene:mesh:query:topic" TopicQueryTrace = "benzene:mesh:query:trace" )
The mesh wire-contract topics (mesh.md §4): what a service sends to a collector (register/heartbeat/traces) and what a view reads back (query:*). The collector side is implemented by the meshd package; the names live here because both sides of the wire share them. They are namespaced under the benzene: default-service-standard prefix (design-principles.md §5.1) and match the .NET reference's MeshTopics/BenzeneTopic, so other language ports interoperate.
const ( // ClassificationContractDrift is the issue classification reserved for collector/reader- // derived issues (mesh.md §4.1): a descriptor-hash mismatch, schema divergence, or - the // case mesh.md §4.2 defines - a trace naming a topic absent from the caller's declared // Consumes or the handler's declared Topics. ClassifyIssue's precedence table never // produces it; a collector (meshd) assigns it directly when it detects one of these cases. ClassificationContractDrift = "contract-drift" )
Issue classification vocabulary (mesh.md §4.1) - a closed set assigned by the normative precedence table in ClassifyIssue. The other five are never produced outside that table, so they stay unexported; ClassificationContractDrift is exported because a collector assigns it directly, outside the precedence table (see its own doc).
const FeedOutboundRegistry = "outbound-registry"
FeedOutboundRegistry names the consumed-topic feed in Descriptor.Degraded: the OutboundRegistry the descriptor's Consumes list is derived from (mesh.md §2.3).
const FeedRegistry = "registry"
FeedRegistry names the topic-catalog feed in Descriptor.Degraded: the Registry the descriptor's topic list is derived from.
const TopicID = "benzene:mesh"
TopicID is the reserved topic intercepted by Middleware to serve the ServiceDescriptor (mesh.md §1). It is namespaced under the benzene: default-service-standard prefix (design-principles.md §5.1) and matches the .NET reference's BenzeneTopic.Mesh.
Variables ¶
This section is empty.
Functions ¶
func ClassifyIssue ¶
ClassifyIssue assigns an issue classification from a failing invocation's Benzene status and captured exception type, by the normative precedence of mesh.md §4.1 (evaluated in order):
- bad-request / validation-error -> validation (even with an exception type present - a deserialization or argument failure is still a validation issue);
- an exception type present -> exception (classify by the throw, not the mapped status);
- not-found / unauthorized / forbidden / not-implemented, or an empty status -> config-wiring (a wiring gap);
- service-unavailable / timeout / too-many-requests -> dependency;
- unexpected-error -> exception;
- any other failing status -> unclassified (an honest fallback beats a lying class).
func IssueFingerprint ¶
IssueFingerprint derives the normative issue fingerprint of mesh.md §4.1: the lowercase hex of the first 16 bytes of SHA-256 over the UTF-8 bytes of service|topic|version|classification| discriminator (pipe-joined), where version is the empty string when absent and discriminator is the exception type when present, else the status. transport is deliberately excluded - the same failure over two transports is one issue.
func IssueMiddleware ¶
func IssueMiddleware(info ServiceInfo, exporter IssueRecorder) benzene.Middleware
IssueMiddleware observes every invocation that passes through it and records a failure signature to exporter for each unsuccessful outcome (a framework failure, an application-defined failure, or an empty status - the wiring gap). Successful invocations produce nothing. Register it outermost alongside TraceMiddleware so it sees intercepted invocations too.
A nil exporter returns a pass-through middleware: the issue feed is simply off and the service behaves identically to an unmeshed one (mesh.md §4.1: optional on both sides). Recording can never fail, slow, or block the invocation it observed - Record holds only a brief lock and the exporter's send runs on its own goroutine.
func Middleware ¶
func Middleware(descriptor Descriptor, aliases ...string) benzene.Middleware
Middleware intercepts the reserved benzene:mesh topic (plus any additional aliases) and short-circuits the pipeline with descriptor, exactly as the healthcheck package does for its reserved topic. Interception is by topic ID alone, ignoring version, matching healthcheck's behavior. Any other topic passes through to next unchanged.
Registering this middleware is what "provisions the descriptor endpoint" - a deployment that must not expose it (e.g. pending security review) simply leaves it out of the Pipeline, and the trace feed keeps working independently.
func RegisterOutbound ¶
func RegisterOutbound[TReq, TRes any](r *OutboundRegistry, topic benzene.Topic) error
RegisterOutbound records topic as a message this service may send: the request type it sends as TReq, and the response type it expects back as TRes (mesh.md §2.3). A sender with no expected response type registers TRes as `any` - schema derivation already maps an interface type to `{}` (unconstrained), which is exactly the responseSchema mesh.md §2 specifies for "no declared response type".
Returns an error if topic is already registered - the same startup-error treatment Register gives a duplicate inbound registration.
func SpecHandler ¶
func SpecHandler(descriptor Descriptor) http.Handler
SpecHandler serves the service's derived spec document over HTTP for the Cloud Service Profile's R5 (docs/specification/cloud-service-profile.md §2). The default service standard mounts it at httpbinding.SpecPath ("/benzene/spec"; design-principles.md §5.2).
The spec document IS the service Descriptor - the registry-derived topic catalog with request/response JSON schemas (mesh.md §5.1). The profile does not mandate a particular spec format (OpenAPI, AsyncAPI, or Benzene's own), only that a real derived document is exposed; the descriptor is exactly that, and because it is derived from the registry it can never go stale (design-principles.md §3). This is the same truth the reserved benzene:mesh topic serves for R6, offered here as a plain GET surface for tooling that wants the spec without speaking the wire envelope.
Only GET is served (405 otherwise). Provisioning it is opt-in, exactly like the descriptor Middleware: a deployment that must not expose the spec (e.g. pending a security review, design-principles.md §5.1) simply doesn't mount it, and the rest of the mesh keeps working.
func TraceMiddleware ¶
func TraceMiddleware(info ServiceInfo, exporter Exporter) benzene.Middleware
TraceMiddleware observes every invocation that passes through it and hands the resulting TraceEvent to exporter after downstream middleware finishes. Register it outermost (before healthcheck/mesh/router interception) so it sees every invocation, including intercepted ones. Because the router converts missing handlers, conversion failures, and handler panics into Results rather than Go errors, every routed invocation produces a status - trace coverage is structural, not best-effort.
A nil exporter returns a pass-through middleware: the trace feed is simply off, and the service behaves identically to an unmeshed one. This is the "reduced mesh" posture - each feed degrades independently, never the service.
An incoming W3C traceparent header joins the existing trace (its trace-id is adopted and its parent-id recorded); a missing or malformed one starts a fresh trace. The x-correlation-id header, when present, is carried verbatim.
func WithTraceContext ¶
WithTraceContext wraps next so an outbound Send propagates the current invocation's mesh trace as a W3C `traceparent` header. It is the outbound half of trace-bindings.md §2's third cross-cutting client behavior ("correlation ID injection, trace context, retry") - the direct counterpart to this package's inbound TraceMiddleware, and it lives here rather than in `client` because the trace context it propagates is this package's Span (keeping `client` free of any mesh dependency).
It reads the span TraceMiddleware recorded for this invocation (SpanFromContext) and, when present, sets `traceparent` to that span's value - this invocation as the downstream call's parent - which is exactly what lets a collector derive who-calls-whom across services without anyone declaring an edge (see Span.Traceparent). Compose it over any transport's outbound client alongside client.WithCorrelationID / client.WithRetry; httpclient.Client and every binding's Client satisfy client.Sender.
Degradation is the package rule: with no trace middleware installed (no span on the context) the decorator sends no traceparent and the call is otherwise untouched - an unmeshed hop loses trace continuity, never the request.
When a span IS present it always wins: any `traceparent` already in the outbound headers is replaced (matched case-insensitively). This is deliberately UNLIKE WithCorrelationID - a correlation id propagates unchanged down a chain, but a traceparent must re-parent at every hop. The case that makes this load-bearing: a handler that forwards its own inbound headers onto the outbound call carries the *inbound* traceparent; leaving it would parent the downstream call to this service's caller and hide this service from the derived who-calls-whom graph - the exact thing the decorator exists to get right.
Types ¶
type Descriptor ¶
type Descriptor struct {
Service string `json:"service"`
ServiceVersion string `json:"serviceVersion,omitempty"`
InstanceID string `json:"instanceId,omitempty"`
Runtime string `json:"runtime"`
Binding string `json:"binding,omitempty"`
Placement Placement `json:"placement"`
Topics []TopicDescriptor `json:"topics"`
// Consumes is every registered outbound topic (mesh.md §2.3): what this service consumes.
// This is the field the collector's consumer-edge derivation reads (mesh.md §4) - a topic
// absent here is not consumed by this service, regardless of what traffic has or hasn't
// flowed. Populated the same way Topics is: always present, empty when the service consumes
// nothing (as opposed to a nil OutboundRegistry, which instead degrades the feed).
Consumes []TopicDescriptor `json:"consumes"`
// DescriptorHash is the contract hash (mesh.md §2.2): stable across instances and
// heartbeats of the same build, changed exactly when the contract changes - which is
// what lets a collector detect a redeploy (or a schema change without a version bump)
// from the hash alone. See descriptorHash for what it covers and excludes.
DescriptorHash string `json:"descriptorHash,omitempty"`
// Degraded lists the feeds that were unavailable when the descriptor was built (e.g.
// FeedRegistry when Describe was given a nil Registry, FeedOutboundRegistry when given a
// nil OutboundRegistry), so a reduced mesh is visible as reduced rather than mistaken for a
// service that provides or consumes nothing.
Degraded []string `json:"degraded,omitempty"`
}
Descriptor is the service self-description of mesh.md §2: identity, placement, the topic catalog derived from the Registry (what this service provides), and the consumed-topic catalog derived from the OutboundRegistry (what this service consumes, §2.3). It is what makes the mesh's catalog "derived, not declared" - there is no hand-maintained counterpart to go stale, on either side of the graph.
func Describe ¶
func Describe(registry *benzene.Registry, outbound *OutboundRegistry, info ServiceInfo) Descriptor
Describe builds the service Descriptor from the live registry, the live outbound registry, and info. Call it after all Register/RegisterOutbound calls (registration is a startup activity, so both lists are complete and static from then on). A nil registry or a nil outbound registry is not an error: the descriptor is built without that half of the catalog and the missing feed is recorded in Degraded, so a service whose registry or outbound-registry feed is deliberately not wired up still participates in the mesh reduced, rather than not at all - the same degradation rule applied symmetrically to both halves of the contract.
type Exporter ¶
type Exporter interface {
Export(ctx context.Context, event TraceEvent)
}
Exporter receives every TraceEvent the trace middleware produces. Implementations must be safe for concurrent use (one pipeline serves concurrent invocations) and should never block for long - and regardless of what they do, the middleware guarantees an exporter cannot affect the invocation it observed (a panic is swallowed, and the middleware has no error path to poison).
type Heartbeat ¶
type Heartbeat struct {
Service string `json:"service"`
InstanceID string `json:"instanceId,omitempty"`
DescriptorHash string `json:"descriptorHash,omitempty"`
SentAt time.Time `json:"sentAt"`
Health healthcheck.Response `json:"health"`
}
Heartbeat is the body of a mesh:heartbeat message (mesh.md §5.3): the standard aggregate health response reused byte-for-byte (no new health vocabulary), wrapped with identity and the contract hash - a changed hash is how a collector notices a redeploy and knows to re-fetch the descriptor.
type Issue ¶
type Issue struct {
// Fingerprint is the lowercase hex of the first 16 bytes of SHA-256 over
// service|topic|version|classification|discriminator (mesh.md §4.1); the collector's merge
// key. An empty fingerprint is an invalid entry and is skipped, never rejecting the batch.
Fingerprint string `json:"fingerprint"`
Classification string `json:"classification"`
Service string `json:"service"`
Topic string `json:"topic"`
Version string `json:"version,omitempty"`
Transport string `json:"transport,omitempty"`
Status string `json:"status"`
ExceptionType string `json:"exceptionType,omitempty"`
Count int64 `json:"count"`
FirstSeen time.Time `json:"firstSeen"`
LastSeen time.Time `json:"lastSeen"`
// ExemplarTraceIds links the signature to concrete flows; the collector keeps the newest ≤3.
ExemplarTraceIds []string `json:"exemplarTraceIds,omitempty"`
ResolutionHint string `json:"resolutionHint,omitempty"`
}
Issue is one deduplicated failure signature (mesh.md §4.1). Count is a DELTA - occurrences since the emitter's previous successful flush, never a cumulative total - so a collector's merge (count += delta, firstSeen = min, lastSeen = max, newest exemplars kept) is restart-proof and needs no instance identity on the wire.
type IssueBatch ¶
IssueBatch is the body of a benzene:mesh:issues message (mesh.md §4.1): a service's deduplicated, classified failure signatures since its last flush. Batch-level Service is REQUIRED (an aggregating relay may batch for several services); an empty Issues list is the feed's liveness assertion ("alive, nothing failing") and must be attributable.
type IssueOccurrence ¶
type IssueOccurrence struct {
Service string
Topic string
Version string
Status string
ExceptionType string
TraceID string
At time.Time
}
IssueOccurrence is one failure the middleware observed, before classification/fingerprinting. ExceptionType is left empty by the standard middleware: Go's router converts a handler panic to a service-unavailable Result before this middleware sees it, so there is no language-native thrown type to capture (mesh.md §4.1 makes exceptionType optional for exactly this reason).
type IssueRecorder ¶
type IssueRecorder interface {
Record(occurrence IssueOccurrence)
}
IssueRecorder receives the failure occurrences IssueMiddleware observes. Implementations must be safe for concurrent use and must not block the invocation.
type LogExporter ¶
type LogExporter struct {
// contains filtered or unexported fields
}
LogExporter writes one JSON line per TraceEvent to a writer - the zero-setup exporter: structured, greppable invocation logs on stdout, which every platform this module targets (Lambda, Functions, Cloud Run, plain processes) already collects. The push exporter that feeds a meshd collector is a later phase (mesh.md §8).
func NewLogExporter ¶
func NewLogExporter(w io.Writer) *LogExporter
NewLogExporter returns a LogExporter writing to w; a nil w means os.Stdout.
func (*LogExporter) Export ¶
func (e *LogExporter) Export(_ context.Context, event TraceEvent)
Export writes event as a single JSON line. Writes are serialized so concurrent invocations never interleave bytes within a line. A write failure is deliberately dropped: the feed is lossy by design, because an ailing log sink must never make the service slower or less reliable than an unmeshed one.
type OutboundRegistry ¶
type OutboundRegistry struct {
// contains filtered or unexported fields
}
OutboundRegistry holds outbound registration records (mesh.md §2.3): topics this service *may send*, with the request type it sends and the response type it expects back - no handler, since nothing here receives. It mirrors Registry's handler discovery exactly, minus the handler, for the identical reason core-concepts.md §9 requires explicit registration for inbound: the list this type holds is what makes ServiceDescriptor.Consumes a hard-coded contract rather than an inference. A port MUST NOT populate it by scanning call sites, string literals, or any other static analysis over handler bodies - RegisterOutbound is the only path.
A registered entry needs no destination address, queue name, or topic ARN: those are transport/deployment configuration (transport-bindings.md), orthogonal to the contract this registers. A service can declare it consumes payments:capture while its actual queue URL is injected at deploy time - the descriptor doesn't change between environments, only the wiring does.
func NewOutboundRegistry ¶
func NewOutboundRegistry() *OutboundRegistry
NewOutboundRegistry returns an empty OutboundRegistry.
func (*OutboundRegistry) TopicTypes ¶
func (r *OutboundRegistry) TopicTypes(topic benzene.Topic) (request, response reflect.Type, ok bool)
TopicTypes returns the request and response types captured when topic was registered, or ok = false when topic isn't registered. Mirrors Registry.TopicTypes.
func (*OutboundRegistry) Topics ¶
func (r *OutboundRegistry) Topics() []benzene.Topic
Topics returns every registered outbound topic, sorted by ID then Version - mirrors Registry.Topics, the enumeration behind Descriptor.Consumes.
type Placement ¶
Placement locates a service instance (mesh.md §4.3). Cloud is one of "aws", "azure", "gcp", or "self-hosted" when detected; an explicit ServiceInfo.Placement override may carry any value.
func DetectPlacement ¶
func DetectPlacement() Placement
DetectPlacement identifies where this process runs from each platform's documented environment variables (mesh.md §4.3), so placement needs no configuration on the three clouds this module ships bindings for:
- AWS Lambda: AWS_LAMBDA_FUNCTION_NAME (a defined Lambda runtime variable); region from AWS_REGION.
- Azure Functions: FUNCTIONS_CUSTOMHANDLER_PORT, the same documented custom-handler contract variable the azurefunctions package is driven by. The Functions host does not expose its region through a documented environment variable, so Region is left empty rather than guessed.
- Google Cloud Run: K_SERVICE (a documented Cloud Run/Knative variable). Cloud Run exposes region only via the metadata server, not the environment, so Region is left empty rather than guessed.
When none match, the placement is "self-hosted". A service that knows better (or runs on a platform not listed) sets ServiceInfo.Placement explicitly, which bypasses detection entirely.
type PushExporter ¶
type PushExporter struct {
// contains filtered or unexported fields
}
PushExporter batches TraceEvents and sends them to a collector as mesh:traces envelopes (mesh.md §8 Phase 3) from a single background goroutine, so exporting never runs on an invocation's goroutine beyond a non-blocking channel send.
The trace feed is lossy by design, in every failure mode: a full buffer drops the new event, a failed send drops the batch (the Sender contract already converts transport failures to a Result, never an error), and a nil Sender yields a nil exporter whose methods are all nil-safe no-ops. The mesh must never make a service slower or less reliable than an unmeshed one.
func NewPushExporter ¶
func NewPushExporter(sender Sender, options PushExporterOptions) *PushExporter
NewPushExporter starts the background sender and returns the exporter. A nil sender returns a nil exporter - usable directly as TraceMiddleware's exporter thanks to nil-safe methods, degrading to a disabled trace feed.
func (*PushExporter) Close ¶
func (e *PushExporter) Close()
Close flushes everything already queued and stops the background sender. Safe to call more than once and on a nil exporter. Call it on shutdown so the tail of the trace feed isn't lost with the process.
func (*PushExporter) Export ¶
func (e *PushExporter) Export(_ context.Context, event TraceEvent)
Export enqueues event for the background sender. Never blocks: a full buffer drops the event. Nil-safe, so a (*PushExporter)(nil) wired in as an Exporter behaves as the disabled trace feed rather than panicking.
type PushExporterOptions ¶
type PushExporterOptions struct {
// BatchSize is the number of events that triggers an immediate flush. Default 64.
BatchSize int
// FlushInterval is how often a partial batch is flushed anyway, so a quiet service's
// traces still arrive promptly. Default 5s.
FlushInterval time.Duration
// BufferSize is the queue between invocations and the background sender. When it is
// full, Export drops new events rather than blocking. Default 1024.
BufferSize int
}
PushExporterOptions tunes a PushExporter. Zero values mean the defaults.
type PushIssueExporter ¶
type PushIssueExporter struct {
// contains filtered or unexported fields
}
PushIssueExporter is the emitter half of the issue feed (mesh.md §4.1): it deduplicates failure occurrences by fingerprint at the source (per-occurrence events are never sent), accumulating delta counts, and flushes them as one benzene:mesh:issues envelope from a single background goroutine. Like the trace feed it is lossy by design - a failed send drops the window's deltas (the collector's delta-merge tolerates the loss) - and a nil Sender yields a nil exporter whose methods are nil-safe no-ops, degrading to a disabled feed.
func NewPushIssueExporter ¶
func NewPushIssueExporter(sender Sender, service string, options PushIssueExporterOptions) *PushIssueExporter
NewPushIssueExporter starts the background flusher and returns the exporter. A nil sender or an empty service returns a nil exporter - usable directly as IssueMiddleware's recorder thanks to nil-safe methods, degrading to a disabled issue feed.
func (*PushIssueExporter) Close ¶
func (e *PushIssueExporter) Close()
Close flushes the current window and stops the background flusher. Safe to call more than once and on a nil exporter; call it on shutdown so the tail of the feed isn't lost with the process.
func (*PushIssueExporter) Flush ¶
func (e *PushIssueExporter) Flush()
Flush sends the accumulated deltas as one benzene:mesh:issues batch and resets the window. It always sends - an empty window flushes an empty batch, the feed's liveness assertion (mesh.md §4.1) - so a collector can distinguish a quiet wired service from an unwired one. The send result is deliberately ignored: a dropped batch loses its deltas, lossy by design. Exposed for tests and for a caller that wants to flush on its own schedule; nil-safe.
func (*PushIssueExporter) Record ¶
func (e *PushIssueExporter) Record(occurrence IssueOccurrence)
Record classifies, fingerprints, and merges one occurrence into the current flush window. Never blocks for long (a brief map lock) and is nil-safe, so a (*PushIssueExporter)(nil) wired in behaves as the disabled feed.
type PushIssueExporterOptions ¶
type PushIssueExporterOptions struct {
// FlushInterval is how often the accumulated deltas are flushed as one benzene:mesh:issues
// batch. An empty interval flush still sends an empty batch - the feed's liveness assertion,
// so a collector can tell a quiet wired service from an unwired one. Default 5s.
FlushInterval time.Duration
// Now supplies the clock, for tests. Defaults to time.Now.
Now func() time.Time
}
PushIssueExporterOptions tunes a PushIssueExporter. Zero values mean the defaults.
type Sender ¶
type Sender interface {
Send(ctx context.Context, topic benzene.Topic, headers map[string]string, message []byte) benzene.Result[json.RawMessage]
}
Sender is the outbound-client subset PushExporter needs - transport-bindings.md §2's one send interface, satisfied by *httpclient.Client. An interface rather than the concrete client so the trace feed isn't married to HTTP: any transport that can carry a wire envelope can carry mesh traffic.
type ServiceInfo ¶
type ServiceInfo struct {
Service string
ServiceVersion string
InstanceID string
Binding string
Placement Placement
}
ServiceInfo is the static identity a service supplies to Describe and TraceMiddleware. Every field is optional; zero values simply leave the corresponding descriptor/trace fields empty. Placement, when its Cloud is non-empty, overrides detection wholesale - otherwise DetectPlacement runs.
type Span ¶
Span is the current invocation's position in a trace: the ids TraceMiddleware assigned (or adopted from the caller's traceparent) for the event it will export. Handlers use it to propagate the trace onto outbound calls, which is what lets a collector join spans across services - an observed signal for liveness and undeclared-edge drift (mesh.md §4.2), layered on top of the producer/consumer graph the declared ServiceDescriptor alone provides (mesh.md §4), never a way to derive who-calls-whom in place of declaring it.
func SpanFromContext ¶
SpanFromContext returns the span TraceMiddleware recorded for the current invocation. ok is false when no trace middleware is installed - per this package's degradation rule, a caller must then simply send no traceparent header (an unmeshed hop degrades trace continuity, never the call).
func (Span) Traceparent ¶
Traceparent renders the span as a W3C traceparent header value for outbound propagation: version 00, this span as the parent-id, sampled flag set.
type TopicDescriptor ¶
type TopicDescriptor struct {
ID string `json:"id"`
Version string `json:"version,omitempty"`
RequestSchema map[string]any `json:"requestSchema"`
ResponseSchema map[string]any `json:"responseSchema"`
}
TopicDescriptor is one registered topic in a Descriptor (mesh.md §5.1). The schemas describe the marshaled request/response forms, derived at startup from the TReq/TRes types captured at the Register call site (see deriveSchema for the exact mapping); they are what lets the mesh flag schema drift from live data instead of hand-written specs. The schemas are never omitted from the wire even when unconstrained ({}): a consumed topic with no declared response type (mesh.md §2.3) marshals responseSchema as the empty object, not as an absent key - a reader must be able to tell "unconstrained" from "not derived".
type TraceBatch ¶
type TraceBatch struct {
Events []TraceEvent `json:"events"`
}
TraceBatch is the body of a mesh:traces message: the events a PushExporter accumulated since its last flush.
type TraceEvent ¶
type TraceEvent struct {
TraceID string `json:"traceId"`
SpanID string `json:"spanId"`
ParentSpanID string `json:"parentSpanId,omitempty"`
Service string `json:"service,omitempty"`
InstanceID string `json:"instanceId,omitempty"`
Topic string `json:"topic"`
TopicVersion string `json:"topicVersion,omitempty"`
// Status is the Benzene status verbatim (empty when no downstream middleware produced
// a Result - a pipeline without a router, which is a wiring gap the mesh reports
// as-is rather than papering over).
Status string `json:"status"`
DurationMs float64 `json:"durationMs"`
StartedAt time.Time `json:"startedAt"`
CorrelationID string `json:"correlationId,omitempty"`
}
TraceEvent is one pipeline invocation as seen by the mesh (mesh.md §5.2). It is semantic where a transport-level span is not: it carries the topic (+ version) and the Benzene status, not a URL and an HTTP code. TraceID/SpanID/ParentSpanID are the W3C traceparent fields (hex, 16/8/8 bytes), so mesh traces interleave with any existing OpenTelemetry pipeline instead of competing with it.