Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
114: strings.Contains, Index, Replace
I was digging through some legacy log files the other day and realized I needed to pull out specific error messages, but the logs were a mess. Some lines had timestamps, some had priority levels, and some were just raw dumps. I found myself needing to quickly scan for keywords and clean up the text before sending it to a reporting tool.
Just checking for a needle in a haystack
When I first looked at a log line like "2023-10-12 14:02:01 [ERROR] Database connection failed: timeout", my first instinct was just to see if the word "ERROR" was even there. I didn't care where it was; I just needed a true or false.
package main
import (
"fmt"
"strings"
)
func main() {
logLine := "2023-10-12 14:02:01 [ERROR] Database connection failed: timeout"
if strings.Contains(logLine, "ERROR") {
fmt.Println("Found an error!")
}
}
This is the simplest tool in the strings package. strings.Contains takes the source string and the substring you're looking for. It returns a boolean. It's efficient, it's readable, and for 90% of "does this exist?" cases, it's all you need.
Pinpointing the exact spot
But here's where I hit a snag. Knowing that "ERROR" exists isn't helpful if I want to strip away the timestamp and only keep the actual message. I need to know where the error starts.
I tried using strings.Index to see if it could give me a coordinate.
package main
import (
"fmt"
"strings"
)
func main() {
logLine := "2023-10-12 14:02:01 [ERROR] Database connection failed: timeout"
idx := strings.Index(logLine, "[ERROR]")
fmt.Printf("Index: %d\n", idx)
// Output: Index: 20
}
Okay, so it's at index 20. Now I can use a slice to grab everything from that point forward. But wait—what happens if the word isn't there? I'll try it with "INFO" instead.
logLine := "2023-10-12 14:02:01 [DEBUG] Heartbeat sent"
idx := strings.Index(logLine, "[ERROR]")
fmt.Printf("Index: %d\n", idx)
// Output: Index: -1
Ah, right. Index returns -1 if the substring isn't found. If I had blindly tried to slice the string using logLine[idx:] without checking for -1, my program would have panicked with an "out of range" error. Always check for that -1 before you slice.
Swapping text on the fly
Lastly, I noticed these logs were being shared with a client, and they contained internal server names like "srv-prod-01" that I didn't want them to see. I needed a way to swap those out for something generic.
I'll try strings.Replace. I remember from the docs that this function requires a bit more information than just "find and replace."
package main
import (
"fmt"
"strings"
)
func main() {
msg := "Connection lost on srv-prod-01 and srv-prod-02"
// I want to replace "srv-prod" with "server"
// strings.Replace(s, old, new, n)
cleaned := strings.Replace(msg, "srv-prod", "server", 1)
fmt.Println(cleaned)
}
The output is: "Connection lost on server-01 and srv-prod-02". Only the first one changed! That's because of that last argument n. In Go, strings.Replace asks you exactly how many occurrences you want to swap. If I only put 1, it stops after the first hit.
Since I want to scrub every instance of that string, I have two choices. I could put a huge number like 1000, but that's amateur hour. The idiomatic way to say "do it all" is to pass -1, or just use strings.ReplaceAll, which is essentially a wrapper that does exactly that.
cleanedAll := strings.ReplaceAll(msg, "srv-prod", "server")
fmt.Println(cleanedAll)
// Output: Connection lost on server-01 and server-02
And there we have it. Check for existence with Contains, find the location with Index, and clean up the mess with ReplaceAll.
📋 Practical Task
The Log Entry Anonymizer
You are building a tool to sanitize logs before they are uploaded to a public forum. You need to write a function that processes a log string and performs the following steps:
- Check if the log line contains the substring
"USER_ID:". If it doesn't, return the original string immediately. - If it does contain
"USER_ID:", find the index where that substring starts. - Extract the substring starting from
"USER_ID:"up until the first space that follows it (or the end of the string). - Replace that entire identified user ID segment (e.g.,
"USER_ID:12345") with the text"USER_ID:[REDACTED]".
Example input: "10:00:01 Login attempt by USER_ID:99887 from IP 1.1.1.1"
Example output: "10:00:01 Login attempt by USER_ID:[REDACTED] from IP 1.1.1.1"
There are no comments for now.