{"id":51323888,"url":"https://github.com/phax/ph-telemetry","last_synced_at":"2026-07-01T16:03:07.719Z","repository":{"id":364314012,"uuid":"1267294489","full_name":"phax/ph-telemetry","owner":"phax","description":"Shared telemety stuff","archived":false,"fork":false,"pushed_at":"2026-06-12T14:50:34.000Z","size":45,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T15:22:37.748Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phax.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-12T12:05:44.000Z","updated_at":"2026-06-12T14:50:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/phax/ph-telemetry","commit_stats":null,"previous_names":["phax/ph-telemetry"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/phax/ph-telemetry","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fph-telemetry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fph-telemetry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fph-telemetry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fph-telemetry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phax","download_url":"https://codeload.github.com/phax/ph-telemetry/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phax%2Fph-telemetry/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35013184,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-01T16:03:07.166Z","updated_at":"2026-07-01T16:03:07.713Z","avatar_url":"https://github.com/phax.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ph-telemetry\n\n\u003c!-- ph-badge-start --\u003e\n[![Sonatype Central](https://maven-badges.sml.io/sonatype-central/com.helger.telemetry/ph-telemetry-parent-pom/badge.svg)](https://maven-badges.sml.io/sonatype-central/com.helger.telemetry/ph-telemetry-parent-pom/)\n[![javadoc](https://javadoc.io/badge2/com.helger.telemetry/ph-telemetry/javadoc.svg)](https://javadoc.io/doc/com.helger.telemetry/ph-telemetry)\n\n\u003e 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.\n\u003c!-- ph-badge-end --\u003e\n\nJava 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.\n\nLicensed under the Apache 2.0 license.\n\n# Modules\n\n* **`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.\n* **`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.\n\n# Maven usage\n\nAdd the following to your `pom.xml`, where `x.y.z` is the latest released version:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.helger.telemetry\u003c/groupId\u003e\n  \u003cartifactId\u003eph-telemetry\u003c/artifactId\u003e\n  \u003cversion\u003ex.y.z\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.helger.telemetry\u003c/groupId\u003e\n  \u003cartifactId\u003eph-telemetry-otel\u003c/artifactId\u003e\n  \u003cversion\u003ex.y.z\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nOr import the BOM and skip per-module versions:\n\n```xml\n\u003cdependencyManagement\u003e\n  \u003cdependencies\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003ecom.helger.telemetry\u003c/groupId\u003e\n      \u003cartifactId\u003eph-telemetry-parent-pom\u003c/artifactId\u003e\n      \u003cversion\u003ex.y.z\u003c/version\u003e\n      \u003ctype\u003epom\u003c/type\u003e\n      \u003cscope\u003eimport\u003c/scope\u003e\n    \u003c/dependency\u003e\n  \u003c/dependencies\u003e\n\u003c/dependencyManagement\u003e\n```\n\nNote: 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.\n\n# Usage\n\n## Emitting a span\n\n```java\nimport com.helger.telemetry.ETelemetrySpanKind;\nimport com.helger.telemetry.Telemetry;\n\nTelemetry.withSpanVoid (\"outbound.send\", ETelemetrySpanKind.PRODUCER, aSpan -\u003e {\n  aSpan.setAttribute (\"transaction.id\", sTxID);\n  ... business work ...\n  aSpan.setStatusOk ();\n});\n```\n\nExceptions 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.\n\n`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)`.\n\n### Bodies that throw checked exceptions\n\n`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 \u003cT, E\u003e` / `IThrowingSpanConsumer \u003cE\u003e`, propagate `E` from the call, and still record the exception on the span before re-throwing:\n\n```java\n// returns a value, may throw IOException\nfinal byte[] aPayload = Telemetry.\u003cbyte[], IOException\u003e withSpanThrowing (\n    \"payload.read\", ETelemetrySpanKind.INTERNAL, aSpan -\u003e {\n      final byte[] aBytes = readRequestBody ();\n      aSpan.setAttribute (\"payload.size_bytes\", aBytes.length);\n      return aBytes;\n    });\n\n// void, may throw IOException\nTelemetry.\u003cIOException\u003e withSpanVoidThrowing (\n    \"outbound.send\", ETelemetrySpanKind.PRODUCER, aSpan -\u003e {\n      aSpan.setAttribute (\"transaction.id\", sTxID);\n      sendOverHttp (...);   // throws IOException\n    });\n```\n\nThe 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.\n\n## Recording metrics\n\n```java\nimport com.helger.telemetry.ITelemetryCounter;\nimport com.helger.telemetry.TelemetryAttributes;\nimport com.helger.telemetry.TelemetryMetrics;\n\npublic final class MyMetrics\n{\n  public static final ITelemetryCounter REQUESTS_RECEIVED = TelemetryMetrics.counter (\n      \"myapp.requests.received\",\n      \"Inbound requests accepted by the service\",\n      \"{request}\");\n\n  private MyMetrics () {}\n}\n\n// at the call site:\nMyMetrics.REQUESTS_RECEIVED.add (1,\n    TelemetryAttributes.builder ().put (\"route\", sRoute).build ());\n```\n\n## Wiring OpenTelemetry\n\nIn your application module, subclass each binding with a no-arg constructor that supplies your instrumentation scope:\n\n```java\npublic final class MyAppTracerSPI extends OtelTelemetryTracerSPI\n{\n  public MyAppTracerSPI ()\n  {\n    super (\"com.example.myapp\", MyAppVersion.BUILD_VERSION);\n  }\n}\n\npublic final class MyAppMeterSPI extends OtelTelemetryMeterSPI\n{\n  public MyAppMeterSPI ()\n  {\n    super (\"com.example.myapp\", MyAppVersion.BUILD_VERSION);\n  }\n}\n```\n\nRegister them via two `META-INF/services` files:\n\n```\nMETA-INF/services/com.helger.telemetry.ITelemetryTracerSPI\n  -\u003e com.example.myapp.MyAppTracerSPI\n\nMETA-INF/services/com.helger.telemetry.ITelemetryMeterSPI\n  -\u003e com.example.myapp.MyAppMeterSPI\n```\n\nInitialise 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.\n\n## Tests\n\nTests can install a custom recording SPI without needing an SDK:\n\n```java\n@After public void tearDown () { Telemetry.install (null); }\n\n@Test public void example ()\n{\n  Telemetry.install ((sName, eKind) -\u003e myRecordingSpan);\n  ... exercise code that calls Telemetry.startSpan ...\n}\n```\n\n`TelemetryMetrics.install (...)` works the same way for the metrics side.\n\n# News and noteworthy\n\nv1.0.1 - 2026-06-16\n* New `Telemetry.withSpanThrowing (...)` and `Telemetry.withSpanVoidThrowing (...)` variants that accept a body declaring a checked exception (`IThrowingSpanFunction \u003cT, E\u003e` / `IThrowingSpanConsumer \u003cE\u003e`).\n  The throwable is recorded on the span and re-thrown without wrapping — callers no longer need to smuggle a checked exception through a `RuntimeException`.\n  Both variants catch `Throwable` and defensively guard the `recordException` call so a defective backend cannot mask the user's exception.\n\nv1.0.0 - 2026-06-12\n* Initial release as a standalone repository.\n  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`.\n* New module `ph-telemetry-otel` extracted from per-project OpenTelemetry bindings.\n  Provides `OtelTelemetryTracerSPI` and `OtelTelemetryMeterSPI` as subclassable base classes that wrap the OpenTelemetry API; project subclasses supply only the instrumentation scope name and version.\n* `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.\n\n---\n\nMy personal [Coding Styleguide](https://github.com/phax/meta/blob/master/CodingStyleguide.md) |\nIt is appreciated if you star the GitHub project if you like it.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphax%2Fph-telemetry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphax%2Fph-telemetry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphax%2Fph-telemetry/lists"}