Skip to content

Commit 8bbf37d

Browse files
Add --log-format flag
1 parent 903a15e commit 8bbf37d

6 files changed

Lines changed: 55 additions & 10 deletions

File tree

docs/cli-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ ember [flags]
2020
| `--expose` | string | _(none)_ | Start Prometheus metrics endpoint on this address (e.g. `:9191`). See [Prometheus Export](prometheus-export.md). |
2121
| `--daemon` | bool | `false` | Headless mode: no TUI. Requires `--expose`. See [Prometheus Export](prometheus-export.md). |
2222
| `--metrics-prefix` | string | _(none)_ | Prefix for exported Prometheus metric names. See [Prometheus Export](prometheus-export.md). |
23+
| `--log-format` | string | `text` | Log format for daemon/json modes (`text` or `json`). JSON format is suitable for log aggregation systems. |
2324
| `--no-color` | bool | `false` | Disable colors |
2425
| `--version` | | | Print version and exit |
2526

internal/app/daemon.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ package app
33
import (
44
"context"
55
"errors"
6-
"fmt"
76
"net/http"
8-
"os"
97
"time"
108

119
"github.com/alexandre-daubois/ember/internal/exporter"
@@ -34,16 +32,17 @@ func runDaemon(ctx context.Context, f fetcher.Fetcher, cfg *config) error {
3432

3533
go func() {
3634
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
37-
cancel(fmt.Errorf("metrics server: %w", err))
35+
cancel(err)
3836
}
3937
}()
4038

41-
fmt.Fprintf(os.Stderr, "ember daemon: exposing metrics on %s\n", metricsURL(cfg.expose))
39+
log := cfg.logger
40+
log.Info("daemon started", "metrics_url", metricsURL(cfg.expose))
4241

4342
poll := func() {
4443
snap, err := f.Fetch(ctx)
4544
if err != nil {
46-
fmt.Fprintf(os.Stderr, "error: %v\n", err)
45+
log.Error("fetch failed", "err", err)
4746
return
4847
}
4948
state.Update(snap)

internal/app/json.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package app
33
import (
44
"context"
55
"encoding/json"
6-
"fmt"
6+
"log/slog"
77
"os"
88
"time"
99

@@ -49,14 +49,14 @@ type jsonDerived struct {
4949
P99 *float64 `json:"p99,omitempty"`
5050
}
5151

52-
func runJSON(ctx context.Context, f fetcher.Fetcher, interval time.Duration, once bool) {
52+
func runJSON(ctx context.Context, f fetcher.Fetcher, interval time.Duration, once bool, log *slog.Logger) {
5353
enc := json.NewEncoder(os.Stdout)
5454
var state model.State
5555

5656
poll := func() {
5757
snap, err := f.Fetch(ctx)
5858
if err != nil {
59-
fmt.Fprintf(os.Stderr, "error: %v\n", err)
59+
log.Error("fetch failed", "err", err)
6060
return
6161
}
6262
state.Update(snap)

internal/app/json_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7+
"log/slog"
78
"net/http"
89
"net/http/httptest"
910
"os"
@@ -244,7 +245,7 @@ caddy_http_requests_total{host="test.com",code="200"} 100
244245
os.Stdout = w
245246

246247
ctx := context.Background()
247-
runJSON(ctx, f, time.Second, true)
248+
runJSON(ctx, f, time.Second, true, slog.New(slog.NewTextHandler(os.Stderr, nil)))
248249

249250
w.Close()
250251
os.Stdout = origStdout

internal/app/run.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package app
33
import (
44
"context"
55
"fmt"
6+
"log/slog"
67
"os"
78
"os/signal"
89
"syscall"
@@ -24,6 +25,8 @@ type config struct {
2425
expose string
2526
daemon bool
2627
metricsPrefix string
28+
logFormat string
29+
logger *slog.Logger
2730
}
2831

2932
func newRootCmd(version string) *cobra.Command {
@@ -61,6 +64,7 @@ Keybindings:
6164
SilenceUsage: true,
6265
SilenceErrors: true,
6366
PreRunE: func(cmd *cobra.Command, args []string) error {
67+
initLogger(&cfg)
6468
return validate(&cfg)
6569
},
6670
RunE: func(cmd *cobra.Command, args []string) error {
@@ -89,7 +93,7 @@ Keybindings:
8993

9094
switch {
9195
case cfg.jsonMode:
92-
runJSON(ctx, f, cfg.interval, cfg.once)
96+
runJSON(ctx, f, cfg.interval, cfg.once, cfg.logger)
9397
case cfg.daemon:
9498
return runDaemon(ctx, f, &cfg)
9599
default:
@@ -113,6 +117,7 @@ Keybindings:
113117
f.StringVar(&cfg.expose, "expose", "", "Expose Prometheus metrics (e.g. :9191)")
114118
f.BoolVar(&cfg.daemon, "daemon", false, "Headless mode (requires --expose)")
115119
f.StringVar(&cfg.metricsPrefix, "metrics-prefix", "", "Prefix for exported Prometheus metric names")
120+
f.StringVar(&cfg.logFormat, "log-format", "text", "Log format for daemon/json modes (text or json)")
116121

117122
cmd.AddCommand(newStatusCmd(&cfg))
118123
cmd.AddCommand(newWaitCmd(&cfg))
@@ -135,6 +140,15 @@ func contextWithTimeout(parent context.Context, timeout time.Duration) (context.
135140
return parent, func() {}
136141
}
137142

143+
func initLogger(cfg *config) {
144+
switch cfg.logFormat {
145+
case "json":
146+
cfg.logger = slog.New(slog.NewJSONHandler(os.Stderr, nil))
147+
default:
148+
cfg.logger = slog.New(slog.NewTextHandler(os.Stderr, nil))
149+
}
150+
}
151+
138152
func validate(cfg *config) error {
139153
if cfg.daemon && cfg.expose == "" {
140154
return fmt.Errorf("--daemon requires --expose")

internal/app/run_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,36 @@ func TestRun_TimeoutInheritedBySubcommands(t *testing.T) {
168168
assert.Contains(t, buf.String(), "--timeout")
169169
}
170170

171+
func TestInitLogger_Text(t *testing.T) {
172+
cfg := &config{logFormat: "text"}
173+
initLogger(cfg)
174+
assert.NotNil(t, cfg.logger)
175+
}
176+
177+
func TestInitLogger_JSON(t *testing.T) {
178+
cfg := &config{logFormat: "json"}
179+
initLogger(cfg)
180+
assert.NotNil(t, cfg.logger)
181+
}
182+
183+
func TestInitLogger_DefaultIsText(t *testing.T) {
184+
cfg := &config{}
185+
initLogger(cfg)
186+
assert.NotNil(t, cfg.logger)
187+
}
188+
189+
func TestRun_LogFormatFlagAvailable(t *testing.T) {
190+
cmd := newRootCmd("1.0.0")
191+
cmd.SetArgs([]string{"--help"})
192+
var buf bytes.Buffer
193+
cmd.SetOut(&buf)
194+
195+
err := cmd.Execute()
196+
197+
assert.NoError(t, err)
198+
assert.Contains(t, buf.String(), "--log-format")
199+
}
200+
171201
func TestRun_HelpContainsKeybindings(t *testing.T) {
172202
cmd := newRootCmd("1.0.0")
173203
cmd.SetArgs([]string{"--help"})

0 commit comments

Comments
 (0)