1694603c7221f5ffdd7f8bf0477f88f36bd2f05d

Author
td-jizdebski <jakub.izdebski@jrdltd.co.uk>
Committer
GitHub <noreply@github.com>
Date

Message

Feature/raw (#10)

* add option to skip empty and enforce fields order

* readme update

* drop backwards compatibilty

* readme fix

* changes

* rework after upstream info

* spelling

* rework

* fix

* Apply suggestion from @TheEdgeOfRage

Co-authored-by: Pavle <29902467+TheEdgeOfRage@users.noreply.github.com>

* Apply suggestion from @TheEdgeOfRage

Co-authored-by: Pavle <29902467+TheEdgeOfRage@users.noreply.github.com>

* Apply suggestion from @TheEdgeOfRage

Co-authored-by: Pavle <29902467+TheEdgeOfRage@users.noreply.github.com>

* remove bin

---------

Co-authored-by: Pavle <29902467+TheEdgeOfRage@users.noreply.github.com>

Diff

This diff is truncated to protect this page.

  1diff --git a/README.md b/README.md
  2index 04e08cb25fbfa294132ead8c338add9c9b2992b8..d4f85bd5b8cdb7388f72806549ba8d9e87bbed9f 100644
  3--- a/README.md
  4+++ b/README.md
  5@@ -37,13 +37,16 @@ Usage:
  6   logfmt [OPTIONS]
  7 
  8 Application Options:
  9-  -l, --level=          Log level filter. One of DEBUG, INFO, WARN, ERROR, FATAL (default: INFO)
 10-  -o, --output=         Output field selector (comma separated)
 11-  -e, --exclude=        Exclude field selector (comma separated)
 12-  -f, --filter=         Filter fields (key=value comma separated)
 13-  -n, --no-color        Disable color output
 14-  -c, --force-color     Force color output, even when outputting to a pipe
 15-  -s, --keep-empty      Keep lines with no field present selected by output or with all excluded
 16+  -l, --level=       Log level filter. One of DEBUG, INFO, WARN, ERROR, FATAL (default: INFO)
 17+  -o, --output=      Output field selector (comma separated)
 18+  -e, --exclude=     Exclude 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+  -t, --no-time      Disable time output
 23+  -k, --keep-empty   Keep lines with no field present selected by output or with all excluded
 24+  -r, --raw          Output only selected fields values (comma separated) lcut like
 25+  -A, --all          Output all field after the output fields effectivly making it ordered
 26 
 27 Help Options:
 28   -h, --help         Show this help message
 29@@ -98,6 +101,14 @@ you still want to have colorized output, for example when piping into `less`, yo
 30 
 31 By default, `logfmt` doesn't output lines when no fields are selected. To **change that behavior** and **show empty lines**, pass the `-k` flag.
 32 
 33+### Raw output
 34+
 35+Don't output keys just values (emulate lcut like behavior), also outputs timestamp and level keys by default. With -o singleField allow getting one field
 36+
 37+### Output All
 38+
 39+Output all keys if flag is set and treat output option as an order which fields should be displayed first
 40+
 41 ### Sponsors
 42 
 43diff --git a/config/config.go b/config/config.go
 44index 53ceded5163f5cffeb47e7605a180aa9127dad92..1d745a5616b0383a469f9f3a04278b74c63b3dc3 100644
 45--- a/config/config.go
 46+++ b/config/config.go
 47@@ -29,17 +29,23 @@ type Config struct {
 48 	NoTime bool
 49 	// Keep lines with no fields
 50 	KeepEmpty bool
 51+	// Only values
 52+	Raw bool
 53+	// All after output in record order (causes output to become order)
 54+	All bool
 55 }
 56 
 57 type rawConfig struct {
 58-	LogLevel      		string `long:"level" short:"l" description:"Log level filter. One of DEBUG, INFO, WARN, ERROR, FATAL" default:"INFO"` // nolint:lll
 59-	OutputFields  		string `long:"output" short:"o" description:"Output field selector (comma separated)"`
 60-	ExcludeFields 		string `long:"exclude" short:"e" description:"Exclude field selector (comma separated)"`
 61-	Filter        		string `long:"filter" short:"f" description:"Filter fields (key=value comma separated)"`
 62-	NoColor       		bool   `long:"no-color" short:"n" description:"Disable color output"`
 63-	ForceColor    		bool   `long:"force-color" short:"c" description:"Force color output, even when outputting to a pipe"`
 64-	NoTime        		bool   `long:"no-time" short:"t" description:"Disable time output"`
 65-	KeepEmpty		bool   `long:"keep-empty" short:"k" description:"Keep lines with no field present selected by output or with all excluded"`
 66+	LogLevel      string `long:"level" short:"l" description:"Log level filter. One of DEBUG, INFO, WARN, ERROR, FATAL" default:"INFO"` // nolint:lll
 67+	OutputFields  string `long:"output" short:"o" description:"Output field selector (comma separated)"`
 68+	ExcludeFields string `long:"exclude" short:"e" description:"Exclude field selector (comma separated)"`
 69+	Filter        string `long:"filter" short:"f" description:"Filter fields (key=value comma separated)"`
 70+	NoColor       bool   `long:"no-color" short:"n" description:"Disable color output"`
 71+	ForceColor    bool   `long:"force-color" short:"c" description:"Force color output, even when outputting to a pipe"`
 72+	NoTime        bool   `long:"no-time" short:"t" description:"Disable time output"`
 73+	KeepEmpty     bool   `long:"keep-empty" short:"k" description:"Keep lines with no field present selected by output or with all excluded"`
 74+	Raw           bool   `long:"raw" short:"r" description:"Output only selected fields values (comma separated) lcut like"`
 75+	All           bool   `long:"all" short:"A" description:"Output all field after the output fields effectivly making it ordered"`
 76 }
 77 
 78 func Parse() (*Config, error) {
 79@@ -51,7 +57,7 @@ func Parse() (*Config, error) {
 80 		return nil, err
 81 	}
 82 
 83-	if raw.ExcludeFields != "" && raw.OutputFields != ""  {
 84+	if raw.ExcludeFields != "" && raw.OutputFields != "" {
 85 		return nil, fmt.Errorf("cannot use both --exclude and --output")
 86 	}
 87 
 88@@ -82,6 +88,12 @@ func Parse() (*Config, error) {
 89 	if raw.KeepEmpty {
 90 		cfg.KeepEmpty = true
 91 	}
 92+	if raw.Raw {
 93+		cfg.Raw = true
 94+	}
 95+	if raw.All {
 96+		cfg.All = true
 97+	}
 98 	return &cfg, nil
 99 }
100 
101diff --git a/parser/parser.go b/parser/parser.go
102index 90c6f266b825bba0d1618356f781444e59641b15..587ff88df12dc0a37db285e7d0122557bdbce6e1 100644
103--- a/parser/parser.go
104+++ b/parser/parser.go
105@@ -28,7 +28,7 @@ func NewParser(cfg *config.Config, input io.Reader, output io.Writer) *Parser {
106 func (p *Parser) Start() error {
107 	decoder := logfmt.NewDecoder(p.input)
108 	for decoder.ScanRecord() {
109-		record, err := NewRecord(decoder)
110+		record, err := NewRecord(decoder, p.cfg)
111 		if err != nil {
112 			return err
113 		}
114diff --git a/parser/record.go b/parser/record.go
115index 3b49fe427e8b80b4c2e10fb8d7f0f659d8f4ef73..f48dc63ca3685f9c4c3b97ed32f42d7ef3a93ce7 100644
116--- a/parser/record.go
117+++ b/parser/record.go
118@@ -44,7 +44,7 @@ type Record struct {
119 }
120 
121 // NewRecord parses a new Record from a logfmt.Decoder
122-func NewRecord(decoder *logfmt.Decoder) (*Record, error) {
123+func NewRecord(decoder *logfmt.Decoder, cfg *config.Config) (*Record, error) {
124 	var record Record
125 	record.fields = make(map[string]string)
126 	record.fieldOrder = make([]string, 0)
127@@ -57,14 +57,18 @@ func NewRecord(decoder *logfmt.Decoder) (*Record, error) {
128 		key, value := string(decoder.Key()), string(decoder.Value())
129 		if key == "level" {
130 			record.parseLevel(value)
131-			continue
132+			if !cfg.Raw {
133+				continue
134+			}
135 		}
136 		if slices.Contains(timestampLabels, key) {
137 			err := record.parseTime(value)
138 			if err != nil {
139 				return nil, err
140 			}
141-			continue
142+			if !cfg.Raw {
143+				continue
144+			}
145 		}
146 		record.fields[key] = value
147 		record.fieldOrder = append(record.fieldOrder, key)
148@@ -144,11 +148,22 @@ func (r *Record) MatchesFilter(filter map[string]string) bool {
149 func (r *Record) String(cfg *config.Config) string {
150 	line := ""
151 
152-	// If OutputFields is specified, iterate through it to determine order
153 	outFields := r.fieldOrder
154 	if len(cfg.OutputFields) > 0 {
155-		outFields = cfg.OutputFields
156+		if cfg.All {
157+			var reorderedFields []string = cfg.OutputFields
158+
159+			for _, key := range r.fieldOrder {
160+				if !slices.Contains(reorderedFields, key) {
161+					reorderedFields = append(reorderedFields, key)
162+				}
163+			}
164+			outFields = reorderedFields
165+		} else {
166+			outFields = cfg.OutputFields
167+		}
168 	}
169+
170 	for _, key := range outFields {
171 		if len(cfg.ExcludeFields) > 0 && slices.Contains(cfg.ExcludeFields, key) {
172 			continue
173@@ -158,13 +173,21 @@ func (r *Record) String(cfg *config.Config) string {
174 			continue
175 		}
176 		key := color.HiBlueString(key)
177-		line += fmt.Sprintf(" %s=%s", key, getFormattedValue(value))
178+		if cfg.Raw {
179+			line += fmt.Sprintf(" %s", value)
180+		} else {
181+			line += fmt.Sprintf(" %s=%s", key, getFormattedValue(value))
182+		}
183 	}
184 
185 	if line == "" && !cfg.KeepEmpty {
186 		return ""
187 	}
188 
189+	if cfg.Raw {
190+		return strings.TrimSpace(line)
191+	}
192+
193 	var fmtString strings.Builder
194 	if !cfg.NoTime {
195 		fmtString.WriteString("%s ")