Sitelet https://github.com/Stephenson-Software/trace-client-java/tree/program-version-on-every-event
Skip to content

About

One call to report that a program was used — a dependency-free Java 8 client for a trace server

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

trace-client

One call to report that a program was used.

A dependency-free Java 8 client for a trace server — the central place a fleet of programs reports usage events to. The whole library is one file, TraceClient.java, and the integration on the program side is meant to stay one call.

TraceClient trace = TraceClient.builder("https://trace.danielstephenson.dev", "MyPlugin",
                getDescription().getVersion())
        .key(config.getString("usage-reporting.key"))
        .enabled(config.getBoolean("usage-reporting.enabled", true))
        .serverWideConfig(getDataFolder().getParentFile()) // plugins/ -- Bukkit plugins only
        .logger(getLogger())
        .build();

// Say so, every startup, on the program's own logger.
if (trace.isEnabled()) {
    getLogger().info("Usage reporting is on: MyPlugin sends its name, version and command names to "
            + "https://trace.danielstephenson.dev - nothing about players or the server. "
            + "Turn it off with usage-reporting.enabled: false in this plugin's config.yml, "
            + "or for every plugin with enabled: false in plugins/trace/config.yml. "
            + "Details: https://github.com/Stephenson-Software/trace#usage-reporting");
} else {
    getLogger().info("Usage reporting is off (" + trace.disabledReason() + ").");
}

trace.report("startup");
trace.report("command", 1.0, Collections.singletonMap("name", "home"));

// on shutdown
trace.close();

Every event carries the program's version

The third argument to builder is the program's own version, and it is required: a blank one, or one over 255 characters, throws IllegalArgumentException. Every event the client sends — startup, command, anything else — carries it as the tag version, so every event can be tied to a release, not just startup. An event that passes its own version tag keeps it. There is no need to tag startup by hand any more.

Before 0.4.0, builder took two arguments and only events tagged by hand carried a version. Upgrading is one argument: in a Bukkit plugin, getDescription().getVersion().

What report promises

Property Meaning
Returns immediately The HTTP call runs on one daemon thread the client owns. A Spigot plugin can report from the server thread and no tick waits on the network.
Never throws A server that is down, slow, or rejecting the key is a dropped report, not an exception in your program. Drops are logged at FINE if you gave a logger, otherwise not at all.
Bounded At most 256 reports wait to be sent; past that, new ones are dropped. A trace server that is unreachable for a week costs a few kilobytes, not your heap.
close() drains Reports already queued get up to the client timeout (5 s total) to be sent before the thread stops, so a CLI that reports and exits at once does not lose its event. Still bounded: an unreachable server delays exit by at most the timeout.

Opting out

Reporting is opt-out, and the person running the program always has the last word. build() checks these in order; the first match wins and is what disabledReason() returns, verbatim, so the program can log it:

Switch disabledReason()
Environment: TRACE_USAGE_REPORTING=off (or false, 0, no) or DO_NOT_TRACK=1 (or true, yes), case-insensitive. Always checked. environment
Server-wide, when serverWideConfig(pluginsDirectory) was given: enabled: false in plugins/trace/config.yml. build() creates the file with enabled: true (and a commented-out tags: example) if it is missing and never rewrites it afterwards; it is read with a line regex, no YAML library. An IO failure is logged at FINE and counts as enabled. server-wide config: plugins/trace/config.yml
The program's own setting: enabled(false). config.yml
No key, or a blank one. no key

disabledReason() is null when the client is enabled. A disabled client does nothing and costs nothing. A program that runs on other people's machines should expose its own switch in its configuration and print, on every startup, whether reporting is on and how to turn it off — see the example above and the usage reporting page for the wording the fleet uses.

Server-wide tags

The same plugins/trace/config.yml can carry a tags: block. Every event every plugin on that server reports gets these tags added — the way a test or CI server marks itself so its events are left out of real-installation figures (the trace server's public numbers exclude ci, service and page):

enabled: true
tags:
  ci: "true"
  • tags: starts at column 0 and is followed by indented key: value lines. Values may be double-quoted, single-quoted or bare; blank lines and # comments inside the block are skipped. The block ends at the next line that is not indented, or at the end of the file.
  • An event's own tag always wins, and the program's version is the event's own: a server-wide version never overwrites it.
  • Entries the trace server would reject are dropped one by one, never the whole report: keys must match [A-Za-z0-9][A-Za-z0-9_.-]*, keys and values are at most 255 characters, and server-wide tags stop being added once an event carries 32 tags in total. Anything the line reader does not understand (flow maps, lists, block scalars, a quote never closed) is dropped the same way; a malformed file never throws and never turns reporting off.
  • The tags are read once, in build(), together with enabled:. enabled: false still wins — a disabled client sends nothing, tags or not.
  • A file created by build() has the example above commented out, so nothing is added until the operator uncomments it.

Getting it

Copy the file. src/main/java/software/stephenson/trace/TraceClient.java has no dependencies and compiles on Java 8. Drop it into your source tree, keep the header so it can be found again, and you are done — the same way plugins already vendor bStats' Metrics.java.

Or depend on it via JitPack:

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependency>
    <groupId>com.github.Stephenson-Software</groupId>
    <artifactId>trace-client-java</artifactId>
    <version>0.4.0</version>
</dependency>

Shade it into a plugin jar; it is one class.

The wire format

POST {baseUrl}/api/metrics with Authorization: Bearer <key> and a body of

{"application":"MyPlugin","name":"command","value":1.0,"tags":{"name":"home","version":"1.4.0"}}

value is omitted when not given; tags always holds at least version. The server assigns the timestamp. A 201 is success; anything else is logged at FINE and dropped.

Keys

A key identifies the program to the server and lets the operator revoke it; it is scoped to reporting only. Because it ships inside the program, it cannot prove anything — treat trace data as best-effort telemetry, which is what it is. Ask the trace operator for a key for your program.

Building

mvn verify

Tests run the client against the JDK's own HttpServer on a loopback port — no more dependencies than the client itself. CI runs them on Java 8, 17 and 21.

License

MIT.

About

One call to report that a program was used — a dependency-free Java 8 client for a trace server

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages