Testing a Log statement output in Golang - go

Hi I want to check that the output of a Log.error() is valid, I have this following code here
func logError(param bool)
bool {
if (param == true)
log.Error(fmt.Sprintf("I need to somehow tst, that the output here is correct"))
return param;
}
I am not allowed to modify my existing code, I just want to ensure that whatever is printed by the console by my logError function, is what I expect it to be.
Is this possible without modifying my existing code, thank you.

With logrus, you can capture the output of the logger in a test:
oldOut:=log.StandardLogger().Out // Save current log target
buf:=bytes.Buffer{}
log.SetOutput(&buf)
logError(true)
// Here, buf.String() should give you the log msg
log.SetOutput(oldOut) // Restore log target

Related

Issue with ANSI cursor movement in goroutine

Background
I'm trying to write a Go library for creating terminal task-lists, inspired by the Node library listr.
My library, golist, prints the task list out in a background goroutine and updates the text and status characters using ANSI escape sequences.
The Problem
There's an issue where the final print of the list will occasionally have extra spaces included, leading to some spaces or repeated lines. Here are two examples – one correct, one not – both from runs of the same exact code (here's a link to the code).
Example
Here's an example of what it should look like:
(Here's a gist of the raw text output for the correct output)
And here's an example of what it sometimes looks like:
(Here's a gist of the raw text output for the incorrect output)
If you look at lines 184 and 185 in the gist of the incorrect version, there are two blank lines that aren't in the correct version.
Why is this happening and why is it only happening sometimes?
Code
I'm printing the list to the terminal in the following loop:
go func() {
defer donePrinting() // Tell the Stop function that we're done printing
ts := l.getTaskStates()
l.print(ts)
for {
select {
case <-ctx.Done(): // Check if the print loop should stop
// Perform a final clear and an optional print depending on `ClearOnComplete`
ts := l.getTaskStates()
if l.ClearOnComplete {
l.clear(ts)
return
}
l.clearThenPrint(ts)
return
case s := <-l.printQ: // Check if there's a message to print
fmt.Fprintln(l.Writer, s)
default: // Otherwise, print the list
ts := l.getTaskStates()
l.clearThenPrint(ts)
l.StatusIndicator.Next()
time.Sleep(l.Delay)
}
}
}()
The list is formatted as a string and then printed. The following function formats the string:
// fmtPrint returns the formatted list of messages
// and statuses, using the supplied TaskStates
func (l *List) fmtPrint(ts []*TaskState) string {
s := make([]string, 0)
for _, t := range ts {
s = append(s, l.formatMessage(t))
}
return strings.Join(s, "\n")
}
and the following function builds the ANSI escape string to clear the lines:
// fmtClear returns a string of ANSI escape characters
// to clear the `n` lines previously printed.
func (l *List) fmtClear(n int) string {
s := "\033[1A" // Move up a line
s += "\033[K" // Clear the line
s += "\r" // Move back to the beginning of the line
return strings.Repeat(s, n)
}
I'm using this site as a reference for the ANSI codes.
Thanks in advance for any suggestions you might have about why this is happening!
Let me know if there's any other information I can add that can help.
I think the ANSI codes are just a red herring. I pulled down the library and tried running it locally, and found that the following section is what is creating this issue:
case s := <-l.printQ: // Check if there's a message to print
fmt.Fprintln(l.Writer, s)
When the printQ channel is getting closed, this case is sometimes running, which seems to be moving the cursor down even though nothing is getting printed. This behaviour went away when I moved the call to close the channel after l.printDone is called.
...
// Wait for the print loop to finish
<-l.printDone
if l.printQ != nil {
close(l.printQ)
}
...
This ensures that the loop is no longer running when the channel is closed, and thus the s := <-l.printQ case cannot run.

How to discard printouts when running tests

Using Go 1.11 I have a function that prints a label above a user input. This is fine and works but when I have started to write the test for this function it prints out this label when running the tests.
I have also use log.Print but then in the test file adding the following
func init() {
log.SetOutput(ioutil.Discard)
}
This stops the log.Print from being displayed when running the tests. So how do I get it to do the same for any fmt.Println?
UPDATE
I thought I would post the func that I am testing and how I set up,
func checkLoop(loopCount int) int {
var input int
var ok bool
for !ok {
fmt.Printf("Input %d :: ", loopCount)
ok, input = takeInput()
}
return input
}
So takeInput() only takes the users input, using fmt.Scan and checks it to see if its within a set range I want. So it returns a bool and if its false it will re-use the label to check what input number it is.
Either you're testing on a wrong level or you're testing a wrong thing, let me explain.
If it's for some reason valuable for you to test that
your function prints something and also accepts user input,
then go ahead and do end-to-end testing for it—that is, also test
that it prints what it's expected to print, and once it does that, submit it some canned input and then verify it processes it the way you expect it to.
I don't know your code but supposedly you should stop
using fmt.P* family of functions—which imply using os.Std{in|out|err} and write the functions forming the core of your code accepting io.Reader and io.Writer interfaces and
call them from the top-level code passing them os.Stdin and os.Stderr etc.
Then, when testing, it will be easy to pass these functions
stub (or mock) values which also satisfy the said interfaces.
(The fmt package supports formatted printing using the fmt.FP* family of functions, where that F is a historical artefact meaning "file" and allows you to pass it any value implementing io.Writer.)
Another possibility is that what you explained looks like
a code smell: a function concerned with processing some input
data has no business printing any labels anywhere.
So it well may be that you instead should split your function
at least in two: the "outer" one prompts the user and reads the data, and the second accepts the data and processes it.
You then test the second one.
For example,
discard_test.go:
package main
import (
"fmt"
"os"
"syscall"
"testing"
)
func printLabel(label string) {
fmt.Println(label)
}
func TestDiscardLabel(t *testing.T) {
defer func(stdout *os.File) {
os.Stdout = stdout
}(os.Stdout)
os.Stdout = os.NewFile(uintptr(syscall.Stdin), os.DevNull)
printLabel("Discard:")
}
func TestPrintLabel(t *testing.T) {
printLabel("Print:")
}
Output:
$ go test discard_test.go -v
--- PASS: TestDiscardLabel (0.00s)
=== RUN TestPrintLabel
Print:
--- PASS: TestPrintLabel (0.00s)
PASS
$
fmt.Printf use the os.Stdout so you can simply:
os.Stdout = nil
fmt.Println("Hello Gopher!")
or more elegant:
os.Stdout,_ = os.Open(os.DevNull)
fmt.Println("Hello Gopher!")
Hope this help

Does closing io.PipeWriter close the underlying file?

I am using logrus for logging and have a few custom format loggers. Each is initialized to write to a different file like:
fp, _ := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0755)
// error handling left out for brevity
log.Out = fp
Later in the application, I need to change the file the logger is writing to (for a log rotation logic). What I want to achieve is to properly close the current file before changing the logger's output file. But the closest thing to the file handle logrus provides me is a Writer() method that returns a io.PipeWriter pointer. So would calling Close() on the PipeWriter also close the underlying file?
If not, what are my options to do this, other than keeping the file pointer stored somewhere.
For the record, twelve-factor tells us that applications should not concern themselves with log rotation. If and how logs are handled best depends on how the application is deployed. Systemd has its own logging system, for instance. Writing to files when deployed in (Docker) containers is annoying. Rotating files are annoying during development.
Now, pipes don't have an "underlying file". There's a Reader end and a Writer end, and that's it. From the docs for PipeWriter:
Close closes the writer; subsequent reads from the read half of the pipe will return no bytes and EOF.
So what happens when you close the writer depends on how Logrus handles EOF on the Reader end. Since Logger.Out is an io.Writer, Logrus cannot possibly call Close on your file.
Your best bet would be to wrap *os.File, perhaps like so:
package main
import "os"
type RotatingFile struct {
*os.File
rotate chan struct{}
}
func NewRotatingFile(f *os.File) RotatingFile {
return RotatingFile{
File: f,
rotate: make(chan struct{}, 1),
}
}
func (r RotatingFile) Rotate() {
r.rotate <- struct{}{}
}
func (r RotatingFile) doRotate() error {
// file rotation logic here
return nil
}
func (r RotatingFile) Write(b []byte) (int, error) {
select {
case <-r.rotate:
if err := r.doRotate(); err != nil {
return 0, err
}
default:
}
return r.File.Write(b)
}
Implementing log file rotation in a robust way is surprisingly tricky. For instance, closing the old file before creating the new one is not a good idea. What if the log directory permissions changed? What if you run out of inodes? If you can't create a new log file you may want to keep writing to the current file. Are you okay with ripping lines apart, or do you only want to rotate after a newline? Do you want to rotate empty files? How do you reliably remove old logs if someone deletes the N-1th file? Will you notice the Nth file or stop looking at the N-2nd?
The best advice I can give you is to leave log rotation to the pros. I like svlogd (part of runit) as a standalone log rotation tool.
The closing of io.PipeWriter will not affect actual Writer behind it. The chain of close execution:
PipeWriter.Close() -> PipeWriter.CloseWithError(err error) ->
pipe.CloseWrite(err error)
and it doesn't influence underlying io.Writer.
To close actual writer you need just to close Logger.Out that is an exported field.

Gin outputs to a file

I'm using Gin to make REST API server and Gin shows its output on a console like this: Gin console example
I'd like to make gin's output to a file instead of console.
What I've found is below code from Gin/mode.go
// DefaultWriter is the default io.Writer used the Gin for debug output and
// middleware output like Logger() or Recovery().
// Note that both Logger and Recovery provides custom ways to configure their
// output io.Writer.
// To support coloring in Windows use:
// import "github.com/mattn/go-colorable"
// gin.DefaultWriter = colorable.NewColorableStdout()
var DefaultWriter io.Writer = os.Stdout
var DefaultErrorWriter io.Writer = os.Stderr
Looks like I can change DefaultWriter and DefaultErrorWriter's behavior by setting like
gin.DefaultWriter = something
on my code.
But I have no idea how to write that 'something' code; it must be a function which writes to a file but have no idea how/where to start.
So, my questions are:
Am I heading to the right direction?
How to write that 'something' function? It would be very helpful if you can provide an example.
Thank you.
You can use os package to create a file.
file, fileErr := os.Create("file")
if fileErr != nil {
fmt.Println(fileErr)
return
}
gin.DefaultWriter = file
This should create a file and start writing to it.

Is there a built-in go logger that can roll

Is there a built-in Go logger that can roll a log file when it reaches a file size limit?
Thanks.
No, there is no built-in in logger that currently has this feature.
log4go, which you will find recommended when searching, is currently broken. has some issues, that lead to messages getting lost (in case the main program exits and some messages are still in the channelbuffer before being written).
this is present in most if not all the examples.
see also this question
Above the syslog package, which probably is not what you really want, no such thing is in the standard library.
From the 3rd party packages, for example log4go claims to have this feature.
import (
"os"
"github.com/nikandfor/tlog"
"github.com/nikandfor/tlog/rotated"
)
func main() {
f, err := rotated.Create("logfile_template_#.log") // # will be substituted by time of file creation
if err != nil {
panic(err)
}
defer f.Close()
f.MaxSize = 1 << 30 // 1GiB
f.Fallback = os.Stderr // in case of failure to write to file, last chance to save log message
tlog.DefaultLogger = tlog.New(tlog.NewConsoleWriter(f, tlog.LstdFlags))
tlog.Printf("now use it much like %v", "log.Logger")
log.SetOutput(f) // also works for any logger or what ever needs io.Writer
log.Printf("also appears in the log")
}
logger https://github.com/nikandfor/tlog
rotated file https://godoc.org/github.com/nikandfor/tlog/rotated

Resources