Skip to content

feat(observability): initialize OpenTelemetry pipeline and system-wide observability#107

Closed
taitelee wants to merge 4 commits into
mainfrom
observability
Closed

feat(observability): initialize OpenTelemetry pipeline and system-wide observability#107
taitelee wants to merge 4 commits into
mainfrom
observability

Conversation

@taitelee

Copy link
Copy Markdown
Member

Summary

This PR establishes the core observability infrastructure for WaveHouse. By integrating OpenTelemetry (OTel) and a structured slog logger, we now have a unified pipeline for traces, metrics, and logs. This foundation allows for deep inspection of system performance and simplifies debugging in distributed or standalone environments.

Changes made:

  • OTel Integration: Added internal/observability to initialize the OTel Trace and Metric providers, supporting exports to OTLP-compatible backends (like SigNoz or Jaeger).
  • Structured Logging: Upgraded the global logger to an OTel-aware slog handler that automatically attaches Trace IDs to log entries when a span is active.
  • System Metrics: Registered core metrics for the embedded NATS server and the Pebble deduplication engine to monitor storage and throughput.
  • Standalone Instrumentation: Wired the wavehouse binary to initialize the observability pipeline on boot based on the observability config block.

Test plan

  • Unit Testing: Verified internal/observability initialization and logger formatting via existing suite.
  • Manual Integration: Verified that traces and logs are successfully exported to a local SigNoz instance via WH_OTEL_ADDR.
  • Graceful Shutdown: Confirmed otelShutdown is called on SIGINT/SIGTERM to prevent spans from being dropped during process exit.
  • Config Validation: Verified that the system correctly falls back to stdout logging if the OTel collector is unreachable.

Related Issues

Closes #

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file go Pull requests that update go code area/observability Metrics, logs, traces, health, profiling area/api HTTP handlers, routing, middleware area/ingest Ingest pipeline (Bento, batching, DLQ) area/docs Documentation, site/, README area/infra CI, build, deploy, Docker, release labels May 12, 2026
@github-actions

github-actions Bot commented May 12, 2026

Copy link
Copy Markdown

PR title doesn't match the required format

Got: feat(observability): initialize OpenTelemetry pipeline and system-wide observability (length: 84)

Expected format: <type>(optional-scope)(optional-!): <lowercase subject, no trailing period> (max 72 chars — the title becomes the squash-merge commit subject)

Allowed types: feat, fix, docs, refactor, test, chore, ci, deps, build, perf, revert, style.

The optional ! before the colon marks a breaking change per Conventional Commits 1.0.0. The lowercase-first-letter rule is house style for log readability, not part of the spec.

Examples:

  • feat: add dedupe cache
  • fix(ingest): drop duplicate events
  • feat(api)!: remove deprecated endpoint
  • chore(deps): bump clickhouse-go to v2.30

This check is required by the main branch protection ruleset — merge is blocked until the title matches. Edit the PR title to trigger a re-check automatically. I'll clean up this comment once the title is valid.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request lays the groundwork for comprehensive system observability by integrating OpenTelemetry across the application. It enables detailed tracing, structured logging, and metric collection, providing a unified view of system behavior. The changes facilitate easier debugging and performance monitoring in both distributed and standalone environments, ensuring that the lifecycle of requests and messages can be tracked from end-to-end, even across asynchronous boundaries like NATS queues.

Highlights

  • OpenTelemetry Integration: Established a comprehensive OpenTelemetry pipeline for traces, metrics, and logs, routing them to an OTLP-compatible backend (e.g., SigNoz). This includes initializing OTel providers and ensuring graceful shutdown.
  • Structured Logging Enhancement: Upgraded the global slog logger to be OpenTelemetry-aware, automatically injecting trace and span IDs into log entries when an active span is present. Error logs now also include stack traces for improved debugging.
  • Distributed Tracing Context Propagation: Implemented robust context propagation for distributed tracing across asynchronous components. Trace IDs are now manually extracted and injected into NATS headers to prevent 'orphaned' traces, ensuring end-to-end visibility from API requests through message queues and worker processing.
  • Dynamic Log Level and Configuration: Introduced dynamic log level configuration via the WH_LOG_LEVEL environment variable and added a new observability block to the main config.yaml for enabling/disabling observability and configuring the OTel endpoint.
  • ClickHouse Observability Setup: Added extensive ClickHouse configuration files (cluster.xml, config.xml, users.xml, storage.xml, custom-function.xml) to support observability, including JSON logging, Prometheus metrics, and a dedicated opentelemetry_span_log table for storing trace data.
  • Bento Ingest Pipeline Refinement: Refactored the Bento ingest pipeline to include a custom ClickHouse output. This new output retroactively creates a 'bento_queue_wait' span to accurately measure message queue latency and a 'clickhouse_insert' span for the database write operation, providing granular tracing for data ingestion.
  • SigNoz Docker Compose Setup: Introduced a new observability/docker-compose.yaml for setting up the SigNoz observability stack, including ClickHouse, Zookeeper, SigNoz UI, and an OTel collector, with specific port mappings and volume configurations to avoid conflicts with the main application.
  • API Query Context Handling: Modified API query handling to detach the query context from the HTTP request's cancellation, allowing long-running queries to complete even if the client disconnects.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive observability stack using OpenTelemetry and SigNoz, including structured logging with slog, distributed tracing, and metrics. It adds several ClickHouse configuration files, a new clickhouse_json_bridge for Bento, and detailed documentation for the observability setup. However, several critical issues were identified: a potential compilation error due to an undeclared Binary variable, a violation of the style guide regarding r.Context() usage in the query handler, and a security risk involving an unbounded read of HTTP response bodies. Additionally, the new ingest path lacks unit tests, and several documentation and configuration syncs are missing. Please address these issues to ensure system stability and maintainability.

