Efficient thread-safe circular byte buffer to keep in-memory logs
Latest commit 97eeabb Oct 28, 2016 @maruel Fix use of AssertEqual to ExpectEqual.
There's a race condition left in this library that will have to be investigated
in a follow up.

Test only change.



Package circular implements an efficient thread-safe circular byte buffer to keep in-memory logs. It implements both io.Writer and io.WriterTo.

GoDoc Build Status Coverage Status


  • No allocation during Write.
  • Full concurrent streaming (reading) support via multiple readers.
  • Automatic lock-less flushes for readers supporting http.Flusher.
  • Full test coverage.


  • Safely writes log to disk synchronously in addition to keeping a circular buffer.
  • Serves the circular log buffer over HTTP asychronously.
  • Writes log to stderr asynchronously.

This permits keeping all the output in case of a panic() on disk. Note that panic() output itself is only written to stderr since it uses print() builtin.

import (


func main() {
  logBuffer := circular.New(10 * 1024 * 1024)
  defer func() {
    // Flush ensures all readers have caught up.
    // Close gracefully closes the readers.
  f, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
  if err != nil {
  defer f.Close()

  // Send to both circular buffer and file.
  log.SetOutput(io.MultiWriter(logBuffer, f))

  // Asynchronously write to stderr.
  go logBuffer.WriteTo(os.Stderr)

  log.Printf("This line is served over HTTP; file and stderr")

      func(w http.ResponseWriter, r *http.Request) {
          w.Header().Set("Content-Type", "text/plain; charset=utf-8")
          // Streams the log buffer over HTTP until Close() is called.
          // AutoFlush ensures the log is not buffered locally indefinitely.
          logBuffer.WriteTo(circular.AutoFlush(w, time.Second))
  http.ListenAndServe(":6060", nil)