diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index 158d4288..af07f8e3 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -11,6 +11,7 @@ jobs: GO111MODULE: on CGO_ENABLED: "0" GOFLAGS: "-mod=vendor" + ANYCABLE_TELEMETRY_TOKEN: ${{ secrets.ANYCABLE_TELEMETRY_TOKEN }} steps: - uses: actions/checkout@v2 - name: Install system deps @@ -41,17 +42,17 @@ jobs: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} - name: Build binaries run: | - env GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-win-amd64.exe cmd/anycable-go/main.go - env GOOS=freebsd GOARCH=arm go build -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-freebsd-arm cmd/anycable-go/main.go - env GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-freebsd-amd64 cmd/anycable-go/main.go - env GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-linux-arm64 cmd/anycable-go/main.go - env GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-linux-amd64 cmd/anycable-go/main.go + env GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-win-amd64.exe cmd/anycable-go/main.go + env GOOS=freebsd GOARCH=arm go build -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-freebsd-arm cmd/anycable-go/main.go + env GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-freebsd-amd64 cmd/anycable-go/main.go + env GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-linux-arm64 cmd/anycable-go/main.go + env GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-linux-amd64 cmd/anycable-go/main.go dist/anycable-go-linux-amd64 -v - name: Build binary with MRuby env: CGO_ENABLED: "1" run: | - env GOOS=linux GOARCH=amd64 go build -tags mrb -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-mrb-linux-amd64 cmd/anycable-go/main.go + env GOOS=linux GOARCH=amd64 go build -tags mrb -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-mrb-linux-amd64 cmd/anycable-go/main.go dist/anycable-go-mrb-linux-amd64 -v - uses: xresloader/upload-to-github-release@v1 env: @@ -76,45 +77,46 @@ jobs: GO111MODULE: on CGO_ENABLED: "0" GOFLAGS: "-mod=vendor" + ANYCABLE_TELEMETRY_TOKEN: ${{ secrets.ANYCABLE_TELEMETRY_TOKEN }} steps: - - uses: actions/checkout@v2 - - name: Install system deps - run: | - brew install bison - - uses: actions/cache@v1 - with: - path: vendor - key: vendor-${{ hashFiles('**/go.sum') }} - restore-keys: | - vendor- - - uses: actions/setup-go@v4 - with: - go-version-file: go.mod - - run: go mod vendor - - uses: ruby/setup-ruby@v1 - with: - # Use <3.0 since go-mruby's Rakefile has some problems with keyword arguments compatibility - ruby-version: 2.7 - bundler-cache: true - - name: Build mruby - run: bash -c '(cd vendor/github.com/mitchellh/go-mruby && MRUBY_CONFIG=../../../../../../etc/build_config.rb make libmruby.a)' - - name: Set VERSION (if any) - if: ${{ contains(github.ref, 'refs/tags/v') }} - id: version - run: | - echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} - - name: Build binaries for MacOS - run: | - env GOOS=darwin GOARCH=${{ matrix.suffix }} go build -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-darwin-${{ matrix.suffix }} cmd/anycable-go/main.go - - name: Build binaries with MRuby for MacOS - env: - CGO_ENABLED: "1" - run: | - env GOOS=darwin GOARCH=${{ matrix.suffix }} go build -tags mrb -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-mrb-darwin-${{ matrix.suffix }} cmd/anycable-go/main.go - dist/anycable-go-mrb-darwin-${{ matrix.suffix }} -v - - uses: xresloader/upload-to-github-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - file: "dist/anycable-go-*" - tags: true + - uses: actions/checkout@v2 + - name: Install system deps + run: | + brew install bison + - uses: actions/cache@v1 + with: + path: vendor + key: vendor-${{ hashFiles('**/go.sum') }} + restore-keys: | + vendor- + - uses: actions/setup-go@v4 + with: + go-version-file: go.mod + - run: go mod vendor + - uses: ruby/setup-ruby@v1 + with: + # Use <3.0 since go-mruby's Rakefile has some problems with keyword arguments compatibility + ruby-version: 2.7 + bundler-cache: true + - name: Build mruby + run: bash -c '(cd vendor/github.com/mitchellh/go-mruby && MRUBY_CONFIG=../../../../../../etc/build_config.rb make libmruby.a)' + - name: Set VERSION (if any) + if: ${{ contains(github.ref, 'refs/tags/v') }} + id: version + run: | + echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} + - name: Build binaries for MacOS + run: | + env GOOS=darwin GOARCH=${{ matrix.suffix }} go build -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-darwin-${{ matrix.suffix }} cmd/anycable-go/main.go + - name: Build binaries with MRuby for MacOS + env: + CGO_ENABLED: "1" + run: | + env GOOS=darwin GOARCH=${{ matrix.suffix }} go build -tags mrb -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o dist/anycable-go-mrb-darwin-${{ matrix.suffix }} cmd/anycable-go/main.go + dist/anycable-go-mrb-darwin-${{ matrix.suffix }} -v + - uses: xresloader/upload-to-github-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + file: "dist/anycable-go-*" + tags: true diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml index 44bd18b5..304a5f43 100644 --- a/.github/workflows/release-docker.yml +++ b/.github/workflows/release-docker.yml @@ -11,6 +11,7 @@ jobs: GO111MODULE: on CGO_ENABLED: "0" GOFLAGS: "-mod=vendor" + ANYCABLE_TELEMETRY_TOKEN: ${{ secrets.ANYCABLE_TELEMETRY_TOKEN }} steps: - uses: actions/checkout@v4 - name: Install system deps @@ -39,8 +40,8 @@ jobs: cp /etc/ssl/certs/ca-certificates.crt ./.docker/ca-certificates.crt - name: Build binary run: | - env GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o .docker/linux/arm64/anycable-go cmd/anycable-go/main.go - env GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o .docker/linux/amd64/anycable-go cmd/anycable-go/main.go + env GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o .docker/linux/arm64/anycable-go cmd/anycable-go/main.go + env GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o .docker/linux/amd64/anycable-go cmd/anycable-go/main.go .docker/linux/amd64/anycable-go -v - uses: docker/setup-buildx-action@v2 - uses: docker/login-action@v2 @@ -113,7 +114,7 @@ jobs: env: CGO_ENABLED: "1" run: | - env GOOS=linux GOARCH=amd64 go build -tags mrb -ldflags "-s -w -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o .docker/linux/amd64/anycable-go cmd/anycable-go/main.go + env GOOS=linux GOARCH=amd64 go build -tags mrb -ldflags "-s -w -X github.com/anycable/anycable-go/telemetry.auth=$(echo "$ANYCABLE_TELEMETRY_TOKEN") -X github.com/anycable/anycable-go/version.version=${{ steps.version.outputs.VERSION }} -X github.com/anycable/anycable-go/version.sha=$(echo "$GITHUB_SHA" | cut -c -7)" -a -o .docker/linux/amd64/anycable-go cmd/anycable-go/main.go .docker/linux/amd64/anycable-go -v - name: Prepare Docker metadata and versions for mrb image id: meta-mrb diff --git a/Makefile b/Makefile index 27d84c34..c51ca37d 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,14 @@ ifndef ANYCABLE_DISABLE_TELEMETRY export ANYCABLE_DISABLE_TELEMETRY=true endif +ifndef ANYCABLE_TELEMETRY_URL + export ANYCABLE_TELEMETRY_URL=http://localhost:4343 +endif + +ifndef ANYCABLE_TELEMETRY_DEBUG + export ANYCABLE_TELEMETRY_DEBUG=1 +endif + BUILD_ARGS ?= TEST_FLAGS= TEST_BUILD_FLAGS= diff --git a/cli/cli.go b/cli/cli.go index 88a269f7..f9eed799 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -244,6 +244,9 @@ func (r *Runner) runNode() (*node.Node, error) { if r.telemetryEnabled { telemetryConfig := telemetry.NewConfig() + if customTelemetryUrl := os.Getenv("ANYCABLE_TELEMETRY_URL"); customTelemetryUrl != "" { + telemetryConfig.Endpoint = customTelemetryUrl + } tracker := telemetry.NewTracker(metrics, r.config, telemetryConfig) r.log.With("context", "telemetry").Info(tracker.Announce()) diff --git a/telemetry/config.go b/telemetry/config.go index 45bbd730..6f05143c 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -1,13 +1,19 @@ package telemetry +import "os" + type Config struct { Token string Endpoint string + Debug bool } +var authToken = "secret" // make it overridable during build time + func NewConfig() *Config { return &Config{ - Token: "phc_fc9VFWdFAAm5gSlCodHq93iaxxnTTKbjOwsWgAS1FMP", - Endpoint: "https://app.posthog.com", + Token: authToken, + Endpoint: "https://telemetry.anycable.io", + Debug: os.Getenv("ANYCABLE_TELEMETRY_DEBUG") == "1", } } diff --git a/telemetry/telemetry.go b/telemetry/telemetry.go index b6919203..2bf30f84 100644 --- a/telemetry/telemetry.go +++ b/telemetry/telemetry.go @@ -1,8 +1,11 @@ package telemetry import ( + "bytes" "context" + "encoding/json" "maps" + "net/http" "os" "runtime" "sync" @@ -14,6 +17,7 @@ import ( "github.com/anycable/anycable-go/version" "github.com/hofstadter-io/cinful" "github.com/posthog/posthog-go" + "golang.org/x/exp/slog" nanoid "github.com/matoous/go-nanoid" ) @@ -23,36 +27,44 @@ const ( ) type Tracker struct { - id string - client posthog.Client + id string + client *http.Client + + // Remote service configuration + url string + authToken string + instrumenter *metrics.Metrics config *config.Config timer *time.Timer closed bool - mu sync.Mutex + mu sync.Mutex + logger *slog.Logger // Observed metrics values observations map[string]interface{} } -type noopLogger struct{} +func NewTracker(instrumenter *metrics.Metrics, c *config.Config, tc *Config) *Tracker { + id, _ := nanoid.Nanoid(8) + + client := &http.Client{} -func (l noopLogger) Logf(format string, args ...interface{}) {} -func (l noopLogger) Errorf(format string, args ...interface{}) {} + logLevel := slog.LevelInfo -func NewTracker(instrumenter *metrics.Metrics, c *config.Config, tc *Config) *Tracker { - client, _ := posthog.NewWithConfig(tc.Token, posthog.Config{ - Endpoint: tc.Endpoint, - // set to no-op to avoid logging - Logger: noopLogger{}, - }) + if tc.Debug { + logLevel = slog.LevelDebug + } - id, _ := nanoid.Nanoid(8) + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})) return &Tracker{ client: client, + url: tc.Endpoint, + authToken: tc.Token, + logger: logger, config: c, instrumenter: instrumenter, id: id, @@ -89,19 +101,63 @@ func (t *Tracker) Shutdown(ctx context.Context) error { t.timer.Stop() } - return t.client.Close() + t.client.CloseIdleConnections() + + return nil } func (t *Tracker) Send(event string, props map[string]interface{}) { + t.logger.Debug("send telemetry event", "event", event) + // Avoid storing IP address props["$ip"] = nil props["distinct_id"] = t.id + props["event"] = event + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + payload, err := json.Marshal(props) - _ = t.client.Enqueue(posthog.Capture{ - DistinctId: t.id, - Event: event, - Properties: props, - }) + if err != nil { + t.logger.Debug("failed to marshal telemetry payload", "err", err) + return + } + + req, err := http.NewRequestWithContext(ctx, "POST", t.url, bytes.NewReader(payload)) + if err != nil { + return + } + + req.Header.Set("Content-Type", "application/json") + + if t.authToken != "" { + req.Header.Set("Authorization", t.authToken) + } + + res, err := t.client.Do(req) + + if err != nil { + if ctx.Err() != nil { + t.logger.Debug("timed out to send telemetry data") + return + } + + t.logger.Debug("failed to perform telemetry request", "err", err) + return + } + + defer res.Body.Close() + + if res.StatusCode == http.StatusUnauthorized { + t.logger.Debug("telemetry authenticated failed") + return + } + + if res.StatusCode != http.StatusOK { + t.logger.Debug("telemetry request failed", "status", res.StatusCode) + return + } } func (t *Tracker) monitorUsage() {