Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 852056615e | |||
| a705fc33c4 | |||
| e067a72887 | |||
| 35f439eff5 | |||
| 8e3cebc6e8 |
@@ -0,0 +1,11 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 2
|
||||||
@@ -20,7 +20,79 @@ jobs:
|
|||||||
go install mvdan.cc/gofumpt@latest
|
go install mvdan.cc/gofumpt@latest
|
||||||
test -z "$(gofumpt -l .)"
|
test -z "$(gofumpt -l .)"
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: go test -race ./...
|
run: go test -race -coverprofile=coverage.txt ./...
|
||||||
|
|
||||||
|
- name: Filter test files from coverage
|
||||||
|
run: |
|
||||||
|
grep -v -E '_test\.go:' coverage.txt > coverage.filtered.txt || true
|
||||||
|
mv coverage.filtered.txt 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: Restore baseline coverage
|
||||||
|
uses: actions/cache/restore@v5
|
||||||
|
with:
|
||||||
|
path: coverage-baseline.txt
|
||||||
|
key: coverage-baseline-${{ gitea.run_id }}
|
||||||
|
restore-keys: |
|
||||||
|
coverage-baseline-
|
||||||
|
- name: Compare coverage
|
||||||
|
run: |
|
||||||
|
CURRENT="${{ steps.coverage.outputs.total-coverage }}"
|
||||||
|
if [ -f coverage-baseline.txt ]; then
|
||||||
|
BASE=$(cat coverage-baseline.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 yet, skipping comparison"
|
||||||
|
echo "Current coverage: ${CURRENT}%"
|
||||||
|
fi
|
||||||
|
- name: Post coverage comment
|
||||||
|
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}%**\"}"
|
||||||
|
|
||||||
|
coverage-baseline:
|
||||||
|
# Records main's coverage into the Actions cache for the next PR's
|
||||||
|
# regression gate to read. Post-merge only, not a required check, blocks
|
||||||
|
# nothing (cf. ADR-0010).
|
||||||
|
if: gitea.event_name == 'push'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: 'stable'
|
||||||
|
- name: Compute coverage
|
||||||
|
id: coverage
|
||||||
|
run: |
|
||||||
|
go install github.com/vladopajic/go-test-coverage/v2@latest
|
||||||
|
go test -coverprofile=coverage.txt ./...
|
||||||
|
grep -v -E '_test\.go:' coverage.txt > coverage.filtered.txt || true
|
||||||
|
mv coverage.filtered.txt coverage.txt
|
||||||
|
go-test-coverage --config ./.testcoverage.yml --github-action-output
|
||||||
|
- name: Write baseline file
|
||||||
|
run: echo "${{ steps.coverage.outputs.total-coverage }}" > coverage-baseline.txt
|
||||||
|
- name: Save baseline to cache
|
||||||
|
uses: actions/cache/save@v5
|
||||||
|
with:
|
||||||
|
path: coverage-baseline.txt
|
||||||
|
key: coverage-baseline-${{ gitea.run_id }}
|
||||||
|
|
||||||
vulnerabilities:
|
vulnerabilities:
|
||||||
if: gitea.event_name == 'pull_request'
|
if: gitea.event_name == 'pull_request'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
name: pre-commit
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre-commit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
SKIP: no-commit-to-branch
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: stable
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.14'
|
||||||
|
- name: Install goimports
|
||||||
|
run: go install golang.org/x/tools/cmd/goimports@latest
|
||||||
|
- uses: pre-commit/action@v3.0.1
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
uses: unboundsoftware/shared-workflows/.gitea/workflows/Release.yml@main
|
||||||
@@ -2,3 +2,4 @@
|
|||||||
.claude
|
.claude
|
||||||
/release
|
/release
|
||||||
coverage.txt
|
coverage.txt
|
||||||
|
coverage-baseline.txt
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
version: "2"
|
||||||
|
run:
|
||||||
|
allow-parallel-runners: true
|
||||||
|
linters:
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- comments
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
formatters:
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v6.0.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
args:
|
||||||
|
- --allow-multiple-documents
|
||||||
|
- id: check-added-large-files
|
||||||
|
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
|
||||||
|
rev: v9.25.0
|
||||||
|
hooks:
|
||||||
|
- id: commitlint
|
||||||
|
stages: [ commit-msg ]
|
||||||
|
additional_dependencies: [ '@commitlint/config-conventional' ]
|
||||||
|
- repo: https://github.com/dnephin/pre-commit-golang
|
||||||
|
rev: v0.5.1
|
||||||
|
hooks:
|
||||||
|
- id: go-mod-tidy
|
||||||
|
- id: go-imports
|
||||||
|
args:
|
||||||
|
- -local
|
||||||
|
- gitea.unbound.se/shiny/logging
|
||||||
|
- repo: https://github.com/lietu/go-pre-commit
|
||||||
|
rev: v1.0.0
|
||||||
|
hooks:
|
||||||
|
- id: go-test
|
||||||
|
- id: gofumpt
|
||||||
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
|
rev: v2.12.2
|
||||||
|
hooks:
|
||||||
|
- id: golangci-lint-full
|
||||||
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
|
rev: v8.30.1
|
||||||
|
hooks:
|
||||||
|
- id: gitleaks
|
||||||
@@ -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$
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.2.0] - 2026-06-19
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- Move MockLogger into a logtest sub-package (#5)
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- *(ci)* Add shared-lib scaffolding and functional coverage gate (#3)
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-06-15
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- Initial shared logging module
|
||||||
|
|
||||||
|
<!-- generated by git-cliff -->
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# logging
|
||||||
|
|
||||||
|
Shared Go library with logging primitives for all Shiny backend services.
|
||||||
|
|
||||||
|
## Shared Documentation
|
||||||
|
|
||||||
|
@../docs/claude/architecture.md
|
||||||
|
@../docs/claude/go-services.md
|
||||||
|
@../docs/claude/conventions.md
|
||||||
|
|
||||||
|
## Library Information
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
Single home for the `slog` setup, context-logger helpers, request-logging
|
||||||
|
middleware and the `MockLogger` test helper that were previously copied (with
|
||||||
|
drift) into every backend service.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"gitea.unbound.se/shiny/logging"
|
||||||
|
logmw "gitea.unbound.se/shiny/logging/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configure the slog default logger (format: text | json | otel).
|
||||||
|
logger := logging.SetupLogger(cli.LogLevel, cli.LogFormat, serviceName, buildVersion)
|
||||||
|
|
||||||
|
// Carry a logger on the request context.
|
||||||
|
ctx = logging.ContextWithLogger(ctx, logger)
|
||||||
|
logger = logging.LoggerFromContext(ctx)
|
||||||
|
|
||||||
|
// Debug-log request/response bodies.
|
||||||
|
handler = logmw.RequestLogger(logger)(handler)
|
||||||
|
```
|
||||||
|
|
||||||
|
In tests:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import logtest "gitea.unbound.se/shiny/logging/logtest"
|
||||||
|
|
||||||
|
m := logtest.NewMockLogger()
|
||||||
|
// ... exercise code with m.Logger() ...
|
||||||
|
m.Check(t, []string{"level=INFO msg=\"...\""})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exported API
|
||||||
|
|
||||||
|
- `SetupLogger(logLevel, logFormat, serviceName, buildVersion string) *slog.Logger`
|
||||||
|
- `ContextWithLogger(ctx, *slog.Logger) context.Context` / `LoggerFromContext(ctx) *slog.Logger`
|
||||||
|
- `logging/logtest.NewMockLogger() *MockLogger` (+ `MockLogger.Logger()`, `MockLogger.Check(t, want)`) — the test helper, in its own sub-package so production code doesn't pull in testify.
|
||||||
|
- `logging/middleware.RequestLogger(logger) func(http.Handler) http.Handler`.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- `MockLogger`/`NewMockLogger` live in the `logging/logtest` sub-package, so the
|
||||||
|
production `logging` package's import graph is free of `testify` — consumers
|
||||||
|
only compile it if they import `logtest`. (`testify` stays in the module's
|
||||||
|
`go.mod` because `logtest` and the library's own tests use it.)
|
||||||
|
|
||||||
|
### Conventions
|
||||||
|
|
||||||
|
Standard Shiny library scaffolding: `gofumpt`/`goimports -local`, golangci-lint,
|
||||||
|
gitleaks and conventional-commit checks via pre-commit; coverage-regression gate
|
||||||
|
in CI (`.testcoverage.yml`); releases auto-tagged from conventional commits by
|
||||||
|
the shared Release workflow. Bump the consuming services' `go.mod` after a
|
||||||
|
release.
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
# git-cliff ~ default configuration file
|
||||||
|
# https://git-cliff.org/docs/configuration
|
||||||
|
#
|
||||||
|
# Lines starting with "#" are comments.
|
||||||
|
# Configuration options are organized into tables and keys.
|
||||||
|
# See documentation for more information on available options.
|
||||||
|
|
||||||
|
[changelog]
|
||||||
|
# template for the changelog header
|
||||||
|
header = """
|
||||||
|
# Changelog\n
|
||||||
|
All notable changes to this project will be documented in this file.\n
|
||||||
|
"""
|
||||||
|
# template for the changelog body
|
||||||
|
# https://keats.github.io/tera/docs/#introduction
|
||||||
|
body = """
|
||||||
|
{% if version %}\
|
||||||
|
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||||
|
{% else %}\
|
||||||
|
## [unreleased]
|
||||||
|
{% endif %}\
|
||||||
|
{% for group, commits in commits | group_by(attribute="group") %}
|
||||||
|
### {{ group | striptags | trim | upper_first }}
|
||||||
|
{% for commit in commits %}
|
||||||
|
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
|
||||||
|
{% if commit.breaking %}[**breaking**] {% endif %}\
|
||||||
|
{{ commit.message | upper_first }}\
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}\n
|
||||||
|
"""
|
||||||
|
# template for the changelog footer
|
||||||
|
footer = """
|
||||||
|
<!-- generated by git-cliff -->
|
||||||
|
"""
|
||||||
|
# remove the leading and trailing s
|
||||||
|
trim = true
|
||||||
|
# postprocessors
|
||||||
|
postprocessors = [
|
||||||
|
# { pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL
|
||||||
|
]
|
||||||
|
# render body even when there are no releases to process
|
||||||
|
# render_always = true
|
||||||
|
# output file path
|
||||||
|
# output = "test.md"
|
||||||
|
|
||||||
|
[git]
|
||||||
|
# parse the commits based on https://www.conventionalcommits.org
|
||||||
|
conventional_commits = true
|
||||||
|
# filter out the commits that are not conventional
|
||||||
|
filter_unconventional = true
|
||||||
|
# process each line of a commit as an individual commit
|
||||||
|
split_commits = false
|
||||||
|
# regex for preprocessing the commit messages
|
||||||
|
commit_preprocessors = [
|
||||||
|
# Replace issue numbers
|
||||||
|
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
|
||||||
|
# Check spelling of the commit with https://github.com/crate-ci/typos
|
||||||
|
# If the spelling is incorrect, it will be automatically fixed.
|
||||||
|
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
|
||||||
|
]
|
||||||
|
# regex for parsing and grouping commits
|
||||||
|
commit_parsers = [
|
||||||
|
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
|
||||||
|
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
|
||||||
|
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
|
||||||
|
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
|
||||||
|
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
|
||||||
|
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
|
||||||
|
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
|
||||||
|
{ message = "^chore\\(release\\): prepare for", skip = true },
|
||||||
|
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
|
||||||
|
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
|
||||||
|
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
|
||||||
|
]
|
||||||
|
# filter out the commits that are not matched by commit parsers
|
||||||
|
filter_commits = false
|
||||||
|
# sort the tags topologically
|
||||||
|
topo_order = false
|
||||||
|
# sort the commits inside sections by oldest/newest order
|
||||||
|
sort_commits = "oldest"
|
||||||
@@ -23,11 +23,3 @@ func TestContextLogger(t *testing.T) {
|
|||||||
ctx := ContextWithLogger(context.Background(), custom)
|
ctx := ContextWithLogger(context.Background(), custom)
|
||||||
assert.Same(t, custom, LoggerFromContext(ctx))
|
assert.Same(t, custom, LoggerFromContext(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMockLogger(t *testing.T) {
|
|
||||||
m := NewMockLogger()
|
|
||||||
m.Logger().Info("hello", "k", "v")
|
|
||||||
m.Check(t, []string{`level=INFO msg=hello k=v`})
|
|
||||||
empty := NewMockLogger()
|
|
||||||
empty.Check(t, nil)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
package logging
|
// Package logtest provides test helpers for the logging library. It is kept in
|
||||||
|
// its own package so that importing the production logging package does not pull
|
||||||
|
// in testify (a test-only dependency) — only consumers that import logtest do.
|
||||||
|
package logtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package logtest
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestMockLogger(t *testing.T) {
|
||||||
|
m := NewMockLogger()
|
||||||
|
m.Logger().Info("hello", "k", "v")
|
||||||
|
m.Check(t, []string{`level=INFO msg=hello k=v`})
|
||||||
|
empty := NewMockLogger()
|
||||||
|
empty.Check(t, nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user