[{"data":1,"prerenderedAt":493},["ShallowReactive",2],{"post-\u002Fposts\u002Fgolang-logger-out":3,"surround-\u002Fposts\u002Fgolang-logger-out":484},{"id":4,"title":5,"body":6,"cover":469,"date":470,"description":471,"draft":472,"extension":473,"meta":474,"minutes":39,"navigation":72,"path":475,"seo":476,"stem":477,"tags":478,"updated":469,"__hash__":483},"posts\u002Fposts\u002Fgolang-logger-out.md","如何自定义处理Golang Cmd的日志输出",{"type":7,"value":8,"toc":467},"minimark",[9,13,240,243,399,402,460,463],[10,11,12],"p",{},"为了更好的处理 Golang Cmd 的日志输出，我们可以先查看exce.Cmd 结构体的定义。在代码中可以 Stdout io.Writer 和 Stderr io.Writer 两个字段，一个进程的标准输出和标准错误输出可以通过这两个字段来指定。 简而言之我们只需要实现一个 io.Writer 接口的结构体，然后将这个结构体赋值给 Cmd 结构体的 Stdout 和 Stderr 字段即可。",[14,15,20],"pre",{"className":16,"code":17,"language":18,"meta":19,"style":19},"language-go shiki shiki-themes tokyo-night github-light","type Cmd struct {\n    \u002F\u002F Path is the path of the command to run.\n    \u002F\u002F\n    \u002F\u002F This is the only field that must be set to a non-zero\n    \u002F\u002F value. If Path is relative, it is evaluated relative\n    \u002F\u002F to Dir.\n    Path string\n\n    \u002F\u002F Args holds command line arguments, including the command as Args[0].\n    \u002F\u002F If the Args field is empty or nil, Run uses {Path}.\n    \u002F\u002F\n    \u002F\u002F In typical use, both Path and Args are set by calling Command.\n    Args []string\n\n    ......\n\n    \u002F\u002F Stdout and Stderr specify the process's standard output and error.\n    \u002F\u002F\n    \u002F\u002F If either is nil, Run connects the corresponding file descriptor\n    \u002F\u002F to the null device (os.DevNull).\n    \u002F\u002F\n    \u002F\u002F If either is an *os.File, the corresponding output from the process\n    \u002F\u002F is connected directly to that file.\n    \u002F\u002F\n    \u002F\u002F Otherwise, during the execution of the command a separate goroutine\n    \u002F\u002F reads from the process over a pipe and delivers that data to the\n    \u002F\u002F corresponding Writer. In this case, Wait does not complete until the\n    \u002F\u002F goroutine reaches EOF or encounters an error or a nonzero WaitDelay\n    \u002F\u002F expires.\n    \u002F\u002F\n    \u002F\u002F If Stdout and Stderr are the same writer, and have a type that can\n    \u002F\u002F be compared with ==, at most one goroutine at a time will call Write.\n    Stdout io.Writer\n    Stderr io.Writer\n\n    .....\n}\n","go","",[21,22,23,31,37,43,49,55,61,67,74,80,86,91,97,103,108,114,119,125,130,136,142,147,153,159,164,170,176,182,188,194,199,205,211,217,223,228,234],"code",{"__ignoreMap":19},[24,25,28],"span",{"class":26,"line":27},"line",1,[24,29,30],{},"type Cmd struct {\n",[24,32,34],{"class":26,"line":33},2,[24,35,36],{},"    \u002F\u002F Path is the path of the command to run.\n",[24,38,40],{"class":26,"line":39},3,[24,41,42],{},"    \u002F\u002F\n",[24,44,46],{"class":26,"line":45},4,[24,47,48],{},"    \u002F\u002F This is the only field that must be set to a non-zero\n",[24,50,52],{"class":26,"line":51},5,[24,53,54],{},"    \u002F\u002F value. If Path is relative, it is evaluated relative\n",[24,56,58],{"class":26,"line":57},6,[24,59,60],{},"    \u002F\u002F to Dir.\n",[24,62,64],{"class":26,"line":63},7,[24,65,66],{},"    Path string\n",[24,68,70],{"class":26,"line":69},8,[24,71,73],{"emptyLinePlaceholder":72},true,"\n",[24,75,77],{"class":26,"line":76},9,[24,78,79],{},"    \u002F\u002F Args holds command line arguments, including the command as Args[0].\n",[24,81,83],{"class":26,"line":82},10,[24,84,85],{},"    \u002F\u002F If the Args field is empty or nil, Run uses {Path}.\n",[24,87,89],{"class":26,"line":88},11,[24,90,42],{},[24,92,94],{"class":26,"line":93},12,[24,95,96],{},"    \u002F\u002F In typical use, both Path and Args are set by calling Command.\n",[24,98,100],{"class":26,"line":99},13,[24,101,102],{},"    Args []string\n",[24,104,106],{"class":26,"line":105},14,[24,107,73],{"emptyLinePlaceholder":72},[24,109,111],{"class":26,"line":110},15,[24,112,113],{},"    ......\n",[24,115,117],{"class":26,"line":116},16,[24,118,73],{"emptyLinePlaceholder":72},[24,120,122],{"class":26,"line":121},17,[24,123,124],{},"    \u002F\u002F Stdout and Stderr specify the process's standard output and error.\n",[24,126,128],{"class":26,"line":127},18,[24,129,42],{},[24,131,133],{"class":26,"line":132},19,[24,134,135],{},"    \u002F\u002F If either is nil, Run connects the corresponding file descriptor\n",[24,137,139],{"class":26,"line":138},20,[24,140,141],{},"    \u002F\u002F to the null device (os.DevNull).\n",[24,143,145],{"class":26,"line":144},21,[24,146,42],{},[24,148,150],{"class":26,"line":149},22,[24,151,152],{},"    \u002F\u002F If either is an *os.File, the corresponding output from the process\n",[24,154,156],{"class":26,"line":155},23,[24,157,158],{},"    \u002F\u002F is connected directly to that file.\n",[24,160,162],{"class":26,"line":161},24,[24,163,42],{},[24,165,167],{"class":26,"line":166},25,[24,168,169],{},"    \u002F\u002F Otherwise, during the execution of the command a separate goroutine\n",[24,171,173],{"class":26,"line":172},26,[24,174,175],{},"    \u002F\u002F reads from the process over a pipe and delivers that data to the\n",[24,177,179],{"class":26,"line":178},27,[24,180,181],{},"    \u002F\u002F corresponding Writer. In this case, Wait does not complete until the\n",[24,183,185],{"class":26,"line":184},28,[24,186,187],{},"    \u002F\u002F goroutine reaches EOF or encounters an error or a nonzero WaitDelay\n",[24,189,191],{"class":26,"line":190},29,[24,192,193],{},"    \u002F\u002F expires.\n",[24,195,197],{"class":26,"line":196},30,[24,198,42],{},[24,200,202],{"class":26,"line":201},31,[24,203,204],{},"    \u002F\u002F If Stdout and Stderr are the same writer, and have a type that can\n",[24,206,208],{"class":26,"line":207},32,[24,209,210],{},"    \u002F\u002F be compared with ==, at most one goroutine at a time will call Write.\n",[24,212,214],{"class":26,"line":213},33,[24,215,216],{},"    Stdout io.Writer\n",[24,218,220],{"class":26,"line":219},34,[24,221,222],{},"    Stderr io.Writer\n",[24,224,226],{"class":26,"line":225},35,[24,227,73],{"emptyLinePlaceholder":72},[24,229,231],{"class":26,"line":230},36,[24,232,233],{},"    .....\n",[24,235,237],{"class":26,"line":236},37,[24,238,239],{},"}\n",[10,241,242],{},"下面是一个简单的实现，将 Cmd 的标准输出和标准错误输出写入到文件中。",[14,244,246],{"className":16,"code":245,"language":18,"meta":19,"style":19},"type LogWriter struct {\n    file       *os.File\n}\n\nfunc NewLogWriter(filename string) (*LogWriter, error) {\n    \u002F\u002F 打开文件，如果文件不存在则创建\n    file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)\n    if err != nil {\n        return nil, err\n    }\n\n    return &LogWriter{\n        file:       file,\n    }, nil\n}\n\nfunc (lw *LogWriter) Write(p []byte) (n int, err error) {\n    message := string(p)\n    go func() {\n        \u002F\u002F 这里可以对日志进行处理，比如sse推送等\n    }()\n    \u002F\u002F 将日志写入到文件中\n    if _, err := lw.file.Write(p); err != nil {\n        return 0, err\n    }\n\n    return len(p), nil\n}\n\u002F\u002F 关闭日志文件\nfunc (lw *LogWriter) Close() error {\n    return lw.file.Close()\n}\n",[21,247,248,253,258,262,266,271,276,281,286,291,296,300,305,310,315,319,323,328,333,338,343,348,353,358,363,367,371,376,380,385,390,395],{"__ignoreMap":19},[24,249,250],{"class":26,"line":27},[24,251,252],{},"type LogWriter struct {\n",[24,254,255],{"class":26,"line":33},[24,256,257],{},"    file       *os.File\n",[24,259,260],{"class":26,"line":39},[24,261,239],{},[24,263,264],{"class":26,"line":45},[24,265,73],{"emptyLinePlaceholder":72},[24,267,268],{"class":26,"line":51},[24,269,270],{},"func NewLogWriter(filename string) (*LogWriter, error) {\n",[24,272,273],{"class":26,"line":57},[24,274,275],{},"    \u002F\u002F 打开文件，如果文件不存在则创建\n",[24,277,278],{"class":26,"line":63},[24,279,280],{},"    file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)\n",[24,282,283],{"class":26,"line":69},[24,284,285],{},"    if err != nil {\n",[24,287,288],{"class":26,"line":76},[24,289,290],{},"        return nil, err\n",[24,292,293],{"class":26,"line":82},[24,294,295],{},"    }\n",[24,297,298],{"class":26,"line":88},[24,299,73],{"emptyLinePlaceholder":72},[24,301,302],{"class":26,"line":93},[24,303,304],{},"    return &LogWriter{\n",[24,306,307],{"class":26,"line":99},[24,308,309],{},"        file:       file,\n",[24,311,312],{"class":26,"line":105},[24,313,314],{},"    }, nil\n",[24,316,317],{"class":26,"line":110},[24,318,239],{},[24,320,321],{"class":26,"line":116},[24,322,73],{"emptyLinePlaceholder":72},[24,324,325],{"class":26,"line":121},[24,326,327],{},"func (lw *LogWriter) Write(p []byte) (n int, err error) {\n",[24,329,330],{"class":26,"line":127},[24,331,332],{},"    message := string(p)\n",[24,334,335],{"class":26,"line":132},[24,336,337],{},"    go func() {\n",[24,339,340],{"class":26,"line":138},[24,341,342],{},"        \u002F\u002F 这里可以对日志进行处理，比如sse推送等\n",[24,344,345],{"class":26,"line":144},[24,346,347],{},"    }()\n",[24,349,350],{"class":26,"line":149},[24,351,352],{},"    \u002F\u002F 将日志写入到文件中\n",[24,354,355],{"class":26,"line":155},[24,356,357],{},"    if _, err := lw.file.Write(p); err != nil {\n",[24,359,360],{"class":26,"line":161},[24,361,362],{},"        return 0, err\n",[24,364,365],{"class":26,"line":166},[24,366,295],{},[24,368,369],{"class":26,"line":172},[24,370,73],{"emptyLinePlaceholder":72},[24,372,373],{"class":26,"line":178},[24,374,375],{},"    return len(p), nil\n",[24,377,378],{"class":26,"line":184},[24,379,239],{},[24,381,382],{"class":26,"line":190},[24,383,384],{},"\u002F\u002F 关闭日志文件\n",[24,386,387],{"class":26,"line":196},[24,388,389],{},"func (lw *LogWriter) Close() error {\n",[24,391,392],{"class":26,"line":201},[24,393,394],{},"    return lw.file.Close()\n",[24,396,397],{"class":26,"line":207},[24,398,239],{},[10,400,401],{},"然后在启动cmd的时候将这个结构体赋值给 Cmd 结构体的 Stdout 和 Stderr 字段即可。例如：",[14,403,405],{"className":16,"code":404,"language":18,"meta":19,"style":19},"\u002F\u002F 创建一个日志文件\nlogWriter, e := NewLogWriter(\"log.txt\")\nif e != nil {\n    log.Log.Errorf(\"StartTask wirte log error:%s\", e.Error())\n}\n\nctx, _ := context.WithCancel(context.Background())\ncmd := exec.CommandContext(ctx, \"sh\", \"run.sh\")\ncmd.Stdout = logWriter\ncmd.Stderr = logWriter\ncmd.Start()\n",[21,406,407,412,417,422,427,431,435,440,445,450,455],{"__ignoreMap":19},[24,408,409],{"class":26,"line":27},[24,410,411],{},"\u002F\u002F 创建一个日志文件\n",[24,413,414],{"class":26,"line":33},[24,415,416],{},"logWriter, e := NewLogWriter(\"log.txt\")\n",[24,418,419],{"class":26,"line":39},[24,420,421],{},"if e != nil {\n",[24,423,424],{"class":26,"line":45},[24,425,426],{},"    log.Log.Errorf(\"StartTask wirte log error:%s\", e.Error())\n",[24,428,429],{"class":26,"line":51},[24,430,239],{},[24,432,433],{"class":26,"line":57},[24,434,73],{"emptyLinePlaceholder":72},[24,436,437],{"class":26,"line":63},[24,438,439],{},"ctx, _ := context.WithCancel(context.Background())\n",[24,441,442],{"class":26,"line":69},[24,443,444],{},"cmd := exec.CommandContext(ctx, \"sh\", \"run.sh\")\n",[24,446,447],{"class":26,"line":76},[24,448,449],{},"cmd.Stdout = logWriter\n",[24,451,452],{"class":26,"line":82},[24,453,454],{},"cmd.Stderr = logWriter\n",[24,456,457],{"class":26,"line":88},[24,458,459],{},"cmd.Start()\n",[10,461,462],{},"启动完成后查看log.txt文件是否自动创建并且驶入写入了日志信息。",[464,465,466],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}",{"title":19,"searchDepth":39,"depth":39,"links":468},[],null,"2024-03-21","当前基于Golang 1.22.2 版本实现",false,"md",{},"\u002Fposts\u002Fgolang-logger-out",{"title":5,"description":471},"posts\u002Fgolang-logger-out",[479,480,481,482],"golang","linux","LinkError","文件移动","uo3jxTTL_zR4ks4pSOq2BNvYZig54ddljllGrYpJims",[485,489],{"title":486,"path":487,"stem":488,"children":-1},"开站：为什么是 Moonstream","\u002Fposts\u002Fhello-moonstream","posts\u002Fhello-moonstream",{"title":490,"path":491,"stem":492,"children":-1},"Golang在linux跨区移动文件失败","\u002Fposts\u002Fgolang-linux-file","posts\u002Fgolang-linux-file",1786707084689]