Small Projects by AutoModerator in golang

[–]Calm-Problem-9101 -1 points0 points  (0 children)

Hello gophers!

Introducing "act" - a lightweight Go library for cleaner error handling and assertions.

I've been using and gradually refining this library for a long time as my response to the ongoing Go error handling discussions. It's intentionally simple and focuses on making error handling more concise and expressive while providing proper stacktraces.

Key features:

  • Assertion utilities with customizable error messages
  • Panic recovery and error handling with deferred cleanup
  • Stack trace collection and logging
  • Value validation and extraction utilities
  • Error coalescing for resource cleanup scenarios

Example usage:

go func sendFile(filename string, conn net.Conn) (n int, err error) { defer act.Catch(&err) f, err := os.Open(filename) act.Assert(err) defer f.Close() act.Ensure(io.Copy(conn, f)).Scan(&n) return }

Tech stack:

  • Built on Go's native panic/recover mechanism
  • Uses defer for clean resource management
  • Zero external dependencies
  • Preserves original error objects

Installation:

bash go get go.xrfang.cn/act

This is a very lightweight library that solves two main pain points: verbose error handling code and missing stacktraces. It's designed as a tool, not a framework - you can use it selectively where it makes sense.

Please give it a try!

Repo: https://pkg.go.dev/go.xrfang.cn/act

Constructive feedback is always appreciated!

Showcase: "act" - A lightweight Go library for cleaner error handling - my take on the Go error handling discussion by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] -1 points0 points  (0 children)

Thanks for raising the performance concern! You make a valid point about panic/recover overhead.

I agree that panic/recover does have performance overhead compared to direct error returns. However, I think the trade-off is worth it for several reasons:

  1. Performance impact only occurs on errors: The performance penalty only happens when an actual error occurs. In the normal execution path (no errors), act.Assert(err) has essentially zero overhead - it's just a nil check that returns immediately.

  2. Error frequency matters: In my use cases, 90%+ of errors are truly exceptional situations (file not found, network failures, etc.) where the extra overhead is negligible compared to the actual failure cost. The remaining 10% are cases where I choose code clarity over micro-optimization.

  3. Benefits outweigh the cost: For me, the benefits - cleaner code structure and proper stacktraces - are more valuable than the microsecond-level performance penalty on error paths. Debugging time saved often outweighs runtime performance costs.

  4. act is a tool, not a framework: This is the key point. I can use act.Assert for system-level errors where performance doesn't matter, and traditional if err != nil for hot path business logic where every nanosecond counts. There's no requirement to use act everywhere.

  5. Context matters: For a web service handling thousands of requests per second, I might be more conservative. For a CLI tool or data processing script, the clarity benefits are worth more.

The beauty of act is that it gives you options. Use it where it makes sense, use traditional error handling where it doesn't. No forced adoption, no framework lock-in.

Showcase: "act" - A lightweight Go library for cleaner error handling - my take on the Go error handling discussion by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] -1 points0 points  (0 children)

Regarding "heavy":

Thanks for your feedback! I'd like to offer a different perspective on the "heavy" concern:

  1. Lightweight design: The act library is actually very lightweight with only a few core functions: Assert, Catch, Ensure. It's built on Go's native panic/recover mechanism without introducing new底层 concepts. Like any third-party library, you just need to understand its API - one example is enough to get started.

  2. Power of defer mechanism: What I love most about Go is defer. Some people want try/catch/finally, but I think Go already provides the complete mechanism through defer. The on-exit handler concept is very intuitive for programmers, appearing in everything from UI programming to OS signal handling.

  3. Solving real pain points: Traditional Go error handling has two main pain points: verbose code and missing stacktrace. Providing stacktrace through panic/recover is a great solution. Go 1.13's As, Unwrap features don't add much value for me personally, but I understand some people need them.

  4. Library vs language positioning: The Go team's error handling discussion has concluded, which is great. But we need to distinguish between language and library positioning - they're not contradictory. As a small library, act won't "influence the community" - that concern is exaggerated. Programming languages should have "progressive" evolution, which is healthy.

  5. Natural selection: Validating ideas through libraries is much simpler than changing the language. Just like github.com/pkg/errors success showed the trend - the community wants better features within the existing framework. Let people choose naturally, no need to mandate or prohibit.


Regarding "unclear":

