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
4 changes: 2 additions & 2 deletions app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ func newStartedApp(
if mockTransmission != nil {
upstreamTransmission = mockTransmission
} else {
upstreamTransmission = transmit.NewDirectTransmission(types.TransmitTypeUpstream, http.DefaultTransport.(*http.Transport), 500, 100*time.Millisecond, 100*time.Millisecond, true)
upstreamTransmission = transmit.NewDirectTransmission(types.TransmitTypeUpstream, http.DefaultTransport.(*http.Transport), 500, 100*time.Millisecond, 100*time.Millisecond, true, nil)
}

// Always create real peer transmission using DirectTransmission
Expand All @@ -325,7 +325,7 @@ func newStartedApp(
Timeout: 3 * time.Second,
}).Dial,
}
peerTransmissionWrapper := transmit.NewDirectTransmission(types.TransmitTypePeer, peerTransport, int(cfg.GetTracesConfigVal.MaxBatchSize), 100*time.Millisecond, 100*time.Millisecond, false)
peerTransmissionWrapper := transmit.NewDirectTransmission(types.TransmitTypePeer, peerTransport, int(cfg.GetTracesConfigVal.MaxBatchSize), 100*time.Millisecond, 100*time.Millisecond, false, nil)

var g inject.Graph
err = g.Provide(
Expand Down
4 changes: 3 additions & 1 deletion cmd/refinery/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ func main() {
time.Duration(c.GetTracesConfig().GetBatchTimeout()),
30*time.Second,
true,
c.GetAdditionalHeaders(), // Custom headers for upstream Honeycomb API
)
peerTransmission := transmit.NewDirectTransmission(
types.TransmitTypePeer,
Expand All @@ -184,6 +185,7 @@ func main() {
time.Duration(c.GetTracesConfig().GetBatchTimeout()),
10*time.Second,
c.GetCompressPeerCommunication(),
nil, // No custom headers for peer-to-peer traffic
)

// we need to include all the metrics types so we can inject them in case they're needed
Expand All @@ -206,7 +208,7 @@ func main() {

if c.GetOTelTracingConfig().Enabled {
// let's set up some OTel tracing
tracer, shutdown = otelutil.SetupTracing(c.GetOTelTracingConfig(), resourceLib, resourceVer)
tracer, shutdown = otelutil.SetupTracing(c.GetOTelTracingConfig(), resourceLib, resourceVer, c.GetAdditionalHeaders())

// add telemetry callback so husky can enrich spans with attributes
husky.AddTelemetryAttributeFunc = func(ctx context.Context, key string, value any) {
Expand Down
15 changes: 14 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 2025-10-02 at 19:03:23 UTC.
It was automatically generated on 2026-01-09 at 22:22:32 UTC.

## The Config file

Expand Down Expand Up @@ -151,6 +151,19 @@ This setting is the destination to which Refinery sends all events that it decid
- Environment variable: `REFINERY_HONEYCOMB_API`
- Command line switch: `--honeycomb-api`

### `AdditionalHeaders`

AdditionalHeaders is a map of additional HTTP headers to add to all upstream Honeycomb API requests.

These headers will be added to all HTTP requests made to the upstream Honeycomb API endpoint, including trace data, OTel metrics, OTel traces, and logs.
This is useful for scenarios where requests need to pass through an mTLS proxy that requires additional headers like FORWARD_TO_URL.
Both keys and values must be strings.
Reserved Honeycomb headers cannot be overridden.

- Eligible for live reload.
- Type: `map`
- Example: `FORWARD_TO_URL:https://api.honeycomb.io`

## OpAMP Configuration

`OpAMP` contains OpAMP configuration options.
Expand Down
4 changes: 4 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ type Config interface {
// the upstream Honeycomb API server
GetHoneycombAPI() string

// GetAdditionalHeaders returns custom HTTP headers to add to all upstream
// Honeycomb API calls, both processed and generated telemetry
GetAdditionalHeaders() map[string]string

GetTracesConfig() TracesConfig

// GetLoggerType returns the type of the logger to use. Valid types are in
Expand Down
63 changes: 63 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1400,3 +1400,66 @@ func BenchmarkIsLegacyAPIKey(b *testing.B) {
})
}
}

func TestAdditionalHeaders(t *testing.T) {
cm := makeYAML(
"General.ConfigurationVersion", 2,
"Network.AdditionalHeaders", map[string]string{
"FORWARD_TO_URL": "https://proxy.example.com",
"X-Custom-Header": "custom-value",
},
)
rm := makeYAML("ConfigVersion", 2)
config, rules := createTempConfigs(t, cm, rm)
c, err := getConfig([]string{"--no-validate", "--config", config, "--rules_config", rules})
assert.NoError(t, err)

expected := map[string]string{
"FORWARD_TO_URL": "https://proxy.example.com",
"X-Custom-Header": "custom-value",
}
assert.Equal(t, expected, c.GetAdditionalHeaders())
}

func TestAdditionalHeadersEmpty(t *testing.T) {
cm := makeYAML("General.ConfigurationVersion", 2)
rm := makeYAML("ConfigVersion", 2)
config, rules := createTempConfigs(t, cm, rm)
c, err := getConfig([]string{"--no-validate", "--config", config, "--rules_config", rules})
assert.NoError(t, err)

// Should return empty map (or nil) when not configured
headers := c.GetAdditionalHeaders()
assert.Empty(t, headers)
}

func TestAdditionalHeadersReservedHeadersRejected(t *testing.T) {
testCases := []struct {
name string
header string
}{
{"x-honeycomb-team", "X-Honeycomb-Team"},
{"x-hny-team", "X-Hny-Team"},
{"x-honeycomb-dataset", "X-Honeycomb-Dataset"},
{"x-honeycomb-samplerate", "X-Honeycomb-Samplerate"},
{"x-honeycomb-event-time", "X-Honeycomb-Event-Time"},
{"lowercase honeycomb team", "x-honeycomb-team"},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cm := makeYAML(
"General.ConfigurationVersion", 2,
"Network.AdditionalHeaders", map[string]string{
tc.header: "should-fail",
},
)
// Use --no-validate to skip YAML schema validation and test our header validation
rm := makeYAML("RulesVersion", 2)
config, rules := createTempConfigs(t, cm, rm)
_, err := getConfig([]string{"--no-validate", "--config", config, "--rules_config", rules})
assert.Error(t, err)
assert.Contains(t, err.Error(), "reserved")
})
}
}
42 changes: 38 additions & 4 deletions config/file_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,32 @@ type OpAMPConfig struct {
}

type NetworkConfig struct {
ListenAddr string `yaml:"ListenAddr" default:"0.0.0.0:8080" cmdenv:"HTTPListenAddr"`
PeerListenAddr string `yaml:"PeerListenAddr" default:"0.0.0.0:8081" cmdenv:"PeerListenAddr"`
HoneycombAPI string `yaml:"HoneycombAPI" default:"https://api.honeycomb.io" cmdenv:"HoneycombAPI"`
HTTPIdleTimeout Duration `yaml:"HTTPIdleTimeout"`
ListenAddr string `yaml:"ListenAddr" default:"0.0.0.0:8080" cmdenv:"HTTPListenAddr"`
PeerListenAddr string `yaml:"PeerListenAddr" default:"0.0.0.0:8081" cmdenv:"PeerListenAddr"`
HoneycombAPI string `yaml:"HoneycombAPI" default:"https://api.honeycomb.io" cmdenv:"HoneycombAPI"`
HTTPIdleTimeout Duration `yaml:"HTTPIdleTimeout"`
AdditionalHeaders map[string]string `yaml:"AdditionalHeaders" default:"{}"`
}

// reservedHeaders contains HTTP headers that cannot be overridden via AdditionalHeaders
// because they are set by Refinery for proper Honeycomb API communication.
var reservedHeaders = map[string]bool{
"x-honeycomb-team": true,
"x-hny-team": true,
"x-honeycomb-dataset": true,
"x-honeycomb-samplerate": true,
"x-honeycomb-event-time": true,
}

// validateAdditionalHeaders checks that no reserved Honeycomb headers are specified
// in the AdditionalHeaders configuration.
func validateAdditionalHeaders(headers map[string]string) error {
for key := range headers {
if reservedHeaders[strings.ToLower(key)] {
return fmt.Errorf("cannot override reserved Honeycomb header %q in Network.AdditionalHeaders", key)
}
}
return nil
}

type AccessKeyConfig struct {
Expand Down Expand Up @@ -550,6 +572,11 @@ func newFileConfig(opts *CmdEnv, cData, rulesData []configData, currentVersion .
return nil, err
}

// Validate AdditionalHeaders doesn't contain reserved Honeycomb headers
if err := validateAdditionalHeaders(mainconf.Network.AdditionalHeaders); err != nil {
return nil, err
}

cfg := &fileConfig{
mainConfig: mainconf,
mainHash: mainhash,
Expand Down Expand Up @@ -862,6 +889,13 @@ func (f *fileConfig) GetHoneycombAPI() string {
return f.mainConfig.Network.HoneycombAPI
}

func (f *fileConfig) GetAdditionalHeaders() map[string]string {
f.mux.RLock()
defer f.mux.RUnlock()

return f.mainConfig.Network.AdditionalHeaders
}

func (f *fileConfig) GetLoggerLevel() Level {
f.mux.RLock()
defer f.mux.RUnlock()
Expand Down
18 changes: 18 additions & 0 deletions config/metadata/configMeta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,24 @@ groups:
description: >
This setting is the destination to which Refinery sends all events
that it decides to keep.
- name: AdditionalHeaders
type: map
valuetype: map
example: "FORWARD_TO_URL:https://api.honeycomb.io"
reload: true
validations:
- type: elementType
arg: string
summary: is a map of additional HTTP headers to add to all upstream Honeycomb API requests.
description: >
These headers will be added to all HTTP requests made to the upstream
Honeycomb API endpoint, including trace data, OTel metrics, OTel
traces, and logs. This is useful for scenarios where requests need
to pass through an mTLS proxy that requires additional headers like
FORWARD_TO_URL. Both keys and values must be strings. Reserved
Honeycomb headers cannot be overridden.
- name: OpAMP
title: "OpAMP Configuration"
description: >
Expand Down
11 changes: 11 additions & 0 deletions config/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type MockConfig struct {
SampleCache SampleCacheConfig
StressRelief StressReliefConfig
AdditionalAttributes map[string]string
AdditionalHeaders map[string]string
TraceIdFieldNames []string
ParentIdFieldNames []string
CfgMetadata []ConfigMetadata
Expand Down Expand Up @@ -462,6 +463,16 @@ func (f *MockConfig) GetAdditionalAttributes() map[string]string {
return f.AdditionalAttributes
}

func (f *MockConfig) GetAdditionalHeaders() map[string]string {
f.Mux.RLock()
defer f.Mux.RUnlock()

if f.AdditionalHeaders == nil {
return make(map[string]string)
}
return f.AdditionalHeaders
}

func (f *MockConfig) GetOpAMPConfig() OpAMPConfig {
f.Mux.RLock()
defer f.Mux.RUnlock()
Expand Down
16 changes: 15 additions & 1 deletion config_complete.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Honeycomb Refinery Configuration ##
######################################
#
# created on 2025-10-02 at 19:03:22 UTC from ../../config.yaml using a template generated on 2025-10-02 at 19:03:19 UTC
# created on 2026-01-09 at 22:22:32 UTC from ../../config.yaml using a template generated on 2026-01-09 at 22:22:30 UTC

# This file contains a configuration for the Honeycomb Refinery. It is in YAML
# format, organized into named groups, each of which contains a set of
Expand Down Expand Up @@ -127,6 +127,20 @@ Network:
## Eligible for live reload.
# HoneycombAPI: "https://api.honeycomb.io"

## AdditionalHeaders is a map of additional HTTP headers to add to all
## upstream Honeycomb API requests.
##
## These headers will be added to all HTTP requests made to the upstream
## Honeycomb API endpoint, including trace data, OTel metrics, OTel
## traces, and logs. This is useful for scenarios where requests need to
## pass through an mTLS proxy that requires additional headers like
## FORWARD_TO_URL. Both keys and values must be strings. Reserved
## Honeycomb headers cannot be overridden.
##
## Eligible for live reload.
# AdditionalHeaders:
# FORWARD_TO_URL: https://api.honeycomb.io

#########################
## OpAMP Configuration ##
#########################
Expand Down
12 changes: 7 additions & 5 deletions internal/otelutil/otel_tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func StartSpanMulti(ctx context.Context, tracer trace.Tracer, name string, field
return tracer.Start(ctx, name, trace.WithAttributes(Attributes(fields)...))
}

func SetupTracing(cfg config.OTelTracingConfig, resourceLibrary string, resourceVersion string) (tracer trace.Tracer, shutdown func()) {
func SetupTracing(cfg config.OTelTracingConfig, resourceLibrary string, resourceVersion string, additionalHeaders map[string]string) (tracer trace.Tracer, shutdown func()) {
if !cfg.Enabled {
pr := noop.NewTracerProvider()
return pr.Tracer(resourceLibrary, trace.WithInstrumentationVersion(resourceVersion)), func() {}
Expand All @@ -122,12 +122,14 @@ func SetupTracing(cfg config.OTelTracingConfig, resourceLibrary string, resource

var sampleRatio float64 = 1.0 / float64(sampleRate)

// set up honeycomb specific headers if an API key is provided
// Add custom headers first, then Honeycomb headers (which take precedence)
headers := make(map[string]string)
for k, v := range additionalHeaders {
headers[k] = v
}
// Honeycomb headers override any custom headers
if cfg.APIKey != "" {
headers = map[string]string{
types.APIKeyHeader: cfg.APIKey,
}
headers[types.APIKeyHeader] = cfg.APIKey

if config.IsLegacyAPIKey(cfg.APIKey) {
headers[types.DatasetHeader] = cfg.Dataset
Expand Down
25 changes: 24 additions & 1 deletion logger/honeycomb.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ import (
"github.com/honeycombio/refinery/config"
)

// headerInjectingTransport wraps an http.RoundTripper and adds custom headers
// to all outgoing requests.
type headerInjectingTransport struct {
base http.RoundTripper
headers map[string]string
}

func (t *headerInjectingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// Add custom headers before making the request
for k, v := range t.headers {
req.Header.Set(k, v)
}
return t.base.RoundTrip(req)
}

// HoneycombLogger is a Logger implementation that sends all logs to a Honeycomb
// dataset.
type HoneycombLogger struct {
Expand Down Expand Up @@ -45,12 +60,20 @@ func (h *HoneycombLogger) Start() error {
if h.loggerConfig.APIKey == "" {
loggerTx = &transmission.DiscardSender{}
} else {
// Wrap transport with header injector if there are additional headers
var transport http.RoundTripper = h.UpstreamTransport
if additionalHeaders := h.Config.GetAdditionalHeaders(); len(additionalHeaders) > 0 {
transport = &headerInjectingTransport{
base: h.UpstreamTransport,
headers: additionalHeaders,
}
}
loggerTx = &transmission.Honeycomb{
// logs are often sent in flurries; flush every half second
MaxBatchSize: 100,
BatchTimeout: 500 * time.Millisecond,
UserAgentAddition: "refinery/" + h.Version + " (metrics)",
Transport: h.UpstreamTransport,
Transport: transport,
PendingWorkCapacity: libhoney.DefaultPendingWorkCapacity,
EnableMsgpackEncoding: true,
}
Expand Down
2 changes: 1 addition & 1 deletion metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Honeycomb Refinery Metrics Documentation

This document contains the description of various metrics used in Refinery.
It was automatically generated on 2025-10-01 at 18:54:38 UTC.
It was automatically generated on 2026-01-09 at 22:22:32 UTC.

Note: This document does not include metrics defined in the dynsampler-go dependency, as those metrics are generated dynamically at runtime. As a result, certain metrics may be missing or incomplete in this document, but they will still be available during execution with their full names.

Expand Down
8 changes: 6 additions & 2 deletions metrics/otel_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,12 @@ func (o *OTelMetrics) Start() error {
}
}),
}
// if we ever need to add user-specified headers, that would go here
hdrs := map[string]string{}
// Add custom headers from config first, then Honeycomb headers (which take precedence)
hdrs := make(map[string]string)
for k, v := range o.Config.GetAdditionalHeaders() {
hdrs[k] = v
}
// Honeycomb headers override any custom headers
if cfg.APIKey != "" {
hdrs["x-honeycomb-team"] = cfg.APIKey
}
Expand Down
Loading