diff --git a/lib/console.js b/lib/console.js index 63e3c5e0bf1aa4..07754fa93c824e 100644 --- a/lib/console.js +++ b/lib/console.js @@ -2,6 +2,13 @@ const util = require('util'); +const LOGGER_FUNC_TYPE_LOG = process._logger.LOGGER_FUNC_TYPE_LOG; +const LOGGER_FUNC_TYPE_INFO = process._logger.LOGGER_FUNC_TYPE_INFO; +const LOGGER_FUNC_TYPE_WARN = process._logger.LOGGER_FUNC_TYPE_WARN; +const LOGGER_FUNC_TYPE_ERROR = process._logger.LOGGER_FUNC_TYPE_ERROR; +const LOGGER_FUNC_TYPE_DIR = process._logger.LOGGER_FUNC_TYPE_DIR; + + function Console(stdout, stderr) { if (!(this instanceof Console)) { return new Console(stdout, stderr); @@ -23,6 +30,27 @@ function Console(stdout, stderr) { Object.defineProperty(this, '_stderr', prop); prop.value = new Map(); Object.defineProperty(this, '_times', prop); + prop.value = function(type, message) { + message += '\n'; + if (process._logger(type, message)) { + // Log message is processed by a custom logger function + return; + } + // Default logger + switch (type) { + case LOGGER_FUNC_TYPE_LOG: + case LOGGER_FUNC_TYPE_INFO: + case LOGGER_FUNC_TYPE_DIR: + this._stdout.write(message); + break; + + case LOGGER_FUNC_TYPE_WARN: + case LOGGER_FUNC_TYPE_ERROR: + this._stderr.write(message); + break; + } + }; + Object.defineProperty(this, '_logger', prop); // bind the prototype functions to this Console instance var keys = Object.keys(Console.prototype); @@ -32,26 +60,31 @@ function Console(stdout, stderr) { } } + Console.prototype.log = function() { - this._stdout.write(util.format.apply(this, arguments) + '\n'); + this._logger(LOGGER_FUNC_TYPE_LOG, util.format.apply(this, arguments)); }; -Console.prototype.info = Console.prototype.log; +Console.prototype.info = function() { + this._logger(LOGGER_FUNC_TYPE_INFO, util.format.apply(this, arguments)); +}; Console.prototype.warn = function() { - this._stderr.write(util.format.apply(this, arguments) + '\n'); + this._logger(LOGGER_FUNC_TYPE_WARN, util.format.apply(this, arguments)); }; -Console.prototype.error = Console.prototype.warn; +Console.prototype.error = function() { + this._logger(LOGGER_FUNC_TYPE_ERROR, util.format.apply(this, arguments)); +}; Console.prototype.dir = function(object, options) { - this._stdout.write(util.inspect(object, util._extend({ + this._logger(LOGGER_FUNC_TYPE_DIR, util.inspect(object, util._extend({ customInspect: false - }, options)) + '\n'); + }, options))); }; diff --git a/src/node.cc b/src/node.cc index 67cf140154866f..866c2c8a80d376 100644 --- a/src/node.cc +++ b/src/node.cc @@ -143,6 +143,8 @@ static uv_async_t dispatch_debug_messages_async; static Isolate* node_isolate = nullptr; static v8::Platform* default_platform; +static logger_func custom_logger = nullptr; + class ArrayBufferAllocator : public ArrayBuffer::Allocator { public: // Impose an upper limit to avoid out of memory errors that bring down @@ -920,6 +922,50 @@ Local WinapiErrnoException(Isolate* isolate, #endif +#define LOG_STDERR(...) \ + if (custom_logger == NULL || \ + !custom_logger(LOGGER_FUNC_TYPE_STDERR, __VA_ARGS__)) \ + { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr); \ + } + + +#define LOG_STDOUT(...) \ + if (custom_logger == NULL || \ + !custom_logger(LOGGER_FUNC_TYPE_STDOUT, __VA_ARGS__)) \ + { \ + fprintf(stdout, __VA_ARGS__); \ + fflush(stdout); \ + } + + +logger_func SetLogger(logger_func func) { + logger_func prev = custom_logger; + custom_logger = func; + return prev; +} + + +void LoggerCallback(const FunctionCallbackInfo& args) { + if (custom_logger) { + Environment* env = Environment::GetCurrent(args); + + HandleScope scope(env->isolate()); + + CHECK(args[0]->IsNumber()); + CHECK(args[1]->IsString()); + + logger_func_type func_type = + static_cast(args[0]->IntegerValue()); + node::Utf8Value message(env->isolate(), args[1].As()); + + if (custom_logger(func_type, *message)) + args.GetReturnValue().Set(True(env->isolate())); + } +} + + void SetupDomainUse(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -935,7 +981,7 @@ void SetupDomainUse(const FunctionCallbackInfo& args) { process_object->Get(tick_callback_function_key).As(); if (!tick_callback_function->IsFunction()) { - fprintf(stderr, "process._tickDomainCallback assigned to non-function\n"); + LOG_STDERR("process._tickDomainCallback assigned to non-function\n"); abort(); } @@ -1264,14 +1310,14 @@ enum encoding ParseEncoding(const char* encoding, return HEX; } else if (strcasecmp(encoding, "raw") == 0) { if (!no_deprecation) { - fprintf(stderr, "'raw' (array of integers) has been removed. " - "Use 'binary'.\n"); + LOG_STDERR("'raw' (array of integers) has been removed. " + "Use 'binary'.\n"); } return BINARY; } else if (strcasecmp(encoding, "raws") == 0) { if (!no_deprecation) { - fprintf(stderr, "'raws' encoding has been renamed to 'binary'. " - "Please update your code.\n"); + LOG_STDERR("'raws' encoding has been renamed to 'binary'. " + "Please update your code.\n"); } return BINARY; } else { @@ -1310,8 +1356,8 @@ ssize_t DecodeBytes(Isolate* isolate, HandleScope scope(isolate); if (val->IsArray()) { - fprintf(stderr, "'raw' encoding (array of integers) has been removed. " - "Use 'binary'.\n"); + LOG_STDERR("'raw' encoding (array of integers) has been removed. " + "Use 'binary'.\n"); UNREACHABLE(); return -1; } @@ -1433,7 +1479,7 @@ void AppendExceptionLine(Environment* env, return; env->set_printed_error(true); uv_tty_reset_mode(); - fprintf(stderr, "\n%s", arrow); + LOG_STDERR("\n%s", arrow); } @@ -1455,7 +1501,7 @@ static void ReportException(Environment* env, // range errors have a trace member set to undefined if (trace.length() > 0 && !trace_value->IsUndefined()) { - fprintf(stderr, "%s\n", *trace); + LOG_STDERR("%s\n", *trace); } else { // this really only happens for RangeErrors, since they're the only // kind that won't have all this info in the trace, or when non-Error @@ -1475,15 +1521,13 @@ static void ReportException(Environment* env, name->IsUndefined()) { // Not an error object. Just print as-is. node::Utf8Value message(env->isolate(), er); - fprintf(stderr, "%s\n", *message); + LOG_STDERR("%s\n", *message); } else { node::Utf8Value name_string(env->isolate(), name); node::Utf8Value message_string(env->isolate(), message); - fprintf(stderr, "%s: %s\n", *name_string, *message_string); + LOG_STDERR("%s: %s\n", *name_string, *message_string); } } - - fflush(stderr); } @@ -2115,11 +2159,10 @@ void DLOpen(const FunctionCallbackInfo& args) { static void OnFatalError(const char* location, const char* message) { if (location) { - fprintf(stderr, "FATAL ERROR: %s %s\n", location, message); + LOG_STDERR("FATAL ERROR: %s %s\n", location, message); } else { - fprintf(stderr, "FATAL ERROR: %s\n", message); + LOG_STDERR("FATAL ERROR: %s\n", message); } - fflush(stderr); abort(); } @@ -2851,6 +2894,23 @@ void SetupProcessObject(Environment* env, env->SetMethod(process, "_setupPromises", SetupPromises); env->SetMethod(process, "_setupDomainUse", SetupDomainUse); + // Readonly _logger method + v8::Local logger = + env->NewFunctionTemplate(LoggerCallback)->GetFunction(); + READONLY_PROPERTY(process, "_logger", logger); + logger->SetName(v8::String::NewFromUtf8(env->isolate(), "_logger")); + // Logger function type constants + READONLY_PROPERTY(logger, "LOGGER_FUNC_TYPE_LOG", + Integer::New(env->isolate(), LOGGER_FUNC_TYPE_LOG)); + READONLY_PROPERTY(logger, "LOGGER_FUNC_TYPE_INFO", + Integer::New(env->isolate(), LOGGER_FUNC_TYPE_INFO)); + READONLY_PROPERTY(logger, "LOGGER_FUNC_TYPE_WARN", + Integer::New(env->isolate(), LOGGER_FUNC_TYPE_WARN)); + READONLY_PROPERTY(logger, "LOGGER_FUNC_TYPE_ERROR", + Integer::New(env->isolate(), LOGGER_FUNC_TYPE_ERROR)); + READONLY_PROPERTY(logger, "LOGGER_FUNC_TYPE_DIR", + Integer::New(env->isolate(), LOGGER_FUNC_TYPE_DIR)); + // pre-set _events object for faster emit checks process->Set(env->events_string(), Object::New(env->isolate())); } @@ -2885,8 +2945,7 @@ static void RawDebug(const FunctionCallbackInfo& args) { CHECK(args.Length() == 1 && args[0]->IsString() && "must be called with a single string"); node::Utf8Value message(args.GetIsolate(), args[0]); - fprintf(stderr, "%s\n", *message); - fflush(stderr); + LOG_STDERR("%s\n", *message); } @@ -2982,7 +3041,7 @@ static bool ParseDebugOpt(const char* arg) { if (port != nullptr) { debug_port = atoi(port); if (debug_port < 1024 || debug_port > 65535) { - fprintf(stderr, "Debug port must be in range 1024 to 65535.\n"); + LOG_STDERR("Debug port must be in range 1024 to 65535.\n"); PrintHelp(); exit(12); } @@ -2992,7 +3051,8 @@ static bool ParseDebugOpt(const char* arg) { } static void PrintHelp() { - printf("Usage: iojs [options] [ -e script | script.js ] [arguments] \n" + LOG_STDOUT( + "Usage: iojs [options] [ -e script | script.js ] [arguments] \n" " iojs debug script.js [arguments] \n" "\n" "Options:\n" @@ -3080,7 +3140,7 @@ static void ParseArgs(int* argc, if (ParseDebugOpt(arg)) { // Done, consumed by ParseDebugOpt(). } else if (strcmp(arg, "--version") == 0 || strcmp(arg, "-v") == 0) { - printf("%s\n", NODE_VERSION); + LOG_STDOUT("%s\n", NODE_VERSION); exit(0); } else if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) { PrintHelp(); @@ -3098,7 +3158,7 @@ static void ParseArgs(int* argc, args_consumed += 1; eval_string = argv[index + 1]; if (eval_string == nullptr) { - fprintf(stderr, "%s: %s requires an argument\n", argv[0], arg); + LOG_STDERR("%s: %s requires an argument\n", argv[0], arg); exit(9); } } else if ((index + 1 < nargs) && @@ -3115,7 +3175,7 @@ static void ParseArgs(int* argc, strcmp(arg, "-r") == 0) { const char* module = argv[index + 1]; if (module == nullptr) { - fprintf(stderr, "%s: %s requires an argument\n", argv[0], arg); + LOG_STDERR("%s: %s requires an argument\n", argv[0], arg); exit(9); } args_consumed += 1; @@ -3196,8 +3256,7 @@ static void StartDebug(Environment* env, bool wait) { DispatchMessagesDebugAgentCallback); debugger_running = env->debugger_agent()->Start(debug_port, wait); if (debugger_running == false) { - fprintf(stderr, "Starting debugger on port %d failed\n", debug_port); - fflush(stderr); + LOG_STDERR("Starting debugger on port %d failed\n", debug_port); return; } } @@ -3227,7 +3286,7 @@ static void EnableDebug(Environment* env) { // Called from the main thread. static void DispatchDebugMessagesAsyncCallback(uv_async_t* handle) { if (debugger_running == false) { - fprintf(stderr, "Starting debugger agent.\n"); + LOG_STDERR("Starting debugger agent.\n"); HandleScope scope(node_isolate); Environment* env = Environment::GetCurrent(node_isolate); @@ -3597,7 +3656,7 @@ void Init(int* argc, // Anything that's still in v8_argv is not a V8 or a node option. for (int i = 1; i < v8_argc; i++) { - fprintf(stderr, "%s: bad option: %s\n", argv[0], v8_argv[i]); + LOG_STDERR("%s: bad option: %s\n", argv[0], v8_argv[i]); } delete[] v8_argv; v8_argv = nullptr; diff --git a/src/node.h b/src/node.h index 28b40aa0721b3a..771a9986ff24ab 100644 --- a/src/node.h +++ b/src/node.h @@ -158,6 +158,23 @@ NODE_EXTERN void Init(int* argc, int* exec_argc, const char*** exec_argv); + +enum logger_func_type { + LOGGER_FUNC_TYPE_STDERR = 0, + LOGGER_FUNC_TYPE_STDOUT = 1, + + LOGGER_FUNC_TYPE_LOG = 2, + LOGGER_FUNC_TYPE_INFO = 3, + LOGGER_FUNC_TYPE_WARN = 4, + LOGGER_FUNC_TYPE_ERROR = 5, + LOGGER_FUNC_TYPE_DIR = 6 +}; + +typedef bool (*logger_func)(logger_func_type func_type, const char* fmt, ...); + +NODE_EXTERN logger_func SetLogger(logger_func func); + + class Environment; NODE_EXTERN Environment* CreateEnvironment(v8::Isolate* isolate,