Master Your Logs and Status Codes: Introducing the Go Observability Sidekick


Are you tired of constantly tweaking your application code just to test your observability alerts? Whether it's validating log patterns for your SIEM or ensuring HTTP status code triggers fire correctly in your monitoring system, the traditional approach often involves modifying and redeploying your core application. This process is不仅 time-consuming but also introduces unnecessary risk to your production-ready code.
Enter the Go Logger Tester, a lightweight yet powerful utility designed to simplify your observability testing workflow. This project provides a simple Go-based API that allows you to programmatically generate logs with specific levels and messages, and return custom HTTP response codes, all without touching your main application's codebase. It's the perfect sidecar for your testing and validation environments!
The Problem It Solves
Modern cloud-native applications rely heavily on robust observability. We set up alerts for error logs, specific log patterns, and HTTP 5xx responses to ensure system health and quick incident response. However, testing these alerts effectively can be a significant challenge:
Log Alert Testing: How do you simulate an
ERROR
log with a very specific message that your alert rule is designed to catch? Often, developers resort to adding temporarylog.Error("TEST_ERROR_PATTERN_123")
lines to their application, then removing them after the test. This is cumbersome and error-prone.HTTP Alert Testing: Need to see if your system triggers an alert when a service returns a
500 Internal Server Error
or a404 Not Found
under specific conditions? Similar to logging, you might temporarily introduce error-inducing code paths.Deployment Overhead: Each change (even temporary ones) often means a new build, a new container image, and a redeployment cycle.
The Go Logger Tester directly addresses these pain points by providing a dedicated, external service that can be deployed alongside your application, acting as a "test harness" for your observability stack.
Core Features and How It Works
This project is built with simplicity and effectiveness in mind, leveraging Go's performance and the popular Gin web framework for its API.
1. Configurable Log Generation
The core feature is the ability to generate logs with a specified level (INFO, WARNING, ERROR) and custom content. This is crucial for testing log-based alerts. The application uses Go's standard log
package, configured to output to os.Stdout
, making it ideal for containerized environments where logs are typically collected from standard output streams.
Let's look at how the loggers are initialized:
// File: logger.go
package main
import (
"log"
"os"
)
var (
WarningLogger *log.Logger
InfoLogger *log.Logger
ErrorLogger *log.Logger
)
func init() {
InfoLogger = log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
WarningLogger = log.New(os.Stdout, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile)
ErrorLogger = log.New(os.Stdout, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
}
By defining separate loggers for each level, the output is clearly prefixed, aiding in parsing and filtering by log aggregation systems. The log.Lshortfile
flag is particularly useful for debugging, as it shows the file and line number where the log was called.
2. Custom HTTP Status Code Responses
Beyond logging, the application allows you to dictate the HTTP status code it returns. This is invaluable for testing API Gateway rules, load balancer health checks, or monitoring alerts that trigger on specific HTTP response ranges (e.g., 5xx errors).
3. Simple JSON API Endpoint
The POST /log
endpoint is the heart of the application, accepting a JSON payload that defines the desired log message, log level, and HTTP status code for the response.
Here's the data structure it expects:
// File: main.go
type logData struct {
Data string `json:"data"`
StatusCode int `json:"statusCode"`
Level string `json:"level"`
}
And the logic to process it:
// File: main.go
func postLog(c *gin.Context) {
var newLog logData
if err := c.BindJSON(&newLog); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"})
return
}
// Default to Bad Request if statusCode is invalid or level is unknown
statusCode := newLog.StatusCode
data := newLog.Data
switch level := strings.ToUpper(newLog.Level); level {
case "INFO":
InfoLogger.Println(newLog.Data)
case "WARNING":
WarningLogger.Println(newLog.Data)
case "ERROR":
ErrorLogger.Println(newLog.Data)
default:
statusCode = http.StatusBadRequest // If level is invalid, return Bad Request
data = "Incorrect Level. Must be INFO, WARNING, or ERROR."
}
// Validate the status code to ensure it's a known HTTP status
if len(http.StatusText(newLog.StatusCode)) == 0 {
data = "Incorrect status code. Please provide a valid HTTP status code."
statusCode = http.StatusBadRequest
}
c.JSON(statusCode, data) // Respond with the requested status code and data
}
This function demonstrates:
JSON Binding: Gin efficiently parses the incoming JSON request into the
logData
struct.Log Level Handling: A
switch
statement dynamically dispatches the log message to the appropriateInfoLogger
,WarningLogger
, orErrorLogger
. Invalid levels result in a400 Bad Request
response.Status Code Control: The
c.JSON()
call ensures the HTTP response code is set to the value provided in the payload, with basic validation to ensure it's a valid HTTP status code.
4. Docker-Ready Deployment
The project includes a multi-stage Dockerfile
, making it incredibly easy to build a small, efficient Docker image. This is key for deploying it as a sidecar container in Kubernetes or any containerized environment.
# File: Dockerfile
FROM golang:${GO_VERSION}-alpine AS builder
# ... install build dependencies ...
WORKDIR /api
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY *.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /app .
FROM alpine:latest
WORKDIR /api
COPY --from=builder /app .
ENV GIN_MODE release
EXPOSE 8080
ENTRYPOINT ["./app"]
This Dockerfile
is optimized for production, building a lean image by copying only the compiled Go binary into a minimal alpine:latest
base image.
How to Use It
Using the Go Logger Tester is straightforward. Once deployed (e.g., as a sidecar alongside your main application in a Kubernetes pod), you can interact with it using a simple curl
command:
curl http://localhost:8080/log \
--header "Content-Type: application/json" \
--request "POST" \
--data '{"data":"This is a test INFO log message for my cluster.","statusCode":201, "level":"INFO"}'
This command will:
Print
INFO: <timestamp> <filename>:... This is a test INFO log message for my cluster.
to the standard output of thego.logger
container.Return an HTTP
201 Created
status code in its response.
You can change level
to WARNING
or ERROR
, and statusCode
to 500
, 404
, or any other valid HTTP code to test different scenarios. There's also a simple GET /ping
endpoint for health checks.
Conclusion
The Go Logger Tester empowers developers and SREs to take control of their observability testing. By providing a dedicated, configurable service for generating specific logs and HTTP responses, it eliminates the need for intrusive code changes in your main applications. This "sidecar" pattern promotes cleaner code, faster testing cycles, and more reliable observability setups.
Future Enhancements could include:
Configurable Log Formats: Support for JSON, CSV, or other structured log formats.
Delayed Responses/Errors: Introduce artificial delays or intermittent errors to simulate real-world network conditions.
More Complex Payloads: Allow logging of more complex data structures.
Metrics Generation: Integrate with Prometheus or other metrics systems to generate custom metrics on demand.
Whether you're validating a new log aggregation pipeline, fine-tuning your alerting rules, or simply demonstrating observability capabilities, the Go Logger Tester is a valuable addition to your toolkit. Give it a try and streamline your observability testing today!
Github Repo
Subscribe to my newsletter
Read articles from Nikhil Ikhar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
