V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
liyu4
V2EX  ›  程序员

用 go 写了一个彩色的 log

  •  2
     
  •   liyu4 · 2016-12-26 13:41:32 +08:00 · 7399 次点击
    这是一个创建于 2671 天前的主题,其中的信息可能已经有所发展或是发生改变。

    #教你写一个 color 日志库,不止有代码还有原理。

    ##前言 在计算机里面, ansi 转义码是使用带内信号去控制格式化,颜色,或者其他的输出选项在视频流或者文本终端中的一种办法。编码这些格式化信息,就是在确定的字节序列中,把上述所说的 ansi 码嵌入到这个文本中。终端会去寻找命令去解释这些字符,而不是把它看作简单的字符码( ascii )。

    1970 年提出的 ansi 码,但是直到 1980 年的早期才普及在了迷你主机和大型机房中。它被使用在早期的电子公告板上,对比之前缺乏光标移动的系统,它改善了显示的效果,这导致了它被广泛的使用。

    尽管硬件文本终端,在 21 世纪已经日益稀少。但是不动摇 ansi 标准的影响。因为大部分的文本模拟器的解释工作,至少还有相当一部分的文本存在 ansi 转义序列。唯一的例外就是微软的 win32 console 。不过这些在微软升级到 window10 之后已经被解决了。

    所以这个古老的协议对我们现在的文本输出依然是有很重要的影响。

    ###一条小命令

    echo  -e "\033[1;31mI ♡  You \e[0m"
    

    output:

    不上图我写什么文档,对吧。这条命令可以用来简单的告白用。送给大家了。从这里我们引出了 ansi 对文本颜色的输出影响,看,就是图中字符的颜色发生了变化。利用这样的性质我们今天就要分享一下如何写一个彩色的日子库( color log )

    ###CSI

    转义序列使用 ESC 控制字符开始,对于 2 个字符序列,第二个字符是 ASCII 的 64 到 95 。(@到_,还有所有大写英文字母和[]^),然而,序列中大多数是超过 2 个字符的,并以 ESC 控制字符和左中括号开始。序列被称作 CSI ,即控制序列引导器或者控制序列启动器的简称。这个序列的最后一个字符是在 ASCII 范围 64 到 126 。也还有一个单字符的 CSI (155/0x9B/0233 ) ESC[,这两个字符序列比单个字符形式用的更多,细节参看 C0 和 C1 控制编码。(下文会添加文档的链接),使用 UTF-8 编码的终端上,两种形式都使用 2 字节( CSI in UTF-8 is 0xC2, 0x9B ),但 ESC[序列更加明了。

    ###文本颜色

    文本颜色(和 SGR (Select Graphic Rendition)参数)使用 CSI n1 [;n2 [; ...]] m 序列来处理,如上所示,序列中每一个 n1, n2, ...就是一个 SGR 参数。因此,例如,你使用 30 ~ 37 表示前景色, 40 ~ 47 表示背景色。下图是一张颜色过渡图。

    ###开始编码

    package util
    
    import (
    	"fmt"
    	"time"
    )
    
    const (
    	color_red = uint8(iota + 91)
    	color_green
    	color_yellow
    	color_blue
    	color_magenta //洋红
    
    	info = "[INFO]"
    	trac = "[TRAC]"
    	erro = "[ERRO]"
    	warn = "[WARN]"
    	succ = "[SUCC]"
    )
    
    // see complete color rules in document in https://en.wikipedia.org/wiki/ANSI_escape_code#cite_note-ecma48-13
    func Trace(format string, a ...interface{}) {
    	prefix := yellow(trac)
    	fmt.Println(formatLog(prefix), fmt.Sprintf(format, a...))
    }
    
    func Info(format string, a ...interface{}) {
    	prefix := blue(info)
    	fmt.Println(formatLog(prefix), fmt.Sprintf(format, a...))
    }
    
    func Success(format string, a ...interface{}) {
    	prefix := green(succ)
    	fmt.Println(formatLog(prefix), fmt.Sprintf(format, a...))
    }
    
    func Warning(format string, a ...interface{}) {
    	prefix := magenta(warn)
    	fmt.Println(formatLog(prefix), fmt.Sprintf(format, a...))
    }
    
    func Error(format string, a ...interface{}) {
    	prefix := red(erro)
    	fmt.Println(formatLog(prefix), fmt.Sprintf(format, a...))
    }
    
    func red(s string) string {
    	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", color_red, s)
    }
    
    func green(s string) string {
    	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", color_green, s)
    }
    
    func yellow(s string) string {
    	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", color_yellow, s)
    }
    
    func blue(s string) string {
    	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", color_blue, s)
    }
    
    func magenta(s string) string {
    	return fmt.Sprintf("\x1b[%dm%s\x1b[0m", color_magenta, s)
    }
    
    func formatLog(prefix string) string {
    	return time.Now().Format("2006/01/02 15:04:05") + " " + prefix + " "
    }
    

    上面就是一个完整的彩色 log 的代码 输出的效果:

    ##完整代码 完整的 colorlog 代码 测试代码 如果你需要在本地测试,请确保你搭建了正确的 go 开发环境,并且 down 下 https://github.com/liyu4/chill 这个项目。找到 util 单元

    yourpath 指的是你的项目路径。
    cd yourpath/chill/util
    go test -v
    

    ##后记 在其中 \x1b[ 实现 CSI :
    转换前景色为黑色,使用\x1b[30m
    转换为红色,使用\x1b[31m
    如果使用加粗参数,灰色写作\x1b[30;1m
    获取红色加粗,使用\x1b[31;1m
    重置颜色为缺省值,使用\x1b[39;49m (或者使用 \x1b[0m 重置所有属性) \033[0m 重置为正常
    \033[1m 设置高亮度或加粗
    \033[4m 下划线
    \033[5m 闪烁
    \033[7m 反显
    \033[8m 消隐
    \033[30m -- /33[37m 设置前景色
    \033[40m -- /33[47m 设置背景色 控制符 ESC 的常用表示方法\e 、\x1b(\x1B)、\033 都可以
    \e 指代 Escape ,对应八进制\033 ,对应十六进制\x1b

    ###参考文档

    ansi 转义序列 中文文档

    28 条回复    2018-01-08 17:20:26 +08:00
    zzWinD
        1
    zzWinD  
       2016-12-26 13:49:27 +08:00
    不错,支持一下。 go 貌似也有其他彩色的 log 库
    mark 下 以后学习~
    Shared
        2
    Shared  
       2016-12-26 14:00:39 +08:00
    用 `tput` 也可以哦,参看我的 dotfiles 安装脚本 https://github.com/Vayn/dotfiles/blob/master/install.sh#L9
    liyu4
        3
    liyu4  
    OP
       2016-12-26 14:12:44 +08:00
    @Shared 这是命令是兼容的吗?
    liyu4
        4
    liyu4  
    OP
       2016-12-26 14:13:16 +08:00
    @zzWinD 有问题可以找我,一起学习和加油!(可加微信)
    unity0703
        5
    unity0703  
       2016-12-26 14:15:42 +08:00
    fmt.Println 都输出到 stdout 了,我觉得你可以用 fmt.Fprintf ,在参数里指定一个输出设备,这样更加通用
    Shared
        6
    Shared  
       2016-12-26 14:17:31 +08:00
    @liyu4 是的, tput 的功能实际由 terminfo 和 termcap 提供
    liyu4
        7
    liyu4  
    OP
       2016-12-26 14:20:34 +08:00
    @unity0703 你的建议很好,谢谢啦!
    liyu4
        8
    liyu4  
    OP
       2016-12-26 14:21:05 +08:00
    @Shared 懂了,底层是 termcap 那兼容性各种终端应该不是什么问题了,学习了。
    urmyfaith
        9
    urmyfaith  
       2016-12-26 14:25:19 +08:00
    孤独的根号 3

    = = !
    liyu4
        10
    liyu4  
    OP
       2016-12-26 14:47:36 +08:00
    @urmyfaith 一辈子只做技术。
    tonic
        11
    tonic  
       2016-12-26 15:05:17 +08:00
    github.com/Siruspen/logrus 挺好用啊... 也是彩色的, 就是丑...
    liyu4
        12
    liyu4  
    OP
       2016-12-26 15:13:14 +08:00
    @tonic 如果你习惯了用这个,那么你可以直接改它里面的颜色那部分代码,改成你喜欢的样子。祝你五颜六色。
    q397064399
        13
    q397064399  
       2016-12-26 16:31:18 +08:00
    弱弱的问一句,有妹纸会喜欢你的终端告白么?
    tomwei7
        14
    tomwei7  
       2016-12-26 17:31:22 +08:00
    我也有个 golang 彩色的 log https://github.com/tomwei7/tlog 😂
    liyu4
        15
    liyu4  
    OP
       2016-12-26 17:39:54 +08:00
    @q397064399 开个玩笑的
    liyu4
        16
    liyu4  
    OP
       2016-12-26 17:40:27 +08:00
    @tomwei7 看了一下,感觉弄复杂了。原理其实都是一样的。
    warlock
        17
    warlock  
       2016-12-26 18:14:00 +08:00
    我也有个 golang 彩色的 log +1 https://github.com/dafengge0913/golog
    liyu4
        18
    liyu4  
    OP
       2016-12-26 18:52:17 +08:00
    @warlock 厉害的,一定是有女朋友的人。
    aiyo218
        19
    aiyo218  
       2016-12-26 19:39:25 +08:00
    好有爱~!
    liyu4
        20
    liyu4  
    OP
       2016-12-26 21:05:25 +08:00
    @aiyo218 谢谢喜欢!
    FreeDog
        21
    FreeDog  
       2016-12-26 21:21:10 +08:00
    还有的代码会闪光等等,当时用 for 循环把排列组合打印了一屏幕
    jerseyjerk
        22
    jerseyjerk  
       2016-12-27 06:41:46 +08:00
    很棒的作品。支持。但是有一些小瑕疵。
    彩色代码很大程序上依赖于 shell 的实现。所以兼容性不是特别好。
    另外,这个可以作为程序中需要用到 printf 的地方的替代品,但是将其命名为 log ,略微言过其实。先抛开 logger 的各种特性不表,通常并不倾向于在 log 中打印这些字符,因为解析日志的时候会徒增麻烦。
    liyu4
        23
    liyu4  
    OP
       2016-12-27 09:06:15 +08:00 via iPhone
    @jerseyjerk 是依赖于终端模拟器的实现,但是这是 ansi 标准的实现,所以对于多数的系统都是适用的,例外可能是 window 的控制台。不过在文档中提到了 window10 之后这个问题应该改善了。以上这段是基于理论说的,毕竟只在我的 mac 上测试过。后半段你提到的点很中肯,学习了。
    liyu4
        24
    liyu4  
    OP
       2016-12-27 09:07:50 +08:00 via iPhone
    @FreeDog 组合这些效果应该是可以实现的, cool !能把代码贴上来吗?
    FreeDog
        25
    FreeDog  
       2016-12-27 14:02:19 +08:00
    @liyu4

    代码很简单,就是 echo -e 之类的多层 for 循环,数字用 i 和 j 代替而已。不过需要在 Linux 真正的文本模式( Ctrl + Alt + 1 ~ 6 )下运行,虚拟终端 console 下很多效果显示不出来。貌似个别代码还有声音。
    liyu4
        27
    liyu4  
    OP
       2017-01-19 15:32:47 +08:00
    @FreeDog 厉害的
    eager7
        28
    eager7  
       2018-01-08 17:20:26 +08:00
    我修改了一下代码,楼主看看能否使用?
    const (
    colorRed = iota + 91
    colorGreen
    colorYellow
    colorBlue
    colorMagenta
    )

    var (
    debug = "\x1b[" + strconv.Itoa(colorBlue) + "m[DEBUG]\x1b[0m"
    info = "\x1b[" + strconv.Itoa(colorYellow) + "m[INFO]\x1b[0m"
    warn = "\x1b[" + strconv.Itoa(colorMagenta) + "m[WARN]\x1b[0m"
    Err = "\x1b[" + strconv.Itoa(colorRed) + "m[ERROR]\x1b[0m"
    success = "\x1b[" + strconv.Itoa(colorGreen) + "m[SUCCESS]\x1b[0m"
    )

    var Debug = Logger{log.New(os.Stdout, debug, log.LstdFlags)}
    var Info = Logger{log.New(os.Stdout, info, log.LstdFlags)}
    var Warn = Logger{log.New(os.Stdout, warn, log.LstdFlags)}
    var Error = Logger{log.New(os.Stdout, Err, log.LstdFlags)}
    var Success = Logger{log.New(os.Stdout, success, log.LstdFlags)}

    type Logger struct {
    *log.Logger
    }

    func (l *Logger) Disable() {
    l.SetOutput(ioutil.Discard)
    }

    func (l *Logger) Enable() {
    l.SetOutput(os.Stdout)
    }
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1085 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 23:13 · PVG 07:13 · LAX 16:13 · JFK 19:13
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.