Tô Màu Cho Chữ Trên Terminal Với Colorama Python

Tô Màu Cho Chữ Trên Terminal Với Colorama Python

Bạn thực sự thấy nhàm chán khi làm việc với màn hình đen chữ trắng của terminal? Bạn muốn có thay đổi về màu chữ, muốn "đỏ" chỗ cần chú ý, muốn "tím" chỗ sắp gây ra problem ?

Vấn đề đặt ra

Một phần công việc mà tôi đã từng làm là viết các script bằng Python hỗ trợ các đồng nghiệp IT-OPs (IT vận hành) và QA rà soát log files từ hệ thống để phát hiện các vấn đề phát sinh mà hệ thống giám sát online chưa phát hiện ra được (phân tích dữ liệu thô để phát hiện ra các "problem" chưa được định nghĩa).

Vì một số lý do đặc biệt mà tôi chỉ được phép cung cấp cho các đồng nghiệp công cụ dạng file script và mỗi lần cần lấy dữ liệu là các đồng nghiệp sẽ gõ lệnh python script_xxx.py trên màn hình terminal, kết quả thì vừa hiển thị trên terminal vừa xuất ra các định dạng file txt hoặc excel,...

Thi thoảng tôi lại đến ngồi cùng các đồng nghiệp để cùng trải nghiệm sản phẩm mà các đồng nghiệp gọi là "méo mó có hơn không". Mọi người thường kêu gào với tôi về việc màn hình nhàm chán, đơn điệu, không có điểm nhấn, yêu cầu tôi sửa lại cho dễ dùng hơn. Những lúc ấy tôi chỉ cười và động viên mọi người chịu khó mở file kết quả để xem cho chi tiết còn output ra màn hình chỉ để hỗ trợ thông báo là đã xử lý xong thôi.

Cho đến một ngày, tôi nhận được một user-story trực tiếp từ quản lý:

"Màn hình báo cáo tra soát từ log-files của anh cung cấp cho users thuộc bộ phận IT và QA đơn điệu quá, mỗi lần muốn tra cứu nhanh thông tin kết quả từ màn hình terminal là users muốn toét cả mắt hoặc phải copy toàn bộ dữ liệu sang text-editor để tìm kiếm theo từ khóa Major, Cirtica, Fatal. Users muốn có màn hình thông tin thân thiện hơn, thay đổi màu sắc ở những chỗ có cảnh báo quan trọng"

Có lệnh của "sếp", đã đến lúc tôi cần nghiêm túc hơn với sản phẩm của mình rồi.  
Nói qua một chút về phần mềm của tôi, tôi và các đồng nghiệp đặt cho nó một cái tên là "ACL" (auto control log-files). Nhiệm vụ của ACL là đọc các file log đã được user lấy về từ hệ thống, thực hiện phân tích các block-log trong file để tìm các cụm thông tin hữu ích, các cụm thông tin chưa từng xuất hiện hoặc thông tin có thể gây ra nguy hiểm cho hệ thống, các thông tin này sẽ được đưa ra màn hình terminal cho người sử dụng có thể nhìn thấy. Sau mỗi lượt đọc & phân tích log sẽ thực hiện tổng hợp lại thành các báo cáo và các cảnh báo trên màn hình terminal.
Bình thường màn hình tổng hợp cuối cùng các báo cáo sẽ ra như kiểu này.

Khá đơn điệu với màu chữ trắng trên nền đen, ngồi nhìn màn hình như này bảo sao các user của tôi không phát chán :)).

Công việc của tôi sẽ là phải tìm cách thực hiện đổi màu các chữ trên màn hình đen để có thể focus đúng vào các vị trí cảnh báo marjor, critical, fatal ?

Tìm kiếm với một vài từ khóa và lang thang "đi dạo" qua các package trên “kho” của python thì ra thư viện khá thú vị là Colorama. Hãy cùng tôi thực hiện khám phá thư viện này nhé.

Cài đặt thư viện

Với Python, để sử dụng thư viện nào đó trên "kho", chúng ta sẽ thực hiện install thư viện này thông qua công cụ pip (lưu ý là nên tạo một virtual enviroment cho đỡ rác nếu bạn chỉ muốn thử nghiệm).

pip install colorama

Tài liệu của tác giả 

Thư viện này được tác giả cung cấp cả sample, documents tại đường dẫn colorama. Theo như mô tả trong tài liệu, tác giả hướng đến việc hiển thị màu trên terminal được chia ra làm 3 mục:

FOREGROUND: Màu của chữ;

BACKGROUND: Màu nền của termial.

Thư viện hỗ trợ các màu cơ bản: Black, red, green, yellow, blue, magenta, cyan, white.

STYLE: Định dạng chữ bright, dim, normal. Nếu để định dạng BRIGHT sẽ làm cho text hiển thị có vẻ như là… sáng hơn so với DIM, NORMAL (hơi khó để cảm nhận).

Khi kết hợp 3 loại này trên cùng một dòng text, có khá nhiều thay đổi thú vị được hiển thị. Dưới đây là ảnh demo sự thay đổi của tác giả:

Ví dụ một chút về màu chữ: 

Tiếp tục đến background

Để clear hết các style và quay trở về mặc định, tác giả cung cấp một từ khóa Style.RESET_ALL

Có một lưu ý là việc sử dụng thư viện colorama trên windows và linux sẽ khác nhau một chút do cơ chế hiển thị lên terminal của 2 hệ điều hành khác nhau.
Trên Linux chỉ cần import thư viện vào là sử dụng được. Trên Windows sẽ phải đi lòng vòng (như ví dụ trên của tôi đang code trên Windows). Tác giả cũng có đề cập đến vấn đề này trong tài liệu của mình.

Ví dụ đoạn code hiển thị nội dung giống nhau được viết trên linux và windows:

Viết trên linux:

from colorama import Fore, Back, Style
print(Fore.RED + 'some red text')
print(Back.GREEN + 'and with a green background')
print(Style.RESET_ALL)
print('back to normal now')

Viết trên windows

import sys
from colorama import Fore, Back, Style
from colorama import init, AnsiToWin32
init(wrap=False)
stream = AnsiToWin32(sys.stderr).stream
print(Fore.RED + 'some red text', file=stream)
print(Back.GREEN + 'and with a green background', file=stream)
print(Style.RESET_ALL, file=stream)
print('back to normal now', file=stream)

Áp dụng vào bài toán thực tế

Sau khi đọc xong các sample của tác giả, tôi bắt đầu áp dụng vào màn hình báo cáo. Sau khi tự "fake" một số hình ảnh báo cáo và gửi cho "khách hàng", mọi người đã lựa chọn hình ảnh như bên dưới:


Tôi bắt đầu đi vào giải quyết dần từng hạng mục.

Ví dụ:
- Số thứ tự của node mạng có background là GREEN.
- Các màu tương ứng với loại issue: warning --> CYAN, major --> YELLOW, critical --> MAGENTA, fatal --> RED
- Background của dòng tổng kết có màu BLUE.

Như ví dụ ở trên, chúng ta thấy việc in ra màn hình là việc cộng dồn các chuỗi theo như chúng ta định nghĩa màu. Việc cộng chuỗi làm cho code không được "đẹp" lắm. Chúng ta sẽ sử dụng function .format để xuất ra một chuỗi.
Đầu tiên là định nghĩa 1 dictionary chứa các style tương ứng:

colors = {
    'highlight_background': Back.BLUE,
    'default_color': Fore.WHITE,
    'issue_color': Fore.WHITE,
    'warning_color': Fore.CYAN,
    'major_color': Fore.YELLOW,
    'cirtical_color': Fore.MAGENTA,
    'fatal_color': Fore.RED,
    'reset': Style.RESET_ALL,
    'highlight_number_background': Back.GREEN
}

3 dòng đầu tiên ứng với 3 biểu mẫu giống nhau có định dạng:

