Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ func newStartedApp(
&inject.Object{Value: http.DefaultTransport, Name: "upstreamTransport"},
&inject.Object{Value: upstreamTransmission, Name: "upstreamTransmission"},
&inject.Object{Value: peerTransmissionWrapper, Name: "peerTransmission"},
&inject.Object{Value: &transmit.NoopTransmission{}, Name: "gcsExport"},
&inject.Object{Value: shrdr},
&inject.Object{Value: noop.NewTracerProvider().Tracer("test"), Name: "tracer"},
&inject.Object{Value: collector},
Expand Down
8 changes: 8 additions & 0 deletions cmd/refinery/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ func main() {
nil, // No custom headers for peer-to-peer traffic
)

// the GCS exporter is optional; when it's disabled we inject a noop so
// that the collector's dependency is always satisfied
var gcsExport transmit.Transmission = &transmit.NoopTransmission{}
if c.GetGCSExportConfig().Enabled {
gcsExport = transmit.NewGCSTransmission()
}

// we need to include all the metrics types so we can inject them in case they're needed
// but we only want to instantiate the ones that are enabled with non-null values
var promMetrics metrics.MetricsBackend = &metrics.NullMetrics{}
Expand Down Expand Up @@ -241,6 +248,7 @@ func main() {
{Value: peerTransport, Name: "peerTransport"},
{Value: upstreamTransmission, Name: "upstreamTransmission"},
{Value: peerTransmission, Name: "peerTransmission"},
{Value: gcsExport, Name: "gcsExport"},
{Value: shrdr},
{Value: collector},
{Value: promMetrics, Name: "promMetrics"},
Expand Down
18 changes: 18 additions & 0 deletions collect/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ type InMemCollector struct {

Transmission transmit.Transmission `inject:"upstreamTransmission"`
PeerTransmission transmit.Transmission `inject:"peerTransmission"`
GCSExport transmit.Transmission `inject:"gcsExport"`
PubSub pubsub.PubSub `inject:""`
Metrics metrics.Metrics `inject:"metrics"`
SamplerFactory *sample.SamplerFactory `inject:""`
Expand Down Expand Up @@ -491,10 +492,20 @@ func (i *InMemCollector) ProcessSpanImmediately(sp *types.Span) (processed bool,
i.addAdditionalAttributes(sp)
mergeTraceAndSpanSampleRates(sp, rate, i.Config.GetIsDryRun())
i.Transmission.EnqueueSpan(sp)
i.exportSpan(sp)

return true, true
}

// exportSpan sends a kept span to the optional GCS exporter, if one is
// configured. It must only be called for spans of traces that were actually
// kept by the sampler (not for dry-run "would have dropped" sends).
func (i *InMemCollector) exportSpan(sp *types.Span) {
if i.GCSExport != nil {
i.GCSExport.EnqueueSpan(sp)
}
}

// dealWithSentTrace handles a span that has arrived after the sampling decision
// on the trace has already been made, and it obeys that decision by either
// sending the span immediately or dropping it.
Expand Down Expand Up @@ -557,6 +568,7 @@ func (i *InMemCollector) dealWithSentTrace(ctx context.Context, tr cache.TraceSe
i.Metrics.Increment(TraceSendLateSpan)
i.addAdditionalAttributes(sp)
i.Transmission.EnqueueSpan(sp)
i.exportSpan(sp)
return
}
i.Metrics.Increment("events_dropped")
Expand Down Expand Up @@ -834,6 +846,12 @@ func (i *InMemCollector) sendTraces() {

sp.APIKey = t.APIKey
i.Transmission.EnqueueSpan(sp)
// only export spans of traces the sampler actually kept; in dry
// run mode, traces that would have been dropped still reach here
// with shouldSend=false and are not exported.
if t.shouldSend {
i.exportSpan(sp)
}
}
span.End()
}
Expand Down
1 change: 1 addition & 0 deletions collect/collect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,7 @@ func TestDependencyInjection(t *testing.T) {
&inject.Object{Value: &sharder.SingleServerSharder{}},
&inject.Object{Value: &transmit.MockTransmission{}, Name: "upstreamTransmission"},
&inject.Object{Value: &transmit.MockTransmission{}, Name: "peerTransmission"},
&inject.Object{Value: &transmit.NoopTransmission{}, Name: "gcsExport"},
&inject.Object{Value: &pubsub.LocalPubSub{}},
&inject.Object{Value: &metrics.NullMetrics{}, Name: "metrics"},
&inject.Object{Value: &sample.SamplerFactory{}},
Expand Down
74 changes: 73 additions & 1 deletion config.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Honeycomb Refinery Configuration Documentation

This is the documentation for the configuration file for Honeycomb's Refinery.
It was automatically generated on 2026-04-09 at 22:21:32 UTC.
It was automatically generated on 2026-07-07 at 21:27:51 UTC.

## The Config file

Expand Down Expand Up @@ -48,6 +48,7 @@ The remainder of this document describes the sections within the file and the fi
- [gRPC Server Parameters](#grpc-server-parameters)
- [Sample Cache](#sample-cache)
- [Stress Relief](#stress-relief)
- [GCS Export](#gcs-export)
## General Configuration

`General` contains general configuration options that apply to the entire Refinery process.
Expand Down Expand Up @@ -1315,3 +1316,74 @@ This setting helps to prevent oscillations.
- Type: `duration`
- Default: `10s`

## GCS Export

`GCSExport` contains configuration for optionally exporting sampled (kept) trace spans to a Google Cloud Storage bucket in addition to sending them to Honeycomb.
Spans are buffered in memory and written as gzipped JSON Lines objects, batched into time-partitioned paths of the form `KeyPrefix/YYYY/MM/DD/HH/hostname-timestamp.jsonl.gz`.
Authentication uses Google Application Default Credentials.

### `Enabled`

Enabled controls whether kept trace spans are also exported to a GCS bucket.

If `true`, every span belonging to a trace that Refinery decides to keep is also written to the configured GCS bucket.
Spans of dropped traces are never exported.

- Not eligible for live reload.
- Type: `bool`

### `Bucket`

Bucket is the name of the GCS bucket to which sampled traces are exported.

The bucket must already exist and the credentials available via Application Default Credentials must have permission to create objects in it.
Required when `Enabled` is `true`.

- Not eligible for live reload.
- Type: `string`
- Example: `my-trace-archive`
- Environment variable: `REFINERY_GCS_EXPORT_BUCKET`

### `KeyPrefix`

KeyPrefix is the object name prefix under which exported objects are written.

Object names are formed by appending time-partitioned path elements to this prefix.
Leave empty to write at the root of the bucket.

- Not eligible for live reload.
- Type: `string`
- Example: `refinery/traces`

### `FlushInterval`

FlushInterval is the maximum time a batch of spans is buffered before being written to GCS.

A batch is flushed to GCS when it reaches `MaxBatchSize` or when this interval has elapsed since the batch was started, whichever comes first.

- Not eligible for live reload.
- Type: `duration`
- Default: `60s`

### `MaxBatchSize`

MaxBatchSize is the maximum uncompressed size of a batch before it is written to GCS.

When the serialized (uncompressed) size of the current batch exceeds this value, it is compressed and written to GCS immediately.
The objects stored in GCS are gzip-compressed and thus considerably smaller than this value.

- Not eligible for live reload.
- Type: `memorysize`
- Default: `100MB`

### `QueueSize`

QueueSize is the number of spans that can be queued for export before spans are dropped.

Spans are handed to the exporter through a fixed-size queue so that a slow or unavailable GCS never blocks sending traces to Honeycomb.
If the queue is full, new spans are dropped from the export (they are still sent to Honeycomb) and the `gcs_export_dropped` metric is incremented.

- Not eligible for live reload.
- Type: `int`
- Default: `100000`

1 change: 1 addition & 0 deletions config/cmdenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type CmdEnv struct {
QueryAuthToken string `long:"query-auth-token" env:"REFINERY_QUERY_AUTH_TOKEN" description:"Token for debug/management queries. Setting this value via a flag may expose credentials - it is recommended to use the env var or a configuration file."`
AvailableMemory MemorySize `long:"available-memory" env:"REFINERY_AVAILABLE_MEMORY" description:"The maximum memory available for Refinery to use (ex: 4GiB)."`
SendKey string `long:"send-key" env:"REFINERY_SEND_KEY" description:"The Honeycomb API key that Refinery can use to send data to Honeycomb."`
GCSExportBucket string `long:"gcs-export-bucket" env:"REFINERY_GCS_EXPORT_BUCKET" description:"Name of the GCS bucket to export sampled traces to (when GCSExport is enabled)."`
Debug bool `short:"d" long:"debug" description:"Runs debug service (on the first open port between localhost:6060 and :6069 by default)"`
Version bool `short:"v" long:"version" description:"Print version number and exit"`
InterfaceNames bool `long:"interface-names" description:"Print system's network interface names and exit."`
Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ type Config interface {

GetOTelTracingConfig() OTelTracingConfig

// GetGCSExportConfig returns the config specific to GCSExport
GetGCSExportConfig() GCSExportConfig

GetUseIPV6Identifier() bool

GetRedisIdentifier() string
Expand Down
1 change: 1 addition & 0 deletions config/config_serializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func populateConfigContents(cfg Config) configContents {
GRPCServerParameters: cfg.GetGRPCConfig(),
SampleCache: cfg.GetSampleCacheConfig(),
StressRelief: cfg.GetStressReliefConfig(),
GCSExport: cfg.GetGCSExportConfig(),
}
}

Expand Down
19 changes: 19 additions & 0 deletions config/file_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type configContents struct {
GRPCServerParameters GRPCServerParameters `yaml:"GRPCServerParameters"`
SampleCache SampleCacheConfig `yaml:"SampleCache"`
StressRelief StressReliefConfig `yaml:"StressRelief"`
GCSExport GCSExportConfig `yaml:"GCSExport"`
}

type GeneralConfig struct {
Expand Down Expand Up @@ -287,6 +288,17 @@ type OTelMetricsConfig struct {
AdditionalAttributes map[string]string `yaml:"AdditionalAttributes" default:"{}" cmdenv:"OTelMetricsAdditionalAttributes"`
}

// GCSExportConfig configures the optional export of sampled (kept) trace
// spans to a Google Cloud Storage bucket as gzipped JSON Lines objects.
type GCSExportConfig struct {
Enabled bool `yaml:"Enabled" default:"false"`
Bucket string `yaml:"Bucket" cmdenv:"GCSExportBucket"`
KeyPrefix string `yaml:"KeyPrefix"`
FlushInterval Duration `yaml:"FlushInterval" default:"60s"`
MaxBatchSize MemorySize `yaml:"MaxBatchSize" default:"100MB"`
QueueSize int `yaml:"QueueSize" default:"100000"`
}

type OTelTracingConfig struct {
Enabled bool `yaml:"Enabled" default:"false"`
APIHost string `yaml:"APIHost" default:"https://api.honeycomb.io" cmdenv:"TelemetryEndpoint"`
Expand Down Expand Up @@ -1027,6 +1039,13 @@ func (f *fileConfig) GetOTelMetricsConfig() OTelMetricsConfig {
return f.mainConfig.OTelMetrics
}

func (f *fileConfig) GetGCSExportConfig() GCSExportConfig {
f.mux.RLock()
defer f.mux.RUnlock()

return f.mainConfig.GCSExport
}

func (f *fileConfig) GetDebugServiceAddr() string {
f.mux.RLock()
defer f.mux.RUnlock()
Expand Down
88 changes: 88 additions & 0 deletions config/metadata/configMeta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2123,3 +2123,91 @@ groups:
If this duration is `0`, then Refinery will not start in stressed
mode, which will provide faster startup at the possible cost of
startup instability.

- name: GCSExport
title: "GCS Export"
description: >
contains configuration for optionally exporting sampled (kept) trace
spans to a Google Cloud Storage bucket in addition to sending them to
Honeycomb. Spans are buffered in memory and written as gzipped JSON
Lines objects, batched into time-partitioned paths of the form
`KeyPrefix/YYYY/MM/DD/HH/hostname-timestamp.jsonl.gz`. Authentication
uses Google Application Default Credentials.
fields:
- name: Enabled
type: bool
valuetype: nondefault
firstversion: v3.3
default: false
reload: false
summary: controls whether kept trace spans are also exported to a GCS bucket.
description: >
If `true`, every span belonging to a trace that Refinery decides to
keep is also written to the configured GCS bucket. Spans of dropped
traces are never exported.

- name: Bucket
type: string
valuetype: nondefault
firstversion: v3.3
default: ""
example: "my-trace-archive"
envvar: REFINERY_GCS_EXPORT_BUCKET
commandline: gcs-export-bucket
reload: false
summary: is the name of the GCS bucket to which sampled traces are exported.
description: >
The bucket must already exist and the credentials available via
Application Default Credentials must have permission to create
objects in it. Required when `Enabled` is `true`.

- name: KeyPrefix
type: string
valuetype: nondefault
firstversion: v3.3
default: ""
example: "refinery/traces"
reload: false
summary: is the object name prefix under which exported objects are written.
description: >
Object names are formed by appending time-partitioned path elements
to this prefix. Leave empty to write at the root of the bucket.

- name: FlushInterval
type: duration
valuetype: nondefault
firstversion: v3.3
default: 60s
reload: false
summary: is the maximum time a batch of spans is buffered before being written to GCS.
description: >
A batch is flushed to GCS when it reaches `MaxBatchSize` or when
this interval has elapsed since the batch was started, whichever
comes first.

- name: MaxBatchSize
type: memorysize
valuetype: memorysize
firstversion: v3.3
default: 100MB
reload: false
summary: is the maximum uncompressed size of a batch before it is written to GCS.
description: >
When the serialized (uncompressed) size of the current batch exceeds
this value, it is compressed and written to GCS immediately. The
objects stored in GCS are gzip-compressed and thus considerably
smaller than this value.

- name: QueueSize
type: int
valuetype: nondefault
firstversion: v3.3
default: 100000
reload: false
summary: is the number of spans that can be queued for export before spans are dropped.
description: >
Spans are handed to the exporter through a fixed-size queue so that
a slow or unavailable GCS never blocks sending traces to Honeycomb.
If the queue is full, new spans are dropped from the export (they
are still sent to Honeycomb) and the `gcs_export_dropped` metric is
incremented.
8 changes: 8 additions & 0 deletions config/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type MockConfig struct {
GetOpAmpConfigVal OpAMPConfig
GetOTelMetricsConfigVal OTelMetricsConfig
GetOTelTracingConfigVal OTelTracingConfig
GetGCSExportConfigVal GCSExportConfig
IdentifierInterfaceName string
UseIPV6Identifier bool
RedisIdentifier string
Expand Down Expand Up @@ -251,6 +252,13 @@ func (m *MockConfig) GetOTelTracingConfig() OTelTracingConfig {
return m.GetOTelTracingConfigVal
}

func (m *MockConfig) GetGCSExportConfig() GCSExportConfig {
m.Mux.RLock()
defer m.Mux.RUnlock()

return m.GetGCSExportConfigVal
}

// GetSamplerConfigForDestName returns the sampler config for the given dataset/environment.
// If Samplers map is populated, it will look up the dataset-specific config.
// Falls back to GetSamplerTypeVal if Samplers is not set (backwards compatible).
Expand Down
Loading