add websocket for log
diff --git a/package.json b/package.json
index 6216b90..3b81071 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
     "pinia": "^2.0.21",
     "pinia-plugin-persistedstate": "^2.1.1",
     "qs": "^6.11.0",
+    "socket.io-client": "^4.5.2",
     "vue": "^3.2.37",
     "vue-i18n": "^9.2.2",
     "vue-router": "^4.1.5"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index eb649dd..1724d07 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -27,6 +27,7 @@
   prettier: ^2.7.1
   qs: ^6.11.0
   sass: ^1.54.5
+  socket.io-client: ^4.5.2
   typescript: '*'
   vite: ^3.0.7
   vite-plugin-compression: ^0.5.1
@@ -45,6 +46,7 @@
   pinia: 2.0.21_typescript@4.8.2+vue@3.2.37
   pinia-plugin-persistedstate: 2.1.1_pinia@2.0.21
   qs: registry.npmmirror.com/qs/6.11.0
+  socket.io-client: registry.npmmirror.com/socket.io-client/4.5.2
   vue: 3.2.37
   vue-i18n: 9.2.2_vue@3.2.37
   vue-router: 4.1.5_vue@3.2.37
@@ -1204,12 +1206,6 @@
       ms: 2.0.0
     dev: true
 
-  /debug/3.2.7:
-    resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
-    dependencies:
-      ms: registry.npmmirror.com/ms/2.1.2
-    dev: true
-
   /debug/4.3.4:
     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
     engines: {node: '>=6.0'}
@@ -1395,7 +1391,7 @@
   /eslint-import-resolver-node/0.3.6:
     resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==}
     dependencies:
-      debug: 3.2.7
+      debug: registry.npmmirror.com/debug/3.2.7
       resolve: 1.22.1
     dev: true
 
@@ -1408,7 +1404,7 @@
       eslint:
         optional: true
     dependencies:
-      debug: 3.2.7
+      debug: registry.npmmirror.com/debug/3.2.7
       eslint: 8.23.0
     dev: true
 
@@ -2781,7 +2777,7 @@
     peerDependencies:
       eslint: '>=6.0.0'
     dependencies:
-      debug: 4.3.4
+      debug: registry.npmmirror.com/debug/4.3.4
       eslint: 8.23.0
       eslint-scope: 7.1.1
       eslint-visitor-keys: 3.3.0
@@ -2915,6 +2911,12 @@
     dev: true
     optional: true
 
+  registry.npmmirror.com/@socket.io/component-emitter/3.1.0:
+    resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz}
+    name: '@socket.io/component-emitter'
+    version: 3.1.0
+    dev: false
+
   registry.npmmirror.com/@types/qs/6.9.7:
     resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz}
     name: '@types/qs'
@@ -3126,6 +3128,14 @@
       which: registry.npmmirror.com/which/2.0.2
     dev: true
 
+  registry.npmmirror.com/debug/3.2.7:
+    resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz}
+    name: debug
+    version: 3.2.7
+    dependencies:
+      ms: registry.npmmirror.com/ms/2.1.2
+    dev: true
+
   registry.npmmirror.com/debug/4.3.4:
     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz}
     name: debug
@@ -3138,7 +3148,6 @@
         optional: true
     dependencies:
       ms: registry.npmmirror.com/ms/2.1.2
-    dev: true
 
   registry.npmmirror.com/delayed-stream/1.0.0:
     resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz}
@@ -3165,6 +3174,29 @@
     version: 9.2.2
     dev: true
 
+  registry.npmmirror.com/engine.io-client/6.2.2:
+    resolution: {integrity: sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/engine.io-client/-/engine.io-client-6.2.2.tgz}
+    name: engine.io-client
+    version: 6.2.2
+    dependencies:
+      '@socket.io/component-emitter': registry.npmmirror.com/@socket.io/component-emitter/3.1.0
+      debug: registry.npmmirror.com/debug/4.3.4
+      engine.io-parser: registry.npmmirror.com/engine.io-parser/5.0.4
+      ws: registry.npmmirror.com/ws/8.2.3
+      xmlhttprequest-ssl: registry.npmmirror.com/xmlhttprequest-ssl/2.0.0
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+    dev: false
+
+  registry.npmmirror.com/engine.io-parser/5.0.4:
+    resolution: {integrity: sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz}
+    name: engine.io-parser
+    version: 5.0.4
+    engines: {node: '>=10.0.0'}
+    dev: false
+
   registry.npmmirror.com/esbuild-android-64/0.14.54:
     resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz}
     name: esbuild-android-64
@@ -3663,7 +3695,6 @@
     resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz}
     name: ms
     version: 2.1.2
-    dev: true
 
   registry.npmmirror.com/normalize-path/3.0.0:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz}
@@ -3838,6 +3869,34 @@
       is-fullwidth-code-point: registry.npmmirror.com/is-fullwidth-code-point/4.0.0
     dev: true
 
+  registry.npmmirror.com/socket.io-client/4.5.2:
+    resolution: {integrity: sha512-naqYfFu7CLDiQ1B7AlLhRXKX3gdeaIMfgigwavDzgJoIUYulc1qHH5+2XflTsXTPY7BlPH5rppJyUjhjrKQKLg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/socket.io-client/-/socket.io-client-4.5.2.tgz}
+    name: socket.io-client
+    version: 4.5.2
+    engines: {node: '>=10.0.0'}
+    dependencies:
+      '@socket.io/component-emitter': registry.npmmirror.com/@socket.io/component-emitter/3.1.0
+      debug: registry.npmmirror.com/debug/4.3.4
+      engine.io-client: registry.npmmirror.com/engine.io-client/6.2.2
+      socket.io-parser: registry.npmmirror.com/socket.io-parser/4.2.1
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+    dev: false
+
+  registry.npmmirror.com/socket.io-parser/4.2.1:
+    resolution: {integrity: sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz}
+    name: socket.io-parser
+    version: 4.2.1
+    engines: {node: '>=10.0.0'}
+    dependencies:
+      '@socket.io/component-emitter': registry.npmmirror.com/@socket.io/component-emitter/3.1.0
+      debug: registry.npmmirror.com/debug/4.3.4
+    transitivePeerDependencies:
+      - supports-color
+    dev: false
+
   registry.npmmirror.com/string-argv/0.3.1:
     resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/string-argv/-/string-argv-0.3.1.tgz}
     name: string-argv
@@ -3952,6 +4011,28 @@
       strip-ansi: registry.npmmirror.com/strip-ansi/6.0.1
     dev: true
 
+  registry.npmmirror.com/ws/8.2.3:
+    resolution: {integrity: sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ws/-/ws-8.2.3.tgz}
+    name: ws
+    version: 8.2.3
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: ^5.0.2
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+    dev: false
+
+  registry.npmmirror.com/xmlhttprequest-ssl/2.0.0:
+    resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz}
+    name: xmlhttprequest-ssl
+    version: 2.0.0
+    engines: {node: '>=0.4.0'}
+    dev: false
+
   registry.npmmirror.com/yaml/2.1.1:
     resolution: {integrity: sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/yaml/-/yaml-2.1.1.tgz}
     name: yaml
diff --git a/studio/components/log/index.tsx b/studio/components/log/index.tsx
index 57ee3bb..6e353cb 100644
--- a/studio/components/log/index.tsx
+++ b/studio/components/log/index.tsx
@@ -16,17 +16,24 @@
  */
 
 import { NTabs, NTabPane, NLog } from 'naive-ui'
