Skip to main content

Command Palette

Search for a command to run...

Notes: Go Design Patterns - Decorator Pattern

This blog contains decorator design pattern implementation using an improved standard library logger

Published
2 min read
Notes: Go Design Patterns - Decorator Pattern

Decorator pattern allows a user to add new functionality to an existing object without altering its structure. This pattern creates a decorator object that wraps the original object and provides additional functionality keeping the object method's signature intact. This is a structural pattern.

Here we'll use an improved logger wrapping standard logger to add color functionality based on the level of log e.g. blue color for warning, yellow color for error, etc.

First, we create a decorator object which wraps the standard logger:

type ImprovedLogger struct {
    Logger *log.Logger
}

Now, we add new functionality via a method that adds color to logs:

func (l *ImprovedLogger) ColoredOutputf(logType LogLevelType, buf *bytes.Buffer, format string, args ...interface{}) {
    fmt.Print(string(colorMap[logType]))
    l.Logger.Printf(format, args...)

    fmt.Print(buf)
    buf.Reset()
}

You can see that inside we used the standard library method l.Logger.Printf(format, args...) but added an additional functionality on top of it. This kind of implementation gives us the flexibility to add methods to the objects without modifying the object directly.

Complete working code where you can see the complete result:

package main

import (
    "bytes"
    "fmt"
    "log"
)

type LogLevelType string
type color string

const (
    Default LogLevelType = "default"
    Debug   LogLevelType = "debug"
    Error   LogLevelType = "error"
    Fatal   LogLevelType = "fatal"
    Info    LogLevelType = "info"
    Warn    LogLevelType = "warn"
)

var colorMap = map[LogLevelType]color{
    Default: "\033[0m",
    Debug:   "\033[37m",
    Error:   "\033[33m",
    Fatal:   "\033[31m",
    Info:    "\033[32m",
    Warn:    "\033[36m",
}

type ImprovedLogger struct {
    Logger *log.Logger
}

// new functionality
func (l *ImprovedLogger) ColoredOutputf(logType LogLevelType, buf *bytes.Buffer, format string, args ...interface{}) {
    fmt.Print(string(colorMap[logType]))
    l.Logger.Printf(format, args...)

    fmt.Print(buf)
    buf.Reset()
}

// modified existing functionality
func (l *ImprovedLogger) SetPrefix(service string, prefix string) {
    l.Logger.SetPrefix(service + " " + prefix)
}

// constructor
func NewImprovedLogger(buf *bytes.Buffer, prefix string, flag int, service string) *ImprovedLogger {
    logger := log.New(buf, prefix, flag)

    improvedLogger := &ImprovedLogger{
        Logger: logger,
    }

    // adding service name is mandatory
    improvedLogger.SetPrefix(service, prefix)

    return improvedLogger
}

func main() {
    var buf bytes.Buffer

    newLogger := NewImprovedLogger(&buf, "logger: ", log.Lshortfile, "(order service)")

    newLogger.ColoredOutputf(Warn, &buf, "warning")
    newLogger.ColoredOutputf(Error, &buf, "error")
    newLogger.ColoredOutputf(Info, &buf, "info")

    fmt.Print(string(colorMap[Default]))
}

More from this blog

btree.dev

28 posts

Hey 👋 , I am a Backend Engineer working at Getir. In this blog, I write about software architecture, distributed systems, and other interesting computer science concepts.