log, regexp & text/template
Text processing in production Go spans logs, pattern matching, and rendered output.
Search across all documentation pages
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.
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.
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:
html/template.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:
slog.Info with typed attributes.text/template and html/template pipelines from one struct.template.Must for parse-time failure at startup when moved to init.slog routes records through a Handler (JSONHandler, TextHandler, or custom) with level gating.regexp compiles patterns to automata with RE2 syntax (no backreferences).{{.Field}} and actions like range and if.html/template applies contextual escaping rules per HTML, JS, URL, and CSS contexts.| 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 |
// 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})regexp.MustCompile at init or use sync.Once.<script>. Fix: html/template for browser output.slog.SetDefault in main early; accept *slog.Logger in packages.encoding/json, html tokenizer, or proper parsers.{{if .Optional}} or pointer checks in data structs.| 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 |
Use log/slog for new services.
Keep log in legacy code until a focused migration pass; both can coexist during transition.
slog.HandlerOptions{Level: slog.LevelDebug} on your handler.
Gate debug logs in production with environment variables or build tags.
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.
Compiled regex on simple literals is heavier than Contains or HasPrefix.
Use string functions for fixed substring checks; regex when structure matters.
Parse a base template with {{define "layout"}} blocks and {{template "layout" .}} in children.
ParseFiles and ParseGlob load sets at startup.
Wrap os.File in a slog.Handler or use io.MultiWriter with lumberjack-style rotation libraries.
Handler choice stays independent of destination.
Contextual escaping depends on attribute type.
Use typed helpers like template.URL for dynamic href values.
Execute into bytes.Buffer in table tests and compare golden strings.
For HTML, assert substrings rather than full whitespace-sensitive files.
log package flags (LstdFlags, Lshortfile) apply only to legacy log output.
slog uses explicit attributes instead.
Pass the (?m) flag for multiline ^ and $ behavior.
Or enable regexp.Multiline constant when compiling with Compile.
Partial output may already be written to the writer.
Use buffers for atomic responses or discard on error before sending HTTP headers.
Replace format strings with attribute key-value pairs: slog.Info("msg", "key", value).
Wrap legacy call sites incrementally behind a small logging interface.
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).
Reviewed by Chris St. John·Last updated Jul 16, 2026