Skip to content
Open
56 changes: 56 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: CI/CD

on:
workflow_dispatch:
push:
branches: ["**"]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# Setup Go
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.22"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go.mod uses: "go 1.23.0" and "toolchain go1.24.2", should this variable reflect that?


- name: Build
run: |
mkdir -p bin
go build -o bin/pyrevit-telemetryserver
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: bin
path: bin

- name: Run Tests
run: go test -v ./...
Comment on lines +31 to +32
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test should be run before the build to ensure everything is OK


docker:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to DockerHub # TODO: uncomment if pushing
# uses: docker/login-action@v3
# with:
# username: ${{ secrets.DOCKERHUB_USERNAME }}
# password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build Docker image
run: docker build -t pyrevit-telemetryserver:latest .

# - name: Push Docker image # Will be uncommented if pushing
# run: docker push pyrevit-telemetryserver:latest
Comment on lines +34 to +56
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job should run only on "release" and sync the docker tag with the version.
Since this PR doesn't have a Dockerfile, it is better to remove this job and think about it later.

58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,56 @@
# telemetry-server
Telemetry Server for pyRevit
# pyRevit Telemetry Server

## Quick Start

```sh
# Build the server
go build -o pyrevit-telemetryserver

# Run with environment variables
PYREVT_TELEMETRY_DB_CONNSTRING="mongodb://localhost:27017/atoma-test" \
PYREVT_TELEMETRY_SCRIPTS_TABLE="scripts" \
PYREVT_TELEMETRY_EVENTS_TABLE="events" \
./pyrevit-telemetryserver --port=8080
```

## Environment Variables

| Variable | Description | Example |
|----------------------------------------|---------------------------------------------|-----------------------------------------|
| PYREVT_TELEMETRY_DB_BACKEND | Database backend (`mongo`, `postgres`, etc) | mongo |
| PYREVT_TELEMETRY_DB_CONNSTRING | Database connection string | mongodb://localhost:27017/atoma-test |
| PYREVT_TELEMETRY_SCRIPTS_TABLE | Name of the scripts table/collection | scripts |
| PYREVT_TELEMETRY_EVENTS_TABLE | Name of the events table/collection | events |
| PYREVT_TELEMETRY_PORT | Port to run the server on | 8080 |
| PYREVT_TELEMETRY_DEBUG | Enable debug logging (`true`/`false`) | true |
| PYREVT_TELEMETRY_TRACE | Enable trace logging (`true`/`false`) | false |

> All CLI options can be set via environment variables. CLI flags override environment variables.

## API Endpoints

- `GET /api/v1/status` — Health check
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also the /api/v2/status endpoint

- `GET /api/v1/scripts/` — List v1 script telemetry
- `POST /api/v1/scripts/` — Submit v1 script telemetry
- `GET /api/v2/scripts/` — List v2 script telemetry
- `POST /api/v2/scripts/` — Submit v2 script telemetry
- `GET /api/v2/events/` — List v2 event telemetry
- `POST /api/v2/events/` — Submit v2 event telemetry

## Example: Submit Script Telemetry

```sh
curl -X POST -H "Content-Type: application/json" \
-d '{"date":"2024-03-30","time":"08:45:00", ... }' \
http://localhost:8080/api/v1/scripts/
```

## Docker Usage

### Build and Run with Docker Compose

```sh
docker-compose up --build
```

This will start both DB and the telemetry server. The server will be available at `http://localhost:8080`.
121 changes: 121 additions & 0 deletions cli/args.go
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we could use a library like config to handle config, envs and cli args all at once declaratively?

This should also remove the need of the usage.go module

Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package cli

import (
"os"
"path/filepath"
"strconv"
"strings"

"github.com/docopt/docopt-go"
)

// Environment variable support:
// PYREVT_TELEMETRY_DB_CONNSTRING
// PYREVT_TELEMETRY_SCRIPTS_TABLE
// PYREVT_TELEMETRY_EVENTS_TABLE
// PYREVT_TELEMETRY_PORT
// PYREVT_TELEMETRY_HTTPS
// PYREVT_TELEMETRY_DEBUG
// PYREVT_TELEMETRY_TRACE
// PYREVT_TELEMETRY_EXENAME
// PYREVT_TELEMETRY_VERSION

type Options struct {
ExeName string `json:"exe_name"`
Version string `json:"version"`
Opts *docopt.Opts
ConnString string `json:"connection_string"`
ScriptsTable string `json:"script_table"`
EventsTable string `json:"events_table"`
Port int `json:"server_port"`
Https bool `json:"https"`
Debug bool `json:"debug_mode"`
Trace bool `json:"trace_mode"`
}

func getExeName() string {
return strings.TrimSuffix(
filepath.Base(os.Args[0]),
filepath.Ext(os.Args[0]),
)
}

func NewOptions() *Options {
argv := os.Args[1:]

parser := &docopt.Parser{
HelpHandler: printHelpAndExit,
}

opts, _ := parser.ParseArgs(help, argv, version)

connString, _ := opts.String("<db_uri>")
scriptTable, _ := opts.String("--scripts")
eventTable, _ := opts.String("--events")
port, _ := opts.Int("--port")
https, _ := opts.Bool("--https")

debug, _ := opts.Bool("--debug")
trace, _ := opts.Bool("--trace")

// Environment variable fallback
if connString == "" {
connString = os.Getenv("PYREVT_TELEMETRY_DB_CONNSTRING")
}
if scriptTable == "" {
scriptTable = os.Getenv("PYREVT_TELEMETRY_SCRIPTS_TABLE")
}
if eventTable == "" {
eventTable = os.Getenv("PYREVT_TELEMETRY_EVENTS_TABLE")
}
Comment on lines +65 to +70
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another ranting for the future: do we really need to have dynamic table names? Especially now that you're introducing the APIs to get the data out of the DB....

if port == 0 {
if portStr := os.Getenv("PYREVT_TELEMETRY_PORT"); portStr != "" {
if p, err := strconv.Atoi(portStr); err == nil {
port = p
}
}
}
if !https {
if httpsStr := os.Getenv("PYREVT_TELEMETRY_HTTPS"); httpsStr != "" {
if httpsStr == "1" || strings.ToLower(httpsStr) == "true" {
https = true
}
}
}
if !debug {
if debugStr := os.Getenv("PYREVT_TELEMETRY_DEBUG"); debugStr != "" {
if debugStr == "1" || strings.ToLower(debugStr) == "true" {
debug = true
}
}
}
if !trace {
if traceStr := os.Getenv("PYREVT_TELEMETRY_TRACE"); traceStr != "" {
if traceStr == "1" || strings.ToLower(traceStr) == "true" {
trace = true
}
}
}

exeName := getExeName()
if envExe := os.Getenv("PYREVT_TELEMETRY_EXENAME"); envExe != "" {
exeName = envExe
}
ver := version
if envVer := os.Getenv("PYREVT_TELEMETRY_VERSION"); envVer != "" {
ver = envVer
}

return &Options{
ExeName: exeName,
Version: ver,
Opts: &opts,
ConnString: connString,
ScriptsTable: scriptTable,
EventsTable: eventTable,
Port: port,
Https: https,
Debug: debug,
Trace: trace,
}
}
39 changes: 39 additions & 0 deletions cli/logger.go
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a note for later... could we use slog here?

Copy link
Author

@fishonamos fishonamos Jun 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We could do that in the next refactor pr. Slog will provide a more structured logging. I will note it in the list of next updates to make. I can do it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should do that in a separate pr after this is merged? @sanzoghenzo because this already has too many changes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, let's save it for later.

I'll create new issues from these comments (and move/split the original issue from pyrevit repo)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cli

import (
"log"

"pkg.re/essentialkaos/ek.v10/fmtc"
)

type Logger struct {
PrintDebug bool
PrintTrace bool
}

func NewLogger(options *Options) *Logger {
return &Logger{
PrintDebug: options.Debug,
PrintTrace: options.Trace,
}
}

func (m *Logger) Fatal(args ...interface{}) {
log.Fatal(args...)
}

func (m *Logger) Debug(args ...interface{}) {
if m.PrintDebug {
log.Print(args...)
}
}

func (m *Logger) Trace(args ...interface{}) {
if m.PrintTrace {
log.Print(args...)
}
}

func (m *Logger) Print(args ...interface{}) {
fmtc.Println(args...)
}
51 changes: 51 additions & 0 deletions cli/usage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cli

import (
"fmt"
"os"
)

const version string = "0.19"
const help string = `Record pyRevit usage logs to database

Usage:
pyrevit-telemetryserver [<db_uri>] [--scripts=<script_table>] [--events=<event_table>] --port=<port> [--https] [--debug] [--trace]

Options:
-h --help show this screen
-V --version show version
--scripts=<script_table> target table or collection for script logs
--events=<event_table> target table or collection for app event logs
--port=<port> server port number to listen on
--https secure connection, expects ./pyrevit-telemetryserver.key and ./pyrevit-telemetryserver.crt
--debug print debug info
--trace print trace info e.g. full json logs and sql queries

Supports:
postgresql: using github.com/lib/pq
mongodb: using gopkg.in/mgo.v2
mysql: using github.com/go-sql-driver/mysql
sqlserver: using github.com/denisenkom/go-mssqldb
sqlite3: using github.com/mattn/go-sqlite3

Examples:
pyrevit-telemetryserver postgres://user:[email protected]/mydb --scripts="pyrevitlogs" --events="appevents" --port=8080 --debug
pyrevit-telemetryserver mongodb://user:pass@localhost:27017/mydb --scripts="pyrevitlogs" --events="appevents" --port=8080
pyrevit-telemetryserver "mysql:user:pass@tcp(localhost:3306)/tests" --scripts="pyrevitlogs" --port=8080
pyrevit-telemetryserver sqlserver://user:[email protected]?database=mydb --scripts="pyrevitlogs" --port=8080
pyrevit-telemetryserver sqlite3:data.db --scripts="pyrevitlogs" --port=8080
`

var printHelpAndExit = func(err error, docoptMessage string) {
if err != nil {
// if err occured print full help
// docopt only includes usage section in its message
fmt.Fprint(os.Stderr, help)
os.Exit(1)
} else {
// otherwise print whatever docopt says
// e.g. reporting version
fmt.Println(docoptMessage)
os.Exit(0)
}
}
40 changes: 40 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module pyrevittelemetryserver

go 1.23.0

toolchain go1.24.2

require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/denisenkom/go-mssqldb v0.11.0
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/go-sql-driver/mysql v1.6.0
github.com/gofrs/uuid v4.3.1+incompatible
github.com/gorilla/mux v1.8.0
github.com/lib/pq v1.10.3
github.com/mattn/go-sqlite3 v1.14.8
github.com/pkg/errors v0.9.1
github.com/satori/go.uuid v1.2.0
go.mongodb.org/mongo-driver v1.11.1
pkg.re/essentialkaos/ek.v10 v12.32.0+incompatible
)

require github.com/stretchr/testify v1.10.0 // indirect

require (
// Indirect dependencies
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/text v0.22.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
pkg.re/essentialkaos/check.v1 v1.0.0 // indirect
)
Loading