Changelog¶
All notable changes to Click-Dog will be documented in this file.
The format is based on Keep a Changelog, and this project uses calendar versioning (vYY.MM.idx).
[v26.07.4] - 2026-07-16¶
The v26.07.3 tag reached the public repository but its release build failed the CI gate, so no binaries, container image, or signed artifacts were published (the docs site did update). v26.07.4 carries the same application and documentation changes as v26.07.3 — listed below — plus the two release-pipeline fixes that unblock the build. It is the first published artifact of this content.
Fixed¶
- Release CI no longer trips a stdlib advisory. The build toolchain moves from go1.26.4 to go1.26.5, clearing
GO-2026-5856incrypto/tls, which govulncheck enforces as a release gate. - A docs-accuracy test tolerates the public archive.
TestFailoverDocs_KeepLookbackRecoveryConditionalasserts against an internal-only spec that is export-ignored and therefore absent from the published tree; it now skips that file when the internal docs are not present, while still checking the public documents and still enforcing the internal spec on the development repository.
[v26.07.3] - 2026-07-16¶
An accuracy release. The public site, README, and install path were audited
against shipping behavior; the corrections below include two code fixes and one
change to install.sh. Superseded by v26.07.4, which is the first build of this
content to publish artifacts.
Fixed¶
service_namenow expands environment variables. In anexporters.otelblock,service_name: ${OTEL_SERVICE}was passed through as the literal string while every other field in the block (collector_address,ca_cert,client_cert,client_key) expanded. Config that templated the service name per environment exported under the unexpanded value.- The README's architecture link resolves. It pointed at an internal-only contributor document that is never published, so the link was broken for every reader of the public repository. It now points at
docs/architecture.md. - Resource-hog findings read correctly when exactly one metric trips —
(1 metric above threshold)rather than(1 metrics above threshold).
Changed¶
install.shno longer requiresGITHUB_TOKEN. The token was mandatory while the repository was private, since the release and asset endpoints 404 unauthenticated. Now that releases are public it is optional, and useful only to raise the GitHub API rate limit on a shared host. The 401/403 and 404 diagnostics no longer attribute the failure to a missing token.- Duplicate-delivery documentation states what click-dog guarantees rather than what backends do. Restart, retry, and leader-partition windows can resend spans carrying stable
(trace_id, span_id)identities. Documentation and implementation comments no longer assert that OTEL collectors or backends collapse those repeats — OTLP does not require it, and the behavior is backend-specific. - The site describes the product as public beta under Apache 2.0, replacing the "truly open source — no restrictions, ever" copy on the homepage and closing CTA.
Added¶
CONTRIBUTING.mddocuments how an external pull request lands. The public repository is the contribution target; accepted PRs are imported into the development tree with authorship preserved and ship in the next release.
Removed¶
- The orphan
/docs/index page. Nothing linked to it oncellms.txtwas repointed at/overview/.
[v26.07.2] - 2026-07-04¶
First public release. The application source is unchanged from the internal v26.07.1 tag, whose release failed partway through (see below), but the builds are versioned as v26.07.2 and the release pipeline is fixed.
Fixed¶
- Public release provenance is single-source. Release tags run the full gate in both repositories, but artifacts and container images are built and signed only in the public
coltconsulting/click-dogrepository. OCI source labels now point to that public source tree. - Public mirror publishing no longer touches the internal working tree. The fence script materializes the tagged archive only in the dedicated public checkout and retains its secret-scan backstop.
Added¶
- Secret-scan configuration scopes the gitleaks exception to the query analyzers' metric-key literals in
internal/analysis/, keeping the release gate strict everywhere else.
[v26.07.1] - 2026-07-02¶
The internal release published private binary and checksum assets, then GoReleaser failed during the container build after deriving the project name from the wrong git remote. The tag and release were not mirrored to the public repository, so these application changes first shipped publicly in v26.07.2.
Added¶
- Public source, documentation site, and verified release path under Apache 2.0, with one aggregate commit per GA release.
- Read-only query analysis and trace drilldown through
click-dog analyze queriesandclick-dog analyze trace, including deterministic JSON schemas for tooling. - OTLP self-metrics push alongside the independent Prometheus scrape endpoint, plus updated Datadog health dashboards.
- Self-metrics state is visible at startup and in
-validate, including an explicit disabled-state message so a blank health dashboard is diagnosable. - Topology self-audit and richer health reporting for duplicate whole-cluster reader detection,
/status, and leader-served/clusterzaggregation.
Changed¶
- Filtering and redaction are surfaced in generated profiles and dashboards, including the query-ID enrichment prerequisite and high-volume internal-operation exclusions.
clickhouse.port: 0is rejected. An omitted port still defaults to9000; an explicit value must be in the range 1-65535.- The public documentation is task-oriented, with dedicated onboarding, support, integration, operating-contract, and machine-readable entry points.
[v26.06.6-beta] - 2026-06-12¶
Changed¶
- Breaking (beta): the default OTEL service name is now
click-dog-monitor(wasclickdog-monitor) — brand consistency with everything else click-dog ships. Changed at every source of the value: the config default,click-dog initand the wizard,install.sh -service, the deploy templates (Ansible / Kubernetes / Docker), the example configs, the Datadog query dashboard's$servicetemplate-variable default, and the docs. A deployment that relied on the default exports under the new name after upgrading — update dashboards/monitors filteringservice:clickdog-monitor, or pinservice_name: clickdog-monitorin config to keep the old name. Re-runclick-dog create-dashboards --on-exists overwriteto pick up the new dashboard default. create-dashboardsoutput now states what was found vs what was done —[query] not found in Datadog — creating,up to date — the live dashboard already matches this build,live dashboard differs from this build (live: X → build: Y)— replacing the ambiguousdefinition source: embedded (X)line. Headless (non-interactive) runs print the same findings.- Install lifecycle defaults aligned across install paths (issue #233) — the production
click-dog initprofile now enables the dedicated health listener, so default systemd installs serve/healthz//readyzandclick-dog deploy statusworks out of the box. The Ansible playbook picks up the production filter/metrics/health defaults,Restart=on-failurewith start limits, and post-install/healthz(plus canary/readyz) verification. The guided update path for an existing install stages the new binary, rejects downgrades, validates the existing config before the swap, and keeps a.prevrollback binary.
Added¶
- HA leader/standby state is first-class in metrics (issue #239) — new
click_dog_leadergauge (1on the active leader,0on standbys) and arole="active|standby"-labeled variant ofclick_dog_last_success_timestamp_seconds. The bare legacy series is retained for existing scrapes; new HA-aware alerts should key onrole:active. The shipped Datadog health dashboard and docs now exclude standbys from stale-export alerting.
Fixed¶
- Healthy HA standbys no longer read as unhealthy (issue #239) — leader-gated standby cycles are recorded under an explicit skip reason, so
/statusreports a healthy idle standby while breaker-open skips remain unhealthy. - Legacy
ha.enabledkey now fails with a pointed error (issue #237) — configs still carrying the removed key get "ha.enabled has been removed: leader election is enabled by setting ha.keeper.hosts — delete the ha.enabled key" instead of a generic unknown-field error. - Disabled or short-retention
query_logno longer silently blinds the topology audit (issue #238) — on zero-reader ticks the detector probes localsystem.query_logfor recent rows and WARNs once if the window is empty (log-only; gauge and debounce behavior unchanged, and a healthy fleet never pays for the probe). - Topology warning remediation acknowledges legitimate duplication (issue #241) — the
multi_instance_cluster_queriesremediation text, the dashboard's topology note, and the Datadog guide now say a red tile can also reflect bounded by-design duplication (Keeper-outage fail-open, long-running backfill from another host) and self-clears once resolved.
Security¶
- Self-update fails closed on oversized archive members (issue #246) — a binary entry larger than the extraction limit is now rejected before extraction instead of being silently truncated by the limited reader.
[v26.06.5-beta] - 2026-06-08¶
Cumulative notes for the alpha/beta line through v26.06.5-beta.
Fixed¶
- Adaptive backoff now recovers after a sustained outage.
RecordSuccessonly reset the poll interval to base when the previous success was withinreset_after_s, butRecordFailurenever advanced that timestamp — so after any outage long enough to push the interval pastreset_after_s(default 60s), the poller stayed pinned atmax_interval(default 300s) until restart, silently degrading span freshness ~10×. A single successful poll now snaps the interval straight back to base. query_logbackfill exports now get deterministic span IDs. The OTLPExportQuerypath derived a fresh random(trace_id, span_id)on every call, so a partial-failure retry or a re-run backfill window could not even correlate repeats by span identity. IDs are now derived deterministically from the ClickHousequery_id(random fallback only for the rare row with noquery_id); downstream duplicate handling remains backend-specific.- Live-mode dedup keys off the composite
(trace_id, span_id)pair (issue #96). Previously the LRU dedup cache keyed offspan_idalone, but OTLP span IDs are only unique within a trace — when two unrelated traces produced spans with the same 64-bit span ID (rare but possible), the second arrival was silently dropped instead of exported. The cache is now keyed by amodel.SpanKey { TraceID, SpanID }struct, and exporter results carry acceptedSpanKeyvalues so the processor marks only successfully exported composite keys as seen. Partial-failure semantics are unchanged: failed exports still leave every unexported span available for retry. Repeat delivery preserves the same composite identity; downstream duplicate handling is backend-specific.
Changed¶
- Breaking: exporter results now include per-sink status (issues #126, #130) —
SpanExporter.ExportSpansandExportQueryreturnmodel.ExportResultpluserror, reporting total sent/accepted counts and oneExportSinkStatusper backend. Custom exporter implementations must update to the new interface.MultiExporterstill keeps the strict all-sinks-required retry policy, while metrics and warning logs now expose which sink accepted or failed each export attempt via theclick_dog_export_*_total{sink}counters. - MultiExporter span fan-out policy is explicit (issue #127) —
NewMultiExporternow acceptsWithMultiExporterPolicy(...). The defaultPolicyAllRequiredpreserves the strict all-sinks-required behavior;PolicyAnySuccessreturns spans accepted by any successful sink while still surfacing failed sink status throughExportResult.ExportQueryremains all-required for backfill correctness. No YAML setting is added in this change; production config keeps the default strict policy. - MultiExporter is now strict by default — when multiple sinks are configured, any sink failure (partial or total) returns an error from
ExportSpans/ExportQuery. The previous behavior of treating "at least one sink succeeded" as success silently hid dead backends from the circuit breaker, adaptive backoff, error metrics, and/readyz. With the new contract every cycle that fails a sink is marked an error (click_dog_cycle_results_total{result="error"}) and feeds the breaker; sinks that previously succeeded receive the spans again on retry with stable IDs, and duplicates may remain visible downstream. See Configuration — Multiple Export Backends. - Processor pipeline extracted to
internal/processor(issue #133). The scheduled-mode loop, canary, heartbeat, enrichment, and backfill driver now live in a dedicated package. No behavioral change; this only affects custom forks that imported the old top-levelprocessor.go. - OTLP span batches stream into size-bounded chunks (issue #121).
OTELExporter.ExportSpansnow estimates the proto-encoded size of each span incrementally and flushes a chunk before the next span would push it over gRPC's 4 MiB ceiling. Recursive splitting handles pathological single-span overflow. Operators no longer need to sizebatch_sizedefensively around span text length; the exporter never sends a request larger than the gRPC limit. - Datadog Application Query Analysis dashboard is now explicitly live-span-only — widget queries require
resource_name:query @click_dog.source:span_logand the dashboard/docs call out the separate historical backfill contract (clickhouse.query,click_dog.source=query_log,db.*). Operators importing the updated dashboard need click-dogv26.03.1or newer, or any build that emitsclick_dog.source; older binaries will not match the new source filter until upgraded.
Removed¶
- Breaking: the legacy top-level
otel:config key. Use theexporters.otel[]list form (the documented form sincev25.04.0). Configs still using the top-levelotel:block now fail to load with an unknown-field error instead of being silently auto-promoted. Migrateotel: { collector_address: … }toexporters: { otel: [ { collector_address: … } ] }. - Breaking: the
click_dog_cycles_totalandclick_dog_errors_totalmetrics. Both are derivable fromclick_dog_cycle_results_total{result}(sum across results = total cycles;{result="error"}= error cycles). The shipped Datadog health dashboard and its OpenMetrics rename list have been repointed; external dashboards or alerts on the old names must migrate. - The
monitor.backoff.reset_after_ssetting. Backoff now resets on the first successful poll (see Fixed above), so the knob no longer had any effect; it is rejected as an unknown config field. Remove it from existing configs.
Added¶
- Cluster query mode now supports normalized query fields when all replicas are compatible (issue #184) — at startup click-dog probes
clusterAllReplicas('<cluster>', system.columns)and enablesnormalized_query_hash/normalized_queryonly when every replica reports the column. Mixed-version, unreachable, or unconfigured-cluster setups keep the safe fallback. The verdict is logged and surfaced as thenormalized_query_hash supportline inclick-dog check(with theN of N replicascount). - Unresolved
${VAR}config references now warn at load. A typo'd or unset braced reference such as${CLICKHOUSE_PASSWORD}previously expanded silently to an empty string; click-dog now emits a warning at startup and under-validatenaming the unset variable. - Per-call exporter timeout (
monitor.export_timeout_s, default30s) — every backend call (live span batch, backfill query, degraded-mode canary) runs under this deadline. A stuck collector now fails into the normal retry / backoff / circuit-breaker path instead of pinning a cycle indefinitely. Multi-sink fan-out shares the same budget rather than multiplying it per sink. Set to0to disable the client-side deadline. - Per-sink export observability counters —
click_dog_export_attempts_total{sink},click_dog_export_accepted_total{sink}, andclick_dog_export_errors_total{sink}expose which backend accepted or failed each export attempt. With a single configured exporter the label is the bare kind (otelorsplunk_hec); with the multi-sink fan-out wrapper each sink is labeledotel[<i>]:<collector_address>/splunk_hec[<i>]:<endpoint>so dashboards can distinguish the individual backends. See Observability — Available Metrics. - Normalized query family attributes (issue #84) — when ClickHouse supports it, live spans carrying
clickhouse.query_idgainquery_log.normalized_query_hash; backfill query spans gaindb.normalized_query_hash. Both paths include a literal-stripped normalized-query preview. Internal child spans without a query ID pass through without query-log enrichment. The Datadog query dashboard groups by these. See Span Attributes — query_log Enrichment and Query Family Rollups. - Datadog health-cockpit dashboard — the shipped
dashboards/datadog-clickdog-health.jsonnow leads with current action state (last success age, breaker, error rate, throughput, backoff) and follows with trends and lifetime counters.click-dog create-dashboardsprovisions it alongside the query dashboard. /clusterzaggregate health endpoint (phase 1 of issue #19) — the HA leader can fan out/readyzto a statically configured peer list and serve a single dashboard / on-call URL. Followers return404 {"status":"not_leader"}. See Observability — Cluster Health.
Added (earlier in this development line)¶
- Multi-sink exporter architecture —
SpanExporterinterface allows multiple export backends simultaneously. - MultiExporter — fan-out wrapper that sends spans to all configured backends.
- Multiple OTEL backends — configure multiple
exporters.otel[]entries (and/orexporters.splunk_hec[]) for redundancy or multi-destination export. - Heartbeat logging — periodic summary logs replace per-cycle noise for quieter operation.
- Degradation simulation tests — Docker-based tests that validate circuit breaker and backoff against real cluster failures.
- Validate mode —
-validateflag to check config without connecting. - Official documentation site with user/developer split.
- Deployment guide (install script, Ansible, Kubernetes, systemd, containers).
- Integration guides for Datadog, Honeycomb, and generic OTLP.
- Architecture and testing guides for contributors.
Changed (earlier in this development line)¶
- Renamed
max_queries_per_runtomax_spans_per_cyclefor clarity. Config loading now rejects unknown keys, so the old name surfaces as a load-time error — rename it tomax_spans_per_cycle. max_spans_per_cyclenow caps spans, not traces (issue #91). Previously the value bounded the trace ID query and step 2 multiplied it by 100, so up tomax_spans_per_cycle * 100spans could be fetched and exported per cycle (clamped at the internalMaxSpanQueryLimitof 100,000). The configured value is now the actual SQLLIMITon the spans query, matching the documented behavior. Operator impact: under the same config, total exported span volume per cycle drops from up to 100x the cap to at most the cap. If you previously sized the value down to compensate, you may now need to raise it; partial traces continue across cycles via the lookback overlap + dedup cache.- Degradation tests excluded from regular CI, available as separate GitHub Actions workflow.
- Default
log_levelisinfo(noterroras previously documented).
Security¶
- Log file mode tightened to
0640— previously0644(world-readable). A separate log-shipping agent running under a different UID must now share the click-dog service user's group; see Logging for setup notes. log_rotation.max_size_mb: 0andmax_files: 0are rejected — explicit0would silently disable rotation and produce unbounded log growth. Omit the keys to take the defaults (100 MB / 3 files).- Wider credential redaction in log output — sanitizer now scrubs space-separated forms (e.g.
password 'x',AuthToken xyz) emitted by some driver / wrapper errors. Defense-in-depth; no known leak today.
Project¶
- Apache 2.0 + DCO contribution model — Click-Dog is licensed under Apache 2.0 and contributions require a DCO
Signed-off-bytrailer on every commit (git commit -s). The root CONTRIBUTING.md is the authoritative source; TRADEMARKS.md and NOTICE cover the trademark policy and third-party attributions respectively. gofmtdrift is enforced in CI andmake preflight—make fmt-checkruns the same sweep both gates use; format your tree before pushing or CI will bounce the PR.- Hybrid installer — the install path is now a thin bootstrap shell that downloads and cosign-verifies the signed release archive, then hands off to per-mode templates under
deploy/templates/for Kubernetes and Docker output. Multi-host SSH plumbing is no longer in the shell entry point; use the Ansible playbook indeploy/ansible/for multi-node rollouts. See Install.
[0.1.0] - Initial Release¶
Added¶
- OpenTelemetry span export from
system.opentelemetry_span_log - Query log export from
system.query_log(backfill mode) - Scheduled mode with configurable polling interval
- Backfill mode for historical time ranges
- Two-tier duration filtering (trace-level and span-level)
- SQL-level filtering for efficiency
- LRU deduplication cache (configurable size, default 10,000)
- Batch processing with configurable size and delay
- Circuit breaker pattern for ClickHouse protection
- Adaptive backoff with exponential retry
- Connection health checks
- Operation whitelist with wildcard support
- IP whitelist
- Query blacklist with regex patterns
- Query length filtering (fetch-time skip + export-time truncation)
- Rate limiting (
max_spans_per_cycle) - Connection pooling with configurable limits
- Query timeouts
- ClickHouse cluster mode support (
cluster()function) - TLS support for both ClickHouse and OTEL connections
- Mutual TLS (mTLS) for OTEL exporter
- Environment variable expansion in config (
${VAR}and$VAR) - Configurable logging (debug/info/warn/error) to stderr or file
- YAML-based configuration
- CI/CD with GitHub Actions
- Automated releases with GoReleaser
- Comprehensive test suite with mock OTEL collector