go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/logging/teelogger/teelogger.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package teelogger 16 17 import ( 18 "context" 19 20 "go.chromium.org/luci/common/logging" 21 ) 22 23 type leveledLogger struct { 24 minLevel logging.Level 25 logger logging.Logger 26 } 27 28 type teeImpl struct { 29 l []leveledLogger 30 } 31 32 func (t *teeImpl) Debugf(fmt string, args ...any) { 33 t.LogCall(logging.Debug, 1, fmt, args) 34 } 35 36 func (t *teeImpl) Infof(fmt string, args ...any) { 37 t.LogCall(logging.Info, 1, fmt, args) 38 } 39 40 func (t *teeImpl) Warningf(fmt string, args ...any) { 41 t.LogCall(logging.Warning, 1, fmt, args) 42 } 43 44 func (t *teeImpl) Errorf(fmt string, args ...any) { 45 t.LogCall(logging.Error, 1, fmt, args) 46 } 47 48 func (t *teeImpl) LogCall(level logging.Level, calldepth int, f string, args []any) { 49 for _, l := range t.l { 50 if level >= l.minLevel { 51 l.logger.LogCall(level, calldepth+1, f, args) 52 } 53 } 54 } 55 56 // Use adds a tee logger to the context, using the logger factory in 57 // the context, as well as the other loggers produced by given factories. 58 // 59 // We use factories (instead of logging.Logger instances), since we must be able 60 // to produce logging.Logger instances bound to contexts to be able to use 61 // logging levels are fields (they are part of the context state). 62 func Use(ctx context.Context, factories ...logging.Factory) context.Context { 63 if cur := logging.GetFactory(ctx); cur != nil { 64 factories = append([]logging.Factory{cur}, factories...) 65 } 66 return logging.SetFactory(ctx, func(ctx context.Context) logging.Logger { 67 ll := make([]leveledLogger, len(factories)) 68 for i, f := range factories { 69 logger := f(ctx) 70 ll[i] = leveledLogger{ 71 logger: logger, 72 minLevel: logging.GetLevel(ctx), 73 } 74 } 75 return &teeImpl{ll} 76 }) 77 } 78 79 // Filtered is a static representation of a single entry to filter messages to 80 // loggers by provided level. 81 type Filtered struct { 82 Factory logging.Factory 83 Level logging.Level 84 } 85 86 // UseFiltered adds a tee logger to the context, using the logger factory in 87 // the context, as well as the other provided by given filtereds. 88 // Filtered loggers ignore the current logging level in the context. 89 // 90 // We use factories (instead of logging.Logger instances), since we must be able 91 // to produce logging.Logger instances bound to contexts to be able to use 92 // logging levels are fields (they are part of the context state). 93 // The logger instance bound to context is used with level provided by context. 94 func UseFiltered(ctx context.Context, filtereds ...Filtered) context.Context { 95 cur := logging.GetFactory(ctx) 96 count := len(filtereds) 97 if cur != nil { 98 count += 1 99 } 100 return logging.SetFactory(ctx, func(ctx context.Context) logging.Logger { 101 ll := make([]leveledLogger, count) 102 for i, f := range filtereds { 103 ll[i] = leveledLogger{ 104 logger: f.Factory(ctx), 105 minLevel: f.Level, 106 } 107 } 108 if cur != nil { 109 ll[count-1] = leveledLogger{ 110 logger: cur(ctx), 111 minLevel: logging.GetLevel(ctx), 112 } 113 } 114 return &teeImpl{ll} 115 }) 116 }