Extending Packages with Go

Go makes extending packages easy. You can easily take a package and add your own functionality. Let’s use logging as an example.

A popular logging package for Go is logrus. It’s at “github.com/sirupsen/logrus”. So let’s start by creating a package of our own and import logrus as a dependency:

package cloudlogs

import (
    "github.com/sirupsen/logrus"
)

Now that we’ve imported logrus, let’s extend it by creating our own custom logging entry. Let’s say that our company CloudForums, Inc requires that we have the app owner and their phone number as part of their application logs (stupid example but you get the point) and that the logs be in JSON format for easier parsing. Let’s create our own logging entry so that you don’t need to define that every time you write an application:

package cloudlogs

import (
    "os"
    "github.com/sirupsen/logrus"
)

type AppOwner struct {
    Name   string
    Number string
}

type CloudLogger interface {
    Debug(args ...interface{})
    Info(args ...interface{})
    Print(args ...interface{})
    Warn(args ...interface{})
    Warning(args ...interface{})
    Error(args ...interface{})
    Fatal(args ...interface{})
    Panic(args ...interface{})
}

var hostName := os.Getenv("HOSTNAME")

func New(name, number string) CloudLogger {

    logger := logrus.New()    

    logger.SetFormatter(&logrus.JSONFormatter{})

    logger.SetReportCaller(true)

    entry := logger.WithFields(logrus.Fields{
        "owner_name": name,
        "owner_number": number,
    })

    return entry

}

We create our own interface that implements only the methods we want our logging package to have. This is both to simplify the example and to show that you can slim down the options available to people if you only want to expose certain things. Then we set the formatting for the logging in the New() function.

Then in our actual application we call our package:

package main

import (
    "github.com/cloudforums/cloudlogs"
)

var owner cloudlogs.AppOwner

func main() {
    owner.Name = "John"
    owner.Number = "555-555-5555"

    var log = cloudlogs.New(owner.Name, owner.Number)

    log.Error("something went wrong")
}

So now our format looks like this

{"file":"/data/data/com.termux/files/usr/tmp/test.MhzSYLadxB/main.go:15","func":"main.main","level":"error","msg":"something went wrong","owner_name":"John","owner_number":"555-555-5555","time":"2019-10-01T23:58:38Z"}

You can see it includes our file, function name, our level, the owner_name, owner_number, and timestamp.
Whereas if we just import logrus and did log.Error("something went wrong") it would look like this:

ERRO[0000] something went wrong

So now any time you want to use this format, you just need to import your package and call the cloudlogs.New() function with the name and number parameters and it sets up your logging format for you.

1 Like

@mroberts this is interesting…