JeringTech / Javascript.NodeJS

Invoke Javascript in NodeJS, from C#
Other
463 stars 43 forks source link

Obtaining console.log output from executed script #162

Open dannyhaak opened 1 year ago

dannyhaak commented 1 year ago

We're using NodeJs in our backend to run custom scripts created by clients on our PaaS. We currently directly use NodeJs via a platform invoke, but the approach from Jering seems much nicer. The only thing I cannot find a solution for is how I can get (in realtime) the logging output from the console.logs in the script. I know I can insert a custom class derived from ILogger, but here I cannot distinguish between script output and Jering output (like "Connected to NodeJS process: 46605.").

Is there any other way of doing this? It would be cool if we could add a custom output handler to the NodeJs executable to obtain those loggings.

soroshsabz commented 1 year ago

maybe related to #163

dannyhaak commented 1 year ago

Yes, it is.

When looking at the code, it seems that also the status of the Node.JS process is monitored by the console.log output - is that correct? That would make it difficult to purely get the code output. Or, at least a lot of refactoring is required I guess. Happy to help, but it is not an easy job it seems.

JeremyTCD commented 1 year ago

Hey apologies for the slow response. I've written instructions for logging in general here. If you're looking to output only logs from your Node.js logic, a custom .Net logging provider will do the job:

using Jering.Javascript.NodeJS;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Example.Logging
{
    internal class Program
    {
        static async Task Main(string[] _)
        {
            // Prepare DI services
            var serviceCollection = new ServiceCollection();
            serviceCollection.AddNodeJS();
            serviceCollection.AddLogging((ILoggingBuilder loggingBuilder) =>
            {
                loggingBuilder.AddProvider(new PrefixFilteringConsoleLoggerProvider("[MyNodeApp]"));
            });

            // Get INodeJSService
            IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
            INodeJSService nodeJSService = serviceProvider.GetRequiredService<INodeJSService>();

            // Invoke js
            await nodeJSService.InvokeFromStringAsync(@"module.exports = (callback) => { 
    console.log('[MyNodeApp] Hello world!'); 
    callback(null); 
}").ConfigureAwait(false);
        }
    }

    // Simplified example, should not be used in production
    public class PrefixFilteringConsoleLoggerProvider : ILoggerProvider
    {
        private readonly string _prefix;

        public PrefixFilteringConsoleLoggerProvider(string prefix)
        {
            _prefix = prefix;
        }

        public ILogger CreateLogger(string categoryName)
        {
            return new PrefixFilteringConsoleLogger(_prefix);
        }

        public void Dispose()
        {
        }
    }

    // Simplified example, should not be used in production
    public class PrefixFilteringConsoleLogger : ILogger
    {
        private readonly string _prefix;

        public PrefixFilteringConsoleLogger(string prefix)
        {
            _prefix = prefix;
        }

        public IDisposable? BeginScope<TState>(TState state) where TState : notnull
        {
            return null;
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return true;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
        {
            if (state is IReadOnlyList<KeyValuePair<string, object>> stateAsList &&
                stateAsList[0].Value is string logMessageString && 
                logMessageString.StartsWith(_prefix))
            {
                Console.WriteLine(logMessageString);
            }
        }
    }
}

Outputs:

[MyNodeApp] Hello world!

Instead of:

info: Jering.Javascript.NodeJS.HttpNodeJSService[0]
      Connected to NodeJS through HTTP/1.1. Endpoint: http://[::1]:62220/.
info: Jering.Javascript.NodeJS.HttpNodeJSService[0]
      Connected to NodeJS process: 11960.
info: System.Net.Http.HttpClient.IHttpClientService.LogicalHandler[100]
      Start processing HTTP request POST http://[::1]:62220/
info: System.Net.Http.HttpClient.IHttpClientService.ClientHandler[100]
      Sending HTTP request POST http://[::1]:62220/
info: Jering.Javascript.NodeJS.HttpNodeJSService[0]
      [MyNodeApp] Hello world!
info: System.Net.Http.HttpClient.IHttpClientService.ClientHandler[101]
      Received HTTP response headers after 101.8191ms - 200
info: System.Net.Http.HttpClient.IHttpClientService.LogicalHandler[101]
      End processing HTTP request after 108.5691ms - 200

Microsoft docs on custom logging providers. Let me know if that is what you were looking for.

DingbatDev commented 1 year ago

console.log() calls are working well for me. Just wondering if it would be feasible to map the different JS log levels to Microsoft.Extensions.Logging levels?

I have some things I would like to see in Debug or Verbose, but don't want to do conditional compilation in the JS side.

JeremyTCD commented 1 year ago

Hi, this library doesn't provide such functionality out-of-the-box, but you can probably achieve what you want using something similar to the logic above. Monkey patch JS log methods to prefix messages > create a custom ILoggerProvider.