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
22 changes: 21 additions & 1 deletion .github/workflows/integration-certification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
- case: gin
app_port: 8080
- case: spring
- case: micronaut
- case: quarkus
steps:
- name: Checkout
Expand Down Expand Up @@ -74,7 +75,7 @@ jobs:
cache-dependency-path: examples/node-express-demo/package-lock.json

- name: Set up Java
if: matrix.case == 'spring' || matrix.case == 'quarkus'
if: matrix.case == 'spring' || matrix.case == 'quarkus' || matrix.case == 'micronaut'
uses: actions/setup-java@v4
with:
distribution: temurin
Expand Down Expand Up @@ -123,6 +124,19 @@ jobs:
working-directory: examples/quarkus-metrics-demo
run: mvn --batch-mode --no-transfer-progress package

- name: Build Micronaut demo
if: matrix.case == 'micronaut'
working-directory: examples/micronaut-metrics-demo
run: mvn --batch-mode --no-transfer-progress package dependency:build-classpath -Dmdep.outputFile=target/classpath.txt

- name: Report Micronaut versions
if: matrix.case == 'micronaut'
working-directory: examples/micronaut-metrics-demo
run: |
java --version
mvn --version
mvn --batch-mode --no-transfer-progress dependency:tree '-Dincludes=io.micronaut:micronaut-core,io.micronaut:micronaut-management,io.micronaut.micrometer:*,io.micrometer:*'

- name: Report Spring Boot versions
if: matrix.case == 'spring'
working-directory: examples/spring-actuator-demo
Expand Down Expand Up @@ -161,3 +175,9 @@ jobs:
env:
STATLITE_BIN: ${{ runner.temp }}/statlite
run: ./scripts/ci/integration-quarkus.sh

- name: Run Micronaut integration journey
if: matrix.case == 'micronaut'
env:
STATLITE_BIN: ${{ runner.temp }}/statlite
run: ./scripts/ci/integration-micronaut.sh
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ This file summarizes the main user-facing changes in each StatLite release.
GitHub release notes use the matching version section, with commit history as
a fallback when a section is missing.

## v0.6.0 (2026-10-05)

- Added first-class Micronaut monitoring with an explicit `micronaut` target,
Micrometer Prometheus metrics, optional management health, and typed
compatibility inspection. Certified Micronaut 5.2.11 as the primary runtime
and retained Micronaut 4.9.9 support.
- Added a runnable Micronaut example and a public integration journey covering
metrics, health, inspection, and restart boundaries.
- Preserved Micronaut health capability and restart detection through partial
metrics failures, with independent application and database health reporting.
- Added canonical Spring, Quarkus, and Micronaut target references, a Python
integration landing page, and clearer deployment and Metrics v1 guidance.

## v0.5.1 (2026-09-30)

- Improved 7-day and 30-day dashboard chart performance with bounded sampling.
Expand Down
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[![GitHub release](https://img.shields.io/github/v/release/PVRLabs/statlite)](https://github.com/PVRLabs/statlite/releases)
[![Go powered](https://img.shields.io/github/go-mod/go-version/PVRLabs/statlite?label=Go%20powered&logo=go&logoColor=white)](go.mod)
[![Frameworks: Spring Boot · Quarkus](https://img.shields.io/badge/Frameworks-Spring%20Boot%20%C2%B7%20Quarkus-e7e7e7?labelColor=333)](docs/integrations.md)
[![Frameworks: Spring Boot · Quarkus · Micronaut](https://img.shields.io/badge/Frameworks-Spring%20Boot%20%C2%B7%20Quarkus%20%C2%B7%20Micronaut-e7e7e7?labelColor=333)](docs/integrations.md)
[![CI](https://github.com/PVRLabs/statlite/actions/workflows/test.yml/badge.svg)](https://github.com/PVRLabs/statlite/actions/workflows/test.yml)
[![License](https://img.shields.io/github/license/PVRLabs/statlite)](LICENSE)

Expand All @@ -21,7 +21,7 @@ charts, without requiring Prometheus or Grafana.
<br><sub>Main application dashboard for the Spring target.</sub>
</p>

StatLite supports Spring Boot and Quarkus integrations, and other
StatLite supports Spring Boot, Quarkus, and Micronaut integrations, and other
applications through [a small, fixed JSON metrics endpoint](docs/statlite-metrics-v1.md).
It collects traffic, latency, CPU, memory, optional application health, and
optional host metrics. Metrics and history stay on your server, without
Expand Down Expand Up @@ -114,11 +114,13 @@ For other supported frameworks, select the type explicitly when needed:

```bash
statlite inspect --type quarkus 'http://localhost:9000'
statlite inspect --type micronaut 'http://localhost:8080'
```

Inspection checks conventional supported endpoints and is bounded and
read-only. For untyped discovery, start with a base HTTP or HTTPS URL without a
query string or fragment.
read-only. Micronaut requires `--type micronaut`; inspection validates its
supported contract without proving framework identity. For untyped discovery, start with
a base HTTP or HTTPS URL without a query string or fragment.

See [Configuration](docs/configuration.md) for exact endpoint forms, discovery
limits, authentication limitations, all settings, and manual target
Expand All @@ -137,14 +139,19 @@ When a target has no health signal, the dashboard reports whether StatLite is
successfully receiving its metrics without treating reachability as application
health.

- **Spring Boot:** Collects authoritative health when Actuator health is
available and automatically selects a compatible Micrometer Prometheus
- **[Spring Boot](docs/targets/spring.md):** Collects authoritative health
when Actuator health is available and automatically selects a compatible Micrometer Prometheus
endpoint or Actuator JSON for request, JVM, process, and optional host
metrics. Independently usable metrics remain reportable if health retrieval
fails.
- **Quarkus Micrometer:** Collects bounded request, latency, CPU, heap, process,
and restart concepts from an exact Prometheus/OpenMetrics endpoint. SmallRye
- **[Quarkus Micrometer](docs/targets/quarkus.md):** Collects bounded request,
latency, CPU, heap, process, and restart concepts from an exact Prometheus/OpenMetrics endpoint. SmallRye
Health is an optional capability when the application publishes it.
- **[Micronaut Micrometer](docs/targets/micronaut.md):** Collects the existing request,
duration, CPU, heap, process, and restart concepts from an exact configured Prometheus endpoint,
conventionally `/prometheus`. Management health is optional; database health
requires visible JDBC aggregate status.
See the [certified setup](docs/targets/micronaut.md).
- **[StatLite Metrics v1](docs/statlite-metrics-v1.md):** A small, fixed JSON
endpoint that applications in any language or framework can implement. See
the [direct integration guides](docs/integrate/) for FastAPI, Express,
Expand Down
19 changes: 14 additions & 5 deletions cmd/statlite/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func runInspectWithTyped(args []string, stdout, stderr io.Writer, inspectApplica
inspectFlags.Usage = func() {
printInspectHelp(stderr)
}
typed := inspectFlags.String("type", "", "inspect a specific target type (currently: quarkus)")
typed := inspectFlags.String("type", "", "inspect a specific target type (currently: quarkus, micronaut)")
name := inspectFlags.String("name", "", "target name (default: derived from type, host, and port)")
createPath := inspectFlags.String("create-config", "", "create a new configuration file at PATH")
addPath := inspectFlags.String("add-to-config", "", "append a target to an existing configuration file at PATH")
Expand Down Expand Up @@ -330,13 +330,14 @@ func noPollStartupMessages(hasStoredPoll bool) []string {
func printHelp(w io.Writer) {
fmt.Fprintf(w, `StatLite - tiny self-hosted metrics dashboard for small servers.

Polls Spring Boot Actuator and StatLite self-monitoring endpoints, stores
samples in local SQLite, and serves a localhost dashboard.
Polls Spring Boot Actuator, Quarkus, Micronaut, and StatLite Metrics endpoints,
stores samples in local SQLite, and serves a localhost dashboard.

Usage:
statlite [--config path] [--no-poll] [--raw-series]
statlite inspect <application-url>
statlite inspect --type quarkus <application-or-metrics-url>
statlite inspect --type micronaut <application-or-metrics-url>
statlite inspect <application-url> --create-config PATH
statlite inspect <application-url> --add-to-config PATH
statlite --version
Expand All @@ -359,6 +360,7 @@ func printInspectHelp(w io.Writer) {

Example:
statlite inspect 'http://localhost:8080'
statlite inspect --type micronaut 'http://localhost:8080/prometheus'
statlite inspect 'http://localhost:8080' --create-config ./statlite.yaml
statlite inspect 'http://localhost:8080' --add-to-config ./statlite.yaml

Expand All @@ -375,7 +377,12 @@ Prometheus/OpenMetrics endpoint. A base URL uses the conventional /q/metrics pat

Quote the URL when pasting it from a browser, especially if it contains ? or &.
Untyped inspection requires a base URL, so remove any query string or fragment first.
Typed Quarkus inspection accepts a base URL or exact metrics endpoint URL.`)
Typed Quarkus inspection accepts a base URL or exact metrics endpoint URL.
Use --type micronaut with a base URL or exact Prometheus endpoint. A root base
URL resolves to /prometheus; non-root paths allow one context fallback after a
conclusive miss. URLs with a query are exact endpoints. Micronaut inspection
checks compatibility without proving framework identity; untyped inspection
does not recognize Micronaut.`)
}

const configurationDocsURL = "https://github.com/PVRLabs/statlite/blob/main/docs/configuration.md"
Expand Down Expand Up @@ -457,6 +464,8 @@ func inspectionPresentation(targetType inspect.TargetType) (inspectionTargetPres
targetType: config.TargetTypeStatliteMetrics,
errorContext: "statlite-metrics target",
}, nil
case inspect.TargetMicronaut:
return inspectionTargetPresentation{displayName: "Micronaut Metrics", targetType: config.TargetTypeMicronaut, errorContext: "micronaut target"}, nil
case inspect.TargetQuarkus:
return inspectionTargetPresentation{
displayName: "Quarkus Metrics",
Expand Down Expand Up @@ -511,7 +520,7 @@ func printInspectFailure(w io.Writer, err error) {
case inspect.FailureMultiple:
fmt.Fprintln(w, "inspect: more than one supported integration was found")
case inspect.FailureIncompatible:
fmt.Fprintf(w, "inspect: the configured Quarkus metrics endpoint is incompatible: %v\n", failure.Err)
fmt.Fprintf(w, "inspect: the configured metrics endpoint is incompatible: %v\n", failure.Err)
case inspect.FailureTypeUnsupported, inspect.FailureTypeUnavailable:
fmt.Fprintf(w, "inspect: %v\n", failure)
default:
Expand Down
54 changes: 52 additions & 2 deletions cmd/statlite/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,8 +434,8 @@ func TestRunInspectTypeErrorsUseAccurateUsageMessages(t *testing.T) {
targetType string
want string
}{
{targetType: "prometheus", want: `unsupported inspection type "prometheus" (supported: quarkus)`},
{targetType: "spring", want: `typed inspection type "spring" is not available (supported: quarkus)`},
{targetType: "prometheus", want: `unsupported inspection type "prometheus" (supported: quarkus, micronaut)`},
{targetType: "spring", want: `typed inspection type "spring" is not available (supported: quarkus, micronaut)`},
}
for _, tt := range tests {
t.Run(tt.targetType, func(t *testing.T) {
Expand Down Expand Up @@ -707,3 +707,53 @@ func TestRunExplicitAndMalformedConfigDoNotSuggestInspect(t *testing.T) {
t.Fatalf("stderr = %q, explicitly supplied default config must not get inspect suggestion", stderr.String())
}
}

func TestRenderInspectionMicronautOutputRoundTripsExactEndpoint(t *testing.T) {
result := &inspect.Result{
TargetType: inspect.TargetMicronaut,
Endpoint: "http://localhost:9000/prometheus/?scope=app",
Status: inspect.CompatibilityCompatible,
Capabilities: []string{"process_cpu_usage", "jvm_heap_used_bytes"},
}

got, err := renderInspection(result)
if err != nil {
t.Fatalf("renderInspection() error = %v", err)
}
for _, want := range []string{
"Detected: Micronaut Metrics",
"Compatibility: compatible",
"type: micronaut",
"url: http://localhost:9000/prometheus/?scope=app",
"process_cpu_usage",
"jvm_heap_used_bytes",
} {
if !strings.Contains(got, want) {
t.Fatalf("renderInspection() = %q, missing %q", got, want)
}
}
assertSuggestedConfigLoads(t, got, config.TargetTypeMicronaut, "http://localhost:9000/prometheus/?scope=app")
}

func TestRunTypedMicronautInspectDispatchesOnlyToRequestedTarget(t *testing.T) {
var stdout, stderr bytes.Buffer
var untypedCalls, typedCalls int
code := runWithInspectors([]string{"inspect", "--type", "micronaut", "http://app.test/prometheus?scope=app"}, &stdout, &stderr,
func(context.Context, string) (*inspect.Result, error) {
untypedCalls++
return nil, errors.New("untyped inspector must not run")
},
func(_ context.Context, targetType inspect.TargetType, endpoint string) (*inspect.Result, error) {
typedCalls++
if targetType != inspect.TargetMicronaut || endpoint != "http://app.test/prometheus?scope=app" {
t.Fatalf("typed inspection arguments = %q, %q", targetType, endpoint)
}
return &inspect.Result{TargetType: inspect.TargetMicronaut, Endpoint: endpoint, Status: inspect.CompatibilityPartial, Capabilities: []string{"jvm_heap_used_bytes"}}, nil
})
if code != 0 || untypedCalls != 0 || typedCalls != 1 {
t.Fatalf("code=%d untyped=%d typed=%d stdout=%q stderr=%q", code, untypedCalls, typedCalls, stdout.String(), stderr.String())
}
if !strings.Contains(stdout.String(), "Detected: Micronaut Metrics") || !strings.Contains(stdout.String(), "type: micronaut") || !strings.Contains(stdout.String(), "Compatibility: partial") {
t.Fatalf("stdout = %q, want typed Micronaut output", stdout.String())
}
}
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ application and dependency health for the selected target.
| Field | Meaning |
|---|---|
| `target` | Configured target name. |
| `type` | Canonical integration type, such as `spring`, `quarkus`, or `statlite-metrics`. |
| `type` | Canonical integration type, such as `spring`, `quarkus`, `micronaut`, or `statlite-metrics`. |
| `collection_status` | `not_polled` before any poll, `ok` after a successful latest poll, or `error` when the latest poll failed. |
| `last_poll_at` | Completion time of the latest collection attempt, or `null`. |
| `last_successful_poll_at` | Completion time of the latest successful collection, or `null`. |
Expand Down
Loading
Loading