A small bug that taught me to read my tools before blaming my code.
The Plan
I was building a Net-Cat project in Go — a simplified version of the classic nc (netcat) Unix tool. One of the features I wanted was a clean timestamp displayed whenever a client connected or sent a message. Something like this:
[2025-01-15 14:32:05] Alice has joined the chat
Simple enough, right? I reached for time.Now().Format() and felt good about it.
timestamp := time.Now().Format("2006-01-02 15:04:05")
log.Println("[" + timestamp + "] Alice has joined the chat")
I ran it, looked at the output, and... something was off.
The Confusing Output
Instead of the clean line I expected, my terminal was showing this:
2025/01/15 14:32:05 [2025-01-15 14:32:05] Alice has joined the chat
Two timestamps. Right next to each other.
My first thought? My format string must be broken. I started Googling time.Now().Format syntax. I tried different layouts. I rewrote the format string three times. Nothing changed. The double timestamp was still there, mocking me.
I even briefly wondered if Go had some weird timezone bug. (It did not.)
The Culprit: log.Println() vs fmt.Println()
Here's what I didn't know at the time:
log.Println() and fmt.Println() are NOT the same thing.
fmt.Println() is simple. It prints exactly what you give it, adds a newline, and calls it a day.
fmt.Println("Hello, world!")
// Output: Hello, world!
log.Println() is different. It's part of Go's log package, which is designed for application logging — the kind where you always want to know when something happened. So by default, log automatically prepends every message with the current date and time.
log.Println("Hello, world!")
// Output: 2025/01/15 14:32:05 Hello, world!
See that prefix? You didn't ask for it. It's just... there. Always. That's the log package doing its job.
So when I wrote:
timestamp := time.Now().Format("2006-01-02 15:04:05")
log.Println("[" + timestamp + "] Alice has joined the chat")
The log package saw my message, slapped its own timestamp at the front, and printed both. My code was working perfectly — I just didn't understand the tool I was using.
Why Does log Do This?
The log package in Go is built for one specific purpose: structured application logging. When you're debugging a server that's been running for days, or tracing a crash in production, the absolute first thing you need to know is when something happened.
So the designers of the log package made that the default. Every log entry gets a timestamp. You don't have to ask for it.
You can actually control this behaviour with log.SetFlags():
// Remove the automatic timestamp entirely
log.SetFlags(0)
log.Println("No timestamp here!")
// Output: No timestamp here!
// Or keep just the time, not the date
log.SetFlags(log.Ltime)
log.Println("Just the time")
// Output: 14:32:05 Just the time
The default flag value is log.LstdFlags, which equals log.Ldate | log.Ltime — both date and time. That's the source of my double timestamp.
How I Finally Figured It Out
After enough head-scratching, I stopped trying to fix my time.Now().Format() code and started asking a different question: "Where is this first timestamp actually coming from?"
I removed my custom timestamp entirely and just logged a plain string:
log.Println("Alice has joined the chat")
// Output: 2025/01/15 14:32:05 Alice has joined the chat
There it was. The timestamp wasn't coming from my code at all. I opened the Go docs for the log package for the first time, and the very first line told me everything:
"Package log implements a simple logging package. Each logging operation makes a single call to the Writer's Write method. A Logger can be used simultaneously from multiple goroutines; it guarantees serialized access to the Writer. The default logger writes to standard error and prints the date and time of each logged message."
Date and time of each logged message. Right there in the first paragraph. I just hadn't read it.
The Fix
For my Net-Cat project, I had two clean options:
Option 1: Switch to fmt.Println() and keep full control of formatting.
timestamp := time.Now().Format("2006-01-02 15:04:05")
fmt.Println("[" + timestamp + "] Alice has joined the chat")
// Output: [2025-01-15 14:32:05] Alice has joined the chat
Option 2: Keep log.Println() but disable its automatic prefix.
log.SetFlags(0) // Call this once at the start of your program
timestamp := time.Now().Format("2006-01-02 15:04:05")
log.Println("[" + timestamp + "] Alice has joined the chat")
// Output: [2025-01-15 14:32:05] Alice has joined the chat
I went with Option 1. fmt was the right tool for user-facing chat messages; log is better suited for actual application diagnostics.
The Real Lesson
The bug was never in my time.Now().Format() code. It was in my assumption that log.Println() behaved like fmt.Println(). They share a similar name and do similar things on the surface, but they're built for completely different jobs.
The takeaway for new Go developers:
When something isn't working, before you rewrite your logic — make sure you understand every tool you're using. A quick trip to the official Go docs (pkg.go.dev) can save you an hour of chasing the wrong bug.
Use fmt when you're talking to the user.
Use log when you're talking to yourself (or your future debugging self).
And when output looks weird? Don't assume your code is wrong. Ask where every part of that output is coming from.
Happy coding — and may your timestamps never double up again. 🕐
Top comments (0)