상세 컨텐츠

본문 제목

25/02 The Singleton Pattern –Building the Hydra Custom Logger

Go/Mastering Go

by Gopythor 2022. 2. 26. 00:44

본문

728x90
반응형

What is the Singleton Pattern

An object gets created only once then reused.

  • Let's start by defining the singleton design pattern that is simply and object that get created only once in our code.
  • And then gets used again and again
  • We don't create multiple instances often.

Why use the Singleton Pattern

When the object needs to keep state throughout our program.

  • The singleton pattern is useful when it's important for us to only have one instance of an object throughtout our entire program.
  • For example, a log library object needs to only be created once.
  • Another example is creating a database handler for your application which only needs to be created once as well.

Type Once

type Once struct {
    // contains filtered or unexported fields
}

example

package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    onceBody := func() {
        fmt.Println("Only once")
    }
    done := make(chan bool)
    for i := 0; i < 10; i++ {
        go func() {
            once.Do(onceBody)
            done <- true
        }()
    }
    for i := 0; i < 10; i++ {
        <-done
    }
}

Output

Output:

Only once
  • So how to implement a singleton in Go.
  • We'll start by exploring an important type called Once which exists in the Go sync package.
  • An object of type Once will only execute code once and never again.
  • The sync package page is that https://pkg.go.dev/sync
  • In there, We'll find the once type and there is an example showcasing how to make use of this type.
  • As shown, the method do once.Do to be specific can take a function and executed only once.
  • You will use this functionality to create our singleton.
  • If we hit run here, we'll see here this function even though it got called 10 times, it was only called once.
  • https://go.dev/play/p/rmPXf540Qof
  • This is because it was called inside once.Do

Once.do

func (o *Once) Do(f func())

Do calls the function f if and only if Do is being called for the first time for this instance of Once. In other words, given

  • So Once.do takes a function type that takes no arguments and returns no arguments which was the same signature of the once body function.

hlogger.go

package hlogger

import (
    "log"
    "os"
    "sync"
)

type hydraLogger struct {
    *log.Logger
    filename string
}

var hlogger *hydraLogger
var once sync.Once

//GetInstance creates a singleton instance of the hydra logger
func GetInstance() *hydraLogger {
    once.Do(func() {
        hlogger = createLogger("hydralogger.log")
    })
    return hlogger
}

//Create a logger instance
func createLogger(fname string) *hydraLogger {
    file, _ := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)

    return &hydraLogger{
        filename: fname,
        Logger:   log.New(file, "Hydra ", log.Lshortfile),
    }
}
  • In our example, let's implement a log library for the hydra software.
  • The log library will output messages to a log file.
  • We'll start by creating an hlogger folder which will be underneath the Hydra folder and this will all be in the Go workspace source folder or src.
  • And then inside our hlogger folder, there will be an hlogger.go file where our code will go.
  • For the for the purpose of this program, We'll need three packages log, os, and sync.
  • So we just import them.
  • Of course, we define the package before we do the import.
  • The package name will be hlogger as well.
  • Now let's create a struct that will represent our logger.
  • use embedding to embed *log.Logger struct from the log package.
  • This type allows us to output messages to a writer interface
  • As discussed previously a writer interface in Go is like a write stream.
  • So any type that implements a writer interface can handle IO stream write operation.
  • Next field will be the filename for the log file.
  • We then create unexported function called createLogger and this unexported so that is private to the hlogger package.
  • Now Let's create a variable in our hlogger package called hlogger of type *hydraLogger which is a struct type were created earlier.
  • This variable will host any instance of the hydra logger object.
  • Let's inspect the body for a createLogger function.
  • First we use though os.Openfile method in order to create an open a file then it gets a handle to it.
  • We'll ignore checking errors for now.
  • Just for simplicity's sake, the file value is in fact an IO.writer interface value.
  • We discuss the writer interface previously.
  • We initialize an instance of HydraLogger and return it.
  • And we use struct literal for the initialization.
  • We use the log.New fuction to initialize the embedded logger type inside our hydraLogger struct.
  • log.New belongs to go's log package and what it does is it will create a new logger object.
  • Then it will attach it to a file, if it supplied a file in the first argument.
  • The actual first argument type that plugged in the log.new takes is a writer interface.
  • So since file types support the writer interface, this is a valid argument to pass.
  • So that whenever a new message comes out the other logger, It will be written to the file.
  • The two other arguments are ???? decide what appears on each new log line in our log file
  • We will see the effect shortly.
  • Now let's use the magic of sync.Once to write the GetInstance function which will get us a singleton instance of our hydraLogger object.
  • First we create a variable called Once of type sync.Once
  • And then inside of our GetInstance function, we will use once.Do in order to call the createLogger function.
  • Once we do that by rapping that createLogger function inside a function signature the once.do accept.
  • Let's initialize the filename to be hydralogger.log.
  • If you have already noticed
  • The filename does get passed to the createLogger function and it's an argument in the os.OpenFile call.
  • So that's how we control the filename.
  • We assign the results to its logger from the createLogger function.
  • And this code block will guarantee that this piece of code will only get excuted once and hence the hlogger will only get created one and then all subsequent calls so that get the instance function.
  • We'll just return each logger which is already created.Embed

Embed type

Has-a relationship

type Car struct {
    name  string
    price int
}

func (c *Car) honking() {
    fmt.Println("Beep beep~")
}

type Convertible struct {
    c         Car // Declaring with a field name. Convitible struct has a Car struct. Has-a relationship
    roof      string
    deflector string
}

func main() {
    var v Convertible
    v.c.honking() // Beep beep~
}
  • If you look at the Convitible structure, it contains a field c Car. In this way, it becomes a relationship in which the structure has the corresponding type (Has-a). That is, “the convitible has a car”. Therefore, when calling the honking function, it is called through the c field like v.c.honking().
    https://go.dev/play/p/bP2KedC7pEf

Is-a relationship

type Car struct {
    name  string
    price int
}

func (c *Car) honking() {
    fmt.Println("Beep beep~")
}

type Convertible struct {
    Car       // Declaring only a type without a field name results in an Is-a relationship
    roof      string
    deflector string
}

func main() {
    var v Convertible
    v.honking() // Beep beep~
}

-When defining the Car field in the Convitible structure, only the structure type was specified, not the field name. This results in a relationship in which the struct contains the type (Is-a). In other words, “Convitibles are cars.” Therefore, when calling the honking function, it can be called through the Car structure type like v.Car.honking() or directly like v.honking().
https://go.dev/play/p/zIzFi4K5DMg

Why we use pointer to log.Logger in hydraLogger?

log.New returns a *Logger which is usually an indication that you should pass the object around as a pointer.
https://stackoverflow.com/questions/18361750/correct-approach-to-global-logging

os.OpenFile in this project

The os.OpenFile function takes a file name, flags, and file mode. First, the types of flags are as follows.
os.O_RDWR : Opens the file for both reading and writing.
os.O_CREATE : If the file does not exist, a new one is created. If the file exists, the file opens that file.
os.O_TRUNC : If the file exists, open the file and delete its contents.

log.New

  • The log.New() function is used to create a new logger. log.New() accepts three parameters. The first is a type that supports the io.Writer interface, which supports standard console output (os.Stdout), standard error (os.Stderr), file pointer or io.Writer. Any target can be used. The second parameter is the prefix written at the beginning of log output, and the program name, category, etc. can be written. The third parameter is the log flag. Standard flags (log.LstdFlags), date flags (log.Ldate), time flags (log.Ltime), file location flags (log.Lshortfile, log.Llongfile), etc. can be specified.
  • Short file name/number of lines (Lshortfile) is specified to be output together.

main.go

package main

import (
    "fmt"
    "net/http"

    "github.com/gopythor/udemy/Hydra/hlogger"
)

func main() {
    logger := hlogger.GetInstance()
    logger.Println("Starting Hydra web service")

    http.HandleFunc("/", sroot)
    http.ListenAndServe(":8080", nil)
}

func sroot(w http.ResponseWriter, r *http.Request) {
    logger := hlogger.GetInstance()
    fmt.Fprint(w, "Welcome to the Hydra software system")

    logger.Println("Received an http request on root url")
}
  • Now in our Hydra main function which exists in the main.go file underneath the Hydra folder.
  • This is the code we created when we were learning how to write a web server.
  • We'll test our code by calling hlogger.GetInstance and then get the logger instance
  • Then we'll use it to print a message saying "Starting Hydra web service."
  • The Println method belongs to the logger type which is embedded in the hlogger or Hydra logger object.
  • So if we go inside hlogger Println belongs to this type.
  • The Logger type embedded into our struct.
  • Let's call Get.Instance again.
  • Inside our http handler function in order to get the singleton instance for a logger.
  • And then let's use it again to print another message.
  • In here, We'll saying that we got an http Get request in order for this to be accurate, we'll remove the word.
  • Because this could be any http method which includes get or post or put and a number of others.
  • But the important thing is this will print our message to our file.
  • Now let's run our code by going to the Hydra folder and then using the go run command with main.go
  • So let's visit the Hydra web server so that we can invoke the log messages.
  • We print when a http get the request comes in.

localhost:8080
hydralogger

  • the web server is at localhost:8080.
  • We'll see that the hydralogger.log file was created in our Hydra folder.
  • And then if we have a look inside, we'll see that our code works.
  • So as you can see even though we called GetInstance for the hydralogger in different places in our code, the messages were still printed in the same file.
  • The information prepended at the beginning of each line represents the arguements that we have passed to the log.new function, When you initialize the embedded logger type inside our hydralogger.
  • So this was the effect of those two arguments so File(Hydra) was shown here and log.Lshortfile is a format that is after(main.go:line number)

Summary

  • We explored the singleton design pattern and discovered how Go provide powerful tools for us to implemented

Section 4: Object-Oriented Patterns
21.The Singleton Pattern –Building the Hydra Custom Logger
From Mastering Go Programming

728x90
반응형

'Go > Mastering Go' 카테고리의 다른 글

Section 5.4.25 Channel Generators  (0) 2022.03.11
Section 5.2.24 Timers and Tickers  (5) 2022.03.05
01/03 Syncs and Locks  (0) 2022.03.02
23/02 Factory Design Pattern in Go  (1) 2022.02.24
22/02 Study daily record  (1) 2022.02.23

관련글 더보기

댓글 영역