diff --git a/vlib/log/README.md b/vlib/log/README.md index 7101f016cb..56ad52917b 100644 --- a/vlib/log/README.md +++ b/vlib/log/README.md @@ -52,3 +52,22 @@ fn main() { l.fatal('fatal') // panic, marked as [noreturn] } ``` + +## Backwards compatibility + +After 2025/01/21, the `log` module outputs to `stderr` by default. +Before that, it used `stdout` by default. + +If you want to restore the previous behaviour, you have to explicitly call l.set_output_stream(): +```v +import os +import log + +fn main() { + // log.info('this will be printed to stderr after 2025/01/21 by default') + mut l := log.ThreadSafeLog{} + l.set_output_stream(os.stdout()) + log.set_logger(l) + log.info('this will be printed to stdout') +} +``` diff --git a/vlib/log/common.v b/vlib/log/common.v index c1b0fa1640..8a90870437 100644 --- a/vlib/log/common.v +++ b/vlib/log/common.v @@ -19,8 +19,8 @@ pub enum LogTarget { both } -// tag_to_cli returns the tag for log level `l` as a colored string. -fn tag_to_cli(l Level, short_tag bool) string { +// tag_to_console returns the tag for log level `l` as a colored string. +fn tag_to_console(l Level, short_tag bool) string { if short_tag { return match l { .disabled { ' ' } diff --git a/vlib/log/log.v b/vlib/log/log.v index 803c989da7..3cc0f4b7d0 100644 --- a/vlib/log/log.v +++ b/vlib/log/log.v @@ -5,6 +5,7 @@ module log import os import time +import io // TimeFormat define the log time string format, come from time/format.v pub enum TimeFormat { @@ -25,10 +26,12 @@ pub enum TimeFormat { tf_custom_format // 'MMMM Do YY N kk:mm:ss A' output like: January 1st 22 AD 13:45:33 PM } +const stderr = os.stderr() + // Log represents a logging object pub struct Log { mut: - level Level + level Level = .debug output_label string ofile os.File output_target LogTarget // output to console (stdout/stderr) or file or both. @@ -36,6 +39,9 @@ mut: custom_time_format string = 'MMMM Do YY N kk:mm:ss A' // timestamp with custom format short_tag bool always_flush bool // flush after every single .fatal(), .error(), .warn(), .info(), .debug() call + output_stream io.Writer = stderr + // + show_notice_about_stdout_to_stderr_change bool = true // this field is temporary, and should be deleted after 2025-03-01 pub mut: output_file_name string // log output to this file } @@ -77,6 +83,12 @@ pub fn (mut l Log) set_output_path(output_file_path string) { l.ofile = ofile } +// set_output_stream sets the output stream to write log e.g. stderr, stdout, etc. +pub fn (mut l Log) set_output_stream(stream io.Writer) { + l.show_notice_about_stdout_to_stderr_change = false + l.output_stream = stream +} + // log_to_console_too turns on logging to the console too, in addition to logging to a file. // You have to call it *after* calling .set_output_path(output_file_path). pub fn (mut l Log) log_to_console_too() { @@ -131,14 +143,31 @@ fn (mut l Log) log_file(s string, level Level) { } } -// log_cli writes log line `s` with `level` to stdout. -fn (l &Log) log_cli(s string, level Level) { - timestamp := l.time_format(time.utc()) - e := tag_to_cli(level, l.short_tag) - println('${timestamp} [${e}] ${s}') - if l.always_flush { +// log_stream writes log line `s` with `level` to stderr or stderr depending on set output stream. +fn (mut l Log) log_stream(s string, level Level) { + if l.show_notice_about_stdout_to_stderr_change { + l.show_notice_about_stdout_to_stderr_change = false + // Show a warning at runtime, once, before the first logged message, that describes the stdout -> stderr change, + // and how to opt in explicitly for the old behaviour: + println(' NOTE: the `log.Log` output goes to stderr now by default, not to stdout.') + println(' Call `l.set_output_stream(os.stdout())` explicitly, to opt in for the previous behavior.') + println(' Call `l.set_output_stream(os.stderr())` explicitly, if you want to silence this message (it will be removed after 2025-03-01 .') flush_stdout() } + timestamp := l.time_format(time.utc()) + tag := tag_to_console(level, l.short_tag) + msg := '${timestamp} [${tag}] ${s}\n' + arr := msg.bytes() + l.output_stream.write(arr) or {} + if l.always_flush { + if mut l.output_stream is os.File { + match l.output_stream.fd { + 1 { flush_stdout() } + 2 { flush_stderr() } + else {} + } + } + } } // send_output writes log line `s` with `level` to either the log file or the console @@ -148,7 +177,7 @@ pub fn (mut l Log) send_output(s &string, level Level) { l.log_file(s, level) } if l.output_target == .console || l.output_target == .both { - l.log_cli(s, level) + l.log_stream(s, level) } } diff --git a/vlib/v/slow_tests/inout/dump_expression.out b/vlib/v/slow_tests/inout/dump_expression.out index dc681e7a89..551bb1cd73 100644 --- a/vlib/v/slow_tests/inout/dump_expression.out +++ b/vlib/v/slow_tests/inout/dump_expression.out @@ -1,36 +1,28 @@ -[vlib/v/slow_tests/inout/dump_expression.vv:5] 1: 1 -[vlib/v/slow_tests/inout/dump_expression.vv:10] 'a': a -[vlib/v/slow_tests/inout/dump_expression.vv:34] a: Aa{ - log: &log.Logger(log.Log{ - level: disabled - output_label: '' - ofile: os.File{ - cfile: 0 - fd: 0 - is_opened: false - } - output_target: console - time_format: tf_rfc3339_micro - custom_time_format: 'MMMM Do YY N kk:mm:ss A' - short_tag: false - always_flush: false - output_file_name: '' - }) +[vlib/v/slow_tests/inout/dump_expression.vv:4] 1: 1 +[vlib/v/slow_tests/inout/dump_expression.vv:9] 'a': a +[vlib/v/slow_tests/inout/dump_expression.vv:33] a: Aa{ + cmd: &os.Command{ + f: 0 + eof: false + exit_code: 0 + path: '' + redirect_stdout: false + } } -[vlib/v/slow_tests/inout/dump_expression.vv:35] p: Point{ +[vlib/v/slow_tests/inout/dump_expression.vv:34] p: Point{ x: 1 y: 2 z: 3 } -[vlib/v/slow_tests/inout/dump_expression.vv:36] p_mut: Point{ +[vlib/v/slow_tests/inout/dump_expression.vv:35] p_mut: Point{ x: 1 y: 2 z: 3 } -[vlib/v/slow_tests/inout/dump_expression.vv:37] p_ptr: &Point{ +[vlib/v/slow_tests/inout/dump_expression.vv:36] p_ptr: &Point{ x: 1 y: 2 z: 3 } -[vlib/v/slow_tests/inout/dump_expression.vv:48] os.file_name(vfile): dump_expression.vv -[vlib/v/slow_tests/inout/dump_expression.vv:51] f.read(mut buf): 10 +[vlib/v/slow_tests/inout/dump_expression.vv:47] os.file_name(vfile): dump_expression.vv +[vlib/v/slow_tests/inout/dump_expression.vv:50] f.read(mut buf): 10 diff --git a/vlib/v/slow_tests/inout/dump_expression.vv b/vlib/v/slow_tests/inout/dump_expression.vv index 4f52d56640..c5d2df77ac 100644 --- a/vlib/v/slow_tests/inout/dump_expression.vv +++ b/vlib/v/slow_tests/inout/dump_expression.vv @@ -1,5 +1,4 @@ import os -import log fn dump_of_int() { x := dump(1) + 1 @@ -19,16 +18,16 @@ mut: } struct Aa { - log &log.Logger + cmd &os.Command } fn dump_of_struct() { p := Point{1, 2, 3} mut p_mut := Point{1, 2, 3} p_ptr := &Point{1, 2, 3} - l := &log.Log{} + c := &os.Command{} a := Aa{ - log: l + cmd: c } dump(a)