Skip to content

Move stats to prometheus instead of statsd#72

Merged
julsemaan merged 6 commits into
masterfrom
feat/stats-prom
Apr 16, 2026
Merged

Move stats to prometheus instead of statsd#72
julsemaan merged 6 commits into
masterfrom
feat/stats-prom

Conversation

@julsemaan
Copy link
Copy Markdown
Owner

No description provided.

@julsemaan julsemaan requested a review from Copilot April 16, 2026 00:36
@julsemaan julsemaan changed the title Feat/stats prom Move stats to prometheus instead of statsd Apr 16, 2026
@github-actions
Copy link
Copy Markdown

Go coverage report (utils)

github.com/julsemaan/anyfile-notepad/utils/utils.go:16:	SendEmail		100.0%
github.com/julsemaan/anyfile-notepad/utils/utils.go:48:	sendMailWithTLSConfig	0.0%
total:							(statements)		46.5%

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 16, 2026

Go coverage report (api)

github.com/julsemaan/anyfile-notepad/api/cmd/api/main.go:8:				main				0.0%
github.com/julsemaan/anyfile-notepad/api/internal/app/config.go:24:			LoadConfigFromEnv		100.0%
github.com/julsemaan/anyfile-notepad/api/internal/app/config.go:51:			loadMaxContactRequestsPerDay	100.0%
github.com/julsemaan/anyfile-notepad/api/internal/app/email.go:17:			sendEmailWithOptionalTLS	100.0%
github.com/julsemaan/anyfile-notepad/api/internal/app/email.go:48:			sendMailWithTLSConfig		0.0%
github.com/julsemaan/anyfile-notepad/api/internal/app/run.go:21:			Run				0.0%
github.com/julsemaan/anyfile-notepad/api/internal/app/run.go:89:			logURL				100.0%
github.com/julsemaan/anyfile-notepad/api/internal/contact/service.go:39:		NewService			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/contact/service.go:48:		BeforeInsert			85.7%
github.com/julsemaan/anyfile-notepad/api/internal/contact/service.go:73:		AfterInsert			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/contact/service.go:87:		buildMessage			70.0%
github.com/julsemaan/anyfile-notepad/api/internal/httpapi/handler_stats.go:17:		NewStatsHandler			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/httpapi/middleware_access_log.go:14:	WriteHeader			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/httpapi/middleware_access_log.go:19:	withAccessLog			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/httpapi/middleware_auth.go:10:	IsOpenResource			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/httpapi/middleware_auth.go:30:	Authenticate			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/httpapi/middleware_cors.go:5:		withCORS			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/httpapi/router.go:10:			NewRouter			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/logging/logging.go:12:		output				100.0%
github.com/julsemaan/anyfile-notepad/api/internal/logging/logging.go:16:		Error				60.0%
github.com/julsemaan/anyfile-notepad/api/internal/logging/logging.go:26:		Errorf				100.0%
github.com/julsemaan/anyfile-notepad/api/internal/logging/logging.go:30:		Fatalf				100.0%
github.com/julsemaan/anyfile-notepad/api/internal/resources/index.go:13:		BuildIndex			0.0%
github.com/julsemaan/anyfile-notepad/api/internal/resources/schema.go:19:		MimeTypeSchema			0.0%
github.com/julsemaan/anyfile-notepad/api/internal/resources/schema.go:43:		ExtensionSchema			0.0%
github.com/julsemaan/anyfile-notepad/api/internal/resources/schema.go:72:		SyntaxSchema			0.0%
github.com/julsemaan/anyfile-notepad/api/internal/resources/schema.go:91:		SettingSchema			0.0%
github.com/julsemaan/anyfile-notepad/api/internal/resources/schema.go:110:		ContactRequestSchema		0.0%
github.com/julsemaan/anyfile-notepad/api/internal/resources/schema.go:136:		Validate			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/stats/prometheus.go:23:		NewPrometheusMetrics		0.0%
github.com/julsemaan/anyfile-notepad/api/internal/stats/prometheus.go:27:		IncrementStatsHits		0.0%
github.com/julsemaan/anyfile-notepad/api/internal/stats/prometheus.go:31:		IncrementKey			0.0%
github.com/julsemaan/anyfile-notepad/api/internal/stats/service.go:56:			NewService			100.0%
github.com/julsemaan/anyfile-notepad/api/internal/stats/service.go:60:			ParsePayload			89.5%
github.com/julsemaan/anyfile-notepad/api/internal/stats/service.go:92:			Record				83.3%
github.com/julsemaan/anyfile-notepad/api/internal/stats/service.go:106:			normalizeIncrementMetricKey	100.0%
github.com/julsemaan/anyfile-notepad/api/internal/stats/service.go:120:			extractIP			60.0%
total:											(statements)			67.2%

@github-actions
Copy link
Copy Markdown

Go coverage report (webserver)

github.com/julsemaan/anyfile-notepad/webserver/billing_handlers.go:38:				renderEmailTemplate		85.7%
github.com/julsemaan/anyfile-notepad/webserver/billing_handlers.go:52:				LoadSubscription		100.0%
github.com/julsemaan/anyfile-notepad/webserver/billing_handlers.go:71:				LoadGoogleUser			96.2%
github.com/julsemaan/anyfile-notepad/webserver/billing_handlers.go:122:				handleSubscriptionRead		100.0%
github.com/julsemaan/anyfile-notepad/webserver/billing_handlers.go:126:				handleSubscriptionResume	100.0%
github.com/julsemaan/anyfile-notepad/webserver/billing_handlers.go:153:				handleSubscriptionCancel	86.7%
github.com/julsemaan/anyfile-notepad/webserver/billing_handlers.go:182:				handleSubscriptionUpgrade	100.0%
github.com/julsemaan/anyfile-notepad/webserver/billing_handlers.go:231:				upgradeError			100.0%
github.com/julsemaan/anyfile-notepad/webserver/billing_handlers.go:238:				handleLinkCancel		60.0%
github.com/julsemaan/anyfile-notepad/webserver/billing_handlers.go:294:				handleStripeHook		72.4%
github.com/julsemaan/anyfile-notepad/webserver/cluster_observer.go:16:				NewClusterObserver		100.0%
github.com/julsemaan/anyfile-notepad/webserver/cluster_observer.go:20:				hostURL				75.0%
github.com/julsemaan/anyfile-notepad/webserver/cluster_observer.go:28:				glpclient			100.0%
github.com/julsemaan/anyfile-notepad/webserver/cluster_observer.go:40:				Start				0.0%
github.com/julsemaan/anyfile-notepad/webserver/email.go:16:					sendEmailWithOptionalTLS	100.0%
github.com/julsemaan/anyfile-notepad/webserver/email.go:47:					sendMailWithTLSConfig		0.0%
github.com/julsemaan/anyfile-notepad/webserver/emails_update/update_customers_emails.go:24:	Next				0.0%
github.com/julsemaan/anyfile-notepad/webserver/emails_update/update_customers_emails.go:28:	Customer			0.0%
github.com/julsemaan/anyfile-notepad/webserver/emails_update/update_customers_emails.go:32:	Err				0.0%
github.com/julsemaan/anyfile-notepad/webserver/emails_update/update_customers_emails.go:42:	main				0.0%
github.com/julsemaan/anyfile-notepad/webserver/emails_update/update_customers_emails.go:47:	updateCustomerEmails		100.0%
github.com/julsemaan/anyfile-notepad/webserver/http_handler.go:12:				ServeHTTP			100.0%
github.com/julsemaan/anyfile-notepad/webserver/http_handler.go:24:				setupPlusPlusSession		75.0%
github.com/julsemaan/anyfile-notepad/webserver/http_handler.go:40:				ServeStaticApplication		100.0%
github.com/julsemaan/anyfile-notepad/webserver/main.go:51:					main				0.0%
github.com/julsemaan/anyfile-notepad/webserver/main.go:71:					setupSessionsPersistence	0.0%
github.com/julsemaan/anyfile-notepad/webserver/main.go:95:					setupHandlers			93.3%
github.com/julsemaan/anyfile-notepad/webserver/main.go:147:					setupSubscriptions		0.0%
github.com/julsemaan/anyfile-notepad/webserver/main.go:165:					setupClusterObserver		0.0%
github.com/julsemaan/anyfile-notepad/webserver/main.go:178:					setupBlockedUsersMap		100.0%
github.com/julsemaan/anyfile-notepad/webserver/main.go:184:					setup				0.0%
github.com/julsemaan/anyfile-notepad/webserver/oauth2_handlers.go:23:				getGoogleOauth2Conf		100.0%
github.com/julsemaan/anyfile-notepad/webserver/oauth2_handlers.go:43:				handleGoogleOauth2Authorize	100.0%
github.com/julsemaan/anyfile-notepad/webserver/oauth2_handlers.go:55:				handleGoogleOauth2Callback	100.0%
github.com/julsemaan/anyfile-notepad/webserver/plus_plus_session.go:26:				NewPlusPlusSession		100.0%
github.com/julsemaan/anyfile-notepad/webserver/plus_plus_session.go:38:				NewPlusPlusSessions		100.0%
github.com/julsemaan/anyfile-notepad/webserver/plus_plus_session.go:45:				GenerateSessionID		80.0%
github.com/julsemaan/anyfile-notepad/webserver/plus_plus_session.go:62:				Set				100.0%
github.com/julsemaan/anyfile-notepad/webserver/plus_plus_session.go:68:				Get				100.0%
github.com/julsemaan/anyfile-notepad/webserver/plus_plus_session.go:74:				Maintenance			100.0%
github.com/julsemaan/anyfile-notepad/webserver/plus_plus_session.go:92:				RestoreFromFile			88.9%
github.com/julsemaan/anyfile-notepad/webserver/plus_plus_session.go:108:			SaveToFile			68.4%
github.com/julsemaan/anyfile-notepad/webserver/realtime_handler.go:13:				publishRealtimeEvent		100.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:23:				Next				0.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:27:				Sub				0.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:31:				Err				0.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:53:				NewSubscriptions		100.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:60:				Empty				100.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:64:				CanHaveAccess			100.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:68:				ExtractUserId			75.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:76:				SetSubscription			100.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:90:				DelSubscription			100.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:97:				GetSubscription			100.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:104:				GetSubscriptionByCustomer	100.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:117:				Reload				100.0%
github.com/julsemaan/anyfile-notepad/webserver/subscriptions.go:145:				Maintenance			83.3%
github.com/julsemaan/anyfile-notepad/webserver/utils.go:10:					secureRandomString		60.0%
github.com/julsemaan/anyfile-notepad/webserver/utils.go:19:					InfoPrint			100.0%
github.com/julsemaan/anyfile-notepad/webserver/utils.go:24:					ErrPrint			100.0%
github.com/julsemaan/anyfile-notepad/webserver/utils.go:29:					EnvOrDefault			100.0%
total:												(statements)			73.1%

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR replaces the existing statsd-based stats instrumentation with Prometheus metrics, adds a dedicated /metrics HTTP listener/port, and updates the stats service interface + tests accordingly.

Changes:

  • Replace statsd integration with Prometheus counters (hits + per-key increments).
  • Add a configurable, separate metrics listener (AFN_METRICS_LISTEN_ADDR, default :9090) and expose /metrics via promhttp.
  • Update docker-compose dev port mappings and adjust stats service tests to match the new metrics interface/behavior.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
docker4dev/docker-compose.yaml Exposes the new metrics port from the API container.
api/internal/stats/service.go Updates metrics interface usage and removes per-IP bucket increments.
api/internal/stats/service_test.go Updates test stub + assertions for new metrics interface.
api/internal/stats/prometheus.go Introduces Prometheus-backed Metrics implementation.
api/internal/app/config.go Adds MetricsListenAddr configuration/env handling.
api/internal/app/config_test.go Adds config tests for AFN_METRICS_LISTEN_ADDR.
api/internal/app/run.go Runs a second HTTP server to serve /metrics on the metrics listener.
api/internal/httpapi/router_test.go Removes an extra blank line.
api/go.mod / api/go.sum Adds Prometheus dependencies and updates module sums.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +13 to +19
var statsIncrementCounter = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "afn_stats_increment_total",
Help: "Number of accepted increment stats by key.",
},
[]string{"key"},
)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

statsIncrementCounter uses a key label whose value ultimately comes from an unauthenticated request payload. Even with the regex, an attacker can send many distinct keys and create unbounded label cardinality, causing unbounded in-memory series growth (DoS risk). Consider restricting key to a small allowlist (or mapping unknowns to a single value), or avoid using a label here and instead expose separate counters for known keys.

Copilot uses AI. Check for mistakes.
Comment thread api/internal/app/config.go Outdated

const defaultDataDir = "./db"
const defaultListenAddr = ":8080"
const defaultMetricsListenAddr = ":9090"
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

defaultMetricsListenAddr is set to ":9090", which binds metrics on all interfaces by default. Since /metrics is typically unauthenticated, consider defaulting to a loopback-only address (e.g. "127.0.0.1:9090") and requiring an explicit env override to expose it publicly.

Suggested change
const defaultMetricsListenAddr = ":9090"
const defaultMetricsListenAddr = "127.0.0.1:9090"

Copilot uses AI. Check for mistakes.
Comment thread api/internal/app/run.go Outdated
Comment on lines +49 to +57
errCh := make(chan error, 2)
go func() {
log.Printf("Serving Prometheus metrics on http://localhost%s/metrics", cfg.MetricsListenAddr)
errCh <- http.ListenAndServe(cfg.MetricsListenAddr, metricsMux)
}()

go func() {
log.Printf("Serving API on http://localhost%s", cfg.ListenAddr)
errCh <- http.ListenAndServe(cfg.ListenAddr, router)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The metrics server is started with http.ListenAndServe using the default http.Server timeouts (none). This leaves the new metrics endpoint susceptible to slowloris-style connections and can tie up goroutines under load. Consider using an explicit http.Server with reasonable ReadHeaderTimeout/ReadTimeout/WriteTimeout/IdleTimeout for the metrics listener (and ideally the API listener too).

Suggested change
errCh := make(chan error, 2)
go func() {
log.Printf("Serving Prometheus metrics on http://localhost%s/metrics", cfg.MetricsListenAddr)
errCh <- http.ListenAndServe(cfg.MetricsListenAddr, metricsMux)
}()
go func() {
log.Printf("Serving API on http://localhost%s", cfg.ListenAddr)
errCh <- http.ListenAndServe(cfg.ListenAddr, router)
metricsServer := &http.Server{
Addr: cfg.MetricsListenAddr,
Handler: metricsMux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
apiServer := &http.Server{
Addr: cfg.ListenAddr,
Handler: router,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
errCh := make(chan error, 2)
go func() {
log.Printf("Serving Prometheus metrics on http://localhost%s/metrics", cfg.MetricsListenAddr)
errCh <- metricsServer.ListenAndServe()
}()
go func() {
log.Printf("Serving API on http://localhost%s", cfg.ListenAddr)
errCh <- apiServer.ListenAndServe()

Copilot uses AI. Check for mistakes.
Comment thread docker4dev/docker-compose.yaml Outdated
target: build
ports:
- 8001:8080
- 8003:9090
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

There is trailing whitespace after the port mapping (- 8003:9090 ). Trim it to avoid noisy diffs and potential lint failures in YAML formatting checks.

Suggested change
- 8003:9090
- 8003:9090

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/internal/app/run.go
Comment on lines +49 to +58
errCh := make(chan error, 2)
go func() {
log.Printf("Serving Prometheus metrics on http://localhost%s/metrics", cfg.MetricsListenAddr)
errCh <- http.ListenAndServe(cfg.MetricsListenAddr, metricsMux)
}()

go func() {
log.Printf("Serving API on http://localhost%s", cfg.ListenAddr)
errCh <- http.ListenAndServe(cfg.ListenAddr, router)
}()
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

Run() starts two servers in goroutines and returns on the first non-ErrServerClosed error, but it doesn't stop the other server. If one listener fails to bind (or crashes), the other goroutine can keep serving unexpectedly until the process exits. Consider using explicit http.Server instances and shutting down the other server (or cancelling a shared context) before returning the error.

Copilot uses AI. Check for mistakes.
Comment thread api/internal/app/run.go Outdated
Comment on lines +51 to +52
log.Printf("Serving Prometheus metrics on http://localhost%s/metrics", cfg.MetricsListenAddr)
errCh <- http.ListenAndServe(cfg.MetricsListenAddr, metricsMux)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

These log lines build a URL by prefixing the listen address with localhost. If cfg.MetricsListenAddr is set to a host-qualified value (e.g. 0.0.0.0:9090), the logged URL becomes malformed (http://localhost0.0.0.0:9090/...). Consider logging the configured address directly, or formatting the URL in a way that handles both ":port" and "host:port" inputs.

Copilot uses AI. Check for mistakes.
Comment thread api/internal/app/config_test.go Outdated
Comment on lines +6 to +8
t.Run("uses default metrics listen addr when env missing", func(t *testing.T) {
t.Setenv("AFN_METRICS_LISTEN_ADDR", "")

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The subtest name says the env var is "missing", but t.Setenv("AFN_METRICS_LISTEN_ADDR", "") sets it to an empty value (which is indistinguishable from unset for os.Getenv). Consider renaming this case to reflect "unset or empty" to avoid confusion when reading the test.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/go.mod
Comment on lines 6 to 10
github.com/julsemaan/anyfile-notepad/utils v0.0.0-20230202010526-481b7f9b59a2
github.com/julsemaan/rest-layer-file v0.0.0-20230518012330-1c28ed9eb6a7
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/prometheus/client_golang v1.23.2
github.com/rs/rest-layer v0.0.0-20160505213648-cb84bc79b5b8
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

github.com/julsemaan/anyfile-notepad/utils is still a direct requirement in go.mod, but api/go.sum no longer contains any checksums for that module. This will cause go mod tidy / go test to reintroduce go.sum changes and can break CI if it enforces a clean module graph. Regenerate go.sum (e.g., via go mod tidy) so the required module has matching sum entries.

Copilot uses AI. Check for mistakes.
@julsemaan julsemaan merged commit abcb4ea into master Apr 16, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants