Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 [Feature]: Log show lowerst time-unit that makes sense [#2258] #2561

Closed
3 tasks done
the-hotmann opened this issue Aug 7, 2023 · 11 comments · Fixed by #2569 or #2580
Closed
3 tasks done

🚀 [Feature]: Log show lowerst time-unit that makes sense [#2258] #2561

the-hotmann opened this issue Aug 7, 2023 · 11 comments · Fixed by #2569 or #2580
Assignees

Comments

@the-hotmann
Copy link
Contributor

the-hotmann commented Aug 7, 2023

Feature Description

Refering to #2258

When I check my Fiber logs, it instead of 0s now always shows: xms.
This is better, as it is more precise, but I really would recommend it, if Fiber (just like Gin) would show he smallest unit that makes sense:

  • 1.423s
  • 456.ms
  • 18.374µs
  • 432ns

Fiber, will (in my expirience) never show µs & ns, even though I am relly sure, it does not always take exactly 1ms ;)

Additional Context (optional)

The log looks like this for me (IP replaced with 123.123.123.123):

12:29:03 | 200 |     5ms |     123.123.123.123 | PUT     | /api/product/1
12:29:03 | 200 |     2ms |     123.123.123.123 | GET     | /api/products
12:29:13 | 200 |     3ms |     123.123.123.123 | GET     | /admin
12:29:13 | 304 |      0s |     123.123.123.123 | GET     | /app.min.css
12:29:13 | 200 |     1ms |     123.123.123.123 | GET     | /api/categories
12:29:13 | 200 |     2ms |     123.123.123.123 | GET     | /api/products
12:29:23 | 200 |      0s |     123.123.123.123 | GET     | /api/product/1

Also the responsetime of the very same request fluctuates between 0s and 5ms - which in my case is very high and other WebFrameworks do not do.
Since I migrate to Fiber I hope that these little things will be fixed somewhen, it is not urgent, but also the Benchmarks (where Fiber always outperforms others) are ATM not matching my real-life expirience, of beeing any faster than Gin for example.
Maybe they just apply, when a big number of requests are triggered.

Code Snippet (optional)

No response

Checklist:

  • I agree to follow Fiber's Code of Conduct.
  • I have checked for existing issues that describe my suggestion prior to opening this one.
  • I understand that improperly formatted feature requests may be closed without explanation.
@sixcolors
Copy link
Member

gin-gonic/gin defaultLogFormatter https://github.com/gin-gonic/gin/blob/62b50cfbc0de877207ff74c160a23dff6394f563/logger.go#L143 used %13v on params.Latency.

which looks like:

|       4.333µs |
|    1.133916ms |
|  1.000057458s |

gofiber/fiber/middleware/logger uses %7v on TagLatency.latency

| 4.834µs |
| 1.136834ms |
| 1.000066333s |

13 chars seems to be better.

Otherwise gofiber/fiber/middleware/logger behaves the same as the default Gin logger.

I will add a test and change the formating of the string to %13v in a PR.

@sixcolors
Copy link
Member

sixcolors commented Aug 10, 2023

@MartinHotmann #2569 should resolve padding, but current code already formats like gin defaultLogFormatter.

@the-hotmann
Copy link
Contributor Author

@sixcolors thanks for your response!

For me it was never about the padding, but thanks for that - I appreciate it. For me it was about:

Fiber never shows any "request process times" which are lower than 1ms or 0s. The lowest it goes for me at sending back a static string is 1ms or sometimes it shows 0s.

(you can see this in the example I provided above)

But here again. I will provide a log from fiber when calling a static site:

23:04:53 | 200 |     7ms |     172.18.0.30 | GET     | /
23:05:05 | 200 |     2ms |     172.18.0.30 | GET     | /warum
23:05:06 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:07 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:07 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:07 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:07 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:07 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:07 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:08 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:08 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:08 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:08 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:08 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:08 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:08 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:09 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:09 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:09 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:09 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:10 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:10 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:10 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:10 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:10 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:10 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:11 | 200 |      0s |     172.18.0.30 | GET     | /warum
23:05:11 | 200 |      0s |     172.18.0.30 | GET     | /warum

Please note, that the page /warum is completely static.

The first time I call it, it takes a little longer, but the other times are much faster, but still it shows 0s.
Yes, Gin formats the time the same, but they actually show me how many µs or even ns it took to process that request in total.

Since we are already talking:

is it possible to pass that string/variable to the template?
I would like to render that exact time on the website, which then shows how long/short it took.

Thanks in advance :)

