DZone
Performance Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Performance Zone > The Dangers of Fatal Logging

The Dangers of Fatal Logging

Fatal logging is practically always a bad idea. It violates the SRP, and introduces some nasty bugs.

Jonathan Hall user avatar by
Jonathan Hall
·
Mar. 21, 22 · Performance Zone · Opinion
Like (5)
Save
Tweet
3.28K Views

Join the DZone community and get the full member experience.

Join For Free

I want to talk about fatal logging. It’s practically always a bad idea. Let me explain…

I was recently reviewing some code written in Go, where I saw this pattern in a constructor function:

Go
 
func NewConnection(url string) *Client {
    conn, err := service.Connect(url)
    if err != nil {
        log.Fatal( err)
    }
    return &Client{conn: conn}
}


Whatever language you use, if your logging library has a Fatal option, I emplore you to never use it, and for one simple, but profound reason:

It Violates the Single-Responsibility Principle

Now, I don’t generally put the SOLID principles on a high pedestal. But this is one place where the SRP violation doesn’t just peek through the curtains, asking you politely to reconsider. It bursts through the seams like the Kool-aid Man screaming for attention. Oh yeah!

In Go, calling log.Fatal in the standard library, and to my knowledge in any other logger implementation, does two things:

  1. It logs an error message with a priority of FATAL
  2. It immediately exits the program (by calling os.Exit(1))

Do you see the SRP violation?

Logging is one concern. Affecting the control flow of the entire program (by exiting) is something else entirely. This function, by design, has two unrelated responsibilities.

But does it matter? If you know that calling log.Fatal exits the program, surely you can make an informed decision, right?

If you’re writing some short, throw-away program, akin to a bash script, sure. Whatever. I often have no problem violating the SRP, or countless other rules of thumb and best practices in such a case.

But if you’re writing anything like a real application, this SRP violation creates an insideous form of tight-coupling in your program.

What if the caller of your function wants to try to recover from an error? Maybe you add an option for the user to provide a list of connection URLs, and you want to try all of them until one works.

Go
 
for _, url := range config.URLs {
    conn = NewConnection(url)
}


With our current implementation, the first failure will cause the program to crash.

“Yes, but I can edit it!” You can. But SRP. You should have only one reason to change a piece of code. We now have two: (1) we want to change the way a connection works (2) we want to decouple control flow.

Or what if you add the option to re-connect to the service after a failure after the app has already been running. Do you want a failed connection attempt to exit the program? Well… maybe. But also maybe not. That should be up to the caller of the function, not the function itself.

Only Exit Your Program From the Top of the Call Stack

In Go parlance, this means: Only ever call os.Exit() from your main() function. This precludes calling log.Fatal from anywhere except possibly in main() itself.

If you follow this simple rule, your constructors will not surprise their callers by exiting the program.

This is doubly important in any language (like Go), where exiting the program precludes any cleanup. In Go, calling os.Exit means that deferred functions don’t get called, and there’s no opportunity for recovery. It’s final. Do not pass Go. Do not collect $200.

This means that in many applications, calling log.Fatal may actually not even log your error! What?

If you’re logging to a network service, one of the last things you must do before exiting your program is flush your log buffer. If you call os.Exit, that flushing never happens.

Won’t that be a lovely debugging session? You typo your database config. Now your app won’t start… and it doesn’t send you any logs.  Ugh!

What’s the Alternative?

In the Go example above, the obvious alternative is to return the error to the caller.

Go
 
func NewConnection(url string) (*Client, error) {
    conn, err := service.Connect(url)
    if err != nil {
        return nil, err
    }
    return &Client{conn: conn}, nil
}


Your language may use exceptions. That’s fine. Use whatever normal error-handling capability your language/tool provides.

In Go, there’s also the option to panic, if returning an error really doesn’t make sense. panic differs from os.Exit in three distinct ways:

  1. It has a different semantic meaning. It means “something unrecoverable happened”, whereas os.Exit means “quit the program” without regard for why.
  2. Deferred functions are still called, so cleanup can be done prior to program exit.
  3. It’s recoverable.
Error message app application Connection (dance) IT Implementation Flow (web browser)

Published at DZone with permission of Jonathan Hall. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • How to Submit a Post to DZone
  • Refactor Switch to a One-Liner
  • Architecture as Code With C4 and Plantuml
  • 7 Ways to Capture Java Heap Dumps

Comments

Performance Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo