agentsclimarketplace

Kora telemetry metrics

Skill kora-projects/kora-skills/plugins/kora-v1/skills/kora-telemetry-metrics

Agent Skills for Kora Framework — compile-time DI for Java/Kotlin backend development.

Install
npx -y skills add kora-projects/kora-skills --skill kora-telemetry-metrics

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Kora Micrometer metrics via the micrometer-module — MetricsModule wires a PrometheusMeterRegistry and a MeterRegistry into the graph for custom Counter/Gauge/Timer/DistributionSummary, exposed in Prometheus format on the private HTTP port (privateApiHttpMetricsPath). Covers MetricsConfig (metrics.opentelemetrySpec V120/V123), per-module telemetry.metrics.slo buckets, PrometheusMeterRegistryInitializer for common tags, and tag-cardinality safety. Use when adding business metrics, configuring the /metrics scrape endpoint, tuning latency buckets, or debugging missing/high-cardinality metrics.

SKILL.md

12.8 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it

Kora Telemetry Metrics

Kora collects metrics with Micrometer. Adding MetricsModule registers a PrometheusMeterRegistry, instruments every Kora module (HTTP, gRPC, JDBC, Kafka, cache, JVM), and publishes a MeterRegistry component you inject to record custom business metrics. Metrics are served in Prometheus text format on the private HTTP port — never the public one.


Quick Start

1. Add the dependency

The micrometer-module already brings the PrometheusMeterRegistry; no separate registry artifact is needed. All ru.tinkoff.kora:* versions come from the kora-parent BOM — never pin them individually.

===! ":fontawesome-brands-java: Java"

```groovy
dependencies {
    koraBom platform("ru.tinkoff.kora:kora-parent:1.2.17")
    annotationProcessor "ru.tinkoff.kora:annotation-processors"

    implementation "ru.tinkoff.kora:micrometer-module"
    implementation "ru.tinkoff.kora:http-server-undertow"
    implementation "ru.tinkoff.kora:config-hocon"
    implementation "ru.tinkoff.kora:logging-logback"
}
```

=== ":simple-kotlin: Kotlin"

```kotlin
dependencies {
    koraBom(platform("ru.tinkoff.kora:kora-parent:1.2.17"))
    ksp("ru.tinkoff.kora:symbol-processors")

    implementation("ru.tinkoff.kora:micrometer-module")
    implementation("ru.tinkoff.kora:http-server-undertow")
    implementation("ru.tinkoff.kora:config-hocon")
    implementation("ru.tinkoff.kora:logging-logback")
}
```

2. Add MetricsModule to the graph

===! ":fontawesome-brands-java: Java"

```java
import ru.tinkoff.kora.application.graph.KoraApplication;
import ru.tinkoff.kora.common.KoraApp;
import ru.tinkoff.kora.config.hocon.HoconConfigModule;
import ru.tinkoff.kora.http.server.undertow.UndertowHttpServerModule;
import ru.tinkoff.kora.logging.logback.LogbackModule;
import ru.tinkoff.kora.micrometer.module.MetricsModule;

@KoraApp
public interface Application extends
        HoconConfigModule,
        LogbackModule,
        MetricsModule,
        UndertowHttpServerModule {

    static void main(String[] args) {
        KoraApplication.run(ApplicationGraph::graph);
    }
}
```

=== ":simple-kotlin: Kotlin"

```kotlin
import ru.tinkoff.kora.common.KoraApp
import ru.tinkoff.kora.config.hocon.HoconConfigModule
import ru.tinkoff.kora.http.server.undertow.UndertowHttpServerModule
import ru.tinkoff.kora.logging.logback.LogbackModule
import ru.tinkoff.kora.micrometer.module.MetricsModule

@KoraApp
interface Application :
    HoconConfigModule,
    LogbackModule,
    MetricsModule,
    UndertowHttpServerModule
```

3. Expose the scrape endpoint on the private port

httpServer {
  publicApiHttpPort = 8080   # business traffic
  privateApiHttpPort = 8085  # metrics, probes, admin
  privateApiHttpMetricsPath = "/metrics"
}

metrics {
  opentelemetrySpec = "V120"  # or "V123"
}

Scrape and verify:

curl http://localhost:8085/metrics

4. Record a custom metric