@sixcolors
Copy link
Member

It should be doing what you want. Let me invesitgate further.

@sixcolors sixcolors reopened this Aug 11, 2023
@sixcolors
Copy link
Member

sixcolors commented Aug 11, 2023

@MartinHotmann I can't seem to replicate your issue with logging the latency.

 ┌───────────────────────────────────────────────────┐ 
 │                   Fiber v2.48.0                   │ 
 │               http://127.0.0.1:8003               │ 
 │       (bound on host 0.0.0.0 and port 8003)       │ 
 │                                                   │ 
 │ Handlers ............. 4  Processes ........... 1 │ 
 │ Prefork ....... Disabled  PID ............. 64375 │ 
 └───────────────────────────────────────────────────┘ 

22:13:06 [127.0.0.1] 200 -    1.218709ms GET /
22:13:08 [127.0.0.1] 200 -    1.282416ms GET /
22:13:08 [127.0.0.1] 200 -    1.254958ms GET /
22:13:09 [127.0.0.1] 200 -    1.389375ms GET /
22:13:09 [127.0.0.1] 200 -     105.792µs GET /
22:13:10 [127.0.0.1] 200 -     144.666µs GET /
22:13:10 [127.0.0.1] 200 -     140.709µs GET /
22:13:11 [127.0.0.1] 200 -     146.792µs GET /
22:13:12 [127.0.0.1] 200 -    1.288667ms GET /
22:13:11 [127.0.0.1] 200 -  1.001153959s GET /
22:13:12 [127.0.0.1] 200 -  1.000757084s GET /
22:13:13 [127.0.0.1] 200 -   1.00119775s GET /

here is the sample app I used:

package main

import (
	"math/rand"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/logger"
)

func main() {
	server := fiber.New()
	server.Use(logger.New(logger.Config{
		Format: "${time} [${ip}] ${status} - ${latency} ${method} ${path}\n",
	}))

	server.Static("/", "../static", fiber.Static{
		Compress: true,
	})

	durations := []time.Duration{time.Nanosecond, time.Microsecond, time.Millisecond, time.Second}
	rand.Seed(time.Now().UnixNano())

	server.Get("/", func(c *fiber.Ctx) error {
		// randomly sleep for either 1ns, 1μs, 1ms, or 1s
		duration := durations[rand.Intn(len(durations))]
		time.Sleep(duration)
		return c.SendString("Hello, World!")
	})
	server.Listen(":8003")
}

What version of fiber are you using and on what platform?

What is your logger config?


To display the latency measured by the logger middleware on your website, you would need to implement an approach that involves tracking the latency externally. Due to the nature of the HTTP request-response cycle, directly measuring and displaying the exact latency within the same request and response is paradoxical. One approach to achieving this is by keeping track of the request and its latency on the server side and then making a subsequent request, perhaps using JavaScript's Fetch API, to retrieve and display the tracked latency on your website. Alternatively, integrating with external monitoring or analytics systems, such as Prometheus or StatsD, can provide a more comprehensive and accurate solution for measuring and visualizing latency and other performance metrics over time. These systems allow you to gather and analyze data without impacting the main application's performance, ensuring a scalable approach to monitoring your application's performance metrics.

As a simplified and hacky demonstration, you could implement an average response time tracking mechanism similar to the following. However, it's important to note that this approach serves more as an illustrative starting point rather than a robust solution. One of its significant limitations is that it calculates a running average since the start of the server without a defined window, which can result in skewed results and may not provide meaningful insights into the actual request latencies:

[...]

