diff --git a/logging/config/presets/instructions.go b/logging/config/presets/instructions.go index 9077d9e8242dc19423e5f1faf913ca97a9e650ec..91065d2faa9effbfc8bf796175b7fa7eb691deae 100644 --- a/logging/config/presets/instructions.go +++ b/logging/config/presets/instructions.go @@ -13,13 +13,14 @@ import ( // Returning the new subroot from which to apply any further Presets. // When chained together in a pre-order instructions can be composed to form an entire Sink tree. type Instruction struct { - name string - desc string + name string + desc string + nargs int // The builder for the Instruction is a function that may modify the stack or ops string. Typically // by mutating the sink at the top of the stack and may move the cursor or by pushing child sinks // to the stack. The builder may also return a modified ops slice whereby it may insert Instruction calls // acting as a macro or consume ops as arguments. - builder func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) + builder func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) } func (i Instruction) Name() string { @@ -37,88 +38,113 @@ const ( Stderr = "stderr" Stdout = "stdout" Terminal = "terminal" + JSON = "json" Up = "up" Down = "down" + File = "file" ) var instructions = []Instruction{ { name: Up, desc: "Ascend the sink tree by travelling up the stack to the previous sink recorded on the stack", - builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { - return pop(stack), ops, nil + builder: func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) { + return pop(stack), nil }, }, { name: Down, desc: "Descend the sink tree by inserting a sink as a child to the current sink and adding it to the stack", - builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { - return push(stack, config.Sink()), ops, nil + builder: func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) { + return push(stack, config.Sink()), nil }, }, { name: Minimal, desc: "A generally less chatty log output, follow with output options", - builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + builder: func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) { return push(stack, - config.Sink().SetTransform(config.PruneTransform(structure.TraceKey, structure.RunId)), - config.Sink().SetTransform(config.FilterTransform(config.IncludeWhenAllMatch, - structure.ChannelKey, structure.InfoChannelName)), - config.Sink().SetTransform(config.FilterTransform(config.ExcludeWhenAnyMatches, - structure.ComponentKey, "Tendermint", - "module", "p2p", - "module", "mempool"))), - ops, nil + config.Sink().SetTransform(config.PruneTransform(structure.TraceKey, structure.RunId)), + config.Sink().SetTransform(config.FilterTransform(config.IncludeWhenAllMatch, + structure.ChannelKey, structure.InfoChannelName)), + config.Sink().SetTransform(config.FilterTransform(config.ExcludeWhenAnyMatches, + structure.ComponentKey, "Tendermint", + "module", "p2p", + "module", "mempool"))), nil }, }, { name: IncludeAny, desc: "Establish an 'include when any predicate matches' filter transform at this this sink", - builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + builder: func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) { sink := peek(stack) ensureFilter(sink) sink.Transform.FilterConfig.FilterMode = config.IncludeWhenAnyMatches - return stack, ops, nil + return stack, nil }, }, { name: Info, desc: "Add a filter predicate to match the Info logging channel", - builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + builder: func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) { sink := peek(stack) ensureFilter(sink) sink.Transform.FilterConfig.AddPredicate(structure.ChannelKey, structure.InfoChannelName) - return stack, ops, nil + return stack, nil }, }, { name: Stdout, desc: "Use Stdout output for this sink", - builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + builder: func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) { sink := peek(stack) ensureOutput(sink) sink.Output.OutputType = config.Stdout - return stack, ops, nil + return stack, nil }, }, { name: Stderr, desc: "Use Stderr output for this sink", - builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + builder: func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) { sink := peek(stack) ensureOutput(sink) sink.Output.OutputType = config.Stderr - return stack, ops, nil + return stack, nil }, }, { name: Terminal, desc: "Use the the terminal output format for this sink", - builder: func(stack []*config.SinkConfig, ops []string) ([]*config.SinkConfig, []string, error) { + builder: func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) { sink := peek(stack) ensureOutput(sink) sink.Output.Format = loggers.TerminalFormat - return stack, ops, nil + return stack, nil + }, + }, + { + name: JSON, + desc: "Use the the terminal output format for this sink", + builder: func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) { + sink := peek(stack) + ensureOutput(sink) + sink.Output.Format = loggers.JSONFormat + return stack, nil + }, + }, + { + name: File, + desc: "Use the the terminal output format for this sink", + nargs: 1, + builder: func(stack []*config.SinkConfig, args []string) ([]*config.SinkConfig, error) { + sink := peek(stack) + ensureOutput(sink) + sink.Output.OutputType = config.File + sink.Output.FileConfig = &config.FileConfig{ + Path: args[0], + } + return stack, nil }, }, } @@ -149,16 +175,26 @@ func Describe(name string) string { func BuildSinkConfig(ops ...string) (*config.SinkConfig, error) { stack := []*config.SinkConfig{config.Sink()} var err error + pos := 0 for len(ops) > 0 { // Keep applying instructions until their are no ops left - preset, ok := instructionsMap[ops[0]] + instruction, ok := instructionsMap[ops[0]] if !ok { return nil, fmt.Errorf("could not find logging preset '%s'", ops[0]) } - stack, ops, err = preset.builder(stack, ops[1:]) + // pop instruction name + ops = ops[1:] + if len(ops) < instruction.nargs { + return nil, fmt.Errorf("did not have enough arguments for instruction %s at position %v "+ + "(requires %v arguments)", instruction.name, pos, instruction.nargs) + } + stack, err = instruction.builder(stack, ops[:instruction.nargs]) if err != nil { return nil, err } + // pop instruction args + ops = ops[instruction.nargs:] + pos++ } return stack[0], nil } diff --git a/logging/config/presets/instructions_test.go b/logging/config/presets/instructions_test.go index adbfe1b0a5fd4bc6863c372c4a61a60a33f0f25c..c8398cc5c76183f09ae40f6480ff738272ae2df7 100644 --- a/logging/config/presets/instructions_test.go +++ b/logging/config/presets/instructions_test.go @@ -39,3 +39,13 @@ func TestMinimalPreset(t *testing.T) { //fmt.Println(config.TOMLString(expectedSink), "\n", config.TOMLString(builtSink)) assert.Equal(t, config.TOMLString(expectedSink), config.TOMLString(builtSink)) } + +func TestFileOutput(t *testing.T) { + path := "foo.log" + builtSink, err := BuildSinkConfig(Down, File, path, JSON) + require.NoError(t, err) + expectedSink := config.Sink(). + AddSinks(config.Sink().SetOutput(config.FileOutput(path).SetFormat(loggers.JSONFormat))) + //fmt.Println(config.TOMLString(expectedSink), "\n", config.TOMLString(builtSink)) + assert.Equal(t, config.TOMLString(expectedSink), config.TOMLString(builtSink)) +}