55 "context"
66 _ "embed"
77 "fmt"
8+ "io"
89 "regexp"
910 "strings"
1011 "sync"
@@ -13,6 +14,7 @@ import (
1314 "github.com/docker/docker/api/types/container"
1415 "github.com/docker/docker/api/types/network"
1516 "github.com/docker/docker/client"
17+ "github.com/docker/docker/pkg/stdcopy"
1618 "github.com/fatih/color"
1719 "github.com/spf13/cobra"
1820)
@@ -21,33 +23,27 @@ import (
2123var cmdUsage string
2224
2325type inputArgs struct {
24- network string
25- showAll bool
26- showErrors bool
27- showWarnings bool
28- showInfo bool
29- showDebug bool
30- filter string
31- levels string
32- service string
26+ network string
27+ filter string
28+ levels string
29+ service string
3330}
3431
3532var dockerloggerInputArgs = inputArgs {}
3633
3734var (
38- // Colors for log output
39- normalColor = color .New (color .FgGreen )
40- warningColor = color .New (color .FgYellow , color .Bold )
41- errorColor = color .New (color .FgRed , color .Bold )
35+ // Colors for log output components
36+ timestampColor = color .New (color .FgCyan )
37+ serviceNameColor = color .New (color .FgBlue )
38+ errorLevelColor = color .New (color .FgRed , color .Bold )
39+ warnLevelColor = color .New (color .FgYellow , color .Bold )
40+ infoLevelColor = color .New (color .FgGreen )
41+ debugLevelColor = color .New (color .FgMagenta )
42+ messageColor = color .New (color .Reset ) // Normal text color
4243)
4344
4445// Types
4546type LogConfig struct {
46- showAll bool
47- showErrors bool
48- showWarns bool
49- showInfo bool
50- showDebug bool
5147 customWords string
5248 logLevels string
5349 serviceNames []string
@@ -62,11 +58,6 @@ func dockerlogger(cmd *cobra.Command, args []string) error {
6258 }
6359
6460 config := LogConfig {
65- showAll : dockerloggerInputArgs .showAll ,
66- showErrors : dockerloggerInputArgs .showErrors ,
67- showWarns : dockerloggerInputArgs .showWarnings ,
68- showInfo : dockerloggerInputArgs .showInfo ,
69- showDebug : dockerloggerInputArgs .showDebug ,
7061 customWords : dockerloggerInputArgs .filter ,
7162 logLevels : dockerloggerInputArgs .levels ,
7263 }
@@ -97,11 +88,6 @@ var Cmd = &cobra.Command{
9788func init () {
9889 f := Cmd .Flags ()
9990 f .StringVar (& dockerloggerInputArgs .network , "network" , "" , "docker network name to monitor" )
100- f .BoolVar (& dockerloggerInputArgs .showAll , "all" , false , "show all logs" )
101- f .BoolVar (& dockerloggerInputArgs .showErrors , "errors" , false , "show error logs" )
102- f .BoolVar (& dockerloggerInputArgs .showWarnings , "warnings" , false , "show warning logs" )
103- f .BoolVar (& dockerloggerInputArgs .showInfo , "info" , false , "show info logs" )
104- f .BoolVar (& dockerloggerInputArgs .showDebug , "debug" , false , "show debug logs" )
10591 f .StringVar (& dockerloggerInputArgs .filter , "filter" , "" , "additional keywords to filter, comma-separated" )
10692 f .StringVar (& dockerloggerInputArgs .levels , "levels" , "" , "comma-separated log levels to show (error,warn,info,debug)" )
10793 f .StringVar (& dockerloggerInputArgs .service , "service" , "" , "filter logs by service names (comma-separated, partial match)" )
@@ -195,8 +181,23 @@ func streamContainerLogs(ctx context.Context, cli *client.Client, containerID, c
195181
196182 fmt .Printf ("Started monitoring container: %s\n " , serviceName )
197183
198- // Read logs line by line
199- scanner := bufio .NewScanner (logs )
184+ // Create a pipe to demultiplex Docker's stdout/stderr stream
185+ reader , writer := io .Pipe ()
186+ defer reader .Close ()
187+ defer writer .Close ()
188+
189+ // Demultiplex the Docker log stream in a goroutine
190+ go func () {
191+ // stdcopy.StdCopy properly handles Docker's 8-byte header format
192+ _ , err := stdcopy .StdCopy (writer , writer , logs )
193+ if err != nil && err != io .EOF {
194+ fmt .Printf ("Error demultiplexing logs for %s: %v\n " , serviceName , err )
195+ }
196+ writer .Close ()
197+ }()
198+
199+ // Read demultiplexed logs line by line
200+ scanner := bufio .NewScanner (reader )
200201 for scanner .Scan () {
201202 logLine := scanner .Text ()
202203 if logLine == "" {
@@ -214,32 +215,75 @@ func streamContainerLogs(ctx context.Context, cli *client.Client, containerID, c
214215 continue
215216 }
216217
217- // Format timestamp and print log
218+ // Format timestamp and print log with colored components
218219 timestamp := time .Now ().UTC ().Format ("2006-01-02 15:04:05" )
219- var logColor * color.Color
220- if isErrorMessage (logLineLower ) {
221- logColor = errorColor
222- } else if isWarningMessage (logLineLower ) {
223- logColor = warningColor
224- } else {
225- logColor = normalColor
226- }
227220
228- logColor .Printf ("[%s] [%s] %s\n " , timestamp , serviceName , logLine )
221+ // Print with different colors for each component
222+ timestampColor .Printf ("[%s] " , timestamp )
223+ serviceNameColor .Printf ("[%s] " , serviceName )
224+
225+ // Colorize the log level within the message and print the rest normally
226+ printColorizedLogLine (logLine , logLineLower )
229227 }
230228
231229 if err := scanner .Err (); err != nil {
232230 fmt .Printf ("Error reading logs for %s: %v\n " , serviceName , err )
233231 }
234232}
235233
236- // Log filtering and processing functions
237- func shouldLogMessage (logLine string , config * LogConfig ) bool {
238- // If showAll is true, skip other checks
239- if config .showAll {
240- return true
234+ // printColorizedLogLine prints a log line with appropriate colors for log levels
235+ func printColorizedLogLine (logLine , logLineLower string ) {
236+ // Define log level patterns to search for
237+ logLevelPatterns := []struct {
238+ pattern string
239+ color * color.Color
240+ }{
241+ {"ERROR" , errorLevelColor },
242+ {"ERRO" , errorLevelColor },
243+ {"EROR" , errorLevelColor },
244+ {"ERR" , errorLevelColor },
245+ {"WARNING" , warnLevelColor },
246+ {"WARN" , warnLevelColor },
247+ {"WRN" , warnLevelColor },
248+ {"INFO" , infoLevelColor },
249+ {"INF" , infoLevelColor },
250+ {"DEBUG" , debugLevelColor },
251+ {"DBG" , debugLevelColor },
252+ }
253+
254+ // Find the log level in the message
255+ foundLevel := false
256+ for _ , lp := range logLevelPatterns {
257+ // Case-insensitive search for the pattern
258+ patternLower := strings .ToLower (lp .pattern )
259+ if idx := strings .Index (logLineLower , patternLower ); idx != - 1 {
260+ // Print everything before the log level
261+ if idx > 0 {
262+ messageColor .Print (logLine [:idx ])
263+ }
264+
265+ // Print the log level with its color (preserve original case)
266+ levelEnd := idx + len (lp .pattern )
267+ lp .color .Print (logLine [idx :levelEnd ])
268+
269+ // Print everything after the log level
270+ if levelEnd < len (logLine ) {
271+ messageColor .Print (logLine [levelEnd :])
272+ }
273+ fmt .Println ()
274+ foundLevel = true
275+ break
276+ }
241277 }
242278
279+ // If no log level found, print the entire line normally
280+ if ! foundLevel {
281+ messageColor .Println (logLine )
282+ }
283+ }
284+
285+ // Log filtering and processing functions
286+ func shouldLogMessage (logLine string , config * LogConfig ) bool {
243287 // Parse configured log levels
244288 var allowedLevels map [string ]bool
245289 if config .logLevels != "" {
@@ -275,17 +319,8 @@ func shouldLogMessage(logLine string, config *LogConfig) bool {
275319 }
276320 }
277321
278- // Check individual flag settings if no level match
279- if config .showErrors && isErrorMessage (logLine ) {
280- return true
281- }
282- if config .showWarns && isWarningMessage (logLine ) {
283- return true
284- }
285- if config .showInfo && isInfoMessage (logLine ) {
286- return true
287- }
288- if config .showDebug && isDebugMessage (logLine ) {
322+ // If no levels or keywords specified, show all logs
323+ if config .logLevels == "" && config .customWords == "" {
289324 return true
290325 }
291326
@@ -324,5 +359,16 @@ func isDebugMessage(logLine string) bool {
324359func sanitizeLogLine (logLine string ) string {
325360 // Remove ANSI color codes
326361 ansiRegex := regexp .MustCompile (`\x1b\[[0-9;]*m` )
327- return ansiRegex .ReplaceAllString (logLine , "" )
362+ logLine = ansiRegex .ReplaceAllString (logLine , "" )
363+
364+ // Remove any remaining non-printable characters except common whitespace
365+ var result strings.Builder
366+ for _ , r := range logLine {
367+ // Keep printable characters and common whitespace (space, tab, newline)
368+ if r >= 32 || r == '\t' || r == '\n' || r == '\r' {
369+ result .WriteRune (r )
370+ }
371+ }
372+
373+ return strings .TrimSpace (result .String ())
328374}
0 commit comments