Diff
1diff --git a/README.md b/README.md
2index b73c23f2700d79e83a7a1d117c87b6b9ae6c3b23..fad24f692ca9c155e88d19eb5055bec2c8ead75d 100644
3--- a/README.md
4+++ b/README.md
5@@ -32,16 +32,17 @@ go install github.com/TheEdgeOfRage/logfmt
6
7 ```
8 Usage:
9- logfmt [OPTIONS]
10+ main [OPTIONS]
11
12 Application Options:
13- -l, --level= Log level filter. One of DEBUG, INFO, WARN, ERROR, FATAL (default: INFO)
14- -o, --output= Output field selector (comma separated)
15- -f, --filter= Filter fields (key=value comma separated)
16- -n, --no-color Disable color output
17+ -l, --level= Log level filter. One of DEBUG, INFO, WARN, ERROR, FATAL (default: INFO)
18+ -o, --output= Output field selector (comma separated)
19+ -f, --filter= Filter fields (key=value comma separated)
20+ -n, --no-color Disable color output
21+ -c, --force-color Force color output, even when outputting to a pipe
22
23 Help Options:
24- -h, --help Show this help message
25+ -h, --help Show this help message
26 ```
27
28 If installed in your PATH, you can just run the `logfmt` program without any arguments and it will start reading log
29@@ -50,6 +51,13 @@ lines from stdin and write the formatted lines to stdout.
30 This CLI follows the UNIX philosophy, so it will only read from stdin and write to stdout. If you want stderr or a different
31 file, use your shell's built-in directives for that.
32
33+A typical usecase would be running a service for local development, or fetching the logs from a Kubernetes pod:
34+
35+```
36+go run yourservice.go | logfmt
37+kubectl logs -n namespace pod | logfmt
38+```
39+
40 #### Level filtering
41
42 To filters your logs based on the log level, you can pass the `-l` flag with a log level in CAPS format. The level you
43@@ -69,4 +77,9 @@ numerical filtering might come in the future.
44
45 #### No color
46
47-If you don't want to have colors on the output, use `-n`.
48+If you don't want to have colors on the output, set `-n`.
49+
50+#### Force color
51+
52+By default, logfmt will detect if the output is a pipe or redirect to a file and will automatically disable colors. If
53+you still want to have colorized output, for example when piping into `less`, you can force it using `-c`.
54diff --git a/config/config.go b/config/config.go
55index d1f5ce535f6e6547215088fcefe12a7c0f3d6ddb..0ab3ee2877aff3aa1115430c562ad28fe8d77cde 100644
56--- a/config/config.go
57+++ b/config/config.go
58@@ -4,7 +4,8 @@ import (
59 "fmt"
60 "strings"
61
62- flags "github.com/jessevdk/go-flags"
63+ "github.com/fatih/color"
64+ "github.com/jessevdk/go-flags"
65 )
66
67 const (
68@@ -22,8 +23,6 @@ type Config struct {
69 OutputFields []string
70 // Filter is a map of fields and values which are used to filter the log output
71 Filter map[string]string
72- // Color is a flag to enable or disable color on the output
73- Color bool
74 }
75
76 type rawConfig struct {
77@@ -31,6 +30,7 @@ type rawConfig struct {
78 OutputFields string `long:"output" short:"o" description:"Output field selector (comma separated)"`
79 Filter string `long:"filter" short:"f" description:"Filter fields (key=value comma separated)"`
80 NoColor bool `long:"no-color" short:"n" description:"Disable color output"`
81+ ForceColor bool `long:"force-color" short:"c" description:"Force color output, even when outputting to a pipe"`
82 }
83
84 func Parse() (*Config, error) {
85@@ -42,9 +42,7 @@ func Parse() (*Config, error) {
86 return nil, err
87 }
88
89- cfg := Config{
90- Color: !raw.NoColor,
91- }
92+ cfg := Config{}
93 cfg.setOutputFields(raw.OutputFields)
94 err = cfg.setFilter(raw.Filter)
95 if err != nil {
96@@ -54,6 +52,15 @@ func Parse() (*Config, error) {
97 if err != nil {
98 return nil, err
99 }
100+ if raw.ForceColor && raw.NoColor {
101+ return nil, fmt.Errorf("cannot use both --force-color and --no-color")
102+ }
103+ if raw.ForceColor {
104+ color.NoColor = false
105+ }
106+ if raw.NoColor {
107+ color.NoColor = true
108+ }
109
110 return &cfg, nil
111 }
112diff --git a/parser/parser.go b/parser/parser.go
113index 75cc148e190122cd62c009806eb7c4ff0c18295d..ff76b7045abd78f6a5cff198efd7ccafbb872ab5 100644
114--- a/parser/parser.go
115+++ b/parser/parser.go
116@@ -37,7 +37,7 @@ func (p *Parser) Start() error {
117 continue
118 }
119
120- _, err = fmt.Fprintf(p.output, "%s\n", record.String(p.cfg.OutputFields, p.cfg.Color))
121+ _, err = fmt.Fprintf(p.output, "%s\n", record.String(p.cfg.OutputFields))
122 if err != nil {
123 return fmt.Errorf("failed to print log to output: %w", err)
124 }
125diff --git a/parser/record.go b/parser/record.go
126index 1e4da18aa5c7cb7e6d2f30cf1b13ef3b0781f211..fcc34e805523b304f7857b1a797e84dddc4bccae 100644
127--- a/parser/record.go
128+++ b/parser/record.go
129@@ -86,28 +86,21 @@ func isNull(s string) bool {
130 return s == "null" || s == "NULL" || s == "nil" || s == "<nil>" || s == "None"
131 }
132
133-func getFormattedValue(value string, colorize bool) string {
134- if strings.Contains(value, " ") {
135- value = fmt.Sprintf(`"%s"`, value)
136- }
137- if !colorize {
138- return value
139- }
140+func getFormattedValue(value string) string {
141 if isNumeric(value) || isBoolean(value) {
142 return color.MagentaString(value)
143 }
144 if isNull(value) {
145 return color.YellowString(value)
146 }
147+ if strings.Contains(value, " ") {
148+ value = fmt.Sprintf(`"%s"`, value)
149+ }
150 return color.HiGreenString(value)
151 }
152
153-func getFormattedLevel(level int, colorize bool) string {
154- levelString := levelStrings[level]
155- if !colorize {
156- return levelString
157- }
158- return levelColors[level].Sprintf("[%s]", levelString)
159+func getFormattedLevel(level int) string {
160+ return levelColors[level].Sprintf("[%s]", levelStrings[level])
161 }
162
163 func (r *Record) parseLevel(level string) {
164@@ -146,25 +139,21 @@ func (r *Record) MatchesFilter(filter map[string]string) bool {
165 return true
166 }
167
168-// String returns a formatted and optionally colorized string representation of the Record
169-func (r *Record) String(outputFields []string, colorize bool) string {
170+// String returns a formatted string representation of the Record
171+func (r *Record) String(outputFields []string) string {
172 line := ""
173 for _, key := range r.fieldOrder {
174 if len(outputFields) > 0 && !slices.Contains(outputFields, key) {
175 continue
176 }
177 value := r.fields[key]
178- if colorize {
179- key = color.HiBlueString(key)
180- }
181- line += fmt.Sprintf("%s=%s ", key, getFormattedValue(value, colorize))
182+ key = color.HiBlueString(key)
183+ line += fmt.Sprintf("%s=%s ", key, getFormattedValue(value))
184 }
185- var fmtString string
186- if colorize {
187- fmtString = "%s %26s %s"
188- } else {
189- fmtString = "%s %s %s"
190+ fmtString := "%s %26s %s"
191+ if color.NoColor {
192+ fmtString = "%s %7s %s"
193 }
194
195- return fmt.Sprintf(fmtString, r.time.Format("2006-01-02 15:04:05"), getFormattedLevel(r.level, colorize), line)
196+ return fmt.Sprintf(fmtString, r.time.Format("2006-01-02 15:04:05"), getFormattedLevel(r.level), line)
197 }