Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 获取应用日志 #1691

Merged
merged 10 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions cnb-builder-shim/internal/devsandbox/webserver/params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* TencentBlueKing is pleased to support the open source community by making
* 蓝鲸智云 - PaaS 平台 (BlueKing - PaaS System) available.
* Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.
*
* We undertake not to change the open source license (MIT license) applicable
* to the current version of the project delivered to anyone in the future.
*/

package webserver

// LogQueryParams : log query params
SheepSheepChen marked this conversation as resolved.
Show resolved Hide resolved
// It includes the following fields:
// - lines: Specifies the number of log lines to retrieve
type LogQueryParams struct {
Lines int `form:"lines,default=100" binding:"omitempty,gte=1,lte=200"`
}
22 changes: 22 additions & 0 deletions cnb-builder-shim/internal/devsandbox/webserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func New(lg *logr.Logger) (*WebServer, error) {
mgr := service.NewDeployManager()
r.POST("/deploys", DeployHandler(s, mgr))
r.GET("/deploys/:deployID/results", ResultHandler(mgr))
r.GET("/app_logs", AppLogHandler())
SheepSheepChen marked this conversation as resolved.
Show resolved Hide resolved

return s, nil
}
Expand Down Expand Up @@ -208,4 +209,25 @@ func ResultHandler(svc service.DeployServiceHandler) gin.HandlerFunc {
}
}

// AppLogHandler 获取 app 日志
func AppLogHandler() gin.HandlerFunc {
return func(c *gin.Context) {
var queryParams LogQueryParams
if err := c.ShouldBindQuery(&queryParams); err != nil {
// 验证失败
c.JSON(http.StatusBadRequest, gin.H{
"message": "查询参数无效,lines 必须是 1 到 200 之间的整数",
})
return
}
// 读取日志
logs, err := service.GetAppLogs(service.DefaultAppLogDir, queryParams.Lines)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": fmt.Sprintf("get app log error: %s", err.Error())})
return
}
c.JSON(http.StatusOK, gin.H{"logs": logs})
}
}

var _ devsandbox.DevWatchServer = (*WebServer)(nil)
107 changes: 107 additions & 0 deletions cnb-builder-shim/internal/devsandbox/webserver/service/app_logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* TencentBlueKing is pleased to support the open source community by making
* 蓝鲸智云 - PaaS 平台 (BlueKing - PaaS System) available.
* Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.
*
* We undertake not to change the open source license (MIT license) applicable
* to the current version of the project delivered to anyone in the future.
*/

package service

import (
"os"
"path/filepath"
"strings"

"github.com/docker/docker/pkg/tailfile"
)

// DefaultAppLogDir 应用日志默认目录
var DefaultAppLogDir = "/cnb/devsandbox/supervisor/log"

// GetAppLogs 获取应用日志
// Parameters:
// - logPath: 日志路径
// - lines: 需要的日志行数
//
// Returns:
// - map[string][]string: key 为日志类型, value 为日志内容
func GetAppLogs(logDir string, lines int) (map[string][]string, error) {
// 检查文件是否存在
if _, err := os.Stat(logDir); os.IsNotExist(err) {
// 文件不存在,返回 nil, nil
return nil, nil
}
logs := make(map[string][]string)
SheepSheepChen marked this conversation as resolved.
Show resolved Hide resolved
logFiles, err := getLogFiles(logDir)
if err != nil {
return nil, err
}
for logType, file := range logFiles {
logPath := filepath.Join(logDir, file.Name())
logLines, err := tailFile(logPath, lines)
if err != nil {
return nil, err
}
logs[logType] = logLines
}
return logs, nil
}

// 按日志类型分类日志文件
func getLogFiles(logDir string) (map[string]os.FileInfo, error) {
logFiles := make(map[string]os.FileInfo)
err := filepath.Walk(logDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 获取日志类型
logType := getLogType(info)
if logType != "" {
logFiles[logType] = info
}
return nil
})
if err != nil {
return nil, err
}
return logFiles, nil
}

// 通过文件名称获取日志类型, 需要符合格式:{{type}}.log
func getLogType(info os.FileInfo) string {
if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".log") {
logType := strings.TrimSuffix(strings.ToLower(info.Name()), filepath.Ext(info.Name()))
return logType
}
return ""
}
Copy link
Collaborator

@narasux narasux Nov 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func getLogType(info os.FileInfo) string {
    if info.IsDir() {
        return ""
    }

    filename := info.Name()
    extName := filepath.Ext(filename)

    // 使用文件名称小写格式作为日志类型
    if extName == ".log" {
        return strings.ToLower(strings.TrimSuffix(filename, extName)) 
    }
    
    return ""
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

稍稍优化下,可读性会更好,目前的写法没有重点,且重复代码太多了,看着累人

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw,在外面依赖 logType == "" 判断其实也不是好的实践,返回 (string,error)/(string,bool)可能会更好?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好,我下个 pr 一起修改


// 获取文件最新部分的内容
func tailFile(filePath string, lines int) (logs []string, err error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
tailBytes, err := tailfile.TailFile(file, lines)
if err != nil {
return nil, err
}
tailStr := make([]string, len(tailBytes))
for index, b := range tailBytes {
tailStr[index] = string(b)
}

logs = append(logs, tailStr...)
return logs, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* TencentBlueKing is pleased to support the open source community by making
* 蓝鲸智云 - PaaS 平台 (BlueKing - PaaS System) available.
* Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* 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.
*
* We undertake not to change the open source license (MIT license) applicable
* to the current version of the project delivered to anyone in the future.
*/

package service

import (
"os"
"path/filepath"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Test GetAppLogs", func() {
var err error
var logPath string
var celeryLogPath string
var mysqlLogPath string
BeforeEach(func() {
logPath, err = os.MkdirTemp("", "log_test")
Expect(err).To(BeNil())
celeryLogPath = filepath.Join(logPath, "celery.log")
mysqlLogPath = filepath.Join(logPath, "mysql.log")
logContent1 := "value1\nvalue2\n"
logContent2 := "value3\nvalue4\n"
err := os.WriteFile(celeryLogPath, []byte(logContent1), 0644)
Expect(err).To(BeNil())
err = os.WriteFile(mysqlLogPath, []byte(logContent2), 0644)
Expect(err).To(BeNil())
})
AfterEach(func() {
Expect(os.RemoveAll(logPath)).To(BeNil())
})
Describe("Test GetAppLogs", func() {
It("test lines < logs", func() {
logs, err := GetAppLogs(logPath, 1)
Expect(err).To(BeNil())
Expect(len(logs["celery"])).To(Equal(1))
Expect(logs["celery"]).To(Equal([]string{"value2"}))
Expect(len(logs["mysql"])).To(Equal(1))
Expect(logs["mysql"]).To(Equal([]string{"value4"}))
})
It("test lines > logs", func() {
logs, err := GetAppLogs(logPath, 5)
Expect(err).To(BeNil())
Expect(len(logs["celery"])).To(Equal(2))
Expect(logs["celery"]).To(Equal([]string{"value1", "value2"}))
Expect(len(logs["mysql"])).To(Equal(2))
Expect(logs["mysql"]).To(Equal([]string{"value3", "value4"}))
})
It("logFile does not exist", func() {
logs, err := GetAppLogs("", 5)
Expect(err).To(BeNil())
Expect(logs).To(BeNil())
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ var _ = Describe("Test DeployManager", func() {
var m *DeployManager
var tmpAppDir string

testSrcFilePath := filepath.Join("testdata", "templates", "django-helloworld")
testSrcFilePath := filepath.Join("testdata", "helloworld")
oldAppDir := devsandbox.DefaultAppDir

BeforeEach(func() {
Expand All @@ -55,12 +55,13 @@ var _ = Describe("Test DeployManager", func() {

Describe("Test deploy", func() {
It("test deploy", func() {
result, _ := m.Deploy(testSrcFilePath)
result, err := m.Deploy(testSrcFilePath)
Expect(err).To(BeNil())

Expect(len(result.DeployID)).To(Equal(32))
Expect(result.Status).To(Equal(devsandbox.ReloadProcessing))

_, err := os.Stat(path.Join(devsandbox.DefaultAppDir, "Procfile"))
_, err = os.Stat(path.Join(devsandbox.DefaultAppDir, "Procfile"))
Expect(err).To(BeNil())

// 验证隐藏目录不会被覆盖(删除)
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
spec_version: 2
module:
language: Python
scripts:
pre_release_hook: "python manage.py migrate --no-input"
processes:
web:
command: python manage.py runserver 0.0.0.0:8080