HTTP Server-Timing for Go


This is a library including middleware for using HTTP Server-Timing with Go. This header allows a server to send timing information from the backend, such as database access time, file reads, etc. The timing information can be then be inspected in the standard browser developer tools:

Server Timing Example


  • Middleware for injecting the server timing struct into the request Context and writing the Server-Timing header.

  • Concurrency-safe structures for easily recording timings of multiple concurrency tasks.

  • Parse Server-Timing headers as a client.

  • Note: No browser properly supports sending the Server-Timing header as an HTTP Trailer so the Middleware only supports a normal header currently.

Browser Support

Browser support is required to view server timings easily. Because server timings are sent as an HTTP header, there is no negative impact to sending the header to unsupported browsers.

  • Chrome 65 or higher is required to properly display server timings in the devtools.

  • Firefox is pending with an open bug report (ID 1403051)

  • IE, Opera, and others are unknown at this time.


Example usage is shown below. A fully runnable example is available in the example/ directory.

func main() {
	// Our handler. In a real application this might be your root router,
	// or some subset of your router. Wrapping this ensures that all routes
	// handled by this handler have access to the server timing header struct.
	var h http.Handler = http.HandlerFunc(handler)

	// Wrap our handler with the server timing middleware
	h = servertiming.Middleware(h, nil)

	// Start!
	http.ListenAndServe(":8080", h)

func handler(w http.ResponseWriter, r *http.Request) {
	// Get our timing header builder from the context
	timing := servertiming.FromContext(r.Context())

	// Imagine your handler performs some tasks in a goroutine, such as
	// accessing some remote service. timing is concurrency safe so we can
	// record how long that takes. Let's simulate making 5 concurrent requests
	// to various servicse.
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		name := fmt.Sprintf("service-%d", i)
		go func(name string) {
			// This creats a new metric and starts the timer. The Stop is
			// deferred so when the function exits it'll record the duration.
			defer timing.NewMetric(name).Start().Stop()
			time.Sleep(random(25, 75))

	// Imagine this is just some blocking code in your main handler such
	// as a SQL query. Let's record that.
	m := timing.NewMetric("sql").WithDesc("SQL query").Start()
	time.Sleep(random(20, 50))

	// Wait for the goroutine to end

	// You could continue recording more metrics, but let's just return now
	w.Write([]byte("Done. Check your browser inspector timing details."))

func random(min, max int) time.Duration {
	return (time.Duration(rand.Intn(max-min) + min)) * time.Millisecond




View Source
const HeaderKey = "Server-Timing"

    HeaderKey is the specified key for the Server-Timing header.


    This section is empty.


    func Middleware

    func Middleware(next http.Handler, _ *MiddlewareOpts) http.Handler

      Middleware wraps an http.Handler and provides a *Header in the request context that can be used to set Server-Timing headers. The *Header can be extracted from the context using FromContext.

      The options supplied to this can be nil to use defaults.

      The Server-Timing header will be written when the status is written only if there are non-empty number of metrics.

      To control when Server-Timing is sent, the easiest approach is to wrap this middleware and only call it if the request should send server timings. For examples, see the README.

      func NewContext

      func NewContext(ctx context.Context, h *Header) context.Context

        NewContext returns a new Context that carries the Header value h.


        type Header struct {
        	// Metrics is the list of metrics in the header.
        	Metrics []*Metric
        	// The lock that is held when Metrics is being modified. This
        	// ONLY NEEDS TO BE SET WHEN working with Metrics directly. If using
        	// the functions on the struct, the lock is managed automatically.

          Header represents a collection of metrics that can be encoded as a Server-Timing header value.

          The functions for working with metrics are concurrency-safe to make it easy to record metrics from goroutines. If you want to avoid the lock overhead, you can access the Metrics field directly.

          The functions for working with metrics are also usable on a nil Header pointer. This allows functions that use FromContext to get the *Header value to skip nil-checking and use it as normal. On a nil *Header, Metrics are not recorded.

          func FromContext

          func FromContext(ctx context.Context) *Header

            FromContext returns the *Header in the context, if any. If no Header value exists, nil is returned.

            func ParseHeader

            func ParseHeader(input string) (*Header, error)

              ParseHeader parses a Server-Timing header value.

              func (*Header) Add

              func (h *Header) Add(m *Metric) *Metric

                Add adds the given metric to the header.

                This function is safe to call concurrently.

                func (*Header) NewMetric

                func (h *Header) NewMetric(name string) *Metric

                  NewMetric creates a new Metric and adds it to this header.

                  func (*Header) String

                  func (h *Header) String() string

                    String returns the valid Server-Timing header value that can be sent in an HTTP response.

                    type Metric

                    type Metric struct {
                    	// Name is the name of the metric. This must be a valid RFC7230 "token"
                    	// format. In a gist, this is an alphanumeric string that may contain
                    	// most common symbols but may not contain any whitespace. The exact
                    	// syntax can be found in RFC7230.
                    	// It is common for Name to be a unique identifier (such as "sql-1") and
                    	// for a more human-friendly name to be used in the "desc" field.
                    	Name string
                    	// Duration is the duration of this Metric.
                    	Duration time.Duration
                    	// Desc is any string describing this metric. For example: "SQL Primary".
                    	// The specific format of this is `token | quoted-string` according to
                    	// RFC7230.
                    	Desc string
                    	// Extra is a set of extra parameters and values to send with the
                    	// metric. The specification states that unrecognized parameters are
                    	// to be ignored so it should be safe to add additional data here. The
                    	// key must be a valid "token" (same syntax as Name) and the value can
                    	// be any "token | quoted-string" (same as Desc field).
                    	// If this map contains a key that would be sent by another field in this
                    	// struct (such as "desc"), then this value is prioritized over the
                    	// struct value.
                    	Extra map[string]string
                    	// contains filtered or unexported fields

                      Metric represents a single metric for the Server-Timing header.

                      The easiest way to use the Metric is to use NewMetric and chain it. This results in a single line defer at the top of a function time a function.

                      timing := FromContext(r.Context())
                      defer timing.NewMetric("sql").Start().Stop()

                      For timing around specific blocks of code:

                      m := timing.NewMetric("sql").Start()
                      // ... run your code being timed here

                      A metric is expected to represent a single timing event. Therefore, no functions on the struct are safe for concurrency by default. If a single Metric is shared by multiple concurrenty goroutines, you must lock access manually.

                      func (*Metric) GoString

                      func (m *Metric) GoString() string

                        GoString is needed for fmt.GoStringer so %v works on pointer value.

                        func (*Metric) Start

                        func (m *Metric) Start() *Metric

                          Start starts a timer for recording the duration of some task. This must be paired with a Stop call to set the duration. Calling this again will reset the start time for a subsequent Stop call.

                          func (*Metric) Stop

                          func (m *Metric) Stop() *Metric

                            Stop ends the timer started with Start and records the duration in the Duration field. Calling this multiple times will modify the Duration based on the last time Start was called.

                            If Start was never called, this function has zero effect.

                            func (*Metric) String

                            func (m *Metric) String() string

                              String returns the valid Server-Timing metric entry value.

                              func (*Metric) WithDesc

                              func (m *Metric) WithDesc(desc string) *Metric

                                WithDesc is a chaining-friendly helper to set the Desc field on the Metric.

                                type MiddlewareOpts

                                type MiddlewareOpts struct {

                                  MiddlewareOpts are options for the Middleware.


                                  Path Synopsis