65 Commits

Author SHA1 Message Date
releaser 6d66a56a20 chore(release): prepare for v0.4.1
otelsetup / test (pull_request) Successful in 2m29s
otelsetup / vulnerabilities (pull_request) Successful in 1m31s
pre-commit / pre-commit (pull_request) Successful in 5m53s
2026-06-21 15:51:11 +00:00
releaser 1728cfff32 chore(release): prepare for v0.4.1 2026-06-21 15:51:08 +00:00
renovate 3f0d9eff99 chore(deps): update actions/checkout action to v7 (#152)
otelsetup / vulnerabilities (push) Successful in 1m30s
Release / release (push) Successful in 1m2s
otelsetup / test (push) Successful in 2m23s
pre-commit / pre-commit (push) Successful in 5m46s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/checkout](https://github.com/actions/checkout) | action | major | `v6` → `v7` |

---

### Release Notes

<details>
<summary>actions/checkout (actions/checkout)</summary>

### [`v7.0.0`](https://github.com/actions/checkout/blob/HEAD/CHANGELOG.md#v700)

[Compare Source](https://github.com/actions/checkout/compare/v7.0.0...v7.0.0)

- Block checking out fork PR for pull\_request\_target and workflow\_run by [@&#8203;aiqiaoy](https://github.com/aiqiaoy) in [#&#8203;2454](https://github.com/actions/checkout/pull/2454)
- Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2458](https://github.com/actions/checkout/pull/2458)
- Bump flatted from 3.3.1 to 3.4.2 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2460](https://github.com/actions/checkout/pull/2460)
- Bump js-yaml from 4.1.0 to 4.2.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2461](https://github.com/actions/checkout/pull/2461)
- Bump [@&#8203;actions/core](https://github.com/actions/core) and [@&#8203;actions/tool-cache](https://github.com/actions/tool-cache) and Remove uuid by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2459](https://github.com/actions/checkout/pull/2459)
- upgrade module to esm and update dependencies by [@&#8203;aiqiaoy](https://github.com/aiqiaoy) in [#&#8203;2463](https://github.com/actions/checkout/pull/2463)
- Bump the minor-npm-dependencies group across 1 directory with 3 updates by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2462](https://github.com/actions/checkout/pull/2462)

### [`v7`](https://github.com/actions/checkout/blob/HEAD/CHANGELOG.md#v700)

[Compare Source](https://github.com/actions/checkout/compare/v6.0.3...v7.0.0)

- Block checking out fork PR for pull\_request\_target and workflow\_run by [@&#8203;aiqiaoy](https://github.com/aiqiaoy) in [#&#8203;2454](https://github.com/actions/checkout/pull/2454)
- Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2458](https://github.com/actions/checkout/pull/2458)
- Bump flatted from 3.3.1 to 3.4.2 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2460](https://github.com/actions/checkout/pull/2460)
- Bump js-yaml from 4.1.0 to 4.2.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2461](https://github.com/actions/checkout/pull/2461)
- Bump [@&#8203;actions/core](https://github.com/actions/core) and [@&#8203;actions/tool-cache](https://github.com/actions/tool-cache) and Remove uuid by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2459](https://github.com/actions/checkout/pull/2459)
- upgrade module to esm and update dependencies by [@&#8203;aiqiaoy](https://github.com/aiqiaoy) in [#&#8203;2463](https://github.com/actions/checkout/pull/2463)
- Bump the minor-npm-dependencies group across 1 directory with 3 updates by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;2462](https://github.com/actions/checkout/pull/2462)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMjAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjIyMC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #152
Co-authored-by: Renovate Bot <renovate@unbound.se>
Co-committed-by: Renovate Bot <renovate@unbound.se>
2026-06-21 15:45:20 +00:00
releaser 4732aa5ab2 chore(release): prepare for v0.4.0 (#151)
Release / release (push) Successful in 57s
otelsetup / vulnerabilities (push) Successful in 1m29s
otelsetup / test (push) Successful in 2m31s
pre-commit / pre-commit (push) Successful in 5m11s
## [0.4.0] - 2026-06-16

### 🚀 Features

- *(metrics)* Add SubscriptionMetrics OTel observer for subscriptions (#150)

### 🐛 Bug Fixes

- *(deps)* Update opentelemetry-go monorepo (#146)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.91 (#148)

<!-- generated by git-cliff -->

---

**Note:** Please use **Squash Merge** when merging this PR.

Reviewed-on: #151
Co-authored-by: Unbound Releaser <releaser@unbound.se>
Co-committed-by: Unbound Releaser <releaser@unbound.se>
2026-06-16 13:22:52 +00:00
argoyle c00564cdcb feat(metrics): add SubscriptionMetrics OTel observer for subscriptions (#150)
Release / release (push) Successful in 1m31s
otelsetup / vulnerabilities (push) Successful in 2m8s
otelsetup / test (push) Successful in 2m47s
pre-commit / pre-commit (push) Successful in 6m47s
2026-06-16 13:14:14 +00:00
renovate 2836af0242 fix(deps): update module github.com/99designs/gqlgen to v0.17.91 (#148)
otelsetup / vulnerabilities (push) Successful in 1m39s
otelsetup / test (push) Successful in 2m24s
Release / release (push) Successful in 1m0s
pre-commit / pre-commit (push) Successful in 6m2s
2026-06-14 15:33:23 +00:00
renovate 212369811f fix(deps): update opentelemetry-go monorepo (#146)
Release / release (push) Failing after 1m3s
otelsetup / vulnerabilities (push) Successful in 1m39s
otelsetup / test (push) Successful in 2m23s
pre-commit / pre-commit (push) Successful in 6m1s
2026-05-31 12:07:55 +00:00
releaser afac050279 chore(release): prepare for v0.3.1 (#145)
Release / release (push) Successful in 52s
otelsetup / vulnerabilities (push) Successful in 1m51s
otelsetup / test (push) Successful in 2m27s
pre-commit / pre-commit (push) Successful in 6m30s
## [0.3.1] - 2026-05-29

### 🐛 Bug Fixes

- Set service.instance.id for unique instance label (#144)

<!-- generated by git-cliff -->

---

**Note:** Please use **Squash Merge** when merging this PR.

Reviewed-on: #145
Co-authored-by: Unbound Releaser <releaser@unbound.se>
Co-committed-by: Unbound Releaser <releaser@unbound.se>
2026-05-29 06:47:04 +00:00
argoyle afa847c76c fix: set service.instance.id for unique instance label (#144)
Release / release (push) Successful in 54s
pre-commit / pre-commit (push) Successful in 6m51s
otelsetup / vulnerabilities (push) Failing after 10m48s
otelsetup / test (push) Failing after 10m50s
2026-05-29 06:39:14 +00:00
releaser fff76c4acc chore(release): prepare for v0.3.0 (#143)
Release / release (push) Successful in 43s
otelsetup / vulnerabilities (push) Successful in 1m40s
otelsetup / test (push) Successful in 2m29s
pre-commit / pre-commit (push) Successful in 6m17s
## [0.3.0] - 2026-05-26

### 🚀 Features

- Add eventsourced MetricsRecorder adapter for OpenTelemetry (#142)

<!-- generated by git-cliff -->

---

**Note:** Please use **Squash Merge** when merging this PR.

Reviewed-on: #143
Co-authored-by: Unbound Releaser <releaser@unbound.se>
Co-committed-by: Unbound Releaser <releaser@unbound.se>
2026-05-26 18:04:49 +00:00
argoyle 7bc3101cee feat: add eventsourced MetricsRecorder adapter for OpenTelemetry (#142)
Release / release (push) Failing after 51s
otelsetup / test (push) Successful in 2m43s
otelsetup / vulnerabilities (push) Successful in 2m44s
pre-commit / pre-commit (push) Successful in 5m55s
2026-05-26 17:57:27 +00:00
releaser a8a8d53cd1 chore(release): prepare for v0.2.7 (#141)
Release / release (push) Successful in 45s
otelsetup / vulnerabilities (push) Successful in 1m44s
otelsetup / test (push) Successful in 2m32s
pre-commit / pre-commit (push) Successful in 6m25s
## [0.2.7] - 2026-05-09

### 🐛 Bug Fixes

- *(deps)* Update opentelemetry-go monorepo (#131)
- *(ci)* Use go-test-coverage binary directly to fix Gitea Actions (#134)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.90 (#133)

### ⚙️ Miscellaneous Tasks

- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.25.0 (#136)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.12.0 (#137)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.12.1 (#138)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.12.2 (#140)

<!-- generated by git-cliff -->

---

**Note:** Please use **Squash Merge** when merging this PR.

Reviewed-on: #141
Co-authored-by: Unbound Releaser <releaser@unbound.se>
Co-committed-by: Unbound Releaser <releaser@unbound.se>
2026-05-09 14:24:35 +00:00
renovate 485b8434d8 chore(deps): update pre-commit hook golangci/golangci-lint to v2.12.2 (#140)
otelsetup / vulnerabilities (push) Successful in 1m46s
otelsetup / test (push) Successful in 2m44s
Release / release (push) Successful in 53s
pre-commit / pre-commit (push) Successful in 6m55s
2026-05-09 12:33:24 +00:00
renovate 9dce38187c chore(deps): update pre-commit hook golangci/golangci-lint to v2.12.1 (#138)
Release / release (push) Successful in 1m12s
otelsetup / vulnerabilities (push) Successful in 1m56s
otelsetup / test (push) Successful in 2m48s
pre-commit / pre-commit (push) Successful in 6m24s
2026-05-04 16:16:10 +00:00
renovate 33f22237dc chore(deps): update pre-commit hook golangci/golangci-lint to v2.12.0 (#137)
otelsetup / vulnerabilities (push) Successful in 1m54s
Release / release (push) Failing after 57s
otelsetup / test (push) Successful in 2m54s
pre-commit / pre-commit (push) Successful in 5m47s
2026-05-04 14:22:17 +00:00
renovate 4bbd8b3e63 chore(deps): update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.25.0 (#136)
Release / release (push) Failing after 49s
otelsetup / vulnerabilities (push) Successful in 1m44s
otelsetup / test (push) Successful in 2m36s
pre-commit / pre-commit (push) Successful in 6m12s
2026-05-03 15:43:30 +00:00
renovate ec0ea2a287 fix(deps): update module github.com/99designs/gqlgen to v0.17.90 (#133)
otelsetup / vulnerabilities (push) Successful in 2m18s
otelsetup / test (push) Successful in 3m18s
Release / release (push) Successful in 1m2s
pre-commit / pre-commit (push) Successful in 6m15s
2026-04-29 06:18:29 +00:00
argoyle dc4bc4a98c fix(ci): use go-test-coverage binary directly to fix Gitea Actions (#134)
Release / release (push) Failing after 1m4s
otelsetup / vulnerabilities (push) Successful in 2m13s
otelsetup / test (push) Successful in 3m21s
pre-commit / pre-commit (push) Successful in 6m28s
## Summary

- `vladopajic/go-test-coverage@v2` (v2.18.5+, released 2026-04-26/27) restructured its composite action to pass inputs via env-var mapping. Gitea `act_runner` doesn't expand `${{ }}` expressions inside docker-action `env:` blocks reliably, so the literal string `${{ inputs.config }}` reached the binary and broke the 'Check coverage' step.
- Replace the action with a direct `go install` + binary invocation (matching the established Frostmoln pattern).
- Use `--github-action-output` to expose `total-coverage` as a step output, replacing the manual `go tool cover -func | grep | awk` calculations.
- Baseline artifact now stores the percentage directly instead of the full coverage profile.

## Test plan

- [x] `prek run --all-files` passes
- [ ] CI passes on this PR
- [ ] After merge, baseline artifact format propagates on next push to main

Reviewed-on: #134
2026-04-29 05:44:15 +00:00
renovate e92f853311 fix(deps): update opentelemetry-go monorepo (#131)
Release / release (push) Failing after 1m4s
otelsetup / vulnerabilities (push) Successful in 1m48s
otelsetup / test (push) Successful in 2m22s
pre-commit / pre-commit (push) Successful in 6m31s
2026-04-06 10:14:40 +00:00
releaser a6660fd70d chore(release): prepare for v0.2.6 (#130)
Release / release (push) Successful in 47s
otelsetup / test (push) Successful in 2m21s
otelsetup / vulnerabilities (push) Successful in 1m42s
pre-commit / pre-commit (push) Successful in 6m10s
## [0.2.6] - 2026-03-24

### 🐛 Bug Fixes

- *(deps)* Update module github.com/99designs/gqlgen to v0.17.88 (#121)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.89 (#129)

### ⚙️ Miscellaneous Tasks

- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.2 (#119)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.3 (#123)
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.30.1 (#125)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.4 (#127)

<!-- generated by git-cliff -->

---

**Note:** Please use **Squash Merge** when merging this PR.

Reviewed-on: #130
Co-authored-by: Unbound Releaser <releaser@unbound.se>
Co-committed-by: Unbound Releaser <releaser@unbound.se>
2026-04-02 09:15:03 +00:00
renovate cbf01d4546 fix(deps): update module github.com/99designs/gqlgen to v0.17.89 (#129)
Release / release (push) Successful in 54s
otelsetup / vulnerabilities (push) Successful in 1m48s
otelsetup / test (push) Successful in 2m39s
pre-commit / pre-commit (push) Successful in 6m7s
2026-03-24 19:16:50 +00:00
renovate cfab683ab8 chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.4 (#127)
otelsetup / vulnerabilities (push) Successful in 1m38s
otelsetup / test (push) Successful in 2m16s
Release / release (push) Successful in 57s
pre-commit / pre-commit (push) Successful in 6m28s
2026-03-22 19:05:30 +00:00
renovate 4a6c014206 chore(deps): update pre-commit hook gitleaks/gitleaks to v8.30.1 (#125)
Release / release (push) Successful in 1m26s
otelsetup / vulnerabilities (push) Successful in 2m8s
otelsetup / test (push) Successful in 3m37s
pre-commit / pre-commit (push) Successful in 7m46s
2026-03-12 16:23:48 +00:00
renovate 88ae10c9d7 chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.3 (#123)
Release / release (push) Successful in 2m0s
otelsetup / vulnerabilities (push) Successful in 2m15s
otelsetup / test (push) Successful in 3m5s
pre-commit / pre-commit (push) Successful in 8m44s
2026-03-10 11:23:11 +00:00
renovate f1cfd58922 fix(deps): update module github.com/99designs/gqlgen to v0.17.88 (#121)
Release / release (push) Successful in 56s
otelsetup / vulnerabilities (push) Successful in 1m51s
otelsetup / test (push) Successful in 2m24s
pre-commit / pre-commit (push) Successful in 7m7s
2026-03-09 01:40:04 +00:00
renovate d5528d3635 chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.2 (#119)
otelsetup / vulnerabilities (push) Successful in 1m35s
otelsetup / test (push) Successful in 2m39s
Release / release (push) Successful in 1m1s
pre-commit / pre-commit (push) Successful in 6m6s
2026-03-07 22:40:37 +00:00
releaser 15cff2329e chore(release): prepare for v0.2.5 (#118)
Release / release (push) Successful in 48s
otelsetup / vulnerabilities (push) Successful in 1m49s
otelsetup / test (push) Successful in 2m16s
pre-commit / pre-commit (push) Successful in 6m14s
## [0.2.5] - 2026-03-06

### 🐛 Bug Fixes

- *(deps)* Update opentelemetry-go monorepo to v1.42.0 (#117)

<!-- generated by git-cliff -->

---

**Note:** Please use **Squash Merge** when merging this PR.

Reviewed-on: #118
Co-authored-by: Unbound Releaser <releaser@unbound.se>
Co-committed-by: Unbound Releaser <releaser@unbound.se>
2026-03-06 20:36:08 +00:00
renovate 4e2f496f5d fix(deps): update opentelemetry-go monorepo to v1.42.0 (#117)
Release / release (push) Failing after 1m2s
otelsetup / test (push) Successful in 2m21s
otelsetup / vulnerabilities (push) Successful in 2m59s
pre-commit / pre-commit (push) Successful in 16m36s
2026-03-06 20:28:23 +00:00
releaser 2d55da0fb9 chore(release): prepare for v0.2.4 (#116)
otelsetup / vulnerabilities (push) Successful in 2m3s
otelsetup / test (push) Successful in 2m40s
Release / release (push) Successful in 1m11s
pre-commit / pre-commit (push) Successful in 7m42s
## [0.2.4] - 2026-03-06

### 🐛 Bug Fixes

- *(deps)* Update golang.org/x/net to v0.51.0
- *(deps)* Update opentelemetry-go monorepo (#110)
- *(deps)* Update opentelemetry-go monorepo (#115)

### ⚙️ Miscellaneous Tasks

- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.0 (#111)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.1 (#113)

<!-- generated by git-cliff -->

---

**Note:** Please use **Squash Merge** when merging this PR.

Reviewed-on: #116
Co-authored-by: Unbound Releaser <releaser@unbound.se>
Co-committed-by: Unbound Releaser <releaser@unbound.se>
2026-03-06 19:59:22 +00:00
renovate af15b97d05 fix(deps): update opentelemetry-go monorepo (#115)
Release / release (push) Successful in 1m4s
otelsetup / vulnerabilities (push) Successful in 1m45s
otelsetup / test (push) Successful in 2m32s
pre-commit / pre-commit (push) Successful in 6m32s
2026-03-06 19:41:00 +00:00
renovate 0bc2b48f20 chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.1 (#113)
Release / release (push) Successful in 1m13s
otelsetup / vulnerabilities (push) Successful in 1m40s
otelsetup / test (push) Successful in 2m51s
pre-commit / pre-commit (push) Successful in 6m10s
2026-03-06 15:41:43 +00:00
renovate 65a41418c6 chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.0 (#111)
Release / release (push) Successful in 3m13s
otelsetup / test (push) Successful in 3m20s
otelsetup / vulnerabilities (push) Successful in 4m20s
pre-commit / pre-commit (push) Successful in 9m20s
2026-03-06 14:16:24 +00:00
renovate 235083bb95 fix(deps): update opentelemetry-go monorepo (#110)
Release / release (push) Failing after 1m11s
otelsetup / vulnerabilities (push) Successful in 2m1s
otelsetup / test (push) Successful in 2m43s
pre-commit / pre-commit (push) Successful in 6m7s
2026-03-02 20:18:25 +00:00
argoyle 4ce727b275 Merge pull request 'fix(deps): update golang.org/x/net to v0.51.0' (#108) from fix-vulncheck-xnet into main
Release / release (push) Successful in 1m3s
otelsetup / vulnerabilities (push) Successful in 1m50s
otelsetup / test (push) Successful in 3m6s
pre-commit / pre-commit (push) Successful in 9m39s
Reviewed-on: #108
2026-02-28 23:20:56 +00:00
argoyle 54e7739ea5 fix(deps): update golang.org/x/net to v0.51.0
otelsetup / vulnerabilities (pull_request) Successful in 2m23s
otelsetup / test (pull_request) Successful in 4m47s
pre-commit / pre-commit (pull_request) Successful in 9m11s
Fixes GO-2026-4559: HTTP/2 server panic vulnerability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 23:57:54 +01:00
argoyle 8a0fd9c60d Merge pull request 'chore(release): prepare for v0.2.3' (#106) from next-release into main
Release / release (push) Successful in 1m3s
otelsetup / vulnerabilities (push) Successful in 2m31s
otelsetup / test (push) Successful in 4m36s
pre-commit / pre-commit (push) Successful in 7m49s
Reviewed-on: #106
2026-02-19 16:09:14 +00:00
releaser 94fb394035 chore(release): prepare for v0.2.3
otelsetup / vulnerabilities (pull_request) Successful in 6m49s
otelsetup / test (pull_request) Successful in 11m28s
pre-commit / pre-commit (pull_request) Successful in 29m57s
2026-02-19 15:36:53 +00:00
releaser c95ba47672 chore(release): prepare for v0.2.3 2026-02-19 15:36:32 +00:00
argoyle f96bd92aa7 Merge pull request 'fix(deps): update module github.com/99designs/gqlgen to v0.17.87' (#105) from renovate/github.com-99designs-gqlgen-0.x into main
Release / release (push) Successful in 4m25s
otelsetup / vulnerabilities (push) Successful in 6m23s
otelsetup / test (push) Successful in 9m26s
pre-commit / pre-commit (push) Successful in 10m38s
Reviewed-on: #105
2026-02-19 15:32:56 +00:00
renovate d7261826b4 fix(deps): update module github.com/99designs/gqlgen to v0.17.87
otelsetup / test (pull_request) Successful in 6m40s
pre-commit / pre-commit (pull_request) Successful in 9m52s
otelsetup / vulnerabilities (pull_request) Successful in 3m36s
2026-02-19 14:59:12 +00:00
renovate f7defd4f23 chore(deps): update pre-commit hook golangci/golangci-lint to v2.10.1 (#103)
Release / release (push) Successful in 4m13s
otelsetup / vulnerabilities (push) Successful in 4m18s
pre-commit / pre-commit (push) Successful in 7m9s
otelsetup / test (push) Successful in 11m22s
2026-02-17 17:34:05 +00:00
renovate 32933694ed chore(deps): update pre-commit hook golangci/golangci-lint to v2.10.0 (#102)
Release / release (push) Failing after 2m4s
otelsetup / vulnerabilities (push) Successful in 4m24s
pre-commit / pre-commit (push) Successful in 15m14s
otelsetup / test (push) Successful in 20m12s
2026-02-17 15:07:01 +00:00
renovate 7c44181434 chore(deps): update pre-commit hook golangci/golangci-lint to v2.9.0 (#100)
Release / release (push) Successful in 43s
otelsetup / vulnerabilities (push) Successful in 4m27s
otelsetup / test (push) Successful in 6m3s
pre-commit / pre-commit (push) Successful in 10m5s
2026-02-11 13:25:07 +00:00
argoyle 1572b26c7a Merge pull request 'chore(release): prepare for v0.2.2' (#95) from next-release into main
Release / release (push) Successful in 1m14s
otelsetup / vulnerabilities (push) Successful in 3m37s
otelsetup / test (push) Successful in 4m15s
pre-commit / pre-commit (push) Successful in 12m8s
Reviewed-on: #95
2026-02-03 19:53:11 +00:00
releaser b6045ca52b chore(release): prepare for v0.2.2
otelsetup / vulnerabilities (pull_request) Successful in 1m16s
otelsetup / test (pull_request) Successful in 7m33s
pre-commit / pre-commit (pull_request) Successful in 8m46s
2026-02-02 19:22:43 +00:00
releaser 094003d561 chore(release): prepare for v0.2.2
otelsetup / vulnerabilities (pull_request) Successful in 4m50s
otelsetup / test (pull_request) Successful in 7m19s
pre-commit / pre-commit (pull_request) Successful in 11m51s
2026-02-02 19:22:40 +00:00
releaser 9491c7db2a chore(release): prepare for v0.2.2 2026-02-02 19:22:37 +00:00
releaser 0d38fbd8bf chore(release): prepare for v0.2.2 2026-02-02 19:22:37 +00:00
releaser f2ed1a375b chore(release): prepare for v0.2.2 2026-02-02 19:22:37 +00:00
releaser db3eac9a0f chore(release): prepare for v0.2.2 2026-02-02 19:22:37 +00:00
releaser e6a193f514 chore(release): prepare for v0.2.2 2026-02-02 19:22:37 +00:00
releaser 4270e528b6 chore(release): prepare for v0.2.2 2026-02-02 19:22:37 +00:00
releaser c2f7b9e8d5 chore(release): prepare for v0.2.2 2026-02-02 19:22:37 +00:00
releaser fb62d12e35 chore(release): prepare for v0.2.2 2026-02-02 19:22:37 +00:00
renovate 4aa3e361b5 fix(deps): update opentelemetry-go monorepo (#99)
otelsetup / test (push) Successful in 1m46s
Release / release (push) Successful in 1m9s
otelsetup / vulnerabilities (push) Successful in 5m29s
pre-commit / pre-commit (push) Successful in 10m51s
2026-02-02 19:21:07 +00:00
renovate 639148e40a fix(deps): update opentelemetry-go monorepo to v1.40.0 (#98)
Release / release (push) Failing after 34s
otelsetup / vulnerabilities (push) Successful in 2m25s
otelsetup / test (push) Successful in 3m49s
pre-commit / pre-commit (push) Successful in 5m20s
2026-02-02 18:14:50 +00:00
argoyle ca8c3a9e1b Merge pull request 'ci: add code coverage integration' (#96) from ci-coverage-integration into main
Release / release (push) Successful in 3m26s
otelsetup / test (push) Successful in 5m40s
otelsetup / vulnerabilities (push) Successful in 6m13s
pre-commit / pre-commit (push) Successful in 8m46s
Reviewed-on: #96
2026-01-28 12:37:47 +00:00
argoyle 8540caba8c ci: add code coverage integration
otelsetup / vulnerabilities (pull_request) Successful in 2m49s
otelsetup / test (pull_request) Successful in 4m11s
pre-commit / pre-commit (pull_request) Successful in 6m22s
Add go-test-coverage for coverage threshold enforcement. Coverage data
is uploaded as artifacts on main branch and compared against baseline
in PRs using shell script that gracefully handles first run without
baseline. PR comments show coverage percentage.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 13:03:07 +01:00
argoyle a66446b1df Merge pull request 'chore: remove GitLab CI configuration' (#94) from remove-gitlab-ci into main
Release / release (push) Successful in 2m29s
otelsetup / vulnerabilities (push) Successful in 6m43s
otelsetup / test (push) Successful in 8m58s
pre-commit / pre-commit (push) Successful in 18m53s
Reviewed-on: #94
2026-01-19 06:54:59 +00:00
argoyle 06aaa0c202 chore: remove GitLab CI configuration
otelsetup / vulnerabilities (pull_request) Successful in 1m43s
otelsetup / test (pull_request) Successful in 7m31s
pre-commit / pre-commit (pull_request) Successful in 8m33s
2026-01-18 20:36:15 +01:00
renovate bfbe6a09e5 chore(deps): update golang docker tag to v1.25.6 (#93)
Release / release (push) Failing after 41s
otelsetup / vulnerabilities (push) Successful in 1m39s
otelsetup / test (push) Successful in 2m48s
pre-commit / pre-commit (push) Successful in 4m7s
2026-01-17 18:39:29 +00:00
renovate 026a08d54e chore(deps): update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.24.0 (#92)
Release / release (push) Failing after 1m14s
otelsetup / test (push) Successful in 2m40s
otelsetup / vulnerabilities (push) Successful in 3m21s
pre-commit / pre-commit (push) Successful in 10m9s
2026-01-13 21:31:21 +00:00
renovate 10282596e2 chore(deps): update golang:1.25.5 docker digest to 3a01526 (#91)
Release / release (push) Failing after 1m10s
otelsetup / vulnerabilities (push) Successful in 6m7s
otelsetup / test (push) Successful in 8m16s
pre-commit / pre-commit (push) Successful in 8m56s
2026-01-13 06:22:24 +00:00
renovate 8271687580 fix(deps): update module github.com/99designs/gqlgen to v0.17.86 (#90)
Release / release (push) Failing after 2m27s
otelsetup / vulnerabilities (push) Successful in 6m19s
otelsetup / test (push) Successful in 7m0s
pre-commit / pre-commit (push) Successful in 9m23s
2026-01-12 02:20:08 +00:00
renovate ae2ca7265b fix(deps): update module github.com/99designs/gqlgen to v0.17.86 (#90)
otelsetup / test (push) Has been cancelled
otelsetup / vulnerabilities (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Release / release (push) Has been cancelled
2026-01-12 02:20:04 +00:00
14 changed files with 658 additions and 121 deletions
+53 -2
View File
@@ -10,17 +10,68 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Run tests
run: go test -race -coverprofile=coverage.txt ./...
- name: Check coverage
id: coverage
run: |
go install github.com/vladopajic/go-test-coverage/v2@latest
go-test-coverage --config ./.testcoverage.yml --github-action-output
- name: Download baseline coverage
if: gitea.event_name == 'pull_request'
uses: actions/download-artifact@v3
with:
name: coverage-baseline
path: ./baseline
continue-on-error: true
- name: Compare coverage
if: gitea.event_name == 'pull_request'
run: |
CURRENT="${{ steps.coverage.outputs.total-coverage }}"
if [ -f ./baseline/coverage.txt ]; then
BASE=$(cat ./baseline/coverage.txt)
echo "Base coverage: ${BASE}%"
echo "Current coverage: ${CURRENT}%"
if [ "$(echo "$CURRENT < $BASE" | bc -l)" -eq 1 ]; then
echo "::error::Coverage decreased from ${BASE}% to ${CURRENT}%"
exit 1
fi
echo "Coverage maintained or improved: ${BASE}% -> ${CURRENT}%"
else
echo "No baseline coverage found, skipping comparison"
echo "Current coverage: ${CURRENT}%"
fi
- name: Save coverage baseline
if: gitea.ref == 'refs/heads/main'
run: echo "${{ steps.coverage.outputs.total-coverage }}" > coverage.txt
- name: Upload coverage baseline
if: gitea.ref == 'refs/heads/main'
uses: actions/upload-artifact@v3
with:
name: coverage-baseline
path: coverage.txt
retention-days: 90
- name: Post coverage comment
if: gitea.event_name == 'pull_request'
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_URL: ${{ gitea.server_url }}
run: |
COVERAGE="${{ steps.coverage.outputs.total-coverage }}"
curl -X POST "${GITEA_URL}/api/v1/repos/${{ gitea.repository }}/issues/${{ gitea.event.pull_request.number }}/comments" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\": \"## Coverage Report\n\nTotal coverage: **${COVERAGE}%**\"}"
vulnerabilities:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version: 'stable'
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
env:
SKIP: no-commit-to-branch
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version: stable
-38
View File
@@ -1,38 +0,0 @@
include:
- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'
- project: unboundsoftware/ci-templates
file: Defaults.gitlab-ci.yml
- project: unboundsoftware/ci-templates
file: Release.gitlab-ci.yml
- project: unboundsoftware/ci-templates
file: Pre-Commit-Go.gitlab-ci.yml
image: amd64/golang:1.25.5@sha256:ad03ba93327b8a6143b49373790b5d92c28067bdb814418509466122ee9c9e63
stages:
- deps
- test
deps:
stage: deps
script:
- go mod download
test:
stage: test
dependencies:
- deps
script:
- CGO_ENABLED=1 go test -mod=readonly -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=$(go list ./... | tr '\n' , | sed 's/,$//') ./...
- go tool cover -html=coverage.txt -o coverage.html
- go tool cover -func=coverage.txt
- curl -Os https://uploader.codecov.io/latest/linux/codecov
- chmod +x codecov
- ./codecov -t ${CODECOV_TOKEN} -R $CI_PROJECT_DIR -C $CI_COMMIT_SHA -r $CI_PROJECT_PATH
vulnerabilities:
stage: test
image: amd64/golang:1.25.5@sha256:ad03ba93327b8a6143b49373790b5d92c28067bdb814418509466122ee9c9e63
script:
- go install golang.org/x/vuln/cmd/govulncheck@latest
- govulncheck ./...
+3 -3
View File
@@ -11,7 +11,7 @@ repos:
- --allow-multiple-documents
- id: check-added-large-files
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.23.0
rev: v9.25.0
hooks:
- id: commitlint
stages: [ commit-msg ]
@@ -30,10 +30,10 @@ repos:
- id: go-test
- id: gofumpt
- repo: https://github.com/golangci/golangci-lint
rev: v2.8.0
rev: v2.12.2
hooks:
- id: golangci-lint-full
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.0
rev: v8.30.1
hooks:
- id: gitleaks
+13
View File
@@ -0,0 +1,13 @@
# Coverage configuration for go-test-coverage
# https://github.com/vladopajic/go-test-coverage
profile: coverage.txt
threshold:
file: 0
package: 0
total: 0
exclude:
paths:
- _test\.go$
+1 -1
View File
@@ -1,3 +1,3 @@
{
"version": "v0.2.1"
"version": "v0.4.1"
}
+106
View File
@@ -2,6 +2,112 @@
All notable changes to this project will be documented in this file.
## [0.4.1] - 2026-06-21
### ⚙️ Miscellaneous Tasks
- *(deps)* Update actions/checkout action to v7 (#152)
## [0.4.0] - 2026-06-16
### 🚀 Features
- *(metrics)* Add SubscriptionMetrics OTel observer for subscriptions (#150)
### 🐛 Bug Fixes
- *(deps)* Update opentelemetry-go monorepo (#146)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.91 (#148)
## [0.3.1] - 2026-05-29
### 🐛 Bug Fixes
- Set service.instance.id for unique instance label (#144)
## [0.3.0] - 2026-05-26
### 🚀 Features
- Add eventsourced MetricsRecorder adapter for OpenTelemetry (#142)
## [0.2.7] - 2026-05-09
### 🐛 Bug Fixes
- *(deps)* Update opentelemetry-go monorepo (#131)
- *(ci)* Use go-test-coverage binary directly to fix Gitea Actions (#134)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.90 (#133)
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.25.0 (#136)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.12.0 (#137)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.12.1 (#138)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.12.2 (#140)
## [0.2.6] - 2026-04-02
### 🐛 Bug Fixes
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.88 (#121)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.89 (#129)
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.2 (#119)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.3 (#123)
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.30.1 (#125)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.4 (#127)
## [0.2.5] - 2026-03-06
### 🐛 Bug Fixes
- *(deps)* Update opentelemetry-go monorepo to v1.42.0 (#117)
## [0.2.4] - 2026-03-06
### 🐛 Bug Fixes
- *(deps)* Update golang.org/x/net to v0.51.0
- *(deps)* Update opentelemetry-go monorepo (#110)
- *(deps)* Update opentelemetry-go monorepo (#115)
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.0 (#111)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.1 (#113)
## [0.2.3] - 2026-02-19
### 🐛 Bug Fixes
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.87
### ⚙️ Miscellaneous Tasks
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.9.0 (#100)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.10.0 (#102)
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.10.1 (#103)
## [0.2.2] - 2026-02-03
### 🐛 Bug Fixes
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.86 (#90)
- *(deps)* Update module github.com/99designs/gqlgen to v0.17.86 (#90)
- *(deps)* Update opentelemetry-go monorepo to v1.40.0 (#98)
- *(deps)* Update opentelemetry-go monorepo (#99)
### ⚙️ Miscellaneous Tasks
- *(deps)* Update golang:1.25.5 docker digest to 3a01526 (#91)
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.24.0 (#92)
- *(deps)* Update golang docker tag to v1.25.6 (#93)
- Remove GitLab CI configuration
- Add code coverage integration
## [0.2.1] - 2026-01-09
### ⚙️ Miscellaneous Tasks
+25 -23
View File
@@ -1,18 +1,20 @@
module gitea.unbound.se/shiny/otelsetup
go 1.24.3
go 1.25.0
require (
github.com/99designs/gqlgen v0.17.85
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0
go.opentelemetry.io/otel/log v0.15.0
go.opentelemetry.io/otel/sdk v1.39.0
go.opentelemetry.io/otel/sdk/log v0.15.0
go.opentelemetry.io/otel/sdk/metric v1.39.0
go.opentelemetry.io/otel/trace v1.39.0
github.com/99designs/gqlgen v0.17.91
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.23.0
go.opentelemetry.io/otel v1.44.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.20.0
go.opentelemetry.io/otel/log v0.20.0
go.opentelemetry.io/otel/metric v1.44.0
go.opentelemetry.io/otel/sdk v1.44.0
go.opentelemetry.io/otel/sdk/log v0.20.0
go.opentelemetry.io/otel/sdk/metric v1.44.0
go.opentelemetry.io/otel/trace v1.44.0
)
require (
@@ -21,18 +23,18 @@ require (
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/vektah/gqlparser/v2 v2.5.31 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
github.com/sosodev/duration v1.4.0 // indirect
github.com/vektah/gqlparser/v2 v2.5.34 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.77.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
golang.org/x/net v0.55.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
google.golang.org/grpc v1.81.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
+54 -52
View File
@@ -1,9 +1,7 @@
github.com/99designs/gqlgen v0.17.85 h1:EkGx3U2FDcxQm8YDLQSpXIAVmpDyZ3IcBMOJi2nH1S0=
github.com/99designs/gqlgen v0.17.85/go.mod h1:yvs8s0bkQlRfqg03YXr3eR4OQUowVhODT/tHzCXnbOU=
github.com/99designs/gqlgen v0.17.91 h1:/mIvXnN0lAorqszP3Vukw10SVRfLVUYtBTQFwmYRMmI=
github.com/99designs/gqlgen v0.17.91/go.mod h1:N7+yJF6zbGIEqohF+ZtEUp/eq2dTnn0bDizLUIYPUCU=
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -21,62 +19,66 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE=
github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k=
github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
github.com/vektah/gqlparser/v2 v2.5.34 h1:MEea5P0qhdcqfBL45ghKE+qr9laidVHTMHjav5h7ckk=
github.com/vektah/gqlparser/v2 v2.5.34/go.mod h1:mFdHLGCio7OGX1fby9ZjTW6FN+qxgmbnBcRIeeScE5s=
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.23.0 h1:qcteJH9D7kHaOgLQ0fzlW9dv42hSa0Vluqt7p4kooWA=
gitlab.com/unboundsoftware/eventsourced/eventsourced v1.23.0/go.mod h1:LrA7I7etRmhIC1PjO8c26BHm+gWsy2rC3eSMe5+XUWE=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0 h1:0BSddrtQqLEylcErkeFrJBmwFzcqfQq9+/uxfTZq+HE=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.15.0/go.mod h1:87sjYuAPzaRCtdd09GU5gM1U9wQLrrcYrm77mh5EBoc=
go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY=
go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE=
go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ=
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 h1:RuynHbfU8JUEw7DyONgkVYg2SVtsoF28y0LGIr69jgA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0/go.mod h1:qZF+/lBs71APw8mlnEZcqZHMzqrYrsFiJOv83lX1OGo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.20.0 h1:aZfdmtI6QU/DAPD4b7YZ5zuJgewxO1EW9miOZklqleU=
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.20.0/go.mod h1:isNl10/Om5CBWu9jj8WOb2+tJLbCVXDgqwzCaJMnJ6w=
go.opentelemetry.io/otel/log v0.20.0 h1:/5i0vuHxCLWUfChWG41K9wkM0jafruPw9NU1/RCJirs=
go.opentelemetry.io/otel/log v0.20.0/go.mod h1:wOcMcjsZpG8x7Bak7IhSi/lg8wscV2C1VdrKCLPlt0E=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/log v0.20.0 h1:vM3xI7TQgKPiSghe6urZtAkyFY7SodrSpC83CffDFuY=
go.opentelemetry.io/otel/sdk/log v0.20.0/go.mod h1:Knej2nmsTUzN79T2eeXdRsjjPcoxoq2pUyUHz9TFyyU=
go.opentelemetry.io/otel/sdk/log/logtest v0.20.0 h1:OqdRZ1guyzamK3M6LlRsmGqRrjkHWw6WZOKKli5ELpg=
go.opentelemetry.io/otel/sdk/log/logtest v0.20.0/go.mod h1:PuMIlm7zAt7c3z8zfOI5ox4iT1Z87We+PF6YoINux/M=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+123
View File
@@ -0,0 +1,123 @@
package otelsetup
import (
"context"
"errors"
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
// eventsourcedMeterName is the instrumentation scope for the event-sourcing
// metrics emitted by the adapter returned from NewEventsourcedMetrics.
const eventsourcedMeterName = "gitea.unbound.se/shiny/otelsetup/eventsourced"
// durationBucketsSeconds are explicit histogram boundaries tuned for
// sub-second event-store and command latencies. The SDK default boundaries are
// scaled for milliseconds, which would bucket nearly every second-valued
// observation into the first bucket and make percentiles useless.
var durationBucketsSeconds = []float64{0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}
// eventsourcedMetrics implements eventsourced.MetricsRecorder by translating
// the framework's Metric values into OpenTelemetry instruments registered on
// the global MeterProvider configured by SetupOTelSDK.
//
// The OTel metric instruments are safe for concurrent use, and the struct is
// immutable after construction, so Record may be called from multiple
// goroutines as the framework requires.
//
// Operation counts are read off each duration histogram's generated _count
// series rather than separate counters; the only standalone counters carry
// information a histogram count cannot (events.loaded sums the number of events
// per load, idempotency.checks counts lookups that have no duration).
type eventsourcedMetrics struct {
commandDuration metric.Float64Histogram
eventStoreDur metric.Float64Histogram
eventsLoaded metric.Int64Counter
eventLoadDur metric.Float64Histogram
snapshotStoreDur metric.Float64Histogram
snapshotLoadDur metric.Float64Histogram
idempotencyCheck metric.Int64Counter
}
// NewEventsourcedMetrics builds an eventsourced.MetricsRecorder that records to
// the global OpenTelemetry MeterProvider. Pass the result to both
// pg.WithMetrics (for event-store operations) and eventsourced.WithMetrics
// (for command handling) so a single recorder covers store and handler
// metrics.
//
// SetupOTelSDK must have run first so the global MeterProvider is configured;
// when metrics are disabled the global provider is a no-op and recording is
// effectively free.
func NewEventsourcedMetrics() (eventsourced.MetricsRecorder, error) {
m := otel.Meter(eventsourcedMeterName)
var errs []error
hist := func(name, desc string) metric.Float64Histogram {
h, err := m.Float64Histogram(
name,
metric.WithDescription(desc),
metric.WithUnit("s"),
metric.WithExplicitBucketBoundaries(durationBucketsSeconds...),
)
errs = append(errs, err)
return h
}
counter := func(name, desc string) metric.Int64Counter {
c, err := m.Int64Counter(name, metric.WithDescription(desc))
errs = append(errs, err)
return c
}
r := &eventsourcedMetrics{
commandDuration: hist("eventsourced.command.duration", "Wall-clock time to process a command in Handle."),
eventStoreDur: hist("eventsourced.event.store.duration", "Time taken to persist a single event."),
eventsLoaded: counter("eventsourced.events.loaded", "Number of events loaded when rehydrating aggregates."),
eventLoadDur: hist("eventsourced.event.load.duration", "Time taken to load events for an aggregate."),
snapshotStoreDur: hist("eventsourced.snapshot.store.duration", "Time taken to persist a snapshot."),
snapshotLoadDur: hist("eventsourced.snapshot.load.duration", "Time taken to load a snapshot."),
idempotencyCheck: counter("eventsourced.idempotency.checks", "Number of command idempotency lookups."),
}
if err := errors.Join(errs...); err != nil {
return nil, err
}
return r, nil
}
// Record implements eventsourced.MetricsRecorder. Metric types the adapter does
// not recognise (for example pg outbox metrics when the outbox is not enabled)
// are ignored.
func (e *eventsourcedMetrics) Record(ctx context.Context, raw eventsourced.Metric) {
switch m := raw.(type) {
case eventsourced.CommandDuration:
e.commandDuration.Record(ctx, m.Duration.Seconds(), metric.WithAttributes(
attribute.String("command.type", m.CommandType),
attribute.Bool("success", m.Success),
))
case eventsourced.EventStored:
e.eventStoreDur.Record(ctx, m.Duration.Seconds(), metric.WithAttributes(
attribute.String("aggregate.type", m.AggregateType),
attribute.String("event.type", m.EventType),
))
case eventsourced.EventsLoaded:
attrs := metric.WithAttributes(attribute.String("aggregate.type", m.AggregateType))
e.eventsLoaded.Add(ctx, int64(m.EventCount), attrs)
e.eventLoadDur.Record(ctx, m.Duration.Seconds(), attrs)
case eventsourced.SnapshotStored:
e.snapshotStoreDur.Record(ctx, m.Duration.Seconds(), metric.WithAttributes(
attribute.String("aggregate.type", m.AggregateType),
attribute.Bool("success", m.Success),
))
case eventsourced.SnapshotLoaded:
e.snapshotLoadDur.Record(ctx, m.Duration.Seconds(), metric.WithAttributes(
attribute.String("aggregate.type", m.AggregateType),
attribute.Bool("found", m.Found),
))
case eventsourced.IdempotencyCheck:
e.idempotencyCheck.Add(ctx, 1, metric.WithAttributes(
attribute.String("aggregate.type", m.AggregateType),
attribute.Bool("hit", m.Hit),
))
}
}
+75
View File
@@ -0,0 +1,75 @@
package otelsetup
import (
"context"
"sort"
"testing"
"time"
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
"go.opentelemetry.io/otel"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
func TestNewEventsourcedMetrics_RecordsContract(t *testing.T) {
reader := sdkmetric.NewManualReader()
otel.SetMeterProvider(sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)))
r, err := NewEventsourcedMetrics()
if err != nil {
t.Fatalf("NewEventsourcedMetrics returned error: %v", err)
}
if r == nil {
t.Fatal("NewEventsourcedMetrics returned nil recorder")
}
// Recording every known metric type (and an unknown one) must not panic
// and must emit the expected instruments.
for _, m := range []eventsourced.Metric{
eventsourced.CommandDuration{CommandType: "AddEntry", Duration: time.Millisecond, Success: true},
eventsourced.EventStored{AggregateType: "Entry", EventType: "EntryAdded", Duration: time.Millisecond},
eventsourced.EventsLoaded{AggregateType: "Entry", EventCount: 3, Duration: time.Millisecond},
eventsourced.SnapshotStored{AggregateType: "Entry", Duration: time.Millisecond, Success: true},
eventsourced.SnapshotLoaded{AggregateType: "Entry", Found: false, Duration: time.Millisecond},
eventsourced.IdempotencyCheck{AggregateType: "Entry", Hit: true},
unknownMetric{},
} {
r.Record(context.Background(), m)
}
var rm metricdata.ResourceMetrics
if err := reader.Collect(context.Background(), &rm); err != nil {
t.Fatalf("collect: %v", err)
}
got := map[string]bool{}
for _, sm := range rm.ScopeMetrics {
for _, md := range sm.Metrics {
got[md.Name] = true
}
}
want := []string{
"eventsourced.command.duration",
"eventsourced.event.store.duration",
"eventsourced.events.loaded",
"eventsourced.event.load.duration",
"eventsourced.snapshot.store.duration",
"eventsourced.snapshot.load.duration",
"eventsourced.idempotency.checks",
}
var missing []string
for _, w := range want {
if !got[w] {
missing = append(missing, w)
}
}
if len(missing) > 0 {
sort.Strings(missing)
t.Errorf("missing expected metrics: %v", missing)
}
}
type unknownMetric struct{}
func (unknownMetric) IsMetric() {}
+11 -1
View File
@@ -22,7 +22,17 @@ import (
// SetupOTelSDK bootstraps the OpenTelemetry pipeline.
func SetupOTelSDK(ctx context.Context, enabled bool, serviceName, buildVersion, environment string) (func(context.Context) error, error) {
if os.Getenv("OTEL_RESOURCE_ATTRIBUTES") == "" {
if err := os.Setenv("OTEL_RESOURCE_ATTRIBUTES", fmt.Sprintf("service.name=%s,service.version=%s,service.environment=%s", serviceName, buildVersion, environment)); err != nil {
// service.instance.id makes every pod a distinct telemetry resource. The
// OTLP→Prometheus exporter maps it to the `instance` label on metrics and
// target_info, which keeps multi-replica services from colliding on a
// single series and gives joins a unique (job, instance) key. Hostname is
// the pod name under Kubernetes; fall back to the service name if it is
// unavailable so the attribute is always present.
instanceID, err := os.Hostname()
if err != nil || instanceID == "" {
instanceID = serviceName
}
if err := os.Setenv("OTEL_RESOURCE_ATTRIBUTES", fmt.Sprintf("service.name=%s,service.version=%s,service.environment=%s,service.instance.id=%s", serviceName, buildVersion, environment, instanceID)); err != nil {
return func(context.Context) error {
return nil
}, err
+85
View File
@@ -0,0 +1,85 @@
package otelsetup
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
// subscriptionMeterName is the instrumentation scope for the cross-service
// subscription push metrics emitted by SubscriptionMetrics.
const subscriptionMeterName = "gitea.unbound.se/shiny/otelsetup/subscriptions"
// SubscriptionMetrics records cross-service subscription push outcomes (the
// read-your-writes subscriptions described in ADR-0012) as OpenTelemetry
// counters.
//
// Its method set (Pushed/PushSkipped/Dropped/ChannelFull) satisfies the
// Observer interface of gitea.unbound.se/shiny/subscriptions *structurally* —
// otelsetup does not import that library. A service constructs it and passes it
// to the registry:
//
// metrics, err := otelsetup.NewSubscriptionMetrics("availableCompanies")
// if err != nil { return err }
// reg := subscriptions.New[T](subscriptions.WithObserver(metrics))
//
// The WithObserver call type-checks the structural match, so a drift in the
// Observer interface fails the service build. Add a
// `var _ subscriptions.Observer = (*otelsetup.SubscriptionMetrics)(nil)` next to
// it for an explicit guard.
//
// Outcomes are recorded against a low-cardinality (subscription, outcome) pair.
// The subscriber key (company id / user email) the Observer methods receive is
// deliberately NOT used as a metric attribute — that would be unbounded
// cardinality; the key stays in the subscriptions library's logs for
// correlation.
type SubscriptionMetrics struct {
notifications metric.Int64Counter
subscription attribute.KeyValue
}
// NewSubscriptionMetrics builds a SubscriptionMetrics that records to the global
// OpenTelemetry MeterProvider. subscription is the low-cardinality name of the
// subscription field (e.g. "availableCompanies", "entryBasesChanged"), recorded
// as an attribute so one counter covers every subscription.
//
// SetupOTelSDK must have run first so the global MeterProvider is configured;
// when metrics are disabled the global provider is a no-op and recording is
// effectively free.
func NewSubscriptionMetrics(subscription string) (*SubscriptionMetrics, error) {
c, err := otel.Meter(subscriptionMeterName).Int64Counter(
"subscription.notifications",
metric.WithDescription("Cross-service subscription push outcomes, by outcome (pushed/skipped/dropped/channel_full)."),
)
if err != nil {
return nil, err
}
return &SubscriptionMetrics{
notifications: c,
subscription: attribute.String("subscription", subscription),
}, nil
}
func (s *SubscriptionMetrics) record(outcome string) {
s.notifications.Add(context.Background(), 1, metric.WithAttributes(
s.subscription,
attribute.String("outcome", outcome),
))
}
// Pushed records a change that was gated and delivered to the key's subscribers
// — the denominator for a skip/drop rate.
func (s *SubscriptionMetrics) Pushed(string) { s.record("pushed") }
// PushSkipped records a push skipped because the read view never reflected the
// change within the retry budget.
func (s *SubscriptionMetrics) PushSkipped(string) { s.record("skipped") }
// Dropped records a notification dropped because the worker queue was full.
func (s *SubscriptionMetrics) Dropped(string) { s.record("dropped") }
// ChannelFull records a notification dropped because a subscriber's buffer was
// full.
func (s *SubscriptionMetrics) ChannelFull(string) { s.record("channel_full") }
+108
View File
@@ -0,0 +1,108 @@
package otelsetup
import (
"context"
"testing"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric/noop"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
// observerShape mirrors gitea.unbound.se/shiny/subscriptions.Observer. This
// compile-time assertion guards that SubscriptionMetrics still satisfies that
// interface structurally, without otelsetup importing the library.
//
// KEEP IN SYNC with subscriptions.Observer: this only proves SubscriptionMetrics
// matches this local copy. The authoritative check that the local copy still
// matches the real interface is the `subscriptions.WithObserver(...)` call site
// in each consuming service — keep a `var _ subscriptions.Observer` guard there.
type observerShape interface {
Pushed(string)
PushSkipped(string)
Dropped(string)
ChannelFull(string)
}
var _ observerShape = (*SubscriptionMetrics)(nil)
// TestSubscriptionMetrics_DisabledProviderIsSafe proves the "recording is free
// when metrics are disabled" claim: with the default global no-op provider, the
// methods neither panic nor emit instruments.
func TestSubscriptionMetrics_DisabledProviderIsSafe(t *testing.T) {
otel.SetMeterProvider(noop.NewMeterProvider())
m, err := NewSubscriptionMetrics("entryBasesChanged")
if err != nil {
t.Fatalf("NewSubscriptionMetrics returned error: %v", err)
}
// Must not panic on the no-op provider.
m.Pushed("c1")
m.PushSkipped("c1")
m.Dropped("c1")
m.ChannelFull("c1")
}
func TestNewSubscriptionMetrics_RecordsOutcomes(t *testing.T) {
reader := sdkmetric.NewManualReader()
otel.SetMeterProvider(sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)))
m, err := NewSubscriptionMetrics("availableCompanies")
if err != nil {
t.Fatalf("NewSubscriptionMetrics returned error: %v", err)
}
// The subscriber key is ignored for metrics; different keys must not create
// new series (cardinality guard is implicit — we only label by outcome).
m.Pushed("c1")
m.Pushed("c2")
m.PushSkipped("c1")
m.Dropped("c1")
m.ChannelFull("c1")
var rm metricdata.ResourceMetrics
if err := reader.Collect(context.Background(), &rm); err != nil {
t.Fatalf("collect: %v", err)
}
counts := map[string]int64{}
dataPoints := 0
found := false
for _, sm := range rm.ScopeMetrics {
for _, md := range sm.Metrics {
if md.Name != "subscription.notifications" {
continue
}
found = true
sum, ok := md.Data.(metricdata.Sum[int64])
if !ok {
t.Fatalf("expected Sum[int64], got %T", md.Data)
}
for _, dp := range sum.DataPoints {
dataPoints++
sub, _ := dp.Attributes.Value(attribute.Key("subscription"))
if sub.AsString() != "availableCompanies" {
t.Errorf("unexpected subscription attribute: %q", sub.AsString())
}
outcome, _ := dp.Attributes.Value(attribute.Key("outcome"))
counts[outcome.AsString()] += dp.Value
}
}
}
if !found {
t.Fatal("subscription.notifications counter not emitted")
}
// One series per outcome, keyed by outcome only (not by subscriber key).
if dataPoints != 4 {
t.Errorf("expected 4 data points (one per outcome), got %d", dataPoints)
}
want := map[string]int64{"pushed": 2, "skipped": 1, "dropped": 1, "channel_full": 1}
for k, v := range want {
if counts[k] != v {
t.Errorf("outcome %q: got %d, want %d", k, counts[k], v)
}
}
}