@@ -31,11 +31,29 @@ func WithLevel(level int) Option {
3131 }
3232}
3333
34+ // WithCallerAtLevel enables automatic caller information capture for log
35+ // entries with level less or equal passed threshold.
36+ //
37+ // When enabled, the logger will automatically capture and include caller
38+ // information (formatted as "/path/to/file:line") as a field in log entries whose
39+ // level is less or equal the threshold. For example, WithCallerAtLevel(LevelWarning)
40+ // will add caller information to Error and Warning logs, but no others.
41+ //
42+ // By default, automatic caller capture is disabled. Use this option when you
43+ // need to track the source location of important log messages like errors and
44+ // warnings.
45+ func WithCallerAtLevel (level int ) Option {
46+ return func (l * Logger ) {
47+ l .callerMaxLevel = level
48+ }
49+ }
50+
3451// mappers holds mapping configurations for [Logger].
3552type mappers struct {
3653 name func (name string ) fields.Field
3754 error func (err error ) fields.Field
3855 stackTrace func (st * stacktrace.Stack ) fields.Field
56+ caller func (frame stacktrace.Frame ) fields.Field
3957}
4058
4159type mapperOption func (* mappers )
@@ -179,12 +197,37 @@ func WithStackTraceMapper(fn func(st *stacktrace.Stack) fields.Field) Option {
179197 })
180198}
181199
200+ // DefaultCallerMapper is the default mapper for converting caller frames to
201+ // fields. It creates a field with key "caller" and formats the frame as
202+ // "file:line".
203+ //
204+ // Example output: "/path/to/logger.go:123".
205+ func DefaultCallerMapper (frame stacktrace.Frame ) fields.Field {
206+ return fields .F ("caller" , frame .FullPath ())
207+ }
208+
209+ // WithCallerMapper sets a custom caller mapper for the logger.
210+ //
211+ // The caller mapper controls how caller frames (captured via automatic caller
212+ // capture with [WithCallerAtLevel]) are converted to fields. By default,
213+ // [DefaultCallerMapper] is used, which creates fields with the key "caller"
214+ // and formats frames as "file:line".
215+ //
216+ // Custom mappers can change the field key, format caller information
217+ // differently, or include additional frame details like function names.
218+ func WithCallerMapper (fn func (frame stacktrace.Frame ) fields.Field ) Option {
219+ return withMapperOption (func (cfg * mappers ) {
220+ cfg .caller = fn
221+ })
222+ }
223+
182224// defaultMappers returns a mappers structure initialized with default mappers.
183225func defaultMappers () * mappers {
184226 return & mappers {
185227 name : DefaultNameMapper ,
186228 error : DefaultErrorMapper ,
187229 stackTrace : DefaultStackTraceMapper ,
230+ caller : DefaultCallerMapper ,
188231 }
189232}
190233
@@ -203,6 +246,11 @@ type Logger struct {
203246
204247 name string
205248 nameFormatter func (prev , next string ) string
249+
250+ // callerMaxLevel is the maximum log-level at which caller information is
251+ // automatically captured and added to log entries. Levels at or below this
252+ // threshold will include caller information. Set to -1 to disable.
253+ callerMaxLevel int
206254}
207255
208256// New creates new [Logger] with maximum log-level set to LevelInfo and default
@@ -215,11 +263,12 @@ func New(adapter Adapter, opts ...Option) Logger {
215263 }
216264
217265 l := Logger {
218- maxLevel : LevelInfo ,
219- adapter : adapter ,
220- mappers : defaultMappers (),
221- name : "" ,
222- nameFormatter : NameFormatterHierarchical ,
266+ maxLevel : LevelInfo ,
267+ adapter : adapter ,
268+ mappers : defaultMappers (),
269+ name : "" ,
270+ nameFormatter : NameFormatterHierarchical ,
271+ callerMaxLevel : math .MinInt ,
223272 }
224273
225274 lp := & l
@@ -234,11 +283,12 @@ func New(adapter Adapter, opts ...Option) Logger {
234283// will also create no-op loggers.
235284func NewNop () Logger {
236285 return Logger {
237- maxLevel : math .MaxInt ,
238- adapter : nil ,
239- mappers : nil ,
240- name : "" ,
241- nameFormatter : nil ,
286+ maxLevel : math .MaxInt ,
287+ adapter : nil ,
288+ mappers : nil ,
289+ name : "" ,
290+ nameFormatter : nil ,
291+ callerMaxLevel : math .MinInt ,
242292 }
243293}
244294
@@ -258,7 +308,7 @@ func (l Logger) IsNop() bool {
258308// where the application cannot continue. It's OK to pass nil as the error.
259309// To attach a stack trace, use [Logger.WithStackTrace].
260310func (l Logger ) Error (msg string , err error , fs ... fields.Field ) {
261- l .Log (LevelError , msg , err , fs ... )
311+ l .log (LevelError , msg , err , fs ... )
262312}
263313
264314// Warning logs a message with the [LevelWarning] log-level.
@@ -267,7 +317,7 @@ func (l Logger) Error(msg string, err error, fs ...fields.Field) {
267317// prevent the application from continuing, such as a deprecated API usage or
268318// a retry-able failure. For warnings with an error, use [Logger.WarningE].
269319func (l Logger ) Warning (msg string , fs ... fields.Field ) {
270- l .Log (LevelWarning , msg , nil , fs ... )
320+ l .log (LevelWarning , msg , nil , fs ... )
271321}
272322
273323// WarningE logs a message with the [LevelWarning] log-level and the provided
@@ -276,55 +326,55 @@ func (l Logger) Warning(msg string, fs ...fields.Field) {
276326// Use WarningE to log any recoverable error, such as an error during a remote
277327// API call where the service did not respond and the application will retry.
278328func (l Logger ) WarningE (msg string , err error , fs ... fields.Field ) {
279- l .Log (LevelWarning , msg , err , fs ... )
329+ l .log (LevelWarning , msg , err , fs ... )
280330}
281331
282332// Info logs a message with the [LevelInfo] log-level.
283333//
284334// Use Info to log informational messages that highlight the progress of the
285335// application.
286336func (l Logger ) Info (msg string , fs ... fields.Field ) {
287- l .Log (LevelInfo , msg , nil , fs ... )
337+ l .log (LevelInfo , msg , nil , fs ... )
288338}
289339
290340// InfoE logs a message with the [LevelInfo] log-level and the provided error.
291341//
292342// Use InfoE to log informational messages that highlight the progress of the
293343// application along with an error.
294344func (l Logger ) InfoE (msg string , err error , fs ... fields.Field ) {
295- l .Log (LevelInfo , msg , err , fs ... )
345+ l .log (LevelInfo , msg , err , fs ... )
296346}
297347
298348// Debug logs a message with the [LevelDebug] log-level.
299349//
300350// Use Debug to log detailed information that is useful during development and
301351// debugging.
302352func (l Logger ) Debug (msg string , fs ... fields.Field ) {
303- l .Log (LevelDebug , msg , nil , fs ... )
353+ l .log (LevelDebug , msg , nil , fs ... )
304354}
305355
306356// DebugE logs a message with the [LevelDebug] log-level and the provided error.
307357//
308358// Use DebugE to log detailed information that is useful during development and
309359// debugging along with an error.
310360func (l Logger ) DebugE (msg string , err error , fs ... fields.Field ) {
311- l .Log (LevelDebug , msg , err , fs ... )
361+ l .log (LevelDebug , msg , err , fs ... )
312362}
313363
314364// Trace logs a message with the [LevelTrace] log-level.
315365//
316366// Use Trace to log very detailed information, typically of interest only when
317367// diagnosing problems.
318368func (l Logger ) Trace (msg string , fs ... fields.Field ) {
319- l .Log (LevelTrace , msg , nil , fs ... )
369+ l .log (LevelTrace , msg , nil , fs ... )
320370}
321371
322372// TraceE logs a message with the [LevelTrace] log-level and the provided error.
323373//
324374// Use TraceE to log very detailed information, typically of interest only when
325375// diagnosing problems along with an error.
326376func (l Logger ) TraceE (msg string , err error , fs ... fields.Field ) {
327- l .Log (LevelTrace , msg , err , fs ... )
377+ l .log (LevelTrace , msg , err , fs ... )
328378}
329379
330380// Log logs a message with the given log-level, optional error, and fields.
@@ -340,10 +390,26 @@ func (l Logger) TraceE(msg string, err error, fs ...fields.Field) {
340390//
341391// For no-op loggers, this method returns immediately without any operation.
342392func (l Logger ) Log (level int , msg string , err error , fs ... fields.Field ) {
393+ l .log (level , msg , err , fs ... )
394+ }
395+
396+ // log is the internal logging method, its sole reason to exist is to uniformly
397+ // catch caller frame in case it is required.
398+ //
399+ //revive:disable-next-line:confusing-naming
400+ func (l Logger ) log (level int , msg string , err error , fs ... fields.Field ) {
343401 if level > l .maxLevel || l .IsNop () {
344402 return
345403 }
346404
405+ if level <= l .callerMaxLevel {
406+ const callerSkip = 2 // skip log and the calling method (Error, Info, etc.)
407+
408+ frame := stacktrace .CaptureCaller (callerSkip )
409+
410+ fs = append (fs , l .mappers .caller (frame ))
411+ }
412+
347413 if l .name != "" {
348414 fs = append (fs , l .mappers .name (l .name ))
349415 }
@@ -435,7 +501,9 @@ func (l Logger) Flush() error {
435501// created via [New] or [NewNop]. Zero-value loggers should not be used, but in
436502// rare cases it is required to check if value is not initialized.
437503func (l Logger ) IsZero () bool {
438- return l .adapter == nil && l .maxLevel == 0 && l .mappers == nil
504+ // properly created loggers have adapters set and callerMaxLevel initialized,
505+ // therefore check for those fields should suffice.
506+ return l .adapter == nil && l .callerMaxLevel == 0
439507}
440508
441509// formatterIsEqual checks if f1 is equal to f2.
@@ -470,6 +538,6 @@ func IsEqual(l1, l2 Logger) bool {
470538// expect an error logging function rather than a full logger.
471539func NewErrorLogger (lgr Logger , level int ) e.ErrorLogger {
472540 return func (msg string , err error , fs ... fields.Field ) {
473- lgr .Log (level , msg , err , fs ... )
541+ lgr .log (level , msg , err , fs ... )
474542 }
475543}
0 commit comments