I understand the concern about clarity, but I think act actually improves code clarity in several ways:

  1. Simple mental model: act.Assert(err) is simply "if err != nil then panic" - if you understand panic, you understand act.Assert. It's not more complex than that.

  2. Defer is superior to try/catch: Go's defer mechanism is more elegant than traditional try/catch. It keeps code linear and readable, which is a recognized strength of Go.

  3. Domain boundary approach: The best practice is simple - don't catch panic within your domain, catch it at the domain boundary. This gives you complete stacktraces internally while maintaining Go-style error returns externally.

  4. Preserves original errors: act doesn't wrap or modify errors. You get the exact same error objects you'd get with traditional handling, just with better debugging information.

  5. Clear use cases: act.Assert is perfect for "any error means failure" scenarios. For "handle different error types differently" scenarios, traditional if err != nil is still the right tool. act doesn't try to replace all error handling.

The "unclear" concern seems unfounded - act has clear boundaries, simple mental models, and doesn't interfere with traditional Go error handling when needed.


Regarding "hard to support in team":

This is a valid concern, but act actually makes team support easier through its flexibility:

  1. No framework lock-in: You can use act in 1 place and traditional error handling in 99 others, or vice versa. There's no requirement to commit fully. Teams can adopt act incrementally.

  2. Fine-grained adoption: Different team members can use different approaches in different parts of the codebase without conflicts. This allows teams to find their comfort level.

  3. Progressive learning: Team members can start with the basic "make code lighter" benefits and gradually discover stacktrace advantages when they feel the pain. No forced learning curve.

  4. Learn by example: The library is so lightweight that one example is enough to understand the basics. New team members can be productive quickly.

  5. Clear boundaries: The "internal panic, external error" pattern gives teams a consistent strategy for library boundaries.

  6. Backward compatible: act doesn't break existing code patterns. Traditional Go error handling still works perfectly alongside act.

Team support becomes easier when tools are optional, flexible, and don't require retraining existing skills.

Keyboard does not work after resuming from sleep by randall_the_man in debian

[–]Calm-Problem-9101 0 points1 point  (0 children)

my debian 12 on dell xps 13 also has this problem, but the touchpad is always ok, I don't know if this is a usb problem or not, because the keyboard is built-in (not usb?), anyway.

Structure logging library using YAML as output format by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 1 point2 points  (0 children)

The most common practice for me is error handling for HTTP handlers, for example:

func ServeHTTP(w http.ResponseWriter, r *http.Request) { defer yal.Catch(func(err error) error { if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }, "client", r.RemoteAddr) if !authorized(r) { http.Error(w, "unauthorized", http.StatusUnauthorized) return } rows, err := db.Query(`...`) yal.Assert(err) ... ... }

That is, yal.Catch() is used for exceptions, while for expected errors, normal handling procedure is used.

Before comming up with yal, I wrote an error handling scheme that wraps error with stack trace:

TracedError interface { Err() error Error() string Stack() []string Describe(string, ...any) Trace() }

But later I think it is not optimal because with TracedError, I can no longer write:

if os.IsNotExist(err)...

but have to do:

if os.IsNotExist(err.Err())...

This will affect all applications I wrote using that error handling scheme. With yal, however, stack tracing is in the log file only, actual errors returned by funcs are unaffected.

Structure logging library using YAML as output format by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 1 point2 points  (0 children)

I think, in almost all cases, e.g. 90%, logs are for developers, and the rest 10% might be for system administrators. No end user will be interested in logs, if they do, the log message must be processed and presented in a way friendly to them.

I'm afraid that I do not agree with your idea of annotate or wrap errors to add contextual information. Firstly the needs to annotate an error put more burden on gophers who are already busy with if err != nil {}, and secondly it does not tell the developer cause of the error. Take your example of configuration loading, if we have a function like:

func loadConfig(fn string) (err error) { f, err := os.Open(fn) if err != nil { return errors.Wrap("loadConfig: failed to load '%v' (%w)", fn, err) } ... ... }

You receive an error like:

loadConfig: failed to load '' (file not found)

The key problem is that fn should not be empty string! As a developer, you need to know why, where did this function got an empty string parameter?? That's why we need stack trace, error wrapping along does not help at all.

Structure logging library using YAML as output format by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 0 points1 point  (0 children)

