Arsip mwmag  [Up]© 2002 PT Masterweb Media

Tutorial Perl Bagian 4: Menggali Makna dalam File Log

Memproses File Log

Mengurutkan

Output HTML

Membuat Program Yang Tahan Banting

Edisi Depan

Tutorial Perl Bagian 4:

Menggali Makna dalam File Log

Bagian 2 tutorial ini telah membahas hal-hal yang spesifik terhadap platform Unix, dan bagian 3 telah membahas hal-hal yang spesifik untuk Windows. Dalam bagian ini saya akan membahas mengenai log file, yang ada baik di Windows maupun di Unix. Pemrosesan log file ini akan menunjukkan pada kita bagaimana kemampuan Perl dalam memroses teks.

Dalam administrasi sistem, para administrator akan sering berhubungan dengan file log. File log adalah file yang berisi catatan (log) aktivitas dari sebuah program. Tergantung dari programnya, kegunaan log file sangat banyak. Misalnya untuk program webserver, log file dapat menunjukkan hit yang diterima oleh suatu situs (lihat mwmag02, “7 Trik Meningkatkan Pageview”), menunjukkan halaman mana saja dalam situs Anda yang dicoba diakses namun tidak ada (mungkin Anda salah mengirim URL ke sebuah milis), browser apa saja yang digunakan oleh pengunjung situs, dll. Untuk program yang berhubungan dengan sekuritas, log file (mungkin) dapat menunjukkan usaha penjebolan keamanan yang dilakukan seseorang.

File log yang dihasilkan oleh suatu program dapat berupa file biner ataupun file teks sederhana (atau keduanya). Dalam tutorial ini kita hanya akan membahas pemrosesan file log yang berupa file teks (bahkan Windows pun mendukung jenis file ini). Dalam tutorial ini Anda akan belajar memproses file log sederhana dengan hasil laporan berupa file teks dan HTML.

Memproses File Log

Format file log bisa bermacam-macam, namun file log yang umum ditemui di dunia Unix dan Windows adalah file log di mana informasi disusun per baris. Setiap kali program me-log aktivitas, catatan tersebut disusun dalam satu baris dan ditambahkan di akhir file log (di-append).

Untuk membuat laporan dari log file, ada beberapa langkah yang harus dilakukan. Langkah pertama adalah parsing log file. Parsing merupakan proses untuk memecah suatu string menjadi token-token; mudahnya, jika string adalah sebuah kalimat, maka token adalah sebuah kata yang menyusun kalimat tersebut. Dalam tutorial bagian kedua Anda bisa melihat bagaimana cara memecah string menjadi bagian-bagiannya jika string tersebut dipisahkan dengan suatu delimiter tertentu dengan menggunakan fungsi split. Dalam memproses log file, fungsi split dapat digunakan jika bentuk file lognya sederhana, untuk log file dengan format CSV, kita perlu memakai modul Text::ParseWords.

Penjelasan di atas akan lebih mudah diberikan dalam bentuk contoh. Misalkan kita punya program fiktif, yang membuat file log seperti ini:

27/03/2001 Saman
27/03/2001 Larung
28/03/2001 Laila
29/03/2001 Larung

Program menambahkan sebuah baris di file log setiap kali program dijalankan. Field pertama adalah tanggal saat program dijalankan dan field kedua adalah user yang menjalankan program tersebut, sebuah spasi memisahkan antara field pertama dengan kedua. Untuk file seperti itu, parsing menggunakan split sudah cukup, untuk memisahkan antara tanggal dan nama, kita cukup melakukannya seperti ini:

open(F, "user.log");
while (<F>) {
chomp;
($tgl, $user) = split(/ /);
}
close(F);

Untuk file CSV sederhana, di mana setiap field dipisahkan dengan koma, dan setiap field tidak boleh mengandung karakter koma (seperti yang ada pada tutorial bagian 2 dan 3), Anda bisa menggunakan split (dengan string pemisah berupa /,/).

Untuk file CSV yang lebih lengkap, koma diijinkan di dalam fieldnya (setiap field dilingkupi oleh tanda petik) Anda perlu menggunakan modul Text::ParseWords, karena file CSV yang lengkap memerlukan pemrosesan khusus. Contoh file CSV yang lengkap seperti ini:

