1

What is the best way to stream my docker container log to my Webbrowser without refreshing using express and node-docker-api.

This is my current script.

app.get('/container/:id/log', async function(req, res){
    await checkTokenHeader(req, res);
    //todo: Check if Container exist
    let container = await getDockerContainer(req.params.id);

    await container.logs({
        follow: true,
        stdout: true,
        stderr: true
    }).then(async function(stream){
        res.setHeader('Content-Type', 'text/html; charset=utf-8');
        res.setHeader('Transfer-Encoding', 'chunked');
        console.log(stream)
        //stream.on('data', info => res.write(info.toString('utf-8').slice(8)))
        //stream.on('data', info => console.log(info.toString('utf-8').slice(8)))
        
        //I WANT TO STREAM THE OUTPUT TO Webbrowser or inside a div

    })
});

I tried using res.write or res.send, but nothing works

Zeitounator
  • 38,476
  • 7
  • 53
  • 66
DGINX
  • 44
  • 1
  • 7

1 Answers1

3

One possible way to achieve what you described is:

  1. Construct an HTML document with some client-side JavaScript to get the logs
  2. Establish a WebSocket connection to stream the logs to the client

Here's a very simple implementation:

// save this to "index.mjs" and run with "node ./index.mjs"

import http from "http";

import express from "express"; // npm i express
import {
  Docker
} from "node-docker-api"; // npm i node-docker-api
import {
  WebSocketServer
} from "ws"; // npm i ws

const app = express();
const docker = new Docker({
  socketPath: "/var/run/docker.sock"
});

app.get("/container/:id/log", async function(req, res) {
  res.setHeader("Content-Type", "text/html; charset=utf-8");

  // This is a naïve way of building an HTML document. 
  // You most likely want to to build this from a template
  res.end(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>Container Logs</title>
      </head>
      <body>
        <h1>Container Logs</h1>
        <div id="logs"></div>
        <script>
          const ws = new WebSocket(\`ws://\${window.location.host}/${req.params.id}\`);
          ws.onmessage = (event) => {
            const log = document.createElement("p");
            log.innerText = event.data;
            document.getElementById("logs").appendChild(log);
            window.scrollTo(0, document.body.scrollHeight);
          };
        </script>
      </body>
    </html>
  `);
});

const server = http.createServer(app);
const wss = new WebSocketServer({
  server
});

wss.on("connection", async(ws, req) => {
  const containerId = req.url.split("/")[1];

  // TODO: validate containerId

  ws.on("error", console.error);

  let container = docker.container.get(containerId);
  const stream = await container.logs({
    follow: true,
    stdout: true,
    stderr: true,
  });

  ws.once("close", () => {
    stream.removeAllListeners();
    stream.destroy();
  });

  stream.on("data", (info) => ws.send(info.toString("utf-8")));
});

server.listen(3000, () => {
  console.log("Server running on port 3000");
});

Running the code above with the following docker-compose.yml:

version: "3.8"
services:
  counter:
    image: busybox:latest
    init: true
    command: '/bin/sh -c "while true; do date; sleep 1; done"'

Appends the log lines to the page without refreshing:

enter image description here

fardjad
  • 20,031
  • 6
  • 53
  • 68
  • when I copy your code I get an error `Invalid URL: [object object]` at `const wss = new WebSocketServer({server})` – DGINX Apr 09 '23 at 11:15
  • There is no URL defined on that line. It’s probably happening somewhere else but it’s hard to debug with that little information. – fardjad Apr 09 '23 at 12:41
  • Ok. I fixed by myself. I changed `const wss = new WebSocketServer({server})` to `const wss = new WebSocketServer.WebSocketServer({server})` – DGINX Apr 10 '23 at 07:01
  • In the code I shared, the import statement on line 5 already imports `{ WebSocketServer }` from the module. I suspect you were importing `WebSocketServer` differently. Anyways, if my answer was useful please consider accepting it – fardjad Apr 10 '23 at 22:48