Hlavní navigace

Jazyk Go a textový terminál ve funkci základního prvku uživatelského rozhraní

15. 7. 2021
Doba čtení: 25 minut

Sdílet

 Autor: Go lang
Jazyk Go se většinou nepoužívá pro tvorbu klasických desktopových aplikací. Je ovšem často využívaný pro tvorbu backendu webových aplikací. Setkáme se i s nástroji (například gotop), které využívají emulátor textového terminálu.

Obsah

1. Jazyk Go a textový terminál ve funkci základního prvku uživatelského rozhraní

2. Knihovny pro využití textového terminálu určené pro jazyk Go

3. Formátované a obarvené logy vytvářené s využitím knihovny zerolog

4. Tisk logovacích zpráv na standardní výstup s obarvením

5. Tisk logovacích zpráv na chybový výstup s obarvením

6. Větší množství atributů zobrazených v jedné logovací zprávě

7. Zobrazení informace o chybě

8. Podporované úrovně logovacích operací

9. Nastavení minimální úrovně logovacích operací

10. Logování bez specifikace úrovně

11. Naformátování zprávy

12. Binární data v logovací zprávě?

13. Specifikace barev textu na terminálu s knihovnou aurora

14. Povolení či zákaz obarveného výstupu specifikací přepínače na příkazové řádce

15. Dvě varianty specifikace stylu písma na terminálu

16. Změna barvy pozadí textu

17. Výstup ve zvolené úrovni šedi (grayscale)

18. Sada standardních 216 barev terminálu

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Jazyk Go a textový terminál ve funkci základního prvku uživatelského rozhraní

Již mnohokrát jsme se na stránkách Roota zabývali problematikou tvorby aplikací s plnohodnotným grafickým uživatelským rozhraním, které se konkrétně (resp. v největší míře) týkají článků o grafickém uživatelském rozhraní v Pythonu. Ovšem existuje relativně velké množství aplikací, popř. nástrojů či her, pro které může být vhodnější použít „pouhý“ textový terminál, resp. textovou konzoli. Příkladem mohou být aplikace, k nimž se přistupuje přes SSH, systémové nástroje apod. Takové aplikace dělíme do dvou skupin. Do první skupiny spadají interaktivně či neinteraktivně ovládané aplikace s příkazovým řádkem. Příkladem takových aplikací mohou být správci balíčků (dnf, apt-get, …), které s uživatelem interagují pouze několika otázkami typu Y/N. Dalším příkladem je nástroj fdisk se složitějším systémem menu, z nichž se však položka vybírá svým jménem (a nikoli například pohybem kurzoru).

dnf

Obrázek 1: Poměrně velké množství aplikací sice využívá rozšířených možností textových terminálů, ovšem stále se jedná spíše o neinteraktivní nástroje bez celoobrazovkového přístupu k terminálu. Dobrým příkladem takových typů aplikací je příkaz dnf, který může v některých případech zobrazit jednoduchou otázku a „interaktivně“ očekávat odpověď.

A do druhé skupiny aplikací určených pro běh v terminálu můžeme zařadit aplikace s plnohodnotným textovým uživatelským rozhraním neboli TUI (Text User Interface). Takových aplikací dnes existuje celá řada. Zmínit můžeme zejména celoobrazovkové textové editory (pochopitelně včetně Vimu a Emacsu), dále Midnight Commander, nástroj Aptitude, ale i některé hry používající celoobrazovkový textový režim (0verkill, Rogue, Nethack, Angband, DoomRL atd.). Částečně sem spadá i hra Dwarf Fortress, která by mohla běžet v terminálu, ale kvůli přenositelnosti používá vlastní engine založený na zobrazení textů.

DoomRL

Obrázek 2: DoomRL – úvodní ASCII art použitý v této hře je takřka dokonalý.

Textové uživatelské rozhraní je u některých aplikací volitelné. Dobrým příkladem takového typu aplikace je GNU Debugger, který buď pracuje v režimu příkazového řádku, nebo ho lze v případě potřeby přepnout do režimu s celoobrazovkovým textovým uživatelským rozhraním. I o tvorbě těchto aplikací jsme se ve stručnosti již zmínili v souvislosti s balíčky GNU Readline a především prompt_toolkit.

gnu gdb

Obrázek 3: Nástroj GNU Debugger po zobrazení textového uživatelského rozhraní (TUI) ve chvíli, kdy je nastaven breakpoint na vstupní bod (přesněji řečeno na první příkaz) ve funkci main. Na levém okraji se zobrazují jak breakpointy, tak i ukazatel na právě prováděný či krokovaný příkaz.

Poznámka: termín „celoobrazovkový“ nemá v kontextu dnešního článku takový význam, že by plocha aplikace pokryla celou plochu monitoru (jako například při přehrávání videa), ale že využije plochu terminálu. U reálných textových terminálů (pamatuje si je někdo?) se skutečně jedná o celou obrazovku, ovšem dnes se naprostá většina uživatelů setká pouze s emulátorem terminálu, který sám může být zobrazen v okně a neběží tedy (striktně řečeno) na celé obrazovce (ovšem i tohoto efektu je možné v případě potřeby dosáhnout).
Angband

Obrázek 4: Hra Angband s textovým uživatelským rozhraním ve verzi pro Linux – podrobnější charakteristiky hrdiny.

U obou dvou výše zmíněných typů aplikací je nutné umět do určité míry ovládat terminálový výstup, aby například bylo možné smazat znak/řádek, zvýraznit text změnou jeho stylu, barvy textu, barvy pozadí atd. A u aplikací s plnohodnotným textovým uživatelským rozhraním je pochopitelně nutné terminál ovládat do ještě větší míry, aby bylo možné pracovat s okny, menu, nápovědou a dalšími ovládacími prvky (widgety). Vzhledem k tomu, že se textové terminály vyvíjejí už velmi dlouho (minimálně od roku 1965, kdy vznikl první skutečný počítačový terminál IBM 2741 jako náhrada za dálnopisy) jsou rozdílné i jejich možnosti (vlastnosti) i způsob jejich ovládání (většinou s využitím řídicích kódů, dnes většinou založených na takzvaných escape sekvencích a několika dalších ASCII znacích).

ibm-4

Obrázek 5: Část sálového počítače IBM System/360 Model 40 (rok výroby 1964). V pozadí můžeme vidět jednotky s magnetickými pásky (model IBM 2401), ovládací panel je umístěn přímo za slečnou. A slečna sedí u dálnopisu, tedy přímého předchůdce počítačových terminálů.

Samozřejmě jsme mohli v průběhu předchozích desetiletí vidět snahu o sjednocení ovládání terminálů, která probíhala paralelně s vytvářením databáze obsahující vlastnosti terminálů (terminfo a termcap). Existují také knihovny, které programátora dokážou odizolovat od nízkoúrovňového přístupu k terminálům a jejich řízení s využitím řídicích kódů. Jedná se především o knihovnu curses a jejího následovníka ncurses (viz článek Psaní aplikací pro terminál: jak funguje knihovna ncurses, popř. o utility spouštěné z příkazového řádku (tput, tset, reset atd.).

Obrázek 6: Ovládací prvky vykreslené s využitím knihovny Blessed-contrib (JavaScript).
Zdroj: https://github.com/yaronn/blessed-contrib.

2. Knihovny pro využití textového terminálu určené pro jazyk Go

V dnešním článku i v navazujících článcích se postupně zaměříme na popis knihoven určených pro programovací jazyk Go. Tyto knihovny se od sebe budou odlišovat svým zaměřením a můžeme je zhruba rozdělit do pěti kategorií:

  1. Knihovny zlepšující logování a podporující barevné zvýraznění logovacích zpráv na terminálu. Sem spadá například dále popsaný balíčekzerolog.
  2. Knihovny pro podporu různých barev popředí a pozadí textu, změnu stylu textu atd. Příkladem může být balíček aurora, popř. cfmt, go-colortext apod.
  3. Knihovny pro zobrazení jednoduchých textových dialogů a boxů se zprávami. Tuto problematiku řeší balíček box-cli-maker.
  4. Knihovny pro tvorbu grafů, „teploměrů“ atd. na ploše terminálu. Příkladem je balíček asciigraph.
  5. Knihovny určené pro tvorbu plnohodnotných textových uživatelských rozhraní (TUI). Příkladem mohou být balíčky termui, termbox-go, progressbar atd.

Obrázek 7: Jednou z terminálových aplikací naprogramovaných v Go je utilitka gotop.
Zdroj: https://github.com/xxxserxxx/gotop.

Poznámka: dnes použité demonstrační příklady byly spuštěny v xtermu s nastavením světlého pozadí. Použité barvy se (podle očekávání) budou lišit ve chvíli, kdy bude použito tmavé pozadí, popřípadě odlišný emulátor terminálu – to je cena, kterou je nutné zaplatit (mj.) i za zpětnou kompatibilitu.

Obrázek 8: Spuštění debuggeru, inicializace laděné aplikace, nastavení breakpointu a doskok na breakpoint.

3. Formátované a obarvené logy vytvářené s využitím knihovny zerolog

Prvním balíčkem, s nímž se v dnešním článku setkáme, je balíček nazvaný zerolog, přesněji řečeno plným jménem Zero Allocation JSON Logger. Tento balíček rozšiřuje možnosti standardního logovacího balíčku log o mnohé další vlastnosti, zejména o výstup do snadno parsovatelného formátu JSON, popř. alternativně o výstup se zvýrazněním jednotlivých zpráv s využitím barev. Druhý způsob se používá při vývoji a ladění aplikací, první pak může být použit při reálném nasazení aplikace.

Podívejme se nyní na nejjednodušší použití tohoto balíčku, z něhož bude patrné, jakým způsobem se jednotlivé zprávy vypisované do logu tvoří. Používá se zde jednotný zápis:

log.LogLevel().TypAtributu(jméno, hodnota).Msg(zpráva)

přičemž atributů může být uvedeno prakticky libovolné množství. V příkladu je logLevel nastaven na Info, což je třetí úroveň z celkem sedmi definovaných úrovní.

Atributy se vždy skládají z dvojice jméno (řetězec) a hodnota, přičemž hodnota musí odpovídat typu atributu – řetězec, celé číslo, pravdivostní hodnota, slovník atd.:

package main
 
import (
        "github.com/rs/zerolog/log"
)
 
const Address = "localhost"
const Port = 8080
const Enabled = false
 
func main() {
        log.Info().Msg("Started")
 
        log.Info().Str("address", Address).Msg("Server address")
        log.Info().Int("port", Port).Msg("Server port")
        log.Info().Bool("enabled", Enabled).Msg("Server enabled")
 
        log.Info().Msg("Finished")
}

Výsledkem by měly být zprávy ve formátu JSONu:

Obrázek 9: Zprávy zobrazené ve formátu JSON.

4. Tisk logovacích zpráv na standardní výstup s obarvením

Knihovna zerolog umožňuje specifikaci výstupního proudu pro logovací zprávy. V případě, že se explicitně provede přesměrování do standardního výstupu, předpokládá se využití inteligentního terminálu a namísto JSON formátu jsou zprávy vypsány s barevným zvýrazněním:

log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout})