Contoh:

"21/03/1985", "Yasmin, Lukas"
"23/02/2002", "Joe, Nita"

Perhatikan bahwa field kedua baris pertama berisi teks yang mengandung karakter koma “Yasmin, Lukas”. Beginilah cara memecahnya dengan modul Text::ParseWords.

use Text::ParseWords;
open(F, "text.log");
while (<F>){
@words = quotewords(",", 0, $_);
print "Field 1: $words[0]\n";
print "Field 2: $words[1]\n";
}

Prototipe quotewords adalah seperti ini:

@words = &quotewords($delim, $keep, @lines);

$delim adalah pembatas teks (bisa berupa regular expression), $keep menentukan apakah tanda kutip tetap dipertahankan dalam outputnya, sedangkan @lines adalah baris-baris yang akan di-parse (boleh juga hanya satu baris, jadi tidak perlu list, seperti pada contoh).

Sebenarnya kita bisa menggunakan regex murni, tanpa perlu modul Text::ParseWords, tapi cara ini agak sulit (kalau ada yang mudah, mengapa pilih yang sulit?). Versi regex bisa dilihat di perlfaq4 (ketik man perlfaq4 di Unix, atau lihat di help ActivePerl), di bagian pertanyaan: How can I split a [character] delimited string except when inside [character]? (Comma-separated files). Kadang-kadang solusi tanpa modul tertentu diperlukan (agar program tidak tergantung pada modul yang harus diinstal di komputer tempat program berjalan), namun untuk modul standar seperti Text::ParseWords kita tidak perlu solusi yang lebih sulit.

Setelah kita bisa memparse setiap aran (entry) dalam file log, langkah kedua adalah memproses data yang kita dapat. Lalu pertanyaan selanjutnya adalah: laporan seperti apa yang bisa dibuat dari suatu file log? Ada banyak laporan yang bisa dibuat, mulai dari sekedar hitungan sederhana, summary (ringkasan), sampai grafik yang kompleks.

Laporan file log biasanya merupakan laporan hitungan. Contoh file log pertama adalah file log pemakai suatu program. Jadi ketika suatu program dijalankan, program tersebut akan me-log siapa saja penggunanya. Jika kita ingin tahu berapa kali suatu program digunakan pada hari tertentu, maka kita bisa membuat program seperti ini:

#!/usr/bin/perl –w
open(F, "s.log");
while (<F>) {
chomp;
($dt, $user) = split(/ /);
$date{$dt}++;
$user{$user}++;
$total++;
}
print "Laporan berdasarkan tanggal:\n";
foreach (keys %date) {
$persen = $date{$_}*100/$total;
print "$_: $date{$_} ($persen%)\n";
}
print "Laporan berdasarkan pemakai:\n";
foreach (keys %user) {
$persen = $user{$_}*100/$total;
print "$_: $user{$_} ($persen%)\n";
}

Baris yang penting dari program di atas adalah:

$date{$dt}++;
$user{$user}++;

Dari tutorial pertama Anda tentunya masih ingat dengan yang namanya hash. Hash merupakan array asosiatif. Jika pada array indeks yang dibolehkan adalah bilangan, maka pada hash memakai indeks boleh berupa string, dalam hash indeks ini disebut dengan key. Dalam contoh di atas, $date{$dt} akan ditambahkan dengan satu dalam setiap langkah loop.

Laporan berdasarkan tanggal:

27/03/2001: 2 (50%)
28/03/2001: 1 (25%)
29/03/2001: 1 (25%)

Laporan berdasarkan pemakai:

Saman: 1 (25%)
Larung: 2 (50%)
Laila: 1 (25%)

Di Perl, nilai variabel skalar yang belum diinisialisasi bisa dianggap sebagai 0 jika kita memperlakukan variabel itu sebagai variabel numerik. Jadi ketika dijumpai tanggal pertama 27/03/2001, nilai $date{"27/03/2001"}—yang belum terdefinisi nilainya— adalah 0, kemudian nilai tersebut ditambah dengan 1, ketika dijumpai tanggal yang sama berikutnya nilai tersebut akan terus ditambah dengan 1. Di sini terlihat kepraktisan Perl. Di bahasa lain, kemungkinan Anda harus memeriksa dulu apakah tanggal sudah ada di list atau hash, dan jika ada, tambahkan nilainya dengan 1, jika tidak buat dulu entry untuk tanggal tersebut, kemudian set nilainya menjadi 1.

Baris ini:

foreach (keys %user) {
print "$_\n";
}

Akan mengiterasi isi hash dan mencetak nilai dari masing-masing elemen hash. Fungsi keys mengembalikan key apa saja yang ada dalam suatu hash dalam bentuk list. Dengan menggunakan fungsi keys kita bisa mendapatkan daftar unik dari suatu field, dari contoh output di atas bisa dilihat siapa saja yang memakai program (daftar tersebut unik, tidak ada user yang disebutkan 2 kali). Anda bisa menambahkan ini di akhir program untuk mencetak daftar user yang ada:

@users = keys %users;
print @user;

Outputnya agak kacau, karena setiap nama tidak dipisahkan dengan spasi atau enter. Agar outputnya kelihatan lebih cantik, Anda bisa mencetak listnya dengan cara seperti ini:

print join(" ", @users);

Prototipe fungsi join adalah seperti berikut:

join EXPR,LIST

Fungsi join menggabungkan suatu list menjadi sebuah string dengan pembatas EXPR.

Misal:

@OS = qw(atheos QNX Linux Windows);
print join("*", @OS);

akan menghasilkan:

atheos*QNX*Linux*Windows

Dengan berbekal contoh di atas, Anda sudah bisa berlatih untuk memparse file log webserver apache untuk mengetahui berapa banyak pemakai browser tertentu, berapa banyak pemakai yang mengakses halaman tertentu, dll.

Mengurutkan

Jika Anda perhatikan output program di atas, terlihat bahwa output yang dihasilkan tidak terurut. Di Perl, jika kita punya list, kita bisa mengurutkannya dengan fungsi sort. Prototipe fungsinya seperti berikut ini:

sort SUBNAME LIST
sort BLOCK LIST
sort LIST

LIST merupakan list yang ingin diurutkan, SUBNAME merupakan nama subrutin yang menentukan bagaimana pengurutan dilakukan, BLOCK merupakan alternatif dari SUBNAME. Jika SUBNAME ataupun BLOCK tidak ada, maka sort akan Mengurutkan dengan cara standar (dengan operator cmp).

Contoh:

@t = ("Shinichi Kudo", "Conan Edogawa", "Ran Mouri", 
"Kogoro Mouri");
@terurut = sort @t;
print join(“,“, @terurut);

Hasilnya adalah:

Conan Edogawa,Kogoro Mouri,Ran Mouri,Shinichi Kudo

tapi jika kita mencobanya pada list numerik, hasilnya tidak akan seperti yang kita harapkan:

@t = (17, 5, 80, 27, 6, 82);
@terurut = sort @t;
print join(" ", @terurut)

hasilnya:

17 27 5 6 80 82

Pengurutan standar didasarkan pada urutan string (seperti pada urutan di buku telepon), bukan pada nilai numeriknya. Untuk mengerti mengenai cara pengurutan secara numerik, dan cara pengurutan yang lain, Anda perlu memahami dulu dua operator perbandingan yang biasanya dipakai pada pengurutan.

Operator cmp membandingkan dua buah string, operator ini mengembalikan -1 0, atau 1 tergantung pada nilai operand di kiri dan kanan operator cmp, operator ini mengembalikan -1, jika operan kiri kurang dari operan kanan, 0 jika sama, dan 1 jika operan kiri lebih dari operan kanan. Suatu string $a dikatakan lebih besar dari string $b jika ketika string tersebut diurutkan, urutan $a adalah setelah $b. Operator <=> (spaceship operator) sama dengan operator cmp namun bekerja untuk operan numerik.

Pernyataan:

@terurut = sort @t;

sebenarnya sama dengan

@terurut = sort {$a cmp $b} @t;

seperti tertulis dalam prototipe di atas, BLOCK berisi ekspresi yang menentukan bagaimana pengurutan dilakukan. BLOCK ini juga bisa digantikan dengan subrutin seperti ini:

sub banding {
$a cmp $b;
}
@terurut = sort banding @t;

