mirror of
https://github.com/miniflux/v2.git
synced 2025-09-15 18:57:04 +00:00
Implement structured logging using log/slog package
This commit is contained in:
parent
54cb8fa028
commit
c0e954f19d
77 changed files with 1868 additions and 892 deletions
|
@ -5,6 +5,8 @@ package httpd // import "miniflux.app/v2/internal/http/server"
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -17,7 +19,6 @@ import (
|
|||
"miniflux.app/v2/internal/fever"
|
||||
"miniflux.app/v2/internal/googlereader"
|
||||
"miniflux.app/v2/internal/http/request"
|
||||
"miniflux.app/v2/internal/logger"
|
||||
"miniflux.app/v2/internal/storage"
|
||||
"miniflux.app/v2/internal/ui"
|
||||
"miniflux.app/v2/internal/version"
|
||||
|
@ -66,12 +67,12 @@ func startSystemdSocketServer(server *http.Server) {
|
|||
f := os.NewFile(3, "systemd socket")
|
||||
listener, err := net.FileListener(f)
|
||||
if err != nil {
|
||||
logger.Fatal(`Unable to create listener from systemd socket: %v`, err)
|
||||
printErrorAndExit(`Unable to create listener from systemd socket: %v`, err)
|
||||
}
|
||||
|
||||
logger.Info(`Listening on systemd socket`)
|
||||
slog.Info(`Starting server using systemd socket`)
|
||||
if err := server.Serve(listener); err != http.ErrServerClosed {
|
||||
logger.Fatal(`Server failed to start: %v`, err)
|
||||
printErrorAndExit(`Server failed to start: %v`, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -82,17 +83,17 @@ func startUnixSocketServer(server *http.Server, socketFile string) {
|
|||
go func(sock string) {
|
||||
listener, err := net.Listen("unix", sock)
|
||||
if err != nil {
|
||||
logger.Fatal(`Server failed to start: %v`, err)
|
||||
printErrorAndExit(`Server failed to start: %v`, err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
if err := os.Chmod(sock, 0666); err != nil {
|
||||
logger.Fatal(`Unable to change socket permission: %v`, err)
|
||||
printErrorAndExit(`Unable to change socket permission: %v`, err)
|
||||
}
|
||||
|
||||
logger.Info(`Listening on Unix socket %q`, sock)
|
||||
slog.Info("Starting server using a Unix socket", slog.String("socket", sock))
|
||||
if err := server.Serve(listener); err != http.ErrServerClosed {
|
||||
logger.Fatal(`Server failed to start: %v`, err)
|
||||
printErrorAndExit(`Server failed to start: %v`, err)
|
||||
}
|
||||
}(socketFile)
|
||||
}
|
||||
|
@ -137,9 +138,12 @@ func startAutoCertTLSServer(server *http.Server, certDomain string, store *stora
|
|||
go s.ListenAndServe()
|
||||
|
||||
go func() {
|
||||
logger.Info(`Listening on %q by using auto-configured certificate for %q`, server.Addr, certDomain)
|
||||
slog.Info("Starting TLS server using automatic certificate management",
|
||||
slog.String("listen_address", server.Addr),
|
||||
slog.String("domain", certDomain),
|
||||
)
|
||||
if err := server.ListenAndServeTLS("", ""); err != http.ErrServerClosed {
|
||||
logger.Fatal(`Server failed to start: %v`, err)
|
||||
printErrorAndExit(`Server failed to start: %v`, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -147,18 +151,24 @@ func startAutoCertTLSServer(server *http.Server, certDomain string, store *stora
|
|||
func startTLSServer(server *http.Server, certFile, keyFile string) {
|
||||
server.TLSConfig = tlsConfig()
|
||||
go func() {
|
||||
logger.Info(`Listening on %q by using certificate %q and key %q`, server.Addr, certFile, keyFile)
|
||||
slog.Info("Starting TLS server using a certificate",
|
||||
slog.String("listen_address", server.Addr),
|
||||
slog.String("cert_file", certFile),
|
||||
slog.String("key_file", keyFile),
|
||||
)
|
||||
if err := server.ListenAndServeTLS(certFile, keyFile); err != http.ErrServerClosed {
|
||||
logger.Fatal(`Server failed to start: %v`, err)
|
||||
printErrorAndExit(`Server failed to start: %v`, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func startHTTPServer(server *http.Server) {
|
||||
go func() {
|
||||
logger.Info(`Listening on %q without TLS`, server.Addr)
|
||||
slog.Info("Starting HTTP server",
|
||||
slog.String("listen_address", server.Addr),
|
||||
)
|
||||
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
||||
logger.Fatal(`Server failed to start: %v`, err)
|
||||
printErrorAndExit(`Server failed to start: %v`, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -206,7 +216,11 @@ func setupHandler(store *storage.Storage, pool *worker.Pool) *mux.Router {
|
|||
|
||||
// Returns a 404 if the client is not authorized to access the metrics endpoint.
|
||||
if route.GetName() == "metrics" && !isAllowedToAccessMetricsEndpoint(r) {
|
||||
logger.Error(`[Metrics] [ClientIP=%s] Client not allowed (%s)`, request.ClientIP(r), r.RemoteAddr)
|
||||
slog.Warn("Authentication failed while accessing the metrics endpoint",
|
||||
slog.String("client_ip", request.ClientIP(r)),
|
||||
slog.String("client_user_agent", r.UserAgent()),
|
||||
slog.String("client_remote_addr", r.RemoteAddr),
|
||||
)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
@ -220,21 +234,37 @@ func setupHandler(store *storage.Storage, pool *worker.Pool) *mux.Router {
|
|||
}
|
||||
|
||||
func isAllowedToAccessMetricsEndpoint(r *http.Request) bool {
|
||||
clientIP := request.ClientIP(r)
|
||||
|
||||
if config.Opts.MetricsUsername() != "" && config.Opts.MetricsPassword() != "" {
|
||||
clientIP := request.ClientIP(r)
|
||||
username, password, authOK := r.BasicAuth()
|
||||
if !authOK {
|
||||
logger.Info("[Metrics] [ClientIP=%s] No authentication header sent", clientIP)
|
||||
slog.Warn("Metrics endpoint accessed without authentication header",
|
||||
slog.Bool("authentication_failed", true),
|
||||
slog.String("client_ip", clientIP),
|
||||
slog.String("client_user_agent", r.UserAgent()),
|
||||
slog.String("client_remote_addr", r.RemoteAddr),
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
if username == "" || password == "" {
|
||||
logger.Info("[Metrics] [ClientIP=%s] Empty username or password", clientIP)
|
||||
slog.Warn("Metrics endpoint accessed with empty username or password",
|
||||
slog.Bool("authentication_failed", true),
|
||||
slog.String("client_ip", clientIP),
|
||||
slog.String("client_user_agent", r.UserAgent()),
|
||||
slog.String("client_remote_addr", r.RemoteAddr),
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
if username != config.Opts.MetricsUsername() || password != config.Opts.MetricsPassword() {
|
||||
logger.Error("[Metrics] [ClientIP=%s] Invalid username or password", clientIP)
|
||||
slog.Warn("Metrics endpoint accessed with invalid username or password",
|
||||
slog.Bool("authentication_failed", true),
|
||||
slog.String("client_ip", clientIP),
|
||||
slog.String("client_user_agent", r.UserAgent()),
|
||||
slog.String("client_remote_addr", r.RemoteAddr),
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -242,7 +272,14 @@ func isAllowedToAccessMetricsEndpoint(r *http.Request) bool {
|
|||
for _, cidr := range config.Opts.MetricsAllowedNetworks() {
|
||||
_, network, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
logger.Fatal(`[Metrics] Unable to parse CIDR %v`, err)
|
||||
slog.Error("Metrics endpoint accessed with invalid CIDR",
|
||||
slog.Bool("authentication_failed", true),
|
||||
slog.String("client_ip", clientIP),
|
||||
slog.String("client_user_agent", r.UserAgent()),
|
||||
slog.String("client_remote_addr", r.RemoteAddr),
|
||||
slog.String("cidr", cidr),
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
// We use r.RemoteAddr in this case because HTTP headers like X-Forwarded-For can be easily spoofed.
|
||||
|
@ -254,3 +291,10 @@ func isAllowedToAccessMetricsEndpoint(r *http.Request) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
func printErrorAndExit(format string, a ...any) {
|
||||
message := fmt.Sprintf(format, a...)
|
||||
slog.Error(message)
|
||||
fmt.Fprintf(os.Stderr, "%v\n", message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
@ -5,11 +5,12 @@ package httpd // import "miniflux.app/v2/internal/http/server"
|
|||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"miniflux.app/v2/internal/config"
|
||||
"miniflux.app/v2/internal/http/request"
|
||||
"miniflux.app/v2/internal/logger"
|
||||
)
|
||||
|
||||
func middleware(next http.Handler) http.Handler {
|
||||
|
@ -22,12 +23,18 @@ func middleware(next http.Handler) http.Handler {
|
|||
config.Opts.HTTPS = true
|
||||
}
|
||||
|
||||
protocol := "HTTP"
|
||||
if config.Opts.HTTPS {
|
||||
protocol = "HTTPS"
|
||||
}
|
||||
|
||||
logger.Debug("[%s] %s %s %s", protocol, clientIP, r.Method, r.RequestURI)
|
||||
t1 := time.Now()
|
||||
defer func() {
|
||||
slog.Debug("Incoming request",
|
||||
slog.String("client_ip", clientIP),
|
||||
slog.Group("request",
|
||||
slog.String("method", r.Method),
|
||||
slog.String("uri", r.RequestURI),
|
||||
slog.String("protocol", r.Proto),
|
||||
slog.Duration("execution_time", time.Since(t1)),
|
||||
),
|
||||
)
|
||||
}()
|
||||
|
||||
if config.Opts.HTTPS && config.Opts.HasHSTS() {
|
||||
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue