logger
packageAPI reference for the logger
package.
Imports
(8)clefEntry
clefEntry is the JSON structure for a Compact Log Event Format (CLEF) line.
Fields are flattened at root level; @l is omitted for Information.
type clefEntry struct
Fields
| Name | Type | Description |
|---|---|---|
| Timestamp | time.Time | json:"@t" |
| Level | string | json:"@l,omitempty" |
| Message | string | json:"@m" |
CLEFSink
CLEFSink writes log entries as CLEF-compatible JSON lines.
Each line is a single JSON object with @t, @l (omitted for Information),
@m, and all structured fields flattened at the root level, matching the
format emitted by Serilog’s RenderedCompactJsonFormatter, so that Go and
C# service logs are queryable with the same field selectors in Grafana/Loki.
type CLEFSink struct
Methods
Log writes e as a CLEF JSON line to the underlying writer.
Parameters
Returns
func (*CLEFSink) Log(e Entry) error
{
// Build a flat map so structured fields sit at the root level.
m := make(map[string]any, len(e.Fields)+3)
for k, v := range e.Fields {
m[k] = v
}
m["@t"] = e.Time.UTC().Format(time.RFC3339Nano)
m["@m"] = e.Msg
// @l is omitted for Information per the CLEF spec.
if lvl, ok := clefLevel[e.Level]; ok {
m["@l"] = lvl
}
b, err := json.Marshal(m)
if err != nil {
return err
}
_, err = c.w.Write(append(b, '\n'))
return err
}
Fields
| Name | Type | Description |
|---|---|---|
| w | io.Writer |
Level
Level represents log severity.
type Level int
Field
Field is a single structured key/value pair.
type Field struct
Fields
| Name | Type | Description |
|---|---|---|
| Key | string | json:"key" |
| Value | interface{} | json:"value" |
Entry
Entry is the log payload passed to sinks.
type Entry struct
Fields
| Name | Type | Description |
|---|---|---|
| Level | string | json:"level" |
| Time | time.Time | json:"time" |
| Msg | string | json:"msg" |
| Fields | map[string]interface{} | json:"fields,omitempty" |
| TraceID | string | json:"trace_id,omitempty" |
| SpanID | string | json:"span_id,omitempty" |
Logger
Logger is the public logging contract.
type Logger interface
Methods
func CloseAsync(...)
Option
Option configures the concrete logger on creation.
type Option func(*stdLogger)
stdLogger
type stdLogger struct
Methods
Parameters
func (*stdLogger) RegisterSink(s Sink)
{
l.shared.mu.Lock()
defer l.shared.mu.Unlock()
l.shared.sinks = append(l.shared.sinks, s)
}
SetLevel changes the log level at runtime.
Parameters
func (*stdLogger) SetLevel(level Level)
{
l.shared.mu.Lock()
defer l.shared.mu.Unlock()
l.shared.level = level
}
With returns a non-owning derived logger that shares sinks but has extra bound fields. Closing a derived logger does not close its parent.
Parameters
Returns
func (*stdLogger) With(fields ...Field) Logger
{
l.mu.RLock()
defer l.mu.RUnlock()
nextFields := make(map[string]interface{}, len(l.fields)+len(fields))
for k, v := range l.fields {
nextFields[k] = v
}
for _, f := range fields {
nextFields[f.Key] = f.Value
}
child := &stdLogger{
fields: nextFields,
ctx: l.ctx,
async: l.async,
shared: l.shared,
owner: false,
}
return child
}
shouldLog reports whether the given level meets the logger's minimum threshold.
Parameters
Returns
func (*stdLogger) shouldLog(level Level) bool
{
l.shared.mu.RLock()
defer l.shared.mu.RUnlock()
return level >= l.shared.level
}
log constructs an Entry and dispatches it to all registered sinks.
Parameters
func (*stdLogger) log(level Level, msg string, fields ...Field)
{
if !l.shouldLog(level) {
return
}
entry := Entry{
Level: level.String(),
Time: time.Now().UTC(),
Msg: msg,
Fields: map[string]interface{}{},
}
l.mu.RLock()
for k, v := range l.fields {
entry.Fields[k] = v
}
ctx := l.ctx
l.mu.RUnlock()
l.shared.mu.RLock()
sinks := append([]Sink(nil), l.shared.sinks...)
l.shared.mu.RUnlock()
if ctx != nil {
if tid, ok := ctx.Value("trace_id").(string); ok {
entry.TraceID = tid
}
if sid, ok := ctx.Value("span_id").(string); ok {
entry.SpanID = sid
}
}
for _, f := range fields {
entry.Fields[f.Key] = f.Value
}
if l.async != nil {
l.async.submit(asyncItem{entry: entry, sinks: sinks})
return
}
for _, sink := range sinks {
_ = safeSinkLog(sink, entry)
}
}
Close flushes pending asynchronous entries and stops the logger worker.
Returns
func (*stdLogger) Close() error
{
if l.async == nil || !l.owner {
return nil
}
l.async.startClose()
<-l.async.done
return nil
}
CloseAsync starts logger shutdown without waiting for sink callbacks. It is safe to call from a Sink.
func (*stdLogger) CloseAsync()
{
if l.async == nil || !l.owner {
return
}
l.async.startClose()
}
Shutdown flushes pending asynchronous entries or returns when ctx is canceled. Sink callbacks must use CloseAsync instead.
Parameters
Returns
func (*stdLogger) Shutdown(ctx context.Context) error
{
if l.async == nil || !l.owner {
return nil
}
if ctx == nil {
ctx = context.Background()
}
l.async.startClose()
select {
case <-l.async.done:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
Debug logs at debug level.
Parameters
func (*stdLogger) Debug(msg string, fields ...Field)
{ l.log(DebugLevel, msg, fields...) }
Info logs at info level.
Parameters
func (*stdLogger) Info(msg string, fields ...Field)
{ l.log(InfoLevel, msg, fields...) }
Warn logs at warn level.
Parameters
func (*stdLogger) Warn(msg string, fields ...Field)
{ l.log(WarnLevel, msg, fields...) }
Error logs at error level.
Parameters
func (*stdLogger) Error(msg string, fields ...Field)
{ l.log(ErrorLevel, msg, fields...) }
Fields
| Name | Type | Description |
|---|---|---|
| mu | sync.RWMutex | |
| sinks | []Sink | |
| level | Level | |
| shared | *loggerState | |
| fields | map[string]interface{} | |
| ctx | context.Context | |
| async | *asyncState | |
| owner | bool | |
| asyncConfigured | bool | |
| asyncBuffer | int |
Uses
loggerState
type loggerState struct
Fields
| Name | Type | Description |
|---|---|---|
| mu | sync.RWMutex | |
| sinks | []Sink | |
| level | Level |
Uses
asyncItem
type asyncItem struct
Uses
asyncState
type asyncState struct
Methods
func (*asyncState) process()
{
defer s.workerWG.Done()
for item := range s.ch {
for _, sink := range item.sinks {
_ = safeSinkLog(sink, item.entry)
}
}
}
Parameters
func (*asyncState) submit(item asyncItem)
{
s.mu.Lock()
if s.closed {
s.mu.Unlock()
return
}
s.submitWG.Add(1)
s.mu.Unlock()
send := func() {
defer s.submitWG.Done()
select {
case s.ch <- item:
default:
}
}
send()
}
func (*asyncState) startClose()
{
s.closeOnce.Do(func() {
s.mu.Lock()
s.closed = true
s.mu.Unlock()
go func() {
s.submitWG.Wait()
close(s.ch)
s.workerWG.Wait()
close(s.done)
}()
})
}
Fields
| Name | Type | Description |
|---|---|---|
| mu | sync.Mutex | |
| ch | chan asyncItem | |
| done | chan struct{} | |
| workerWG | sync.WaitGroup | |
| submitWG | sync.WaitGroup | |
| closeOnce | sync.Once | |
| closed | bool |
New
New constructs a logger with optional options.
Parameters
Returns
func New(opts ...Option) Logger
{
l := &stdLogger{
level: InfoLevel,
fields: map[string]interface{}{},
sinks: []Sink{NewConsoleSink(nil)},
owner: true,
}
for _, o := range opts {
o(l)
}
l.shared = &loggerState{
sinks: append([]Sink(nil), l.sinks...),
level: l.level,
}
if l.asyncConfigured {
state := &asyncState{
ch: make(chan asyncItem, l.asyncBuffer),
done: make(chan struct{}),
}
state.workerWG.Add(1)
l.async = state
go state.process()
}
return l
}
Example
log := logger.New(
logger.WithLevel(logger.DebugLevel),
logger.WithoutDefaultSink(),
logger.WithSink(mySink),
)
log.Info("started", logger.Field{Key: "version", Value: "1.0"})
Uses
WithLevel
WithLevel sets the minimum level for emitted logs.
func WithLevel(level Level) Option
{
return func(l *stdLogger) { l.level = level }
}
WithSink
WithSink adds an initial sink.
func WithSink(s Sink) Option
{
return func(l *stdLogger) { l.sinks = append(l.sinks, s) }
}
WithoutDefaultSink
WithoutDefaultSink removes the default ConsoleSink added by New.
Use before WithSink to create a logger with only custom sinks:
logger.New(logger.WithoutDefaultSink(), logger.WithSink(clefSink))
Returns
func WithoutDefaultSink() Option
{
return func(l *stdLogger) { l.sinks = nil }
}
Uses
WithFields
WithFields binds fields to the logger returned from New.
Parameters
Returns
func WithFields(fields ...Field) Option
{
return func(l *stdLogger) {
for _, f := range fields {
l.fields[f.Key] = f.Value
}
}
}
Uses
WithAsync
WithAsync enables asynchronous logging with a bounded queue.
Entries are dropped when the queue is full.
Parameters
Returns
func WithAsync(bufSize int) Option
{
return func(l *stdLogger) {
if bufSize <= 0 {
panic("logger: async buffer size must be positive")
}
if l.asyncConfigured {
panic("logger: async logging is already configured")
}
l.asyncConfigured = true
l.asyncBuffer = bufSize
}
}
Uses
safeSinkLog
func safeSinkLog(sink Sink, entry Entry) (err error)
{
if sink == nil {
return nil
}
defer func() {
if recovered := recover(); recovered != nil {
err = errors.New("logger: sink panic")
}
}()
return sink.Log(entry)
}
WithContext
WithContext binds a context to the logger, allowing sinks
to extract trace/span IDs for distributed tracing.
Parameters
Returns
func WithContext(ctx context.Context) Option
{
return func(l *stdLogger) { l.ctx = ctx }
}
Uses
RegisterSink
RegisterSink adds a sink at runtime.
func RegisterSink(l Logger, s Sink)
{
if sl, ok := l.(*stdLogger); ok {
sl.RegisterSink(s)
}
}
ConsoleSink
ConsoleSink writes entries as compact JSON lines to an io.Writer.
type ConsoleSink struct
Methods
Fields
| Name | Type | Description |
|---|---|---|
| w | io.Writer |
NewConsoleSink
NewConsoleSink constructs a ConsoleSink.
Parameters
Returns
func NewConsoleSink(w io.Writer) *ConsoleSink
{
if w == nil {
w = os.Stdout
}
return &ConsoleSink{w: w}
}