Úplný zdrojový kód dnešního druhého demonstračního příkladu:

package main
 
import (
        "os"
 
        "github.com/rs/zerolog"
        "github.com/rs/zerolog/log"
)
 
const Address = "localhost"
const Port = 8080
const Enabled = false
 
func main() {
        log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout})
        log.Info().Msg("Started")
 
        log.Info().Str("address", Address).Msg("Server address")
        log.Info().Int("port", Port).Msg("Server port")
        log.Info().Bool("enabled", Enabled).Msg("Server enabled")
 
        log.Info().Msg("Finished")
}

Výsledkem by měly být zprávy s barevným zvýrazněním:

Obrázek 10: Zprávy  barevným zvýrazněním vypsané na standardní výstup na inteligentním terminálu.

5. Tisk logovacích zpráv na chybový výstup s obarvením

Naprosto stejným postupem lze ovšem zajistit výstup na chybový výstup, který je opět proveden s barevným zvýrazněním:

log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})

Pro jistotu si opět zobrazme úplný zdrojový kód tohoto demonstračního příkladu:

package main
 
import (
        "os"
 
        "github.com/rs/zerolog"
        "github.com/rs/zerolog/log"
)
 
const Address = "localhost"
const Port = 8080
const Enabled = false
 
func main() {
        log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
        log.Info().Msg("Started")
 
        log.Info().Str("address", Address).Msg("Server address")
        log.Info().Int("port", Port).Msg("Server port")
        log.Info().Bool("enabled", Enabled).Msg("Server enabled")
 
        log.Info().Msg("Finished")
}

Výsledek:

Obrázek 11: Zprávy  barevným zvýrazněním vypsané na chybový výstup na inteligentním terminálu.

6. Větší množství atributů zobrazených v jedné logovací zprávě

Velmi často je nutné v jedné logovací zprávě zobrazit větší množství atributů. I to pochopitelně knihovna zerolog umožňuje, pouze si musíme dát pozor na to, aby samotná zpráva (Msg) byla poslední volanou metodou:

log.Info().
        Str("address", Address).
        Int("port", Port).
        Bool("enabled", Enabled).
        Msg("Server settings")
Poznámka: formátování je standardní podle specifikace samotného jazyka Go a nástroje go fmt.

Úplný zdrojový kód takto upraveného demonstračního příkladu:

package main
 
import (
        "os"
 
        "github.com/rs/zerolog"
        "github.com/rs/zerolog/log"
)
 
const Address = "localhost"
const Port = 8080
const Enabled = false
 
func main() {
        log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
        log.Info().Msg("Started")
 
        log.Info().
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).
                Msg("Server settings")
 
        log.Info().Msg("Finished")
}

Výsledek:

Obrázek 12: Logovací zpráva s větším množstvím atributů.

7. Zobrazení informace o chybě

Pro výpis informací o detekované chybě se používá tento zápis:

log.LogLevel().Err(hodnota typu error).TypAtributu(jméno, hodnota).Msg(zpráva)

Typicky se používá úroveň logů Error nebo Fatal, takže:

log.LogError().Err(hodnota typu error).TypAtributu(jméno, hodnota).Msg(zpráva)

popř.:

log.LogFatal().Err(hodnota typu error).TypAtributu(jméno, hodnota).Msg(zpráva)

nebo dokonce:

log.LogPanic().Err(hodnota typu error).TypAtributu(jméno, hodnota).Msg(zpráva)

V následujícím demonstračním příkladu je chyba testována (a popř. vypsána do logů) při pokusu o spuštění HTTP serveru:

package main
 
import (
        "fmt"
        "io"
        "os"
 
        "net/http"
 
        "github.com/rs/zerolog"
        "github.com/rs/zerolog/log"
)
 
const Address = "localhost"
const Port = 8080
const Enabled = true
 
func main() {
        log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
        log.Info().Msg("Started")
 
        log.Info().
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        if Enabled {
                http.HandleFunc("/", handler)
                where := fmt.Sprintf("%s:%d", Address, Port)
                err := http.ListenAndServe(where, nil)
                if err != nil {
                        log.Error().Err(err).Msg("Initialize server")
                }
        }
        log.Info().Msg("Finished")
}
 
func handler(writer http.ResponseWriter, request *http.Request) {
        io.WriteString(writer, "Hello world!")
}

Pokud se nyní pokusíme příklad spustit dvakrát, dojde k chybě, protože první instance serveru si zabere specifikovaný port 8080:

Obrázek 13: Zobrazení informace o chybě.

8. Podporované úrovně logovacích operací

Knihovna zerolog podporuje celkem sedm úrovní logovacích operací, které jsou pojmenovány následovně (což je kompatibilní i s mnoha dalšími podobně koncipovanými knihovnami):

panic (zerolog.PanicLevel, 5)
fatal (zerolog.FatalLevel, 4)
error (zerolog.ErrorLevel, 3)
warn (zerolog.WarnLevel, 2)
info (zerolog.InfoLevel, 1)
debug (zerolog.DebugLevel, 0)
trace (zerolog.TraceLevel, -1)

Prozatím jsme používali úroveň Strong, ale nikdo nám samozřejmě nezabraňuje využít další úrovně, z nichž každá je určena volanou funkcí z knihovny zerolog. Tento přístup je ukázán v dalším demonstračním příkladu:

package main
 
import (
        "os"
 
        "github.com/rs/zerolog"
        "github.com/rs/zerolog/log"
)
 
const Address = "localhost"
const Port = 8080
const Enabled = true
 
func main() {
        log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
        log.Info().Msg("Started")
 
        log.Trace().
                Str("level", "trace").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Debug().
                Str("level", "debug").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Info().
                Str("level", "info").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Warn().
                Str("level", "warn").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Error().
                Str("level", "error").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Fatal().
                Str("level", "fatal").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Panic().
                Str("level", "panic").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Info().Msg("Finished")
}

Výsledek může vypadat následovně:

Obrázek 14: Zobrazení logovacích informací s různou úrovní.

9. Nastavení minimální úrovně logovacích operací

Minimální úroveň logovacích informací bude odlišná pro vývoj (kdy budeme vyžadovat podrobnější informace) a jiná pro produkční nasazení. Pro specifikaci minimální úrovně, která se ještě zobrazuje, slouží funkce SetGlobalLevel:

zerolog.SetGlobalLevel(zerolog.InfoLevel)

Příklad použití (uvedený jen pro úplnost):

package main
 
import (
        "os"
 
        "github.com/rs/zerolog"
        "github.com/rs/zerolog/log"
)
 
const Address = "localhost"
const Port = 8080
const Enabled = true
 
func main() {
        zerolog.SetGlobalLevel(zerolog.InfoLevel)
 
        log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
        log.Info().Msg("Started")
 
        log.Trace().
                Str("level", "trace").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Debug().
                Str("level", "debug").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Info().
                Str("level", "info").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Warn().
                Str("level", "warn").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Error().
                Str("level", "error").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Fatal().
                Str("level", "fatal").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Panic().
                Str("level", "panic").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Info().Msg("Finished")
}

10. Logování bez specifikace úrovně

V případě, že se u jednodušších aplikací nechcete rozhodovat, jaké logovací úrovně zvolit, lze celou situaci zjednodušit a úrovně vůbec nepoužívat. V tomto případě se pouze použije funkce Log namísto Info, Error, Fatal atd.:

log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})

Opět si ukažme úplný zdrojový kód takto upraveného demonstračního příkladu:

package main
 
import (
        "os"
 
        "github.com/rs/zerolog"
        "github.com/rs/zerolog/log"
)
 
const Address = "localhost"
const Port = 8080
const Enabled = true
 
func main() {
        zerolog.SetGlobalLevel(zerolog.InfoLevel)
 
        log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
        log.Info().Msg("Started")
 
        log.Log().
                Str("level", "trace").
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Info().Msg("Finished")
}

S výsledkem:

Obrázek 15: Zpráva vypsaná funkcí Log.

11. Naformátování zprávy

Prozatím jsme při logování zpráv měli možnost specifikovat pouze hodnoty atributů, kdežto samotná zpráva byla reprezentována řetězcem:

log.LogLevel().TypAtributu(jméno, hodnota).Msg(zpráva)

Tento řetězec lze samozřejmě zkonstruovat různými způsoby (konkatenací, s využitím fmt.Sprintf atd.), ovšem tyto alternativy je možné nahradit za metodu Msgf, což je spojení možností metody Msg (bez „f“ na konci) a standardní funkce fmt.Spritnf. Použití je zcela idiomatické:

log.LogLevel().TypAtributu(jméno, hodnota).Msg(formátovací řetězec, parametry, ...)

Podívejme se na praktické použití, konkrétně v tomto příkazu, v němž budeme chtít zobrazit adresu a port serveru:

err := http.ListenAndServe(where, nil)
if err != nil {
        log.Error().Err(err).Msgf("Initialize server on address %s:%d", Address, Port)
}

Nyní upravíme demonstrační příklad s HTTP servem tak, aby se v něm použila metoda Msgf:

package main
 
import (
        "fmt"
        "io"
        "os"
 
        "net/http"
 
        "github.com/rs/zerolog"
        "github.com/rs/zerolog/log"
)
 
const Address = "localhost"
const Port = 8080
const Enabled = true
 
func main() {
        log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
        log.Info().Msg("Started")
 
        log.Info().
                Str("address", Address).
                Int("port", Port).
                Bool("enabled", Enabled).
                Msg("Server settings")
 
        if Enabled {
                http.HandleFunc("/", handler)
                where := fmt.Sprintf("%s:%d", Address, Port)
                err := http.ListenAndServe(where, nil)
                if err != nil {
                        log.Error().Err(err).Msgf("Initialize server on address %s:%d", Address, Port)
                }
        }
        log.Info().Msg("Finished")
}
 