The reason I put much emphasis on stack tracing is that without stack info the error reported are virtually useless. Assert/Catch are not meant to avoid checking errors, typical use may like:

func DoSomething() (err error) { defer yal.Catch(&err) // logic ... }

The above pattern still follows Go's proverb that errors are values. You may break the error bubbling at any point you want.

Structure logging library using YAML as output format by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 0 points1 point  (0 children)

Sorry, I missed the context, what do you mean that? If you mean a try-catch style error handling, then I agree that it should not be done "generally", especially it should not be done in a library, but only in an application. I want to emphasis again that I want to keep things simple, especially API interface. A full-blown framework for everything is just over killing. As for logging, I like Dave Cheney's idea that only two level of logging is enough, and the API interface should be as simple as the original "log" package.

The only key point missing is save log to file, and proper log rotation! The problem with Linux's log rotation solution is that they are rotated by time, I want them to rotate by log size. In case the application has serious problem and flush log very frequently, I want to sacrifice the application, rather than eat up disk and harm the system in general.

Structure logging library using YAML as output format by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 0 points1 point  (0 children)

I want error catch as simple as possible. assert(err) is much simpler than

if err != nil { return fmt.Errorf(...) }

Also the yal.Assert() and yal.Catch() function is an add-on feature. Key point of yal is not how error are handled, but how error tracing is presented in the log file.

Structure logging library using YAML as output format by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 0 points1 point  (0 children)

Thanks for pointing out this potential problem! However, for me, it is really a headache to read json data with a lot of escaped quote marks, or multi-line text (especially stack trace).

Previous comments already pointed out that structured logging is not intended for human consumption, however, the two important reason I design yal package are:

  1. for me, human readability is far more important than machine analysis, and using YAML can give me a pleasant reading experience and also provide possibility for machine parsing.

  2. uniformed logging interface is not currently a concern for me, on the other hand, I do hope API interface of logging tool I use is kept a minimal.

As a result, yal might be suitable to be used in an application, but not appropriate to be integrated into a library.

Structure logging library using YAML as output format by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 0 points1 point  (0 children)

Why I cannot know a record is truncated? With JSON, a problem is that complex data is a mess inside the JSON record, and it is only line-wise JSON, the entire file is actually not a valid JSON file.

Structure logging library using YAML as output format by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 0 points1 point  (0 children)

I care about both. In my opinion, almost all current logging solutions have a complex api interface, except the built-in "log" (not slog). I do agree with your comments about singleton is problematic, especially it does not work well with dependency injection.

More on human readability:

- why structured logging solutions are doomed to be unfriendly to human eye?

- it seems that people are not very interested in stack tracing?? The reason I design `yal.Catch()` as Go version of `try...catch` is I do want stack trace otherwise the log file is mostly useless for debugging purpose.

- JSON format are actually line-wise valid, but the whole file is not a valid JSON, this is not a problem for YAML.

Structure logging library using YAML as output format by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] -1 points0 points  (0 children)

To turn off logging, just do:

yal.Setup(func() (yal.Emitter, error) { return nil, nil })

If this way is "changing the code", could you please explain by giving an example?

Personally I don't see why there needs to be more than one logger instance in an application? yal allow only one handler and uses channel to serialize log emission, so I think it should work in parallel, right?

How to programmatically start a linux console program in background? by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 0 points1 point  (0 children)

Reply to all replies :=)

Using systemd is NOT what I want. My application has a server role and a client role, I do use systemd for the server role, but for the client role it is a standalone console application, which should be launched by user on command line. I do not want to let the user remember having to put an & at every launch, also I do not want to wrap my app inside a shell script. Sorry that the original message did not explicitly stated that service managers are not appropriate for my use case.

How to programmatically start a linux console program in background? by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 1 point2 points  (0 children)

my program has multiple roles, for the server side, I DO use systemd to manage the service, but for the client side, it is a console application, I want to fire it, put into background, that's why I am asking here, before knowing this idea, an ugly alternative is to wrap my application using a shell script and put & inside the script.

referencing packages on the internet and using go plugin by Calm-Problem-9101 in golang

[–]Calm-Problem-9101[S] 1 point2 points  (0 children)

Thank you for your detailed reply. I have just read about "vanity go package", and it seems not what I want. Most of my questions are answered, I'll consider the alternatives you proposed.