Compare commits
	
		
			4 Commits
		
	
	
		
			v0.11.1
			...
			340cf67515
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 340cf67515 | |||
| f5cb3456b1 | |||
| ede5bc92f7 | |||
| 063ff0f1d0 | 
@@ -1,3 +1,10 @@
 | 
			
		||||
# v0.12.1
 | 
			
		||||
* feat: Implement custom OpenTelemetry span name formatting for HTTP requests.
 | 
			
		||||
* refactor: Streamline OpenTelemetry handler integration by removing custom wrapper.
 | 
			
		||||
 | 
			
		||||
# v0.12.0
 | 
			
		||||
* feat: Add support for excluding HTTP request paths from logging using configurable regular expressions.
 | 
			
		||||
 | 
			
		||||
# v0.11.1:
 | 
			
		||||
* docs: Add comprehensive package-level documentation for the `http` package.
 | 
			
		||||
* docs: Improve documentation for the `InitHTTPServer` function.
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -105,6 +106,12 @@ func prepareConfig(cfg *AppConfig) error {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Prepare user-provided expressions, panic up-front if invalid
 | 
			
		||||
	cfg.HTTP.excludeRegexps = make([]*regexp.Regexp, len(cfg.HTTP.LogExcludePathRegexps))
 | 
			
		||||
	for i, re := range cfg.HTTP.LogExcludePathRegexps {
 | 
			
		||||
		cfg.HTTP.excludeRegexps[i] = regexp.MustCompile(re)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var defaultHTTPConfig = &HTTPConfig{
 | 
			
		||||
	Enabled: true,
 | 
			
		||||
@@ -14,13 +18,23 @@ var defaultHTTPConfig = &HTTPConfig{
 | 
			
		||||
 | 
			
		||||
// HTTPConfig provides HTTP server Configuration
 | 
			
		||||
type HTTPConfig struct {
 | 
			
		||||
	Enabled      bool   `yaml:"enabled" env:"APP_HTTP_ENABLED" json:"enabled,omitempty"`
 | 
			
		||||
	Listen       string `yaml:"listen,omitempty" env:"APP_HTTP_LISTEN" json:"listen,omitempty"`
 | 
			
		||||
	LogRequests  bool   `yaml:"logRequests" env:"APP_HTTP_LOG_REQUESTS" json:"logRequests,omitempty"`
 | 
			
		||||
	ReadTimeout  string `yaml:"readTimeout" env:"APP_HTTP_READ_TIMEOUT" json:"readTimeout,omitempty"`    // Go duration (e.g. 10s)
 | 
			
		||||
	WriteTimeout string `yaml:"writeTimeout" env:"APP_HTTP_WRITE_TIMEOUT" json:"writeTimeout,omitempty"` // Go duration (e.g. 10s)
 | 
			
		||||
	IdleTimeout  string `yaml:"idleTimeout" env:"APP_HTTP_IDLE_TIMEOUT" json:"idleTimeout,omitempty"`    // Go duration (e.g. 10s)
 | 
			
		||||
	rT           *time.Duration
 | 
			
		||||
	wT           *time.Duration
 | 
			
		||||
	iT           *time.Duration
 | 
			
		||||
	Enabled               bool     `yaml:"enabled" env:"APP_HTTP_ENABLED" json:"enabled,omitempty"`
 | 
			
		||||
	Listen                string   `yaml:"listen,omitempty" env:"APP_HTTP_LISTEN" json:"listen,omitempty"`
 | 
			
		||||
	LogRequests           bool     `yaml:"logRequests" env:"APP_HTTP_LOG_REQUESTS" json:"logRequests,omitempty"`
 | 
			
		||||
	LogExcludePathRegexps []string `yaml:"logExcludePathRegexps" env:"APP_HTTP_LOG_EXCLUDE_PATH_REGEXPS" json:"logExcludePathRegexps,omitempty"`
 | 
			
		||||
	ReadTimeout           string   `yaml:"readTimeout" env:"APP_HTTP_READ_TIMEOUT" json:"readTimeout,omitempty"`    // Go duration (e.g. 10s)
 | 
			
		||||
	WriteTimeout          string   `yaml:"writeTimeout" env:"APP_HTTP_WRITE_TIMEOUT" json:"writeTimeout,omitempty"` // Go duration (e.g. 10s)
 | 
			
		||||
	IdleTimeout           string   `yaml:"idleTimeout" env:"APP_HTTP_IDLE_TIMEOUT" json:"idleTimeout,omitempty"`    // Go duration (e.g. 10s)
 | 
			
		||||
	excludeRegexps        []*regexp.Regexp
 | 
			
		||||
	rT                    *time.Duration
 | 
			
		||||
	wT                    *time.Duration
 | 
			
		||||
	iT                    *time.Duration
 | 
			
		||||
	lock                  sync.RWMutex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *HTTPConfig) GetExcludeRegexps() []*regexp.Regexp {
 | 
			
		||||
	h.lock.RLock()
 | 
			
		||||
	defer h.lock.RUnlock()
 | 
			
		||||
 | 
			
		||||
	return h.excludeRegexps
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -25,11 +26,12 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	httpMeter       metric.Meter
 | 
			
		||||
	httpTracer      trace.Tracer
 | 
			
		||||
	defReadTimeout  = 10 * time.Second
 | 
			
		||||
	defWriteTimeout = 10 * time.Second
 | 
			
		||||
	defIdleTimeout  = 15 * time.Second
 | 
			
		||||
	httpMeter                   metric.Meter
 | 
			
		||||
	httpTracer                  trace.Tracer
 | 
			
		||||
	httpPatternWithMethodRegexp = regexp.MustCompile(`(\w+) .*`)
 | 
			
		||||
	defReadTimeout              = 10 * time.Second
 | 
			
		||||
	defWriteTimeout             = 10 * time.Second
 | 
			
		||||
	defIdleTimeout              = 15 * time.Second
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func prepHTTPServer(opts *opts.AppHTTP) *http.Server {
 | 
			
		||||
@@ -39,19 +41,12 @@ func prepHTTPServer(opts *opts.AppHTTP) *http.Server {
 | 
			
		||||
		mux = &http.ServeMux{}
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// NOTE: Wraps handle func with otelhttp handler and
 | 
			
		||||
	// inserts route tag
 | 
			
		||||
	otelHandleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) {
 | 
			
		||||
		handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc))
 | 
			
		||||
		mux.Handle(pattern, handler) // Associate pattern with handler
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	healthChecks := handleHealthCheckFunc(opts.Ctx, opts.HealthChecks...)
 | 
			
		||||
	otelHandleFunc("/health", healthChecks)
 | 
			
		||||
	otelHandleFunc("/", healthChecks)
 | 
			
		||||
	mux.HandleFunc("/health", healthChecks)
 | 
			
		||||
	mux.HandleFunc("/", healthChecks)
 | 
			
		||||
 | 
			
		||||
	for _, f := range opts.Funcs {
 | 
			
		||||
		otelHandleFunc(f.Path, f.HandlerFunc)
 | 
			
		||||
		mux.HandleFunc(f.Path, f.HandlerFunc)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Prometheus metrics endpoint
 | 
			
		||||
@@ -74,12 +69,12 @@ func prepHTTPServer(opts *opts.AppHTTP) *http.Server {
 | 
			
		||||
		if h.StripPrefix {
 | 
			
		||||
			h.Handler = http.StripPrefix(h.Prefix[:len(h.Prefix)-1], h.Handler)
 | 
			
		||||
		}
 | 
			
		||||
		mux.Handle(h.Prefix, h.Handler)
 | 
			
		||||
		mux.Handle(h.Prefix, otelhttp.WithRouteTag(h.Prefix, h.Handler))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add OTEL, skip health-check spans
 | 
			
		||||
	// NOTE: Add any other span exclusions here
 | 
			
		||||
	// Add OTEL instrumentation, filter noise, set span names
 | 
			
		||||
	handler := otelhttp.NewHandler(mux, "/",
 | 
			
		||||
		// TODO: Make configurable similar to config.http.LogExcludePathRegexps
 | 
			
		||||
		otelhttp.WithFilter(func(r *http.Request) bool {
 | 
			
		||||
			switch r.URL.Path {
 | 
			
		||||
			case "/health":
 | 
			
		||||
@@ -89,6 +84,18 @@ func prepHTTPServer(opts *opts.AppHTTP) *http.Server {
 | 
			
		||||
			default:
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}),
 | 
			
		||||
		otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
 | 
			
		||||
			endpoint := r.URL.Path
 | 
			
		||||
			if _, pattern := mux.Handler(r); pattern != "" {
 | 
			
		||||
				endpoint = pattern
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if httpPatternWithMethodRegexp.MatchString(endpoint) {
 | 
			
		||||
				return endpoint
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return fmt.Sprintf("%s %s", r.Method, endpoint)
 | 
			
		||||
		}))
 | 
			
		||||
 | 
			
		||||
	// Set timeouts from defaults, override
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,8 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/rs/zerolog"
 | 
			
		||||
 | 
			
		||||
	"gitea.libretechconsulting.com/rmcguire/go-app/pkg/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var ExcludeFromLogging = regexp.MustCompile(`\/(ready|live|metrics)$`)
 | 
			
		||||
@@ -21,12 +23,22 @@ type LoggingResponseWriter struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loggingMiddleware(appCtx context.Context, next http.Handler) http.Handler {
 | 
			
		||||
	appConfig := config.MustFromCtx(appCtx)
 | 
			
		||||
 | 
			
		||||
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		if ExcludeFromLogging.Match([]byte(r.URL.Path)) {
 | 
			
		||||
			next.ServeHTTP(w, r)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// User-configurable logging exclusions
 | 
			
		||||
		for _, re := range appConfig.HTTP.GetExcludeRegexps() {
 | 
			
		||||
			if re.MatchString(r.URL.Path) {
 | 
			
		||||
				next.ServeHTTP(w, r)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log := zerolog.Ctx(appCtx)
 | 
			
		||||
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user