Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ dist/
# JetBrains IDE
.idea/

# VS Code
.vscode/

# Unit test reports
TEST*.xml

Expand All @@ -65,3 +68,6 @@ Thumbs.db
*.mov
*.wmv


# Snyk Security Extension - AI Rules (auto-generated)
.github/instructions/snyk_rules.instructions.md
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ brew install gildas/tap/lv

### Windows

You can (soon) install `lv` with [chocolatey](https://chocolatey.org):
You can install `lv` with [chocolatey](https://chocolatey.org):

```bash
choco install bunyan-logviewer
Expand Down Expand Up @@ -111,6 +111,7 @@ Here is a list of the flags you can use with `lv`:
-f, --filter string Run each log message through the filter.
-h, --help help for lv
--level string Only shows log entries with a level at or above the given value.
-k, --key string Use the given key to decrypt obfuscated log entries.
-L, --local Display time field in local time, rather than UTC.
--log string where lv's logs are writen if given (by default, no log is generated)
--no-color Do not colorize output. By default, the output is colorized if stdout is a TTY
Expand All @@ -122,13 +123,16 @@ Here is a list of the flags you can use with `lv`:
--version version for lv
```

The key must be 16, 24, or 32 bytes long.

### Environment Variables

`lv` uses the following environment variables:

- `LOGVIEWER_COLOR` to force colorization of the output
- `LOGVIEWER_LOCAL` to display the time in local time
- `LOGVIEWER_TIMEZONE` to display the time in a specific timezone
- `LV_COLOR` to force colorization of the output
- `LV_LOCAL` to display the time in local time
- `LV_TIMEZONE` to display the time in a specific timezone
- `LV_OBFUSCATIONKEY` to specify the key used to decrypt obfuscated log entries

The command line flags have precedence over the environment variables.

Expand All @@ -142,6 +146,7 @@ Here is an example of a configuration file:
color: true
local: true
output: short
obfuscationKey: 1231213
```

The environment variables and the command line flags have precedence over the configuration file.
Expand Down
6 changes: 5 additions & 1 deletion cmd/log_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ func (entry LogEntry) Write(context context.Context, output io.Writer, options *
entry.writeHeader(output, options)
entry.writeString(output, options, ": ")
entry.writeTopicAndScope(output, options)
entry.writeStringWithColor(output, options, entry.Message, Cyan)
message, err := log.Unobfuscate(entry.Message)
if err != nil {
log.Errorf("Failed to Unobfuscate message (%s)", entry.Message, err)
Comment thread
gildas marked this conversation as resolved.
}
entry.writeStringWithColor(output, options, message, Cyan)

log.Debugf("Fields: %v", entry.Fields)
entry.writeString(output, options, " (")
Expand Down
25 changes: 19 additions & 6 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"bufio"
"context"
"crypto/aes"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -32,6 +33,7 @@ var CmdOptions struct {
OutputOptions
Completion *flags.EnumFlag
ConfigFile string
CipherKey string
LogDestination string
Timezone string
UsePager bool
Expand Down Expand Up @@ -62,6 +64,7 @@ func init() {
RootCmd.PersistentFlags().StringVar(&CmdOptions.LogLevel, "level", "", "Only shows log entries with a level at or above the given value.")
RootCmd.PersistentFlags().StringVarP(&CmdOptions.Filter, "filter", "f", "", "Run each log message through the filter.")
RootCmd.PersistentFlags().StringVarP(&CmdOptions.Filter, "condition", "c", "", "Run each log message through the filter.")
RootCmd.PersistentFlags().StringVarP(&CmdOptions.CipherKey, "key", "k", "", "Use the given key to decrypt obfuscated log entries. The key must be 16, 24, or 32 bytes long.")
RootCmd.PersistentFlags().BoolP("local", "L", false, "Display time field in local time, rather than UTC.")
RootCmd.PersistentFlags().StringVar(&CmdOptions.Timezone, "time", "", "Display time field in the given timezone.")
RootCmd.PersistentFlags().BoolVar(&CmdOptions.UsePager, "no-pager", true, "Do not pipe output into a pager. By default, the output is piped throug `less` (or $PAGER if set), if stdout is a TTY")
Expand All @@ -79,6 +82,7 @@ func init() {
_ = viper.BindPFlag("timezone", RootCmd.PersistentFlags().Lookup("time"))
_ = viper.BindPFlag("output", RootCmd.PersistentFlags().Lookup("output"))
_ = viper.BindPFlag("color", RootCmd.PersistentFlags().Lookup("color"))
_ = viper.BindPFlag("obfuscationKey", RootCmd.PersistentFlags().Lookup("key"))
viper.SetDefault("local", false)
viper.SetDefault("timezone", "UTC")
viper.SetDefault("output", "long")
Expand Down Expand Up @@ -117,8 +121,8 @@ func initConfig() {
viper.SetConfigName(".logviewer")
}

viper.SetEnvPrefix("LV_")
_ = viper.BindEnv("local")
viper.SetEnvPrefix("LV")
_ = viper.BindEnv("local", "obfuscationKey")
viper.AutomaticEnv() // read in environment variables that match

err := viper.ReadInConfig()
Expand Down Expand Up @@ -153,6 +157,15 @@ func runRootCommand(cmd *cobra.Command, args []string) (err error) {
}
CmdOptions.Output.Value = viper.GetString("output")

if len(viper.GetString("obfuscationKey")) > 0 {
cipherBlock, err := aes.NewCipher([]byte(viper.GetString("obfuscationKey")))
if err != nil {
log.Fatalf("Failed to create cipher block: %s", err)
return err
}
log.SetObfuscationKey(cipherBlock)
}

if viper.GetBool("local") {
log.Infof("Displaying local time")
CmdOptions.Location = time.Local
Expand All @@ -170,7 +183,7 @@ func runRootCommand(cmd *cobra.Command, args []string) (err error) {
log.Fatalf("Failed to open file %s: %s", args[0], err)
return err
}
defer file.Close()
defer func() { _ = file.Close() }()
reader = bufio.NewReader(file)
}

Expand Down Expand Up @@ -202,7 +215,7 @@ func runRootCommand(cmd *cobra.Command, args []string) (err error) {
filters.Add(filter)
log.Infof("Added Filter: %#v", filter)
}
var filter LogFilter = filters.AsFilter()
var filter = filters.AsFilter()

for {
var line []byte
Expand All @@ -223,15 +236,15 @@ func runRootCommand(cmd *cobra.Command, args []string) (err error) {

if err := json.Unmarshal(line, &entry); err != nil {
log.Errorf("Failed to parse JSON: %s", err)
fmt.Fprintln(outstream, string(line))
_, _ = fmt.Fprintln(outstream, string(line))
continue
}
if filter.Filter(cmd.Context(), entry) {
output := strings.Builder{}

entry.Write(cmd.Context(), &output, &CmdOptions.OutputOptions)
if output.Len() > 0 {
fmt.Fprintln(outstream, output.String())
_, _ = fmt.Fprintln(outstream, output.String())
}
}
}
Expand Down
86 changes: 42 additions & 44 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,63 +1,61 @@
module github.com/gildas/lv

go 1.23.0

toolchain go1.24.2
go 1.25.7

require (
github.com/gildas/go-core v0.6.1
github.com/gildas/go-core v0.6.4
github.com/gildas/go-errors v0.4.0
github.com/gildas/go-flags v0.4.0
github.com/gildas/go-logger v1.8.0
github.com/gildas/go-flags v0.4.1
github.com/gildas/go-logger v1.9.1
github.com/joho/godotenv v1.5.1
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.20.1
golang.org/x/term v0.32.0
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
golang.org/x/term v0.40.0
)

require (
cloud.google.com/go v0.121.0 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go v0.123.0 // indirect
cloud.google.com/go/auth v0.18.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/logging v1.13.0 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/logging v1.13.2 // indirect
cloud.google.com/go/longrunning v0.8.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.8.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/api v0.232.0 // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20260209203927-2842357ff358 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/api v0.266.0 // indirect
google.golang.org/genproto v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
Loading