func handler(writer http.ResponseWriter, request *http.Request) {
        io.WriteString(writer, "Hello world!")
}

S tímto výsledkem (pochopitelně v případě chyby):

Obrázek 16: Zpráva naformátovaná metodou Msgf.

12. Binární data v logovací zprávě?

Do logovací zprávy není vhodné posílat přímo binární data, protože některé kombinace bajtů mohou změnit chování terminálu. Namísto toho se jako jedno z možných řešení nabízí výstup binárních dat v přece jen čitelnějším hexadecimálním formátu, který je knihovnou zerolog taktéž podporován:

var rawData []byte = []byte("ěščřžýáíé")
 
log.Info().
        Str("level", "info").
        Str("address", Address).
        Int("port", Port).
        Hex("raw data", rawData).
        Bool("enabled", Enabled).Msg("Server settings")

Tento výstup/formát je pochopitelně podporován pro všechny úrovně logovacích zpráv:

package main
 
import (
        "os"
 
        "github.com/rs/zerolog"
        "github.com/rs/zerolog/log"
)
 
const Address = "localhost"
const Port = 8080
const Enabled = true
 
func main() {
        var rawData []byte = []byte("ěščřžýáíé")
 
        zerolog.SetGlobalLevel(zerolog.InfoLevel)
        log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
 
        log.Info().Msg("Started")
 
        log.Trace().
                Str("level", "trace").
                Str("address", Address).
                Int("port", Port).
                Hex("raw data", rawData).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Debug().
                Str("level", "debug").
                Str("address", Address).
                Int("port", Port).
                Hex("raw data", rawData).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Info().
                Str("level", "info").
                Str("address", Address).
                Int("port", Port).
                Hex("raw data", rawData).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Warn().
                Str("level", "warn").
                Str("address", Address).
                Int("port", Port).
                Hex("raw data", rawData).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Error().
                Str("level", "error").
                Str("address", Address).
                Int("port", Port).
                Hex("raw data", rawData).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Fatal().
                Str("level", "fatal").
                Str("address", Address).
                Int("port", Port).
                Hex("raw data", rawData).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Panic().
                Str("level", "panic").
                Str("address", Address).
                Int("port", Port).
                Hex("raw data", rawData).
                Bool("enabled", Enabled).Msg("Server settings")
 
        log.Info().Msg("Finished")
}

Obrázek 17: Zprávy s binárními daty reprezentovanými v hexadecimálním formátu.

13. Specifikace barev textu na terminálu s knihovnou aurora

U některých aplikací je nutné přímo zvýraznit barvu určitého textu, důležité hodnoty atd. K tomuto účelu se ovšem logovací knihovna zerolog příliš nehodí. Můžeme ovšem namísto ní použít knihovny cfmt či aurora. Ve druhé části dnešního článku si ukážeme některé možnosti poskytované právě knihovnou aurora.

Nejprve se podívejme na způsob inicializace této knihovny a výpis textu v šesti základních barvách (posledními dvěma barvami jsou bílá a černá, ovšem v závislosti na nastavení pozadí terminálu se někdy jejich role prohazují):

package main
 
import (
        "fmt"
 
        "github.com/logrusorgru/aurora"
)
 
func main() {
        // konstrukce "obarvovacího" objektu
        var colorizer aurora.Aurora
        colorizer = aurora.NewAurora(true)
 
        // výpis textu šesti základními barvami
        fmt.Println(colorizer.Red("Test"))
        fmt.Println(colorizer.Green("Test"))
        fmt.Println(colorizer.Blue("Test"))
        fmt.Println(colorizer.Cyan("Test"))
        fmt.Println(colorizer.Magenta("Test"))
        fmt.Println(colorizer.Yellow("Test"))
}

Výsledek:

Obrázek 18: Text obarvený knihovnou aurora.

Obarvení textu lze ovšem zakázat, a to již v konstruktoru:

package main
 
import (
        "fmt"
 
        "github.com/logrusorgru/aurora"
)
 
func main() {
        var colorizer aurora.Aurora
        colorizer = aurora.NewAurora(false)
 
        fmt.Println(colorizer.Red("Test"))
        fmt.Println(colorizer.Green("Test"))
        fmt.Println(colorizer.Blue("Test"))
        fmt.Println(colorizer.Cyan("Test"))
        fmt.Println(colorizer.Magenta("Test"))
        fmt.Println(colorizer.Yellow("Test"))
}

Výsledek:

Obrázek 19: Text vypsaný knihovnou aurora, nyní ovšem bez obarvení.

14. Povolení či zákaz obarveného výstupu specifikací přepínače na příkazové řádce

Slušně napsané aplikace umožňují obarvení povolit či zakázat vhodným přepínačem zadávaným na příkazové řádce. Tuto techniku lze implementovat velmi snadno, což je ostatně ukázáno i v dalším demonstračním příkladu. Využita je zde standardní knihovna flag, kterou jsme si již v seriálu o jazyce Go popsali:

package main
 
import (
        "flag"
        "fmt"
 
        "github.com/logrusorgru/aurora"
)
 
