Skip to content

Commit

Permalink
cleaner log handling
Browse files Browse the repository at this point in the history
  • Loading branch information
reflog authored and myleshorton committed Jan 30, 2025
1 parent 6fb9858 commit 335942d
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 32 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Library using a series of techniques to send and receive small amounts of data t

```go
k := kindling.NewKindling(
kindling.WithLogWriter(os.Stdout),
kindling.WithDomainFronting("https://url-with-gzipped-domain-fronting-config"),
kindling.WithProxyless("example.com"),
//kindling.WithDoHTunnel(),
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ require (
github.com/Jigsaw-Code/outline-sdk v0.0.18-0.20241106233708-faffebb12629
github.com/Jigsaw-Code/outline-sdk/x v0.0.0-20250113162209-efa808309e1e
github.com/getlantern/fronted v0.0.0-20250129184840-abcec12f754e
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65
go.opentelemetry.io/otel v1.19.0
)

Expand Down Expand Up @@ -35,6 +34,7 @@ require (
github.com/getlantern/elevate v0.0.0-20200430163644-2881a121236d // indirect
github.com/getlantern/errors v1.0.3 // indirect
github.com/getlantern/filepersist v0.0.0-20160317154340-c5f0cd24e799 // indirect
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc // indirect
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect
github.com/getlantern/iptool v0.0.0-20230112135223-c00e863b2696 // indirect
Expand Down
59 changes: 36 additions & 23 deletions kindling.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"embed"
"fmt"
"io"
"log/slog"
"net"
"net/http"
Expand All @@ -14,15 +15,13 @@ import (
"github.com/Jigsaw-Code/outline-sdk/transport"
"github.com/Jigsaw-Code/outline-sdk/x/smart"
"github.com/getlantern/fronted"
"github.com/getlantern/golog"
)

var log = golog.LoggerFor("kindling")
var log *slog.Logger

// Kindling is the interface that wraps the basic Dial and DialContext methods for control
// plane traffic.
type Kindling interface {

// NewHTTPClient returns a new HTTP client that is configured to use kindling.
NewHTTPClient() *http.Client
}
Expand All @@ -31,6 +30,7 @@ type httpDialer func(ctx context.Context, addr string) (http.RoundTripper, error

type kindling struct {
httpDialers []httpDialer
logWriter io.Writer
}

// Make sure that kindling implements the Kindling interface.
Expand All @@ -42,11 +42,14 @@ type Option func(*kindling)
// NewKindling returns a new Kindling.
func NewKindling(options ...Option) Kindling {
k := &kindling{}
k.logWriter = os.Stdout

// Apply all the functional options to configure the client.
for _, opt := range options {
opt(k)
}

log = slog.New(slog.NewTextHandler(k.logWriter, &slog.HandlerOptions{}))
return k
}

Expand All @@ -63,9 +66,9 @@ func (k *kindling) NewHTTPClient() *http.Client {
// WithDomainFronting is a functional option that enables domain fronting for the Kindling.
func WithDomainFronting(configURL, countryCode string) Option {
return func(k *kindling) {
frontedDialer, err := newFrontedDialer(configURL, countryCode)
frontedDialer, err := k.newFrontedDialer(configURL, countryCode)
if err != nil {
slog.Error("Failed to create fronted dialer", "error", err)
log.Error("Failed to create fronted dialer", "error", err)
return
}
k.httpDialers = append(k.httpDialers, frontedDialer)
Expand All @@ -79,13 +82,22 @@ func WithDoHTunnel() Option {
}
}

// WithLogWriter is a functional option that sets the log writer for the Kindling.
// By default, the log writer is set to os.Stdout.
// This should be the first option to be applied to the Kindling to ensure that all logs are captured.
func WithLogWriter(w io.Writer) Option {
return func(k *kindling) {
k.logWriter = w
}
}

// WithProxyless is a functional option that enables proxyless mode for the Kindling such that
// it accesses the control plane directly using a variety of proxyless techniques.
func WithProxyless(domains ...string) Option {
return func(k *kindling) {
smartDialer, err := newSmartHTTPDialer(domains...)
smartDialer, err := k.newSmartHTTPDialer(domains...)
if err != nil {
slog.Error("Failed to create smart dialer", "error", err)
log.Error("Failed to create smart dialer", "error", err)
return
}
k.httpDialers = append(k.httpDialers, smartDialer)
Expand All @@ -97,21 +109,21 @@ func (k *kindling) newRaceTransport() http.RoundTripper {
return newRaceTransport(k.httpDialers...)
}

func newFrontedDialer(configURL, countryCode string) (httpDialer, error) {
func (k *kindling) newFrontedDialer(configURL, countryCode string) (httpDialer, error) {
// Parse the domain from the URL.
u, err := url.Parse(configURL)
if err != nil {
slog.Error("Failed to parse URL", "error", err)
log.Error("Failed to parse URL", "error", err)
return nil, fmt.Errorf("failed to parse URL: %v", err)
}
// Extract the domain from the URL.
domain := u.Host

// First, download the file from the specified URL using the smart dialer.
// Then, create a new fronted instance with the downloaded file.
trans, err := newSmartHTTPTransport(domain)
trans, err := k.newSmartHTTPTransport(domain)
if err != nil {
slog.Error("Failed to create smart HTTP transport", "error", err)
log.Error("Failed to create smart HTTP transport", "error", err)
return nil, fmt.Errorf("failed to create smart HTTP transport: %v", err)
}
httpClient := &http.Client{
Expand All @@ -125,8 +137,8 @@ func newFrontedDialer(configURL, countryCode string) (httpDialer, error) {
return fr.NewConnectedRoundTripper, nil
}

func newSmartHTTPDialer(domains ...string) (httpDialer, error) {
d, err := newSmartDialer(domains...)
func (k *kindling) newSmartHTTPDialer(domains ...string) (httpDialer, error) {
d, err := k.newSmartDialer(domains...)
if err != nil {
return nil, fmt.Errorf("failed to create smart dialer: %v", err)
}
Expand All @@ -135,27 +147,28 @@ func newSmartHTTPDialer(domains ...string) (httpDialer, error) {
if err != nil {
return nil, fmt.Errorf("failed to dial stream: %v", err)
}
return newTransportWithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) {
return k.newTransportWithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) {
return streamConn, nil
}), nil
}, nil
}

func newSmartHTTPTransport(domains ...string) (*http.Transport, error) {
d, err := newSmartDialer(domains...)
func (k *kindling) newSmartHTTPTransport(domains ...string) (*http.Transport, error) {
d, err := k.newSmartDialer(domains...)
if err != nil {
slog.Error("Failed to create smart dialer", "error", err)
log.Error("Failed to create smart dialer", "error", err)
return nil, fmt.Errorf("failed to create smart dialer: %v", err)
}
return newTransportWithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) {
return k.newTransportWithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) {
streamConn, err := d.DialStream(ctx, addr)
if err != nil {
return nil, fmt.Errorf("failed to dial stream: %v", err)
}
return streamConn, nil
}), nil
}
func newTransportWithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) *http.Transport {

func (k *kindling) newTransportWithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) *http.Transport {
return &http.Transport{
DialContext: dialContext,
ForceAttemptHTTP2: true,
Expand All @@ -169,22 +182,22 @@ func newTransportWithDialContext(dialContext func(ctx context.Context, network,
//go:embed smart_dialer_config.yml
var embedFS embed.FS

func newSmartDialer(domains ...string) (transport.StreamDialer, error) {
func (k *kindling) newSmartDialer(domains ...string) (transport.StreamDialer, error) {
finder := &smart.StrategyFinder{
TestTimeout: 5 * time.Second,
LogWriter: os.Stdout,
LogWriter: k.logWriter,
StreamDialer: &transport.TCPDialer{},
PacketDialer: &transport.UDPDialer{},
}

configBytes, err := embedFS.ReadFile("smart_dialer_config.yml")
if err != nil {
slog.Error("Failed to read smart dialer config", "error", err)
log.Error("Failed to read smart dialer config", "error", err)
return nil, err
}
dialer, err := finder.NewDialer(context.Background(), domains, configBytes)
if err != nil {
slog.Error("Failed to create smart dialer", "error", err)
log.Error("Failed to create smart dialer", "error", err)
return nil, err
}
return dialer, nil
Expand Down
16 changes: 8 additions & 8 deletions race_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (t *raceTransport) RoundTrip(req *http.Request) (*http.Response, error) {

span.SetAttributes(attribute.String("http.url", req.URL.String()))

log.Debugf("Starting RoundTrip race %v", req.URL.Host)
log.Debug("Starting RoundTrip race", "host", req.URL.Host)
// Try all methods in parallel and return the first successful response.
// If all fail, return the last error.
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
Expand All @@ -42,7 +42,7 @@ func (t *raceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
var httpErrors = new(atomic.Int64)
var roundTrippherCh = make(chan http.RoundTripper)
var errCh = make(chan error)
log.Debugf("Dialing with %v dialers", len(t.httpDialers))
log.Debug(fmt.Sprintf("Dialing with %v dialers", len(t.httpDialers)))
for _, d := range t.httpDialers {
go func(d httpDialer) {
t.connectedRoundTripper(ctx, d, req, errCh, roundTrippherCh, cancel, httpErrors)
Expand All @@ -56,7 +56,7 @@ func (t *raceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
for i := 0; i < retryTimes; i++ {
select {
case roundTripper := <-roundTrippherCh:
log.Debugf("Got connected roundTripper for %v", req.URL.Host)
log.Debug("Got connected roundTripper", "host", req.URL.Host)
// Since we're already connected, set a lower timeout on the request context.
singleRTCtx, cancelRoundTrip := context.WithTimeout(reqRespCtx, 10*time.Second)
req = req.Clone(singleRTCtx)
Expand All @@ -65,11 +65,11 @@ func (t *raceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := roundTripper.RoundTrip(req)
// If the request fails, close the connection and return the error.
if err != nil {
log.Errorf("HTTP request failed %v", err)
log.Error("HTTP request failed", "err", err)
cancelRoundTrip()
continue
}
log.Debugf("Got response '%v' for %v", resp.Status, req.URL.Host)
log.Debug("Got response", "status", resp.Status, "host", req.URL.Host)
cancelRoundTrip()
return resp, nil
case err := <-errCh:
Expand Down Expand Up @@ -100,15 +100,15 @@ func (t *raceTransport) connectedRoundTripper(parentCtx context.Context, d httpD
addr = net.JoinHostPort(addr, "80")
}
}
log.Debugf("Dialing %v", addr)
log.Debug("Dialing", "addr", addr)
connectedRoundTripper, err := d(dialCtx, addr)
if err != nil {
log.Errorf("Error dialing to %v:\n%v", addr, err)
log.Error("Error dialing", "addr", addr, "err", err)
if httpErrors.Add(1) == int64(len(t.httpDialers)) {
errCh <- fmt.Errorf("failed to connect to any dialer with last error: %v", err)
}
} else {
log.Debugf("Dialing done %v", addr)
log.Debug("Dialing done", "err", addr)
roundTrippherCh <- connectedRoundTripper
}
}

0 comments on commit 335942d

Please sign in to comment.