Comment thread cmd/wavehouse/main.go
logLevel.Set(level)

baseLogger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel, AddSource: true}))
logger := baseLogger.With("version", Version, "build_time", BuildTime, "git_commit", GitCommit, "binary", Binary)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

[MUST] The variable Binary is used here but is not declared in the var block or anywhere else in the package. This will cause a compilation error. Please declare it along with Version, BuildTime, and GitCommit.

Suggested change
logger := baseLogger.With("version", Version, "build_time", BuildTime, "git_commit", GitCommit, "binary", Binary)
logger := baseLogger.With("version", Version, "build_time", BuildTime, "git_commit", GitCommit)

Comment thread internal/api/query.go
// Execute query with singleflight to protect ClickHouse from thundering herds.
v, err, _ := h.sf.Do(cacheKey, func() (interface{}, error) {
queryCtx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
detachedCtx := context.WithoutCancel(r.Context())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

[MUST] Using context.WithoutCancel(r.Context()) explicitly ignores the client's cancellation signal. This violates the repository style guide rule: "HTTP handlers ignoring r.Context()". While this is likely intended to prevent singleflight from aborting shared work, it can lead to resource leaks (e.g., ClickHouse queries continuing to run after all clients have disconnected). Consider using r.Context() directly or a more sophisticated singleflight pattern that honors individual request cancellations.

References
  1. HTTP handlers ignoring r.Context() is a correctness violation. (link)

Comment thread internal/ingest/bento.go
defer resp.Body.Close()

if resp.StatusCode >= 300 {
body, _ := io.ReadAll(resp.Body)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

[MUST] io.ReadAll(resp.Body) performs an unbounded read into memory. If the ClickHouse server returns a large error response, this could lead to an Out-Of-Memory (OOM) condition. Use io.LimitReader to constrain the amount of data read.

Suggested change
body, _ := io.ReadAll(resp.Body)
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024*1024)) // Limit to 1MB


// Attach Stack Trace for Errors
if r.Level >= slog.LevelError {
r.AddAttrs(slog.String("stacktrace", string(debug.Stack())))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[SHOULD] Calling debug.Stack() on every error log is expensive and can significantly impact performance in high-throughput scenarios. Consider making stack trace collection optional or using a more efficient way to capture context.

Comment thread internal/ingest/bento.go

if err := service.RegisterBatchOutput("clickhouse_json_bridge", service.NewConfigSpec(),
func(conf *service.ParsedConfig, mgr *service.Resources) (service.BatchOutput, service.BatchPolicy, int, error) {
return &clickhouseOutput{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MUST] The new clickhouseOutput component and its batching logic lack unit tests. Per the repository style guide, new code on critical paths like ingest must include tests.

References
  1. New code on ingest paths requires tests.

Comment thread docs/observability.md
```
**2. Copy the necessary files into WaveHouse**
```bash
mkdir -p ~/BeachHouse/observability

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

[MUST] The project name is incorrectly referenced as BeachHouse. It should be WaveHouse for consistency across documentation.

Suggested change
mkdir -p ~/BeachHouse/observability
mkdir -p ~/WaveHouse/observability

trace.WithBatcher(traceExporter, trace.WithBatchTimeout(time.Second*5)),
trace.WithResource(res),
trace.WithSampler(trace.TraceIDRatioBased(0.10)), // TRACE SAMPLE RATE
trace.WithSampler(trace.TraceIDRatioBased(0.10)), // TRACE SAMPLE RATE COMMENTED OUT FOR TESTING

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

[MAY] The comment "TRACE SAMPLE RATE COMMENTED OUT FOR TESTING" is misleading because the sampler is actually active. Please update the comment to reflect the current state.

Suggested change
trace.WithSampler(trace.TraceIDRatioBased(0.10)), // TRACE SAMPLE RATE COMMENTED OUT FOR TESTING
trace.WithSampler(trace.TraceIDRatioBased(0.10)), // 10% sampling rate

Comment thread internal/config/config.go
DLQ DLQ `yaml:"dlq"`
Policy Policy `yaml:"policy"`
Pipes Pipes `yaml:"pipes"`
Observability Observability `yaml:"observability"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

[MUST] New configuration fields and environment variables (WH_OBSERVABILITY_ENABLED, WH_OTEL_ADDR) have been added. These must be documented in docs/configuration.md, docs/deployment.md, and CHANGELOG.md. Additionally, per repository rules, new configuration fields should be validated to prevent invalid values, such as empty strings, which could cause runtime issues.

References
  1. New configuration fields should be validated to prevent invalid values, such as empty strings, which could cause runtime issues.
  2. Notable API changes, such as new configuration fields, must be documented in CHANGELOG.md and the relevant API documentation.
  3. Ensure all related documentation files are updated consistently when configuration is changed.

Comment thread internal/ingest/bento.go
"github.com/Wave-RF/WaveHouse/internal/observability"
"go.opentelemetry.io/otel"

"go.opentelemetry.io/otel/attribute" // ADD THIS

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

[MAY] Please remove the "// ADD THIS" comment as it appears to be a leftover from development. Iterate.

Suggested change
"go.opentelemetry.io/otel/attribute" // ADD THIS
"go.opentelemetry.io/otel/attribute"

@github-actions github-actions Bot removed dependencies Pull requests that update a dependency file area/api HTTP handlers, routing, middleware area/ingest Ingest pipeline (Bento, batching, DLQ) area/infra CI, build, deploy, Docker, release labels May 12, 2026
@github-actions github-actions Bot added dependencies Pull requests that update a dependency file area/api HTTP handlers, routing, middleware area/ingest Ingest pipeline (Bento, batching, DLQ) area/infra CI, build, deploy, Docker, release labels May 12, 2026
EricAndrechek added a commit that referenced this pull request May 12, 2026
)

## Summary

Second-order fix on top of #105. The author-trust + required-check gate
in `claude-review.yml` now uses the REST `check-runs` endpoint instead
of GraphQL's `statusCheckRollup` field on the PR view.

## Why

`gh pr view --json statusCheckRollup` asks GraphQL to navigate
`checkSuite.workflowRun` on every check context on the PR's head commit.
The workflow's `GITHUB_TOKEN` is not allowed to read that sub-field for
checks owned by other integrations (Gemini Code Assist, Copilot,
third-party statuses). When a PR has enough cross-integration checks the
whole call fails with:

```
GraphQL: Resource not accessible by integration
  (...statusCheckRollup.contexts.nodes.5)
  (...nodes.0..4.checkSuite.workflowRun)
```

We don't actually use any of that traversal — the gate just needs
`name`, `status`, `conclusion` for two named checks (`CI`, `PR
housekeeping`). The REST endpoint returns exactly that and nothing else,
so it doesn't trip the cross-integration permission issue.

This failure was always there — #105 fixing `authorAssociation` just let
the script reach the next bug. Visible on PR #88 (long-lived, many
integrations) but not PR #7 (fewer integrations), which is why we didn't
catch it in initial testing.

## What changed

- `gh pr view --json` drops `statusCheckRollup`; keeps `isDraft`,
`author`, `headRefOid`.
- New `gh api repos/$REPO/commits/$head_sha/check-runs?per_page=100`
call for the rollup, only inside the `workflow_run` branch (other event
paths bypass the snapshot like before).
- Status / conclusion comparisons updated from uppercase (GraphQL enum:
`COMPLETED`, `SUCCESS`, ...) to lowercase (REST enum: `completed`,
`success`, ...).
- Gate behavior is otherwise identical: same set of pass-through
conclusions, same skip notices, same fall-through to the Claude action
when CI is green.

## Verified

`gh api
repos/Wave-RF/WaveHouse/commits/a4f5a6b1cc83ffdca5429c0d72fb593f3f232eda/check-runs`
(PR #88's current head) returns both `CI` and `PR housekeeping` with
`status=completed, conclusion=success`. No GraphQL errors.

## Out of scope (mentioned in investigation, not changed here)

- `observability` branch and `context_aware_mq_2` branch still carry
pre-#89 workflow files (`board-state-sync.yml`, `claude-agent.yml`) and
a pre-#77 runner label. Rebase those branches onto current `main` to
drop them.
- The \"Claude\" run on `context_aware_mq_2` (event `dynamic`, runs on
`ubuntu-24.04`) is GitHub Copilot Coding Agent in Claude mode, not our
workflow file.
- PR #107 (observability) `PR housekeeping` failures are legitimate —
the PR title exceeds 72 chars.

## Test plan

- [ ] Next workflow_run for PR #88 (or any internal PR with
cross-integration checks) — the gate completes without GraphQL errors.
- [ ] Draft / Dependabot / untrusted-author PRs still skip with their
respective notices.
- [ ] A green CI on a trusted-author PR still falls through to the
Claude review step.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@taitelee taitelee closed this May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api HTTP handlers, routing, middleware area/docs Documentation, site/, README area/infra CI, build, deploy, Docker, release area/ingest Ingest pipeline (Bento, batching, DLQ) area/observability Metrics, logs, traces, health, profiling dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant