As shown here using docker extension API's you can stream the output of a container but when I try to store the data.stdout string for each line of log it simply keep changing like if it's a object reference....I even tried to copy the string using data.stdout.slice()
or transforming it in JSON using JSON.stringify(data.stdout)
in order to get a new object with different reference but doesn't work :/
...
const[logs,setLogs]=useState<string[]>([]);
...
ddClient.docker.cli.exec('logs', ['-f', data.Id], {
stream: {
onOutput(data): void {
console.log(data.stdout);
setLogs([...logs, data.stdout]);
},
onError(error: unknown): void {
ddClient.desktopUI.toast.error('An error occurred');
console.log(error);
},
onClose(exitCode) {
console.log("onClose with exit code " + exitCode);
},
splitOutputLines: true,
},
});
Docker extension team member here. I am not a React expert, so I might be wrong in my explanation, but I think that the issue is linked to how React works.
In your component, the call ddClient.docker.cli.exec('logs', ['-f'], ...)
updates the value of the logs
state every time some data are streamed from the backend. This update makes the component to re-render and execute everything once again. Thus, you will have many calls to ddClient.docker.cli.exec
executed. And it will grow exponentially.
The problem is that, since there are many ddClient.docker.cli.exec
calls, there are as many calls to setLogs
that update the logs
state simultaneously. But spreading the logs
value does not guarantee the value is the last set.
You can try the following to move out the Docker Extensions SDK of the picture. It does exactly the same thing: it updates the content
state inside the setTimeout
callback every 400ms.
Since the setTimeout
is ran on every render and never cleared, there will be many updates of content
simultaneously.
let counter = 0;
export function App() {
const [content, setContent] = React.useState<string[]>([]);
setTimeout(() => {
counter++;
setContent([...content, "\n " + counter]);
}, 400);
return (<div>{content}</div>);
}
You will see weird results :)
Here is how to do what you want to do:
const ddClient = createDockerDesktopClient();
export function App() {
const [logs, setLogs] = React.useState<string[]>([]);
useEffect(() => {
const listener = ddClient.docker.cli.exec('logs', ['-f', "xxxx"], {
stream: {
onOutput(data): void {
console.log(data.stdout);
setLogs((current) => [...current, data.stdout]);
},
onError(error: unknown): void {
ddClient.desktopUI.toast.error('An error occurred');
console.log(error);
},
onClose(exitCode) {
console.log("onClose with exit code " + exitCode);
},
splitOutputLines: true,
},
});
return () => {
listener.close();
}
}, []);
return (
<>
<h1>Logs</h1>
<div>{logs}</div>
</>
);
}
What happens here is
ddClient.docker.cli.exec
is done in an useEffect
logs
a callback is provided to the setLogs
function to make sure the latest value contained in data.stdout
is added to the freshest version of the stateTo try it, make sure you update the array of parameters of the logs command.