CONTRIBUTING.md
June 3, 2025 · View on GitHub
You can get in touch on GitHub issues if you have any questions or problems.
Don't be afraid to just ask if you're struggling with something. Chances are that I can quickly give you an answer or point you in the right direction by spending just a few minutes.
Running
You can start a test/development server with:
% goatcounter serve -dev
The -dev flag makes some small things a bit more convenient for development:
the application will automatically restart on recompiles, templates and static
files will be read directly from the filesystem, and a few other minor changes.
General notes
-
It's probably best to create an issue first for non-trivial patches. This might prevent you from wasting time on a wrong approach, or from working on something that will never be merged.
- I'm pretty wary of introducing dependencies, especially if they come with large dependency trees. So it's probably best to communicate in the issue if you're planning to do that.
-
Use
-debug=<mod>to enable debug logs for specific modules, or-debug=allto enable it for all modules. -
Automatic reload is managed with github.com/teamwork/reload. Basically it will restart on recompile, and reload templates once they change (no restart required).
-
Tests can be run with
go test ./...; nothing special needed. You can run tests against PostgreSQL (instead of SQLite) withgo test -tags=testpg ./.... You can use the standardPG*environment variables to control the connection (e.g.PGHOST,PGPORT). -
Keep lines under 80 characters if possible, but don't bend over backwards to do so: it's usually okay for a function definition or call to be 90 or even 100 characters. Comments should pretty much always be wrapped to 80 characters though.
Code design
Some notes about the code; most of it should – hopefully – be fairly straightforward
-
./cmd/goatcounter/ is the
mainpackage which starts everything. -
The "models" are contained in ./site.go, ./hit.go, etc. site.go is probably the best place to look at to get an overview of the patterns used. Most of this is fairly straight-forward and uncomplicated.
-
HTTP handlers go in ./handlers/
-
The
/countendpoint to records pageviews is dealt different than most other requests: instead of persisting to the DB immediately it's added to memstore first. The cron package will persist that to DB every 10 seconds, which also regenerates various cached stats. -
Hits ("pageviews") are stored in the
hitstable with minimal processing; for the most part, this table isn't queried directly for reasons of performance. When inserting new rows in the table the various*_statsand*_counttables are updated which contain a more efficient aggregation of the data (hit_stats, browser_stats, etc.) This is done through thecron` package. -
Templates live in
/tpl, and are standard Go templates. The Go template library is a bit idiosyncratic, but once you "get" it they're quite pleasant to work with (I can't find a good/gentle "getting started with Go templates" introduction, let me know if you know of one; but just ask if you're struggling with this). -
The frontend is in /public. It's all simple basic CSS with simple jQuery-based JavaScript.
Special cookies
These only work in -dev mode:
-
Set the
debug-delaycookie to a numerical value to delay the response of every request by n seconds. This is mostly intended to debug frontend timing issues. -
Set the
debug-explaincookie to automatically print all queries and their EXPLAIN. If this is an empty string everything will be printed, and if it's non-empty only queries containing the given string will be printed. -
Set
debug-dumpto automatically print all queries and the results (but not the EXPLAIN). Likedebug-explain, an empty string will print everything and a non-empty string prints on the queries containing the given string.
Pro-tip: setting cookies in the debugger tools is a bit of a pain; I tend to
just set these cookies once, and set the path to /asdasd to "disable" then,
and back to / if I want to enable it again :-)