Building Automation Services with Go: Practical Tools & Real-World Solutions
Master building useful automation services and tools with Go. Learn to create production-ready services that solve real problems: log processors, API monitors, deployment tools, data pipelines, and more.
Introduction: Go Services That Solve Real Problems
Most developers think of building automation as a side project. A script. Something quick and temporary.
Wrong.
The best automation is built as a service. A tool. Something that runs continuously, reliably, and solves a real problem.
Go is perfect for this. Not just because it’s fast and concurrent. But because it lets you build services that:
- Deploy as a single binary
- Run with minimal resources
- Scale horizontally
- Integrate with existing tools
- Require zero infrastructure
This guide teaches you to build real automation services with Go. Services you’d actually use. Services you’d deploy to production.
Not theoretical examples. Not hello-world toys.
Real tools that solve real problems.
Chapter 1: The Anatomy of an Automation Service
Before we build, understand what makes a good automation service.
Essential Components
Component 1: Configuration Management
Your service needs to be configurable. Environment variables, config files, command-line flags.
type Config struct {
APIKey string
Port int
DatabaseURL string
LogLevel string
}
func loadConfig() Config {
return Config{
APIKey: os.Getenv("API_KEY"),
Port: getEnvInt("PORT", 8080),
DatabaseURL: os.Getenv("DATABASE_URL"),
LogLevel: os.Getenv("LOG_LEVEL", "info"),
}
}
Component 2: Structured Logging
Your service must be observable. You need logs, not print statements.
import "github.com/sirupsen/logrus"
func main() {
log := logrus.New()
log.SetFormatter(&logrus.JSONFormatter{})
log.WithFields(logrus.Fields{
"service": "api-monitor",
"version": "1.0.0",
}).Info("Service started")
}
Component 3: Health Checks
Services need to report their status. A /health endpoint.
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
health := map[string]string{
"status": "healthy",
"timestamp": time.Now().Format(time.RFC3339),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(health)
}
Component 4: Graceful Shutdown
Services should shut down cleanly. Not abruptly kill connections.
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-sigChan
log.Info("Shutdown signal received, closing gracefully...")
server.Close()
}()
Component 5: Metrics & Monitoring
Services should expose metrics. For debugging, alerting, and observability.
import "github.com/prometheus/client_golang/prometheus"
var (
requestsProcessed = prometheus.NewCounter(prometheus.CounterOpts{
Name: "requests_processed_total",
Help: "Total number of requests processed",
})
processingTime = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "request_duration_seconds",
Help: "Time spent processing request",
})
)
Chapter 2: Service 1 - API Health Monitor
Monitor multiple APIs and alert on failures.
package main
import (
"fmt"
"io"
"net/http"
"os"
"sync"
"time"
"github.com/sirupsen/logrus"
)
type APICheck struct {
Name string
URL string
Interval time.Duration
Timeout time.Duration
}
type Monitor struct {
checks map[string]*APICheck
logger *logrus.Logger
mu sync.RWMutex
}
func NewMonitor() *Monitor {
logger := logrus.New()
logger.SetFormatter(&logrus.JSONFormatter{})
return &Monitor{
checks: make(map[string]*APICheck),
logger: logger,
}
}
func (m *Monitor) AddCheck(check APICheck) {
m.mu.Lock()
defer m.mu.Unlock()
m.checks[check.Name] = &check
}
func (m *Monitor) Start() {
m.mu.RLock()
checks := make([]*APICheck, 0, len(m.checks))
for _, check := range m.checks {
checks = append(checks, check)
}
m.mu.RUnlock()
for _, check := range checks {
go m.monitorAPI(check)
}
}
func (m *Monitor) monitorAPI(check *APICheck) {
ticker := time.NewTicker(check.Interval)
defer ticker.Stop()
for range ticker.C {
m.performCheck(check)
}
}
func (m *Monitor) performCheck(check *APICheck) {
start := time.Now()
client := &http.Client{
Timeout: check.Timeout,
}
resp, err := client.Get(check.URL)
duration := time.Since(start)
if err != nil {
m.logger.WithFields(logrus.Fields{
"api": check.Name,
"status": "down",
"duration": duration,
"error": err.Error(),
}).Error("API check failed")
m.alertSlack(fmt.Sprintf("🚨 %s is DOWN: %v", check.Name, err))
return
}
defer resp.Body.Close()
// Discard body (don't need to read it)
io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
m.logger.WithFields(logrus.Fields{
"api": check.Name,
"status": "unhealthy",
"http_code": resp.StatusCode,
"duration": duration,
}).Warn("API returned non-200 status")
m.alertSlack(fmt.Sprintf("⚠️ %s returned %d", check.Name, resp.StatusCode))
return
}
m.logger.WithFields(logrus.Fields{
"api": check.Name,
"status": "healthy",
"duration": duration,
}).Debug("API check passed")
}
func (m *Monitor) alertSlack(message string) {
webhook := os.Getenv("SLACK_WEBHOOK")
if webhook == "" {
return
}
payload := fmt.Sprintf(`{"text": "%s"}`, message)
http.Post(webhook, "application/json", io.NopCloser(nil))
}
func main() {
monitor := NewMonitor()
// Add checks
monitor.AddCheck(APICheck{
Name: "API Server",
URL: "http://localhost:8080/health",
Interval: 30 * time.Second,
Timeout: 5 * time.Second,
})
monitor.AddCheck(APICheck{
Name: "Database API",
URL: "http://localhost:5432/health",
Interval: 60 * time.Second,
Timeout: 5 * time.Second,
})
monitor.Start()
// Keep running
select {}
}
Deploy as service:
# Build
go build -o api-monitor
# Run
SLACK_WEBHOOK="https://hooks.slack.com/..." ./api-monitor
# Or in Docker
docker build -t api-monitor .
docker run -e SLACK_WEBHOOK="..." api-monitor
Chapter 3: Service 2 - Log Processor & Analyzer
Process logs in real-time, extract metrics, detect patterns.
package main
import (
"bufio"
"flag"
"fmt"
"io"
"log"
"os"
"regexp"
"strings"
"sync"
"sync/atomic"
"time"
)
type LogProcessor struct {
errorCount int64
warningCount int64
successCount int64
slowRequests int64
slowThreshold int
errorPatterns map[string]int64
mu sync.RWMutex
started time.Time
}
func NewLogProcessor(slowThreshold int) *LogProcessor {
return &LogProcessor{
slowThreshold: slowThreshold,
errorPatterns: make(map[string]int64),
started: time.Now(),
}
}
func (lp *LogProcessor) ProcessStream(reader io.Reader) error {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
lp.processLine(line)
}
return scanner.Err()
}
func (lp *LogProcessor) processLine(line string) {
// Example log format: [2026-02-17T10:30:45] ERROR Database connection failed
if strings.Contains(line, "ERROR") {
atomic.AddInt64(&lp.errorCount, 1)
lp.recordErrorPattern(line)
} else if strings.Contains(line, "WARN") {
atomic.AddInt64(&lp.warningCount, 1)
} else if strings.Contains(line, "SUCCESS") || strings.Contains(line, "INFO") {
atomic.AddInt64(&lp.successCount, 1)
}
// Detect slow requests
if match := regexp.MustCompile(`duration=(\d+)ms`).FindStringSubmatch(line); len(match) > 1 {
var duration int
fmt.Sscanf(match[1], "%d", &duration)
if duration > lp.slowThreshold {
atomic.AddInt64(&lp.slowRequests, 1)
}
}
}
func (lp *LogProcessor) recordErrorPattern(line string) {
// Extract error message
parts := strings.SplitN(line, "ERROR", 2)
if len(parts) < 2 {
return
}
pattern := strings.TrimSpace(parts[1])
// Keep only first 50 chars to group similar errors
if len(pattern) > 50 {
pattern = pattern[:50]
}
lp.mu.Lock()
lp.errorPatterns[pattern]++
lp.mu.Unlock()
}
func (lp *LogProcessor) Report() {
elapsed := time.Since(lp.started)
fmt.Printf("\n=== Log Analysis Report ===\n")
fmt.Printf("Time: %.0f seconds\n", elapsed.Seconds())
fmt.Printf("Errors: %d\n", atomic.LoadInt64(&lp.errorCount))
fmt.Printf("Warnings: %d\n", atomic.LoadInt64(&lp.warningCount))
fmt.Printf("Successes: %d\n", atomic.LoadInt64(&lp.successCount))
fmt.Printf("Slow Requests (>%dms): %d\n", lp.slowThreshold, atomic.LoadInt64(&lp.slowRequests))
if atomic.LoadInt64(&lp.errorCount) > 0 {
fmt.Printf("\nTop Error Patterns:\n")
lp.mu.RLock()
for pattern, count := range lp.errorPatterns {
if count > 0 {
fmt.Printf(" %d × %s\n", count, pattern)
}
}
lp.mu.RUnlock()
}
errorRate := float64(atomic.LoadInt64(&lp.errorCount)) /
float64(atomic.LoadInt64(&lp.errorCount) + atomic.LoadInt64(&lp.successCount)) * 100
fmt.Printf("\nError Rate: %.2f%%\n", errorRate)
}
func main() {
slowThreshold := flag.Int("threshold", 1000, "Slow request threshold in milliseconds")
flag.Parse()
processor := NewLogProcessor(*slowThreshold)
// Process stdin or file
var input io.Reader
if len(flag.Args()) > 0 {
file, err := os.Open(flag.Args()[0])
if err != nil {
log.Fatal(err)
}
defer file.Close()
input = file
} else {
input = os.Stdin
}
if err := processor.ProcessStream(input); err != nil {
log.Fatal(err)
}
processor.Report()
}
Use it:
# Build
go build -o log-analyzer
# Process file
./log-analyzer --threshold 500 app.log
# Process live logs
tail -f app.log | ./log-analyzer
# Process from stdin
cat production.log | ./log-analyzer --threshold 1000
Chapter 4: Service 3 - Scheduled Task Runner
Run tasks on a schedule, with retries and notifications.
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/robfig/cron/v3"
)
type Task struct {
Name string
Schedule string
Func func(ctx context.Context) error
MaxRetry int
}
type TaskRunner struct {
cron *cron.Cron
tasks map[string]*Task
mu sync.RWMutex
logger func(string, ...interface{})
onError func(string, error)
onSuccess func(string, time.Duration)
}
func NewTaskRunner() *TaskRunner {
return &TaskRunner{
cron: cron.New(),
tasks: make(map[string]*Task),
logger: log.Printf,
}
}
func (tr *TaskRunner) AddTask(task Task) error {
tr.mu.Lock()
defer tr.mu.Unlock()
// Schedule the task
_, err := tr.cron.AddFunc(task.Schedule, func() {
tr.executeTask(task)
})
if err != nil {
return fmt.Errorf("failed to schedule task: %w", err)
}
tr.tasks[task.Name] = &task
tr.logger("Task scheduled: %s (%s)", task.Name, task.Schedule)
return nil
}
func (tr *TaskRunner) executeTask(task Task) {
var lastErr error
start := time.Now()
for attempt := 1; attempt <= task.MaxRetry; attempt++ {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
err := task.Func(ctx)
cancel()
if err == nil {
duration := time.Since(start)
tr.logger("✓ %s completed in %v", task.Name, duration)
if tr.onSuccess != nil {
tr.onSuccess(task.Name, duration)
}
return
}
lastErr = err
if attempt < task.MaxRetry {
tr.logger("⚠ %s attempt %d/%d failed: %v, retrying...",
task.Name, attempt, task.MaxRetry, err)
time.Sleep(time.Duration(attempt*5) * time.Second)
}
}
tr.logger("✗ %s failed after %d attempts: %v", task.Name, task.MaxRetry, lastErr)
if tr.onError != nil {
tr.onError(task.Name, lastErr)
}
}
func (tr *TaskRunner) Start() {
tr.cron.Start()
tr.logger("Task runner started with %d tasks", len(tr.tasks))
}
func (tr *TaskRunner) Stop() {
tr.cron.Stop()
tr.logger("Task runner stopped")
}
// Example tasks
func backupDatabase(ctx context.Context) error {
log.Println("Backing up database...")
// Your backup logic
time.Sleep(2 * time.Second)
return nil
}
func cleanupTempFiles(ctx context.Context) error {
log.Println("Cleaning up temp files...")
// Your cleanup logic
return nil
}
func sendDailyReport(ctx context.Context) error {
log.Println("Sending daily report...")
// Your report logic
return nil
}
func main() {
runner := NewTaskRunner()
// Add tasks
runner.AddTask(Task{
Name: "Database Backup",
Schedule: "0 2 * * *", // 2 AM daily
Func: backupDatabase,
MaxRetry: 3,
})
runner.AddTask(Task{
Name: "Cleanup Temp Files",
Schedule: "@daily",
Func: cleanupTempFiles,
MaxRetry: 2,
})
runner.AddTask(Task{
Name: "Daily Report",
Schedule: "0 9 * * 1-5", // 9 AM weekdays
Func: sendDailyReport,
MaxRetry: 3,
})
// Handle errors
runner.onError = func(name string, err error) {
fmt.Printf("Task %s failed: %v\n", name, err)
// Send alert to Slack, email, etc.
}
runner.Start()
// Graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
<-sigChan
runner.Stop()
}
Chapter 5: Service 4 - Data Pipeline
Transform and process data from multiple sources.
package main
import (
"context"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"sync"
"time"
)
type Pipeline struct {
sources []Source
transformers []Transformer
sinks []Sink
logger *log.Logger
}
type Source interface {
Name() string
Fetch(ctx context.Context) ([]map[string]interface{}, error)
}
type Transformer interface {
Transform(data []map[string]interface{}) []map[string]interface{}
}
type Sink interface {
Write(data []map[string]interface{}) error
}
// Example source: API
type APISource struct {
name string
url string
}
func (s *APISource) Name() string { return s.name }
func (s *APISource) Fetch(ctx context.Context) ([]map[string]interface{}, error) {
req, _ := http.NewRequestWithContext(ctx, "GET", s.url, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var data []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)
return data, nil
}
// Example transformer: Filter
type FilterTransformer struct {
field string
value interface{}
}
func (t *FilterTransformer) Transform(data []map[string]interface{}) []map[string]interface{} {
result := make([]map[string]interface{}, 0)
for _, item := range data {
if val, ok := item[t.field]; ok && val == t.value {
result = append(result, item)
}
}
return result
}
// Example sink: CSV file
type CSVSink struct {
filename string
mu sync.Mutex
}
func (s *CSVSink) Write(data []map[string]interface{}) error {
s.mu.Lock()
defer s.mu.Unlock()
file, err := os.Create(s.filename)
if err != nil {
return err
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// Write headers
if len(data) > 0 {
headers := make([]string, 0)
for k := range data[0] {
headers = append(headers, k)
}
writer.Write(headers)
// Write rows
for _, item := range data {
row := make([]string, len(headers))
for i, h := range headers {
row[i] = fmt.Sprintf("%v", item[h])
}
writer.Write(row)
}
}
return nil
}
func NewPipeline() *Pipeline {
return &Pipeline{
logger: log.New(os.Stdout, "[PIPELINE] ", log.LstdFlags),
}
}
func (p *Pipeline) AddSource(s Source) {
p.sources = append(p.sources, s)
}
func (p *Pipeline) AddTransformer(t Transformer) {
p.transformers = append(p.transformers, t)
}
func (p *Pipeline) AddSink(s Sink) {
p.sinks = append(p.sinks, s)
}
func (p *Pipeline) Run(ctx context.Context) error {
p.logger.Println("Pipeline starting")
start := time.Now()
// Fetch from all sources
var allData []map[string]interface{}
for _, source := range p.sources {
p.logger.Printf("Fetching from %s...", source.Name())
data, err := source.Fetch(ctx)
if err != nil {
p.logger.Printf("Error fetching from %s: %v", source.Name(), err)
continue
}
allData = append(allData, data...)
p.logger.Printf("Fetched %d records from %s", len(data), source.Name())
}
// Apply transformations
for _, transformer := range p.transformers {
p.logger.Println("Applying transformation...")
allData = transformer.Transform(allData)
p.logger.Printf("After transformation: %d records", len(allData))
}
// Write to sinks
for _, sink := range p.sinks {
p.logger.Println("Writing to sink...")
if err := sink.Write(allData); err != nil {
p.logger.Printf("Error writing to sink: %v", err)
}
}
p.logger.Printf("Pipeline completed in %v", time.Since(start))
return nil
}
func main() {
pipeline := NewPipeline()
// Add source
pipeline.AddSource(&APISource{
name: "API",
url: "https://api.example.com/data",
})
// Add transformer
pipeline.AddTransformer(&FilterTransformer{
field: "status",
value: "active",
})
// Add sink
pipeline.AddSink(&CSVSink{
filename: "output.csv",
})
// Run
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
if err := pipeline.Run(ctx); err != nil {
log.Fatal(err)
}
}
Chapter 6: Service 5 - Deployment Orchestrator
Deploy to multiple environments with status tracking.
package main
import (
"context"
"fmt"
"os/exec"
"sync"
"time"
)
type Deployment struct {
ID string
Service string
Environment string
Version string
Status string
StartTime time.Time
EndTime time.Time
Output string
}
type DeploymentOrchestrator struct {
deployments map[string]*Deployment
mu sync.RWMutex
logger func(string, ...interface{})
}
func NewDeploymentOrchestrator() *DeploymentOrchestrator {
return &DeploymentOrchestrator{
deployments: make(map[string]*Deployment),
logger: fmt.Printf,
}
}
func (do *DeploymentOrchestrator) Deploy(service, environment, version string) (*Deployment, error) {
id := fmt.Sprintf("%s-%s-%d", service, environment, time.Now().Unix())
deployment := &Deployment{
ID: id,
Service: service,
Environment: environment,
Version: version,
Status: "pending",
StartTime: time.Now(),
}
do.mu.Lock()
do.deployments[id] = deployment
do.mu.Unlock()
go do.executeDeployment(deployment)
return deployment, nil
}
func (do *DeploymentOrchestrator) executeDeployment(d *Deployment) {
d.Status = "running"
do.logger("Starting deployment %s\n", d.ID)
// Step 1: Build
if err := do.buildService(d); err != nil {
d.Status = "failed"
d.Output = err.Error()
d.EndTime = time.Now()
do.logger("Build failed: %v\n", err)
return
}
// Step 2: Test
if err := do.testService(d); err != nil {
d.Status = "failed"
d.Output = err.Error()
d.EndTime = time.Now()
do.logger("Tests failed: %v\n", err)
return
}
// Step 3: Deploy
if err := do.deployToEnvironment(d); err != nil {
d.Status = "failed"
d.Output = err.Error()
d.EndTime = time.Now()
do.logger("Deployment failed: %v\n", err)
return
}
// Step 4: Verify
if err := do.verifyDeployment(d); err != nil {
d.Status = "failed"
d.Output = err.Error()
d.EndTime = time.Now()
do.logger("Verification failed: %v\n", err)
return
}
d.Status = "success"
d.EndTime = time.Now()
do.logger("Deployment %s completed successfully\n", d.ID)
}
func (do *DeploymentOrchestrator) buildService(d *Deployment) error {
do.logger("Building %s:%s\n", d.Service, d.Version)
cmd := exec.Command("go", "build", "-o", d.Service)
output, err := cmd.CombinedOutput()
d.Output += string(output)
return err
}
func (do *DeploymentOrchestrator) testService(d *Deployment) error {
do.logger("Testing %s\n", d.Service)
cmd := exec.Command("go", "test", "./...")
output, err := cmd.CombinedOutput()
d.Output += string(output)
return err
}
func (do *DeploymentOrchestrator) deployToEnvironment(d *Deployment) error {
do.logger("Deploying to %s\n", d.Environment)
cmd := exec.Command("kubectl", "set", "image",
fmt.Sprintf("deployment=%s", d.Service),
fmt.Sprintf("%s=registry/%s:%s", d.Service, d.Service, d.Version),
"-n", d.Environment,
)
output, err := cmd.CombinedOutput()
d.Output += string(output)
return err
}
func (do *DeploymentOrchestrator) verifyDeployment(d *Deployment) error {
do.logger("Verifying deployment of %s\n", d.Service)
for i := 0; i < 30; i++ {
cmd := exec.Command("kubectl", "rollout", "status",
fmt.Sprintf("deployment/%s", d.Service),
"-n", d.Environment,
)
if err := cmd.Run(); err == nil {
return nil
}
time.Sleep(2 * time.Second)
}
return fmt.Errorf("deployment verification timeout")
}
func (do *DeploymentOrchestrator) GetStatus(id string) *Deployment {
do.mu.RLock()
defer do.mu.RUnlock()
return do.deployments[id]
}
func (do *DeploymentOrchestrator) GetAll() []*Deployment {
do.mu.RLock()
defer do.mu.RUnlock()
deployments := make([]*Deployment, 0, len(do.deployments))
for _, d := range do.deployments {
deployments = append(deployments, d)
}
return deployments
}
func main() {
orchestrator := NewDeploymentOrchestrator()
// Deploy multiple services
deploys := []struct {
service string
environment string
version string
}{
{"api-service", "staging", "v1.2.3"},
{"web-service", "staging", "v1.2.3"},
{"worker-service", "staging", "v1.2.3"},
}
for _, d := range deploys {
deployment, _ := orchestrator.Deploy(d.service, d.environment, d.version)
fmt.Printf("Started deployment: %s\n", deployment.ID)
}
// Monitor progress
for {
time.Sleep(5 * time.Second)
allDeployments := orchestrator.GetAll()
pending := 0
for _, d := range allDeployments {
if d.Status == "pending" || d.Status == "running" {
pending++
}
}
if pending == 0 {
fmt.Println("All deployments complete")
break
}
fmt.Printf("Still running: %d deployments\n", pending)
}
// Print final status
for _, d := range orchestrator.GetAll() {
duration := d.EndTime.Sub(d.StartTime)
fmt.Printf("%s: %s (%.0fs)\n", d.ID, d.Status, duration.Seconds())
}
}
Chapter 7: Deploying Services
Make your services production-ready.
Docker
Dockerfile:
FROM golang:1.21-alpine AS builder
WORKDIR /build
COPY . .
RUN go build -o service .
FROM alpine:latest
RUN apk --no-cache add ca-certificates curl
COPY --from=builder /build/service /usr/local/bin/
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
EXPOSE 8080
ENTRYPOINT ["service"]
Build and run:
docker build -t my-service:v1 .
docker run -p 8080:8080 my-service:v1
Kubernetes
deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
spec:
replicas: 3
selector:
matchLabels:
app: my-service
template:
metadata:
labels:
app: my-service
spec:
containers:
- name: my-service
image: my-service:v1
ports:
- containerPort: 8080
env:
- name: LOG_LEVEL
value: "info"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
Deploy:
kubectl apply -f deployment.yaml
kubectl get pods -l app=my-service
kubectl logs -f deployment/my-service
Chapter 8: Monitoring Services
Keep your services healthy and observable.
Prometheus Metrics
import "github.com/prometheus/client_golang/prometheus"
var (
tasksDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "tasks_duration_seconds",
Help: "Time taken to execute tasks",
},
[]string{"task_name", "status"},
)
tasksTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "tasks_total",
Help: "Total tasks executed",
},
[]string{"task_name", "status"},
)
)
func init() {
prometheus.MustRegister(tasksDuration, tasksTotal)
}
Health Endpoint
func healthHandler(w http.ResponseWriter, r *http.Request) {
health := map[string]interface{}{
"status": "healthy",
"uptime": time.Since(startTime).Seconds(),
"timestamp": time.Now().RFC3339,
"version": "1.0.0",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(health)
}
Appendix A: Common Patterns
Error Handling:
if err != nil {
log.WithError(err).Error("Operation failed")
return fmt.Errorf("failed operation: %w", err)
}
Timeouts:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
Graceful Shutdown:
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM)
<-sigChan
cleanup()
Concurrency:
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(i Item) {
defer wg.Done()
process(i)
}(item)
}
wg.Wait()
Appendix B: Service Deployment Checklist
Before Deploying:
- ✅ Unit tests pass
- ✅ Integration tests pass
- ✅ Docker image builds
- ✅ Health endpoint responds
- ✅ Metrics exposed
- ✅ Logging configured
- ✅ Configuration in environment variables
- ✅ Error handling complete
After Deploying:
- ✅ Service starts without errors
- ✅ Health checks passing
- ✅ Metrics available
- ✅ Logs appearing
- ✅ Can handle graceful shutdown
- ✅ Alerts configured
- ✅ Monitoring dashboard set up
Conclusion: Services That Matter
Go gives you the tools to build automation services that:
- Deploy easily (single binary)
- Scale reliably (concurrent by default)
- Monitor clearly (metrics and logging)
- Run safely (graceful shutdown)
- Integrate cleanly (HTTP, gRPC, CLI)
These aren’t hobby projects. They’re production services that run your operations.
Start with one. Build it right. Deploy it safely.
Then build the next one.
Tags
Related Articles
Automation with Go: Building Scalable, Concurrent Systems for Real-World Tasks
Master Go for automation. Learn to build fast, concurrent automation tools, CLI utilities, monitoring systems, and deployment pipelines. Go's concurrency model makes it perfect for real-world automation.
Automation Tools for Developers: Real Workflows Without AI - CLI, Scripts & Open Source
Master free automation tools for developers. Learn to automate repetitive tasks, workflows, deployments, monitoring, and operations. Build custom automation pipelines with open-source tools—no AI needed.
Data Analysis for Backend Engineers: Using Metrics to Make Better Technical Decisions
Master data analysis as a backend engineer. Learn to collect meaningful metrics, analyze performance data, avoid common pitfalls, and make technical decisions backed by evidence instead of hunches.