Subrutin atau blok yang ada menentukan bagaimana pengurutan dilakukan. Dalam fungsi atau blok sort, variabel $a dan $b terdefinisi. $a sebagai operan pertama dan $b sebagai operan kedua. Fungsi harus mengembalikan (atau block harus menghasilkan) nilai -1 jika $a<$b, 0 jika $a sama dengan $b, dan 1 jika $a>$b.

Jadi untuk mengurutkan berdasarkan nilai numerik, kita bisa melakukannya seperti ini:

@terurut = sort {$a <=> $b} @t;

Jika kita memiliki daftar seperti ini:

@names = ("Peter Parker", "Clark Kent", "Bruce Wayne");

dan ingin mengurutkannya berdasarkan nama belakang, kita bisa melakukannya seperti ini:

sub bylastname{
my (undef, $last1) = split(/ /, $a);
my (undef, $last2) = split(/ /, $b);
$a cmp $b;
}
@urut = sort bylastname @names;
print join(“,”, @urut);

outputnya:

Clark Kent,Peter Parker,Bruce Wayne

Dari contoh-contoh di atas, bisa disimpulkan bahwa untuk mencetak data terurut berdasarkan key, kita bisa melakukannya seperti ini:

foreach $i (sort keys %hash) {
print "$i ".$hash{$i};
}

Dan untuk mencetak terurut berdasarkan nilainya:

@keys = sort {  $hash{$b} <=> $hash{$a}  } keys %hash;
foreach $i (@keys) {
print "$i ".$hash{$i};
}

Output HTML

Anda bisa dengan mudah membuat file teks yang menjadi output. Namun sangat jelas terlihat bahwa output teks murni tidak terlihat cantik. Inilah langkah ketiga dalam pembuatan laporan log yaitu penentuan format laporan. Agar tampilannya lebih cantik, Anda juga bisa membuat file HTML sebagai output. Caranya mudah, kita cukup mengoutputkan teks dengan tag HTML biasa ke sebuah file, seperti ini:

 

#!/usr/bin/perl –w
open(F, "s.log");
while (<F>) {
chomp;
(undef, $user) = split(/ /);
$user{$user}++;
}
$isi = "";
foreach (keys %user) {
$isi .= "<tr><td>$_</td><td>$user{$_}</td></tr>"
}
$title = "Laporan Penggunaan Program Berdasarkan User";
open (X, ">laporan.html");
print X <<EOX;
<html><head><title>$title</title></head>
<body><h1>$title</h1>
<table border=1>
<tr><td>User</td><td>Jumlah Pemakaian</td></tr>
$isi
</table>
</body>
</html>
EOX
close(X);

Tapi tentunya tampilan output program di atas sulit dimodifikasi. Untuk memudahkan, kita bisa membuat file template. Template ini berisi design tampilan, sedangkan isi laporan tetap dihasilkan program. Untuk mengubah tampilan, kita cukup mengubah file template. Contoh file template seperti ini

<html>
<head><title><!—title--></title></head>
<body bgcolor=”#CCCCCC”>
<h1>><!—title--></h1>
<!--content-->
</body>
</html>

Dalam template tersebut bagian yang akan diisi oleh program ditandai dengan tag komentar html, seperti <!--content-->. Untuk mengganti bagian komentar dengan isi yang seharusnya, Anda dapat melakukannya seperti ini:

open (T, "template.html");
@content = <T>;
close (T);
$all = join("", @content);
$all =~ s/<!--title-->/$title/g;
$all =~ s/<!--content-->/$isi/g;
open (X, ">laporan.html");
print X $all;
close(X);

Jika hanya satu file template itu saja yang Anda pakai, tidak efektif jika Anda menyimpannya di file lain, karena setiap kali Anda menyalin ke komputer lain, Anda perlu menyalin juga file templatenya. Anda bisa menggabungkan isi sebuah file tersebut ke dalam skrip Perl, seperti ini:

while (<DATA>) {
print;
}
__DATA__
isi file
semua baris ini adalah isi file

Semua baris setelah baris __DATA__ di file Perl Anda akan dianggap sebagai data, dan Anda bisa membaca data itu seperti membaca isi file dengan file handle DATA.

Jika Anda ingin belajar membuat report memakai file log yang lebih besar dan kompleks, Anda bisa mencoba mengambilnya dari file log di sistem Anda. File log di Unix biasanya ada di direktori /var/log (atau mungkin di dalam subdirektorinya) . Di Windows (NT/2K/XP), Anda bisa mengekspor dari event viewer. Beberapa aplikasi di Windows me-log dalam format multi line (banyak baris), sehingga meskipun di ekspor dalam format CSV, Anda akan menemui kesulitan untuk memprosesnya.

Membuat Program Yang Tahan Banting

Sampai bagian ini, setiap program yang saya berikan tidak memeriksa kesalahan yang diberikan. Tujuan penghilangan bagian yang penting ini adalah untuk kesederhanaan. Namun semakin rumit program, semakin banyak kesalahan yang mungkin muncul. Kesalahan yang terjadi misalnya file atau direktori yang tidak ada atau input yang diberikan salah. Jika program diteruskan berjalan, maka kemungkinan output program menjadi tidak benar (atau bahkan mungkin program dapat melakukan sesuatu yang berbahaya).

Jika ada kesalahan fatal yang ditemui oleh program, maka program sebaiknya berhenti, untuk keluar kita bisa menggunakan exit:

if (-e "data.out") {
print "File output sudah ada";
exit;
}

Operator -e digunakan untuk mengetes apakah suatu file ada atau tidak. Program di atas akan berhenti jika file data.out sudah ada. Anda bisa mencetak pesan dan keluar dengan exit, tapi Perl sudah memiliki fungsi untuk memudahkan dua langkah itu, fungsi itu adalah die, yang akan mencetak pesan ke STDERR (defaultnya layar), lalu keluar. Cara yang lebih lazim dipakai adalah seperti ini:

die "file sudah ada " if (-e "data.out");

Dalam Perl, ada banyak cara untuk melakukan satu hal. Konstruksi seperti di atas bisa diubah menjadi:

die "file sudah ada " unless (!(-e "data.out"));

atau

(-e "data.out") && die "file sudah ada ";

Baris yang terakhir memanfaatkan operator && yang bersifat short circuit, dimana jika operan kiri false maka operan kanan tidak diperiksa, tapi jika operan kiri bernilai true (dalam kasus ini file ada), maka die akan dijalankan (dievaluasi).

Biasanya hal yang umum Anda jumpai adalah pernyataan seperti ini:

open(F, "file.txt") || die "tidak bisa membuka file"

baris tersebut memakai sifat operator || (or), dimana jika operan kiri sudah bernilai true (dalam hal ini, open berhasil), maka ekspresi di sebelah kanan tidak dievaluasi.

Jika Anda hanya ingin memberi pesan peringatan, tanpa keluar, Anda bisa menggunakan fungsi warn.

warn "data tidak diproses" if (!$valid);

Edisi Depan

Sebagai artikel tutorial, banyak hal yang saya sederhanakan agar lebih mudah dimengerti, misalnya saya tidak membahas sifat die di dalam eval(), atau warn() jika handler untuk $SIG{__WARN__} diinstal. Tapi jangan khawatir, sambil mengikuti tutorial ini, sedikit demi sedikit pemahaman yang benar tentang Perl bisa Anda dapatkan.

Di edisi depan kita akan mulai belajar bagaimana melakukan pemrograman berbasis objek di Perl. OOP di Perl merupakan hal yang cukup sulit (karena Perl dulunya tidak dirancang sebagai bahasa berorientasi objek sehingga implementasi objek di Perl dipenuhi dengan hack), namun pemahaman mengenai di Perl akan memungkinkan kita memakai modul-modul Perl berbasis OO. Salah satu modul yang berbasis OO yang akan dibahas di edisi depan adalah modul GD untuk membuat file grafik di Perl, dengan modul GD kita akan mempercantik laporan log file di edisi ini dengan grafik yang menarik.

Yohanes Nugroho mahasiswa tingkat akhir IF ITB. Administrator Jaringan Teknik Informatika. Asisten di Teknik Informatika. Programmer Satgas Pengolah Data Institut ITB. Dapat dihubungi di yohanes@opensource.or.id.

mw

Arsip mwmag  [Up]www.master.web.id/mwmag