adding-a-metric by microbus-io
TRIGGER when user asks to add or modify a metric, counter, gauge, or histogram, or to track/measure an operation. Affects intermediate.go, manifest.yaml. Do NOT manually define metrics - this skill wires up recorders and OpenTelemetry names.
Content & Writing
169 Stars
5 Forks
Updated May 15, 2026, 08:01 AM
Why Use This
This skill provides specialized capabilities for microbus-io's codebase.
Use Cases
- Developing new features in the microbus-io repository
- Refactoring existing code to follow microbus-io standards
- Understanding and working with microbus-io's codebase structure
Install Guide
2 steps- 1
Skip this step if Ananke is already installed.
- 2
Skill Snapshot
Auto scan of skill assets. Informational only.
Valid SKILL.md
Checks against SKILL.md specification
Source & Community
Skill Stats
SKILL.md 273 Lines
Total Files 1
Total Size 0 B
License NOASSERTION
---
name: add-metric
description: TRIGGER when user asks to add or modify a metric, counter, gauge, or histogram, or to track/measure an operation. Affects intermediate.go, manifest.yaml. Do NOT manually define metrics - this skill wires up recorders and OpenTelemetry names.
---
**CRITICAL**: Do NOT explore or analyze other microservices unless explicitly instructed to do so. The instructions in this skill are self-contained to this microservice.
**CRITICAL**: Do not omit the `MARKER` comments when generating the code. They are intended as waypoints for future edits.
## Workflow
Copy this checklist and track your progress:
```
Creating or modifying a metric:
- [ ] Step 1: Read local CLAUDE.md file
- [ ] Step 2: Determine kind
- [ ] Step 3: Determine if observable just in time
- [ ] Step 4: Determine signature
- [ ] Step 5: Determine OpenTelemetry name
- [ ] Step 6: Extend the ToDo interface
- [ ] Step 7: Describe the metric
- [ ] Step 8: Implement the recorders
- [ ] Step 9: Use the metric
- [ ] Step 10: Observe with callback
- [ ] Step 11: Regenerate the mock
- [ ] Step 12: Test the callback
- [ ] Step 13: Housekeeping
```
#### Step 1: Read Local `CLAUDE.md` File
Read the local `CLAUDE.md` file in the microservice's directory. It contains microservice-specific instructions that should take precedence over global instructions.
#### Step 2: Determine Kind
Determine if the metric is a:
- **Counter** - an ever increasing number
- **Gauge** - a number that can go up or down
- **Histogram** - a distribution of cumulative values over time
If a histogram, determine the boundaries that determine its buckets.
#### Step 3: Determine If Observable Just in Time
Some metrics, and in particular gauges, it is enough to observe just in time before recording. For example, there's no reason to sample the available system memory constantly if only the last sampling will be reported. Determine if this metric is observable.
#### Step 4: Determine Signature
Determine the Go signature of the metric.
```go
func MyMetric(ctx context.Context, value int, label1 string, label2 string) (err error)
```
Constraints:
- The first input argument must be `ctx context.Context`
- The function must return an `err error`
- The second input argument is the numerical value of the metric and must be of type `int`, `float64` or `time.Duration`
- The remaining input arguments are labels and should be a `string` or any type that can be converted to a string
#### Step 5: Determine OpenTelemetry Name
The OpenTelemetry name of the metric:
- Should have a (single-word) application prefix relevant to the domain the metric belongs to. The prefix is usually the application name itself
- Should have a suffix describing the unit, in plural form
For example, `myapplication_my_metric_units`.
#### Step 6: Extend the `ToDo` interface
Skip this step if the metric is not observable just in time.
Extend the `ToDo` interface in `intermediate.go`.
```go
type ToDo interface {
// ...
OnObserveMyMetric(ctx context.Context) (err error) // MARKER: MyMetric
}
```
#### Step 7: Describe the Metric
Describe the metric in `NewIntermediate` in `intermediate.go`, after the corresponding `HINT` comment. If other metric descriptions already exist under this HINT, add the new one after the last existing metric description.
- Use snake_case for the metric alias
- If the metric has a unit such as `seconds` or `mb`, append it to the alias
- Replace `MyMetric counts X` with the description of the metric
If a histogram:
```go
func NewIntermediate(impl ToDo) *Intermediate {
// ...
svc.DescribeHistogram("myapplication_my_metric_units", "MyMetric counts X", []float64{1, 2, 5, 10, 100}) // MARKER: MyMetric
}
```
If a gauge:
```go
func NewIntermediate(impl ToDo) *Intermediate {
// ...
svc.DescribeGauge("myapplication_my_metric_units", "MyMetric counts X") // MARKER: MyMetric
}
```
If a counter:
```go
func NewIntermediate(impl ToDo) *Intermediate {
// ...
svc.DescribeCounter("myapplication_my_metric_units", "MyMetric counts X") // MARKER: MyMetric
}
```
#### Step 8: Implement the Recorders
Append the recording methods at the end of `intermediate.go`.
- Cast `int` values to `float64`
- Convert `time.Duration` values to seconds using `dur.Seconds()`
- Set an appropriate comment that describes the metric
- Use snake_case for the metric alias and label names
- If the metric has a unit such as `seconds` or `mb`, append it to the alias
If the metric is a histogram, add the following code.
```go
/*
RecordMyMetric records X.
*/
func (svc *Intermediate) RecordMyMetric(ctx context.Context, value int, label1 string, label2 string) (err error) { // MARKER: MyMetric
return svc.RecordHistogram(ctx, "myapplication_my_metric_units", float64(value),
"label_1", utils.AnyToString(label1),
"label_2", utils.AnyToString(label2),
)
}
```
If the metric is a counter, add the following code.
```go
/*
IncrementMyMetric counts X.
*/
func (svc *Intermediate) IncrementMyMetric(ctx context.Context, value int, label1 string, label2 string) (err error) { // MARKER: MyMetric
return svc.IncrementCounter(ctx, "myapplication_my_metric_units", float64(value),
"label_1", utils.AnyToString(label1),
"label_2", utils.AnyToString(label2),
)
}
```
If the metric is a gauge, add the following code.
```go
/*
RecordMyMetric records X.
*/
func (svc *Intermediate) RecordMyMetric(ctx context.Context, value int, label1 string, label2 string) (err error) { // MARKER: MyMetric
return svc.RecordGauge(ctx, "myapplication_my_metric_units", float64(value),
"label_1", utils.AnyToString(label1),
"label_2", utils.AnyToString(label2),
)
}
```
Note that `utils.AnyToString` is defined in `github.com/microbus-io/fabric/utils`.
#### Step 9: Use the Metric
Skip this step if the metric is observable just in time.
Call `IncrementMyMetric` (if counter) or `RecordMyMetric` (if histogram or gauge) from the appropriate location in `service.go` where the metric should be recorded.
```go
svc.IncrementMyMetric(ctx, 1, "label1", "label2")
```
```go
svc.RecordMyMetric(ctx, value, "label1", "label2")
```
#### Step 10: Observe With Callback
Skip this step if the metric is not observable just in time.
Define a callback in `service.go` that calculates the metric and records it using `RecordMyMetric` (if histogram or gauge) or `IncrementMyMetric` (if counter).
```go
func (svc *Service) OnObserveMyMetric(ctx context.Context) (err error) { // MARKER: MyMetric
v, err := calculateMyMetric()
if err != nil {
return errors.Trace(err)
}
err = svc.RecordMyMetric(ctx, v, "label1", "label2")
return errors.Trace(err)
}
```
Call the `OnObserveMyMetric` callback in `doOnObserveMetrics` in `intermediate.go`.
```go
func (svc *Intermediate) doOnObserveMetrics(ctx context.Context) (err error) {
return svc.Parallel(
// ...
func() (err error) { return svc.OnObserveMyMetric(ctx) }, // MARKER: MyMetric
)
}
```
#### Step 11: Regenerate the Mock
Skip this step if the metric is not observable just in time.
Run `go run github.com/microbus-io/fabric/cmd/genmock --path .` from the microservice's directory.
#### Step 12: Test the Callback
Skip this step if the metric is not observable just in time.
Append the integration test to `service_test.go`.
```go
func TestMyService_OnObserveMyMetric(t *testing.T) { // MARKER: MyMetric
t.Parallel()
ctx := t.Context()
_ = ctx
// Initialize the microservice under test
svc := NewService()
// Run the testing app
app := application.New()
app.Add(
// HINT: Add microservices or mocks required for this test
svc,
)
app.RunInTest(t)
/*
HINT: Use the following pattern for each test case
t.Run("test_case_name", func(t *testing.T) {
assert := testarossa.For(t)
err := svc.OnObserveMyMetric(ctx)
assert.NoError(err)
})
*/
}
```
Skip the remainder of this step if instructed to be "quick" or to skip tests.
Insert test cases at the bottom of the integration test function using the recommended pattern.
- Do not remove the `HINT` comments.
```go
t.Run("test_case_name", func(t *testing.T) {
assert := testarossa.For(t)
err := svc.OnObserveMyMetric(ctx)
assert.NoError(err)
})
```
#### Step 13: Housekeeping
Follow the `housekeeping` skill.
Name Size