Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

breaking,log: set stderr as default log output, add .set_output_stream() #23444

Merged
merged 3 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions vlib/log/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
```
4 changes: 2 additions & 2 deletions vlib/log/common.v
Original file line number Diff line number Diff line change
Expand Up @@ -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 { ' ' }
Expand Down
43 changes: 36 additions & 7 deletions vlib/log/log.v
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -25,17 +26,22 @@ 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.
time_format TimeFormat = .tf_rfc3339_micro
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
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -131,13 +143,30 @@ 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) {
// 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())
e := tag_to_cli(level, l.short_tag)
println('${timestamp} [${e}] ${s}')
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 {
flush_stdout()
if mut l.output_stream is os.File {
match l.output_stream.fd {
1 { flush_stdout() }
2 { flush_stderr() }
else {}
}
}
}
}

Expand All @@ -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)
}
}

Expand Down
38 changes: 15 additions & 23 deletions vlib/v/slow_tests/inout/dump_expression.out
Original file line number Diff line number Diff line change
@@ -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
7 changes: 3 additions & 4 deletions vlib/v/slow_tests/inout/dump_expression.vv
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import log

fn dump_of_int() {
x := dump(1) + 1
Expand All @@ -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)
Expand Down
Loading