Inject MeterRegistry through the constructor (it is a normal graph component) and register meters once.

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import ru.tinkoff.kora.common.Component;

import java.time.Duration;

@Component
public final class MetricsService {

    private final Timer userCreationTimer;
    private final Counter userCreationCounter;

    public MetricsService(MeterRegistry meterRegistry) {
        this.userCreationTimer = Timer.builder("user.creation.duration")
                .description("Time taken to create users")
                .serviceLevelObjectives(
                        Duration.ofMillis(50),
                        Duration.ofMillis(100),
                        Duration.ofMillis(250),
                        Duration.ofMillis(500))
                .register(meterRegistry);
        this.userCreationCounter = Counter.builder("user.creation.total")
                .description("Total number of users created")
                .register(meterRegistry);
    }

    public void createUser(Runnable work) {
        userCreationTimer.record(work);
        userCreationCounter.increment();
    }
}

What's in references/ and assets/

FilePurpose
references/micrometer-types-reference.mdChoosing Counter / Gauge / Timer / DistributionSummary, builder APIs
references/custom-metrics-reference.mdBusiness-metric patterns, dynamic-tag caching, full payment example
references/metrics-config-reference.mdmetrics.opentelemetrySpec, PrometheusMeterRegistryInitializer, per-module telemetry.metrics
references/metrics-cardinality-reference.mdGood vs bad tags, memory-leak prevention, cardinality checklist
references/metrics-export-reference.mdPrometheus pull model, scrape config, verification
references/metrics-reference.mdCatalogue of built-in Kora metrics with real OpenTelemetry tag names
assets/Application, config, service, and monitoring templates

Metric Types

TypeUse CaseExample
CounterMonotonically increasing event countCounter.builder("requests.total").register(registry).increment()
GaugeCurrent point-in-time valueGauge.builder("queue.size", queue, Queue::size).register(registry)
TimerDuration / latencyTimer.builder("request.duration").register(registry).record(() -> {...})
DistributionSummaryDistribution of arbitrary values (sizes)summary.record(payloadBytes)

See Micrometer Types Reference.


Common tags for all metrics

To stamp every metric with shared labels, add a PrometheusMeterRegistryInitializer as a default method in a @Module. It is a Function<PrometheusMeterRegistry, PrometheusMeterRegistry>, so it must return the registry. It is applied exactly once at application start.

===! ":fontawesome-brands-java: Java"

```java
import ru.tinkoff.kora.common.Module;
import ru.tinkoff.kora.micrometer.module.PrometheusMeterRegistryInitializer;

@Module
public interface MetricsConfigModule {
    default PrometheusMeterRegistryInitializer commonTagsInit() {
        return registry -> {
            registry.config().commonTags("service", "order-service", "environment", "production");
            return registry;
        };
    }
}
```

=== ":simple-kotlin: Kotlin"

```kotlin
import ru.tinkoff.kora.common.Module
import ru.tinkoff.kora.micrometer.module.PrometheusMeterRegistryInitializer

@Module
interface MetricsConfigModule {
    fun commonTagsInit(): PrometheusMeterRegistryInitializer {
        return PrometheusMeterRegistryInitializer { registry ->
            registry.config().commonTags("service", "order-service", "environment", "production")
            registry
        }
    }
}
```

Histogram buckets (SLO targets)

Bucketed measurements let monitoring compute "how many calls were under 100 ms?" and percentile estimates. Configure them with serviceLevelObjectives(...) on the builder; the values are your business latency targets, not universal defaults.

Timer.builder("api.request.duration")
    .serviceLevelObjectives(
        Duration.ofMillis(50),
        Duration.ofMillis(100),
        Duration.ofMillis(250),
        Duration.ofMillis(500),
        Duration.ofSeconds(1))
    .register(registry);

Built-in Kora module metrics expose the same control through config as a millisecond array: <module>.telemetry.metrics.slo = [ 1, 10, 50, 100, 200, 500, 1000, ... ]. See Metrics Config Reference.


Tag cardinality (critical)

Each unique tag-value combination creates a separate time series. Tag values must be a bounded, finite set, otherwise the registry grows without limit and the process leaks memory.

Safe tags (bounded)Dangerous tags (unbounded)
method (GET, POST)userId
status (200, 404, 500)requestId (UUID per call)
http.route template (/users/{id})raw path (/users/128734)
provider (gmail.com, yahoo.com)email (one per user)

