88 "runtime"
99 "runtime/debug"
1010 "strings"
11+ "sync"
1112 "time"
1213
1314 "github.com/lmittmann/tint"
@@ -41,15 +42,20 @@ func SLog(ctx context.Context) *slog.Logger {
4142 return Log (ctx ).SLog ()
4243}
4344
44- type iLogger struct {
45- ctx context.Context
46- // Function to exit the application, defaults to `os.Exit()`
47- ExitFunc exitFunc
48-
45+ // LogEntry handles logging functionality with immutable chained calls
46+ type LogEntry struct {
47+ ctx context.Context
4948 log * slog.Logger
5049 stackTraces bool
5150}
5251
52+ // logEntryPool maintains a pool of LogEntry objects to reduce GC pressure
53+ var logEntryPool = sync.Pool {
54+ New : func () interface {} {
55+ return & LogEntry {}
56+ },
57+ }
58+
5359type LogOptions struct {
5460 * slog.HandlerOptions
5561 PrintFormat string
@@ -90,6 +96,7 @@ func ParseLevel(levelStr string) (slog.Level, error) {
9096 }
9197}
9298
99+ // NewLogger creates a new instance of LogEntry with the provided context and options
93100func NewLogger (ctx context.Context , opts * LogOptions ) * LogEntry {
94101 logLevel := opts .Level .Level ()
95102 outputWriter := os .Stdout
@@ -105,189 +112,175 @@ func NewLogger(ctx context.Context, opts *LogOptions) *LogEntry {
105112 }
106113
107114 tintHandler := tint .NewHandler (outputWriter , handlerOptions )
108-
109115 log := slog .New (tintHandler )
110-
111116 slog .SetDefault (log )
112117
113- il := & iLogger {ctx : ctx , log : log , stackTraces : opts .ShowStackTrace }
114- return newLogEntry (il )
115- }
116-
117- func (l * iLogger ) clone (ctx context.Context ) * iLogger {
118- sl := * l .log
119- return & iLogger {ctx : ctx , log : & sl , stackTraces : l .stackTraces }
120- }
121-
122- func (l * iLogger ) WithError (err error ) {
123- l .log = l .log .With (tint .Err (err ))
118+ // Get a LogEntry from the pool
119+ entry := logEntryPool .Get ().(* LogEntry )
120+ entry .ctx = ctx
121+ entry .log = log
122+ entry .stackTraces = opts .ShowStackTrace
123+
124+ return entry
124125}
125126
126- func (l * iLogger ) WithAttr (attr ... any ) {
127- l .log = l .log .With (attr ... )
128- }
129-
130- func (l * iLogger ) WithField (key string , value any ) {
131- l .log = l .log .With (key , value )
132- }
133-
134- func (l * iLogger ) With (args ... any ) {
135- l .log = l .log .With (args ... )
136- }
137-
138- func (l * iLogger ) _ctx () context.Context {
139- if l .ctx == nil {
140- return context .Background ()
127+ // Release returns the LogEntry to the pool for reuse
128+ // Call this when you're done with a LogEntry and won't use it again
129+ func (e * LogEntry ) Release () {
130+ if e == nil {
131+ return
141132 }
142- return l .ctx
143- }
144133
145- func (l * iLogger ) Log (ctx context.Context , level slog.Level , msg string , fields ... any ) {
146- l .log .Log (ctx , level , msg , fields ... )
147- }
134+ // Reset fields before returning to pool
135+ e .ctx = nil
136+ e .log = nil
137+ e .stackTraces = false
148138
149- func (l * iLogger ) Logf (ctx context.Context , level slog.Level , format string , args ... interface {}) {
150- if l .Enabled (ctx , level ) {
151- l .log .Log (ctx , level , fmt .Sprintf (format , args ... ))
152- }
153- }
154-
155- func (l * iLogger ) Trace (msg string , args ... any ) {
156- l .Debug (msg , args ... )
157- }
158-
159- func (l * iLogger ) Debug (msg string , args ... any ) {
160- log := l .withFileLineNum ()
161- log .DebugContext (l ._ctx (), msg , args ... )
162- }
163-
164- func (l * iLogger ) Info (msg string , args ... any ) {
165- l .log .InfoContext (l ._ctx (), msg , args ... )
166- }
167-
168- func (l * iLogger ) Warn (msg string , args ... any ) {
169- l .log .WarnContext (l ._ctx (), msg , args ... )
139+ logEntryPool .Put (e )
170140}
171141
172- func (l * iLogger ) Error (msg string , args ... any ) {
173- log := l .withFileLineNum ()
174-
175- if l .stackTraces {
176- log .ErrorContext (l ._ctx (), fmt .Sprintf (" %s\n %s\n " , msg , debug .Stack ()), args ... )
142+ // clone creates a new LogEntry with the same properties as the original
143+ func (e * LogEntry ) clone () * LogEntry {
144+ if e == nil {
145+ return NewLogger (context .Background (), DefaultLogOptions ())
177146 }
178147
179- log . ErrorContext ( l . _ctx (), msg , args ... )
180- }
181-
182- func ( l * iLogger ) Fatal ( msg string , args ... any ) {
183- log := l . withFileLineNum ()
148+ // Get a new entry from the pool
149+ clone := logEntryPool . Get ().( * LogEntry )
150+ clone . ctx = e . ctx
151+ clone . log = e . log
152+ clone . stackTraces = e . stackTraces
184153
185- if l .stackTraces {
186- log .ErrorContext (l ._ctx (), fmt .Sprintf (" %s\n %s\n " , msg , debug .Stack ()), args ... )
187- }
188- l .log .ErrorContext (l ._ctx (), msg , args ... )
189- l .Exit (1 )
154+ return clone
190155}
191156
192- func (l * iLogger ) Panic (msg string , _ ... any ) {
193- panic (fmt .Sprintf (" %s\n %s\n " , msg , debug .Stack ()))
157+ // WithContext returns a new LogEntry with the given context
158+ func (e * LogEntry ) WithContext (ctx context.Context ) * LogEntry {
159+ clone := e .clone ()
160+ clone .ctx = ctx
161+ return clone
194162}
195163
196- func (l * iLogger ) Exit (code int ) {
197- if l .ExitFunc == nil {
198- l .ExitFunc = os .Exit
199- }
200- l .ExitFunc (code )
164+ // WithError returns a new LogEntry with the error added
165+ func (e * LogEntry ) WithError (err error ) * LogEntry {
166+ return e .With (tint .Err (err ))
201167}
202168
203- func (l * iLogger ) Enabled (ctx context.Context , level slog.Level ) bool {
204- return l .log .Enabled (ctx , level )
169+ // WithField returns a new LogEntry with the field added
170+ func (e * LogEntry ) WithField (key string , value any ) * LogEntry {
171+ return e .With (key , value )
205172}
206173
207- func (l * iLogger ) withFileLineNum () * slog.Logger {
208- _ , file , line , ok := runtime .Caller (CallerDepth )
209- if ok {
210- return l .log .With (tint .Attr (FileLineAttr , slog .Any ("file" , fmt .Sprintf ("%s:%d" , file , line ))))
174+ // With returns a new LogEntry with the provided attributes added
175+ func (e * LogEntry ) With (args ... any ) * LogEntry {
176+ // No args, return the same logger
177+ if len (args ) == 0 {
178+ return e
211179 }
212- return l .log
213- }
214-
215- // LogEntry Need a type to handle the chained calls.
216- type LogEntry struct {
217- l * iLogger
218- }
219180
220- func newLogEntry (l * iLogger ) * LogEntry {
221- return & LogEntry {l : l }
181+ clone := e .clone ()
182+ clone .log = clone .log .With (args ... )
183+ return clone
222184}
223185
224- type exitFunc func (int )
225-
226- func (e * LogEntry ) LevelEnabled (ctx context.Context , level slog.Level ) bool {
227- return e .l .Enabled (ctx , level )
228- }
229-
230- func (e * LogEntry ) SLog () * slog.Logger {
231- return e .l .log
232- }
233-
234- func (e * LogEntry ) WithContext (ctx context.Context ) * LogEntry {
235- return newLogEntry (e .l .clone (ctx ))
186+ // _ctx returns the context or background if nil
187+ func (e * LogEntry ) _ctx () context.Context {
188+ if e .ctx == nil {
189+ return context .Background ()
190+ }
191+ return e .ctx
236192}
237193
194+ // Log logs a message at the given level
238195func (e * LogEntry ) Log (ctx context.Context , level slog.Level , msg string , fields ... any ) {
239- e .l .Log (ctx , level , msg , fields ... )
196+ e .log .Log (ctx , level , msg , fields ... )
240197}
198+
199+ // Logf logs a formatted message at the given level
241200func (e * LogEntry ) Logf (ctx context.Context , level slog.Level , format string , args ... interface {}) {
242- e .l .Logf (ctx , level , format , args ... )
201+ if e .Enabled (ctx , level ) {
202+ e .log .Log (ctx , level , fmt .Sprintf (format , args ... ))
203+ }
243204}
244205
206+ // Trace logs a message at debug level (alias for backward compatibility)
245207func (e * LogEntry ) Trace (msg string , args ... any ) {
246- e .l . Debug (msg , args ... )
208+ e .Debug (msg , args ... )
247209}
248210
211+ // Debug logs a message at debug level
249212func (e * LogEntry ) Debug (msg string , args ... any ) {
250- e .l .Debug (msg , args ... )
213+ log := e .withFileLineNum ()
214+ log .DebugContext (e ._ctx (), msg , args ... )
251215}
252216
217+ // Info logs a message at info level
253218func (e * LogEntry ) Info (msg string , args ... any ) {
254- e .l . Info ( msg , args ... )
219+ e .log . InfoContext ( e . _ctx (), msg , args ... )
255220}
256221
222+ // Printf logs a formatted message at info level
257223func (e * LogEntry ) Printf (format string , args ... any ) {
258- e .l . Logf (e . l ._ctx (), slog .LevelInfo , format , args ... )
224+ e .Logf (e ._ctx (), slog .LevelInfo , format , args ... )
259225}
260226
227+ // Warn logs a message at warn level
261228func (e * LogEntry ) Warn (msg string , args ... any ) {
262- e .l . Warn ( msg , args ... )
229+ e .log . WarnContext ( e . _ctx (), msg , args ... )
263230}
264231
232+ // Error logs a message at error level
265233func (e * LogEntry ) Error (msg string , args ... any ) {
266- e .l .Error (msg , args ... )
234+ log := e .withFileLineNum ()
235+
236+ if e .stackTraces {
237+ log .ErrorContext (e ._ctx (), fmt .Sprintf (" %s\n %s\n " , msg , debug .Stack ()), args ... )
238+ }
239+
240+ log .ErrorContext (e ._ctx (), msg , args ... )
267241}
268242
243+ // Fatal logs a message at error level and exits with code 1
269244func (e * LogEntry ) Fatal (msg string , args ... any ) {
270- e .l .Fatal (msg , args ... )
245+ log := e .withFileLineNum ()
246+
247+ if e .stackTraces {
248+ log .ErrorContext (e ._ctx (), fmt .Sprintf (" %s\n %s\n " , msg , debug .Stack ()), args ... )
249+ }
250+ e .log .ErrorContext (e ._ctx (), msg , args ... )
251+ e .Exit (1 )
271252}
272253
254+ // Panic logs a message and panics
273255func (e * LogEntry ) Panic (msg string , _ ... any ) {
274- e . l . Panic ( msg )
256+ panic ( fmt . Sprintf ( " %s \n %s \n " , msg , debug . Stack ()) )
275257}
276258
277- func ( e * LogEntry ) WithAttr ( attr ... any ) * LogEntry {
278- e . l . WithAttr ( attr ... )
279- return e
259+ // Exit terminates the application with the given code
260+ func ( e * LogEntry ) Exit ( code int ) {
261+ os . Exit ( code )
280262}
281263
282- func ( e * LogEntry ) WithError ( err error ) * LogEntry {
283- e . l . WithError ( err )
284- return e
264+ // Enabled returns whether the logger will log at the given level
265+ func ( e * LogEntry ) Enabled ( ctx context. Context , level slog. Level ) bool {
266+ return e . log . Enabled ( ctx , level )
285267}
286- func (e * LogEntry ) WithField (key string , value any ) * LogEntry {
287- e .l .WithField (key , value )
288- return e
268+
269+ // LevelEnabled is an alias for Enabled for backward compatibility
270+ func (e * LogEntry ) LevelEnabled (ctx context.Context , level slog.Level ) bool {
271+ return e .Enabled (ctx , level )
289272}
290- func (e * LogEntry ) With (args ... any ) * LogEntry {
291- e .l .With (args ... )
292- return e
273+
274+ // SLog returns the underlying slog.Logger
275+ func (e * LogEntry ) SLog () * slog.Logger {
276+ return e .log
277+ }
278+
279+ // withFileLineNum adds file and line information to the log entry
280+ func (e * LogEntry ) withFileLineNum () * slog.Logger {
281+ _ , file , line , ok := runtime .Caller (CallerDepth )
282+ if ok {
283+ return e .log .With (tint .Attr (FileLineAttr , slog .Any ("file" , fmt .Sprintf ("%s:%d" , file , line ))))
284+ }
285+ return e .log
293286}
0 commit comments