type customWriter struct {
	stdout io.Writer
	avgLat time.Duration
	count  int
	mux    sync.Mutex
}

func (d *customWriter) Write(p []byte) (n int, err error) {
	// write to stdout
	n, err = d.stdout.Write(p)

	if err != nil {
		return n, err
	}

	// figure out the average latency and track that in a global variable
	// this is a hacky way to do it, but it works
	// Get latency from Format: "${time} [${ip}] ${status} - ${latency} ${method} ${path}\n",
	regexp := regexp.MustCompile(`([0-9]+(\.[0-9]+)?(ns|µs|ms|s))`)
	matches := regexp.FindStringSubmatch(string(p))
	if len(matches) > 0 {
		latency, err := time.ParseDuration(matches[1])
		if err != nil {
			return n, err
		}
		d.mux.Lock()
		d.avgLat = ((d.avgLat * time.Duration(d.count)) + latency) / time.Duration(d.count+1)
		d.count++
		d.mux.Unlock()
	}

	return n, nil
}

func main() {
	server := fiber.New()
	customWriter := &customWriter{
		stdout: os.Stdout,
	}

	server.Use(logger.New(logger.Config{
		Format: "${time} [${ip}] ${status} - ${latency} ${method} ${path}\n",
		Output: customWriter,
	}))

	[...]

	server.Get("/", func(c *fiber.Ctx) error {
		return c.SendString("Hello, World! The Average Latency is: " + customWriter.avgLat.String())
	})
	server.Listen(":8003")
}

Hope that helps. Considering the complexities and limitations of measuring and displaying request latencies within the same response, you might find it more effective to explore alternative approaches. Perhaps even creating a dedicated middleware designed specifically for tracking and presenting latency data could provide a more accurate and maintainable solution for showcasing request latencies on your website.

@sixcolors
Copy link
Member

@MartinHotmann. If you aren't aware, Fiber offers a built-in middleware called monitor that might partially serve your purpose. It's worth exploring this middleware as it provides monitoring functionalities that could potentially enhance your ability to track and display latency and other metrics on your website.

@gaby
Copy link
Member

gaby commented Aug 11, 2023

There's also the fiberprometheus middleware, that's used by a lot of projects. It records latencies and includes a grafana dashboard.

https://github.com/ansrivas/fiberprometheus

@the-hotmann
Copy link
Contributor Author

the-hotmann commented Aug 13, 2023

@sixcolors

server.Use(logger.New(logger.Config{
	Format: "${time} [${ip}] ${status} - ${latency} ${method} ${path}\n",
}))

You are right - this did the trick!

I was using:

server.Use(logger.New())

So it seems like that the default config is not providing the wanted output.
Thanks for pointing that out - I checked the default config and I am fine with it, as it also includes the latency variable - but it renders it differently.

Source: https://docs.gofiber.io/api/middleware/logger/#default-config

var ConfigDefault = Config{
    Next:          nil,
    Done:          nil,
    Format:        "[${time}] ${status} - ${latency} ${method} ${path}\n",
    TimeFormat:    "15:04:05",
    TimeZone:      "Local",
    TimeInterval:  500 * time.Millisecond,
    Output:        os.Stdout,
    DisableColors: false,
}

(but it seems this is not the default config?)

And also thanks for #2569 this should get rid, of the not properly formatted logs.

@gaby thanks for the recommendation, but I guess this is a little overkill for my small project. Will look into anyway!

@sixcolors
Copy link
Member

@the-hotmann It seems I've figured out the issue. When you utilize the default formatter, the code within the tags.go file are not utilized. Instead, the output functions located in logger.go are used. It seems this was done for the purpose of adding colors; the duplication seems somewhat unnecessary and I may refactor the code at a later time. For now I have submitted a pull request to address the issue in PR #2580.

@the-hotmann
Copy link
Contributor Author

Thanks! I appreciate the fix :)

Do we get 2.48.1 as a quickfix? 👀

@ReneWerner87
Copy link
Member

will release end of this week or next week, depends on the open pull requests and the progress there

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment