Updated patch attached. Now designed only for debugging and profiling. Applies to trunk. Changes: It now uses a single centralized hook, rather than one per symbol. It passes the new value as an argument, rather than passing only the symbol and environment. The speed (for both hooked and unhooked symbols) is unaffected by property lists. Unhooked symbols are immediately skipped. It still passes the environment, since it's useful during debugging to notice when your setq is accidentally setting a buffer-local variable that you thought didn't exist, or setting a global that you thought had been made buffer-local, or setting a dynamic local because some other code (that called your code) let-bound a symbol that you thought you were setting globally. Also, if you do: (let ((foo 'bar)) (setq-default foo 'baz)) then varhook now intentionally reports env as ⌜invalid⌝ (but the behavior of the code is not changed). If that's actually a valid thing to do, then I'll change how it's reported. I'm unclear on your reason for extending the «constant» field to include the «hooked» bit, rather than giving the latter its own name. Either way, a new bit is needed (I can't fit the meaning of «hooked» into «constant»'s current two bits), and either way, the size of a symbol remains unchanged: 24 bytes on 32-bit platforms, and 48 bytes on 64-bits. The bit field now has 21 and 53 remaining unused bits, respectively, after «hooked» is added. Example usage: (setq syms-to-watch '(foo bar poo par goo gar)) (setq syms-to-pause-on '(poo par)) (setq sym-profiles '((foo 0) (bar 0) (goo 0) (gar 0))) (setq nonglobals-to-barf-on '(goo gar)) ; Supposed to be set only globally (defun tattle (sym env val) (if (boundp sym) (message "Symbol %S modified in env %S. New value: %S" sym env val) (message "Symbol %S unbound in env %S" sym env))) (defun pause (sym _env _val) (if (memq sym syms-to-pause-on) (unless (y-or-n-p "Continue? ") (keyboard-quit)))) (defun profile (sym _env _val) (let ((p (assq sym sym-profiles))) (if p (incf (cadr p))))) (defun barf-nonglobal (sym env _val) (and (not (eq env 'global)) (memq sym nonglobals-to-barf-on) (debug))) (add-hook 'varhook #'tattle) ; There's only one hook, used for all symbols (add-hook 'varhook #'pause t) (add-hook 'varhook #'profile) (add-hook 'varhook #'barf-nonglobal t) (mapc #'hook syms-to-watch) ;; Open *Messages* in another window, then do your debugging... (mapc #'unhook syms-to-watch) ; When you're done. (cl-loop for s being the symbols ; Check if anything is still hooked with hooked = nil finally return hooked do (if (hookedp s) (push s hooked)))