diagnostics.zsh
11698 bytes
1# diagnostics.zsh
2#
3# Diagnostic and debugging support for oh-my-zsh
4
5# omz_diagnostic_dump()
6#
7# Author: Andrew Janke <andrew@apjanke.net>
8#
9# Usage:
10#
11# omz_diagnostic_dump [-v] [-V] [file]
12#
13# NOTE: This is a work in progress. Its interface and behavior are going to change,
14# and probably in non-back-compatible ways.
15#
16# Outputs a bunch of information about the state and configuration of
17# oh-my-zsh, zsh, and the user's system. This is intended to provide a
18# bunch of context for diagnosing your own or a third party's problems, and to
19# be suitable for posting to public bug reports.
20#
21# The output is human-readable and its format may change over time. It is not
22# suitable for parsing. All the output is in one single file so it can be posted
23# as a gist or bug comment on GitHub. GitHub doesn't support attaching tarballs
24# or other files to bugs; otherwise, this would probably have an option to produce
25# tarballs that contain copies of the config and customization files instead of
26# catting them all in to one file.
27#
28# This is intended to be widely portable, and run anywhere that oh-my-zsh does.
29# Feel free to report any portability issues as bugs.
30#
31# This is written in a defensive style so it still works (and can detect) cases when
32# basic functionality like echo and which have been redefined. In particular, almost
33# everything is invoked with "builtin" or "command", to work in the face of user
34# redefinitions.
35#
36# OPTIONS
37#
38# [file] Specifies the output file. If not given, a file in the current directory
39# is selected automatically.
40#
41# -v Increase the verbosity of the dump output. May be specified multiple times.
42# Verbosity levels:
43# 0 - Basic info, shell state, omz configuration, git state
44# 1 - (default) Adds key binding info and configuration file contents
45# 2 - Adds zcompdump file contents
46#
47# -V Reduce the verbosity of the dump output. May be specified multiple times.
48#
49# TODO:
50# * Multi-file capture
51# * Add automatic gist uploading
52# * Consider whether to move default output file location to TMPDIR. More robust
53# but less user friendly.
54#
55
56autoload -Uz is-at-least
57
58function omz_diagnostic_dump() {
59 emulate -L zsh
60
61 builtin echo "Generating diagnostic dump; please be patient..."
62
63 local thisfcn=omz_diagnostic_dump
64 local -A opts
65 local opt_verbose opt_noverbose opt_outfile
66 local timestamp=$(date +%Y%m%d-%H%M%S)
67 local outfile=omz_diagdump_$timestamp.txt
68 builtin zparseopts -A opts -D -- "v+=opt_verbose" "V+=opt_noverbose"
69 local verbose n_verbose=${#opt_verbose} n_noverbose=${#opt_noverbose}
70 (( verbose = 1 + n_verbose - n_noverbose ))
71
72 if [[ ${#*} > 0 ]]; then
73 opt_outfile=$1
74 fi
75 if [[ ${#*} > 1 ]]; then
76 builtin echo "$thisfcn: error: too many arguments" >&2
77 return 1
78 fi
79 if [[ -n "$opt_outfile" ]]; then
80 outfile="$opt_outfile"
81 fi
82
83 # Always write directly to a file so terminal escape sequences are
84 # captured cleanly
85 _omz_diag_dump_one_big_text &> "$outfile"
86 if [[ $? != 0 ]]; then
87 builtin echo "$thisfcn: error while creating diagnostic dump; see $outfile for details"
88 fi
89
90 builtin echo
91 builtin echo Diagnostic dump file created at: "$outfile"
92 builtin echo
93 builtin echo To share this with OMZ developers, post it as a gist on GitHub
94 builtin echo at "https://gist.github.com" and share the link to the gist.
95 builtin echo
96 builtin echo "WARNING: This dump file contains all your zsh and omz configuration files,"
97 builtin echo "so don't share it publicly if there's sensitive information in them."
98 builtin echo
99
100}
101
102function _omz_diag_dump_one_big_text() {
103 local program programs progfile md5
104
105 builtin echo oh-my-zsh diagnostic dump
106 builtin echo
107 builtin echo $outfile
108 builtin echo
109
110 # Basic system and zsh information
111 command date
112 command uname -a
113 builtin echo OSTYPE=$OSTYPE
114 builtin echo ZSH_VERSION=$ZSH_VERSION
115 builtin echo User: $USERNAME
116 builtin echo umask: $(umask)
117 builtin echo
118 _omz_diag_dump_os_specific_version
119 builtin echo
120
121 # Installed programs
122 programs=(sh zsh ksh bash sed cat grep ls find git posh)
123 local progfile="" extra_str="" sha_str=""
124 for program in $programs; do
125 extra_str="" sha_str=""
126 progfile=$(builtin which $program)
127 if [[ $? == 0 ]]; then
128 if [[ -e $progfile ]]; then
129 if builtin whence shasum &>/dev/null; then
130 sha_str=($(command shasum $progfile))
131 sha_str=$sha_str[1]
132 extra_str+=" SHA $sha_str"
133 fi
134 if [[ -h "$progfile" ]]; then
135 extra_str+=" ( -> ${progfile:A} )"
136 fi
137 fi
138 builtin printf '%-9s %-20s %s\n' "$program is" "$progfile" "$extra_str"
139 else
140 builtin echo "$program: not found"
141 fi
142 done
143 builtin echo
144 builtin echo Command Versions:
145 builtin echo "zsh: $(zsh --version)"
146 builtin echo "this zsh session: $ZSH_VERSION"
147 builtin echo "bash: $(bash --version | command grep bash)"
148 builtin echo "git: $(git --version)"
149 builtin echo "grep: $(grep --version)"
150 builtin echo
151
152 # Core command definitions
153 _omz_diag_dump_check_core_commands || return 1
154 builtin echo
155
156 # ZSH Process state
157 builtin echo Process state:
158 builtin echo pwd: $PWD
159 if builtin whence pstree &>/dev/null; then
160 builtin echo Process tree for this shell:
161 pstree -p $$
162 else
163 ps -fT
164 fi
165 builtin set | command grep -a '^\(ZSH\|plugins\|TERM\|LC_\|LANG\|precmd\|chpwd\|preexec\|FPATH\|TTY\|DISPLAY\|PATH\)\|OMZ'
166 builtin echo
167 #TODO: Should this include `env` instead of or in addition to `export`?
168 builtin echo Exported:
169 builtin echo $(builtin export | command sed 's/=.*//')
170 builtin echo
171 builtin echo Locale:
172 command locale
173 builtin echo
174
175 # Zsh installation and configuration
176 builtin echo Zsh configuration:
177 builtin echo setopt: $(builtin setopt)
178 builtin echo
179 builtin echo zstyle:
180 builtin zstyle
181 builtin echo
182 builtin echo 'compaudit output:'
183 compaudit
184 builtin echo
185 builtin echo '$fpath directories:'
186 command ls -lad $fpath
187 builtin echo
188
189 # Oh-my-zsh installation
190 builtin echo oh-my-zsh installation:
191 command ls -ld ~/.z*
192 command ls -ld ~/.oh*
193 builtin echo
194 builtin echo oh-my-zsh git state:
195 (builtin cd $ZSH && builtin echo "HEAD: $(git rev-parse HEAD)" && git remote -v && git status | command grep "[^[:space:]]")
196 if [[ $verbose -ge 1 ]]; then
197 (builtin cd $ZSH && git reflog --date=default | command grep pull)
198 fi
199 builtin echo
200 if [[ -e $ZSH_CUSTOM ]]; then
201 local custom_dir=$ZSH_CUSTOM
202 if [[ -h $custom_dir ]]; then
203 custom_dir=$(builtin cd $custom_dir && pwd -P)
204 fi
205 builtin echo "oh-my-zsh custom dir:"
206 builtin echo " $ZSH_CUSTOM ($custom_dir)"
207 (builtin cd ${custom_dir:h} && command find ${custom_dir:t} -name .git -prune -o -print)
208 builtin echo
209 fi
210
211 # Key binding and terminal info
212 if [[ $verbose -ge 1 ]]; then
213 builtin echo "bindkey:"
214 builtin bindkey
215 builtin echo
216 builtin echo "infocmp:"
217 command infocmp -L
218 builtin echo
219 fi
220
221 # Configuration file info
222 local zdotdir=${ZDOTDIR:-$HOME}
223 builtin echo "Zsh configuration files:"
224 local cfgfile cfgfiles
225 # Some files for bash that zsh does not use are intentionally included
226 # to help with diagnosing behavior differences between bash and zsh
227 cfgfiles=( /etc/zshenv /etc/zprofile /etc/zshrc /etc/zlogin /etc/zlogout
228 $zdotdir/.zshenv $zdotdir/.zprofile $zdotdir/.zshrc $zdotdir/.zlogin $zdotdir/.zlogout
229 ~/.zsh.pre-oh-my-zsh
230 /etc/bashrc /etc/profile ~/.bashrc ~/.profile ~/.bash_profile ~/.bash_logout )
231 command ls -lad $cfgfiles 2>&1
232 builtin echo
233 if [[ $verbose -ge 1 ]]; then
234 for cfgfile in $cfgfiles; do
235 _omz_diag_dump_echo_file_w_header $cfgfile
236 done
237 fi
238 builtin echo
239 builtin echo "Zsh compdump files:"
240 local dumpfile dumpfiles
241 command ls -lad $zdotdir/.zcompdump*
242 dumpfiles=( $zdotdir/.zcompdump*(N) )
243 if [[ $verbose -ge 2 ]]; then
244 for dumpfile in $dumpfiles; do
245 _omz_diag_dump_echo_file_w_header $dumpfile
246 done
247 fi
248
249}
250
251function _omz_diag_dump_check_core_commands() {
252 builtin echo "Core command check:"
253 local redefined name builtins externals reserved_words
254 redefined=()
255 # All the zsh non-module builtin commands
256 # These are taken from the zsh reference manual for 5.0.2
257 # Commands from modules should not be included.
258 # (For back-compatibility, if any of these are newish, they should be removed,
259 # or at least made conditional on the version of the current running zsh.)
260 # "history" is also excluded because OMZ is known to redefine that
261 reserved_words=( do done esac then elif else fi for case if while function
262 repeat time until select coproc nocorrect foreach end '!' '[[' '{' '}'
263 )
264 builtins=( alias autoload bg bindkey break builtin bye cd chdir command
265 comparguments compcall compctl compdescribe compfiles compgroups compquote comptags
266 comptry compvalues continue dirs disable disown echo echotc echoti emulate
267 enable eval exec exit false fc fg functions getln getopts hash
268 jobs kill let limit log logout noglob popd print printf
269 pushd pushln pwd r read rehash return sched set setopt shift
270 source suspend test times trap true ttyctl type ulimit umask unalias
271 unfunction unhash unlimit unset unsetopt vared wait whence where which zcompile
272 zle zmodload zparseopts zregexparse zstyle )
273 if is-at-least 5.1; then
274 reserved_word+=( declare export integer float local readonly typeset )
275 else
276 builtins+=( declare export integer float local readonly typeset )
277 fi
278 builtins_fatal=( builtin command local )
279 externals=( zsh )
280 for name in $reserved_words; do
281 if [[ $(builtin whence -w $name) != "$name: reserved" ]]; then
282 builtin echo "reserved word '$name' has been redefined"
283 builtin which $name
284 redefined+=$name
285 fi
286 done
287 for name in $builtins; do
288 if [[ $(builtin whence -w $name) != "$name: builtin" ]]; then
289 builtin echo "builtin '$name' has been redefined"
290 builtin which $name
291 redefined+=$name
292 fi
293 done
294 for name in $externals; do
295 if [[ $(builtin whence -w $name) != "$name: command" ]]; then
296 builtin echo "command '$name' has been redefined"
297 builtin which $name
298 redefined+=$name
299 fi
300 done
301
302 if [[ -n "$redefined" ]]; then
303 builtin echo "SOME CORE COMMANDS HAVE BEEN REDEFINED: $redefined"
304 else
305 builtin echo "All core commands are defined normally"
306 fi
307
308}
309
310function _omz_diag_dump_echo_file_w_header() {
311 local file=$1
312 if [[ ( -f $file || -h $file ) ]]; then
313 builtin echo "========== $file =========="
314 if [[ -h $file ]]; then
315 builtin echo "========== ( => ${file:A} ) =========="
316 fi
317 command cat $file
318 builtin echo "========== end $file =========="
319 builtin echo
320 elif [[ -d $file ]]; then
321 builtin echo "File '$file' is a directory"
322 elif [[ ! -e $file ]]; then
323 builtin echo "File '$file' does not exist"
324 else
325 command ls -lad "$file"
326 fi
327}
328
329function _omz_diag_dump_os_specific_version() {
330 local osname osver version_file version_files
331 case "$OSTYPE" in
332 darwin*)
333 osname=$(command sw_vers -productName)
334 osver=$(command sw_vers -productVersion)
335 builtin echo "OS Version: $osname $osver build $(sw_vers -buildVersion)"
336 ;;
337 cygwin)
338 command systeminfo | command head -n 4 | command tail -n 2
339 ;;
340 esac
341
342 if builtin which lsb_release >/dev/null; then
343 builtin echo "OS Release: $(command lsb_release -s -d)"
344 fi
345
346 version_files=( /etc/*-release(N) /etc/*-version(N) /etc/*_version(N) )
347 for version_file in $version_files; do
348 builtin echo "$version_file:"
349 command cat "$version_file"
350 builtin echo
351 done
352}
353