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

Allow AnnotatedStrings in log messages #51802

Merged
merged 1 commit into from
Feb 17, 2024

Conversation

tecosaur
Copy link
Contributor

Permitting annotated strings allows for styling information to be preserved through to log printing.

image

@tecosaur tecosaur added domain:display and printing Aesthetics and correctness of printed representations of objects. logging The logging framework labels Oct 21, 2023
@LilithHafner LilithHafner added the needs tests Unit tests are required for this change label Nov 12, 2023
@tecosaur
Copy link
Contributor Author

I'm not sure that this actually does need any additional tests, without StyledStrings loaded there won't be any change in behavior. Perhaps it would be nice to separately test the "with styled strings loaded" and "without styled strings loaded" cases, but I'm not sure how that could be done well, and whether it would be worth it.

@@ -116,7 +116,13 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module

# Generate a text representation of the message and all key value pairs,
# split into lines.
msglines = [(indent=0, msg=l) for l in split(chomp(convert(String, string(message))::String), '\n')]
msglines = if message isa Base.AnnotatedString
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels a bit like a design failure that any custom string type needs to be hard coded here, but maybe it is unavoidable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specialisation on String not AbstractString is largely unnoticeable, it just so happens that with an AnnotatedString (and only AnnotatedString, as far as I'm aware), there's a potential difference in the externally visible behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fredrikekre do please let me know if you've got any further comments here, otherwise I'll just mark this as resolved shortly.

Copy link
Sponsor Member

@mbauman mbauman Feb 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this seems odd. The assert and convert here was added by #38292 and #44500 in the latency hunts; without them there'd be no need for a separate codepath — and the separate codepath is what makes this want for tests.

But I suppose going back to the simple [(indent=0, msg=l) for l in split(chomp(string(message)), '\n')] would likely reintroduce invalidations. Maybe worth a check?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe? I'm not sure I'd be a good person to check however — perhaps someone more familiar with invalidations could take a look if needed?

If this is indeed a little tricky to work out, might it be possible to merge this as an initial 1 -> 2 AbstractString widening of the allowed types here, and then explore making this fully generic again in a subsequent PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'm not entirely sure of this, but I think the following might suggest that a widening to "any AbstractString" may be premature given Tim's invalidation concerns.

# What it used to be before Tim's PRs
julia> m0(message) = [(indent=0, msg=l) for l in split(chomp(string(message)), '\n')]
m0 (generic function with 1 method)

# The current code
julia> m1(message) = [(indent=0, msg=l) for l in split(chomp(convert(String, string(message))::String), '\n')]
m1 (generic function with 1 method)

# The current PR 
julia> m2(message) = if message isa Base.AnnotatedString
               NamedTuple{(:indent, :msg), Tuple{Int, SubString{<:Union{typeof(message), String}}}}[
                   (indent=0, msg=l) for l in split(chomp(message), '\n')]
           else
               [(indent=0, msg=l) for l in split(
                    chomp(convert(String, string(message))::String), '\n')]
           end
m2 (generic function with 1 method)

# What I'm about to push
julia> m3(message) = if message isa Base.AnnotatedString
               message = Base.AnnotatedString(String(message), Base.annotations(message))
               NamedTuple{(:indent, :msg), Tuple{Int, SubString{<:Union{Base.AnnotatedString{String}, String}}}}[
                   (indent=0, msg=l) for l in split(chomp(message), '\n')]
           else
               [(indent=0, msg=l) for l in split(
                    chomp(convert(String, string(message))::String), '\n')]
           end
m3 (generic function with 1 method)

julia> Base.return_types(m0, (Any,))
1-element Vector{Any}:
 Vector (alias for Array{T, 1} where T)

julia> Base.return_types(m1, (Any,))
1-element Vector{Any}:
 Vector{@NamedTuple{indent::Int64, msg::SubString{String}}} (alias for Array{@NamedTuple{indent::Int64, msg::SubString{String}}, 1})

julia> Base.return_types(m2, (Any,))
1-element Vector{Any}:
 Array{NamedTuple{(:indent, :msg), var"#s178"}, 1} where var"#s178"<:Tuple{Int64, Vararg}

julia> Base.return_types(m3, (Any,))
1-element Vector{Any}:
 Union{Vector{@NamedTuple{indent::Int64, msg::SubString{<:Union{Base.AnnotatedString{String}, String}}}}, Vector{@NamedTuple{indent::Int64, msg::SubString{String}}}}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(message has no type restriction in the signature of handle_message)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the separate codepath is what makes this want for tests.

Added a test for the separate codepath.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle_message shifted to being called via invokelatest (the "nuclear option" when it comes to eliminating invalidations) in #47889; any changes that occurred prior to that PR may not be required anymore. If you want to be sure but don't feel comfortable using SnoopCompile, just (1) start Julia with --trace-compile, (2) define a function that uses logging and run it to force compilation, (3) load InlineStrings, (4) run it again and see if your function got recompiled.

@tecosaur
Copy link
Contributor Author

It would be great if somebody with merge rights could spare the time to have a look at this PR.

@tecosaur tecosaur added the status:awaiting review PR is complete and seems ready to merge. Has tests and news/compat if needed. CI failures unrelated. label Feb 1, 2024
@tecosaur tecosaur force-pushed the log-annot-strings branch 2 times, most recently from f88021d to 4cabfcc Compare February 4, 2024 15:11
@tecosaur tecosaur removed the needs tests Unit tests are required for this change label Feb 4, 2024
@vtjnash vtjnash removed the status:awaiting review PR is complete and seems ready to merge. Has tests and news/compat if needed. CI failures unrelated. label Feb 5, 2024
@tecosaur tecosaur force-pushed the log-annot-strings branch 2 times, most recently from d4f2c3a to c766cbf Compare February 11, 2024 09:49
message = string(message)
msglines = if Base._isannotated(message)
message = Base.AnnotatedString(String(message), Base.annotations(message))
@NamedTuple{indent::Int, msg::SubString{<:Union{Base.AnnotatedString{String}, String}}}[
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@NamedTuple{indent::Int, msg::SubString{<:Union{Base.AnnotatedString{String}, String}}}[
@NamedTuple{indent::Int, msg::SubString{Base.AnnotatedString{String}}[

Copy link
Contributor Author

@tecosaur tecosaur Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is invalid since later code pushes values like (indent=2, msg=SubString("$key =")) to msglines.

Copy link
Sponsor Member

@vtjnash vtjnash Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That shouldn't be invalid, if the convert methods are defined correctly.

But anyways, that suggest this should have the same type on both branches then, so you should update the other branch as well to have this type. I think you can slightly simplify it by making it a pair of concrete type also, instead of a UnionAll type (though this indeed would break the convert method handling if something isn't already a SubString of one of these 2 kinds, since we cannot convert to a Union).

Suggested change
@NamedTuple{indent::Int, msg::SubString{<:Union{Base.AnnotatedString{String}, String}}}[
@NamedTuple{indent::Int, msg::Union{SubString{Base.AnnotatedString{String}}, SubString{String}}}}[

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh? I recall widening this type because of an issue I ran into when developing this PR, but October last year is too far back for me to recall what was happening clearly.

I've applied the concrete-ification you've suggested.

Comment on lines 123 to 125
message = string(message)
msglines = if Base._isannotated(message)
message = Base.AnnotatedString(String(message), Base.annotations(message))
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, seems right to me. I think you can also combine a few more code paths like this also

Suggested change
message = string(message)
msglines = if Base._isannotated(message)
message = Base.AnnotatedString(String(message), Base.annotations(message))
message = string(message)
messagestr = String(message)::String
annots = Base._isannotated(message)
messageattrs = annots ? Base.annotations(message) : ()
msglines = if annots && !isempty(messageattrs)
message = Base.AnnotatedString(messagestr, messageattrs)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't help but feel this is taking DRY too far...

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess mainly it is ugly since I am taking advantage of the DRY-er code to have it use the non-annotated code path if it detects there are no relevant annotations on this (sub)string. I don't know if it is necessarily worth doing that, as it does force the code to be somewhat more linear here (longer) as each line cannot do multiple computations anymore (even just annotations and String).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the push I recently did, I added that check too by just doing if Base._isannotated(message) && !isempty(Base.annotations(message)). FWWIW, I think the main visual impediment with the current code isn't the fact it could be slightly DRY-er but the Base. prefixes. Without them, this is the current code:

message = string(message)
msglines = if _isannotated(message) && !isempty(annotations(message))
    message = AnnotatedString(String(message), annotations(message))
    @NamedTuple{indent::Int, msg::Union{SubString{AnnotatedString{String}}, SubString{String}}}[
        (indent=0, msg=l) for l in split(chomp(message), '\n')]
else
    [(indent=0, msg=l) for l in split(
         chomp(convert(String, message)::String), '\n')]
end

stdlib/Logging/src/ConsoleLogger.jl Show resolved Hide resolved
Permitting annotated strings allows for styling information to be
preserved through to log printing.
@vtjnash vtjnash added the status:merge me PR is reviewed. Merge when all tests are passing label Feb 12, 2024
@tecosaur
Copy link
Contributor Author

Ok, the only test failure seems to be a bootstrap error with the Mingw32 windows build, everything else in CI looks happy.

@tecosaur tecosaur added the backport 1.11 Change should be backported to release-1.11 label Feb 16, 2024
@vtjnash vtjnash merged commit 1998518 into JuliaLang:master Feb 17, 2024
6 of 9 checks passed
@inkydragon inkydragon removed the status:merge me PR is reviewed. Merge when all tests are passing label Feb 18, 2024
tecosaur added a commit to tecosaur/julia that referenced this pull request Mar 4, 2024
Permitting annotated strings allows for styling information to be
preserved through to log printing.
KristofferC pushed a commit that referenced this pull request Mar 6, 2024
Permitting annotated strings allows for styling information to be
preserved through to log printing.

(cherry picked from commit 1998518)
@KristofferC KristofferC mentioned this pull request Mar 6, 2024
60 tasks
KristofferC added a commit that referenced this pull request Mar 17, 2024
Backported PRs:
- [x] #39071 <!-- Add a lazy `logrange` function and `LogRange` type -->
- [x] #51802 <!-- Allow AnnotatedStrings in log messages -->
- [x] #53369 <!-- Orthogonalize re-indexing for FastSubArrays -->
- [x] #48050 <!-- improve `--heap-size-hint` arg handling -->
- [x] #53482 <!-- add IR encoding for EnterNode -->
- [x] #53499 <!-- Avoid compiler warning about redefining jl_globalref_t
-->
- [x] #53507 <!-- update staled `Core.Compiler.Effects` documentation
-->
- [x] #53408 <!-- task splitting: change additive accumulation to
multiplicative -->
- [x] #53523 <!-- add back an alias for `check_top_bit` -->
- [x] #53377 <!-- add _readdirx for returning more object info gathered
during dir scan -->
- [x] #53525 <!-- fix InteractiveUtils call in Base.runtests on failure
-->
- [x] #53540 <!-- use more efficient `_readdirx` for tab completion -->
- [x] #53545 <!-- use `_readdirx` for `walkdir` -->
- [x] #53551 <!-- revert "Add @create_log_macro for making custom styled
logging macros (#52196)" -->
- [x] #53554 <!-- Always return a value in 1-d circshift! of
abstractarray.jl -->
- [x] #53424 <!-- yet more atomics & cache-line fixes on work-stealing
queue -->
- [x] #53571 <!-- Update Documenter to v1.3 for inventory writing -->
- [x] #53403 <!-- Move parallel precompilation to Base -->
- [x] #53589 <!-- add back `unsafe_convert` to pointer for arrays -->
- [x] #53596 <!-- build: remove extra .a file -->
- [x] #53606 <!-- fix error path in `precompilepkgs` -->
- [x] #53004 <!-- Unexport with, at_with, and ScopedValue from Base -->
- [x] #53629 <!-- typo fix in scoped values docs -->
- [x] #53630 <!-- sroa: Fix incorrect scope counting -->
- [x] #53598 <!-- Use Base parallel precompilation to build stdlibs -->
- [x] #53649 <!-- precompilepkgs: package in boths deps and weakdeps are
in fact only weak -->
- [x] #53671 <!-- Fix bootstrap Base precompile in cross compile
configuration -->
- [x] #52125 <!-- Load Pkg if not already to reinstate missing package
add prompt -->
- [x] #53602 <!-- Handle zero on arrays of unions of number types and
missings -->
- [x] #53516 <!-- permit NamedTuple{<:Any, Union{}} to be created -->
- [x] #53643 <!-- Bump CSL to 1.1.1 to fix libgomp bug -->
- [x] #53679 <!-- move precompile workload back from Base -->
- [x] #53663 <!-- add isassigned methods for reinterpretarray -->
- [x] #53662 <!-- [REPL] fix incorrectly cleared line after completions
accepted -->
- [x] #53611 <!-- Linalg: matprod_dest for Diagonal and adjvec -->
- [x] #53659 <!-- fix #52025, re-allow all implicit pointer casts in
cconvert for Array -->
- [x] #53631 <!-- LAPACK: validate input parameters to throw informative
errors -->
- [x] #53628 <!-- Make some improvements to the Scoped Values
documentation. -->
- [x] #53655 <!-- Change tbaa of ptr_phi to tbaa_value  -->
- [x] #53391 <!-- Default to the medium code model in x86 linux -->
- [x] #53699 <!-- Move `isexecutable, isreadable, iswritable` to
`filesystem.jl` -->
- [x] #41232 <!-- Fix linear indexing for ReshapedArray if the parent
has offset axes -->
- [x] #53527 <!-- Enable analyzegc checks for try catch and fix found
issues -->
- [x] #52092 
- [x] #53682 <!-- Increase build precompilation -->
- [x] #53720 
- [x] #53553 <!-- typeintersect: fix `UnionAll` unaliasing bug caused by
innervars. -->

Contains multiple commits, manual intervention needed:
- [ ] #53305 <!-- Propagate inbounds in isassigned with CartesianIndex
indices -->

Non-merged PRs with backport label:
- [ ] #53736 <!-- fix literal-pow to return the right type when the base
is -1 -->
- [ ] #53707 <!-- Make ScopedValue public -->
- [ ] #53696 <!-- add invokelatest to on_done callback in bracketed
paste -->
- [ ] #53660 <!-- put Logging back in default sysimage -->
- [ ] #53509 <!-- revert moving "creating packages" from Pkg.jl -->
- [ ] #53452 <!-- RFC: allow Tuple{Union{}}, returning Union{} -->
- [ ] #53402 <!-- Add `jl_getaffinity` and `jl_setaffinity` -->
- [ ] #52694 <!-- Reinstate similar for AbstractQ for backward
compatibility -->
- [ ] #51928 <!-- Styled markdown, with a few tweaks -->
- [ ] #51816 <!-- User-themable stacktraces -->
- [ ] #51811 <!-- Make banner size depend on terminal size -->
- [ ] #51479 <!-- prevent code loading from lookin in the versioned
environment when building Julia -->
@KristofferC KristofferC removed the backport 1.11 Change should be backported to release-1.11 label Mar 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:display and printing Aesthetics and correctness of printed representations of objects. logging The logging framework
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants