refactor: make log level to use int32

This commit is contained in:
Fu Diwei
2025-07-23 21:37:46 +08:00
parent 9138abc71b
commit c07caeb45b
12 changed files with 126 additions and 174 deletions
+6 -3
View File
@@ -1,6 +1,9 @@
package domain
import "strings"
import (
"log/slog"
"strings"
)
const CollectionNameWorkflowLog = "workflow_logs"
@@ -11,7 +14,7 @@ type WorkflowLog struct {
NodeId string `json:"nodeId" db:"nodeId"`
NodeName string `json:"nodeName" db:"nodeName"`
Timestamp int64 `json:"timestamp" db:"timestamp"` // 毫秒级时间戳
Level string `json:"level" db:"level"`
Level int32 `json:"level" db:"level"`
Message string `json:"message" db:"message"`
Data map[string]any `json:"data" db:"data"`
}
@@ -21,7 +24,7 @@ type WorkflowLogs []WorkflowLog
func (r WorkflowLogs) ErrorString() string {
var builder strings.Builder
for _, log := range r {
if log.Level == "ERROR" {
if log.Level >= int32(slog.LevelError) {
builder.WriteString(log.Message)
builder.WriteString("\n")
}
+1 -1
View File
@@ -104,7 +104,7 @@ func (r *WorkflowLogRepository) castRecordToModel(record *core.Record) (*domain.
NodeId: record.GetString("nodeId"),
NodeName: record.GetString("nodeName"),
Timestamp: int64(record.GetInt("timestamp")),
Level: record.GetString("level"),
Level: int32(record.GetInt("level")),
Message: record.GetString("message"),
Data: logdata,
}
+1 -1
View File
@@ -83,7 +83,7 @@ func (w *workflowInvoker) processNode(ctx context.Context, node *domain.Workflow
log.NodeId = current.Id
log.NodeName = current.Name
log.Timestamp = record.Time.UnixMilli()
log.Level = record.Level.String()
log.Level = int32(record.Level)
log.Message = record.Message
log.Data = record.Data
log.CreatedAt = record.Time
+91
View File
@@ -0,0 +1,91 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
tracer := NewTracer("v0.4.0")
tracer.Printf("go ...")
// update collection `workflow_logs`
{
collection, err := app.FindCollectionByNameOrId("pbc_1682296116")
if err != nil {
return err
}
field := collection.Fields.GetByName("level")
if field != nil && field.Type() == "text" {
// add temp field `levelTmp`
if err := collection.Fields.AddMarshaledJSONAt(7, []byte(`{
"hidden": false,
"id": "number760395071",
"max": null,
"min": null,
"name": "levelTmp",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}`)); err != nil {
return err
}
if err := app.Save(collection); err != nil {
return err
}
// copy `level` to `levelTmp`
if _, err := app.DB().NewQuery("UPDATE workflow_logs SET levelTmp = -4 WHERE level = 'DEBUG'").Execute(); err != nil {
return err
}
if _, err := app.DB().NewQuery("UPDATE workflow_logs SET levelTmp = 4 WHERE level = 'WARN'").Execute(); err != nil {
return err
}
if _, err := app.DB().NewQuery("UPDATE workflow_logs SET levelTmp = 8 WHERE level = 'ERROR'").Execute(); err != nil {
return err
}
if _, err := app.DB().NewQuery("UPDATE workflow_logs SET levelTmp = 0 WHERE levelTmp IS NULL").Execute(); err != nil {
return err
}
// remove old field `level`
collection.Fields.RemoveById(field.GetId())
if err := app.Save(collection); err != nil {
println(err)
return err
}
// rename field `levelTmp` to `level`
if err := collection.Fields.AddMarshaledJSONAt(6, []byte(`{
"hidden": false,
"id": "number760395071",
"max": null,
"min": null,
"name": "level",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}`)); err != nil {
return err
}
if err := app.Save(collection); err != nil {
println(err)
return err
}
tracer.Printf("collection '%s' updated", collection.Name)
}
}
tracer.Printf("done")
return nil
}, func(app core.App) error {
return nil
})
}
+1 -14
View File
@@ -2,7 +2,6 @@ package logging
import (
"context"
"fmt"
"log/slog"
"sync"
@@ -114,22 +113,10 @@ func (h *HookHandler) Handle(ctx context.Context, r slog.Record) error {
log := &Record{
Time: r.Time,
Level: r.Level,
Message: r.Message,
Data: types.JSONMap[any](data),
}
switch r.Level {
case slog.LevelDebug:
log.Level = LevelDebug
case slog.LevelInfo:
log.Level = LevelInfo
case slog.LevelWarn:
log.Level = LevelWarn
case slog.LevelError:
log.Level = LevelError
default:
log.Level = Level(fmt.Sprintf("LV(%d)", r.Level))
}
if err := h.writeRecord(ctx, log); err != nil {
return err
}
-31
View File
@@ -1,31 +0,0 @@
package logging
import "log/slog"
type Level string
const (
LevelDebug Level = "DEBUG"
LevelInfo Level = "INFO"
LevelWarn Level = "WARN"
LevelError Level = "ERROR"
)
func (l Level) String() string {
return string(l)
}
func (l Level) Level() slog.Level {
switch l {
case LevelDebug:
return slog.LevelDebug
case LevelInfo:
return slog.LevelInfo
case LevelWarn:
return slog.LevelWarn
case LevelError:
return slog.LevelError
default:
return slog.Level(-1)
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
package logging
import (
"log/slog"
"time"
types "github.com/pocketbase/pocketbase/tools/types"
@@ -8,7 +9,7 @@ import (
type Record struct {
Time time.Time
Level Level
Level slog.Level
Message string
Data types.JSONMap[any]
}
+1
View File
@@ -93,6 +93,7 @@ const RootApp = () => {
Layout: {
...antdTheme?.components?.Layout,
bodyBg: "transparent",
headerBg: "transparent",
siderBg: "transparent",
},
Dropdown: {
@@ -9,7 +9,7 @@ import { ClientResponseError } from "pocketbase";
import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer";
import Show from "@/components/Show";
import { type CertificateModel } from "@/domain/certificate";
import { type WorkflowLogModel } from "@/domain/workflowLog";
import { WorkflowLogLevel, type WorkflowLogModel } from "@/domain/workflowLog";
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
import { useBrowserTheme } from "@/hooks";
import { listByWorkflowRunId as listCertificatesByWorkflowRunId } from "@/repository/certificate";
@@ -138,10 +138,14 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
<div
className={mergeCls(
"font-mono",
record.level === "DEBUG" ? "text-stone-400" : "",
record.level === "WARN" ? "text-yellow-600" : "",
record.level === "ERROR" ? "text-red-600" : "",
!showWhitespace ? "whitespace-pre-line" : ""
record.level < WorkflowLogLevel.Info
? "text-stone-400"
: record.level < WorkflowLogLevel.Warn
? ""
: record.level < WorkflowLogLevel.Error
? "text-warning"
: "text-error",
{ ["whitespace-pre-line"]: !showWhitespace }
)}
>
{message}
@@ -259,94 +263,6 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
);
};
const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
const { t } = useTranslation();
const { notification } = App.useApp();
const tableColumns: TableProps<CertificateModel>["columns"] = [
{
key: "$index",
align: "center",
fixed: "left",
width: 50,
render: (_, __, index) => index + 1,
},
{
key: "type",
title: t("workflow_run_artifact.props.type"),
render: () => t("workflow_run_artifact.props.type.certificate"),
},
{
key: "name",
title: t("workflow_run_artifact.props.name"),
render: (_, record) => {
return (
<div className="max-w-full truncate">
<Typography.Text delete={!!record.deleted} ellipsis>
{record.subjectAltNames}
</Typography.Text>
</div>
);
},
},
{
key: "$action",
align: "end",
width: 120,
render: (_, record) => (
<div className="flex items-center justify-end">
<CertificateDetailDrawer
data={record}
trigger={
<Tooltip title={t("common.button.view")}>
<Button color="primary" disabled={!!record.deleted} icon={<IconBrowserShare size="1.25em" />} variant="text" />
</Tooltip>
}
/>
</div>
),
},
];
const [tableData, setTableData] = useState<CertificateModel[]>([]);
const { loading: tableLoading } = useRequest(
() => {
return listCertificatesByWorkflowRunId(runId);
},
{
refreshDeps: [runId],
onSuccess: (res) => {
setTableData(res.items);
},
onError: (err) => {
if (err instanceof ClientResponseError && err.isAbort) {
return;
}
console.error(err);
notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
throw err;
},
}
);
return (
<>
<Typography.Title level={5}>{t("workflow_run.artifacts")}</Typography.Title>
<Table<CertificateModel>
columns={tableColumns}
dataSource={tableData}
loading={tableLoading}
locale={{
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />,
}}
pagination={false}
rowKey={(record) => record.id}
size="small"
/>
</>
);
};
const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {};
export default WorkflowRunDetail;
+8 -1
View File
@@ -2,7 +2,14 @@ export interface WorkflowLogModel extends Omit<BaseModel, "updated"> {
nodeId: string;
nodeName: string;
timestamp: ReturnType<typeof Date.prototype.getTime>;
level: "DEBUG" | "INFO" | "WARN" | "ERROR";
level: number;
message: string;
data: Record<string, any>;
}
export enum WorkflowLogLevel {
Debug = -4,
Info = 0,
Warn = 4,
Error = 8,
}
+4 -27
View File
@@ -12,10 +12,10 @@ import {
IconMenu2,
IconSettings,
} from "@tabler/icons-react";
import { Alert, Button, Drawer, Layout, Menu, type MenuProps, theme } from "antd";
import { Alert, Button, Divider, Drawer, Layout, Menu, type MenuProps, theme } from "antd";
import AppLocale, { useAppLocaleMenuItems } from "@/components/AppLocale";
import AppTheme, { useAppThemeMenuItems } from "@/components/AppTheme";
import AppLocale from "@/components/AppLocale";
import AppTheme from "@/components/AppTheme";
import AppVersion from "@/components/AppVersion";
import Show from "@/components/Show";
import { APP_DOCUMENT_URL, APP_REPO_URL } from "@/domain/app";
@@ -30,9 +30,6 @@ const ConsoleLayout = () => {
const { token: themeToken } = theme.useToken();
const localeMenuItems = useAppLocaleMenuItems();
const themeMenuItems = useAppThemeMenuItems();
const handleLogoutClick = () => {
auth.clear();
navigate("/login");
@@ -76,26 +73,6 @@ const ConsoleLayout = () => {
<Menu
style={{ background: "transparent", borderInlineEnd: "none" }}
items={[
{
key: "theme",
icon: (
<span className="anticon scale-125" role="img">
<AppTheme.Icon size="1em" />
</span>
),
label: t("common.menu.theme"),
children: themeMenuItems,
},
{
key: "locale",
icon: (
<span className="anticon scale-115" role="img">
<AppLocale.Icon size="1em" />
</span>
),
label: t("common.menu.locale"),
children: localeMenuItems,
},
{
key: "document",
icon: (
@@ -126,7 +103,7 @@ const ConsoleLayout = () => {
</Layout.Sider>
<Layout className="flex flex-col overflow-hidden">
<Layout.Header className="shadow-xs md:hidden" style={{ padding: 0 }}>
<Layout.Header className="border-b shadow-sm md:hidden" style={{ padding: 0, borderBottomColor: themeToken.colorBorderSecondary }}>
<div className="flex size-full items-center justify-between overflow-hidden px-4">
<div className="flex items-center gap-4">
<SiderMenuDrawer trigger={<Button icon={<IconMenu2 size="1.25em" stroke="1.25" />} />} />
+1 -1
View File
@@ -52,7 +52,7 @@ const SettingsAppearanceTheme = ({ className, style }: { className?: string; sty
<div className="flex w-full items-center gap-4 max-md:flex-wrap">
{themeItems.map((item) => (
<div className="relative max-w-44 flex-1/3 max-md:flex-1/2 max-sm:flex-1" key={item.key}>
<div className="overflow-hidden rounded-lg border border-solid" style={{ borderColor: themeToken.colorBorderSecondary }}>
<div className="overflow-hidden rounded-lg border border-solid" style={{ borderColor: themeToken.colorBorder }}>
<img className="mb-2 w-full" src={`/imgs/themes/${item.key}.png`} />
<div className="mb-2 px-2">
<Radio value={item.key}>{item.label}</Radio>