惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
MyScale Blog
MyScale Blog
P
Proofpoint News Feed
S
Security @ Cisco Blogs
N
News and Events Feed by Topic
H
Hacker News: Front Page
Attack and Defense Labs
Attack and Defense Labs
S
Secure Thoughts
Microsoft Security Blog
Microsoft Security Blog
N
Netflix TechBlog - Medium
U
Unit 42
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
Google Online Security Blog
Google Online Security Blog
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
L
LINUX DO - 热门话题
T
Tenable Blog
博客园 - 叶小钗
D
DataBreaches.Net
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
人人都是产品经理
人人都是产品经理
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
量子位
P
Proofpoint News Feed
H
Help Net Security
Blog — PlanetScale
Blog — PlanetScale
宝玉的分享
宝玉的分享
Recorded Future
Recorded Future
The Register - Security
The Register - Security
F
Fortinet All Blogs
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
S
Schneier on Security
V
Vulnerabilities – Threatpost
雷峰网
雷峰网
Microsoft Azure Blog
Microsoft Azure Blog
G
GRAHAM CLULEY
G
Google Developers Blog
月光博客
月光博客
V
V2EX
T
Troy Hunt's Blog
A
Arctic Wolf

alexwlchan

Preventing line breaks in <code> elements Fixing a bug with byte order marks A Git hook to prevent committing directly to main Describing all my photos I don’t want to repeat repeat myself Rebuilding the computer room What can wonky APIs tell us about the web? Using the Screen Capture API to record a browser window Using Pytester to test my Playwright fixtures Rendering a chat thread in CSS and JavaScript Waiting for website changes in the browser Watching for file changes on macOS Using Playwright to test my static sites Building a basic cache with SQLite HTTP GET requests with the Python standard library Auditing my local Python packages Quietly quantum-resistant blogging Creating a personalised bin calendar Monki Gras 2026 “Prepping Craft” The selfish case for public libraries Dreaming of a ten-year computer Gumdrop, a silly app for messing with my webcam The bare minimum for syncing Git repos Creating Caddyfiles with Cog Swapping gems for tiles Parody posters for made-up movies The Good, the Bad, and the Gutters Using perceptual distance to create better headers The passwords I actually memorise Where I store my multi-factor recovery codes My favourite books from 2025 Drawing Truchet tiles in SVG Adding a README to S3 buckets with Terraform The palm tree that led to Palmyra
Quick-and-dirty print debugging in Go
2026-01-08 · via alexwlchan

I’ve been writing a lot of Go in my new job, and trying to understand a new codebase.

When I’m reading unfamiliar code, I like to use print debugging to follow what’s happening. I print what branches I’m in, the value of different variables, which functions are being called, and so on. Some people like debuggers or similar tools, but when you’re learning a new language they’re another thing to learn – whereas printing “hello world” is the first step in every language tutorial.

The built-in way to do print debugging in Go is fmt.Printf or log.Printf. That’s fine, but my debug messages get interspersed with the existing logs so they’re harder to find, and it’s easy for those debug statements to slip through code review.

Instead, I’ve taken inspiration from Ping Yee’s Python module “q”. If you’re unfamiliar with it, I recommend his lightning talk, where he explains the frustration of trying to find a single variable in a sea of logs. His module provides a function q.q(), which logs any expressions to a standalone file. It’s quick and easy to type, and the output is separate from all your other logging.

I created something similar for Go: a module which exports a single function Q(), and logs anything it receives to /tmp/q.txt. Here’s an example:

package main

import (
	"github.com/alexwlchan/q"
	"os"
)

func printShapeInfo(name string, sides int) {
	q.Q("a %s has %d sides", name, sides)
}

func main() {
	q.Q("hello world")

	q.Q(2 + 2)

	_, err := os.Stat("does_not_exist.txt")
	q.Q(err)

	printShapeInfo("triangle", 3)
}

The logged output in /tmp/q.txt includes the name of the function and the expression that was passed to Q():

main: "hello world"

main: 2 + 2 = 4

main: err = stat does_not_exist.txt: no such file or directory

printShapeInfo: a triangle has 3 sides

I usually open a terminal window running tail -f /tmp/q.txt to watch what gets logged by q.

The module is only 120 lines of Go, and available on this website. You can copy it into your project, or it’s simple enough that you could write your own version. It has two interesting ideas that might have broader use.

Getting context with the runtime package

When you call Q(), it receives the final value – for example, if you call Q(2 + 2), it receives 4 – but I wanted to log the original expression and function name. This is a feature from Ping’s Python package, and it’s what makes q so pleasant to use. This gives context for the log messages, and saves you typing that context yourself.

I get this information from Go’s runtime package, in particular the runtime.Caller function, which gives you information about the currently-running function.

I call runtime.Caller(1) to step up the callstack by 1, to the actual line in my code where I typed Q(). It tells me the “program counter”, the filename, and the line number. I can resolve the program counter to a function name with runtime.FuncForPC, and I can just open the file and look up that line to read the expression. (This assumes the source code hasn’t changed since compilation, which is always true when I’m doing local debugging.)

Not affecting my coworkers with a local gitignore

To use this file, I copy q.go into my work repos and add it to my .git/info/exclude. The latter is a local-only ignore file, unlike the .gitignore file which is checked into the repo. This means I won’t accidentally check in q.go or push it to GitHub.

It also means I can’t forget to remove my debugging code, because if I do, the tests in CI will fail when they can’t find q.go.

This avoids other approaches that would be more disruptive or annoying, like making it a project dependency or adding it to the shared .gitignore file.