Conversation
Add README with comprehensive library description, update LICENSE to Elastic License 2.0, align .gitignore with lib-commons conventions, and add GitHub Actions workflows (lint, security, release), golangci-lint config, goreleaser config, semantic-release config, dependabot, and PR template. X-Lerian-Ref: 0x1
Initialize go.mod at github.com/LerianStudio/lib-observability with flat package layout (no pkg/ or redundant parent dir). Packages at module root: log, tracing, metrics, runtime, assert, zap, constants — each with doc.go. Includes Makefile with full test, lint, coverage, security, and release targets ported from lib-commons, plus scripts/ for shared make includes. X-Lerian-Ref: 0x1
Simplify .idea/* to .idea/ in .gitignore, move @semantic-release/exec to extra_plugins with exact version pin, and bump Go to 1.25.9 matching lib-commons. X-Lerian-Ref: 0x1
feat: bootstrap repository with CI/CD, linting, and release config
Add OpenTelemetry SDK, zap, gopsutil, shopspring/decimal, uuid and all transitive dependencies required by the migrated observability packages. X-Lerian-Ref: 0x1
Port all observability and telemetry packages from lib-commons to their new flat layout at module root. Packages migrated: constants (OTEL attrs, metric names, event names, headers), redaction (sensitive field detection, extensible via variadic extra fields), root observability package (context carriers: ContextWithTracer, ContextWithMetricFactory, ContextWithLogger, ContextWithSpanAttributes, AttributesFromContext; system metrics: GetCPUUsage, GetMemUsage), log (Logger interface, GoLogger with CWE-117 prevention, NopLogger, sanitizer), metrics (MetricsFactory with fluent Counter/Gauge/Histogram builders, domain recorders), runtime (panic recovery, PanicMetrics, RecordPanicToSpan, SafeGo, ErrorReporter), assert (Asserter, AssertionMetrics, financial predicates), tracing (OpenTelemetry SDK bootstrap, propagation, span helpers, redaction engine; package renamed from opentelemetry to tracing), zap (zap adapter with OTEL bridge and trace_id/span_id injection). X-Lerian-Ref: 0x1
X-Lerian-Ref: 0x1
…mons Ports withTelemetry, helpers, metrics collector, and context span helpers to the middleware/ package. Adds ContextWithHeaderID root helper required by the middleware. X-Lerian-Ref: 0x1
Ports emit_span, metrics, and metrics_recorders to the streaming/ package. Pure telemetry extraction with no franz-go/kafka dependency. X-Lerian-Ref: 0x1
Ports test files for assert, log, runtime, tracing, and zap packages. Remaps all imports from lib-commons paths to lib-observability and adds go:build unit tags. X-Lerian-Ref: 0x1
X-Lerian-Ref: 0x1
X-Lerian-Ref: 0x1
Addresses CVE GO-2026-4918 - infinite loop in HTTP/2 transport when given bad SETTINGS_MAX_FRAME_SIZE. X-Lerian-Ref: 0x1
Batch SetAttributes calls in SetExceptionSpanAttributes and SetDisputeSpanAttributes. Tighten UUID regex to strict 8-4-4-4-12 pattern. Add comment explaining null/nil string handling rationale. X-Lerian-Ref: 0x1
tracerName, emitSpanName, and setEmitSpanAttributes are defined but not yet called since the Emit() method has not been migrated. Suppress unused linter warnings with nolint and TODO comments. X-Lerian-Ref: 0x1
Remove concrete error type pin in assert test. Widen 1-second time boundaries to 1-minute. Restore global OTel propagator in test helper. Strengthen no-span and external-client span assertions. Fix global state mutation in parallel test. Pass actual nil contexts in nil-ctx tests. Strengthen production-mode panic assertion. Make otel_test hermetic and remove network-dependent assertion. Fix vacuous tracestate check. X-Lerian-Ref: 0x1
Streaming telemetry (emit_span, metrics, metrics_recorders) belongs in lib-commons alongside Producer.Emit(), not here. lib-observability provides general-purpose OTEL primitives (tracing, metrics, log, middleware). lib-commons will import lib-observability and build kafka-specific instrumentation on top of those primitives. X-Lerian-Ref: 0x1
feat: migrate from lib commons
Replace bespoke CI with LerianStudio/github-actions-shared-workflows@v1.28.12, matching the go-boilerplate-ddd reference adapted for a pure Go library (no Dockerfile, no build artifact, no migrations). Workflows: - pr-validation: new, with library-aligned pr_title_scopes and source-branch enforcement. - pr-security-scan: new, enable_docker_scan=false (no Dockerfile in this repo). - go-combined-analysis: replace bespoke gosec/golangci-lint composition with shared go-pr-analysis. - release: replace bespoke semantic-release + backmerge job with shared release.yml. - routine: new, schedules labels-sync, stale-pr/issue, branch and workflow-run cleanup. Repo conventions: - labeler.yml: scopes mirror flat package layout (assert, log, metrics, middleware, redaction, runtime, tracing, zap, core, scripts, config, ci, docs, tests, deps). - labels.yml: full label catalogue (scopes + change types + size + triage + automation/backmerge). - dependabot.yml: gomod + github-actions only; weekly; develop target; groups preserved. - pull_request_template.md: adopt portfolio header/sections; add lib-commons import guard. X-Lerian-Ref: 0x1
…ot schedule - .releaserc.yml: declare release-candidate (prerelease: rc) so semantic-release matches the workflow trigger set [main, release-candidate, develop]. - .github/dependabot.yml: add time: "08:00" to the github-actions schedule for consistency with the gomod entry. X-Lerian-Ref: 0x1
…ment go.mod requires go >= 1.25.9 but setup-go was resolving "1.25" to 1.25.6, which fails build/test/lint under GOTOOLCHAIN=local. X-Lerian-Ref: 0x1
….28.12 fix(ci): align workflows with shared workflows v1.28.12 boilerplate
Update shared workflows, fix lint findings, and make the gosec target use the Go bin fallback when gosec is installed locally. Requested-by: @qnen
Update the gosec resolver to prefer PATH, then GOBIN, then GOPATH/bin so the sec target works with custom Go binary install directories. Requested-by: @qnen
Keep the structured error field key independent from log level labels. Requested-by: @qnen
chore(ci): prepare stable release gates
Requested-by: @qnen
Requested-by: @qnen
feat: add logging middleware
Requested-by: @qnen
🔍 PR Validation Summary✅ PR Mergeable — no blocking failures
|
🔒 Security Scan Results —
|
| Stage | Status | Blocking? |
|---|---|---|
| Filesystem Scan | ✅ Clean | — |
| Docker Image Scan | ➖ Skipped | — |
| Docker Hub Health Score | ➖ Skipped | — |
| Pre-release Version Check | ✅ Clean | — |
Trivy
Filesystem Scan
✅ No vulnerabilities or secrets found.
Pre-release Version Check
✅ No unstable version pins found.
📊 Unit Test Coverage Report:
|
| Metric | Value |
|---|---|
| Overall Coverage | 84.2% ✅ PASS |
| Threshold | 80% |
Coverage by Package
| Package | Coverage |
|---|---|
github.com/LerianStudio/lib-observability/assert |
97.9% |
github.com/LerianStudio/lib-observability/constants |
83.3% |
github.com/LerianStudio/lib-observability/log |
94.9% |
github.com/LerianStudio/lib-observability/metrics |
91.4% |
github.com/LerianStudio/lib-observability/middleware |
67.6% |
github.com/LerianStudio/lib-observability/redaction |
95.8% |
github.com/LerianStudio/lib-observability/runtime |
80.4% |
github.com/LerianStudio/lib-observability/tracing |
84.9% |
github.com/LerianStudio/lib-observability/zap |
96.0% |
github.com/LerianStudio/lib-observability |
91.1% |
Generated by Go PR Analysis workflow
There was a problem hiding this comment.
Actionable comments posted: 18
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.releaserc.yml:
- Around line 11-20: YAML flow mappings in .releaserc.yml have extra spaces
inside braces causing YAMLlint `braces` errors; update each mapping (examples:
the objects currently written as { type: "feat", release: "minor" }, { type:
"perf", release: "minor" }, ... and the breaking entry { breaking: true,
release: "major" }) to remove the spaces immediately after '{' and before '}' so
they become {type: "feat", release: "minor"} etc., and apply the same change to
the entry around the 33rd line.
In `@CLAUDE.md`:
- Around line 17-18: Update the stale table entry that declares "Streaming
telemetry | `streaming/` package" and the related rule "lib-commons dependency |
**NEVER import lib-commons**": replace the streaming row to indicate streaming
has been moved back into lib-commons (e.g., "Streaming telemetry | now in
lib-commons") and revise the lib-commons guidance to reflect that importing
lib-commons is allowed for streaming-related code or clarify permitted uses
(remove the absolute "NEVER import lib-commons" wording); ensure the table
entries and surrounding sentence(s) referencing `streaming/` and `lib-commons`
are consistent with the current release.
In `@constants/headers.go`:
- Around line 4-9: HeaderTraceparent is incorrectly set to the PascalCase value
and duplicates HeaderTraceparentPascal; change the value of the constant
HeaderTraceparent to the canonical lowercase "traceparent" (leave
HeaderTraceparentPascal as "Traceparent" and keep HeaderTracestatePascal
unchanged) so exact-key header propagation can rely on distinct lowercase and
PascalCase variants.
In `@go.mod`:
- Line 3: The go directive in go.mod is pinned to "go 1.25.9" which has known
security advisories; update that directive to "go 1.25.10" to pick up the fixes
(replace the existing go 1.25.9 token in go.mod with go 1.25.10) and re-run go
mod tidy/build to ensure modules are compatible.
In `@middleware/logging_obfuscation.go`:
- Around line 12-24: The current getBodyObfuscatedString falls back to returning
string(bodyBytes) for unknown content types, which can leak PII; change the
fallback to return a safe redacted placeholder (e.g., "[REDACTED]") instead of
the raw body, and ensure the same pattern is applied to any other raw-body
fallbacks in this file (replace raw returns in the URL-encoded/JSON error paths
and the other similar function) so only handleJSONBody, handleURLEncodedBody,
and handleMultipartBody return content-derived values while all
unsupported/error paths return the placeholder.
In `@middleware/telemetry_route_test.go`:
- Around line 19-27: The test calls t.Parallel() but mutates global
OpenTelemetry tracer provider via setupTestTracer, otel.SetTracerProvider and
otel.GetTracerProvider which can cause flakes; remove t.Parallel() from the test
(or scope the tracer mutation to a subtest that is not parallel) so the test
runs serially while it sets the global tracer, keep the existing defer restoring
otel.SetTracerProvider(oldTP) and tp.Shutdown to ensure cleanup, and reference
the functions setupTestTracer, otel.SetTracerProvider, and
otel.GetTracerProvider when locating the change.
In `@middleware/telemetry.go`:
- Line 99: The code currently ends the same tracing span in two places (defer
span.End() inside WithTelemetry/WithTelemetryInterceptor and again inside
EndTracingSpans/EndTracingSpansInterceptor), which can double-end spans; pick a
single owner and remove the duplicate calls—e.g., remove the deferred span.End()
invocations in WithTelemetry and WithTelemetryInterceptor and let
EndTracingSpans/EndTracingSpansInterceptor be the sole place that calls
span.End(), ensuring each span is started in
WithTelemetry/WithTelemetryInterceptor and ended exactly once in the
EndTracingSpans/EndTracingSpansInterceptor helpers.
In `@README.md`:
- Around line 7-13: The README headings and examples reference package paths
"opentelemetry" / "opentelemetry/metrics" but the repo exposes modules as
"tracing" and "metrics"; update the section headings and any import examples to
use "tracing" (for the OpenTelemetry bootstrap, tracer helpers and functions
like NewTelemetry(cfg) and ShutdownTelemetry()) and "metrics" (for
MetricsFactory, NewNopFactory and the fluent API), and search/replace any other
occurrences to ensure docs match actual module paths so import statements and
symbols resolve correctly.
In `@runtime/error_reporter.go`:
- Around line 98-133: The call to reporter.CaptureException inside
reportPanicToErrorService must be made best-effort so a panic inside the error
reporter cannot re-panic the recovery path; wrap the CaptureException invocation
in a local guarded call (e.g., an anonymous function) that defers a recover()
and swallows or logs any panic from CaptureException so it cannot propagate to
KeepRunning; retain existing logic around GetErrorReporter, toPanicError and
IsProductionMode and ensure any panic from the reporter is handled locally and
does not escape reportPanicToErrorService.
In `@runtime/helpers_test.go`:
- Around line 30-42: The testLogger.Log currently marks panicLogged for every
call because it ignores the log.Level parameter; change the signature to capture
the level (e.g., lvl log.Level) and only set panicLogged.Store(true) when the
level indicates a panic (e.g., lvl == log.Panic) or when the message clearly
indicates a panic if your test uses message-based detection; keep the rest of
the behavior (appending to errorCalls, setting lastMessage, and signalling via
logged) unchanged so only real panic-level logs satisfy panic assertions.
In `@runtime/recover.go`:
- Around line 155-178: The panic logger replaces caller contexts with
context.Background(); update logPanicWithStack to accept a context.Context
parameter (e.g., change signature to logPanicWithStack(ctx context.Context,
logger Logger, name string, panicValue any, stack []byte) and use that ctx in
logger.Log calls), update all callers to pass their existing ctx, and leave
context.Background() only where there truly is no caller context; ensure
references to IsProductionMode, redactedPanicMsg and Logger remain unchanged and
that non-context callers still compile by explicitly passing
context.Background().
In `@runtime/tracing.go`:
- Around line 104-107: The code calls trace.SpanFromContext(ctx) without
checking ctx for nil, which can panic; update the guard in the function that
contains span := trace.SpanFromContext(ctx) to first check ctx != nil (or use a
nil-safe helper) and return early if nil before calling trace.SpanFromContext or
span.IsRecording(), so the nil context path is handled safely and avoids panics
in the panic-recovery path.
In `@system.go`:
- Around line 19-59: The collectors (CPU and memory) currently log read errors
but still call RecordSystemCPUUsage/RecordSystemMemUsage with a zero value,
producing misleading telemetry; update both functions (the CPU collection code
that sets percentageCPU from out and GetMemUsage which uses mem.VirtualMemory
and percentageMem) to only call factory.RecordSystemCPUUsage or
factory.RecordSystemMemUsage when the underlying read succeeded (i.e., err ==
nil) — keep the existing error logs and the factory-nil checks, but skip the
metric recording entirely on read error so you don't record a 0 value.
In `@tracing/otel.go`:
- Around line 102-104: normalizeEndpointEnvVars currently mutates process-wide
OTEL env vars via os.Setenv during tracer initialization; change it to be pure:
remove any os.Setenv calls and instead accept the current cfg (or return the
computed endpoint and headers) so normalizeEndpointEnvVars/normalizeEndpoint
compute and return normalized values which the caller then assigns into the
local config used by the tracer (e.g., update the config struct passed into
normalizeEndpoint(&cfg) and the caller of normalizeEndpointEnvVars to set cfg.
Ensure no global os.Setenv is used anywhere in tracing/otel.go (remove or
refactor any os.Setenv in lines around normalizeEndpointEnvVars and 163-175) and
update any tests that assumed env mutation to use the returned config values.
- Around line 562-569: The code builds statusMsg using sanitizeSpanMessage but
concatenates the raw message variable and then passes the original err into
span.RecordError, which can leak control characters/sensitive data; update this
block so you sanitize the full status payload (sanitize the message string
before concatenation or sanitize the combined statusMsg via sanitizeSpanMessage)
and create/record a sanitized error payload to pass into span.RecordError (e.g.,
construct a new error or error wrapper whose Error() returns the sanitized
statusMsg) so both span.SetStatus and span.RecordError use the same sanitized
text (touch symbols: sanitizeSpanMessage, message, statusMsg, span.SetStatus,
span.RecordError, err).
- Around line 154-156: The code currently unconditionally sets
cfg.InsecureExporter = true for scheme-less endpoints (bare host:port); instead,
preserve the caller's explicit intent by removing that automatic downgrade: only
set cfg.InsecureExporter to true if the parsed endpoint explicitly uses the
"http" scheme or if cfg.InsecureExporter was already true; do not flip
cfg.InsecureExporter to true when the input is scheme-less—leave it unchanged so
callers can default to secure gRPC. Ensure the change is made where the endpoint
parsing logic sets cfg.InsecureExporter (the block that currently comments "No
scheme — assume insecure (common in k8s internal comms)").
In `@zap/zap.go`:
- Around line 176-193: The Debug/Info/Warn/Error convenience methods call
l.must().Debug/Info/Warn/Error directly and bypass sanitizeConsoleMsg, so
messages can contain control characters; update each method (Debug, Info, Warn,
Error) to sanitize the message first by calling sanitizeConsoleMsg(message) and
then pass the sanitized string to l.must().Debug/Info/Warn/Error (keeping the
original fields...), or alternatively delegate to the protected Log method after
sanitizing to ensure consistent behavior with Log and prevent forged console
lines.
- Around line 109-111: WithGroup currently treats names containing only
whitespace as valid groups; update the function WithGroup to treat
whitespace-only names the same as empty by checking strings.TrimSpace(name) ==
"" and returning l in that case. Add the strings import if missing and apply the
trimmed-name check where the current name == "" check is located to preserve
documented/GoLogger behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 29421f2a-1b2d-4b34-9c7e-fdf98cea549c
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (97)
.github/FUNDING.yml.github/dependabot.yml.github/labeler.yml.github/labels.yml.github/pull_request_template.md.github/workflows/go-combined-analysis.yml.github/workflows/pr-security-scan.yml.github/workflows/pr-validation.yml.github/workflows/release.yml.github/workflows/routine.yml.gitignore.golangci.yml.goreleaser.yml.ignorecoverunit.releaserc.ymlCLAUDE.mdLICENSEMakefileREADME.mdassert/assert.goassert/assert_extended_test.goassert/assert_test.goassert/doc.goassert/predicates.goassert/predicates_test.goconstants/doc.goconstants/headers.goconstants/log.goconstants/metadata.goconstants/obfuscation.goconstants/opentelemetry.goconstants/opentelemetry_test.gocontext_helpers.gocontext_tracking_test.gogo.modlog/doc.golog/go_logger.golog/log.golog/log_mock.golog/log_test.golog/nil.golog/nil_test.golog/sanitizer.golog/sanitizer_test.gometrics/account.gometrics/builders.gometrics/doc.gometrics/metrics.gometrics/metrics_test.gometrics/operation_routes.gometrics/system.gometrics/transaction.gometrics/transaction_routes.gomiddleware/context_span.gomiddleware/helpers.gomiddleware/logging.gomiddleware/logging_obfuscation.gomiddleware/logging_test.gomiddleware/metrics.gomiddleware/telemetry.gomiddleware/telemetry_route_test.gomiddleware/telemetry_test.goobservability.goobservability_test.goredaction/doc.goredaction/sensitive_fields.goredaction/sensitive_fields_test.goruntime/doc.goruntime/error_reporter.goruntime/goroutine.goruntime/goroutine_test.goruntime/helpers_test.goruntime/log_mode_link_test.goruntime/metrics.goruntime/metrics_test.goruntime/policy.goruntime/policy_test.goruntime/recover.goruntime/recover_test.goruntime/tracing.goruntime/tracing_test.goscripts/makefile_colors.mkscripts/makefile_utils.mksystem.gosystem_test.gotracing/doc.gotracing/obfuscation.gotracing/obfuscation_test.gotracing/otel.gotracing/otel_test.gotracing/processor.gotracing/v2_test.gozap/doc.gozap/injector.gozap/injector_test.gozap/zap.gozap/zap_test.go
Requested-by: @qnen
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
system.go (1)
24-27:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSkip CPU metric recording when no sample is returned.
When
cpu.Percentreturns an empty slice, this path records0, which can misrepresent collection quality as real telemetry data. Guardlen(out)==0and return after warning instead of emitting a zero datapoint.Proposed fix
- var percentageCPU int64 = 0 - if len(out) > 0 { - percentageCPU = int64(out[0]) - } + if len(out) == 0 { + logger.Log(ctx, log.LevelWarn, "cpu usage response is empty, skipping CPU usage recording") + return + } + percentageCPU := int64(out[0])Also applies to: 34-35
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@system.go` around lines 24 - 27, The code currently converts an empty cpu.Percent result into a misleading zero by setting percentageCPU := int64(out[0]) without guarding for no samples; update the cpu sampling blocks (the one initializing percentageCPU and the analogous block around lines 34-35) to check if len(out) == 0 and if so log a warning and return/skip recording instead of emitting a zero datapoint. Locate the cpu.Percent call and the percentageCPU variable assignment, add the len(out) == 0 guard, and ensure the function exits early (or skips metric submission) when no sample is returned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@middleware/logging.go`:
- Around line 251-269: The buildOpts(opts...) call is duplicated; initialize the
options once before the conditional that calls
getValidBodyRequestID/getMetadataID by assigning mid := buildOpts(opts...)
(ensure you don't shadow mid) and use that mid for the debug Log call and later
for logger creation (logger := mid.Logger.With(...)). Remove the later
buildOpts(opts...) invocation so the same mid is reused across the interceptor.
---
Duplicate comments:
In `@system.go`:
- Around line 24-27: The code currently converts an empty cpu.Percent result
into a misleading zero by setting percentageCPU := int64(out[0]) without
guarding for no samples; update the cpu sampling blocks (the one initializing
percentageCPU and the analogous block around lines 34-35) to check if len(out)
== 0 and if so log a warning and return/skip recording instead of emitting a
zero datapoint. Locate the cpu.Percent call and the percentageCPU variable
assignment, add the len(out) == 0 guard, and ensure the function exits early (or
skips metric submission) when no sample is returned.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 06788ec4-63e6-49a5-93d0-dc8ac77c55c6
📒 Files selected for processing (19)
.github/workflows/go-combined-analysis.yml.releaserc.ymlCLAUDE.mdREADME.mdconstants/headers.gogo.modmiddleware/logging.gomiddleware/logging_obfuscation.gomiddleware/telemetry.gomiddleware/telemetry_route_test.goruntime/error_reporter.goruntime/helpers_test.goruntime/recover.goruntime/recover_test.goruntime/tracing.gosystem.gotracing/otel.gotracing/otel_test.gozap/zap.go
Summary
Validation
Requested by: @qnen