-import { defineComponent, ref } from 'vue'
+import { defineComponent, PropType } from 'vue'
+
+const props = {
+  value: {
+    type: String as PropType<string>,
+    default: ''
+  }
+}
 
 export const Log = defineComponent({
   name: 'log',
-  setup() {
-    const logRef = ref('')
+  props,
+  setup(props) {
     return () => {
       return (
         <NTabs type='card' closable size='small'>
           <NTabPane name='运行日志'>
-            <NLog log={logRef.value} />
+            <NLog log={props.value} />
           </NTabPane>
         </NTabs>
       )
diff --git a/studio/components/studio-content/index.tsx b/studio/components/studio-content/index.tsx
index 9533bfa..829a8e6 100644
--- a/studio/components/studio-content/index.tsx
+++ b/studio/components/studio-content/index.tsx
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import { defineComponent } from 'vue'
+import { defineComponent, ref } from 'vue'
 import { NLayoutContent } from 'naive-ui'
 import { Toolbar } from '../toolbar'
 import { Tabs } from '../tab'
@@ -24,12 +24,18 @@
 export const StudioContent = defineComponent({
   name: 'studio-content',
   setup() {
+    const logIdRef = ref()
+
+    const showLog = (id: number) => {
+      logIdRef.value = id
+    }
+
     return () => (
       <NLayoutContent class={styles['studio-content']}>
         <div class={styles['editor']}>
-          <Toolbar />
+          <Toolbar onShowLog={showLog} />
           <div class={styles['tab']}>
-            <Tabs />
+            <Tabs runId={logIdRef.value} />
           </div>
         </div>
       </NLayoutContent>
diff --git a/studio/components/tab/index.tsx b/studio/components/tab/index.tsx
index 1be8e02..18aca24 100644
--- a/studio/components/tab/index.tsx
+++ b/studio/components/tab/index.tsx
@@ -15,16 +15,27 @@
  * limitations under the License.
  */
 
-import { defineComponent, watch } from 'vue'
+import { defineComponent, watch, PropType, reactive } from 'vue'
 import { NTabPane, NTabs } from 'naive-ui'
 import { MonacoEditor } from '../monaco'
 import utils from '@/utils'
 import { useFileStore } from '@/store/file'
 import { Log } from '../log'
+import { createLogSocket } from '@/service/modules/log'
+import type { Socket } from 'socket.io-client'
+
+const props = {
+  runId: {
+    type: Number as PropType<number>,
+    default: 0
+  }
+}
 
 export const Tabs = defineComponent({
   name: 'tabs',
-  setup() {
+  props,
+  setup(props) {
+    const socketRef = reactive<{ [key: string]: Socket }>({})
     const fileStore = useFileStore()
 
     const updateContent = (value: string) => {
@@ -33,27 +44,30 @@
 
     const handleClose = (fileName: string) => {
       fileStore.closeFile(fileName)
+      socketRef[fileName].close()
     }
 
     const handleChange = (value: string) => {
       updateContent(value)
     }
 
-    const createTabPane = () => {
-      return fileStore.getOpenFiles.map((file) => {
-        const language = utils.getLanguageByName(file.name)
-        return (
-          <NTabPane name={file.name} key={file.name} tab={file.name}>
-            <MonacoEditor v-model:value={file.content} options={{ language }} />
-            <Log />
-          </NTabPane>
-        )
-      })
+    const getLogContent = (id: number) => {
+      const file = fileStore.getOpenFiles.filter(
+        (file) => file.name === fileStore.getCurrentFile
+      )[0]
+
+      if (!socketRef[file.name]) {
+        socketRef[file.name] = createLogSocket(id)
+      }
+
+      const socket = socketRef[file.name]
+      console.log(file.log)
+      socket.on('log', (data) => (file.log += data))
     }
 
     watch(
-      () => fileStore.getCurrentFile,
-      () => createTabPane()
+      () => props.runId,
+      () => getLogContent(props.runId)
     )
 
     return () => (
@@ -66,7 +80,18 @@
         onClose={handleClose}
         on-update:value={handleChange}
       >
-        {createTabPane()}
+        {fileStore.getOpenFiles.map((file) => {
+          const language = utils.getLanguageByName(file.name)
+          return (
+            <NTabPane name={file.name} key={file.name} tab={file.name}>
+              <MonacoEditor
+                v-model:value={file.content}
+                options={{ language }}
+              />
+              <Log v-model:value={file.log} />
+            </NTabPane>
+          )
+        })}
       </NTabs>
     )
   }
diff --git a/studio/components/toolbar/index.tsx b/studio/components/toolbar/index.tsx
index 7e62c44..9f9ec01 100644
--- a/studio/components/toolbar/index.tsx
+++ b/studio/components/toolbar/index.tsx
@@ -29,7 +29,8 @@
 
 export const Toolbar = defineComponent({
   name: 'toolbar',
-  setup() {
+  emits: ['showLog'],
+  setup(props, ctx) {
     const fileStore = useFileStore()
 
     const handleSave = () => {
@@ -44,7 +45,8 @@
         (file) => file.name === fileStore.getCurrentFile
       )[0]
 
-      runFile(file.id)
+      // runFile(file.id)
+      ctx.emit('showLog', file.id)
     }
 
     const openFile = () => {
@@ -61,28 +63,28 @@
     return () => (
       <div class={styles.toolbar}>
         <div class={styles.operate}>
-          <NButton text style={{ fontSize: '24px' }} onClick={openFile}>
+          <NButton text style={{ fontSize: '18px' }} onClick={openFile}>
             <NIcon>
               <FileAddOutlined />
             </NIcon>
           </NButton>
         </div>
         <div class={styles.operate}>
-          <NButton text style={{ fontSize: '24px' }} onClick={handleSave}>
+          <NButton text style={{ fontSize: '18px' }} onClick={handleSave}>
             <NIcon>
               <SaveOutlined />
             </NIcon>
           </NButton>
         </div>
         <div class={styles.operate}>
-          <NButton text style={{ fontSize: '24px' }} onClick={handleRun}>
+          <NButton text style={{ fontSize: '18px' }} onClick={handleRun}>
             <NIcon>
               <PlayCircleOutlined />
             </NIcon>
           </NButton>
         </div>
         <div class={styles.operate}>
-          <NButton text style={{ fontSize: '24px' }}>
+          <NButton text style={{ fontSize: '18px' }}>
             <NIcon>
               <FullscreenOutlined />
             </NIcon>
diff --git a/studio/service/modules/log/index.ts b/studio/service/modules/log/index.ts
new file mode 100644
index 0000000..cb902e6
--- /dev/null
+++ b/studio/service/modules/log/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { io, Socket } from 'socket.io-client'
+
+export const createLogSocket = (id: number): Socket => {
+  return io('', {
+    path: '/studio/ws'
+  })
+}
diff --git a/studio/store/file/index.ts b/studio/store/file/index.ts
index 5b8232e..834aaf2 100644
--- a/studio/store/file/index.ts
+++ b/studio/store/file/index.ts
@@ -41,6 +41,7 @@
         this.files.push(file)
         this.fileNames.push(file.name)
       }
+      file.log = ''
       this.currentFile = file.name
     },
     closeFile(fileName: string): void {
diff --git a/studio/store/file/types.ts b/studio/store/file/types.ts
index 57d7ae1..0ea4043 100644
--- a/studio/store/file/types.ts
+++ b/studio/store/file/types.ts
@@ -21,6 +21,7 @@
   content: string
   oldContent?: string
   saved: boolean
+  log?: string
 }
 
 interface IFileState {
diff --git a/vite.config.ts b/vite.config.ts
index 84e6ed8..429ce10 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -50,6 +50,11 @@
         target: loadEnv('development', './').VITE_APP_DEV_WEB_URL,
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/studio\/api/, '')
+      },
+      '/studio/ws': {
+        target: loadEnv('development', './').VITE_APP_DEV_WEB_URL,
+        ws: true,
+        rewrite: (path) => path.replace(/^\/studio\/ws/, '/socket.io/')
       }
     }
   },