Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ whole library is one file, `TraceClient.java`, and the integration on the
program side is meant to stay one call.

```java
TraceClient trace = TraceClient.builder("https://trace.danielstephenson.dev", "MyPlugin")
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
Expand All @@ -33,6 +34,19 @@ trace.report("command", 1.0, Collections.singletonMap("name", "home"));
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 |
Expand Down Expand Up @@ -79,8 +93,8 @@ tags:
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: a server-wide `version` never overwrites the
`version` a plugin sends.
- 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
Expand Down Expand Up @@ -113,7 +127,7 @@ plugins already vendor bStats' `Metrics.java`.
<dependency>
<groupId>com.github.Stephenson-Software</groupId>
<artifactId>trace-client-java</artifactId>
<version>0.3.0</version>
<version>0.4.0</version>
</dependency>
```

Expand All @@ -124,10 +138,10 @@ Shade it into a plugin jar; it is one class.
`POST {baseUrl}/api/metrics` with `Authorization: Bearer <key>` and a body of

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

`value` and `tags` are omitted when not given. The server assigns the
`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
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>software.stephenson</groupId>
<artifactId>trace-client</artifactId>
<version>0.3.0</version>
<version>0.4.0</version>
<packaging>jar</packaging>

<name>trace-client</name>
Expand Down
57 changes: 48 additions & 9 deletions src/main/java/software/stephenson/trace/TraceClient.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* trace-client 0.3.0 -- https://github.com/Stephenson-Software/trace-client-java
* trace-client 0.4.0 -- https://github.com/Stephenson-Software/trace-client-java
*
* One call to report that a program was used. Copy this file into a project as
* is, or depend on the artifact; either way there is nothing else to add.
Expand Down Expand Up @@ -72,12 +72,18 @@
* event's own tag wins over a server-wide one of the same name. See
* {@link Builder#serverWideConfig(File)}.
*
* <p>Every event carries the program's own version as the tag
* {@code version} -- the third argument to {@link #builder}, required, so a
* {@code command} event can be tied to a release as well as a
* {@code startup} one. An event's own {@code version} tag wins over it.
*
* <p>A disabled client is a no-op that costs nothing. Programs that run on
* other people's machines should expose their own switch in their
* configuration and say on startup whether reporting is on.
*
* <pre>{@code
* TraceClient trace = TraceClient.builder("https://trace.example.org", "MyPlugin")
* TraceClient trace = TraceClient.builder("https://trace.example.org", "MyPlugin",
* getDescription().getVersion())
* .key(config.getString("usage-reporting.key"))
* .enabled(config.getBoolean("usage-reporting.enabled", true))
* .serverWideConfig(getDataFolder().getParentFile()) // plugins/
Expand All @@ -100,7 +106,7 @@
public final class TraceClient {

/** This client's version, as sent in the User-Agent. */
public static final String VERSION = "0.3.0";
public static final String VERSION = "0.4.0";

/** How many reports may wait to be sent before new ones are dropped. */
public static final int QUEUE_CAPACITY = 256;
Expand Down Expand Up @@ -158,6 +164,7 @@ public final class TraceClient {
private final String endpoint;
private final String key;
private final String application;
private final String version;
private final Logger logger;
private final String disabledReason; // null when enabled
private final Map<String, String> serverWideTags; // never null; read once, at build()
Expand All @@ -167,6 +174,7 @@ private TraceClient(Builder builder) {
this.endpoint = builder.baseUrl.replaceAll("/+$", "") + "/api/metrics";
this.key = builder.key;
this.application = builder.application;
this.version = builder.version;
this.logger = builder.logger;
ServerWideConfig serverWide = builder.pluginsDirectory == null || environmentDisables()
? ServerWideConfig.NONE
Expand All @@ -191,15 +199,18 @@ private TraceClient(Builder builder) {

/**
* Starts describing a client for the program named {@code application},
* reporting to the trace server at {@code baseUrl}.
* at {@code version}, reporting to the trace server at {@code baseUrl}.
* The version is sent as the tag {@code version} on every event; a blank
* one, or one longer than {@value #MAX_TAG_LENGTH} characters, is an
* {@link IllegalArgumentException}.
*/
public static Builder builder(String baseUrl, String application) {
return new Builder(baseUrl, application);
public static Builder builder(String baseUrl, String application, String version) {
return new Builder(baseUrl, application, version);
}

/** A client that reports nothing. Useful as a default before configuration is read. */
public static TraceClient disabled() {
return new Builder("http://disabled.invalid", "disabled").enabled(false).build();
return new Builder("http://disabled.invalid", "disabled", "disabled").enabled(false).build();
}

/** Whether {@link #report} will actually send anything. */
Expand Down Expand Up @@ -460,6 +471,25 @@ static Map<String, String> withServerWideTags(Map<String, String> tags, Map<Stri
return merged;
}

/**
* The event's own tags plus {@code version}, unless the event already
* carries one. A copy; the caller's map is never modified.
*/
static Map<String, String> withVersion(Map<String, String> tags, String version) {
Map<String, String> merged = new LinkedHashMap<>();
if (tags != null) {
for (Map.Entry<String, String> tag : new LinkedHashMap<>(tags).entrySet()) {
if (tag.getKey() != null && tag.getValue() != null) {
merged.put(tag.getKey(), tag.getValue());
}
}
}
if (!merged.containsKey("version")) {
merged.put("version", version);
}
return merged;
}

/** Reports that {@code name} happened. */
public void report(String name) {
report(name, null, null);
Expand All @@ -473,7 +503,8 @@ public void report(String name, Double value, Map<String, String> tags) {
if (executor == null || name == null || name.trim().isEmpty()) {
return;
}
final String body = json(application, name, value, withServerWideTags(tags, serverWideTags));
final String body = json(application, name, value,
withServerWideTags(withVersion(tags, version), serverWideTags));
executor.execute(() -> send(body));
}

Expand Down Expand Up @@ -606,20 +637,28 @@ static String quote(String text) {
public static final class Builder {
private final String baseUrl;
private final String application;
private final String version;
private String key;
private boolean enabled = true;
private File pluginsDirectory;
private Logger logger;

private Builder(String baseUrl, String application) {
private Builder(String baseUrl, String application, String version) {
if (baseUrl == null || baseUrl.trim().isEmpty()) {
throw new IllegalArgumentException("baseUrl is required");
}
if (application == null || application.trim().isEmpty()) {
throw new IllegalArgumentException("application is required");
}
if (version == null || version.trim().isEmpty()) {
throw new IllegalArgumentException("version is required");
}
if (version.trim().length() > MAX_TAG_LENGTH) {
throw new IllegalArgumentException("version is longer than " + MAX_TAG_LENGTH + " characters");
}
this.baseUrl = baseUrl.trim();
this.application = application.trim();
this.version = version.trim();
}

/** The program's write key. Without one the client is a no-op. */
Expand Down
Loading
Loading