Emacs, scripting and anything text oriented.

Using Emacs advice to silence messages from functions

Kaushal Modi

Using the :around advice to prevent messages from being displayed in the echo area or the ∗Messages∗ buffer.

Today I was working on a minor cleanup of the ox-hugo package and as a part of that, I figured out a way to reduce the noisy messages printed in the ∗Messages∗  Random tidbit: You can quickly access the ∗Messages∗ buffer using the default binding C-h e. buffer. This post describes how I used the 📖 Emacs Advice feature to solve the noisy-messages-buffer problem.

What is this ‘advising’?
In Emacs, advising  Advice with a ‘c’ is the noun form. The noun form is used everywhere in the Emacs advising API e.g. advice-add, advice-remove. Advise with an ’s’ is the verb form. More Reading. is a means to modify a function defined in any library — You can modify the arguments to the function, return values of that function, run other functions before and/or after that advised function, .. or may be just replace that function with something else entirely!

Problem Statement #

If I run C-c C-e H A  This is the default binding of ox-hugo to export all the post subtrees in the current Org file. in this site’s Org file, I will see a wall of messages, which won’t be too useful to look at if I am exporting dozens of posts in one go.

Here’s a snippet from that which shows the text printed while two of the posts were being exporting:

[ox-hugo] 60/ Exporting ‘Nim: Deploying static binaries’ ..
org-babel-exp process nim at position 246918...
Evaluation of this nim code block is disabled.
org-babel-exp process shell at position 246985...
org-babel-exp process shell at position 247505...
org-babel-exp process shell at position 248054...
org-babel-exp process nim at position 250620...
Evaluation of this nim code block (code__musl_config_nims) is disabled.
org-babel-exp process yaml at position 253830...
Wrote /home/kmodi/hugo/scripter.co/content/posts/nim-deploying-static-binaries/index.md
[ox-hugo] 61/ Exporting ‘Binding Nim to C++ std::list’ ..
org-babel-exp process nim at position 261418...
Evaluation of this nim code block (code__nim_bindings_importcpp) is disabled.
org-babel-exp process nim at position 262802...
Evaluation of this nim code block (code__nim_std_list_bindings) is disabled.
org-babel-exp process cpp at position 266921...
org-babel-exp process nim at position 267957...
Evaluation of this nim code block (code__nim_bindings_emit) is disabled.
org-babel-exp process nim at position 268861...
Evaluation of this nim code block (code__nim_bindings_test_nim_code) is disabled.
org-babel-exp process text at position 269849...
org-babel-exp process nim at position 270412...
Evaluation of this nim code block (code__nim_bindings_toSeq) is disabled.
org-babel-exp process nim at position 271808...
Evaluation of this nim code block (code__nim_bindings_full_code) is disabled.
Wrote /home/kmodi/hugo/scripter.co/content/posts/binding-nim-to-c-plus-plus-std-list/index.md
Code Snippet 1: Snippet of the noise seen in the ∗Messages∗ buffer when exporting all post subtrees using ox-hugo

After applying the advices described in this post, that output reduced to this:

[ox-hugo] 60/ Exporting ‘Nim: Deploying static binaries’ ..
[ox-hugo] 61/ Exporting ‘Binding Nim to C++ std::list’ ..
Code Snippet 2: Snippet of the ∗Messages∗ buffer after applying the silencing advices

Course of action #

Here is how I tackled this problem:

  1. Find the functions responsible for printing those messages.
  2. Figure out how to silence those messages.
  3. Advise those functions (temporarily, while the export is happening) to not print those messages.

1 Finding the culprit functions #

In Code Snippet 1, I have highlighted 2 lines. I first needed to figure out which functions printed those messages. I’ll show two methods of finding sources of any printed messages.

Using plain-old grep or rg #

This method is pretty easy (but not robust) to use if the search string is unique enough. The first highlighted line above is a good candidate for this:

org-babel-exp process nim at position 246918…

The message gives me a clue that it’s originating somewhere in the Org Babel source code (one of the ob-*.el files). So I cd to the Org source directory and search for the “org-babel-exp process ..” string ..

Figure 1: Finding the source of “org-babel-exp process ..” message using ripgrep (rg) in Org source

Figure 1: Finding the source of “org-babel-exp process ..” message using ripgrep (rg) in Org source

.. and sure enough, there is this one and only one hit .. in the org-babel-exp-src-block function in lisp/ob-exp.el.

But this method is not robust for some obvious reasons like,

  • the string being grepped might not be present in raw literal form like above,
  • may be it’s created through variable interpolation, or
  • may be that whole string is not present on the same line; may be it wrapped to the next line.

While this grep method helped find the source of “org-babel-exp process ..”, let’s use a better way of source tracing for the next message.

Using debug-on-message #

From C-h v debug-on-message, we see:

If non-nil, debug if a message matching this regexp is displayed.

This means that each time a message gets displayed that matches the regular expression set in this variable, we get a backtrace to that message. Who said we can get backtraces for errors only‽ 🤓

The second highlighted line from Code Snippet 1 is:

Wrote /home/kmodi/hugo/scripter.co/content/posts/nim-deploying-static-binaries/index.md

After evaluating (setq debug-on-message "Wrote"), when I exported this post, I saw a backtrace that looked like this.

Debugger entered--Lisp error: "Wrote /home/kmodi/hugo/scripter.co/content/posts/u..."
  write-region(nil nil "/home/kmodi/hugo/scripter.co/ ..
  basic-save-buffer-2()
  basic-save-buffer-1()
  ..
  basic-save-buffer(nil)
  save-buffer()
  write-file("/home/kmodi/hugo/scripter.co/content/posts/using-e...")
  ..
  org-export-to-file(hugo "/home/kmodi/hugo/scripter.co/content/posts/using-e..." ..
  org-hugo-export-to-md(nil :subtreep nil)
  ..
Code Snippet 3: Backtrace created on setting debug-on-message to "Wrote"

The top line of that backtrace shows the source of that message: write-region.

Remember to reset debug-on-message back to nil by evaluating (setq debug-on-message nil) once you are done with this debug!

2 Figure out how to silence those messages #

Now that I knew that I needed to alter the org-babel-exp-src-block and write-region functions  There was a third function too that I found using this second method: table-generate-source. This was printing “Generating source…” when parsing each table.el message. , I wanted to quiet these messages in both the echo area and the ∗Messages∗ buffer, but only when exporting all the post subtrees from a file.

Emacs provides us ways to separately control the displaying of messages in those regions:

Prevent messages in the echo area
From 📖 Elisp – Displaying Messages  You can visit the same manual page from within Emacs by C-h i g (elisp)Displaying Messages. , if inhibit-message is set to t , message outputs are not shown in the echo area (though they would still be logged in the ∗Messages∗ buffer.
Prevent messages in the ∗Messages∗ buffer
From 📖 Elisp – Logging Messages  C-h i g (elisp)Logging Messages , if message-log-max is set to nil , no messages will be printed in the ∗Messages∗ buffer.

3 Applying the ‘silence’ advice #

With that information, I created this advising function which calls the function name it receives in its first argument.

1
2
3
4
5
(defun org-hugo--advice-silence-messages (orig-fun &rest args)
  "Advice function that silences all messages in ORIG-FUN."
  (let ((inhibit-message t)      ;Don't show the messages in Echo area
        (message-log-max nil))   ;Don't show the messages in the *Messages* buffer
    (apply orig-fun args)))
Code Snippet 4: Advising function to silence the messages in the advised orig-fun

Note that the above function does not set the inhibit-message and message-log-max variables globally (e.g. using setq). We do not want to permanently stop the printing of messages for all the functions! That’s why they are set in the let scope or the local scope, and the orig-fun is called within that scope.

This snippet, though, only defines the advising function. The advices are actually applied using the advice-add  The counterpart of advice-add is advice-remove, to remove the advising functions from the original functions. function.

Emacs provides a variety of Advice Combinators. These combinators are used to specify how an advice should be applied to the original function. I have found the :around combinator to usually fit my needs because the advices I add typically look like:

  1. Do something before.
  2. Set some variables in a let scope.
  3. Call the original function with its arguments inside that scope.
  4. Do something afterwards.

As I wanted to apply the same advice to multiple functions (org-babel-exp-src-block and write-region), I used dolist to loop through them and advise them to 🤫 :

1
2
(dolist (fn '(org-babel-exp-src-block write-region)
  (advice-add fn :around #'org-hugo--advice-silence-messages))
Code Snippet 5: Enabling the advices

Limiting the scope of these advices #

You might wonder — If ox-hugo is applying these advices, wouldn’t it affect the global behavior of write-region, etc. outside of ox-hugo exports as well?

    The answer is “no”.

In ox-hugo, the advice-add calls are made just before the “export all post subtrees” operation begins. Once the export command finishes, advice-remove calls remove those advices from those functions. So those advices are effective only for the duration of the export.

Details of the implementation can be seen in this ox-hugo commit.

Conclusion #

The main effort here was to (i) identify the problem, (ii) find out which functions were causing the problem, and (iii) what knobs or variables are available to us to help fix that.

Once that is done, you write an advising function and advice-add that function to the original function using a combinator of your choice  If you are not sure which combinator to use, pick :around 😃. .

In this post, I am using Emacs advices to make ox-hugo exports less noisy. But you can add similar advices to your Emacs config  You can find the ways I am using advices in my Emacs config here. to tweak and fine-tune any function that’s getting on your nerves 😄.