@@ -28,6 +28,7 @@ import (
2828	"time" 
2929)
3030
31+ // LogStyle represents the common logging formats in the Prometheus ecosystem. 
3132type  LogStyle  string 
3233
3334const  (
@@ -38,115 +39,29 @@ const (
3839)
3940
4041var  (
41- 	LevelFlagOptions   =  []string {"debug" , "info" , "warn" , "error" }
42+ 	// LevelFlagOptions represents allowed logging levels. 
43+ 	LevelFlagOptions  =  []string {"debug" , "info" , "warn" , "error" }
44+ 	// FormatFlagOptions represents allowed formats. 
4245	FormatFlagOptions  =  []string {"logfmt" , "json" }
4346
44- 	callerAddFunc              =  false 
45- 	defaultWriter              =  os .Stderr 
46- 	goKitStyleReplaceAttrFunc  =  func (groups  []string , a  slog.Attr ) slog.Attr  {
47- 		key  :=  a .Key 
48- 		switch  key  {
49- 		case  slog .TimeKey , "ts" :
50- 			if  t , ok  :=  a .Value .Any ().(time.Time ); ok  {
51- 				a .Key  =  "ts" 
52- 
53- 				// This timestamp format differs from RFC3339Nano by using .000 instead 
54- 				// of .999999999 which changes the timestamp from 9 variable to 3 fixed 
55- 				// decimals (.130 instead of .130987456). 
56- 				a .Value  =  slog .StringValue (t .UTC ().Format ("2006-01-02T15:04:05.000Z07:00" ))
57- 			} else  {
58- 				// If we can't cast the any from the value to a 
59- 				// time.Time, it means the caller logged 
60- 				// another attribute with a key of `ts`. 
61- 				// Prevent duplicate keys (necessary for proper 
62- 				// JSON) by renaming the key to `logged_ts`. 
63- 				a .Key  =  reservedKeyPrefix  +  key 
64- 			}
65- 		case  slog .SourceKey , "caller" :
66- 			if  src , ok  :=  a .Value .Any ().(* slog.Source ); ok  {
67- 				a .Key  =  "caller" 
68- 				switch  callerAddFunc  {
69- 				case  true :
70- 					a .Value  =  slog .StringValue (filepath .Base (src .File ) +  "("  +  filepath .Base (src .Function ) +  "):"  +  strconv .Itoa (src .Line ))
71- 				default :
72- 					a .Value  =  slog .StringValue (filepath .Base (src .File ) +  ":"  +  strconv .Itoa (src .Line ))
73- 				}
74- 			} else  {
75- 				// If we can't cast the any from the value to 
76- 				// an *slog.Source, it means the caller logged 
77- 				// another attribute with a key of `caller`. 
78- 				// Prevent duplicate keys (necessary for proper 
79- 				// JSON) by renaming the key to 
80- 				// `logged_caller`. 
81- 				a .Key  =  reservedKeyPrefix  +  key 
82- 			}
83- 		case  slog .LevelKey :
84- 			if  lvl , ok  :=  a .Value .Any ().(slog.Level ); ok  {
85- 				a .Value  =  slog .StringValue (strings .ToLower (lvl .String ()))
86- 			} else  {
87- 				// If we can't cast the any from the value to 
88- 				// an slog.Level, it means the caller logged 
89- 				// another attribute with a key of `level`. 
90- 				// Prevent duplicate keys (necessary for proper 
91- 				// JSON) by renaming the key to `logged_level`. 
92- 				a .Key  =  reservedKeyPrefix  +  key 
93- 			}
94- 		default :
95- 		}
96- 
97- 		return  a 
98- 	}
99- 	defaultReplaceAttrFunc  =  func (groups  []string , a  slog.Attr ) slog.Attr  {
100- 		key  :=  a .Key 
101- 		switch  key  {
102- 		case  slog .TimeKey :
103- 			if  t , ok  :=  a .Value .Any ().(time.Time ); ok  {
104- 				a .Value  =  slog .TimeValue (t .UTC ())
105- 			} else  {
106- 				// If we can't cast the any from the value to a 
107- 				// time.Time, it means the caller logged 
108- 				// another attribute with a key of `time`. 
109- 				// Prevent duplicate keys (necessary for proper 
110- 				// JSON) by renaming the key to `logged_time`. 
111- 				a .Key  =  reservedKeyPrefix  +  key 
112- 			}
113- 		case  slog .SourceKey :
114- 			if  src , ok  :=  a .Value .Any ().(* slog.Source ); ok  {
115- 				a .Value  =  slog .StringValue (filepath .Base (src .File ) +  ":"  +  strconv .Itoa (src .Line ))
116- 			} else  {
117- 				// If we can't cast the any from the value to 
118- 				// an *slog.Source, it means the caller logged 
119- 				// another attribute with a key of `source`. 
120- 				// Prevent duplicate keys (necessary for proper 
121- 				// JSON) by renaming the key to 
122- 				// `logged_source`. 
123- 				a .Key  =  reservedKeyPrefix  +  key 
124- 			}
125- 		case  slog .LevelKey :
126- 			if  _ , ok  :=  a .Value .Any ().(slog.Level ); ! ok  {
127- 				// If we can't cast the any from the value to 
128- 				// an slog.Level, it means the caller logged 
129- 				// another attribute with a key of `level`. 
130- 				// Prevent duplicate keys (necessary for proper 
131- 				// JSON) by renaming the key to 
132- 				// `logged_level`. 
133- 				a .Key  =  reservedKeyPrefix  +  key 
134- 			}
135- 		default :
136- 		}
137- 
138- 		return  a 
139- 	}
47+ 	defaultWriter  =  os .Stderr 
14048)
14149
142- // AllowedLevel is  a settable identifier for the minimum  level a log entry  
143- // must be have . 
144- type   AllowedLevel   struct  { 
145- 	 s     string 
50+ // Level controls  a logging  level, with an info default.  
51+ // It wraps slog.LevelVar with string-based level control . 
52+ // Level is safe to be used concurrently. 
53+ type   Level   struct  { 
14654	lvl  * slog.LevelVar 
14755}
14856
149- func  (l  * AllowedLevel ) UnmarshalYAML (unmarshal  func (interface {}) error ) error  {
57+ // NewLevel returns a new Level. 
58+ func  NewLevel () * Level  {
59+ 	return  & Level {
60+ 		lvl : & slog.LevelVar {},
61+ 	}
62+ }
63+ 
64+ func  (l  * Level ) UnmarshalYAML (unmarshal  func (interface {}) error ) error  {
15065	var  s  string 
15166	type  plain  string 
15267	if  err  :=  unmarshal ((* plain )(& s )); err  !=  nil  {
@@ -155,55 +70,60 @@ func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error {
15570	if  s  ==  ""  {
15671		return  nil 
15772	}
158- 	lo  :=  & AllowedLevel {}
159- 	if  err  :=  lo .Set (s ); err  !=  nil  {
73+ 	if  err  :=  l .Set (s ); err  !=  nil  {
16074		return  err 
16175	}
162- 	* l  =  * lo 
16376	return  nil 
16477}
16578
166- func  (l  * AllowedLevel ) String () string  {
167- 	return  l .s 
168- }
169- 
170- // Set updates the value of the allowed level. 
171- func  (l  * AllowedLevel ) Set (s  string ) error  {
172- 	if  l .lvl  ==  nil  {
173- 		l .lvl  =  & slog.LevelVar {}
79+ // String returns the current level. 
80+ func  (l  * Level ) String () string  {
81+ 	switch  l .lvl .Level () {
82+ 	case  slog .LevelDebug :
83+ 		return  "debug" 
84+ 	case  slog .LevelInfo :
85+ 		return  "info" 
86+ 	case  slog .LevelWarn :
87+ 		return  "warn" 
88+ 	case  slog .LevelError :
89+ 		return  "error" 
90+ 	default :
91+ 		return  "" 
17492	}
93+ }
17594
95+ // Set updates the logging level with the validation. 
96+ func  (l  * Level ) Set (s  string ) error  {
17697	switch  strings .ToLower (s ) {
17798	case  "debug" :
17899		l .lvl .Set (slog .LevelDebug )
179- 		callerAddFunc  =  true 
180100	case  "info" :
181101		l .lvl .Set (slog .LevelInfo )
182- 		callerAddFunc  =  false 
183102	case  "warn" :
184103		l .lvl .Set (slog .LevelWarn )
185- 		callerAddFunc  =  false 
186104	case  "error" :
187105		l .lvl .Set (slog .LevelError )
188- 		callerAddFunc  =  false 
189106	default :
190107		return  fmt .Errorf ("unrecognized log level %s" , s )
191108	}
192- 	l .s  =  s 
193109	return  nil 
194110}
195111
196- // AllowedFormat is a settable identifier for the output format that the logger can have. 
197- type  AllowedFormat  struct  {
112+ // Format controls a logging output format. 
113+ // Not concurrency-safe. 
114+ type  Format  struct  {
198115	s  string 
199116}
200117
201- func  (f  * AllowedFormat ) String () string  {
118+ // NewFormat creates a new Format. 
119+ func  NewFormat () * Format  { return  & Format {} }
120+ 
121+ func  (f  * Format ) String () string  {
202122	return  f .s 
203123}
204124
205125// Set updates the value of the allowed format. 
206- func  (f  * AllowedFormat ) Set (s  string ) error  {
126+ func  (f  * Format ) Set (s  string ) error  {
207127	switch  s  {
208128	case  "logfmt" , "json" :
209129		f .s  =  s 
@@ -215,18 +135,113 @@ func (f *AllowedFormat) Set(s string) error {
215135
216136// Config is a struct containing configurable settings for the logger 
217137type  Config  struct  {
218- 	Level   * AllowedLevel 
219- 	Format  * AllowedFormat 
138+ 	Level   * Level 
139+ 	Format  * Format 
220140	Style   LogStyle 
221141	Writer  io.Writer 
222142}
223143
144+ func  newGoKitStyleReplaceAttrFunc (lvl  * Level ) func (groups  []string , a  slog.Attr ) slog.Attr  {
145+ 	return  func (groups  []string , a  slog.Attr ) slog.Attr  {
146+ 		key  :=  a .Key 
147+ 		switch  key  {
148+ 		case  slog .TimeKey , "ts" :
149+ 			if  t , ok  :=  a .Value .Any ().(time.Time ); ok  {
150+ 				a .Key  =  "ts" 
151+ 
152+ 				// This timestamp format differs from RFC3339Nano by using .000 instead 
153+ 				// of .999999999 which changes the timestamp from 9 variable to 3 fixed 
154+ 				// decimals (.130 instead of .130987456). 
155+ 				a .Value  =  slog .StringValue (t .UTC ().Format ("2006-01-02T15:04:05.000Z07:00" ))
156+ 			} else  {
157+ 				// If we can't cast the any from the value to a 
158+ 				// time.Time, it means the caller logged 
159+ 				// another attribute with a key of `ts`. 
160+ 				// Prevent duplicate keys (necessary for proper 
161+ 				// JSON) by renaming the key to `logged_ts`. 
162+ 				a .Key  =  reservedKeyPrefix  +  key 
163+ 			}
164+ 		case  slog .SourceKey , "caller" :
165+ 			if  src , ok  :=  a .Value .Any ().(* slog.Source ); ok  {
166+ 				a .Key  =  "caller" 
167+ 				switch  lvl .String () {
168+ 				case  "debug" :
169+ 					a .Value  =  slog .StringValue (filepath .Base (src .File ) +  "("  +  filepath .Base (src .Function ) +  "):"  +  strconv .Itoa (src .Line ))
170+ 				default :
171+ 					a .Value  =  slog .StringValue (filepath .Base (src .File ) +  ":"  +  strconv .Itoa (src .Line ))
172+ 				}
173+ 			} else  {
174+ 				// If we can't cast the any from the value to 
175+ 				// an *slog.Source, it means the caller logged 
176+ 				// another attribute with a key of `caller`. 
177+ 				// Prevent duplicate keys (necessary for proper 
178+ 				// JSON) by renaming the key to 
179+ 				// `logged_caller`. 
180+ 				a .Key  =  reservedKeyPrefix  +  key 
181+ 			}
182+ 		case  slog .LevelKey :
183+ 			if  lvl , ok  :=  a .Value .Any ().(slog.Level ); ok  {
184+ 				a .Value  =  slog .StringValue (strings .ToLower (lvl .String ()))
185+ 			} else  {
186+ 				// If we can't cast the any from the value to 
187+ 				// an slog.Level, it means the caller logged 
188+ 				// another attribute with a key of `level`. 
189+ 				// Prevent duplicate keys (necessary for proper 
190+ 				// JSON) by renaming the key to `logged_level`. 
191+ 				a .Key  =  reservedKeyPrefix  +  key 
192+ 			}
193+ 		default :
194+ 		}
195+ 		return  a 
196+ 	}
197+ }
198+ 
199+ func  defaultReplaceAttr (_  []string , a  slog.Attr ) slog.Attr  {
200+ 	key  :=  a .Key 
201+ 	switch  key  {
202+ 	case  slog .TimeKey :
203+ 		if  t , ok  :=  a .Value .Any ().(time.Time ); ok  {
204+ 			a .Value  =  slog .TimeValue (t .UTC ())
205+ 		} else  {
206+ 			// If we can't cast the any from the value to a 
207+ 			// time.Time, it means the caller logged 
208+ 			// another attribute with a key of `time`. 
209+ 			// Prevent duplicate keys (necessary for proper 
210+ 			// JSON) by renaming the key to `logged_time`. 
211+ 			a .Key  =  reservedKeyPrefix  +  key 
212+ 		}
213+ 	case  slog .SourceKey :
214+ 		if  src , ok  :=  a .Value .Any ().(* slog.Source ); ok  {
215+ 			a .Value  =  slog .StringValue (filepath .Base (src .File ) +  ":"  +  strconv .Itoa (src .Line ))
216+ 		} else  {
217+ 			// If we can't cast the any from the value to 
218+ 			// an *slog.Source, it means the caller logged 
219+ 			// another attribute with a key of `source`. 
220+ 			// Prevent duplicate keys (necessary for proper 
221+ 			// JSON) by renaming the key to 
222+ 			// `logged_source`. 
223+ 			a .Key  =  reservedKeyPrefix  +  key 
224+ 		}
225+ 	case  slog .LevelKey :
226+ 		if  _ , ok  :=  a .Value .Any ().(slog.Level ); ! ok  {
227+ 			// If we can't cast the any from the value to 
228+ 			// an slog.Level, it means the caller logged 
229+ 			// another attribute with a key of `level`. 
230+ 			// Prevent duplicate keys (necessary for proper 
231+ 			// JSON) by renaming the key to 
232+ 			// `logged_level`. 
233+ 			a .Key  =  reservedKeyPrefix  +  key 
234+ 		}
235+ 	default :
236+ 	}
237+ 	return  a 
238+ }
239+ 
224240// New returns a new slog.Logger. Each logged line will be annotated 
225241// with a timestamp. The output always goes to stderr. 
226242func  New (config  * Config ) * slog.Logger  {
227243	if  config .Level  ==  nil  {
228- 		config .Level  =  & AllowedLevel {}
229- 		_  =  config .Level .Set ("info" )
244+ 		config .Level  =  NewLevel ()
230245	}
231246
232247	if  config .Writer  ==  nil  {
@@ -236,11 +251,11 @@ func New(config *Config) *slog.Logger {
236251	logHandlerOpts  :=  & slog.HandlerOptions {
237252		Level :       config .Level .lvl ,
238253		AddSource :   true ,
239- 		ReplaceAttr : defaultReplaceAttrFunc ,
254+ 		ReplaceAttr : defaultReplaceAttr ,
240255	}
241256
242257	if  config .Style  ==  GoKitStyle  {
243- 		logHandlerOpts .ReplaceAttr  =  goKitStyleReplaceAttrFunc 
258+ 		logHandlerOpts .ReplaceAttr  =  newGoKitStyleReplaceAttrFunc ( config . Level ) 
244259	}
245260
246261	if  config .Format  !=  nil  &&  config .Format .s  ==  "json"  {
0 commit comments