var colorizer aurora.Aurora
 
func init() {
        var colors = flag.Bool("colors", false, "enable or disable colors")
        flag.Parse()
 
        colorizer = aurora.NewAurora(*colors)
}
 
func main() {
        fmt.Println(colorizer.Red("Test"))
        fmt.Println(colorizer.Green("Test"))
        fmt.Println(colorizer.Blue("Test"))
        fmt.Println(colorizer.Cyan("Test"))
        fmt.Println(colorizer.Magenta("Test"))
        fmt.Println(colorizer.Yellow("Test"))
}

Výsledky při nepoužití a použití přepínače –colors:

Obrázek 20: Aplikace spuštěná bez přepínače –colors i s přepínačem –colors.

15. Dvě varianty specifikace stylu písma na terminálu

Knihovna aurora umožňuje specifikaci stylu písma – tučné, kurzíva, podtržené, blikající, přeškrtnuté atd. – ovšem prakticky žádný emulátor terminálu nedokáže všechny zmíněné styly zobrazit. Relativně nejvíce podporované je tučné písmo, které se zapíná takto:

colorizer.Bold(text, který má být zobrazen tučně)

Možná je i kombinace tohoto příkazu se specifikací barvy, například:

colorizer.Bold(colorizer.Red("Test"))

Alternativní způsob zápisu vypadá takto:

colorizer.Red("Test").Bold()

V dalším demonstračním příkladu se nejprve zobrazí text v šesti základních barvách a posléze tentýž text, ovšem tučně:

package main
 
import (
        "flag"
        "fmt"
 
        "github.com/logrusorgru/aurora"
)
 
var colorizer aurora.Aurora
 
func init() {
        var colors = flag.Bool("colors", false, "enable or disable colors")
        flag.Parse()
 
        colorizer = aurora.NewAurora(*colors)
}
 
func main() {
        fmt.Println(colorizer.Red("Test"))
        fmt.Println(colorizer.Green("Test"))
        fmt.Println(colorizer.Blue("Test"))
        fmt.Println(colorizer.Cyan("Test"))
        fmt.Println(colorizer.Magenta("Test"))
        fmt.Println(colorizer.Yellow("Test"))
 
        fmt.Println()
 
        fmt.Println(colorizer.Bold(colorizer.Red("Test")))
        fmt.Println(colorizer.Bold(colorizer.Green("Test")))
        fmt.Println(colorizer.Bold(colorizer.Blue("Test")))
        fmt.Println(colorizer.Bold(colorizer.Cyan("Test")))
        fmt.Println(colorizer.Bold(colorizer.Magenta("Test")))
        fmt.Println(colorizer.Bold(colorizer.Yellow("Test")))
}

Obrázek 21: Kombinace tučného písma se specifikací barvy.

Použít je pochopitelně možné i alternativní zápis tučného písma:

package main
 
import (
        "flag"
        "fmt"
 
        "github.com/logrusorgru/aurora"
)
 
var colorizer aurora.Aurora
 
func init() {
        var colors = flag.Bool("colors", false, "enable or disable colors")
        flag.Parse()
 
        colorizer = aurora.NewAurora(*colors)
}
 
func main() {
        fmt.Println(colorizer.Red("Test"))
        fmt.Println(colorizer.Green("Test"))
        fmt.Println(colorizer.Blue("Test"))
        fmt.Println(colorizer.Cyan("Test"))
        fmt.Println(colorizer.Magenta("Test"))
        fmt.Println(colorizer.Yellow("Test"))
 
        fmt.Println()
 
        fmt.Println(colorizer.Red("Test").Bold())
        fmt.Println(colorizer.Green("Test").Bold())
        fmt.Println(colorizer.Blue("Test").Bold())
        fmt.Println(colorizer.Cyan("Test").Bold())
        fmt.Println(colorizer.Magenta("Test").Bold())
        fmt.Println(colorizer.Yellow("Test").Bold())
}

Obrázek 22: Opět kombinace tučného písma se specifikací barvy.

16. Změna barvy pozadí textu

Podobně jako barvu textu lze měnit i barvu pozadí, ovšem s tím, že některé terminály povolují použít pouze osm barev pozadí (a nikoli šestnáct) – je nutné otestovat v praxi. Pro specifikaci barvy pozadí se používají funkce BgJménoBarvy, například BgRed, BgGreen atd. Ostatně se podívejme na další demonstrační příklad s ukázkou:

package main
 
import (
        "flag"
        "fmt"
 
        "github.com/logrusorgru/aurora"
)
 
var colorizer aurora.Aurora
 
func init() {
        var colors = flag.Bool("colors", false, "enable or disable colors")
        flag.Parse()
 
        colorizer = aurora.NewAurora(*colors)
}
 
func main() {
        fmt.Println(colorizer.BgRed("Test"))
        fmt.Println(colorizer.BgGreen("Test"))
        fmt.Println(colorizer.BgBlue("Test"))
        fmt.Println(colorizer.BgCyan("Test"))
        fmt.Println(colorizer.BgMagenta("Test"))
        fmt.Println(colorizer.BgYellow("Test"))
 
        fmt.Println()
 
        fmt.Println(colorizer.BgRed("Test").Bold())
        fmt.Println(colorizer.BgGreen("Test").Bold())
        fmt.Println(colorizer.BgBlue("Test").Bold())
        fmt.Println(colorizer.BgCyan("Test").Bold())
        fmt.Println(colorizer.BgMagenta("Test").Bold())
        fmt.Println(colorizer.BgYellow("Test").Bold())
}

