Claude
Skills
Sign in
Back

ring:using-lib-observability

Included with Lifetime
$97 forever

Using lib-observability v1.0.0, Lerian's OpenTelemetry foundation (lib-commons, lib-systemplane, lib-streaming depend on it), in two modes. Sweep Mode detects DIY zap/slog logging, raw OTel metrics, hand-rolled redaction, and hard-coded attribute strings. Reference Mode catalogs the log, metrics, zap, redaction, and constants packages. Go-only. Skip for non-Go or assert/runtime/tracing.

General

What this skill does


# ring:using-lib-observability

## When to use
Sweep mode:
- "Sweep the codebase for lib-observability opportunities"
- "Find where we use raw zap / slog instead of lib-observability/log"
- "Audit OTel metric collectors and replace with MetricsFactory"
- "Find hand-rolled redaction or hard-coded OTel attribute strings"

Reference mode:
- "What does lib-observability provide for X?"
- "How do I initialize the logger for production vs local?"
- "What's the right way to create a counter / gauge / histogram?"
- "Which constants ship for OTel attributes/metric names/event names?"
- "How does redaction.IsSensitiveField decide what to mask?"

## Skip when
- Working on non-Go services
- Working on frontend code
- Target codebase is Ring itself (no lib-observability dependency)
- Target package is `assert/`, `runtime/`, or `tracing/` (see Related)

## Related
**Similar:** ring:using-assert, ring:using-runtime, ring:using-tracing, ring:using-lib-commons

`assert/` is owned by [[using-assert]] (production assertions).
`runtime/` is owned by [[using-runtime]] (panic recovery telemetry).
`tracing/` is owned by [[using-tracing]] (OTel trace SDK + processor).

This skill covers the foundation layer only: `log`, `metrics`, `zap`, `redaction`, `constants`.

---

## Mode Selection

| Request Shape | Mode |
|---|---|
| "Sweep / audit / find DIY observability" | **Sweep** |
| "Replace our zap.New / slog setup with lib-observability" | **Sweep** |
| "What logger interface do we use?" | **Reference** |
| "How do I build a Counter with attributes?" | **Reference** |
| "What constants exist for `db.system` etc?" | **Reference** |

---

# SWEEP MODE

Orchestrate a 3-phase sweep. Each phase has a hard gate — do not proceed until the current phase produces its artifact.

```
Phase 1: Version Reconnaissance  → version-report.json
Phase 2: Multi-Angle DIY Sweep   → 5 × libobs-sweep-{N}-{angle}.json
Phase 3: Consolidated Report     → libobs-sweep-report.md + tasks.json
```

## Phase 1: Version Reconnaissance

1. Read `go.mod` — extract pinned version of `github.com/LerianStudio/lib-observability`
2. WebFetch `https://api.github.com/repos/LerianStudio/lib-observability/releases/latest` — extract `tag_name`
3. Classify drift: up-to-date / minor-drift / moderate-drift / major-upgrade / not-pinned
4. Emit `version-report.json`: `{pinned_version, latest_version, drift_classification, module_path}`

If the target does not depend on `lib-observability` directly but depends on `lib-commons v5+`, note that lib-observability is pulled in transitively — sweep findings still apply.

## Phase 2: Multi-Angle DIY Sweep

### ⛔ STOP-CHECK BEFORE DISPATCH

Before emitting any Task call, count the explorers you intend to launch in this turn.
- Count MUST equal 5.
- If count < 5 → STOP. Do not partial-dispatch. Reconcile against the 5 angles below and try again.
- The 5 angles are the canonical sweep. No substitutions, no omissions.

### ⛔ MUST NOT trickle-dispatch

All 5 explorers leave in the SAME TURN, before reading any explorer output.

Forbidden sequences:
- Dispatch explorer 1 → read result → dispatch explorer 2
- Dispatch a subset → wait → dispatch the rest
- Dispatch follow-up explorers conditioned on partial output
- Loop sequentially over the angle list

If you find yourself about to dispatch an explorer in a turn AFTER any explorer has already returned a result → STOP. You violated parallel dispatch. Report the violation and mark the phase INCOMPLETE rather than completing the trickle.

### Self-verify after dispatch

After the dispatch turn, verify all 5 Task calls were emitted in that single turn. If fewer than 5 went out, the phase did NOT execute correctly. Mark INCOMPLETE and surface the dispatch failure — do NOT silently continue with a partial pool.

### Parallel dispatch — atomic batch

Emit all 5 Task calls in a SINGLE TURN, as one atomic batch.

**If your runtime exposes a `multi_tool_use.parallel` wrapper**, use it to dispatch the complete pool in one wrapped invocation. This is the canonical fan-out mechanism on OpenAI-style tool envelopes and on certain Anthropic SDK consumers — naming it explicitly activates parallel emission on runtimes where trickle-dispatch is the default behavior.

**If your runtime emits parallel tool_use blocks natively** (Claude Code with Claude models), `multi_tool_use.parallel` may not be needed — but naming it is harmless and serves as an enforcement anchor.

The STOP-CHECK, anti-trickle, and self-verify guards above remain binding regardless of which mechanism your runtime uses.

Dispatch all 5 explorer angles in **one batch** (`subagent_type: ring:codebase-explorer`).

Per-explorer dispatch prompt:

```
## Target
<absolute path to target repo root>

## Your Angle
<angle number + name from the catalog below>

## DIY Patterns / Replacement / Severity / Migration Complexity
<verbatim from this file for this angle>

## Output
Write findings to: /tmp/libobs-sweep-{N}-{angle-slug}.json
Schema: { angle_number, angle_name, severity, migration_complexity,
          findings: [{file, line, diy_pattern, replacement, evidence_snippet, notes}],
          summary }
If no findings: empty findings array, summary "No DIY patterns detected for this angle".
```

### Angle 1: Raw zap / slog logger setup

**Severity:** HIGH
**Migration Complexity:** moderate

**DIY Patterns to Detect:**
- `zap.NewProduction(`, `zap.NewDevelopment(`, `zap.NewExample(`, `zap.New(zapcore.NewCore(...))` outside `lib-observability/zap`
- `zap.Config{...}.Build(`, `zap.NewProductionConfig(`, `zap.NewDevelopmentConfig(` in service code
- `slog.New(`, `slog.NewJSONHandler(`, `slog.NewTextHandler(` in service code
- `log.New(` (stdlib) used for structured logging
- Service-owned environment switches (`if env == "prod" { ... } else { ... }`) wrapping zap config
- Hand-wired `otelzap.NewCore(` for trace correlation

**lib-observability Replacement:**
- `zap.New(zap.Config{Environment: zap.EnvironmentProduction, Level: "info", OTelLibraryName: "my-service"})` — returns `*zap.Logger` that implements `log.Logger`
- For tests: `log.NewNop()`
- For lightweight stdlib-style: `&log.GoLogger{Level: log.LevelInfo}` (implements `log.Logger`, includes CWE-117 sanitization)

**Why:** lib-observability/zap auto-injects `trace_id` and `span_id` from `ctx`, bridges to OTel Logs SDK via otelzap, applies CWE-117 control-char sanitization for console encoding, and exposes a runtime-adjustable `AtomicLevel` for hot reload.

**Evidence grep:**
```
grep -rn "zap\.NewProduction\|zap\.NewDevelopment\|zap\.NewProductionConfig\|slog\.New\|otelzap\.NewCore" --include="*.go"
```

### Angle 2: Raw OpenTelemetry metric instruments

**Severity:** HIGH
**Migration Complexity:** moderate

**DIY Patterns to Detect:**
- `otel.Meter(` followed by `Int64Counter(`, `Int64Gauge(`, `Int64Histogram(`, `Float64*(` in service code
- `meter.Int64Counter(`, `meter.Int64Histogram(` called directly without going through a factory
- Package-level `var counter, _ = meter.Int64Counter(...)` (no error handling, no caching coordination)
- Hand-rolled `sync.Map` or `map[string]metric.Int64Counter` caches for instrument reuse
- `metric.WithAttributes(attribute.String(...))` chains repeated inline per call site

**lib-observability Replacement:**
- `metrics.NewMetricsFactory(meter, logger)` — single factory, thread-safe lazy instrument cache via `sync.Map`
- `factory.Counter(metrics.Metric{...}).WithAttributes(...).AddOne(ctx)` — fluent builder
- `factory.Gauge(...)`, `factory.Histogram(...)` follow the same pattern
- For tests / nil-meter fallback: `metrics.NewNopFactory()`
- Histograms auto-select default buckets based on name substring (`latency`/`duration`/`time` → `DefaultLatencyBuckets`)

**Why:** the factory enforces error returns on negative counter values, prevents instrument re-registration, and offers `Builder.WithLabels(map[string]string)` for less-typed call sites. Direct OTel instruments leak nil-meter panics and have no shared c

Related in General