For a runtime tag with a bounded value set, cache the meter per value instead of rebuilding it on every call:

private final ConcurrentHashMap<String, Counter> counters = new ConcurrentHashMap<>();

private Counter counter(String provider) {
    return counters.computeIfAbsent(provider, p ->
        Counter.builder("user.creation.total")
            .tag("email.provider", p)
            .register(registry));
}

See Cardinality Reference and Custom Metrics Reference.


Export model — Prometheus pull only

Kora's micrometer-module registers a PrometheusMeterRegistry and serves it on privateApiHttpMetricsPath. The export model is pull: a Prometheus server scrapes the private port. There is no Kora configuration for switching the registry to a push exporter (StatsD/OTLP/etc.) — to ship to another backend, scrape the endpoint with an agent (Prometheus, OpenTelemetry Collector, vmagent) and forward from there. For distributed tracing export, use the separate tracing modules (kora-telemetry-tracing), which are unrelated to the metric registry.

# prometheus.yml
scrape_configs:
  - job_name: 'order-service'
    scrape_interval: 15s
    metrics_path: '/metrics'
    static_configs:
      - targets: ['order-service:8085']

See Metrics Export Reference.


Built-in Kora metrics

After MetricsModule is connected, Kora instruments its modules automatically using OpenTelemetry semantic-convention names and tags.

ModuleKey metricType
HTTP Serverhttp.server.request.duration, http.server.active_requestsDistributionSummary, Gauge
HTTP Clienthttp.client.request.durationDistributionSummary
Databasedb.client.request.durationDistributionSummary
Kafkamessaging.receive.duration, messaging.publish.duration, messaging.kafka.consumer.lagDistributionSummary, Gauge
gRPCrpc.server.duration, rpc.client.durationDistributionSummary
Cachecache.duration, cache.ratioDistributionSummary, Counter
Systemkora.up, jvm.memory.used, jvm.gc.pause, process.cpu.usageGauge / DistributionSummary

The full catalogue with exact tag names is in Metrics Reference.


Common pitfalls

SymptomFix
/metrics returns 404Add the HTTP server module and set privateApiHttpMetricsPath; scrape the private port
Metrics not appearingConfirm MetricsModule is in @KoraApp extends ... and MeterRegistry is injected
Memory grows continuouslyUnbounded tag (userId, requestId, raw URL) — drop it or map to a bounded value
Initializer doesn't compilePrometheusMeterRegistryInitializer must return the registry
Wrong metric typeCounter/Gauge/Timer/DistributionSummary — choose based on metric semantics
Metrics on the public portAlways keep /metrics on privateApiHttpPort
Looking for probes moduleNo such module — probes are in HTTP server module (see Probes Reference)

Verification

After build (annotation processors generate ApplicationGraph), run the service and:

curl -s http://localhost:8085/metrics | grep kora_up

kora_up 1.0 confirms the framework is running and the registry is exposed.

Gives 0 of the 12 instructions most monitoring observability skills give in ~2.8k tokens

Counted across 481 of the 483 authors here whose files we hold, read 2026-08-06

  • link every alert to a runbookin 43 of 481, across 35 files
  • use structured json loggingin 36 of 481, across 31 files
  • alert on user-facing symptomsin 20 of 481, across 15 files
  • emit structured JSON logs with stable event namesin 18 of 481, across 13 files
  • propagate trace context across boundariesin 16 of 481
  • use histograms for latency trackingin 14 of 481, across 9 files
  • use OpenTelemetry for distributed tracingin 13 of 481, across 8 files
  • include a correlation ID on every log linein 13 of 481, across 8 files
  • Define service level objectivesin 10 of 481, across 7 files
  • Call useAzureMonitor before importing other modulesin 9 of 481, across 2 files
  • stop and ask for clarification if inputs are missingin 9 of 481, across 2 files
  • define on-call questions before adding telemetryin 9 of 481, across 4 files

Said here and by no other author read

  • add MetricsModule to the application graph
  • serve metrics on the private http port
  • set privateApiHttpMetricsPath to the scrape path
  • inject MeterRegistry to record custom metrics
  • register custom meters exactly once
  • configure histogram buckets using service level objectives

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.