https://github.com/phax/ph-telemetry
Shared telemety stuff
https://github.com/phax/ph-telemetry
Last synced: 11 days ago
JSON representation
Shared telemety stuff
- Host: GitHub
- URL: https://github.com/phax/ph-telemetry
- Owner: phax
- License: apache-2.0
- Created: 2026-06-12T12:05:44.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-12T14:50:34.000Z (30 days ago)
- Last Synced: 2026-06-12T15:22:37.748Z (30 days ago)
- Language: Java
- Size: 43.9 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# ph-telemetry
[](https://maven-badges.sml.io/sonatype-central/com.helger.telemetry/ph-telemetry-parent-pom/)
[](https://javadoc.io/doc/com.helger.telemetry/ph-telemetry)
> If this project saved you some time or made your day a little easier, a star would mean a lot — it helps others find it too.
Java 17+ vendor-neutral telemetry abstraction (tracing + metrics) with a pluggable OpenTelemetry binding. Lets libraries emit spans and instruments without pulling the OpenTelemetry API into their dependency graph, and lets applications swap in a real backend (OpenTelemetry out of the box; any `ServiceLoader`-registered SPI implementation — Jaeger, Zipkin, a custom recorder, etc. — works the same way) or a no-op fallback.
Licensed under the Apache 2.0 license.
# Modules
* **`ph-telemetry`** — the abstraction itself. Static facades `Telemetry` (tracing) and `TelemetryMetrics` (counters / up-down counters / histograms / observable gauges), backed by SPIs (`ITelemetryTracerSPI`, `ITelemetryMeterSPI`). If no SPI is registered, both facades transparently degrade to cheap no-ops, so libraries can emit telemetry unconditionally without forcing the cost or the dependency on downstream consumers.
* **`ph-telemetry-otel`** — the OpenTelemetry binding. Provides `OtelTelemetryTracerSPI` and `OtelTelemetryMeterSPI` as subclassable base classes that resolve the SDK via `GlobalOpenTelemetry`. Project applications subclass them with a no-arg constructor supplying an instrumentation scope name + version, register the subclass via `META-INF/services`, and let `ServiceLoader` wire it all up at runtime.
# Maven usage
Add the following to your `pom.xml`, where `x.y.z` is the latest released version:
```xml
com.helger.telemetry
ph-telemetry
x.y.z
```
```xml
com.helger.telemetry
ph-telemetry-otel
x.y.z
```
Or import the BOM and skip per-module versions:
```xml
com.helger.telemetry
ph-telemetry-parent-pom
x.y.z
pom
import
```
Note: prior to v1.0.0 the abstraction shipped from `ph-commons` as `com.helger.commons:ph-telemetry`. That module is now `@Deprecated(forRemoval = true)`; switch the dependency over.
# Usage
## Emitting a span
```java
import com.helger.telemetry.ETelemetrySpanKind;
import com.helger.telemetry.Telemetry;
Telemetry.withSpanVoid ("outbound.send", ETelemetrySpanKind.PRODUCER, aSpan -> {
aSpan.setAttribute ("transaction.id", sTxID);
... business work ...
aSpan.setStatusOk ();
});
```
Exceptions thrown inside the body are automatically recorded on the span and the status is set to `ERROR`. If no tracer SPI is registered, the body still runs and `aSpan` is a no-op.
`ETelemetrySpanKind` mirrors OpenTelemetry's `SpanKind`: `INTERNAL`, `CLIENT`, `SERVER`, `PRODUCER`, `CONSUMER`. Use `Telemetry.withSpan (name, kind, body)` when the body needs to return a value; `withSpanVoid (...)` for the void-returning case. Both start the span, record exceptions, set OK/ERROR status, and close the span in a `finally` block. `Telemetry.startSpan (...)` is also available for callers that want to manage the lifecycle manually — `ITelemetrySpan` exposes typed attribute setters (`setAttribute (String, String|long|double|boolean)`), `recordException (Throwable)`, and `setStatusOk ()` / `setStatusError (String)`.
### Bodies that throw checked exceptions
`withSpan (...)` and `withSpanVoid (...)` take a `Function` / `Consumer` and therefore cannot accept a body that declares a checked exception. Use the `*Throwing` variants when the body needs to throw — they take `IThrowingSpanFunction ` / `IThrowingSpanConsumer `, propagate `E` from the call, and still record the exception on the span before re-throwing:
```java
// returns a value, may throw IOException
final byte[] aPayload = Telemetry. withSpanThrowing (
"payload.read", ETelemetrySpanKind.INTERNAL, aSpan -> {
final byte[] aBytes = readRequestBody ();
aSpan.setAttribute ("payload.size_bytes", aBytes.length);
return aBytes;
});
// void, may throw IOException
Telemetry. withSpanVoidThrowing (
"outbound.send", ETelemetrySpanKind.PRODUCER, aSpan -> {
aSpan.setAttribute ("transaction.id", sTxID);
sendOverHttp (...); // throws IOException
});
```
The throwing variants catch `Throwable` (not just `RuntimeException`), so they also handle `Error` correctly: the exception is recorded on the span and the original is always re-thrown — a defective backend that itself throws from `recordException` cannot mask the user's exception.
## Recording metrics
```java
import com.helger.telemetry.ITelemetryCounter;
import com.helger.telemetry.TelemetryAttributes;
import com.helger.telemetry.TelemetryMetrics;
public final class MyMetrics
{
public static final ITelemetryCounter REQUESTS_RECEIVED = TelemetryMetrics.counter (
"myapp.requests.received",
"Inbound requests accepted by the service",
"{request}");
private MyMetrics () {}
}
// at the call site:
MyMetrics.REQUESTS_RECEIVED.add (1,
TelemetryAttributes.builder ().put ("route", sRoute).build ());
```
## Wiring OpenTelemetry
In your application module, subclass each binding with a no-arg constructor that supplies your instrumentation scope:
```java
public final class MyAppTracerSPI extends OtelTelemetryTracerSPI
{
public MyAppTracerSPI ()
{
super ("com.example.myapp", MyAppVersion.BUILD_VERSION);
}
}
public final class MyAppMeterSPI extends OtelTelemetryMeterSPI
{
public MyAppMeterSPI ()
{
super ("com.example.myapp", MyAppVersion.BUILD_VERSION);
}
}
```
Register them via two `META-INF/services` files:
```
META-INF/services/com.helger.telemetry.ITelemetryTracerSPI
-> com.example.myapp.MyAppTracerSPI
META-INF/services/com.helger.telemetry.ITelemetryMeterSPI
-> com.example.myapp.MyAppMeterSPI
```
Initialise the OpenTelemetry SDK once at application startup (e.g. via `AutoConfiguredOpenTelemetrySdk.builder().setResultAsGlobal().build()`). The SPI bindings resolve the SDK from `GlobalOpenTelemetry` on first use; until the SDK is installed, the OTel no-op returned by `GlobalOpenTelemetry.get()` keeps the whole pipeline cheap.
## Tests
Tests can install a custom recording SPI without needing an SDK:
```java
@After public void tearDown () { Telemetry.install (null); }
@Test public void example ()
{
Telemetry.install ((sName, eKind) -> myRecordingSpan);
... exercise code that calls Telemetry.startSpan ...
}
```
`TelemetryMetrics.install (...)` works the same way for the metrics side.
# News and noteworthy
v1.0.1 - 2026-06-16
* New `Telemetry.withSpanThrowing (...)` and `Telemetry.withSpanVoidThrowing (...)` variants that accept a body declaring a checked exception (`IThrowingSpanFunction ` / `IThrowingSpanConsumer `).
The throwable is recorded on the span and re-thrown without wrapping — callers no longer need to smuggle a checked exception through a `RuntimeException`.
Both variants catch `Throwable` and defensively guard the `recordException` call so a defective backend cannot mask the user's exception.
v1.0.0 - 2026-06-12
* Initial release as a standalone repository.
The abstraction (`Telemetry`, `TelemetryMetrics`, `ITelemetryTracerSPI`, `ITelemetryMeterSPI`, `TelemetryAttributes`, instrument interfaces, no-op fallbacks) is unchanged from its previous home in `ph-commons:ph-telemetry` v12.3.0 — only the Maven coordinates moved from `com.helger.commons:ph-telemetry` to `com.helger.telemetry:ph-telemetry`.
* New module `ph-telemetry-otel` extracted from per-project OpenTelemetry bindings.
Provides `OtelTelemetryTracerSPI` and `OtelTelemetryMeterSPI` as subclassable base classes that wrap the OpenTelemetry API; project subclasses supply only the instrumentation scope name and version.
* `ph-telemetry-otel` depends on `opentelemetry-api` only — applications that also need the SDK (autoconfigure, OTLP exporter, etc.) pull those dependencies themselves at the deployment boundary.
---
My personal [Coding Styleguide](https://github.com/phax/meta/blob/master/CodingStyleguide.md) |
It is appreciated if you star the GitHub project if you like it.