Skip to content

Commit e2d08c2

Browse files
Add support to custom log level through command line flag and environment variable (#842)
* added log level through commnd line flag * added changelog entry * implemented own level enum and add err when not found the inputed log level * changed log visibility to debug on some info logs * changed test job to use debug log level * running precommit * adjusted log visibility levels to ensure is following the flag * changed the behaviour on how default value is set * removed not used levels and add comments to levels * changed level to auto package and use Level as parameter on WithLogLevel * changed type to log level, add validate method and enforce a type on withLogLevel method * additional changelog message * runned precommit * changed levels to log preffix and simplified unmarshal * change some descriptions and added ParseLogLevel test * changed to use ParseLogLevel instead of Unmarshal --------- Co-authored-by: Tyler Yahn <[email protected]>
1 parent fd3af66 commit e2d08c2

File tree

14 files changed

+320
-38
lines changed

14 files changed

+320
-38
lines changed

.github/workflows/e2e/k8s/sample-job.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ spec:
2626
- name: auto-instrumentation
2727
image: otel-go-instrumentation
2828
imagePullPolicy: IfNotPresent
29-
command: ["/otel-go-instrumentation", "-global-impl"]
29+
command: ["/otel-go-instrumentation", "-global-impl", "-log-level=debug"]
3030
env:
3131
- name: OTEL_GO_AUTO_TARGET_EXE
3232
value: /sample-app/main

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http
1212

1313
- Initial support for `trace-flags`. ([#868](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/868))
1414
- Support `google.golang.org/grpc` `1.66.0-dev`. ([#872](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/872))
15+
- Add support to log level through command line flag. ([#842](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/842))
16+
- 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))
1517

1618
### Fixed
1719

cli/main.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,11 @@ func newLogger() logr.Logger {
6868

6969
func main() {
7070
var globalImpl bool
71+
var logLevel string
7172

7273
flag.BoolVar(&globalImpl, "global-impl", false, "Record telemetry from the OpenTelemetry default global implementation")
74+
flag.StringVar(&logLevel, "log-level", "", "Define log visibility level, default is `info`")
75+
7376
flag.Usage = usage
7477
flag.Parse()
7578

@@ -99,6 +102,16 @@ func main() {
99102
instOptions = append(instOptions, auto.WithGlobal())
100103
}
101104

105+
if logLevel != "" {
106+
level, err := auto.ParseLogLevel(logLevel)
107+
if err != nil {
108+
logger.Error(err, "failed to parse log level")
109+
return
110+
}
111+
112+
instOptions = append(instOptions, auto.WithLogLevel(level))
113+
}
114+
102115
inst, err := auto.NewInstrumentation(ctx, instOptions...)
103116
if err != nil {
104117
logger.Error(err, "failed to create instrumentation")

instrumentation.go

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ const (
5858
// envOtelGlobalImplKey is the key for the environment variable value enabling to opt-in for the
5959
// OpenTelemetry global implementation. It should be a boolean value.
6060
envOtelGlobalImplKey = "OTEL_GO_AUTO_GLOBAL"
61+
// envLogLevelKey is the key for the environment variable value containing the log level.
62+
envLogLevelKey = "OTEL_LOG_LEVEL"
6163
)
6264

6365
// Instrumentation manages and controls all OpenTelemetry Go
@@ -72,8 +74,17 @@ type Instrumentation struct {
7274
// binary or pid.
7375
var errUndefinedTarget = fmt.Errorf("undefined target Go binary, consider setting the %s environment variable pointing to the target binary to instrument", envTargetExeKey)
7476

75-
func newLogger() logr.Logger {
76-
zapLog, err := zap.NewProduction()
77+
func newLogger(logLevel LogLevel) logr.Logger {
78+
level, logErr := zap.ParseAtomicLevel(logLevel.String())
79+
if logErr != nil {
80+
level, _ = zap.ParseAtomicLevel(LogLevelInfo.String())
81+
}
82+
83+
config := zap.NewProductionConfig()
84+
85+
config.Level.SetLevel(level.Level())
86+
87+
zapLog, err := config.Build()
7788

7889
var logger logr.Logger
7990
if err != nil {
@@ -83,6 +94,10 @@ func newLogger() logr.Logger {
8394
logger = zapr.NewLogger(zapLog)
8495
}
8596

97+
if logErr != nil {
98+
logger.Error(logErr, "invalid log level; using LevelInfo instead", zap.Error(logErr), zap.String("input", logLevel.String()))
99+
}
100+
86101
return logger
87102
}
88103

@@ -92,14 +107,6 @@ func newLogger() logr.Logger {
92107
// If conflicting or duplicate options are provided, the last one will have
93108
// precedence and be used.
94109
func NewInstrumentation(ctx context.Context, opts ...InstrumentationOption) (*Instrumentation, error) {
95-
// TODO: pass this in as an option.
96-
//
97-
// We likely want to use slog instead of logr in the longterm. Wait until
98-
// that package has enough Go version support and then switch to that so we
99-
// can expose it in an option.
100-
logger := newLogger()
101-
logger = logger.WithName("Instrumentation")
102-
103110
c, err := newInstConfig(ctx, opts)
104111
if err != nil {
105112
return nil, err
@@ -108,6 +115,11 @@ func NewInstrumentation(ctx context.Context, opts ...InstrumentationOption) (*In
108115
return nil, err
109116
}
110117

118+
// We likely want to use slog instead of logr in the longterm. Wait until
119+
// that package has enough Go version support
120+
logger := newLogger(c.logLevel)
121+
logger = logger.WithName("Instrumentation")
122+
111123
pa := process.NewAnalyzer(logger)
112124
pid, err := pa.DiscoverProcessID(ctx, &c.target)
113125
if err != nil {
@@ -179,6 +191,7 @@ type instConfig struct {
179191
additionalResAttrs []attribute.KeyValue
180192
globalImpl bool
181193
loadIndicator chan struct{}
194+
logLevel LogLevel
182195
}
183196

184197
func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfig, error) {
@@ -209,6 +222,10 @@ func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfi
209222
c.sampler = trace.AlwaysSample()
210223
}
211224

225+
if c.logLevel == logLevelUndefined {
226+
c.logLevel = LogLevelInfo
227+
}
228+
212229
return c, err
213230
}
214231

@@ -347,9 +364,10 @@ var lookupEnv = os.LookupEnv
347364
// - OTEL_SERVICE_NAME (or OTEL_RESOURCE_ATTRIBUTES): sets the service name
348365
// - OTEL_TRACES_EXPORTER: sets the trace exporter
349366
// - OTEL_GO_AUTO_GLOBAL: enables the OpenTelemetry global implementation
367+
// - OTEL_LOG_LEVEL: sets the log level
350368
//
351369
// This option may conflict with [WithTarget], [WithPID], [WithTraceExporter],
352-
// [WithServiceName] and [WithGlobal] if their respective environment variable is defined.
370+
// [WithServiceName], [WithGlobal] and [WithLogLevel] if their respective environment variable is defined.
353371
// If more than one of these options are used, the last one provided to an
354372
// [Instrumentation] will be used.
355373
//
@@ -383,6 +401,16 @@ func WithEnv() InstrumentationOption {
383401
c.globalImpl = boolVal
384402
}
385403
}
404+
if l, ok := lookupEnv(envLogLevelKey); ok {
405+
var e error
406+
level, e := ParseLogLevel(l)
407+
408+
if e == nil {
409+
c.logLevel = level
410+
}
411+
412+
err = errors.Join(err, e)
413+
}
386414
return c, err
387415
})
388416
}
@@ -490,3 +518,17 @@ func WithLoadedIndicator(indicator chan struct{}) InstrumentationOption {
490518
return c, nil
491519
})
492520
}
521+
522+
// WithLogLevel returns an [InstrumentationOption] that will configure
523+
// an [Instrumentation] to use the provided logging level.
524+
func WithLogLevel(level LogLevel) InstrumentationOption {
525+
return fnOpt(func(ctx context.Context, c instConfig) (instConfig, error) {
526+
if err := level.validate(); err != nil {
527+
return c, err
528+
}
529+
530+
c.logLevel = level
531+
532+
return c, nil
533+
})
534+
}

instrumentation_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ func TestWithEnv(t *testing.T) {
8282
require.NoError(t, err)
8383
assert.Equal(t, name, c.serviceName)
8484
})
85+
86+
t.Run("OTEL_LOG_LEVEL", func(t *testing.T) {
87+
const name = "debug"
88+
mockEnv(t, map[string]string{"OTEL_LOG_LEVEL": name})
89+
90+
c, err := newInstConfig(context.Background(), []InstrumentationOption{WithEnv()})
91+
require.NoError(t, err)
92+
assert.Equal(t, LogLevelDebug, c.logLevel)
93+
94+
const wrong = "invalid"
95+
96+
mockEnv(t, map[string]string{"OTEL_LOG_LEVEL": wrong})
97+
_, err = newInstConfig(context.Background(), []InstrumentationOption{WithEnv()})
98+
require.Error(t, err)
99+
})
85100
}
86101

87102
func TestOptionPrecedence(t *testing.T) {
@@ -172,6 +187,28 @@ func TestWithResourceAttributes(t *testing.T) {
172187
})
173188
}
174189

190+
func TestWithLogLevel(t *testing.T) {
191+
t.Run("With Valid Input", func(t *testing.T) {
192+
c, err := newInstConfig(context.Background(), []InstrumentationOption{WithLogLevel("error")})
193+
194+
require.NoError(t, err)
195+
196+
assert.Equal(t, LogLevelError, c.logLevel)
197+
198+
c, err = newInstConfig(context.Background(), []InstrumentationOption{WithLogLevel(LogLevelInfo)})
199+
200+
require.NoError(t, err)
201+
202+
assert.Equal(t, LogLevelInfo, c.logLevel)
203+
})
204+
205+
t.Run("Will Validate Input", func(t *testing.T) {
206+
_, err := newInstConfig(context.Background(), []InstrumentationOption{WithLogLevel("invalid")})
207+
208+
require.Error(t, err)
209+
})
210+
}
211+
175212
func mockEnv(t *testing.T, env map[string]string) {
176213
orig := lookupEnv
177214
t.Cleanup(func() { lookupEnv = orig })

internal/pkg/instrumentation/manager.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func (m *Manager) FilterUnusedProbes(target *process.TargetDetails) {
137137
}
138138

139139
if !funcsFound {
140-
m.logger.Info("no functions found for probe, removing", "name", name)
140+
m.logger.V(1).Info("no functions found for probe, removing", "name", name)
141141
delete(m.probes, name)
142142
}
143143
}
@@ -172,13 +172,13 @@ func (m *Manager) Run(ctx context.Context, target *process.TargetDetails) error
172172
for {
173173
select {
174174
case <-ctx.Done():
175-
m.logger.Info("shutting down all probes due to context cancellation")
175+
m.logger.V(1).Info("shutting down all probes due to context cancellation")
176176
err := m.cleanup(target)
177177
err = errors.Join(err, ctx.Err())
178178
m.closingErrors <- err
179179
return nil
180180
case <-m.done:
181-
m.logger.Info("shutting down all probes due to signal")
181+
m.logger.V(1).Info("shutting down all probes due to signal")
182182
err := m.cleanup(target)
183183
m.closingErrors <- err
184184
return nil
@@ -205,23 +205,23 @@ func (m *Manager) load(target *process.TargetDetails) error {
205205

206206
// Load probes
207207
for name, i := range m.probes {
208-
m.logger.Info("loading probe", "name", name)
208+
m.logger.V(0).Info("loading probe", "name", name)
209209
err := i.Load(exe, target)
210210
if err != nil {
211211
m.logger.Error(err, "error while loading probes, cleaning up", "name", name)
212212
return errors.Join(err, m.cleanup(target))
213213
}
214214
}
215215

216-
m.logger.Info("loaded probes to memory", "total_probes", len(m.probes))
216+
m.logger.V(1).Info("loaded probes to memory", "total_probes", len(m.probes))
217217
return nil
218218
}
219219

220220
func (m *Manager) mount(target *process.TargetDetails) error {
221221
if target.AllocationDetails != nil {
222-
m.logger.Info("Mounting bpffs", "allocations_details", target.AllocationDetails)
222+
m.logger.V(1).Info("Mounting bpffs", "allocations_details", target.AllocationDetails)
223223
} else {
224-
m.logger.Info("Mounting bpffs")
224+
m.logger.V(1).Info("Mounting bpffs")
225225
}
226226
return bpffs.Mount(target)
227227
}
@@ -233,7 +233,7 @@ func (m *Manager) cleanup(target *process.TargetDetails) error {
233233
err = errors.Join(err, i.Close())
234234
}
235235

236-
m.logger.Info("Cleaning bpffs")
236+
m.logger.V(1).Info("Cleaning bpffs")
237237
return errors.Join(err, bpffs.Cleanup(target))
238238
}
239239

internal/pkg/instrumentation/probe/probe.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (i *Base[BPFObj, BPFEvent]) buildObj(exec *link.Executable, td *process.Tar
149149
links, err := up.Fn(up.Sym, exec, td, obj)
150150
if err != nil {
151151
if up.Optional {
152-
i.Logger.Info("failed to attach optional uprobe", "probe", i.ID, "symbol", up.Sym, "error", err)
152+
i.Logger.V(1).Info("failed to attach optional uprobe", "probe", i.ID, "symbol", up.Sym, "error", err)
153153
continue
154154
}
155155
return nil, err
@@ -175,7 +175,7 @@ func (i *Base[BPFObj, BPFEvent]) Run(dest chan<- *Event) {
175175
}
176176

177177
if record.LostSamples != 0 {
178-
i.Logger.Info("perf event ring buffer full", "dropped", record.LostSamples)
178+
i.Logger.V(1).Info("perf event ring buffer full", "dropped", record.LostSamples)
179179
continue
180180
}
181181

@@ -211,7 +211,7 @@ func (i *Base[BPFObj, BPFEvent]) Close() error {
211211
err = errors.Join(err, c.Close())
212212
}
213213
if err == nil {
214-
i.Logger.Info("Closed", "Probe", i.ID)
214+
i.Logger.V(1).Info("Closed", "Probe", i.ID)
215215
}
216216
return err
217217
}

internal/pkg/opentelemetry/controller.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ func (c *Controller) getTracer(pkg string) trace.Tracer {
5151
// Trace creates a trace span for event.
5252
func (c *Controller) Trace(event *probe.Event) {
5353
for _, se := range event.SpanEvents {
54-
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())
54+
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())
5555
ctx := context.Background()
5656

5757
if se.SpanContext == nil {
58-
c.logger.Info("got event without context - dropping")
58+
c.logger.V(1).Info("got event without context - dropping")
5959
return
6060
}
6161

internal/pkg/process/allocate.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func Allocate(logger logr.Logger, pid int) (*AllocationDetails, error) {
4848
}
4949

5050
mapSize := uint64(os.Getpagesize() * nCPU * 8)
51-
logger.Info(
51+
logger.V(1).Info(
5252
"Requesting memory allocation",
5353
"size", mapSize,
5454
"page size", os.Getpagesize(),
@@ -59,7 +59,7 @@ func Allocate(logger logr.Logger, pid int) (*AllocationDetails, error) {
5959
return nil, err
6060
}
6161

62-
logger.Info(
62+
logger.V(1).Info(
6363
"mmaped remote memory",
6464
"start_addr", fmt.Sprintf("0x%x", addr),
6565
"end_addr", fmt.Sprintf("0x%x", addr+mapSize),
@@ -81,7 +81,7 @@ func remoteAllocate(logger logr.Logger, pid int, mapSize uint64) (uint64, error)
8181
}
8282

8383
defer func() {
84-
logger.Info("Detaching from process", "pid", pid)
84+
logger.V(0).Info("Detaching from process", "pid", pid)
8585
err := program.Detach()
8686
if err != nil {
8787
logger.Error(err, "Failed to detach ptrace", "pid", pid)
@@ -91,7 +91,7 @@ func remoteAllocate(logger logr.Logger, pid int, mapSize uint64) (uint64, error)
9191
if err := program.SetMemLockInfinity(); err != nil {
9292
logger.Error(err, "Failed to set memlock on process")
9393
} else {
94-
logger.Info("Set memlock on process successfully")
94+
logger.V(1).Info("Set memlock on process successfully")
9595
}
9696

9797
fd := -1

internal/pkg/process/analyze.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func (a *Analyzer) Analyze(pid int, relevantFuncs map[string]interface{}) (*Targ
103103
return nil, err
104104
}
105105
for _, fn := range funcs {
106-
a.logger.Info("found function", "function_name", fn)
106+
a.logger.V(1).Info("found function", "function_name", fn)
107107
}
108108

109109
result.Functions = funcs
@@ -145,7 +145,7 @@ func (a *Analyzer) findFunctions(elfF *elf.File, relevantFuncs map[string]interf
145145
result, err := binary.FindFunctionsUnStripped(elfF, relevantFuncs)
146146
if err != nil {
147147
if errors.Is(err, elf.ErrNoSymbols) {
148-
a.logger.Info("No symbols found in binary, trying to find functions using .gosymtab")
148+
a.logger.V(1).Info("No symbols found in binary, trying to find functions using .gosymtab")
149149
return binary.FindFunctionsStripped(elfF, relevantFuncs)
150150
}
151151
return nil, err

0 commit comments

Comments
 (0)