주의: [콘솔 프로그램의 출력에 색깔 입히기 - Log::Simple::Color]의 가장 최근 판은 이곳에서 확인할 수 있습니다.
시작하며
거의 모든 작업을 GUI 환경에서 처리하는 요즘에도 업무의 자동화나 일괄 처리 때문에
터미널의 명령줄에서 실행하는 프로그램의 사용의 빈도는 여전히 잦습니다.
보통 이런 프로그램은 작업의 진행 내역을 알려주기 위해 표준 출력이나 표준 에러에
유용한 정보를 출력하거나 또는 로그에 기록을 남깁니다.
이 중에서도 특히 표준 출력이나 표준 에러에 정보를 출력하는 경우
글자나 배경에 색깔을 입혀서 가독성을 높힐 수 있습니다.
ANSI 를 지원하는 리눅스 계열의 시스템과 윈도우즈 시스템에서
출력물에 색깔을 입혀서 사용자 친화적인 프로그램을 만듭니다.
준비물
명령줄 프로그램을 작성하기 위해 많이 사용하는 스크립트 언어로는 펄(Perl)이 있습니다.
CPAN 모듈을 이용해서 리눅스와 윈도우즈 환경 모두에서 동작하는 프로그램의 뼈대를 작성합니다.
관련 연구
Term::ANSIColor
Term::ANSIColor 는 ANSI 회피 문자를 사용해서 화면에 출력하는 글자 또는 배경에 색을 입히는 모듈입니다.
당연히 ANSI를 지원하는 터미널에서 동작하며 지원하지 않는 터미널에서는 ANSI 회피 문자열 그 자체가
화면에 나타납니다.
다음은 녹색 배경에 밑줄과 굵은 속성을 가진 노란색 글씨와 굵은 속성의 파란색 글씨를 출력하는 예제입니다:
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
use Term::ANSIColor qw(:constants);
say BOLD, UNDERLINE, YELLOW, ON_GREEN, "ugly", RESET;
say "This text is normal.";
say BOLD, BLUE, "This text is in bold blue.", RESET;
say "This text is normal.";
Win32::Console
윈도우즈 콘솔은 ANSI 회피 문자를 지원하지 않기 때문에 다른 기능을 사용해야합니다.
Win32::Console 모듈은 윈도우즈 콘솔과 문자 모드를 다룰 수 있는 기능을 제공합니다.
윈도우즈 콘솔은 굵은 글씨나 밑줄 등의 속성을 지원하지 않으므로 색상만 변경할 수 있습니다.
다음은 녹색 배경에 노란색 글씨와 파란색 글씨를 출력하는 예제입니다:
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
use Win32::Console;
my $console = Win32::Console->new(STD_OUTPUT_HANDLE);
my $reset = $console->Attr;
$console->Attr($FG_YELLOW | $BG_GREEN);
say "ugly";
$console->Attr($reset);
say "This text is normal.";
$console->Attr($FG_BLUE);
say BOLD, BLUE, "This text is in bold blue.", RESET;
$console->Attr($reset);
say "This text is normal.";
구현
다음은 Term::ANSIColor 과 Win32::Console 모듈을 이용해 작성한
간단한 로그 모듈 Log::Simple::Color 입니다:
package Log::Simple::Color;
use strict;
use warnings;
sub say { print @_, "\n" }
my $console;
my %color_of;
my %msg;
my $default_level = 'info';
my %log_level_of = (
debug => 0,
info => 1,
warning => 2,
error => 3,
);
my %default_msg = (
debug => sub {
my ( $self, @args ) = @_;
return if $log_level_of{debug} < $log_level_of{$self->level};
say @args;
},
info => sub {
my ( $self, @args ) = @_;
return if $log_level_of{info} < $log_level_of{$self->level};
say @args;
},
warning => sub {
my ( $self, @args ) = @_;
return if $log_level_of{warning} < $log_level_of{$self->level};
say @args;
},
error => sub {
my ( $self, @args ) = @_;
return if $log_level_of{error} < $log_level_of{$self->level};
say @args;
},
default => sub {
my ( $self, $mode, @args ) = @_;
return if $log_level_of{default} < $log_level_of{$self->level};
say "[$mode] ", @args;
},
);
my %linux_msg = (
debug => sub {
my ( $self, @args ) = @_;
return if $log_level_of{debug} < $log_level_of{$self->level};
say @{ $color_of{debug} }, @args, @{ $color_of{default} };
},
info => sub {
my ( $self, @args ) = @_;
return if $log_level_of{info} < $log_level_of{$self->level};
say @{ $color_of{info} }, @args, @{ $color_of{default} };
},
warning => sub {
my ( $self, @args ) = @_;
return if $log_level_of{warning} < $log_level_of{$self->level};
say @{ $color_of{warning} }, @args, @{ $color_of{default} };
},
error => sub {
my ( $self, @args ) = @_;
return if $log_level_of{error} < $log_level_of{$self->level};
say @{ $color_of{error} }, @args, @{ $color_of{default} };
},
default => sub {
my ( $self, $mode, @args ) = @_;
return if $log_level_of{default} < $log_level_of{$self->level};
say "[$mode] ", @args;
},
);
my %window_msg = (
debug => sub {
my ( $self, @args ) = @_;
return if $log_level_of{debug} < $log_level_of{$self->level};
$console->Attr(@{ $color_of{debug} });
say @args;
$console->Attr(@{ $color_of{default} });
},
info => sub {
my ( $self, @args ) = @_;
return if $log_level_of{info} < $log_level_of{$self->level};
$console->Attr(@{ $color_of{info} });
say @args;
$console->Attr(@{ $color_of{default} });
},
warning => sub {
my ( $self, @args ) = @_;
return if $log_level_of{warning} < $log_level_of{$self->level};
$console->Attr(@{ $color_of{warning} });
say @args;
$console->Attr(@{ $color_of{default} });
},
error => sub {
my ( $self, @args ) = @_;
return if $log_level_of{error} < $log_level_of{$self->level};
$console->Attr(@{ $color_of{error} });
say @args;
$console->Attr(@{ $color_of{default} });
},
default => sub {
my ( $self, $mode, @args ) = @_;
return if $log_level_of{default} < $log_level_of{$self->level};
say "[$mode] ", @args
},
);
if ($^O eq 'linux') {
eval 'use Term::ANSIColor qw(:constants);';
if (!$@) {
eval(
'$color_of{default} = [ RESET ];'
. '$color_of{debug} = [ CYAN ];'
. '$color_of{info} = [ YELLOW ];'
. '$color_of{warning} = [ WHITE, ON_BLUE ];'
. '$color_of{error} = [ WHITE, ON_RED ];'
);
$msg{debug} = $linux_msg{debug};
$msg{info} = $linux_msg{info};
$msg{warning} = $linux_msg{warning};
$msg{error} = $linux_msg{error};
$msg{default} = $linux_msg{default};
}
else {
say "Recommand CPAN Perl Module: [Win32::Console]";
$msg{debug} = $default_msg{debug};
$msg{info} = $default_msg{info};
$msg{warning} = $default_msg{warning};
$msg{error} = $default_msg{error};
$msg{default} = $default_msg{default};
}
}
elsif ($^O eq 'MSWin32') {
eval 'use Win32::Console;';
if (!$@) {
eval(
'$console = Win32::Console->new(STD_OUTPUT_HANDLE);'
. '$color_of{default} = $console->Attr;'
. '$color_of{debug} = [ $FG_CYAN ];'
. '$color_of{info} = [ $FG_YELLOW ];'
. '$color_of{warning} = [ $FG_WHITE | $BG_BLUE ];'
. '$color_of{error} = [ $FG_WHITE | $BG_RED ];'
);
$msg{debug} = $window_msg{debug};
$msg{info} = $window_msg{info};
$msg{warning} = $window_msg{warning};
$msg{error} = $window_msg{error};
$msg{default} = $window_msg{default};
}
else {
say "Recommand CPAN Perl Module: [Win32::Console]";
$msg{debug} = $default_msg{debug};
$msg{info} = $default_msg{info};
$msg{warning} = $default_msg{warning};
$msg{error} = $default_msg{error};
$msg{default} = $default_msg{default};
}
}
else {
$msg{debug} = $default_msg{debug};
$msg{info} = $default_msg{info};
$msg{warning} = $default_msg{warning};
$msg{error} = $default_msg{error};
$msg{default} = $default_msg{default};
}
sub new {
my ($class, %param) = @_;
my $self = bless {
level => $default_level,
}, $class;
$self->level($param{level}) if exists $param{level};
return $self;
}
sub debug { $msg{debug}->(@_) }
sub info { $msg{info}->(@_) }
sub warning { $msg{warning}->(@_) }
sub error { $msg{error}->(@_) }
sub level {
my ( $self, $level ) = @_;
return $self->{level} unless $level;
if ( $level =~ m/^debug$/i ) { $self->{level} = 'debug'; }
elsif ( $level =~ m/^info$/i ) { $self->{level} = 'info'; }
elsif ( $level =~ m/^warning$/i ) { $self->{level} = 'warning'; }
elsif ( $level =~ m/^error$/i ) { $self->{level} = 'error'; }
else { $self->{level} = 'info'; }
}
1;
사용
Log::Simple::Color 를 사용하기 위해서는 생성자를 이용해서 객체를 먼저 만듭니다:
use Log::Simple::Color;
my $log = Log::Simple::Color->new;
객체가 지원하는 메소드 종류는 다음과 같습니다:
- $log->level
- $log->debug
- $log->info
- $log->warning
- $log->error
완전한 예제는 다음과 같습니다:
#!/usr/bin/perl
use strict;
use warnings;
use Log::Simple::Color;
my $log = Log::Simple::Color->new;
for my $level (qw/ incorrect_mode debug info warning error /) {
print "$level: [", $log->level, "] -> [", $log->level($level), "]\n";
$log->debug("This is a debug message");
$log->info("This is an info message");
$log->warning("This is a warning message");
$log->error("This is an error message");
}
정리하며
로그메시지는 프로그램의 동작 상태를 확인할 수 있는 효과적인 방법입니다.
특이사항이나 문제가 발생한 부분처럼 중요하기 때문에 눈에 띄어야 한다면
중요도 별로 색깔을 서로 다르게 입혀서 효율적으로 로그를 관리할 수도 있습니다.
비록 윈도우즈 기본 터미널은 ANSI 를 지원하지 않기 때문에 서로 다른 두 가지 모듈을
사용해서 구현했지만 모듈로 한번 만들어놓으면 간단한 콘솔 스크립트를 작성할 때
편리하게 사용할 수 있습니다.