S výsledkem:

Obrázek 23: Šest základních barev pozadí i s alternativní formou využívající tučný text.

Poznámka: barva popředí, například „yellow“ se na konkrétním terminálu může lišit od stejně pojmenované barvy pozadí!

17. Výstup ve zvolené úrovni šedi (grayscale)

Většina terminálů podporuje kromě osmi základních barev i jejich světlejší varianty (viz též tento přehledový článek). Navíc jsou u většiny terminálů podporovány i stupně šedi, kterých je 24 (kódy těchto barev jsou v rozmezí 232 až 255). V dalším příkladu je ukázáno, jak se používá metoda Gray pro specifikaci úrovně šedi textu (tedy popředí):

package main
 
import (
        "flag"
        "fmt"
 
        "github.com/logrusorgru/aurora"
)
 
var colorizer aurora.Aurora
 
func init() {
        var colors = flag.Bool("colors", false, "enable or disable colors")
        flag.Parse()
 
        colorizer = aurora.NewAurora(*colors)
}
 
func main() {
        for i := uint8(0); i < 24; i++ {
                message := fmt.Sprintf("Grayscale %d", i)
                fmt.Println(colorizer.Gray(i, message))
        }
}

Obrázek 24: Text v 24 úrovních šedi.

Prakticky stejným způsobem, ovšem s využitím metody BgGray, se určí jeden z 24 úrovní šedi pro pozadí textu:

package main
 
import (
        "flag"
        "fmt"
 
        "github.com/logrusorgru/aurora"
)
 
var colorizer aurora.Aurora
 
func init() {
        var colors = flag.Bool("colors", false, "enable or disable colors")
        flag.Parse()
 
        colorizer = aurora.NewAurora(*colors)
}
 
func main() {
        for i := uint8(0); i < 24; i++ {
                message := fmt.Sprintf("Grayscale %d", i)
                fmt.Println(colorizer.BgGray(i, message))
        }
}

Obrázek 25: Text v 24 úrovních šedi pro pozadí.

Barvu pozadí i popředí lze pochopitelně kombinovat, což platí i pro úrovně šedi. Povšimněte si triku potřebného pro současné nastavení barvy popředí i barvy pozadí:

package main
 
import (
        "flag"
        "fmt"
 
        "github.com/logrusorgru/aurora"
)
 
var colorizer aurora.Aurora
 
func init() {
        var colors = flag.Bool("colors", false, "enable or disable colors")
        flag.Parse()
 
        colorizer = aurora.NewAurora(*colors)
}
 
func main() {
        for j := uint8(24); j > 0; j-- {
                for i := uint8(0); i < 24; i += 2 {
                        message := fmt.Sprintf(" [%2d %2d] ", i, j)
                        fmt.Print(colorizer.BgGray(j, colorizer.Gray(i, message)))
                }
                fmt.Println()
        }
}

Výsledek by měl vypadat následovně:

Obrázek 26: Kombinace barvy popředí a pozadí.

18. Sada standardních 216 barev terminálu

Z de-facto standardní barvové palety textových terminálů nám zbývá 256–16–24=216 barev, které lze vybrat pro popředí metodou Index a pro pozadí metodou BgIndex. Opět jsou většinou umožněny i kombinace těchto barev, což využijeme v dnes již posledním demonstračním příkladu:

package main
 
import (
        "flag"
        "fmt"
 
        "github.com/logrusorgru/aurora"
)
 
var colorizer aurora.Aurora
 
func init() {
        var colors = flag.Bool("colors", false, "enable or disable colors")
        flag.Parse()
 
        colorizer = aurora.NewAurora(*colors)
}
 
func main() {
        for j := uint8(16); j < 231; j += 4 {
                for i := uint8(16); i < 231; i += 2 {
                        fmt.Print(colorizer.Index(i, colorizer.BgIndex(j, "x")))
                }
                fmt.Println()
        }
}

Výsledek by měl vypadat následovně:

MIF temata

Obrázek 27: Kombinace barvy popředí a pozadí.

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně stovku kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:

# Příklad/soubor Stručný popis Cesta
1 01-basic-usage základní použití knihovny zerolog, výstup do formátu JSON https://github.com/tisnik/go-root/blob/master/article76/zerolog/01-basic-usage
2 02-output-to-stdout tisk logovacích zpráv na standardní výstup s obarvením https://github.com/tisnik/go-root/blob/master/article76/zerolog/02-output-to-stdout
3 03-output-to-stderr tisk logovacích zpráv na chybový výstup s obarvením https://github.com/tisnik/go-root/blob/master/article76/zerolog/03-output-to-stderr
4 04-chaining větší množství atributů zobrazených v jedné logovací zprávě https://github.com/tisnik/go-root/blob/master/article76/zerolog/04-chaining
5 05-errors zobrazení informace o chybě https://github.com/tisnik/go-root/blob/master/article76/zerolog/05-errors
6 06-levels podporované úrovně logovacích operací https://github.com/tisnik/go-root/blob/master/article76/zerolog/06-levels
7 07-set-level nastavení minimální úrovně logovacích operací https://github.com/tisnik/go-root/blob/master/article76/zerolog/07-set-level
8 08-no-level logování bez specifikace úrovně https://github.com/tisnik/go-root/blob/master/article76/zerolog/08-no-level
9 09-format-message naformátování zprávy https://github.com/tisnik/go-root/blob/master/article76/zerolog/09-format-message
10 10-different-output změna výstupního formátu zprávy, binární data https://github.com/tisnik/go-root/blob/master/article76/zerolog/10-different-output
       
