Repository navigation
Expand file tree
/
Copy patherrorlog.func
More file actions
250 lines (228 loc) · 10.5 KB
/
Copy patherrorlog.func
File metadata and controls
250 lines (228 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# License: MIT | https://raw.githubusercontent.com/community-scripts/core/main/LICENSE
# ==============================================================================
# API/ERROR LOG EXTRACTION
# ==============================================================================
# Turns a failure into the telemetry error trace: which capture belongs to it,
# what the failing step printed (progress chatter removed), and the one-line
# header. Loaded through api/api.func; host side only.
#
# The .errinfo capture, written by silent() and the error handler:
#
# EXIT_CODE=<n>
# LINE=<n>
# COMMAND=<command, one line>
# FUNCTION=<function the command ran in>
# STEP=<msg_info text of the running step>
# MESSAGE=<first msg_error of that step>
# PHASE=<host|container>
# --- OUTPUT ---
# <raw output>
# ==============================================================================
[[ -n "${_API_ERRORLOG_LOADED:-}" ]] && return 0
_API_ERRORLOG_LOADED=1
# json_escape <text> - JSON string body, control characters and ANSI removed
json_escape() {
local input
input=$(printf '%s' "$1" |
sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' |
tr -d '\000-\010\013\014\016-\037\177\r')
if command -v jq &>/dev/null; then
printf '%s' "$input" | jq -Rs '.' | sed 's/^"//;s/"$//'
return 0
fi
printf '%s' "$input" |
awk '
BEGIN { ORS="" }
{
if (NR > 1) printf "%s", "\\n"
for (i = 1; i <= length($0); i++) {
c = substr($0, i, 1)
if (c == "\\") printf "%s", "\\\\"
else if (c == "\"") printf "%s", "\\\""
else if (c == "\t") printf "%s", "\\t"
else printf "%s", c
}
}'
}
# Lines that never explain a failure.
_TM_NOISE=(
'(Get|Hit|Ign):[0-9]+ ' 'Fetched [0-9]' 'Reading (package lists|state information|database)'
'Building dependency tree' '\(Reading database' 'Selecting previously unselected' 'Preparing to unpack'
'Unpacking ' 'Setting up ' 'Processing triggers for' 'update-alternatives: ' 'Created symlink '
"Removed ['\"]?/etc/systemd" 'Generating locales' 'Generation complete' 'Need to get [0-9]' 'After this operation'
'[0-9]+ upgraded, [0-9]+ newly installed' 'The following (NEW |additional )?packages will be'
'Updating certificates in' 'Running hooks in' '[0-9]+ added, [0-9]+ removed; done' 'done\.$'
'(Summary|Installing|Upgrading):$' ' (Upgrading|Installing): [0-9]+, ' ' (Download size|Space needed): '
'debconf: (unable to initialize frontend|\(|falling back to frontend|delaying package configuration)'
'invoke-rc\.d: (could not determine current runlevel|policy-rc\.d denied)'
'Could not execute systemctl: +at /usr/bin/deb-systemd-invoke' 'rehash: warning: skipping'
'Creating config file ' '(info: )?Adding (new )?(user|group|system user) ' 'info: (Selecting|Not creating|Creating home)'
'\([0-9]+/[0-9]+\) (Installing|Upgrading|Purging|Replacing) ' 'Executing [^ ]+\.(trigger|post-install|pre-install|post-upgrade)'
'OK: [0-9.]+ ?[KMG]i?B in [0-9]+ packages' 'fetch https?://'
' +(Installing|Upgrading|Verifying|Running scriptlet|Preparing|Cleanup|Downgrading|Erasing) *: '
' *[0-9]+K[ .]{10,}' 'Saving to: ' 'Length: [0-9]' 'Resolving [^ ]+ \(' 'Connecting to .*connected\.$'
'HTTP request sent, awaiting response\.\.\. (20[0-9]|30[0-9])' 'Location: .*\[following\]'
' *% Total +% Received' ' *[0-9]+ +[0-9.]+[kMG]? +[0-9]+ +[0-9.]+[kMG]? +[0-9]+ +[0-9]+ .*[0-9:-]{8}'
'go: (downloading|finding|extracting) ' ' +(Compiling|Downloaded|Downloading|Fresh|Locking|Updating crates\.io index) '
'(Collecting|Using cached|Requirement already satisfied|Installing collected packages) '
' *(Downloaded|Prepared|Installed|Resolved|Audited|Uninstalled|Built) [0-9]+ packages? ' ' [+-] [A-Za-z0-9_.-]+=='
'Using C?Python [0-9]' 'Creating virtual environment at' 'npm (warn|WARN) deprecated'
'(added|removed|changed) [0-9]+ packages' '[0-9]+ packages? (is|are) looking for funding' ' run `npm fund`'
'found 0 vulnerabilities' 'Progress: resolved [0-9]+' '\[[0-9]/[0-9]\] (Resolving|Fetching|Linking|Building)'
'(remote: )?(Enumerating|Counting|Compressing|Total) objects' 'Receiving objects:' 'Resolving deltas:' 'Updating files:'
'\++ '
' WARNING: (You have not turned on protection|Set activation/thin_pool|Sum of all thin volume)'
' Logical volume .* (created|changed)\.' 'Creating filesystem with' 'Filesystem UUID:' 'Superblock backups stored'
'[[:space:]]+[0-9]+(, [0-9]+)+,?$' '(Allocating group tables|Writing inode tables|Creating journal|Writing superblocks|Discarding device blocks)'
'extracting archive' 'Total bytes read:' 'Detected container architecture' 'Creating SSH host key' 'done: SHA256:'
'={20,}$' 'COMBINED INSTALLATION LOG' 'PHASE [0-9]+: ' '(Container ID|Session ID|Timestamp): '
'[[:space:]]*(⏳|✔|✖|💡|ℹ|🔧|📋)'
)
_TM_NOISE_RE="^($(IFS='|'; echo "${_TM_NOISE[*]}"))"
# stdin filter: ANSI and CR stripped, IPv4 anonymised, noise and blank lines dropped
_tm_clean_log() {
sed -E -e 's/\r$//' -e 's/\x1b\[[0-9;?]*[a-zA-Z]//g' \
-e 's/\b([0-9]{1,3})\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\b/\1.x.x.x/g' |
grep -avE "$_TM_NOISE_RE" | grep -avE '^[[:space:]]*$' || true
}
# stdin filter: from the last [INFO] or [OK] log line on - the step that failed
_tm_step_output() {
awk '/^\[[0-9-]+ [0-9:]+\] \[(INFO|OK)\] /{n=0} {buf[n++]=$0} END{for(i=0;i<n;i++)print buf[i]}'
}
# stdin filter: what goes into the trace
_tm_focus() {
_tm_step_output | _tm_clean_log | tail -n "${TELEMETRY_ERROR_MAX_LINES:-60}" | head -c "${TELEMETRY_ERROR_MAX_BYTES:-10240}"
}
# The most specific log that exists: combined (host + container), install, build.
_tm_pick_logfile() {
local candidate
for candidate in \
"${combined_log:-}" \
"/tmp/install-${SESSION_ID:-}-combined.log" \
"/tmp/${NSAPP:-lxc}-${CTID:-}-${SESSION_ID:-}.log" \
"${INSTALL_LOG:-}" \
"${BUILD_LOG:-}" \
"${SILENT_LOGFILE:-}"; do
if [[ -n "$candidate" && -s "$candidate" ]]; then
echo "$candidate"
return 0
fi
done
return 0
}
# get_error_log - the failing step's output from the active log, when there is no capture
get_error_log() {
local logfile
if declare -f ensure_log_on_host >/dev/null 2>&1; then
ensure_log_on_host 2>/dev/null || true
fi
logfile=$(_tm_pick_logfile)
[[ -z "$logfile" || ! -s "$logfile" ]] && return 0
_tm_focus <"$logfile"
}
# The capture for this failure: the container's (pulled by the host), else this side's.
_tm_find_errinfo() {
local candidate
for candidate in \
"${TELEMETRY_ERRINFO:-}" \
"/tmp/.errinfo-${SESSION_ID:-none}" \
"${BUILD_LOG:-/nonexistent}.errinfo" \
"${INSTALL_LOG:-/nonexistent}.errinfo"; do
if [[ -n "$candidate" && -s "$candidate" ]]; then
echo "$candidate"
return 0
fi
done
return 1
}
# Tool-aware explanation when the error handler is loaded (it knows which tool ran).
_tm_explain() {
if [[ -n "${2:-}" ]] && declare -f _eh_explanation >/dev/null 2>&1; then
_eh_explanation "$1" "$2"
else
explain_exit_code "$1"
fi
}
_tm_one_line() {
printf '%s' "$1" | tr '\n\t' ' ' | sed -E 's/ +/ /g; s/^ //; s/ $//' | head -c "${2:-300}"
}
# ------------------------------------------------------------------------------
# _tm_error_context <exit_code>
#
# Sets TM_ERR_{CODE,COMMAND,LINE,FUNCTION,STEP,MESSAGE,PHASE,OUTPUT,EXPLANATION}
# in the calling shell. A capture counts only when its code matches the one
# being reported (or that code is the generic 1/255 bash passes up); otherwise
# it belongs to an earlier failure.
# ------------------------------------------------------------------------------
_tm_error_context() {
local exit_code="${1:-1}" errinfo="" captured=""
TM_ERR_CODE="$exit_code"
TM_ERR_COMMAND="${FAILED_COMMAND:-}"
TM_ERR_LINE="${FAILED_LINE:-}"
TM_ERR_FUNCTION="${FAILED_FUNCTION:-}"
TM_ERR_STEP="${CS_STEP:-${CS_STEP_DONE:+after ${CS_STEP_DONE}}}"
TM_ERR_MESSAGE="${CS_ERROR_MSG:-}"
TM_ERR_PHASE="host"
TM_ERR_OUTPUT=""
if errinfo=$(_tm_find_errinfo); then
captured=$(sed -n 's/^EXIT_CODE=//p' "$errinfo" | head -n1)
if [[ -n "$captured" && ("$captured" == "$exit_code" || "$exit_code" == "1" || "$exit_code" == "255") ]]; then
TM_ERR_CODE="$captured"
TM_ERR_LINE=$(sed -n 's/^LINE=//p' "$errinfo" | head -n1)
TM_ERR_COMMAND=$(sed -n 's/^COMMAND=//p' "$errinfo" | head -n1)
TM_ERR_FUNCTION=$(sed -n 's/^FUNCTION=//p' "$errinfo" | head -n1)
local phase v
phase=$(sed -n 's/^PHASE=//p' "$errinfo" | head -n1)
[[ -z "$phase" && "$errinfo" == "/tmp/.errinfo-${SESSION_ID:-none}" ]] && phase="container"
if [[ "$phase" == "container" ]]; then
# The host's own message ("Installation failed in container ...") says nothing the container did not.
TM_ERR_PHASE="container"
TM_ERR_STEP=$(sed -n 's/^STEP=//p' "$errinfo" | head -n1)
TM_ERR_MESSAGE=$(sed -n 's/^MESSAGE=//p' "$errinfo" | head -n1)
else
v=$(sed -n 's/^STEP=//p' "$errinfo" | head -n1)
[[ -n "$v" ]] && TM_ERR_STEP="$v"
v=$(sed -n 's/^MESSAGE=//p' "$errinfo" | head -n1)
[[ -n "$v" ]] && TM_ERR_MESSAGE="$v"
fi
TM_ERR_OUTPUT=$(awk 'f{print} /^--- OUTPUT ---$/{f=1}' "$errinfo" | _tm_focus)
fi
fi
[[ -z "$TM_ERR_OUTPUT" ]] && TM_ERR_OUTPUT=$(get_error_log)
[[ "$TM_ERR_LINE" =~ ^[0-9]+$ ]] || TM_ERR_LINE=""
[[ "$TM_ERR_COMMAND" == "unknown" ]] && TM_ERR_COMMAND=""
[[ "$TM_ERR_FUNCTION" == "main" ]] && TM_ERR_FUNCTION=""
TM_ERR_COMMAND=$(_tm_one_line "$TM_ERR_COMMAND" 300)
TM_ERR_STEP=$(_tm_one_line "$TM_ERR_STEP" 120)
TM_ERR_MESSAGE=$(_tm_one_line "$TM_ERR_MESSAGE" 300 | sed -E 's/\b([0-9]{1,3})\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\b/\1.x.x.x/g')
TM_ERR_EXPLANATION=$(_tm_explain "$TM_ERR_CODE" "$TM_ERR_COMMAND")
}
# _tm_format_error - the trace from TM_ERR_* (see _tm_error_context)
#
# exit_code=N | <explanation>[ | step: S][ | in F][ | at line L: C]
# ---
# <output of the failing step>
_tm_format_error() {
local header="exit_code=${TM_ERR_CODE} | ${TM_ERR_EXPLANATION}"
[[ -n "$TM_ERR_STEP" ]] && header+=" | step: ${TM_ERR_STEP}"
[[ -n "$TM_ERR_FUNCTION" ]] && header+=" | in ${TM_ERR_FUNCTION}"
if [[ -n "$TM_ERR_COMMAND" && -n "$TM_ERR_LINE" ]]; then
header+=" | at line ${TM_ERR_LINE}: ${TM_ERR_COMMAND}"
elif [[ -n "$TM_ERR_COMMAND" ]]; then
header+=" | command: ${TM_ERR_COMMAND}"
fi
if [[ -n "$TM_ERR_OUTPUT" ]]; then
printf '%s
---
%s' "$header" "$TM_ERR_OUTPUT"
else
printf '%s' "$header"
fi
}
# build_error_string <exit_code>
build_error_string() {
_tm_error_context "${1:-1}"
_tm_format_error
}