node_template = "System Info node {node_number} # Count: {issue_count} issues"\
                "=> {warning_count} warning; {major_count} marjor;"\
                "{cirtical_count} critical; {fatal_count} fatal;"

Mỗi dòng sẽ sử dụng một dictionary chứa dữ liệu như bên dưới để đổ dữ liệu vào

info_1 = {
    'node_number': 1,
    'issue_count': 300,
    'warning_count': 40,
    'major_count': 2,
    'cirtical_count': 8,
    'fatal_count': 0     
}

data_line_1 = node_template.format(**info_1)
print(data_line_1)

Ta nhận được kết quả: 

Chúng ta sẽ đưa dần các màu sắc tương ứng vào, đầu tiên là màu background ở node_number

import sys
from colorama import Fore, Back, Style
from colorama import init, AnsiToWin32
init(wrap=False)
stream = AnsiToWin32(sys.stderr).stream

colors = {
    'highlight_background': Back.BLUE,
    'default_color': Fore.WHITE,
    'issue_color': Fore.WHITE,
    'warning_color': Fore.CYAN,
    'major_color': Fore.YELLOW,
    'cirtical_color': Fore.MAGENTA,
    'fatal_color': Fore.RED,
    'reset': Style.RESET_ALL,
    'highlight_number_background': Back.GREEN
}
node_template = "System Info node {highlight_number_background} {node_number}"\
                "# Count: {issue_count} issues"\
                "=> {warning_count} warning; {major_count} marjor;"\
                "{cirtical_count} critical; {fatal_count} fatal;"

info_1 = {
    'node_number': 1,
    'issue_count': 300,
    'warning_count': 40,
    'major_count': 2,
    'cirtical_count': 8,
    'fatal_count': 0     
}
info_1.update(colors)
data_line_1 = node_template.format(**info_1)
print(data_line_1, file=stream)

Kết quả nhận được

Oạch!!! Màu background GREEN đã chạy xuyên suốt toàn bộ cả dòng rồi. Do chúng ta chưa thực hiện reset lại style. Bổ sung thêm đoạn reset style vào template.

node_template = "System Info node {highlight_number_background} {node_number}{reset}"\
                "# Count: {issue_count} issues"\
                "=> {warning_count} warning; {major_count} marjor;"\
                "{cirtical_count} critical; {fatal_count} fatal;"

Kết quả nhận được:

Tiếp theo là thêm màu CYAN vào chữ số lượng warning issue.

node_template = "System Info node {highlight_number_background} {node_number}{reset}"\
                "# Count: {issue_count} issues => "\
                "{warning_color}{warning_count} warning;{reset}"\
                "{major_count} marjor;"\
                "{cirtical_count} critical; {fatal_count} fatal;"

Làm tương tự với major và critical

node_template = "System Info node {highlight_number_background} {node_number}{reset}"\
                "# Count: {issue_count} issues"\
                "=> {warning_color}{warning_count} warning;{reset}"\
                "{major_color}{major_count} marjor;{reset}"\
                "{cirtical_color}{cirtical_count} critical;{reset}"\
                "{fatal_count} fatal;"

Phần fatal issue, nếu là 0 issue thì dể màu mặc định, nếu có issue thì để màu đỏ, vì vậy chúng ta phải thêm một chút "sơ chế" dữ liệu

if info_1.get('fatal_count', 0) == 0:
    info_1['fatal_color'] = Fore.WHITE

Bổ sung thêm dữ liệu node_2, node_3, thực hiện format string và print ra màn hình như ví dụ dòng thứ 1. Kết quả thu được:

Sau khi làm xong 3 dòng phía trên thì dòng cuối cùng lại...trở thành đơn giản quá.

Kết luận: 

Trong nội dung bài viết, tôi vừa giới thiệu và hướng dẫn các bạn cách sử dụng thư viện colorama, nếu các bạn thấy hay và hữu ích hãy thả một star cho tác giả trên codelearn và trên github nhé:  https://github.com/tartley/colorama

Cảm ơn các bạn đã đọc bài viết của tôi.