6
6
#include " ser.h"
7
7
#include " setup.h"
8
8
9
+ #include < openssl/rand.h>
10
+
9
11
#include < kj/debug.h>
10
12
11
13
#include < cstdlib>
@@ -90,9 +92,53 @@ kj::String typeName(const std::type_info& type) {
90
92
return kj::mv (result);
91
93
}
92
94
95
+ namespace {
96
+
97
+ // For internal errors, we generate an ID to include when rendering user-facing "internal error"
98
+ // exceptions and writing internal exception logs, to make it easier to search for logs
99
+ // corresponding to "internal error" exceptions reported by users.
100
+ //
101
+ // We'll use an ID of 24 base-32 encoded characters, just because its relatively simple to
102
+ // generate from random bytes. This should give us a value with 120 bits of uniqueness, which is
103
+ // about as good as a UUID.
104
+ //
105
+ // (We're not using base-64 encoding to avoid issues with case insensitive search, as well as
106
+ // ensuring that the id is easy to select and copy via double-clicking.)
107
+ using InternalErrorId = kj::FixedArray<char , 24 >;
108
+
109
+ constexpr char BASE32_DIGITS[] = " 0123456789abcdefghijklmnopqrstuv" ;
110
+
111
+ InternalErrorId makeInternalErrorId () {
112
+ InternalErrorId id;
113
+ if (isPredictableModeForTest ()) {
114
+ // In testing mode, use content that generates a "0123456789abcdefghijklm" ID:
115
+ for (auto i: kj::indices (id)) {
116
+ id[i] = i;
117
+ }
118
+ } else {
119
+ KJ_ASSERT (RAND_bytes (id.asPtr ().asBytes ().begin (), id.size ()) == 1 );
120
+ }
121
+ for (auto i: kj::indices (id)) {
122
+ id[i] = BASE32_DIGITS[static_cast <unsigned char >(id[i]) % 32 ];
123
+ }
124
+ return id;
125
+ }
126
+
127
+ kj::String renderInternalError (InternalErrorId& internalErrorId) {
128
+ // TODO(now): put "internal error" change behind autogate or compatibility flag?
129
+ //
130
+ // It's possible that existing user error handling systems could rely on an exact match with the
131
+ // existing "internal error" string. On the other hand, feature flags need a jsg::Lock to read,
132
+ // which may not be available in all contexts that generate internal errors?
133
+ return kj::str (" internal error; reference = " , internalErrorId);
134
+ }
135
+
136
+ } // namespace
137
+
93
138
v8::Local<v8::Value> makeInternalError (v8::Isolate* isolate, kj::StringPtr internalMessage) {
94
- KJ_LOG (ERROR, internalMessage);
95
- return v8::Exception::Error (v8StrIntern (isolate, " internal error" ));
139
+ auto wdErrId = makeInternalErrorId ();
140
+ KJ_LOG (ERROR, internalMessage, wdErrId);
141
+ return v8::Exception::Error (v8StrIntern (isolate, renderInternalError (wdErrId)));
96
142
}
97
143
98
144
namespace {
@@ -143,6 +189,8 @@ struct DecodedException {
143
189
bool isInternal;
144
190
bool isFromRemote;
145
191
bool isDurableObjectReset;
192
+ // TODO(cleanup): Maybe<> is redundant with isInternal flag field?
193
+ kj::Maybe<InternalErrorId> internalErrorId;
146
194
};
147
195
148
196
DecodedException decodeTunneledException (
@@ -167,15 +215,17 @@ DecodedException decodeTunneledException(
167
215
// TODO(someday): Support arbitrary user-defined error types, not just Error?
168
216
auto tunneledInfo = tunneledErrorType (internalMessage);
169
217
218
+ DecodedException result;
170
219
auto errorType = tunneledInfo.message ;
171
- auto appMessage = [&](kj::StringPtr errorString) -> kj::StringPtr {
220
+ auto appMessage = [&](kj::StringPtr errorString) -> kj::ConstString {
172
221
if (tunneledInfo.isInternal ) {
173
- return " internal error" _kj;
222
+ result.internalErrorId = makeInternalErrorId ();
223
+ return kj::ConstString (renderInternalError (KJ_ASSERT_NONNULL (result.internalErrorId )));
174
224
} else {
175
- return trimErrorMessage (errorString);
225
+ // .attach() to convert StringPtr to ConstString:
226
+ return trimErrorMessage (errorString).attach ();
176
227
}
177
228
};
178
- DecodedException result;
179
229
result.isInternal = tunneledInfo.isInternal ;
180
230
result.isFromRemote = tunneledInfo.isFromRemote ;
181
231
result.isDurableObjectReset = tunneledInfo.isDurableObjectReset ;
@@ -213,7 +263,9 @@ DecodedException decodeTunneledException(
213
263
}
214
264
}
215
265
// unrecognized exception type
216
- result.handle = v8::Exception::Error (v8StrIntern (isolate, " internal error" ));
266
+ result.internalErrorId = makeInternalErrorId ();
267
+ result.handle = v8::Exception::Error (
268
+ v8Str (isolate, renderInternalError (KJ_ASSERT_NONNULL (result.internalErrorId ))));
217
269
result.isInternal = true ;
218
270
} while (false );
219
271
#undef HANDLE_V8_ERROR
@@ -240,6 +292,7 @@ DecodedException decodeTunneledException(
240
292
kj::StringPtr extractTunneledExceptionDescription (kj::StringPtr message) {
241
293
auto tunneledError = tunneledErrorType (message);
242
294
if (tunneledError.isInternal ) {
295
+ // TODO(soon): Include an internal error ID in message, and also return the id.
243
296
return " Error: internal error" ;
244
297
} else {
245
298
return tunneledError.message ;
@@ -271,7 +324,11 @@ v8::Local<v8::Value> makeInternalError(v8::Isolate* isolate, kj::Exception&& exc
271
324
// DISCONNECTED exceptions as these are unlikely to represent bugs worth tracking.
272
325
if (exception .getType () != kj::Exception::Type::DISCONNECTED &&
273
326
!isDoNotLogException (exception .getDescription ())) {
274
- LOG_EXCEPTION (" jsgInternalError" , exception );
327
+ // LOG_EXCEPTION("jsgInternalError", ...), but with internal error ID:
328
+ auto & e = exception ;
329
+ constexpr auto sentryErrorContext = " jsgInternalError" ;
330
+ auto & wdErrId = KJ_ASSERT_NONNULL (tunneledException.internalErrorId );
331
+ KJ_LOG (ERROR, e, sentryErrorContext, wdErrId);
275
332
} else {
276
333
KJ_LOG (INFO, exception ); // Run with --verbose to see exception logs.
277
334
}
0 commit comments