v / vlib / log / log.v
333 lines · 295 sloc · 10.26 KB · a5f400ee77ccbcfef35e53595470ef41044cd92a
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module log
5
6import os
7import time
8import io
9
10// TimeFormat define the log time string format, come from time/format.v
11pub enum TimeFormat {
12 tf_ss_micro // YYYY-MM-DD HH:mm:ss.123456 (24h)
13 tf_default // YYYY-MM-DD HH:mm (24h)
14 tf_ss // YYYY-MM-DD HH:mm:ss (24h)
15 tf_ss_milli // YYYY-MM-DD HH:mm:ss.123 (24h)
16 tf_ss_nano // YYYY-MM-DD HH:mm:ss.123456789 (24h)
17 tf_rfc3339 // YYYY-MM-DDTHH:mm:ss.123Z (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
18 tf_rfc3339_micro // default, YYYY-MM-DDTHH:mm:ss.123456Z (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
19 tf_rfc3339_nano // YYYY-MM-DDTHH:mm:ss.123456789Z (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
20 tf_hhmm // HH:mm (24h)
21 tf_hhmmss // HH:mm:ss (24h)
22 tf_hhmm12 // hh:mm (12h)
23 tf_ymmdd // YYYY-MM-DD
24 tf_ddmmy // DD.MM.YYYY
25 tf_md // MMM D
26 tf_custom_format // 'MMMM Do YY N kk:mm:ss A' output like: January 1st 22 AD 13:45:33 PM
27}
28
29const stderr = os.stderr()
30
31// Log represents a logging object
32pub struct Log {
33mut:
34 level Level = .debug
35 output_label string
36 ofile os.File
37 output_target LogTarget // output to console (stdout/stderr) or file or both.
38 time_format TimeFormat = .tf_rfc3339_micro
39 custom_time_format string = 'MMMM Do YY N kk:mm:ss A' // timestamp with custom format
40 short_tag bool
41 local_time bool // use local time or utc time in log
42 always_flush bool // flush after every single .fatal(), .error(), .warn(), .info(), .debug() call
43 output_stream io.Writer = stderr
44pub mut:
45 output_file_name string // log output to this file
46}
47
48// get_level gets the internal logging level.
49pub fn (l &Log) get_level() Level {
50 return l.level
51}
52
53// set_level sets the logging level to `level`. Messages for levels above it will skipped.
54// For example, after calling log.set_level(.info), log.debug('message') will produce nothing.
55// Call log.set_level(.disabled) to turn off the logging of all messages.
56pub fn (mut l Log) set_level(level Level) {
57 l.level = level
58}
59
60// set_full_logpath sets the output label and output path from `full_log_path`.
61pub fn (mut l Log) set_full_logpath(full_log_path string) {
62 rlog_file := os.real_path(full_log_path)
63 l.set_output_label(os.file_name(rlog_file))
64 l.set_output_path(os.dir(rlog_file))
65}
66
67// set_output_label sets the `label` for the output.
68pub fn (mut l Log) set_output_label(label string) {
69 l.output_label = label
70}
71
72// set_output_path sets the file to which output is logged to.
73pub fn (mut l Log) set_output_path(output_file_path string) {
74 if l.ofile.is_opened {
75 l.ofile.close()
76 }
77 l.output_target = .file
78 l.output_file_name = os.join_path(os.real_path(output_file_path), l.output_label)
79 ofile := os.open_append(l.output_file_name) or {
80 panic('error while opening log file ' + l.output_file_name + ' for appending')
81 }
82 l.ofile = ofile
83}
84
85// set_output_stream sets the output stream to write log e.g. stderr, stdout, etc.
86pub fn (mut l Log) set_output_stream(stream io.Writer) {
87 l.output_stream = stream
88}
89
90// log_to_console_too turns on logging to the console too, in addition to logging to a file.
91// You have to call it *after* calling .set_output_path(output_file_path).
92pub fn (mut l Log) log_to_console_too() {
93 if l.output_target != .file {
94 panic('log_to_console_too should be called *after* .set_output_path')
95 }
96 l.output_target = .both
97}
98
99// flush writes the log file content to disk.
100pub fn (mut l Log) flush() {
101 l.ofile.flush()
102}
103
104// close closes the log file.
105pub fn (mut l Log) close() {
106 l.ofile.close()
107}
108
109// reopen reopens the log file. Useful for log rotation.
110// This does nothing if you are only writing to the console.
111pub fn (mut l Log) reopen() ! {
112 l.flush()
113 if l.output_target == .file || l.output_target == .both {
114 l.ofile.reopen(l.output_file_name, 'ab') or {
115 return error_with_code('re-opening log file `${l.output_file_name}` for appending failed',
116 1)
117 }
118 }
119}
120
121// log_file writes log line `s` with `level` to the log file.
122fn (mut l Log) log_file(s string, level Level) {
123 timestamp := l.time_format(if l.local_time { time.now() } else { time.utc() })
124 e := tag_to_file(level, l.short_tag)
125
126 unsafe {
127 l.ofile.write_ptr(timestamp.str, timestamp.len)
128 l.ofile.write_ptr(c' ', 1)
129
130 l.ofile.write_ptr(c'[', 1)
131 l.ofile.write_ptr(e.str, e.len)
132 l.ofile.write_ptr(c']', 1)
133
134 l.ofile.write_ptr(c' ', 1)
135 l.ofile.write_ptr(s.str, s.len)
136
137 l.ofile.write_ptr(c'\n', 1)
138 }
139 if l.always_flush {
140 l.flush()
141 }
142}
143
144// log_stream writes log line `s` with `level` to stderr or stderr depending on set output stream.
145fn (mut l Log) log_stream(s string, level Level) {
146 timestamp := l.time_format(if l.local_time { time.now() } else { time.utc() })
147 tag := tag_to_console(level, l.short_tag)
148 msg := '${timestamp} [${tag}] ${s}\n'
149 arr := msg.bytes()
150 l.output_stream.write(arr) or {}
151 if l.always_flush {
152 if mut l.output_stream is os.File {
153 match l.output_stream.fd {
154 1 { flush_stdout() }
155 2 { flush_stderr() }
156 else {}
157 }
158 }
159 }
160}
161
162// send_output writes log line `s` with `level` to either the log file or the console
163// according to the value of the `.output_target` field.
164pub fn (mut l Log) send_output(s &string, level Level) {
165 if l.output_target == .file || l.output_target == .both {
166 l.log_file(s, level)
167 }
168 if l.output_target == .console || l.output_target == .both {
169 l.log_stream(s, level)
170 }
171}
172
173// fatal logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.fatal` category.
174// Note that this method performs a panic at the end, even if log level is not enabled.
175@[noreturn]
176pub fn (mut l Log) fatal(s string) {
177 if int(l.level) >= int(Level.fatal) {
178 l.send_output(s, .fatal)
179 l.ofile.close()
180 }
181 panic(l.output_label + ': ' + s)
182}
183
184// error logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.error` category.
185pub fn (mut l Log) error(s string) {
186 if int(l.level) < int(Level.error) {
187 return
188 }
189 l.send_output(s, .error)
190}
191
192// warn logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.warn` category.
193pub fn (mut l Log) warn(s string) {
194 if int(l.level) < int(Level.warn) {
195 return
196 }
197 l.send_output(s, .warn)
198}
199
200// info logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.info` category.
201pub fn (mut l Log) info(s string) {
202 if int(l.level) < int(Level.info) {
203 return
204 }
205 l.send_output(s, .info)
206}
207
208// debug logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.debug` category.
209pub fn (mut l Log) debug(s string) {
210 if int(l.level) < int(Level.debug) {
211 return
212 }
213 l.send_output(s, .debug)
214}
215
216// free frees the given Log instance
217@[unsafe]
218pub fn (mut f Log) free() {
219 unsafe {
220 f.output_label.free()
221 f.ofile.close()
222 f.output_file_name.free()
223 }
224}
225
226// time_format return a timestamp string in the pre-defined format
227fn (l Log) time_format(t time.Time) string {
228 match l.time_format {
229 .tf_rfc3339_micro { // YYYY-MM-DDTHH:mm:ss.123456Z (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
230 return t.format_rfc3339_micro()
231 }
232 .tf_ss_micro { // YYYY-MM-DD HH:mm:ss.123456 (24h) default
233 return t.format_ss_micro()
234 }
235 .tf_default { // YYYY-MM-DD HH:mm (24h)
236 return t.format()
237 }
238 .tf_ss { // YYYY-MM-DD HH:mm:ss (24h)
239 return t.format_ss()
240 }
241 .tf_ss_milli { // YYYY-MM-DD HH:mm:ss.123 (24h)
242 return t.format_ss_milli()
243 }
244 .tf_ss_nano { // YYYY-MM-DD HH:mm:ss.123456789 (24h)
245 return t.format_ss_nano()
246 }
247 .tf_rfc3339 { // YYYY-MM-DDTHH:mm:ss.123Z (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
248 return t.format_rfc3339()
249 }
250 .tf_rfc3339_nano { // YYYY-MM-DDTHH:mm:ss.123456789Z (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
251 return t.format_rfc3339_nano()
252 }
253 .tf_hhmm { // HH:mm (24h)
254 return t.hhmm()
255 }
256 .tf_hhmmss { // HH:mm:ss (24h)
257 return t.hhmmss()
258 }
259 .tf_hhmm12 { // hh:mm (12h)
260 return t.hhmm12()
261 }
262 .tf_ymmdd { // YYYY-MM-DD
263 return t.ymmdd()
264 }
265 .tf_ddmmy { // DD.MM.YYYY
266 return t.ddmmy()
267 }
268 .tf_md { // MMM D
269 return t.md()
270 }
271 .tf_custom_format { // 'MMMM Do YY N kk:mm:ss A' output like: January 1st 22 AD 13:45:33 PM
272 return t.custom_format(l.custom_time_format)
273 }
274 }
275}
276
277// set_time_format will set the log time format to a pre-defined format
278pub fn (mut l Log) set_time_format(f TimeFormat) {
279 l.time_format = f
280}
281
282// set_always_flush called with true, will make the log flush after every single .fatal(), .error(), .warn(), .info(), .debug() call.
283// That can be much slower, if you plan to do lots of frequent calls, but if your program exits early or crashes, your logs will be more complete.
284pub fn (mut l Log) set_always_flush(should_flush_every_time bool) {
285 l.always_flush = should_flush_every_time
286}
287
288// get_time_format will get the log time format
289pub fn (l Log) get_time_format() TimeFormat {
290 return l.time_format
291}
292
293// set_custom_time_format will set the log custom time format
294// refer to time/custom_format() for more information
295// eg. 'MMMM Do YY N kk:mm:ss A' output like: January 1st 22 AD 13:45:33 PM
296pub fn (mut l Log) set_custom_time_format(f string) {
297 l.time_format = .tf_custom_format
298 l.custom_time_format = f
299}
300
301// get_custom_time_format will get the log custom time format
302pub fn (l Log) get_custom_time_format() string {
303 return l.custom_time_format
304}
305
306// set_short_tag will set the log tag to it's short version
307// eg. '[FATAL]'=>'[F]', '[ERROR]'=>'[E]','[WARN ]'=>'[W]','[INFO ]'=>'[I]','[DEBUG]'=>'[D]'
308pub fn (mut l Log) set_short_tag(enabled bool) {
309 l.short_tag = enabled
310}
311
312// get_short_tag will get the log short tag enable state
313pub fn (l Log) get_short_tag() bool {
314 return l.short_tag
315}
316
317// set_local_time will set the log using local time
318pub fn (mut l Log) set_local_time(enabled bool) {
319 l.local_time = enabled
320}
321
322// get_local_time will get the log using local time state
323pub fn (l Log) get_local_time() bool {
324 return l.local_time
325}
326
327// use_stdout will restore the old behaviour of logging to stdout, instead of stderr.
328// It will also silence the deprecation note in the transition period.
329pub fn use_stdout() {
330 mut l := ThreadSafeLog{}
331 l.set_output_stream(os.stdout())
332 set_logger(l)
333}
334