Skip to content

In-Memory Logger ​

Provides a thread-safe in-memory circular buffer logger with WebSocket-based real-time streaming for log data.

Overview ​

The memlogger package implements a thread-safe in-memory log buffer with WebSocket streaming capabilities. It stores log data in memory and pushes new entries to connected WebSocket clients and event subscribers.

Primary Consumers ​

  • internal/api/v1/cert/renew - Provides WebSocket endpoint for certificate renewal logs
  • Diagnostic and debugging interfaces

Non-goals ​

  • Does not persist logs to disk
  • Does not provide log rotation or retention policies
  • Does not support structured/log levels
  • Does not provide authentication for WebSocket connections

Stability ​

Internal package. Public interfaces are stable.

Public API ​

Exported Types ​

MemLogger Interface ​

go
type MemLogger io.Writer

The MemLogger is an io.Writer interface. Any data written to it is stored in the circular buffer and broadcast to subscribers.

Exported Functions ​

GetMemLogger ​

go
func GetMemLogger() MemLogger

Returns the global singleton MemLogger instance.

Example:

go
logger := memlogger.GetMemLogger()
logger.Write([]byte("log message"))

HandlerFunc ​

go
func HandlerFunc() gin.HandlerFunc

Returns a Gin middleware handler that upgrades HTTP connections to WebSocket and streams log data.

Example:

go
router.GET("/logs/ws", memlogger.HandlerFunc())

Events ​

go
func Events() (<-chan []byte, func())

Returns a channel for receiving log events and a cancel function to unsubscribe.

Returns:

  • <-chan []byte - Channel receiving log entry slices
  • func() - Cleanup function that unsubscribes and closes the channel

Example:

go
ch, cancel := memlogger.Events()
defer cancel()

for event := range ch {
    fmt.Println(string(event))
}

Architecture ​

Core Components ​

ComponentResponsibility
memLoggerMain struct holding buffer and subscription maps
bytes.BufferCircular buffer for log storage (16KB max)
connChansxsync.Map of WebSocket channels
listenersxsync.Map of event channels

Write Flow ​

Buffer Behavior ​

The circular buffer has fixed maximum size:

PropertyValue
Maximum Size16 KB
Truncate Threshold8 KB (50%)
Write Chunk Size4 KB
Write Timeout10 seconds

Truncation Logic: When the buffer exceeds the maximum size:

  1. The buffer is truncated to 8 KB (half the maximum)
  2. Oldest entries are removed first
  3. Recent logs are always preserved

Thread Safety ​

Multiple synchronization mechanisms ensure thread safety:

FieldMutex TypePurpose
Buffersync.RWMutexProtecting buffer operations
notifyLocksync.RWMutexProtecting notification maps
connChansxsync.MapThread-safe WebSocket channel storage
listenersxsync.MapThread-safe event listener storage

Configuration Surface ​

No explicit configuration. The singleton instance uses fixed constants:

go
const (
    maxMemLogSize         = 16 * 1024  // 16KB buffer
    truncateSize          = maxMemLogSize / 2  // 8KB
    initialWriteChunkSize = 4 * 1024
    writeTimeout          = 10 * time.Second
)

Dependency and Integration Map ​

Internal Dependencies ​

DependencyPurpose
github.com/yusing/goutils/http/websocketWebSocket management

External Dependencies ​

DependencyPurpose
github.com/gin-gonic/ginHTTP/WebSocket handling
github.com/puzpuzpuz/xsync/v4Concurrent map operations

Observability ​

Logs ​

No logging in this package. Errors are returned via WebSocket write failures.

Metrics ​

None exposed.

Failure Modes and Recovery ​

FailureDetectionRecovery
WebSocket write timeout3-second timerSkip subscriber, continue
Buffer write errorwriteBuf returns errorLogged but not returned
Subscriber channel fullChannel send timeoutSkip subscriber, continue
Buffer exceeds max sizetruncateIfNeededTruncate to 8KB

Concurrency Guarantees ​

  • Multiple goroutines can write concurrently
  • Multiple WebSocket connections supported
  • Subscriptions can be added/removed during operation
  • Buffer truncation is atomic

Usage Examples ​

Basic Log Writing ​

go
import "github.com/yusing/godoxy/internal/logging/memlogger"

logger := memlogger.GetMemLogger()

// Write a simple message
logger.Write([]byte("Application started\n"))

// Write formatted logs
logger.Write([]byte(fmt.Sprintf("[INFO] Request received: %s\n", path)))

WebSocket Endpoint ​

go
import (
    "github.com/gin-gonic/gin"
    "github.com/yusing/godoxy/internal/logging/memlogger"
)

func setupRouter(r *gin.Engine) {
    // Real-time log streaming via WebSocket
    r.GET("/api/logs/stream", memlogger.HandlerFunc())
}

Subscribing to Log Events ​

go
func monitorLogs(ctx context.Context) {
    ch, cancel := memlogger.Events()
    defer cancel()

    for {
        select {
        case <-ctx.Done():
            return
        case event := <-ch:
            processLogEvent(event)
        }
    }
}

func processLogEvent(event []byte) {
    // Handle the log event
    fmt.Printf("Log: %s", string(event))
}

WebSocket Client ​

javascript
// Client-side JavaScript
const ws = new WebSocket("ws://localhost:8080/api/logs/stream");

ws.onmessage = (event) => {
  console.log("New log entry:", event.data);
};

ws.onclose = () => {
  console.log("Log stream disconnected");
};

ws.onerror = (error) => {
  console.error("Log stream error:", error);
};

Complete Integration ​

go
func setupLogging(r *gin.Engine) *memlogger.MemLogger {
    logger := memlogger.GetMemLogger()

    // WebSocket endpoint for real-time logs
    r.GET("/ws/logs", memlogger.HandlerFunc())

    return logger
}

// Elsewhere in the application
func recordRequest(logger memlogger.MemLogger, path string, status int) {
    logger.Write([]byte(fmt.Sprintf("[%s] %s - %d\n",
        time.Now().Format(time.RFC3339), path, status)))
}

Performance Characteristics ​

  • O(1) write operations (amortized)
  • O(n) for truncation where n is buffer size
  • WebSocket notifications are non-blocking (3-second timeout)
  • Memory usage is bounded at 16KB

Testing Notes ​

  • Mock by providing a custom io.Writer implementation
  • Test concurrent writes with goroutines
  • Verify truncation behavior
  • Test WebSocket upgrade failures
  • internal/api - HTTP API endpoints
  • github.com/gin-gonic/gin - HTTP framework
  • github.com/yusing/goutils/http/websocket - WebSocket utilities

Released under the MIT License.