From df532a5f4487e42ffc796a3688943576d0e866a5 Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Mon, 13 May 2024 14:29:42 -0300 Subject: [PATCH 01/16] added log level through commnd line flag --- cli/main.go | 5 +++++ instrumentation.go | 40 ++++++++++++++++++++++++++++++---------- instrumentation_test.go | 8 ++++++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/cli/main.go b/cli/main.go index fc2e93905..e7f57eb8c 100644 --- a/cli/main.go +++ b/cli/main.go @@ -68,8 +68,11 @@ func newLogger() logr.Logger { func main() { var globalImpl bool + var logLevel string flag.BoolVar(&globalImpl, "global-impl", false, "Record telemetry from the OpenTelemetry default global implementation") + flag.StringVar(&logLevel, "log-level", "info", "Define log visibility level") + flag.Usage = usage flag.Parse() @@ -99,6 +102,8 @@ func main() { instOptions = append(instOptions, auto.WithGlobal()) } + instOptions = append(instOptions, auto.WithLogLevel(logLevel)) + inst, err := auto.NewInstrumentation(ctx, instOptions...) if err != nil { logger.Error(err, "failed to create instrumentation") diff --git a/instrumentation.go b/instrumentation.go index 1b10cc4ae..a5e9fc625 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -72,8 +72,17 @@ type Instrumentation struct { // binary or pid. var errUndefinedTarget = fmt.Errorf("undefined target Go binary, consider setting the %s environment variable pointing to the target binary to instrument", envTargetExeKey) -func newLogger() logr.Logger { - zapLog, err := zap.NewProduction() +func newLogger(logLevel string) logr.Logger { + level, err := zap.ParseAtomicLevel(logLevel) + if err != nil { + level, _ = zap.ParseAtomicLevel(zap.InfoLevel.String()) + } + + config := zap.NewProductionConfig() + + config.Level.SetLevel(level.Level()) + + zapLog, err := config.Build() var logger logr.Logger if err != nil { @@ -92,14 +101,6 @@ func newLogger() logr.Logger { // If conflicting or duplicate options are provided, the last one will have // precedence and be used. func NewInstrumentation(ctx context.Context, opts ...InstrumentationOption) (*Instrumentation, error) { - // TODO: pass this in as an option. - // - // We likely want to use slog instead of logr in the longterm. Wait until - // that package has enough Go version support and then switch to that so we - // can expose it in an option. - logger := newLogger() - logger = logger.WithName("Instrumentation") - c, err := newInstConfig(ctx, opts) if err != nil { return nil, err @@ -108,6 +109,11 @@ func NewInstrumentation(ctx context.Context, opts ...InstrumentationOption) (*In return nil, err } + // We likely want to use slog instead of logr in the longterm. Wait until + // that package has enough Go version support + logger := newLogger(c.logLevel) + logger = logger.WithName("Instrumentation") + pa := process.NewAnalyzer(logger) pid, err := pa.DiscoverProcessID(ctx, &c.target) if err != nil { @@ -179,6 +185,7 @@ type instConfig struct { additionalResAttrs []attribute.KeyValue globalImpl bool loadIndicator chan struct{} + logLevel string } func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfig, error) { @@ -209,6 +216,10 @@ func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfi c.sampler = trace.AlwaysSample() } + if c.logLevel == "" { + c.logLevel = "info" + } + return c, err } @@ -490,3 +501,12 @@ func WithLoadedIndicator(indicator chan struct{}) InstrumentationOption { return c, nil }) } + +// WithLogLevel returns an [InstrumentationOption] that will configure +// an [Instrumentation] with the logger level visibility defined as inputed. +func WithLogLevel(level string) InstrumentationOption { + return fnOpt(func(ctx context.Context, c instConfig) (instConfig, error) { + c.logLevel = level + return c, nil + }) +} diff --git a/instrumentation_test.go b/instrumentation_test.go index d638af53b..d8c7f5090 100644 --- a/instrumentation_test.go +++ b/instrumentation_test.go @@ -172,6 +172,14 @@ func TestWithResourceAttributes(t *testing.T) { }) } +func TestWithLogLevel(t *testing.T) { + c, err := newInstConfig(context.Background(), []InstrumentationOption{WithLogLevel("error")}) + + require.NoError(t, err) + + assert.Equal(t, "error", c.logLevel) +} + func mockEnv(t *testing.T, env map[string]string) { orig := lookupEnv t.Cleanup(func() { lookupEnv = orig }) From eddaf8466035ee1f8b8e7b0428fec09a5f4f4977 Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Mon, 13 May 2024 17:03:58 -0300 Subject: [PATCH 02/16] added changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ee1f674..b7ae8011a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http - Initial support for `trace-flags`. ([#868](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/868)) - Support `google.golang.org/grpc` `1.66.0-dev`. ([#872](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/872)) +- Add support to log level through command line flag. ([#842](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/842)) ### Fixed From 5853576cde3c8e56ca6813d0726f895d7822636f Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Wed, 15 May 2024 16:40:41 -0300 Subject: [PATCH 03/16] implemented own level enum and add err when not found the inputed log level --- instrumentation.go | 25 ++++++++-- instrumentation_test.go | 30 ++++++++++-- internal/pkg/log/level.go | 83 ++++++++++++++++++++++++++++++++++ internal/pkg/log/level_test.go | 58 ++++++++++++++++++++++++ 4 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 internal/pkg/log/level.go create mode 100644 internal/pkg/log/level_test.go diff --git a/instrumentation.go b/instrumentation.go index a5e9fc625..bcb943ce2 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -39,6 +39,7 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.opentelemetry.io/auto/internal/pkg/instrumentation" + internalLog "go.opentelemetry.io/auto/internal/pkg/log" "go.opentelemetry.io/auto/internal/pkg/opentelemetry" "go.opentelemetry.io/auto/internal/pkg/process" ) @@ -58,6 +59,8 @@ const ( // envOtelGlobalImplKey is the key for the environment variable value enabling to opt-in for the // OpenTelemetry global implementation. It should be a boolean value. envOtelGlobalImplKey = "OTEL_GO_AUTO_GLOBAL" + // envLogLevelKey is the key for the environment variable value containing the log level. + envLogLevelKey = "OTEL_LOG_LEVEL" ) // Instrumentation manages and controls all OpenTelemetry Go @@ -72,10 +75,11 @@ type Instrumentation struct { // binary or pid. var errUndefinedTarget = fmt.Errorf("undefined target Go binary, consider setting the %s environment variable pointing to the target binary to instrument", envTargetExeKey) -func newLogger(logLevel string) logr.Logger { - level, err := zap.ParseAtomicLevel(logLevel) +func newLogger(logLevel internalLog.Level) logr.Logger { + level, err := zap.ParseAtomicLevel(logLevel.String()) + if err != nil { - level, _ = zap.ParseAtomicLevel(zap.InfoLevel.String()) + level, _ = zap.ParseAtomicLevel(internalLog.LevelInfo.String()) } config := zap.NewProductionConfig() @@ -185,7 +189,7 @@ type instConfig struct { additionalResAttrs []attribute.KeyValue globalImpl bool loadIndicator chan struct{} - logLevel string + logLevel internalLog.Level } func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfig, error) { @@ -394,6 +398,11 @@ func WithEnv() InstrumentationOption { c.globalImpl = boolVal } } + if l, ok := lookupEnv(envLogLevelKey); ok { + var e error + c.logLevel, e = internalLog.ParseLevel(l) + err = errors.Join(err, e) + } return c, err }) } @@ -506,7 +515,13 @@ func WithLoadedIndicator(indicator chan struct{}) InstrumentationOption { // an [Instrumentation] with the logger level visibility defined as inputed. func WithLogLevel(level string) InstrumentationOption { return fnOpt(func(ctx context.Context, c instConfig) (instConfig, error) { - c.logLevel = level + l, err := internalLog.ParseLevel(level) + if err != nil { + return c, err + } + + c.logLevel = l + return c, nil }) } diff --git a/instrumentation_test.go b/instrumentation_test.go index d8c7f5090..ed766af11 100644 --- a/instrumentation_test.go +++ b/instrumentation_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/auto/internal/pkg/log" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) @@ -82,6 +83,21 @@ func TestWithEnv(t *testing.T) { require.NoError(t, err) assert.Equal(t, name, c.serviceName) }) + + t.Run("OTEL_LOG_LEVEL", func(t *testing.T) { + const name = "debug" + mockEnv(t, map[string]string{"OTEL_LOG_LEVEL": name}) + + c, err := newInstConfig(context.Background(), []InstrumentationOption{WithEnv()}) + require.NoError(t, err) + assert.Equal(t, log.LevelDebug, c.logLevel) + + const wrong = "invalid" + + mockEnv(t, map[string]string{"OTEL_LOG_LEVEL": wrong}) + _, err = newInstConfig(context.Background(), []InstrumentationOption{WithEnv()}) + require.Error(t, err) + }) } func TestOptionPrecedence(t *testing.T) { @@ -173,11 +189,19 @@ func TestWithResourceAttributes(t *testing.T) { } func TestWithLogLevel(t *testing.T) { - c, err := newInstConfig(context.Background(), []InstrumentationOption{WithLogLevel("error")}) + t.Run("With Valid Input", func(t *testing.T) { + c, err := newInstConfig(context.Background(), []InstrumentationOption{WithLogLevel("error")}) - require.NoError(t, err) + require.NoError(t, err) + + assert.Equal(t, log.LevelError, c.logLevel) + }) + + t.Run("Will Validate Input", func(t *testing.T) { + _, err := newInstConfig(context.Background(), []InstrumentationOption{WithLogLevel("invalid")}) - assert.Equal(t, "error", c.logLevel) + require.Error(t, err) + }) } func mockEnv(t *testing.T, env map[string]string) { diff --git a/internal/pkg/log/level.go b/internal/pkg/log/level.go new file mode 100644 index 000000000..cd4ba6f52 --- /dev/null +++ b/internal/pkg/log/level.go @@ -0,0 +1,83 @@ +package log + +import ( + "bytes" + "errors" + "fmt" +) + +type Level string + +const ( + LevelDebug Level = "debug" + LevelInfo Level = "info" + LevelWarn Level = "warn" + LevelError Level = "error" + LevelDPanic Level = "dpanic" + LevelPanic Level = "panic" + LevelFatal Level = "fatal" +) + +func (l Level) String() string { + switch l { + case LevelDebug: + return "debug" + case LevelInfo: + return "info" + case LevelWarn: + return "warn" + case LevelError: + return "error" + case LevelDPanic: + return "dpanic" + case LevelPanic: + return "panic" + case LevelFatal: + return "fatal" + default: + return fmt.Sprintf("Level(%s)", string(l)) + } +} + +func (l *Level) UnmarshalText(text []byte) error { + if l == nil { + return errors.New("can't unmarshal nil values") + } + + if !l.unmarshalText(bytes.ToLower(text)) { + return fmt.Errorf("") + } + + return nil +} + +func (l *Level) unmarshalText(text []byte) bool { + switch string(text) { + case "debug": + *l = LevelDebug + case "info": + *l = LevelInfo + case "warn": + *l = LevelWarn + case "error": + *l = LevelError + case "dpanic": + *l = LevelDPanic + case "panic": + *l = LevelPanic + case "fatal": + *l = LevelFatal + default: + return false + } + + return true +} + +func ParseLevel(text string) (Level, error) { + var level Level + + err := level.UnmarshalText([]byte(text)) + + return level, err +} diff --git a/internal/pkg/log/level_test.go b/internal/pkg/log/level_test.go new file mode 100644 index 000000000..ddea55e34 --- /dev/null +++ b/internal/pkg/log/level_test.go @@ -0,0 +1,58 @@ +package log_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/auto/internal/pkg/log" +) + +func TestLevel(t *testing.T) { + testCases := []struct { + name string + level log.Level + str string + }{ + { + name: "LevelDebug", + level: log.LevelDebug, + str: "debug", + }, + { + name: "LevelInfo", + level: log.LevelInfo, + str: "info", + }, + { + name: "LevelWarn", + level: log.LevelWarn, + str: "warn", + }, + { + name: "LevelError", + level: log.LevelError, + str: "error", + }, + { + name: "LevelDPanic", + level: log.LevelDPanic, + str: "dpanic", + }, + { + name: "LevelPanic", + level: log.LevelPanic, + str: "panic", + }, + { + name: "LevelFatal", + level: log.LevelFatal, + str: "fatal", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.str, tc.level.String(), "string does not match") + }) + } +} From 209f23f6ae364212b000bbb76a48a0eb49ba182f Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Wed, 15 May 2024 16:41:24 -0300 Subject: [PATCH 04/16] changed log visibility to debug on some info logs --- internal/pkg/instrumentation/manager.go | 16 ++++++++-------- internal/pkg/instrumentation/probe/probe.go | 6 +++--- internal/pkg/opentelemetry/controller.go | 4 ++-- internal/pkg/process/allocate.go | 8 ++++---- internal/pkg/process/analyze.go | 4 ++-- internal/pkg/process/discover.go | 6 +++--- internal/pkg/process/ptrace/ptrace_linux.go | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/internal/pkg/instrumentation/manager.go b/internal/pkg/instrumentation/manager.go index aae99dbc1..9329fcb96 100644 --- a/internal/pkg/instrumentation/manager.go +++ b/internal/pkg/instrumentation/manager.go @@ -137,7 +137,7 @@ func (m *Manager) FilterUnusedProbes(target *process.TargetDetails) { } if !funcsFound { - m.logger.Info("no functions found for probe, removing", "name", name) + m.logger.V(-1).Info("no functions found for probe, removing", "name", name) delete(m.probes, name) } } @@ -172,13 +172,13 @@ func (m *Manager) Run(ctx context.Context, target *process.TargetDetails) error for { select { case <-ctx.Done(): - m.logger.Info("shutting down all probes due to context cancellation") + m.logger.V(-1).Info("shutting down all probes due to context cancellation") err := m.cleanup(target) err = errors.Join(err, ctx.Err()) m.closingErrors <- err return nil case <-m.done: - m.logger.Info("shutting down all probes due to signal") + m.logger.V(-1).Info("shutting down all probes due to signal") err := m.cleanup(target) m.closingErrors <- err return nil @@ -205,7 +205,7 @@ func (m *Manager) load(target *process.TargetDetails) error { // Load probes for name, i := range m.probes { - m.logger.Info("loading probe", "name", name) + m.logger.V(-1).Info("loading probe", "name", name) err := i.Load(exe, target) if err != nil { m.logger.Error(err, "error while loading probes, cleaning up", "name", name) @@ -213,15 +213,15 @@ func (m *Manager) load(target *process.TargetDetails) error { } } - m.logger.Info("loaded probes to memory", "total_probes", len(m.probes)) + m.logger.V(-1).Info("loaded probes to memory", "total_probes", len(m.probes)) return nil } func (m *Manager) mount(target *process.TargetDetails) error { if target.AllocationDetails != nil { - m.logger.Info("Mounting bpffs", "allocations_details", target.AllocationDetails) + m.logger.V(-1).Info("Mounting bpffs", "allocations_details", target.AllocationDetails) } else { - m.logger.Info("Mounting bpffs") + m.logger.V(-1).Info("Mounting bpffs") } return bpffs.Mount(target) } @@ -233,7 +233,7 @@ func (m *Manager) cleanup(target *process.TargetDetails) error { err = errors.Join(err, i.Close()) } - m.logger.Info("Cleaning bpffs") + m.logger.V(-1).Info("Cleaning bpffs") return errors.Join(err, bpffs.Cleanup(target)) } diff --git a/internal/pkg/instrumentation/probe/probe.go b/internal/pkg/instrumentation/probe/probe.go index ee04dcc3c..fa7c15481 100644 --- a/internal/pkg/instrumentation/probe/probe.go +++ b/internal/pkg/instrumentation/probe/probe.go @@ -149,7 +149,7 @@ func (i *Base[BPFObj, BPFEvent]) buildObj(exec *link.Executable, td *process.Tar links, err := up.Fn(up.Sym, exec, td, obj) if err != nil { if up.Optional { - i.Logger.Info("failed to attach optional uprobe", "probe", i.ID, "symbol", up.Sym, "error", err) + i.Logger.V(-1).Info("failed to attach optional uprobe", "probe", i.ID, "symbol", up.Sym, "error", err) continue } return nil, err @@ -175,7 +175,7 @@ func (i *Base[BPFObj, BPFEvent]) Run(dest chan<- *Event) { } if record.LostSamples != 0 { - i.Logger.Info("perf event ring buffer full", "dropped", record.LostSamples) + i.Logger.V(-1).Info("perf event ring buffer full", "dropped", record.LostSamples) continue } @@ -211,7 +211,7 @@ func (i *Base[BPFObj, BPFEvent]) Close() error { err = errors.Join(err, c.Close()) } if err == nil { - i.Logger.Info("Closed", "Probe", i.ID) + i.Logger.V(-1).Info("Closed", "Probe", i.ID) } return err } diff --git a/internal/pkg/opentelemetry/controller.go b/internal/pkg/opentelemetry/controller.go index da678c955..469c1c8dc 100644 --- a/internal/pkg/opentelemetry/controller.go +++ b/internal/pkg/opentelemetry/controller.go @@ -52,11 +52,11 @@ func (c *Controller) getTracer(pkg string) trace.Tracer { // Trace creates a trace span for event. func (c *Controller) Trace(event *probe.Event) { for _, se := range event.SpanEvents { - c.logger.Info("got event", "kind", event.Kind.String(), "pkg", event.Package, "attrs", se.Attributes, "traceID", se.SpanContext.TraceID().String(), "spanID", se.SpanContext.SpanID().String()) + c.logger.V(-1).Info("got event", "kind", event.Kind.String(), "pkg", event.Package, "attrs", se.Attributes, "traceID", se.SpanContext.TraceID().String(), "spanID", se.SpanContext.SpanID().String()) ctx := context.Background() if se.SpanContext == nil { - c.logger.Info("got event without context - dropping") + c.logger.V(-1).Info("got event without context - dropping") return } diff --git a/internal/pkg/process/allocate.go b/internal/pkg/process/allocate.go index 5697ec86e..821d54a16 100644 --- a/internal/pkg/process/allocate.go +++ b/internal/pkg/process/allocate.go @@ -48,7 +48,7 @@ func Allocate(logger logr.Logger, pid int) (*AllocationDetails, error) { } mapSize := uint64(os.Getpagesize() * nCPU * 8) - logger.Info( + logger.V(-1).Info( "Requesting memory allocation", "size", mapSize, "page size", os.Getpagesize(), @@ -59,7 +59,7 @@ func Allocate(logger logr.Logger, pid int) (*AllocationDetails, error) { return nil, err } - logger.Info( + logger.V(-1).Info( "mmaped remote memory", "start_addr", fmt.Sprintf("0x%x", addr), "end_addr", fmt.Sprintf("0x%x", addr+mapSize), @@ -81,7 +81,7 @@ func remoteAllocate(logger logr.Logger, pid int, mapSize uint64) (uint64, error) } defer func() { - logger.Info("Detaching from process", "pid", pid) + logger.V(-1).Info("Detaching from process", "pid", pid) err := program.Detach() if err != nil { logger.Error(err, "Failed to detach ptrace", "pid", pid) @@ -91,7 +91,7 @@ func remoteAllocate(logger logr.Logger, pid int, mapSize uint64) (uint64, error) if err := program.SetMemLockInfinity(); err != nil { logger.Error(err, "Failed to set memlock on process") } else { - logger.Info("Set memlock on process successfully") + logger.V(-1).Info("Set memlock on process successfully") } fd := -1 diff --git a/internal/pkg/process/analyze.go b/internal/pkg/process/analyze.go index cc761b9c1..ac807981c 100644 --- a/internal/pkg/process/analyze.go +++ b/internal/pkg/process/analyze.go @@ -103,7 +103,7 @@ func (a *Analyzer) Analyze(pid int, relevantFuncs map[string]interface{}) (*Targ return nil, err } for _, fn := range funcs { - a.logger.Info("found function", "function_name", fn) + a.logger.V(-1).Info("found function", "function_name", fn) } result.Functions = funcs @@ -145,7 +145,7 @@ func (a *Analyzer) findFunctions(elfF *elf.File, relevantFuncs map[string]interf result, err := binary.FindFunctionsUnStripped(elfF, relevantFuncs) if err != nil { if errors.Is(err, elf.ErrNoSymbols) { - a.logger.Info("No symbols found in binary, trying to find functions using .gosymtab") + a.logger.V(-1).Info("No symbols found in binary, trying to find functions using .gosymtab") return binary.FindFunctionsStripped(elfF, relevantFuncs) } return nil, err diff --git a/internal/pkg/process/discover.go b/internal/pkg/process/discover.go index a55cdf792..93e08c527 100644 --- a/internal/pkg/process/discover.go +++ b/internal/pkg/process/discover.go @@ -70,16 +70,16 @@ func (a *Analyzer) DiscoverProcessID(ctx context.Context, target *TargetArgs) (i for { select { case <-ctx.Done(): - a.logger.Info("stopping process id discovery due to kill signal") + a.logger.V(-1).Info("stopping process id discovery due to kill signal") return 0, ErrInterrupted case <-t.C: pid, err := a.findProcessID(target, proc) if err == nil { - a.logger.Info("found process", "pid", pid) + a.logger.V(-1).Info("found process", "pid", pid) return pid, nil } if err == ErrProcessNotFound { - a.logger.Info("process not found yet, trying again soon", "exe_path", target.ExePath) + a.logger.V(-1).Info("process not found yet, trying again soon", "exe_path", target.ExePath) } else { a.logger.Error(err, "error while searching for process", "exe_path", target.ExePath) } diff --git a/internal/pkg/process/ptrace/ptrace_linux.go b/internal/pkg/process/ptrace/ptrace_linux.go index d1f2dc1fa..9ee067510 100644 --- a/internal/pkg/process/ptrace/ptrace_linux.go +++ b/internal/pkg/process/ptrace/ptrace_linux.go @@ -116,7 +116,7 @@ func NewTracedProgram(pid int, logger logr.Logger) (*TracedProgram, error) { retryCount[tid]++ } if retryCount[tid] < threadRetryLimit { - logger.Info("retry attaching thread", "tid", tid, "retryCount", retryCount[tid], "limit", threadRetryLimit) + logger.V(-1).Info("retry attaching thread", "tid", tid, "retryCount", retryCount[tid], "limit", threadRetryLimit) continue } @@ -135,7 +135,7 @@ func NewTracedProgram(pid int, logger logr.Logger) (*TracedProgram, error) { return nil, errors.WithStack(err) } - logger.Info("attach successfully", "tid", tid) + logger.V(-1).Info("attach successfully", "tid", tid) tids[tid] = true tidMap[tid] = true } @@ -247,7 +247,7 @@ func (p *TracedProgram) Madvise(addr uint64, length uint64) error { } minVersion := version.Must(version.NewVersion("5.14")) - p.logger.Info("Detected linux kernel version", "version", ver) + p.logger.V(-1).Info("Detected linux kernel version", "version", ver) if ver.GreaterThanOrEqual(minVersion) { advice = syscall.MADV_WILLNEED | MadvisePopulateRead | MadvisePopulateWrite } @@ -259,6 +259,6 @@ func (p *TracedProgram) Madvise(addr uint64, length uint64) error { // Mlock runs mlock syscall. func (p *TracedProgram) Mlock(addr uint64, length uint64) error { ret, err := p.Syscall(syscall.SYS_MLOCK, addr, length, 0, 0, 0, 0) - p.logger.Info("mlock ret", "ret", ret) + p.logger.V(-1).Info("mlock ret", "ret", ret) return err } From dd9d63820bc2e9074af58c7bcf040464e1f85a25 Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Wed, 15 May 2024 16:41:58 -0300 Subject: [PATCH 05/16] changed test job to use debug log level --- .github/workflows/e2e/k8s/sample-job.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e/k8s/sample-job.yml b/.github/workflows/e2e/k8s/sample-job.yml index 610d66d0c..40c758d6e 100644 --- a/.github/workflows/e2e/k8s/sample-job.yml +++ b/.github/workflows/e2e/k8s/sample-job.yml @@ -26,7 +26,7 @@ spec: - name: auto-instrumentation image: otel-go-instrumentation imagePullPolicy: IfNotPresent - command: ["/otel-go-instrumentation", "-global-impl"] + command: ["/otel-go-instrumentation", "-global-impl", "-log-level=debug"] env: - name: OTEL_GO_AUTO_TARGET_EXE value: /sample-app/main From 8dc0545efb03a5a5caa96787077400ad57a56208 Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Thu, 16 May 2024 09:48:22 -0300 Subject: [PATCH 06/16] running precommit --- instrumentation.go | 1 - instrumentation_test.go | 3 ++- internal/pkg/log/level.go | 33 +++++++++++++++++++++++++++------ internal/pkg/log/level_test.go | 15 +++++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/instrumentation.go b/instrumentation.go index bcb943ce2..4e821b6e9 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -77,7 +77,6 @@ var errUndefinedTarget = fmt.Errorf("undefined target Go binary, consider settin func newLogger(logLevel internalLog.Level) logr.Logger { level, err := zap.ParseAtomicLevel(logLevel.String()) - if err != nil { level, _ = zap.ParseAtomicLevel(internalLog.LevelInfo.String()) } diff --git a/instrumentation_test.go b/instrumentation_test.go index ed766af11..98c7f4dc4 100644 --- a/instrumentation_test.go +++ b/instrumentation_test.go @@ -22,9 +22,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.opentelemetry.io/auto/internal/pkg/log" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + + "go.opentelemetry.io/auto/internal/pkg/log" ) func TestWithServiceName(t *testing.T) { diff --git a/internal/pkg/log/level.go b/internal/pkg/log/level.go index cd4ba6f52..237d5f6aa 100644 --- a/internal/pkg/log/level.go +++ b/internal/pkg/log/level.go @@ -1,3 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package log import ( @@ -9,13 +23,20 @@ import ( type Level string const ( - LevelDebug Level = "debug" - LevelInfo Level = "info" - LevelWarn Level = "warn" - LevelError Level = "error" + // Log Level [debug]. + LevelDebug Level = "debug" + // Log Level [info]. + LevelInfo Level = "info" + // Log Level [warn]. + LevelWarn Level = "warn" + // Log Level [error]. + LevelError Level = "error" + // Log Level [dpanic]. LevelDPanic Level = "dpanic" - LevelPanic Level = "panic" - LevelFatal Level = "fatal" + // Log Level [panic]. + LevelPanic Level = "panic" + // Log Level [fatal]. + LevelFatal Level = "fatal" ) func (l Level) String() string { diff --git a/internal/pkg/log/level_test.go b/internal/pkg/log/level_test.go index ddea55e34..42a9484d0 100644 --- a/internal/pkg/log/level_test.go +++ b/internal/pkg/log/level_test.go @@ -1,9 +1,24 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package log_test import ( "testing" "github.com/stretchr/testify/assert" + "go.opentelemetry.io/auto/internal/pkg/log" ) From db79e4c3a752b6c9ef453a3b3f05193d9c10ef88 Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Thu, 16 May 2024 16:05:22 -0300 Subject: [PATCH 07/16] adjusted log visibility levels to ensure is following the flag --- internal/pkg/instrumentation/manager.go | 16 ++++++++-------- internal/pkg/instrumentation/probe/probe.go | 6 +++--- internal/pkg/opentelemetry/controller.go | 4 ++-- internal/pkg/process/allocate.go | 8 ++++---- internal/pkg/process/analyze.go | 4 ++-- internal/pkg/process/discover.go | 6 +++--- internal/pkg/process/ptrace/ptrace_linux.go | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/internal/pkg/instrumentation/manager.go b/internal/pkg/instrumentation/manager.go index 9329fcb96..28f3e4273 100644 --- a/internal/pkg/instrumentation/manager.go +++ b/internal/pkg/instrumentation/manager.go @@ -137,7 +137,7 @@ func (m *Manager) FilterUnusedProbes(target *process.TargetDetails) { } if !funcsFound { - m.logger.V(-1).Info("no functions found for probe, removing", "name", name) + m.logger.V(1).Info("no functions found for probe, removing", "name", name) delete(m.probes, name) } } @@ -172,13 +172,13 @@ func (m *Manager) Run(ctx context.Context, target *process.TargetDetails) error for { select { case <-ctx.Done(): - m.logger.V(-1).Info("shutting down all probes due to context cancellation") + m.logger.V(1).Info("shutting down all probes due to context cancellation") err := m.cleanup(target) err = errors.Join(err, ctx.Err()) m.closingErrors <- err return nil case <-m.done: - m.logger.V(-1).Info("shutting down all probes due to signal") + m.logger.V(1).Info("shutting down all probes due to signal") err := m.cleanup(target) m.closingErrors <- err return nil @@ -205,7 +205,7 @@ func (m *Manager) load(target *process.TargetDetails) error { // Load probes for name, i := range m.probes { - m.logger.V(-1).Info("loading probe", "name", name) + m.logger.V(0).Info("loading probe", "name", name) err := i.Load(exe, target) if err != nil { m.logger.Error(err, "error while loading probes, cleaning up", "name", name) @@ -213,15 +213,15 @@ func (m *Manager) load(target *process.TargetDetails) error { } } - m.logger.V(-1).Info("loaded probes to memory", "total_probes", len(m.probes)) + m.logger.V(1).Info("loaded probes to memory", "total_probes", len(m.probes)) return nil } func (m *Manager) mount(target *process.TargetDetails) error { if target.AllocationDetails != nil { - m.logger.V(-1).Info("Mounting bpffs", "allocations_details", target.AllocationDetails) + m.logger.V(1).Info("Mounting bpffs", "allocations_details", target.AllocationDetails) } else { - m.logger.V(-1).Info("Mounting bpffs") + m.logger.V(1).Info("Mounting bpffs") } return bpffs.Mount(target) } @@ -233,7 +233,7 @@ func (m *Manager) cleanup(target *process.TargetDetails) error { err = errors.Join(err, i.Close()) } - m.logger.V(-1).Info("Cleaning bpffs") + m.logger.V(1).Info("Cleaning bpffs") return errors.Join(err, bpffs.Cleanup(target)) } diff --git a/internal/pkg/instrumentation/probe/probe.go b/internal/pkg/instrumentation/probe/probe.go index fa7c15481..804245e33 100644 --- a/internal/pkg/instrumentation/probe/probe.go +++ b/internal/pkg/instrumentation/probe/probe.go @@ -149,7 +149,7 @@ func (i *Base[BPFObj, BPFEvent]) buildObj(exec *link.Executable, td *process.Tar links, err := up.Fn(up.Sym, exec, td, obj) if err != nil { if up.Optional { - i.Logger.V(-1).Info("failed to attach optional uprobe", "probe", i.ID, "symbol", up.Sym, "error", err) + i.Logger.V(1).Info("failed to attach optional uprobe", "probe", i.ID, "symbol", up.Sym, "error", err) continue } return nil, err @@ -175,7 +175,7 @@ func (i *Base[BPFObj, BPFEvent]) Run(dest chan<- *Event) { } if record.LostSamples != 0 { - i.Logger.V(-1).Info("perf event ring buffer full", "dropped", record.LostSamples) + i.Logger.V(1).Info("perf event ring buffer full", "dropped", record.LostSamples) continue } @@ -211,7 +211,7 @@ func (i *Base[BPFObj, BPFEvent]) Close() error { err = errors.Join(err, c.Close()) } if err == nil { - i.Logger.V(-1).Info("Closed", "Probe", i.ID) + i.Logger.V(1).Info("Closed", "Probe", i.ID) } return err } diff --git a/internal/pkg/opentelemetry/controller.go b/internal/pkg/opentelemetry/controller.go index 469c1c8dc..af7d895b3 100644 --- a/internal/pkg/opentelemetry/controller.go +++ b/internal/pkg/opentelemetry/controller.go @@ -52,11 +52,11 @@ func (c *Controller) getTracer(pkg string) trace.Tracer { // Trace creates a trace span for event. func (c *Controller) Trace(event *probe.Event) { for _, se := range event.SpanEvents { - c.logger.V(-1).Info("got event", "kind", event.Kind.String(), "pkg", event.Package, "attrs", se.Attributes, "traceID", se.SpanContext.TraceID().String(), "spanID", se.SpanContext.SpanID().String()) + c.logger.V(1).Info("got event", "kind", event.Kind.String(), "pkg", event.Package, "attrs", se.Attributes, "traceID", se.SpanContext.TraceID().String(), "spanID", se.SpanContext.SpanID().String()) ctx := context.Background() if se.SpanContext == nil { - c.logger.V(-1).Info("got event without context - dropping") + c.logger.V(1).Info("got event without context - dropping") return } diff --git a/internal/pkg/process/allocate.go b/internal/pkg/process/allocate.go index 821d54a16..265ef48c1 100644 --- a/internal/pkg/process/allocate.go +++ b/internal/pkg/process/allocate.go @@ -48,7 +48,7 @@ func Allocate(logger logr.Logger, pid int) (*AllocationDetails, error) { } mapSize := uint64(os.Getpagesize() * nCPU * 8) - logger.V(-1).Info( + logger.V(1).Info( "Requesting memory allocation", "size", mapSize, "page size", os.Getpagesize(), @@ -59,7 +59,7 @@ func Allocate(logger logr.Logger, pid int) (*AllocationDetails, error) { return nil, err } - logger.V(-1).Info( + logger.V(1).Info( "mmaped remote memory", "start_addr", fmt.Sprintf("0x%x", addr), "end_addr", fmt.Sprintf("0x%x", addr+mapSize), @@ -81,7 +81,7 @@ func remoteAllocate(logger logr.Logger, pid int, mapSize uint64) (uint64, error) } defer func() { - logger.V(-1).Info("Detaching from process", "pid", pid) + logger.V(0).Info("Detaching from process", "pid", pid) err := program.Detach() if err != nil { logger.Error(err, "Failed to detach ptrace", "pid", pid) @@ -91,7 +91,7 @@ func remoteAllocate(logger logr.Logger, pid int, mapSize uint64) (uint64, error) if err := program.SetMemLockInfinity(); err != nil { logger.Error(err, "Failed to set memlock on process") } else { - logger.V(-1).Info("Set memlock on process successfully") + logger.V(1).Info("Set memlock on process successfully") } fd := -1 diff --git a/internal/pkg/process/analyze.go b/internal/pkg/process/analyze.go index ac807981c..03d346397 100644 --- a/internal/pkg/process/analyze.go +++ b/internal/pkg/process/analyze.go @@ -103,7 +103,7 @@ func (a *Analyzer) Analyze(pid int, relevantFuncs map[string]interface{}) (*Targ return nil, err } for _, fn := range funcs { - a.logger.V(-1).Info("found function", "function_name", fn) + a.logger.V(1).Info("found function", "function_name", fn) } result.Functions = funcs @@ -145,7 +145,7 @@ func (a *Analyzer) findFunctions(elfF *elf.File, relevantFuncs map[string]interf result, err := binary.FindFunctionsUnStripped(elfF, relevantFuncs) if err != nil { if errors.Is(err, elf.ErrNoSymbols) { - a.logger.V(-1).Info("No symbols found in binary, trying to find functions using .gosymtab") + a.logger.V(1).Info("No symbols found in binary, trying to find functions using .gosymtab") return binary.FindFunctionsStripped(elfF, relevantFuncs) } return nil, err diff --git a/internal/pkg/process/discover.go b/internal/pkg/process/discover.go index 93e08c527..a8aed4206 100644 --- a/internal/pkg/process/discover.go +++ b/internal/pkg/process/discover.go @@ -70,16 +70,16 @@ func (a *Analyzer) DiscoverProcessID(ctx context.Context, target *TargetArgs) (i for { select { case <-ctx.Done(): - a.logger.V(-1).Info("stopping process id discovery due to kill signal") + a.logger.V(1).Info("stopping process id discovery due to kill signal") return 0, ErrInterrupted case <-t.C: pid, err := a.findProcessID(target, proc) if err == nil { - a.logger.V(-1).Info("found process", "pid", pid) + a.logger.V(0).Info("found process", "pid", pid) return pid, nil } if err == ErrProcessNotFound { - a.logger.V(-1).Info("process not found yet, trying again soon", "exe_path", target.ExePath) + a.logger.V(1).Info("process not found yet, trying again soon", "exe_path", target.ExePath) } else { a.logger.Error(err, "error while searching for process", "exe_path", target.ExePath) } diff --git a/internal/pkg/process/ptrace/ptrace_linux.go b/internal/pkg/process/ptrace/ptrace_linux.go index 9ee067510..9ee27d344 100644 --- a/internal/pkg/process/ptrace/ptrace_linux.go +++ b/internal/pkg/process/ptrace/ptrace_linux.go @@ -116,7 +116,7 @@ func NewTracedProgram(pid int, logger logr.Logger) (*TracedProgram, error) { retryCount[tid]++ } if retryCount[tid] < threadRetryLimit { - logger.V(-1).Info("retry attaching thread", "tid", tid, "retryCount", retryCount[tid], "limit", threadRetryLimit) + logger.V(1).Info("retry attaching thread", "tid", tid, "retryCount", retryCount[tid], "limit", threadRetryLimit) continue } @@ -135,7 +135,7 @@ func NewTracedProgram(pid int, logger logr.Logger) (*TracedProgram, error) { return nil, errors.WithStack(err) } - logger.V(-1).Info("attach successfully", "tid", tid) + logger.V(1).Info("attach successfully", "tid", tid) tids[tid] = true tidMap[tid] = true } @@ -247,7 +247,7 @@ func (p *TracedProgram) Madvise(addr uint64, length uint64) error { } minVersion := version.Must(version.NewVersion("5.14")) - p.logger.V(-1).Info("Detected linux kernel version", "version", ver) + p.logger.V(1).Info("Detected linux kernel version", "version", ver) if ver.GreaterThanOrEqual(minVersion) { advice = syscall.MADV_WILLNEED | MadvisePopulateRead | MadvisePopulateWrite } @@ -259,6 +259,6 @@ func (p *TracedProgram) Madvise(addr uint64, length uint64) error { // Mlock runs mlock syscall. func (p *TracedProgram) Mlock(addr uint64, length uint64) error { ret, err := p.Syscall(syscall.SYS_MLOCK, addr, length, 0, 0, 0, 0) - p.logger.V(-1).Info("mlock ret", "ret", ret) + p.logger.V(1).Info("mlock ret", "ret", ret) return err } From 771b1a54db7719da9a878691d05d13c00d805d5f Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Fri, 17 May 2024 10:44:38 -0300 Subject: [PATCH 08/16] changed the behaviour on how default value is set --- cli/main.go | 6 ++++-- instrumentation.go | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/main.go b/cli/main.go index e7f57eb8c..3512e4195 100644 --- a/cli/main.go +++ b/cli/main.go @@ -71,7 +71,7 @@ func main() { var logLevel string flag.BoolVar(&globalImpl, "global-impl", false, "Record telemetry from the OpenTelemetry default global implementation") - flag.StringVar(&logLevel, "log-level", "info", "Define log visibility level") + flag.StringVar(&logLevel, "log-level", "", "Define log visibility level, default is `info`") flag.Usage = usage flag.Parse() @@ -102,7 +102,9 @@ func main() { instOptions = append(instOptions, auto.WithGlobal()) } - instOptions = append(instOptions, auto.WithLogLevel(logLevel)) + if logLevel != "" { + instOptions = append(instOptions, auto.WithLogLevel(logLevel)) + } inst, err := auto.NewInstrumentation(ctx, instOptions...) if err != nil { diff --git a/instrumentation.go b/instrumentation.go index 4e821b6e9..bae5a134d 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -361,9 +361,10 @@ var lookupEnv = os.LookupEnv // - OTEL_SERVICE_NAME (or OTEL_RESOURCE_ATTRIBUTES): sets the service name // - OTEL_TRACES_EXPORTER: sets the trace exporter // - OTEL_GO_AUTO_GLOBAL: enables the OpenTelemetry global implementation +// - OTEL_LOG_LEVEL: sets the log level // // This option may conflict with [WithTarget], [WithPID], [WithTraceExporter], -// [WithServiceName] and [WithGlobal] if their respective environment variable is defined. +// [WithServiceName], [WithGlobal] and [WithLogLevel] if their respective environment variable is defined. // If more than one of these options are used, the last one provided to an // [Instrumentation] will be used. // From 9f7d2a42d015a254162326aa4992ed00281bb936 Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Wed, 29 May 2024 11:02:53 -0300 Subject: [PATCH 09/16] removed not used levels and add comments to levels --- instrumentation.go | 2 +- internal/pkg/log/level.go | 27 +++++---------------------- internal/pkg/log/level_test.go | 15 --------------- 3 files changed, 6 insertions(+), 38 deletions(-) diff --git a/instrumentation.go b/instrumentation.go index bae5a134d..0dd9f5907 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -220,7 +220,7 @@ func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfi } if c.logLevel == "" { - c.logLevel = "info" + c.logLevel = internalLog.LevelInfo } return c, err diff --git a/internal/pkg/log/level.go b/internal/pkg/log/level.go index 237d5f6aa..24f06df82 100644 --- a/internal/pkg/log/level.go +++ b/internal/pkg/log/level.go @@ -20,23 +20,18 @@ import ( "fmt" ) +// Level defines the log level which instrumentation uses. type Level string const ( - // Log Level [debug]. + // Log Level [debug] Print all logs generate by instrumentation. LevelDebug Level = "debug" - // Log Level [info]. + // Log Level [info].Print most of logs where these is running status of instrumentation. LevelInfo Level = "info" - // Log Level [warn]. + // Log Level [warn].Print errors and logs which need attention on instrumentation. LevelWarn Level = "warn" - // Log Level [error]. + // Log Level [error].Print errors generated by instrumentation. LevelError Level = "error" - // Log Level [dpanic]. - LevelDPanic Level = "dpanic" - // Log Level [panic]. - LevelPanic Level = "panic" - // Log Level [fatal]. - LevelFatal Level = "fatal" ) func (l Level) String() string { @@ -49,12 +44,6 @@ func (l Level) String() string { return "warn" case LevelError: return "error" - case LevelDPanic: - return "dpanic" - case LevelPanic: - return "panic" - case LevelFatal: - return "fatal" default: return fmt.Sprintf("Level(%s)", string(l)) } @@ -82,12 +71,6 @@ func (l *Level) unmarshalText(text []byte) bool { *l = LevelWarn case "error": *l = LevelError - case "dpanic": - *l = LevelDPanic - case "panic": - *l = LevelPanic - case "fatal": - *l = LevelFatal default: return false } diff --git a/internal/pkg/log/level_test.go b/internal/pkg/log/level_test.go index 42a9484d0..a40bae102 100644 --- a/internal/pkg/log/level_test.go +++ b/internal/pkg/log/level_test.go @@ -48,21 +48,6 @@ func TestLevel(t *testing.T) { level: log.LevelError, str: "error", }, - { - name: "LevelDPanic", - level: log.LevelDPanic, - str: "dpanic", - }, - { - name: "LevelPanic", - level: log.LevelPanic, - str: "panic", - }, - { - name: "LevelFatal", - level: log.LevelFatal, - str: "fatal", - }, } for _, tc := range testCases { From 56871d50e9de559f7153cb995d4eb94aa949b8ce Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Wed, 29 May 2024 14:48:32 -0300 Subject: [PATCH 10/16] changed level to auto package and use Level as parameter on WithLogLevel --- cli/main.go | 10 +++++++++- instrumentation.go | 18 ++++++++---------- instrumentation_test.go | 12 ++++++++---- internal/pkg/log/level.go => level.go | 2 +- .../pkg/log/level_test.go => level_test.go | 14 ++++++-------- 5 files changed, 32 insertions(+), 24 deletions(-) rename internal/pkg/log/level.go => level.go (99%) rename internal/pkg/log/level_test.go => level_test.go (85%) diff --git a/cli/main.go b/cli/main.go index 3512e4195..3d675cadf 100644 --- a/cli/main.go +++ b/cli/main.go @@ -103,7 +103,15 @@ func main() { } if logLevel != "" { - instOptions = append(instOptions, auto.WithLogLevel(logLevel)) + var level auto.Level + + err := level.UnmarshalText([]byte(logLevel)) + if err != nil { + logger.Error(err, "failed to parse log level") + return + } + + instOptions = append(instOptions, auto.WithLogLevel(level)) } inst, err := auto.NewInstrumentation(ctx, instOptions...) diff --git a/instrumentation.go b/instrumentation.go index 0dd9f5907..359ee42eb 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -39,7 +39,6 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.opentelemetry.io/auto/internal/pkg/instrumentation" - internalLog "go.opentelemetry.io/auto/internal/pkg/log" "go.opentelemetry.io/auto/internal/pkg/opentelemetry" "go.opentelemetry.io/auto/internal/pkg/process" ) @@ -75,10 +74,10 @@ type Instrumentation struct { // binary or pid. var errUndefinedTarget = fmt.Errorf("undefined target Go binary, consider setting the %s environment variable pointing to the target binary to instrument", envTargetExeKey) -func newLogger(logLevel internalLog.Level) logr.Logger { +func newLogger(logLevel Level) logr.Logger { level, err := zap.ParseAtomicLevel(logLevel.String()) if err != nil { - level, _ = zap.ParseAtomicLevel(internalLog.LevelInfo.String()) + level, _ = zap.ParseAtomicLevel(LevelInfo.String()) } config := zap.NewProductionConfig() @@ -188,7 +187,7 @@ type instConfig struct { additionalResAttrs []attribute.KeyValue globalImpl bool loadIndicator chan struct{} - logLevel internalLog.Level + logLevel Level } func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfig, error) { @@ -220,7 +219,7 @@ func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfi } if c.logLevel == "" { - c.logLevel = internalLog.LevelInfo + c.logLevel = LevelInfo } return c, err @@ -400,7 +399,7 @@ func WithEnv() InstrumentationOption { } if l, ok := lookupEnv(envLogLevelKey); ok { var e error - c.logLevel, e = internalLog.ParseLevel(l) + c.logLevel, e = ParseLevel(l) err = errors.Join(err, e) } return c, err @@ -513,14 +512,13 @@ func WithLoadedIndicator(indicator chan struct{}) InstrumentationOption { // WithLogLevel returns an [InstrumentationOption] that will configure // an [Instrumentation] with the logger level visibility defined as inputed. -func WithLogLevel(level string) InstrumentationOption { +func WithLogLevel(level Level) InstrumentationOption { return fnOpt(func(ctx context.Context, c instConfig) (instConfig, error) { - l, err := internalLog.ParseLevel(level) - if err != nil { + if err := level.UnmarshalText([]byte(level.String())); err != nil { return c, err } - c.logLevel = l + c.logLevel = level return c, nil }) diff --git a/instrumentation_test.go b/instrumentation_test.go index 98c7f4dc4..5894e6045 100644 --- a/instrumentation_test.go +++ b/instrumentation_test.go @@ -24,8 +24,6 @@ import ( "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" - - "go.opentelemetry.io/auto/internal/pkg/log" ) func TestWithServiceName(t *testing.T) { @@ -91,7 +89,7 @@ func TestWithEnv(t *testing.T) { c, err := newInstConfig(context.Background(), []InstrumentationOption{WithEnv()}) require.NoError(t, err) - assert.Equal(t, log.LevelDebug, c.logLevel) + assert.Equal(t, LevelDebug, c.logLevel) const wrong = "invalid" @@ -195,7 +193,13 @@ func TestWithLogLevel(t *testing.T) { require.NoError(t, err) - assert.Equal(t, log.LevelError, c.logLevel) + assert.Equal(t, LevelError, c.logLevel) + + c, err = newInstConfig(context.Background(), []InstrumentationOption{WithLogLevel(LevelInfo)}) + + require.NoError(t, err) + + assert.Equal(t, LevelInfo, c.logLevel) }) t.Run("Will Validate Input", func(t *testing.T) { diff --git a/internal/pkg/log/level.go b/level.go similarity index 99% rename from internal/pkg/log/level.go rename to level.go index 24f06df82..30db5f641 100644 --- a/internal/pkg/log/level.go +++ b/level.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package log +package auto import ( "bytes" diff --git a/internal/pkg/log/level_test.go b/level_test.go similarity index 85% rename from internal/pkg/log/level_test.go rename to level_test.go index a40bae102..d1aa44b85 100644 --- a/internal/pkg/log/level_test.go +++ b/level_test.go @@ -12,40 +12,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -package log_test +package auto import ( "testing" "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/auto/internal/pkg/log" ) func TestLevel(t *testing.T) { testCases := []struct { name string - level log.Level + level Level str string }{ { name: "LevelDebug", - level: log.LevelDebug, + level: LevelDebug, str: "debug", }, { name: "LevelInfo", - level: log.LevelInfo, + level: LevelInfo, str: "info", }, { name: "LevelWarn", - level: log.LevelWarn, + level: LevelWarn, str: "warn", }, { name: "LevelError", - level: log.LevelError, + level: LevelError, str: "error", }, } From a0dc6da6363d139daf06996d6c35db31bade8156 Mon Sep 17 00:00:00 2001 From: Vitor Hugo Rodrigues Merencio Date: Mon, 17 Jun 2024 15:13:54 -0300 Subject: [PATCH 11/16] changed type to log level, add validate method and enforce a type on withLogLevel method --- cli/main.go | 2 +- instrumentation.go | 25 ++++++++++++++------- level.go | 55 +++++++++++++++++++++++++--------------------- level_test.go | 17 +++++++++++++- 4 files changed, 64 insertions(+), 35 deletions(-) diff --git a/cli/main.go b/cli/main.go index 3d675cadf..7594c40d1 100644 --- a/cli/main.go +++ b/cli/main.go @@ -103,7 +103,7 @@ func main() { } if logLevel != "" { - var level auto.Level + var level auto.LogLevel err := level.UnmarshalText([]byte(logLevel)) if err != nil { diff --git a/instrumentation.go b/instrumentation.go index 359ee42eb..0d2a37fdf 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -74,9 +74,9 @@ type Instrumentation struct { // binary or pid. var errUndefinedTarget = fmt.Errorf("undefined target Go binary, consider setting the %s environment variable pointing to the target binary to instrument", envTargetExeKey) -func newLogger(logLevel Level) logr.Logger { - level, err := zap.ParseAtomicLevel(logLevel.String()) - if err != nil { +func newLogger(logLevel LogLevel) logr.Logger { + level, logErr := zap.ParseAtomicLevel(logLevel.String()) + if logErr != nil { level, _ = zap.ParseAtomicLevel(LevelInfo.String()) } @@ -94,6 +94,10 @@ func newLogger(logLevel Level) logr.Logger { logger = zapr.NewLogger(zapLog) } + if logErr != nil { + logger.V(2).Info("error to parse log level, changed to LevelInfo by default") + } + return logger } @@ -187,7 +191,7 @@ type instConfig struct { additionalResAttrs []attribute.KeyValue globalImpl bool loadIndicator chan struct{} - logLevel Level + logLevel LogLevel } func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfig, error) { @@ -218,7 +222,7 @@ func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfi c.sampler = trace.AlwaysSample() } - if c.logLevel == "" { + if c.logLevel == LevelUndefined { c.logLevel = LevelInfo } @@ -399,7 +403,12 @@ func WithEnv() InstrumentationOption { } if l, ok := lookupEnv(envLogLevelKey); ok { var e error - c.logLevel, e = ParseLevel(l) + level, e := ParseLevel(l) + + if err == nil { + c.logLevel = level + } + err = errors.Join(err, e) } return c, err @@ -512,9 +521,9 @@ func WithLoadedIndicator(indicator chan struct{}) InstrumentationOption { // WithLogLevel returns an [InstrumentationOption] that will configure // an [Instrumentation] with the logger level visibility defined as inputed. -func WithLogLevel(level Level) InstrumentationOption { +func WithLogLevel(level LogLevel) InstrumentationOption { return fnOpt(func(ctx context.Context, c instConfig) (instConfig, error) { - if err := level.UnmarshalText([]byte(level.String())); err != nil { + if err := level.validate(); err != nil { return c, err } diff --git a/level.go b/level.go index 30db5f641..9e27a1218 100644 --- a/level.go +++ b/level.go @@ -21,47 +21,52 @@ import ( ) // Level defines the log level which instrumentation uses. -type Level string +type LogLevel string const ( - // Log Level [debug] Print all logs generate by instrumentation. - LevelDebug Level = "debug" - // Log Level [info].Print most of logs where these is running status of instrumentation. - LevelInfo Level = "info" - // Log Level [warn].Print errors and logs which need attention on instrumentation. - LevelWarn Level = "warn" - // Log Level [error].Print errors generated by instrumentation. - LevelError Level = "error" + // LevelUndefined is an unset log level, it should not be used. + LevelUndefined LogLevel = "" + // LevelDebug sets the logging level to log all messages. + LevelDebug LogLevel = "debug" + // LevelInfo sets the logging level to log only informational, warning, and error messages. + LevelInfo LogLevel = "info" + // LevelWarn sets the logging level to log only warning and error messages. + LevelWarn LogLevel = "warn" + // LevelError sets the logging level to log only error messages. + LevelError LogLevel = "error" ) -func (l Level) String() string { +// String returns the string encoding of the Level l. +func (l LogLevel) String() string { switch l { - case LevelDebug: - return "debug" - case LevelInfo: - return "info" - case LevelWarn: - return "warn" - case LevelError: - return "error" + case LevelDebug, LevelInfo, LevelWarn, LevelError, LevelUndefined: + return string(l) default: return fmt.Sprintf("Level(%s)", string(l)) } } -func (l *Level) UnmarshalText(text []byte) error { +func (l *LogLevel) UnmarshalText(text []byte) error { + if ok := l.unmarshalText(bytes.ToLower(text)); ok { + return nil + } + + return l.validate() +} + +func (l *LogLevel) validate() error { if l == nil { - return errors.New("can't unmarshal nil values") + return errors.New("can't parse nil values") } - if !l.unmarshalText(bytes.ToLower(text)) { - return fmt.Errorf("") + if l.unmarshalText(bytes.ToLower([]byte(l.String()))) == false { + return errors.New("log level value is not accepted") } return nil } -func (l *Level) unmarshalText(text []byte) bool { +func (l *LogLevel) unmarshalText(text []byte) bool { switch string(text) { case "debug": *l = LevelDebug @@ -78,8 +83,8 @@ func (l *Level) unmarshalText(text []byte) bool { return true } -func ParseLevel(text string) (Level, error) { - var level Level +func ParseLevel(text string) (LogLevel, error) { + var level LogLevel err := level.UnmarshalText([]byte(text)) diff --git a/level_test.go b/level_test.go index d1aa44b85..9ce017ccc 100644 --- a/level_test.go +++ b/level_test.go @@ -23,9 +23,14 @@ import ( func TestLevel(t *testing.T) { testCases := []struct { name string - level Level + level LogLevel str string }{ + { + name: "LevelUndefined", + level: LevelUndefined, + str: "", + }, { name: "LevelDebug", level: LevelDebug, @@ -54,3 +59,13 @@ func TestLevel(t *testing.T) { }) } } + +func TestValidate(t *testing.T) { + t.Run("can validate wrong log levels", func(t *testing.T) { + var l LogLevel + + l.unmarshalText([]byte("notexist")) + + assert.Equal(t, "log level value is not accepted", l.validate().Error(), "log level is not nil") + }) +} From 72d3d04af0f1444c5ffc078ea25b1a142d0b33e2 Mon Sep 17 00:00:00 2001 From: Vitor Hugo Rodrigues Merencio Date: Mon, 17 Jun 2024 15:27:25 -0300 Subject: [PATCH 12/16] additional changelog message --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7ae8011a..c4cdf60fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http - Initial support for `trace-flags`. ([#868](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/868)) - Support `google.golang.org/grpc` `1.66.0-dev`. ([#872](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/872)) - Add support to log level through command line flag. ([#842](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/842)) +- The `WithLogLevel` function and `LogLevel` type are added to set the log level for `Instrumentation`. ([#842](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/842)) ### Fixed From bb3cdbaa396eade74558bd294b77cff3f8795dc2 Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Mon, 17 Jun 2024 15:59:12 -0300 Subject: [PATCH 13/16] runned precommit --- level.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/level.go b/level.go index 9e27a1218..6b0215c61 100644 --- a/level.go +++ b/level.go @@ -46,6 +46,7 @@ func (l LogLevel) String() string { } } +// UnmarshalText applies the LogLevel type when inputted text is valid. func (l *LogLevel) UnmarshalText(text []byte) error { if ok := l.unmarshalText(bytes.ToLower(text)); ok { return nil @@ -59,7 +60,7 @@ func (l *LogLevel) validate() error { return errors.New("can't parse nil values") } - if l.unmarshalText(bytes.ToLower([]byte(l.String()))) == false { + if !l.unmarshalText(bytes.ToLower([]byte(l.String()))) { return errors.New("log level value is not accepted") } @@ -83,6 +84,7 @@ func (l *LogLevel) unmarshalText(text []byte) bool { return true } +// ParseLevel return a new LogLevel using text, and will return err if inputted text is not accepted. func ParseLevel(text string) (LogLevel, error) { var level LogLevel From d03c47a7c2d5359b1667ecc11df8cc49aaa42f19 Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Mon, 17 Jun 2024 16:45:53 -0300 Subject: [PATCH 14/16] changed levels to log preffix and simplified unmarshal --- instrumentation.go | 8 +++--- instrumentation_test.go | 8 +++--- level.go | 61 ++++++++++++++++------------------------- level_test.go | 29 ++++++++------------ 4 files changed, 43 insertions(+), 63 deletions(-) diff --git a/instrumentation.go b/instrumentation.go index 0d2a37fdf..cee174229 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -77,7 +77,7 @@ var errUndefinedTarget = fmt.Errorf("undefined target Go binary, consider settin func newLogger(logLevel LogLevel) logr.Logger { level, logErr := zap.ParseAtomicLevel(logLevel.String()) if logErr != nil { - level, _ = zap.ParseAtomicLevel(LevelInfo.String()) + level, _ = zap.ParseAtomicLevel(LogLevelInfo.String()) } config := zap.NewProductionConfig() @@ -222,8 +222,8 @@ func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfi c.sampler = trace.AlwaysSample() } - if c.logLevel == LevelUndefined { - c.logLevel = LevelInfo + if c.logLevel == logLevelUndefined { + c.logLevel = LogLevelInfo } return c, err @@ -403,7 +403,7 @@ func WithEnv() InstrumentationOption { } if l, ok := lookupEnv(envLogLevelKey); ok { var e error - level, e := ParseLevel(l) + level, e := ParseLogLevel(l) if err == nil { c.logLevel = level diff --git a/instrumentation_test.go b/instrumentation_test.go index 5894e6045..ea8a41e77 100644 --- a/instrumentation_test.go +++ b/instrumentation_test.go @@ -89,7 +89,7 @@ func TestWithEnv(t *testing.T) { c, err := newInstConfig(context.Background(), []InstrumentationOption{WithEnv()}) require.NoError(t, err) - assert.Equal(t, LevelDebug, c.logLevel) + assert.Equal(t, LogLevelDebug, c.logLevel) const wrong = "invalid" @@ -193,13 +193,13 @@ func TestWithLogLevel(t *testing.T) { require.NoError(t, err) - assert.Equal(t, LevelError, c.logLevel) + assert.Equal(t, LogLevelError, c.logLevel) - c, err = newInstConfig(context.Background(), []InstrumentationOption{WithLogLevel(LevelInfo)}) + c, err = newInstConfig(context.Background(), []InstrumentationOption{WithLogLevel(LogLevelInfo)}) require.NoError(t, err) - assert.Equal(t, LevelInfo, c.logLevel) + assert.Equal(t, LogLevelInfo, c.logLevel) }) t.Run("Will Validate Input", func(t *testing.T) { diff --git a/level.go b/level.go index 6b0215c61..fbeb0e052 100644 --- a/level.go +++ b/level.go @@ -20,26 +20,28 @@ import ( "fmt" ) -// Level defines the log level which instrumentation uses. +// LogLevel defines the log level which instrumentation uses. type LogLevel string const ( // LevelUndefined is an unset log level, it should not be used. - LevelUndefined LogLevel = "" - // LevelDebug sets the logging level to log all messages. - LevelDebug LogLevel = "debug" - // LevelInfo sets the logging level to log only informational, warning, and error messages. - LevelInfo LogLevel = "info" - // LevelWarn sets the logging level to log only warning and error messages. - LevelWarn LogLevel = "warn" - // LevelError sets the logging level to log only error messages. - LevelError LogLevel = "error" + logLevelUndefined LogLevel = "" + // LogLevelDebug sets the logging level to log all messages. + LogLevelDebug LogLevel = "debug" + // LogLevelInfo sets the logging level to log only informational, warning, and error messages. + LogLevelInfo LogLevel = "info" + // LogLevelWarn sets the logging level to log only warning and error messages. + LogLevelWarn LogLevel = "warn" + // LogLevelError sets the logging level to log only error messages. + LogLevelError LogLevel = "error" ) -// String returns the string encoding of the Level l. +var errInvalidLogLevel = errors.New("invalid LogLevel") + +// String returns the string encoding of the LogLevel l. func (l LogLevel) String() string { switch l { - case LevelDebug, LevelInfo, LevelWarn, LevelError, LevelUndefined: + case LogLevelDebug, LogLevelInfo, LogLevelWarn, LogLevelError, logLevelUndefined: return string(l) default: return fmt.Sprintf("Level(%s)", string(l)) @@ -48,44 +50,27 @@ func (l LogLevel) String() string { // UnmarshalText applies the LogLevel type when inputted text is valid. func (l *LogLevel) UnmarshalText(text []byte) error { - if ok := l.unmarshalText(bytes.ToLower(text)); ok { - return nil - } + *l = LogLevel(bytes.ToLower(text)) return l.validate() } func (l *LogLevel) validate() error { if l == nil { - return errors.New("can't parse nil values") + return errors.New("nil LogLevel") } - if !l.unmarshalText(bytes.ToLower([]byte(l.String()))) { - return errors.New("log level value is not accepted") - } - - return nil -} - -func (l *LogLevel) unmarshalText(text []byte) bool { - switch string(text) { - case "debug": - *l = LevelDebug - case "info": - *l = LevelInfo - case "warn": - *l = LevelWarn - case "error": - *l = LevelError + switch *l { + case LogLevelDebug, LogLevelInfo, LogLevelWarn, LogLevelError: + // Valid. default: - return false + return fmt.Errorf("%w: %s", errInvalidLogLevel, l.String()) } - - return true + return nil } -// ParseLevel return a new LogLevel using text, and will return err if inputted text is not accepted. -func ParseLevel(text string) (LogLevel, error) { +// ParseLogLevel return a new LogLevel parsed from text. A non-nil error is returned if text is not a valid LogLevel. +func ParseLogLevel(text string) (LogLevel, error) { var level LogLevel err := level.UnmarshalText([]byte(text)) diff --git a/level_test.go b/level_test.go index 9ce017ccc..b6e0e2db4 100644 --- a/level_test.go +++ b/level_test.go @@ -27,28 +27,28 @@ func TestLevel(t *testing.T) { str string }{ { - name: "LevelUndefined", - level: LevelUndefined, + name: "LogLevelUndefined", + level: logLevelUndefined, str: "", }, { - name: "LevelDebug", - level: LevelDebug, + name: "LogLevelDebug", + level: LogLevelDebug, str: "debug", }, { - name: "LevelInfo", - level: LevelInfo, + name: "LogLevelInfo", + level: LogLevelInfo, str: "info", }, { - name: "LevelWarn", - level: LevelWarn, + name: "LogLevelWarn", + level: LogLevelWarn, str: "warn", }, { - name: "LevelError", - level: LevelError, + name: "LogLevelError", + level: LogLevelError, str: "error", }, } @@ -61,11 +61,6 @@ func TestLevel(t *testing.T) { } func TestValidate(t *testing.T) { - t.Run("can validate wrong log levels", func(t *testing.T) { - var l LogLevel - - l.unmarshalText([]byte("notexist")) - - assert.Equal(t, "log level value is not accepted", l.validate().Error(), "log level is not nil") - }) + l := LogLevel("notexist") + assert.ErrorIs(t, l.validate(), errInvalidLogLevel) } From 70e415f48c795e03b41fd5686f5cdf688b90b7e3 Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Tue, 18 Jun 2024 09:25:25 -0300 Subject: [PATCH 15/16] change some descriptions and added ParseLogLevel test --- instrumentation.go | 6 +++--- level.go | 2 +- level_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/instrumentation.go b/instrumentation.go index cee174229..3378d2c7a 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -95,7 +95,7 @@ func newLogger(logLevel LogLevel) logr.Logger { } if logErr != nil { - logger.V(2).Info("error to parse log level, changed to LevelInfo by default") + logger.Error(logErr, "invalid log level; using LevelInfo instead", zap.Error(logErr), zap.String("input", logLevel.String())) } return logger @@ -405,7 +405,7 @@ func WithEnv() InstrumentationOption { var e error level, e := ParseLogLevel(l) - if err == nil { + if e == nil { c.logLevel = level } @@ -520,7 +520,7 @@ func WithLoadedIndicator(indicator chan struct{}) InstrumentationOption { } // WithLogLevel returns an [InstrumentationOption] that will configure -// an [Instrumentation] with the logger level visibility defined as inputed. +// an [Instrumentation] to use the provided logging level. func WithLogLevel(level LogLevel) InstrumentationOption { return fnOpt(func(ctx context.Context, c instConfig) (instConfig, error) { if err := level.validate(); err != nil { diff --git a/level.go b/level.go index fbeb0e052..780b9d61b 100644 --- a/level.go +++ b/level.go @@ -24,7 +24,7 @@ import ( type LogLevel string const ( - // LevelUndefined is an unset log level, it should not be used. + // logLevelUndefined is an unset log level, it should not be used. logLevelUndefined LogLevel = "" // LogLevelDebug sets the logging level to log all messages. LogLevelDebug LogLevel = "debug" diff --git a/level_test.go b/level_test.go index b6e0e2db4..5fd8806e9 100644 --- a/level_test.go +++ b/level_test.go @@ -20,7 +20,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestLevel(t *testing.T) { +func TestLevelString(t *testing.T) { testCases := []struct { name string level LogLevel @@ -64,3 +64,46 @@ func TestValidate(t *testing.T) { l := LogLevel("notexist") assert.ErrorIs(t, l.validate(), errInvalidLogLevel) } + +func TestParseLogLevel(t *testing.T) { + testCases := []struct { + name string + str string + level LogLevel + }{ + { + name: "ParseLogLevelDebug", + str: "debug", + level: LogLevelDebug, + }, + { + name: "ParseLogLevelInfo", + str: "info", + level: LogLevelInfo, + }, + { + name: "ParseLogLevelWarn", + str: "warn", + level: LogLevelWarn, + }, + { + name: "ParseLogLevelError", + str: "error", + level: LogLevelError, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + l, _ := ParseLogLevel(tc.str) + + assert.Equal(t, tc.level, l, "LogLevel does not match") + }) + } + + t.Run("ParseNotExist", func(t *testing.T) { + _, err := ParseLogLevel("notexist") + + assert.ErrorIs(t, err, errInvalidLogLevel) + }) +} From 91e4e34ef58b7a05ca1e72110b6477f5cf942cbe Mon Sep 17 00:00:00 2001 From: Vitor Merencio Date: Tue, 18 Jun 2024 09:32:29 -0300 Subject: [PATCH 16/16] changed to use ParseLogLevel instead of Unmarshal --- cli/main.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cli/main.go b/cli/main.go index 7594c40d1..476431514 100644 --- a/cli/main.go +++ b/cli/main.go @@ -103,9 +103,7 @@ func main() { } if logLevel != "" { - var level auto.LogLevel - - err := level.UnmarshalText([]byte(logLevel)) + level, err := auto.ParseLogLevel(logLevel) if err != nil { logger.Error(err, "failed to parse log level") return