11 01-basic-usage základní způsob použití knihovny Aurora https://github.com/tisnik/go-root/blob/master/article76/aurora/01-basic-usage
12 02-disable-colors zákaz barevného výstupu https://github.com/tisnik/go-root/blob/master/article76/aurora/02-disable-colors
13 03-colors-flag povolení či zákaz obarveného výstupu specifikací přepínače na příkazové řádce https://github.com/tisnik/go-root/blob/master/article76/aurora/03-colors-flag
14 04-bold-attribute specifikace stylu písma na terminálu https://github.com/tisnik/go-root/blob/master/article76/aurora/04-bold-attribute
15 05-bold-attribute-B specifikace stylu písma na terminálu https://github.com/tisnik/go-root/blob/master/article76/aurora/05-bold-attribute-B
16 06-background-colors změna barvy pozadí textu https://github.com/tisnik/go-root/blob/master/article76/aurora/06-background-colors
17 07-grayscale-foreground výstup ve zvolené úrovni šedi (grayscale) https://github.com/tisnik/go-root/blob/master/article76/aurora/07-grayscale-foreground
18 08-grayscale-background pozadí ve zvolené úrovni šedi (grayscale) https://github.com/tisnik/go-root/blob/master/article76/aurora/08-grayscale-background
19 09-grayscale-fg-and-bg změna úrovně šedi pozadí i popředí textu https://github.com/tisnik/go-root/blob/master/article76/aurora/09-grayscale-fg-and-bg
20 10–8-bit-colors sada standardních 216 barev terminálu https://github.com/tisnik/go-root/blob/master/article76/aurora/10–8-bit-colors

20. Odkazy na Internetu

  1. Tvorba aplikací a her s textovým uživatelským rozhraním s využitím knihovny Blessed
    https://www.root.cz/clanky/tvorba-aplikaci-a-her-s-textovym-uzivatelskym-rozhranim-s-vyuzitim-knihovny-blessed/
  2. Tvorba aplikací a her s textovým rozhraním s knihovnou Blessed (dokončení)
    https://www.root.cz/clanky/tvorba-aplikaci-a-her-s-textovym-rozhranim-s-knihovnou-blessed-dokonceni/
  3. ANSI Escape Code – Colors
    https://en.wikipedia.org/wi­ki/ANSI_escape_code#Colors
  4. A curated list of awesome Go frameworks, libraries and software
    https://awesome-go.com/
  5. Aurora
    https://github.com/logrusorgru/aurora
  6. colourize
    https://github.com/TreyBas­tian/colourize
  7. go-colortext
    https://github.com/daviddengcn/go-colortext
  8. blessed na PyPi
    https://pypi.org/project/blessed/
  9. blessed na GitHubu
    https://github.com/jquast/blessed
  10. Blessed documentation!
    https://blessed.readthedoc­s.io/en/latest/
  11. termbox-go na GitHubu
    https://github.com/nsf/termbox-go
  12. termui na GitHubu
    https://github.com/gizak/termui
  13. blessed na GitHubu
    https://github.com/chjj/blessed
  14. blessed-contrib na GitHubu
    https://github.com/yaronn/blessed-contrib
  15. tui-rs na GitHubu
    https://github.com/fdehau/tui-rs
  16. asciigraph
    https://github.com/guptaro­hit/asciigraph
  17. Standardní balíček text/tabwriter
    https://golang.org/pkg/tex­t/tabwriter/
  18. Elastic tabstops: A better way to indent and align code
    https://nickgravgaard.com/elastic-tabstops/
  19. ASCII Table Writer
    https://github.com/olekukon­ko/tablewriter
  20. TablePrinter
    https://github.com/lensesi­o/tableprinter
  21. go-pretty
    https://github.com/jedib0t/go-pretty
  22. What are the drawbacks of elastic tabstops?
    https://softwareengineerin­g.stackexchange.com/questi­ons/137290/what-are-the-drawbacks-of-elastic-tabstops
  23. Elastic tabstop editors and plugins
    https://stackoverflow.com/qu­estions/28652/elastic-tabstop-editors-and-plugins
  24. Příkaz gofmt
    https://golang.org/cmd/gofmt/
  25. Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
    https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/
  26. zerolog
    https://github.com/rs/zerolog
  27. Zero Allocation JSON Logger na Go.doc
    https://pkg.go.dev/github­.com/rs/zerolog?utm_source=go­doc
  28. cfmt
    https://github.com/mingrammer/cfmt
  29. box-cli-maker
    https://github.com/Delta456/box-cli-maker
  30. Who uses zerolog
    https://github.com/rs/zerolog/wiki/Who-uses-zerolog