clues

package module
v0.0.0-...-1b7c88a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 15, 2026 License: MIT Imports: 11 Imported by: 40

README

CLUES

/kluːz/

noun

  1. Something that guides through an intricate procedure or maze of difficulties. Specifically: a piece of evidence that leads one toward the solution of a problem.

verb

  1. To give reliable information to.

PkgGoDev goreportcard

Clues is a golang telemetry aid designed to simplify debugging down to an O(1) operation. What is O(1) debugging? That's when a single event provides all the runtime context you need to grok what was happening in your code at that moment.

Clues works in tandem with the Cluerr and Clog subpackages to produce a simple api that achieves this goal. By populating a context-bound cache of runtime variables, Cluerr can bind a cache snapshot within an error, and Clog extracts those variables for logging.

Need more support? Clues comes with OTEL configuration out of the box. Attributes added to the context are automatically added to the span, while Clog implicitly handles OTEL logging alongside the default stdout logger. Additional support packages like Ctats and Clutel help minimize the effort of engaging with those systems and provide a consistent interface for your telemetry production.


How To Get A Clue

func foo(ctx context.Context, someID string) error {
    // Annotate the ctx with your important runtime attributes.
    ctx = clues.Add(ctx, "important_id", someID)
    return bar(ctx, someID)
}
func bar(ctx context.Context, someID string) error {
    err := externalPkg.DoThing(ctx, someID)
    if err != nil {
        // Wrap the error with a snapshot of the current attributes
        return clues.WrapWC(ctx, err, "doing something")
    }
    return nil
}
func main() {
    err := foo(context.Background(), "importantID")
    if err != nil {
        // Centralize your logging at the top
        // without losing any low-level details.
        clog.
            CtxErr(ctx, err).
            Error("calling foo")
    }
}

Quickstart

The most basic setup only needs to flush the logger on exit.

package main

import (
    "context"

    "github.com/alcionai/clues/clog"
)
func main() {
  ctx := clog.Init(context.Background(), clog.Settings{
   Format: clog.FormatForHumans,
  })
  defer clog.Flush()
  // And away you go!
}

OTEL support requires its own initialization and flush.

package main
import (
    "context"

    "github.com/alcionai/clues"
    "github.com/alcionai/clues/clog"
    "github.com/alcionai/clues/clutel"
)

func main() {
  ctx, err := clues.InitializeOTEL(
    context.Background(),
    myServiceName,
    clutel.OTELConfig{
      GRPCEndpoint: os.GetEnv(myconsts.OTELGRPCEndpoint),
    },
  )
  if err != nil {
    panic(err)
  }

  ctx = clog.Init(ctx, clog.Settings{
    Format: clog.FormatForHumans,
  })

  defer func() {
    clog.Flush(ctx)

    err := clues.Close(ctx)
    if err != nil {
      // use the standard log here since clog was already flushed
      log.Printf("closing clues: %v\n", err)
    }
  }
}

Resources


Why Not {my favorite logging package}?

Many logging packages let you build attributes within the context, handing in down the layers of your runtime stack. But that's often as far as they go. In order to utilize those built-up attributes, you have to push all of your logging to the leaves of your process tree. This adds considerable boilerplate, not to mention cognitive burden, to your telemetry surface area.

Providing an interface that hooks into both downward (ctx) and upward (error) data transmission, clues helps you minimize the amount of logging you do to only those necessary occurrences.

As for your favorite logger: look forward for more robust logger support in clog in the future.

Why Not Just OTEL?

OTEL is awesome. We love OTEL 'round here. We do not, however, love the effort it takes to set it up. And we think the apis are awful clunky.

If your codebase is already OTEL-enabled and decked out, then there isn't much clues can offer except for nicer, cleaner code and happier devs.

But if you're on a new project, prototyping a new feature, or switching to OTEL from some other paradigm, then Clues can get your telemetry bootstrapped and running in, well, less time that it took you to read this far.

Documentation

Index

Constants

View Source
const (
	DefaultOTELGRPCEndpoint = "localhost:4317"
)

Variables

View Source
var AllowAllBaggage baggagecopy.Filter = func(baggage.Member) bool { return true }

AllowAllBaggage is a filter which allows copying of all members of baggage to a span.

View Source
var BlockAllBaggage baggagecopy.Filter = func(baggage.Member) bool { return false }

BlockAllBaggage is a filter which blocks copying of all members of baggage to a span.

View Source
var ErrMissingOtelGRPCEndpoint = errors.New("missing otel grpc endpoint")

Functions

func Add

func Add(ctx context.Context, kvs ...any) context.Context

Add adds all key-value pairs to the clues.

func AddAgent

func AddAgent(
	ctx context.Context,
	name string,
) context.Context

AddAgent adds an agent with a given name to the context. What's an agent? It's a special case data adder that you can spawn to collect clues for you. Unlike standard clues additions, you have to tell the agent exactly what data you want it to Relay() for you.

Agents are recorded in the current clues node and all of its descendants. Data relayed by the agent will appear as part of the standard data map, namespaced by each agent.

Agents are specifically handy in a certain set of uncommon cases where retrieving clues is otherwise difficult to do, such as working with middleware that doesn't allow control over error creation. In these cases your only option is to relay that data back to some prior clues node.

func AddComment

func AddComment(
	ctx context.Context,
	msg string,
	vs ...any,
) context.Context

AddComment adds a long form comment to the clues.

Comments are special case additions to the context. They're here to, well, let you add comments! Why? Because sometimes it's not sufficient to have a log let you know that a line of code was reached. Even a bunch of clues to describe system state may not be enough. Sometimes what you need in order to debug the situation is a long-form explanation (you do already add that to your code, don't you?). Or, even better, a linear history of long-form explanations, each one building on the prior (which you can't easily do in code).

Should you transfer all your comments to clues? Absolutely not. But in cases where extra explantion is truly important to debugging production, when all you've got are some logs and (maybe if you're lucky) a span trace? Those are the ones you want.

Unlike other additions, which are added as top-level key:value pairs to the context, comments are all held as a single array of additions, persisted in order of appearance, and prefixed by the file and line in which they appeared. This means comments are always added to the context and never clobber each other, regardless of their location. IE: don't add them to a loop.

func AddMap

func AddMap[K comparable, V any](
	ctx context.Context,
	m map[K]V,
) context.Context

AddMap adds a shallow clone of the map to a namespaced set of clues.

func AddSpan

func AddSpan(
	ctx context.Context,
	name string,
	kvs ...any,
) context.Context

deprecated: use clutel.StartSpan instead. AddSpan stacks a clues node onto this context and uses the provided name for the trace id, instead of a randomly generated hash. AddSpan can be called without additional values if you only want to add a trace marker. The assumption is that an otel span is generated and attached to the node. Callers should always follow this addition with a closing `defer clues.CloseSpan(ctx)`.

func Close

func Close(ctx context.Context) error

Close will flush all buffered data waiting to be read. If Initialize was not called, this call is a no-op. Should be called in a defer after initializing.

func CloseSpan

func CloseSpan(ctx context.Context)

deprecated: use clutel.EndSpan instead. CloseSpan closes the current span in the clues node. Should only be called following a `clues.AddSpan()` call.

func In

func In(ctx context.Context) *node.Node

In retrieves the clues structured data from the context.

func Inherit

func Inherit(
	from, to context.Context,
	clobber bool,
) context.Context

Inherit propagates all clients and attributes from one context to another. This is particularly useful for taking an initialized context from a main() func and ensuring its clients are available for request-bound conetxts, such as in a http server pattern.

If the 'to' context already contains initialized clients or attrs, no changes are made. Callers can force a 'from' clie to override a 'to' client by setting clobber=true.

func InitializeOTEL

func InitializeOTEL(
	ctx context.Context,
	serviceName string,
	config OTELConfig,
) (context.Context, error)

InitializeOTEL will spin up the OTEL clients that are held by clues, Clues will eagerly use these clients in the background to provide additional telemetry hook-ins.

Clues will operate as expected in the event of an error, or if OTEL is not initialized. This is a purely optional step.

DEPRECATED: use clutel.Init instead.

func InjectTrace

func InjectTrace[C node.TraceMapCarrierBase](
	ctx context.Context,
	mapCarrier C,
) C

deprecated: use clutel.InjectTrace instead. InjectTrace adds the current trace details to the provided headers. If otel is not initialized, no-ops.

The mapCarrier is mutated by this request. The passed reference is returned mostly as a quality-of-life step so that callers don't need to declare the map outside of this call.

func ReceiveTrace

func ReceiveTrace[C node.TraceMapCarrierBase](
	ctx context.Context,
	mapCarrier C,
) context.Context

deprecated: use clutel.ReceiveTrace instead. ReceiveTrace extracts the current trace details from the headers and adds them to the context. If otel is not initialized, no-ops.

func Relay

func Relay(
	ctx context.Context,
	agent string,
	vs ...any,
)

Relay adds all key-value pairs to the provided agent. The agent will record those values to the node in which it was created. All relayed values are namespaced to the owning agent.

Types

type OTELConfig

type OTELConfig struct {
	// Resource contains information about the thing sourcing logs, metrics, and
	// traces in OTEL. This information will be available in backends on all logs
	// traces, and metrics that are generated from this source.
	//
	// The provided resource should represent the service that's initializing
	// clues. The resource should encapsulate all parts of the metrics that need
	// reporting, not just a subset of them (i.e. it represents the "root" of the
	// information that will be reported to OTEL).
	//
	// If not provided, a minimal Resource containing the service name will be
	// created.
	Resource *resource.Resource

	// specify the endpoint location to use for grpc communication.
	// If empty, no telemetry exporter will be generated.
	// ex: localhost:4317
	// ex: 0.0.0.0:4317
	// ex: opentelemetry-collector.monitoring.svc.cluster.local:4317
	GRPCEndpoint string

	// Filter contains the filter used when copying baggage to a span, by adding span
	// attributes. If no filter is specified, all baggage is copied over to a span.
	Filter baggagecopy.Filter
}

DEPRECATED: use clutel.OTELConfig instead.

Directories

Path Synopsis
experimental
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL