Trying to handle all error in a single defer function.
But err can't be assigned as a pointer to error? As it gives me Invalid memory address error
package main
import (
"fmt"
"errors"
)
func main() {
var err *error
defer func(err *error) {
if *err != nil {
fmt.Println("hi")
} else {
fmt.Println("oh")
}
}(err)
*err = errors.New("EMPTY_BODY")
}
playground
you need to assign the memory to the err where you are just defining now.
Replace var err *error with var err = new(error) or err := new(error) to instantiate and make this code work.
I never needed to pass pointer
package main
import (
"fmt"
"errors"
)
func main() {
var err error
defer func() {
if err != nil {
fmt.Println("hi")
} else {
fmt.Println("oh")
}
}()
err = errors.New("EMPTY_BODY")
}
I thought i needed as defer was taking the value of err at that point not at the end of function.
Related
It may be a stupid question because I just learned Golang. I hope you understand.
I am making a program to extract data from the homepage using the goquery package:
package main
import (
"fmt"
"log"
"net/http"
"github.com/PuerkitoBio/goquery"
)
var url string = "https://www.jobkorea.co.kr/Search/?stext=golang&tabType=recruit&Page_No=3"
func main() {
getPages()
}
func getPages() int {
res, err := http.Get(url)
checkErr(err)
checkCode(res)
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
checkErr(err)
doc.Find(".tplPagination").Each(func(i int, s *goquery.Selection) {
fmt.Println(s.Find("a"))
})
return 0
}
func checkErr(err error) {
if err != nil {
log.Fatalln(err)
fmt.Println(err)
}
}
func checkCode(res *http.Response) {
if res.StatusCode != 200 {
log.Fatalln("Request failed with statusCode:", res.StatusCode)
}
}
It prints below:
&{[0x140002db0a0 0x140002db570 0x140002db810 0x140002dbd50 0x140002dc000 0x140002dc2a0 0x140002dc540 0x140002dc850] 0x140000b2438 0x14000305680}
&{[0x140002dcd90 0x140002dd810] 0x140000b2438 0x14000305710}
But I just want to print only the first array out. Like this:
[0x140002dcd90 0x140002dd810]
How can I destruct them?
The problem is that you are printing as result is matched.
You can save the *goquery.Selection in a new slice and print only the last element. This example is working because you want the last occurrence, but in real life you must parse the query result for something in specific to not depend about result order.
// type Selection struct {
// Nodes []*html.Node
// document *Document
// prevSel *Selection
// }
var temp []*goquery.Selection
temp = append(temp, doc.Find(".tplPagination").Each(func(i int, s *goquery.Selection) {
s.Find("a")
}))
fmt.Printf("last: %v\n", temp[len(temp)-1])
temp[len(temp)-1]: &{[0xc0002dcd90 0xc0002e0a80] 0xc00000e3f0 0xc000309770}
The Nodes []*html.Node can be accessed with same example:
fmt.Printf("last: %v\n", temp[len(temp)-1].Nodes)
As per your comment you were looking to parse the page and get the number of pages and number of posts. Here is my attempt:
package main
import (
"fmt"
"github.com/PuerkitoBio/goquery"
"log"
"math"
"net/http"
"strconv"
"strings"
)
func errCheck(err error) {
if err != nil {
log.Fatal(err)
}
}
func ExampleScrape() {
url := "https://www.jobkorea.co.kr/Search/?stext=golang&tabType=recruit&Page_No=%s"
page := 3
fmt.Println("Current page:", page)
res, err := http.Get(fmt.Sprintf(url, page))
errCheck(err)
defer res.Body.Close()
if res.StatusCode != 200 {
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
}
doc, err := goquery.NewDocumentFromReader(res.Body)
errCheck(err)
posts_div := doc.Find(".recruit-info div.dev_list.lists-cnt")
total_count_div := posts_div.Nodes[0]
var total_count int
for _, a := range total_count_div.Attr {
if a.Key == "total-count" {
total_count, err = strconv.Atoi(a.Val)
errCheck(err)
break
}
}
fmt.Println("Total count:", total_count)
titles := posts_div.Find(".list-post .title")
fmt.Println("On this page:", len(titles.Nodes))
fmt.Println("Pages:", math.Ceil(float64(total_count)/float64(len(titles.Nodes))))
fmt.Println("\nTitles on this page:")
titles.Each(func(i int, s *goquery.Selection) {
fmt.Println("\t-", strings.TrimSpace(s.Text()))
})
}
func main() {
ExampleScrape()
}
Running the below code, the stack trace is outputted with the line number of fmt.Print(...). But I want to output the line of logError(err). I think I need to call xerrors.Caller(1) to do that but I don't know how. Help me.
import (
"fmt"
"io/ioutil"
"golang.org/x/xerrors"
)
func main() {
_, err := ioutil.ReadFile("")
if err != nil {
logError(err)
return
}
}
func logError(err error) {
fmt.Printf("%+v", xerrors.Errorf(": %w", err))
}
I think what you want is:
import (
"fmt"
"io/ioutil"
"golang.org/x/xerrors"
)
func doWhatever() error {
_, err := ioutil.ReadFile("")
if err != nil {
return xerrors.Errorf("failed doing whatever: %w", err)
}
return nil
}
func main() {
err := doWhatever()
if err != nil {
logError(err)
return
}
}
func logError(err error) {
fmt.Printf("%+v", err)
}
I am trying to get the content of a publicly available file using ioutil.ReadFile() but it doesn't find the file: panic: open http://www.pdf995.com/samples/pdf.pdf: No such file or directory
Here's my code:
// Reading and writing files are basic tasks needed for
// many Go programs. First we'll look at some examples of
// reading files.
package main
import (
"fmt"
"io/ioutil"
)
// Reading files requires checking most calls for errors.
// This helper will streamline our error checks below.
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
fileInUrl, err := ioutil.ReadFile("http://www.pdf995.com/samples/pdf.pdf")
if err != nil {
panic(err)
}
fmt.Printf("HERE --- fileInUrl: %+v", fileInUrl)
}
Here's a go playground example
ioutil.ReadFile() does not support http.
If you look at the source code(https://golang.org/src/io/ioutil/ioutil.go?s=1503:1549#L42), open the file using os.Open.
I think I can do this coding.
package main
import (
"io"
"net/http"
"os"
)
func main() {
fileUrl := "http://www.pdf995.com/samples/pdf.pdf"
if err := DownloadFile("example.pdf", fileUrl); err != nil {
panic(err)
}
}
func DownloadFile(filepath string, url string) error {
// Get the data
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// Create the file
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
// Write the body to file
_, err = io.Copy(out, resp.Body)
return err
}
but, go playgound not protocol(go error dial tcp: Protocol not available).
so, You have to do it PC.
I am trying to stub os.Stat and ioutil.ReadFile(path) as used the code below or if you like here on go playground [1]
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
)
func AssignFileValueFrom(path string, val *string) {
var (
tempValue []byte
err error
)
if _, err = os.Stat(path); err == nil {
if err != nil {
fmt.Println("There was a os stat error:", err)
}
tempValue, err = ioutil.ReadFile(path)
if err != nil {
fmt.Println("There was an io read error:", err)
}
*val = strings.TrimSpace(string(tempValue))
}
}
I have used testify and tried following the example here [2]
package main
import (
"testing"
"github.com/stretchr/testify/mock"
)
type osMock struct {
mock.Mock
}
func (o osMock) Stat(path string) (interface{}, error) {
return nil, nil
}
func TestAssignFileValueFrom(t *testing.T) {
var test string
osm := new(osMock)
osm.On(`Stat`, `./.test`).Return([]byte(`1`), nil)
AssignFileValueFrom(`./.test`, &test)
// assert.Equal(t, `1`, test)
osm.AssertExpectations(t)
}
What am I not doing correctly??
[1] https://play.golang.org/p/xcbdMkMwoBN
[2] https://github.com/stretchr/testify#mock-package
Your code with osMock doesn't any how influence execution of AssignFileValueFrom function. There is a direct call of os.Stat and it won't be substituted just because you have declared osMock somewhere.
To do actual testing you should use interfaces and dependency injection to be able to test your code.
First of all os.Stat call must be substituted with call to your struct that implements an interface with same method defined. And you need to create at least 2 implementations of this interface: 1 - is actual working code to use, 2 - mock as your osMock struct to use in test. And you need to inject it or pass it to AssignFileValueFrom and then use to call Stat method on it.
Thanks, guys for your inputs I have rewritten my tests as follows..
package main
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
var (
err error
testFile *os.File
test string
)
const (
TestPrefix = `test_file_prefix`
FileContent = `1234`
)
func init() {
testFile, err = ioutil.TempFile(os.TempDir(), TestPrefix)
if err != nil {
panic(err)
}
err = ioutil.WriteFile(testFile.Name(), []byte(FileContent), 0644)
if err != nil {
panic(err)
}
}
func TestAssignFileValueFrom(t *testing.T) {
AssignFileValueFrom(testFile.Name(), &test)
assert.Equal(t, test, FileContent)
}
When trying to move log setup code into a separate function I ran into inability to hide the destination file object from the main function. In the following INCORRECT simplified example the attempt is made to setup log writing to both Stderr and a file via a single function call:
package main
import (
"io"
"log"
"os"
)
func SetupLogging() {
logFile, err := os.OpenFile("test.log", os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
log.Panicln(err)
}
defer logFile.Close()
log.SetOutput(io.MultiWriter(os.Stderr, logFile))
}
func main() {
SetupLogging()
log.Println("Test message")
}
Clearly is does not work because defer closes the log file at the end of the SetupLogging function.
A working example below adds extra code and IMHO looses some clarity if repeated in a larger application as a pattern:
package main
import (
"io"
"log"
"os"
)
func SetupLogging() *os.File {
logFile, err := os.OpenFile("test.log", os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
log.Panicln(err)
}
log.SetOutput(io.MultiWriter(os.Stderr, logFile))
return logFile
}
func main() {
logf := SetupLogging()
defer logf.Close()
log.Println("Test message")
}
Is there a different way to fully encapsulate open file management into a function, yet still nicely release the handle?
I have now successfully used the below approach for about a year in multiple projects. The idea is to return a function from the setup call. That resulting function contains the destruction logic. Here is an example:
package main
import (
"fmt"
"io"
"log"
"os"
)
func LogSetupAndDestruct() func() {
logFile, err := os.OpenFile("test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
if err != nil {
log.Panicln(err)
}
log.SetOutput(io.MultiWriter(os.Stderr, logFile))
return func() {
e := logFile.Close()
if e != nil {
fmt.Fprintf(os.Stderr, "Problem closing the log file: %s\n", e)
}
}
}
func main() {
defer LogSetupAndDestruct()()
log.Println("Test message")
}
It is using a closure around the cleanup logic being deferred.
A somewhat more elaborate public example of using this approach is in the Viper code: here is the return from a test initializer, and here it is used to encapsulate the cleanup logic and objects
The proper way of doing this is passing the handle in main to SetupLogging:
func SetupLogging(lf *os.File) {
log.SetOutput(io.MultiWriter(os.Stderr, logFile))
log.Println("Started")
}
func main() {
logFile, err := os.OpenFile("test.log", os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
log.Panicln(err)
}
defer logFile.Close()
SetupLogging(logFile)
log.Println("Test message")
}
Another option is to use runtime.SetFinalizer, but it's not always guaranteed to run before main exits.
func SetupLogging() {
logFile, err := os.OpenFile("test.log", os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
log.Panicln(err)
}
runtime.SetFinalizer(logFile, func(h *os.File) {
h.Close()
})
log.SetOutput(io.MultiWriter(os.Stderr, logFile))
}
You can do this using channels, here is my approach
type InfoLog struct {
InfoChan chan string
CloseChan chan struct{} //empty signal
log *log.Logger
file *os.File
}
func NewInfoLog(file *os.File) *InfoLog {
return &InfoLog{
InfoChan: make(chan string),
CloseChan: make(chan struct{}),
log: log.New(file, "TAG", log.Ldate|log.Ltime),
file: file,
}
}
func (i *InfoLog) listen() {
for {
select {
case infoMsg := <-i.InfoChan:
i.log.Println(infoMsg)
case <-i.CloseChan:
i.file.Close()
close(i.InfoChan)
}
}
}
then in main
func main() {
infoLog := NewInfoLog(ANY_OPEN_FILE_HERE)
go infoLog.listen()
infoLog.InfoChan <- "msg"
infoLog.InfoChan <- "msg"
infoLog.InfoChan <- "msg"
infoLog.CloseChan <- struct{}{}
// exits normaly
}
you can see an asynchronous log system i have made for a complete example: https://github.com/sescobb27/ciudad-gourmet/blob/master/services/log_service.go
in case where multiple "teardown" processes are needed, great solution to this is using google context package (https://blog.golang.org/context). advantage is that you can teardown all currently executing procedures with single context. smth like this:
package main
import (
"fmt"
"io"
"log"
"os"
"golang.org/x/net/context"
)
func LogSetup(ctx context.Context) error {
logFile, err := os.OpenFile("test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
if err != nil {
return err
}
log.SetOutput(io.MultiWriter(os.Stderr, logFile))
// here we could f.ex. execute:
// sendLogOutputToExternalService(ctx)
// and it could have it's own teardown procedure
// which would be called on main context's expiration
go func() {
for _ = range ctx.Done() {
err := logFile.Close()
if err = nil {
fmt.Fprintf(os.Stderr, "Problem closing the log file: %s\n", e)
}
}()
return nil
}
func main() {
var stopAll func()
mainContext, stopAll = context.WithCancel(context.Background())
defer stopAll()
err := LogSetup(mainContext)
if err!=nil {
log.Fatal("error while initializing logging")
}
log.Println("Test message")
}