log, regexp & text/template
Text processing in production Go spans logs, pattern matching, and rendered output.
log/slog structures events for observability pipelines.
regexp extracts and validates text with predictable performance.
text/template and html/template generate emails, CLIs, and HTML without string concatenation bugs.
Summary
Migrate new services to slog with JSON handlers and level filters.
Keep package log only where migration cost exceeds benefit.
Precompile regular expressions at package init or startup, not per request.
Choose html/template whenever output lands in browsers to get contextual auto-escaping.
Recipe
Quick-reference recipe card - copy-paste ready.
import (
"log/slog"
"os"
"regexp"
"text/template"
)
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
var reUUID = regexp.MustCompile(`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`)
tmpl := template.Must(template.New("mail").Parse("Hello {{.Name}}\n"))When to reach for this:
- Emitting structured logs with levels and attributes for Loki, Elasticsearch, or CloudWatch.
- Parsing log lines, validating identifiers, or rewriting config text.
- Generating reports, Helm-style manifests, or email bodies from data structs.
- Escaping user content in HTML responses with
html/template.
Working Example
package main
import (
"bytes"
"errors"
"html/template"
"log/slog"
"os"
"regexp"
"text/template"
)
type User struct {
Name string
Email string
}
var emailRe = regexp.MustCompile(`^[^@\s]+@[^@\s]+\.[^@\s]+$`)
func validateAndRender(u User) (string, error) {
if !emailRe.MatchString(u.Email) {
return "", ErrInvalidEmail
}
slog.Info("render welcome",
slog.String("user", u.Name),
slog.String("email", u.Email),
)
textT := template.Must(template.New("text").Parse("Welcome {{.Name}} <{{.Email}}>"))
var textBuf bytes.Buffer
if err := textT.Execute(&textBuf, u); err != nil {
return "", err
}
htmlT := template.Must(template.New("html").Parse(`<p>Welcome <b>{{.Name}}</b></p>`))
var htmlBuf bytes.Buffer
if err := htmlT.Execute(&htmlBuf, u); err != nil {
return "", err
}
return htmlBuf.String(), nil
}
var ErrInvalidEmail = errors.New("invalid email")
func main() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})))
out, err := validateAndRender(User{Name: "Ada", Email: "ada@example.com"})
if err != nil {
panic(err)
}
println(out)
}What this demonstrates:
- Compiled regex reused across calls.
slog.Infowith typed attributes.- Parallel
text/templateandhtml/templatepipelines from one struct. template.Mustfor parse-time failure at startup when moved toinit.
Deep Dive
How It Works
slogroutes records through aHandler(JSONHandler,TextHandler, or custom) with level gating.regexpcompiles patterns to automata with RE2 syntax (no backreferences).- Templates execute against Go values using
{{.Field}}and actions likerangeandif. html/templateapplies contextual escaping rules per HTML, JS, URL, and CSS contexts.
Logging Comparison
| API | Output | Best for |
|---|---|---|
log | Unstructured stderr lines | Scripts, quick debugging |
log/slog | Leveled, attributed records | Services and libraries |
| Third-party zap/zerolog | Ecosystem-specific performance | Existing stacks already standardized |
Go Notes
// Attach request ID across handlers:
logger := slog.With(slog.String("request_id", id))
logger.Info("handled", slog.Int("status", 200))
// Regex submatches:
parts := re.FindStringSubmatch(line)
// Template func map:
tmpl.Funcs(template.FuncMap{"upper": strings.ToUpper})Gotchas
- Compiling regex per request - CPU spikes under traffic. Fix:
regexp.MustCompileat init or usesync.Once. - Using text/template for HTML pages - XSS when user names contain
<script>. Fix:html/templatefor browser output. - Logging sensitive fields - PII and tokens in JSON logs violate compliance. Fix: redact attributes; use allowlists.
- Global slog default unset - Libraries log to discarded handler. Fix:
slog.SetDefaultinmainearly; accept*slog.Loggerin packages. - Complex regex for HTML parsing - Fragile and slow compared to parsers. Fix: use
encoding/json,htmltokenizer, or proper parsers. - Template nil pointer dereference - Execute panics on missing fields. Fix: use
{{if .Optional}}or pointer checks in data structs.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| zap / zerolog | Nanosecond logging at huge QPS | Greenfield slog suffices |
strings functions | Simple prefix/split checks | Pattern needs are regex-heavy |
embed + static files | Fixed HTML assets | Personalized email content |
fmt.Fprintf one-liners | Debug-only output | Production structured logs |
FAQs
Should I still use package log in 2026?
Use log/slog for new services.
Keep log in legacy code until a focused migration pass; both can coexist during transition.
How do I set slog level to debug?
slog.HandlerOptions{Level: slog.LevelDebug} on your handler.
Gate debug logs in production with environment variables or build tags.
Does regexp support lookahead?
No.
Go regexp follows RE2, which omits backreferences and lookahead for linear time guarantees.
Restructure patterns or use a different engine if you truly need those features.
What is the cost of regexp compared to strings.Contains?
Compiled regex on simple literals is heavier than Contains or HasPrefix.
Use string functions for fixed substring checks; regex when structure matters.
How do templates share layouts?
Parse a base template with {{define "layout"}} blocks and {{template "layout" .}} in children.
ParseFiles and ParseGlob load sets at startup.
Can slog output go to rotating files?
Wrap os.File in a slog.Handler or use io.MultiWriter with lumberjack-style rotation libraries.
Handler choice stays independent of destination.
Why did my html/template escape URLs wrong?
Contextual escaping depends on attribute type.
Use typed helpers like template.URL for dynamic href values.
How do I test templates?
Execute into bytes.Buffer in table tests and compare golden strings.
For HTML, assert substrings rather than full whitespace-sensitive files.
Are log flags still relevant?
log package flags (LstdFlags, Lshortfile) apply only to legacy log output.
slog uses explicit attributes instead.
Can regexp match multiline text?
Pass the (?m) flag for multiline ^ and $ behavior.
Or enable regexp.Multiline constant when compiling with Compile.
What happens if template.Execute fails mid-write?
Partial output may already be written to the writer.
Use buffers for atomic responses or discard on error before sending HTTP headers.
How do I migrate from log.Printf to slog?
Replace format strings with attribute key-value pairs: slog.Info("msg", "key", value).
Wrap legacy call sites incrementally behind a small logging interface.
Related
- Standard Library Basics - fmt and string basics before templating
- os, io, bufio & path/filepath - Reading template files from disk
- The Go Standard Library: Batteries Included - slog as modern stdlib path
- runtime, debug & pprof Packages - Correlating logs with profiles
- Standard Library Best Practices - Logging and regex discipline
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).