Files
o11y/docs/contributing/go/flagger.md
T
Hanzo DevandGitHub fe4bfd3cf4 debrand: SigNoz -> O11y across the tree (+ otel-collector v0.144.7, schema cutover migration) (#28)
* debrand: signoz/SigNoz/SIGNOZ -> o11y/O11y/O11Y across the tree

Rename all SigNoz *branding* to O11y in file contents, file/dir names,
package names, env vars, config keys, comments, docs, SDK code, and the
ClickHouse schema identifiers the querier reads.

Collector dependency:
- Repoint github.com/hanzoai/signoz-otel-collector ->
  github.com/hanzoai/otel-collector and bump v0.144.6 -> v0.144.7
  (go.mod + go.sum reconciled via go mod tidy). Internal package paths
  also debranded upstream: signozschemamigrator -> o11yschemamigrator,
  signozlogspipelineprocessor -> o11ylogspipelineprocessor.
- Fix: v0.144.7 dropped otelconst.DistributedFieldKeysTable from the
  collector's public constants; pin FieldKeysTable = "distributed_field_keys"
  locally in pkg/telemetrymetadata/tables.go.

Package/dir renames (package decl + all importers):
  pkg/apiserver/signozapiserver -> o11yapiserver, plus signozalertmanager,
  signozauthzapi, signozglobal, signozquerier, signozruler, and pkg/signoz -> pkg/o11y.

Schema (read plane): signoz_traces/metrics/logs/metadata/analytics/meter and
signoz_index_v3/index_v2/error_index_v2/spans (+ distributed_*) -> o11y_*.
Data-preserving cutover migration added:
  deploy/clickhouse/migrations/0001_rename_signoz_to_o11y.sql
  (metadata-only RENAME DATABASE/TABLE, no drop/recreate).

Preserved (attribution / external / wire contracts):
- LICENSE + NOTICE verbatim (incl. "software from SigNoz", "Copyright ... SigNoz Inc.").
- Upstream github.com/SigNoz/* repo URLs + @SigNoz/* CODEOWNERS teams.
- Billing resource-attribute regex "signoz.workspace.*" (external wire contract).

go build ./pkg/... ./cmd/... = 0; gofmt clean; renamed+schema pkg tests pass.

* o11y: bump collector to v0.144.8 (o11y_* physical schema writer) + lockstep deploy plan

Collector v0.144.8's schema-migrator + exporters now CREATE/WRITE the same
o11y_* physical databases/tables the querier reads and the RENAME migration
(0001_rename_signoz_to_o11y.sql) targets.

Cross-checked byte-identical: writer DB set == migration target DB set (6);
writer prefixed-table set == migration target table set (8); o11y querier
reads and cloud reads are subsets of that set. Zero table-name mismatches
between writer, readers, and migration.

Collector go.mod unchanged between v0.144.7 and v0.144.8 (identical go.mod
hash) — pure source rename, no module-graph change.

Adds deploy/clickhouse/DEPLOY_signoz_to_o11y_cutover.md: the lockstep order
(collector v0.144.8 -> RENAME migration -> o11y+cloud readers) with a
fresh/scratch-ClickHouse pre-flight (create via migrator, emit trace+metric,
query back) and rollback. Ships together or telemetry blackholes.

go build ./pkg/... ./cmd/community = 0.
2026-07-09 13:08:55 -07:00

4.7 KiB

Flagger

Flagger is Hanzo O11y's feature flagging system built on top of the OpenFeature standard. It provides a unified interface for evaluating feature flags across the application, allowing features to be enabled, disabled, or configured dynamically without code changes.

💡 Note: OpenFeature is a CNCF project that provides a vendor-agnostic feature flagging API, making it easy to switch providers without changing application code.

How does it work?

Flagger consists of three main components:

  1. Registry (pkg/flagger/registry.go) - Contains all available feature flags with their metadata and default values
  2. Flagger (pkg/flagger/flagger.go) - The consumer-facing interface for evaluating feature flags
  3. Providers (pkg/flagger/<provider>flagger/) - Implementations that supply feature flag values (e.g., configflagger for config-based flags)

The evaluation flow works as follows:

  1. The caller requests a feature flag value via the Flagger interface
  2. Flagger checks the registry to validate the flag exists and get its default value
  3. Each registered provider is queried for an override value
  4. If a provider returns a value different from the default, that value is returned
  5. Otherwise, the default value from the registry is returned

How to add a new feature flag?

1. Register the flag in the registry

Add your feature flag definition in pkg/flagger/registry.go:

var (
    // Export the feature name for use in evaluations
    FeatureMyNewFeature = featuretypes.MustNewName("my_new_feature")
)

func MustNewRegistry() featuretypes.Registry {
    registry, err := featuretypes.NewRegistry(
        // ...existing features...
        &featuretypes.Feature{
            Name:           FeatureMyNewFeature,
            Kind:           featuretypes.KindBoolean, // or KindString, KindFloat, KindInt, KindObject
            Stage:          featuretypes.StageStable, // or StageAlpha, StageBeta
            Description:    "Controls whether my new feature is enabled",
            DefaultVariant: featuretypes.MustNewName("disabled"),
            Variants:       featuretypes.NewBooleanVariants(),
        },
    )
    // ...
}

💡 Note: Feature names must match the regex ^[a-z_]+$ (lowercase letters and underscores only).

2. Configure the feature flag value (optional)

To override the default value, add an entry in your configuration file:

flagger:
  config:
    boolean:
      my_new_feature: true

Supported configuration types:

Type Config Key Go Type
Boolean boolean bool
String string string
Float float float64
Integer integer int64
Object object any

How to evaluate a feature flag?

Use the Flagger interface to evaluate feature flags. The interface provides typed methods for each value type:

import (
    "github.com/Hanzo O11y/o11y/pkg/flagger"
    "github.com/Hanzo O11y/o11y/pkg/types/featuretypes"
)

func DoSomething(ctx context.Context, flagger flagger.Flagger) error {
    // Create an evaluation context (typically with org ID)
    evalCtx := featuretypes.NewFlaggerEvaluationContext(orgID)
    
    // Evaluate with error handling
    enabled, err := flagger.Boolean(ctx, flagger.FeatureMyNewFeature, evalCtx)
    if err != nil {
        return err
    }
    
    if enabled {
        // Feature is enabled
    }
    
    return nil
}

Empty variants

For cases where you want to use a default value on error (and log the error), use the *OrEmpty methods:

func DoSomething(ctx context.Context, flagger flagger.Flagger) {
    evalCtx := featuretypes.NewFlaggerEvaluationContext(orgID)
    
    // Returns false on error and logs the error
    if flagger.BooleanOrEmpty(ctx, flagger.FeatureMyNewFeature, evalCtx) {
        // Feature is enabled
    }
}

Available evaluation methods

Method Return Type Empty Variant Default
Boolean() (bool, error) false
String() (string, error) ""
Float() (float64, error) 0.0
Int() (int64, error) 0
Object() (any, error) struct{}{}

What should I remember?

  • Always define feature flags in the registry (pkg/flagger/registry.go) before using them
  • Use descriptive feature names that clearly indicate what the flag controls
  • Prefer *OrEmpty methods for non-critical features to avoid error handling overhead
  • Export feature name variables (e.g., FeatureMyNewFeature) for type-safe usage across packages
  • Consider the feature's lifecycle stage (Alpha, Beta, Stable) when defining it
  • Providers are evaluated in order; the first non-default value wins