← Home About Archive Photos Replies Also on Micro.blog
  • How Does Charm Logger Format Logs?

    I woke up today wondering how charm’s logger library (https://github.com/charmbracelet/log) actually handles log levels. I know they have some levels predefined in an enum, but what’re they doing with that? This is relevant to #golang #opensource #programming #readingcode

    logger.Log is the natural entrypoint, as it’s the actual code doing the logging https://github.com/charmbracelet/log/blob/1e6353e3ca793f1177148e09f990ef220e19b037/logger.go#L56

    It dispatches to logger.handle, which has this curious setup:

    func (l *Logger) handle(level Level, ts time.Time, frames []runtime.Frame, msg interface{}, keyvals ...interface{}) {
    
    	var kvs []interface{}
    	if l.reportTimestamp && !ts.IsZero() {
    		kvs = append(kvs, TimestampKey, ts)
    	}
    
    	if level != noLevel {
    		kvs = append(kvs, LevelKey, level)
    	}
    

    I think this means that no matter your log level, whether the log will print or not, the logger always constructs a log line for you, unless you match the noLevel?

    As a note, noLevel is private, but is defined as math.MaxInt32. This probably means the most efficient way to generate no logs is to declare your own equivalent noLog level as a default, but I can come back to that in a few paragraphs.

    So how do the logs get written? Scanning to the end of the function, it’s this straightforwards l.b.WriteTo(l.w). l is a pointer to the logger struct, b is a bytes.buffer inside that logger, and it’s written out to whatever destination is stored in an io.writer called w.

    So if we can understand how b get populated, we’ve cracked how these logs are written. The answer is in this switch statement, though it’s indirect:

    	switch l.formatter {
    	case LogfmtFormatter:
    		l.logfmtFormatter(kvs...)
    	case JSONFormatter:
    		l.jsonFormatter(kvs...)
    	default:
    		l.textFormatter(kvs...)
    

    Each of these formatters accepts responsibility for populating the buffer with an appropriately formatted log output. The logfmtFormatter, for example, begins with e := logfmt.NewEncoder(&l.b), accepting a reference to the buffer.

    So, that’s the story. Logs are appended to a key value store, which is handed to a formatter that writes formatted output to a buffer on the logger, which is then written.

    I’ll come back to the question of figuring out if the best performing no-opt logger is setting hte logLevel to math.MaxInt32 in my next post about this.

    → 7:23 AM, Jan 24
  • RSS
  • JSON Feed
  • Micro.blog