GeezerVison is showing github.com — cli: implement --trace-env and --trace-env-[js|native]-stack · nodejs/node@a933103
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/cli.md +42Lines changed: 42 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -2604,6 +2604,45 @@ added: v0.8.0 | 2604 | 2604 |
| 2605 | 2605 | Print stack traces for deprecations. | 2606 | 2606 |
| | 2607 | +### `--trace-env` | | 2608 | + | | 2609 | +<!-- YAML | | 2610 | +added: REPLACEME | | 2611 | +--> | | 2612 | + | | 2613 | +Print information about any access to environment variables done in the current Node.js | | 2614 | +instance to stderr, including: | | 2615 | + | | 2616 | +* The environment variable reads that Node.js does internally. | | 2617 | +* Writes in the form of `process.env.KEY = "SOME VALUE"`. | | 2618 | +* Reads in the form of `process.env.KEY`. | | 2619 | +* Definitions in the form of `Object.defineProperty(process.env, 'KEY', {...})`. | | 2620 | +* Queries in the form of `Object.hasOwn(process.env, 'KEY')`, | | 2621 | + `process.env.hasOwnProperty('KEY')` or `'KEY' in process.env`. | | 2622 | +* Deletions in the form of `delete process.env.KEY`. | | 2623 | +* Enumerations inf the form of `...process.env` or `Object.keys(process.env)`. | | 2624 | + | | 2625 | +Only the names of the environment variables being accessed are printed. The values are not printed. | | 2626 | + | | 2627 | +To print the stack trace of the access, use `--trace-env-js-stack` and/or | | 2628 | +`--trace-env-native-stack`. | | 2629 | + | | 2630 | +### `--trace-env-js-stack` | | 2631 | + | | 2632 | +<!-- YAML | | 2633 | +added: REPLACEME | | 2634 | +--> | | 2635 | + | | 2636 | +In addition to what `--trace-env` does, this prints the JavaScript stack trace of the access. | | 2637 | + | | 2638 | +### `--trace-env-native-stack` | | 2639 | + | | 2640 | +<!-- YAML | | 2641 | +added: REPLACEME | | 2642 | +--> | | 2643 | + | | 2644 | +In addition to what `--trace-env` does, this prints the native stack trace of the access. | | 2645 | + | 2607 | 2646 | ### `--trace-event-categories` | 2608 | 2647 |
| 2609 | 2648 | <!-- YAML | @@ -3150,6 +3189,9 @@ one is included in the list below. | 3150 | 3189 | * `--tls-min-v1.3` | 3151 | 3190 | * `--trace-atomics-wait` | 3152 | 3191 | * `--trace-deprecation` | | 3192 | +* `--trace-env-js-stack` | | 3193 | +* `--trace-env-native-stack` | | 3194 | +* `--trace-env` | 3153 | 3195 | * `--trace-event-categories` | 3154 | 3196 | * `--trace-event-file-pattern` | 3155 | 3197 | * `--trace-events-enabled` | |
Collapse file Copy file name to clipboardExpand all lines: src/debug_utils.cc +2-2Lines changed: 2 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -62,9 +62,9 @@ EnabledDebugList enabled_debug_list; | 62 | 62 | using v8::Local; | 63 | 63 | using v8::StackTrace; | 64 | 64 |
| 65 | | -void EnabledDebugList::Parse(std::shared_ptr<KVStore> env_vars) { | | 65 | +void EnabledDebugList::Parse(Environment* env) { | 66 | 66 | std::string cats; | 67 | | - credentials::SafeGetenv("NODE_DEBUG_NATIVE", &cats, env_vars); | | 67 | + credentials::SafeGetenv("NODE_DEBUG_NATIVE", &cats, env); | 68 | 68 | Parse(cats); | 69 | 69 | } | 70 | 70 |
| |
Collapse file Copy file name to clipboardExpand all lines: src/debug_utils.h +2-2Lines changed: 2 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -74,10 +74,10 @@ class NODE_EXTERN_PRIVATE EnabledDebugList { | 74 | 74 | return enabled_[static_cast<unsigned int>(category)]; | 75 | 75 | } | 76 | 76 |
| 77 | | - // Uses NODE_DEBUG_NATIVE to initialize the categories. The env_vars variable | | 77 | + // Uses NODE_DEBUG_NATIVE to initialize the categories. env->env_vars() | 78 | 78 | // is parsed if it is not a nullptr, otherwise the system environment | 79 | 79 | // variables are parsed. | 80 | | - void Parse(std::shared_ptr<KVStore> env_vars); | | 80 | + void Parse(Environment* env); | 81 | 81 |
| 82 | 82 | private: | 83 | 83 | // Enable all categories matching cats. | |
Collapse file Copy file name to clipboardExpand all lines: src/env.cc +9-6Lines changed: 9 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -875,9 +875,6 @@ Environment::Environment(IsolateData* isolate_data, | 875 | 875 | EnvironmentFlags::kOwnsInspector; | 876 | 876 | } | 877 | 877 |
| 878 | | - set_env_vars(per_process::system_environment); | 879 | | - enabled_debug_list_.Parse(env_vars()); | 880 | | - | 881 | 878 | // We create new copies of the per-Environment option sets, so that it is | 882 | 879 | // easier to modify them after Environment creation. The defaults are | 883 | 880 | // part of the per-Isolate option set, for which in turn the defaults are | @@ -887,6 +884,13 @@ Environment::Environment(IsolateData* isolate_data, | 887 | 884 | inspector_host_port_ = std::make_shared<ExclusiveAccess<HostPort>>( | 888 | 885 | options_->debug_options().host_port); | 889 | 886 |
| | 887 | + set_env_vars(per_process::system_environment); | | 888 | + // This should be done after options is created, so that --trace-env can be | | 889 | + // checked when parsing NODE_DEBUG_NATIVE. It should also be done after | | 890 | + // env_vars() is set so that the parser uses values from env->env_vars() | | 891 | + // which may or may not be the system environment variable store. | | 892 | + enabled_debug_list_.Parse(this); | | 893 | + | 890 | 894 | heap_snapshot_near_heap_limit_ = | 891 | 895 | static_cast<uint32_t>(options_->heap_snapshot_near_heap_limit); | 892 | 896 |
| @@ -1115,8 +1119,7 @@ void Environment::InitializeLibuv() { | 1115 | 1119 |
| 1116 | 1120 | void Environment::InitializeCompileCache() { | 1117 | 1121 | std::string dir_from_env; | 1118 | | - if (!credentials::SafeGetenv( | 1119 | | - "NODE_COMPILE_CACHE", &dir_from_env, env_vars()) || | | 1122 | + if (!credentials::SafeGetenv("NODE_COMPILE_CACHE", &dir_from_env, this) || | 1120 | 1123 | dir_from_env.empty()) { | 1121 | 1124 | return; | 1122 | 1125 | } | @@ -1128,7 +1131,7 @@ CompileCacheEnableResult Environment::EnableCompileCache( | 1128 | 1131 | CompileCacheEnableResult result; | 1129 | 1132 | std::string disable_env; | 1130 | 1133 | if (credentials::SafeGetenv( | 1131 | | - "NODE_DISABLE_COMPILE_CACHE", &disable_env, env_vars())) { | | 1134 | + "NODE_DISABLE_COMPILE_CACHE", &disable_env, this)) { | 1132 | 1135 | result.status = CompileCacheEnableStatus::DISABLED; | 1133 | 1136 | result.message = "Disabled by NODE_DISABLE_COMPILE_CACHE"; | 1134 | 1137 | Debug(this, | |
Collapse file Copy file name to clipboardExpand all lines: src/node.cc +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -1045,7 +1045,7 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args, | 1045 | 1045 | if (!(flags & ProcessInitializationFlags::kNoParseGlobalDebugVariables)) { | 1046 | 1046 | // Initialized the enabled list for Debug() calls with system | 1047 | 1047 | // environment variables. | 1048 | | - per_process::enabled_debug_list.Parse(per_process::system_environment); | | 1048 | + per_process::enabled_debug_list.Parse(nullptr); | 1049 | 1049 | } | 1050 | 1050 |
| 1051 | 1051 | PlatformInit(flags); | |
Collapse file Copy file name to clipboardExpand all lines: src/node_credentials.cc +24-9Lines changed: 24 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -72,9 +72,7 @@ static bool HasOnly(int capability) { | 72 | 72 | // process only has the capability CAP_NET_BIND_SERVICE set. If the current | 73 | 73 | // process does not have any capabilities set and the process is running as | 74 | 74 | // setuid root then lookup will not be allowed. | 75 | | -bool SafeGetenv(const char* key, | 76 | | - std::string* text, | 77 | | - std::shared_ptr<KVStore> env_vars) { | | 75 | +bool SafeGetenv(const char* key, std::string* text, Environment* env) { | 78 | 76 | #if !defined(__CloudABI__) && !defined(_WIN32) | 79 | 77 | #if defined(__linux__) | 80 | 78 | if ((!HasOnly(CAP_NET_BIND_SERVICE) && linux_at_secure()) || | @@ -87,14 +85,31 @@ bool SafeGetenv(const char* key, | 87 | 85 |
| 88 | 86 | // Fallback to system environment which reads the real environment variable | 89 | 87 | // through uv_os_getenv. | 90 | | - if (env_vars == nullptr) { | | 88 | + std::shared_ptr<KVStore> env_vars; | | 89 | + if (env == nullptr) { | 91 | 90 | env_vars = per_process::system_environment; | | 91 | + } else { | | 92 | + env_vars = env->env_vars(); | 92 | 93 | } | 93 | 94 |
| 94 | 95 | std::optional<std::string> value = env_vars->Get(key); | 95 | | - if (!value.has_value()) return false; | 96 | | - *text = value.value(); | 97 | | - return true; | | 96 | + | | 97 | + bool has_env = value.has_value(); | | 98 | + if (has_env) { | | 99 | + *text = value.value(); | | 100 | + } | | 101 | + | | 102 | + auto options = | | 103 | + (env != nullptr ? env->options() | | 104 | + : per_process::cli_options->per_isolate->per_env); | | 105 | + | | 106 | + if (options->trace_env) { | | 107 | + fprintf(stderr, "[--trace-env] get environment variable \"%s\"\n", key); | | 108 | + | | 109 | + PrintTraceEnvStack(options); | | 110 | + } | | 111 | + | | 112 | + return has_env; | 98 | 113 | } | 99 | 114 |
| 100 | 115 | static void SafeGetenv(const FunctionCallbackInfo<Value>& args) { | @@ -103,7 +118,7 @@ static void SafeGetenv(const FunctionCallbackInfo<Value>& args) { | 103 | 118 | Isolate* isolate = env->isolate(); | 104 | 119 | Utf8Value strenvtag(isolate, args[0]); | 105 | 120 | std::string text; | 106 | | - if (!SafeGetenv(*strenvtag, &text, env->env_vars())) return; | | 121 | + if (!SafeGetenv(*strenvtag, &text, env)) return; | 107 | 122 | Local<Value> result = | 108 | 123 | ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked(); | 109 | 124 | args.GetReturnValue().Set(result); | @@ -117,7 +132,7 @@ static void GetTempDir(const FunctionCallbackInfo<Value>& args) { | 117 | 132 |
| 118 | 133 | // Let's wrap SafeGetEnv since it returns true for empty string. | 119 | 134 | auto get_env = [&dir, &env](std::string_view key) { | 120 | | - USE(SafeGetenv(key.data(), &dir, env->env_vars())); | | 135 | + USE(SafeGetenv(key.data(), &dir, env)); | 121 | 136 | return !dir.empty(); | 122 | 137 | }; | 123 | 138 |
| |
Collapse file Copy file name to clipboardExpand all lines: src/node_env_var.cc +60-2Lines changed: 60 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -337,6 +337,19 @@ Maybe<void> KVStore::AssignToObject(v8::Isolate* isolate, | 337 | 337 | return JustVoid(); | 338 | 338 | } | 339 | 339 |
| | 340 | +void PrintTraceEnvStack(Environment* env) { | | 341 | + PrintTraceEnvStack(env->options()); | | 342 | +} | | 343 | + | | 344 | +void PrintTraceEnvStack(std::shared_ptr<EnvironmentOptions> options) { | | 345 | + if (options->trace_env_native_stack) { | | 346 | + DumpNativeBacktrace(stderr); | | 347 | + } | | 348 | + if (options->trace_env_js_stack) { | | 349 | + DumpJavaScriptBacktrace(stderr); | | 350 | + } | | 351 | +} | | 352 | + | 340 | 353 | static Intercepted EnvGetter(Local<Name> property, | 341 | 354 | const PropertyCallbackInfo<Value>& info) { | 342 | 355 | Environment* env = Environment::GetCurrent(info); | @@ -348,7 +361,18 @@ static Intercepted EnvGetter(Local<Name> property, | 348 | 361 | CHECK(property->IsString()); | 349 | 362 | MaybeLocal<String> value_string = | 350 | 363 | env->env_vars()->Get(env->isolate(), property.As<String>()); | 351 | | - if (!value_string.IsEmpty()) { | | 364 | + | | 365 | + bool has_env = !value_string.IsEmpty(); | | 366 | + if (env->options()->trace_env) { | | 367 | + Utf8Value key(env->isolate(), property.As<String>()); | | 368 | + fprintf(stderr, | | 369 | + "[--trace-env] get environment variable \"%.*s\"\n", | | 370 | + static_cast<int>(key.length()), | | 371 | + key.out()); | | 372 | + PrintTraceEnvStack(env); | | 373 | + } | | 374 | + | | 375 | + if (has_env) { | 352 | 376 | info.GetReturnValue().Set(value_string.ToLocalChecked()); | 353 | 377 | return Intercepted::kYes; | 354 | 378 | } | @@ -386,6 +410,14 @@ static Intercepted EnvSetter(Local<Name> property, | 386 | 410 | } | 387 | 411 |
| 388 | 412 | env->env_vars()->Set(env->isolate(), key, value_string); | | 413 | + if (env->options()->trace_env) { | | 414 | + Utf8Value key_utf8(env->isolate(), key); | | 415 | + fprintf(stderr, | | 416 | + "[--trace-env] set environment variable \"%.*s\"\n", | | 417 | + static_cast<int>(key_utf8.length()), | | 418 | + key_utf8.out()); | | 419 | + PrintTraceEnvStack(env); | | 420 | + } | 389 | 421 |
| 390 | 422 | return Intercepted::kYes; | 391 | 423 | } | @@ -396,7 +428,18 @@ static Intercepted EnvQuery(Local<Name> property, | 396 | 428 | CHECK(env->has_run_bootstrapping_code()); | 397 | 429 | if (property->IsString()) { | 398 | 430 | int32_t rc = env->env_vars()->Query(env->isolate(), property.As<String>()); | 399 | | - if (rc != -1) { | | 431 | + bool has_env = (rc != -1); | | 432 | + | | 433 | + if (env->options()->trace_env) { | | 434 | + Utf8Value key_utf8(env->isolate(), property.As<String>()); | | 435 | + fprintf(stderr, | | 436 | + "[--trace-env] query environment variable \"%.*s\": %s\n", | | 437 | + static_cast<int>(key_utf8.length()), | | 438 | + key_utf8.out(), | | 439 | + has_env ? "is set" : "is not set"); | | 440 | + PrintTraceEnvStack(env); | | 441 | + } | | 442 | + if (has_env) { | 400 | 443 | // Return attributes for the property. | 401 | 444 | info.GetReturnValue().Set(v8::None); | 402 | 445 | return Intercepted::kYes; | @@ -411,6 +454,15 @@ static Intercepted EnvDeleter(Local<Name> property, | 411 | 454 | CHECK(env->has_run_bootstrapping_code()); | 412 | 455 | if (property->IsString()) { | 413 | 456 | env->env_vars()->Delete(env->isolate(), property.As<String>()); | | 457 | + | | 458 | + if (env->options()->trace_env) { | | 459 | + Utf8Value key_utf8(env->isolate(), property.As<String>()); | | 460 | + fprintf(stderr, | | 461 | + "[--trace-env] delete environment variable \"%.*s\"\n", | | 462 | + static_cast<int>(key_utf8.length()), | | 463 | + key_utf8.out()); | | 464 | + PrintTraceEnvStack(env); | | 465 | + } | 414 | 466 | } | 415 | 467 |
| 416 | 468 | // process.env never has non-configurable properties, so always | @@ -423,6 +475,12 @@ static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) { | 423 | 475 | Environment* env = Environment::GetCurrent(info); | 424 | 476 | CHECK(env->has_run_bootstrapping_code()); | 425 | 477 |
| | 478 | + if (env->options()->trace_env) { | | 479 | + fprintf(stderr, "[--trace-env] enumerate environment variables\n"); | | 480 | + | | 481 | + PrintTraceEnvStack(env); | | 482 | + } | | 483 | + | 426 | 484 | info.GetReturnValue().Set( | 427 | 485 | env->env_vars()->Enumerate(env->isolate())); | 428 | 486 | } | |
Collapse file Copy file name to clipboardExpand all lines: src/node_internals.h +4-3Lines changed: 4 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -321,11 +321,12 @@ class ThreadPoolWork { | 321 | 321 | #endif // defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) | 322 | 322 |
| 323 | 323 | namespace credentials { | 324 | | -bool SafeGetenv(const char* key, | 325 | | - std::string* text, | 326 | | - std::shared_ptr<KVStore> env_vars = nullptr); | | 324 | +bool SafeGetenv(const char* key, std::string* text, Environment* env = nullptr); | 327 | 325 | } // namespace credentials | 328 | 326 |
| | 327 | +void PrintTraceEnvStack(Environment* env); | | 328 | +void PrintTraceEnvStack(std::shared_ptr<EnvironmentOptions> options); | | 329 | + | 329 | 330 | void DefineZlibConstants(v8::Local<v8::Object> target); | 330 | 331 | v8::Isolate* NewIsolate(v8::Isolate::CreateParams* params, | 331 | 332 | uv_loop_t* event_loop, | |
Collapse file Copy file name to clipboardExpand all lines: src/node_options.cc +18Lines changed: 18 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -775,6 +775,24 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | 775 | 775 | "show stack traces on promise initialization and resolution", | 776 | 776 | &EnvironmentOptions::trace_promises, | 777 | 777 | kAllowedInEnvvar); | | 778 | + | | 779 | + AddOption("--trace-env", | | 780 | + "Print accesses to the environment variables", | | 781 | + &EnvironmentOptions::trace_env, | | 782 | + kAllowedInEnvvar); | | 783 | + Implies("--trace-env-js-stack", "--trace-env"); | | 784 | + Implies("--trace-env-native-stack", "--trace-env"); | | 785 | + AddOption("--trace-env-js-stack", | | 786 | + "Print accesses to the environment variables and the JavaScript " | | 787 | + "stack trace", | | 788 | + &EnvironmentOptions::trace_env_js_stack, | | 789 | + kAllowedInEnvvar); | | 790 | + AddOption( | | 791 | + "--trace-env-native-stack", | | 792 | + "Print accesses to the environment variables and the native stack trace", | | 793 | + &EnvironmentOptions::trace_env_native_stack, | | 794 | + kAllowedInEnvvar); | | 795 | + | 778 | 796 | AddOption("--experimental-default-type", | 779 | 797 | "set module system to use by default", | 780 | 798 | &EnvironmentOptions::type, | |
Collapse file Copy file name to clipboardExpand all lines: src/node_options.h +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -210,6 +210,9 @@ class EnvironmentOptions : public Options { | 210 | 210 | bool trace_uncaught = false; | 211 | 211 | bool trace_warnings = false; | 212 | 212 | bool trace_promises = false; | | 213 | + bool trace_env = false; | | 214 | + bool trace_env_js_stack = false; | | 215 | + bool trace_env_native_stack = false; | 213 | 216 | bool extra_info_on_fatal_exception = true; | 214 | 217 | std::string unhandled_rejections; | 215 | 218 | std::vector<std::string> userland_loaders; | |
|