Cách tốt nhất để theo dõi nhật ký và thực thi lệnh khi một số văn bản xuất hiện trong nhật ký


54

Tôi có một nhật ký máy chủ xuất ra một dòng văn bản cụ thể vào tệp nhật ký của nó khi máy chủ hoạt động. Tôi muốn thực thi một lệnh khi máy chủ hoạt động, và do đó thực hiện một số việc như sau:

tail -f /path/to/serverLog | grep "server is up" ...(now, e.g., wget on server)?

Cách tốt nhất để làm việc này là gì?


3
hãy chắc chắn sử dụng tail -Fđể xử lý xoay vòng nhật ký - tức là my.logtrở nên đầy đủ và di chuyển đến my.log.1và quá trình của bạn tạo ra một mớimy.log
sg

Câu trả lời:


34

Một cách đơn giản sẽ là awk.

tail -f /path/to/serverLog | awk '
                    /Printer is on fire!/ { system("shutdown -h now") }
                    /new USB high speed/  { system("echo \"New USB\" | mail admin") }'

Và vâng, cả hai đều là tin nhắn thực từ nhật ký kernel. Perl có thể thanh lịch hơn một chút để sử dụng cho việc này và cũng có thể thay thế nhu cầu về đuôi. Nếu sử dụng perl, nó sẽ trông giống như thế này:

open(my $fd, "<", "/path/to/serverLog") or die "Can't open log";
while(1) {
    if(eof $fd) {
        sleep 1;
        $fd->clearerr;
        next;
    }
    my $line = <$fd>;
    chomp($line);
    if($line =~ /Printer is on fire!/) {
        system("shutdown -h now");
    } elsif($line =~ /new USB high speed/) {
        system("echo \"New USB\" | mail admin");
    }
}

Tôi thích awkgiải pháp cho việc ngắn gọn và dễ thực hiện khi đang bay trong một dòng. Tuy nhiên, nếu lệnh tôi muốn chạy có dấu ngoặc kép, thì điều này có thể hơi phức tạp, có một cách khác, có thể sử dụng các đường ống và lệnh ghép cũng cho phép giải pháp một lớp ngắn gọn nhưng không yêu cầu lệnh kết quả được thông qua như một chuỗi?
jonderry

1
@jon Bạn có thể viết awk như một kịch bản. Sử dụng "#! / Usr / bin awk -f" làm dòng đầu tiên của tập lệnh. Điều này sẽ loại bỏ sự cần thiết cho các trích dẫn đơn bên ngoài trong ví dụ của tôi và giải phóng chúng để sử dụng bên trong một system()lệnh.
chim cánh cụt359

@ penguin359, Đúng, nhưng vẫn rất thú vị khi làm điều đó từ dòng lệnh. Trong trường hợp của tôi, có rất nhiều điều khác nhau tôi muốn làm bao gồm nhiều điều tôi không thể thấy trước, vì vậy thật tiện lợi khi có thể chỉ cần khởi động máy chủ và thực hiện tất cả trong một dòng.
jonderry

Tôi đã tìm thấy một giải pháp thay thế, mặc dù tôi không biết nó rắn đến mức nào:tail -f /path/to/serverLog | grep "server is up" | head -1 && do_some_command
jonderry

@jon Điều đó có vẻ hơi mong manh khi sử dụng đầu theo cách đó. Quan trọng hơn, nó không lặp lại như ví dụ của tôi. Nếu "máy chủ hoạt động" nằm trong mười dòng cuối cùng của nhật ký, nó sẽ kích hoạt lệnh và thoát ngay lập tức. Nếu bạn khởi động lại nó, rất có thể nó sẽ kích hoạt và thoát lại trừ khi mười dòng không chứa "máy chủ" đã được thêm vào nhật ký. Một sửa đổi có thể hoạt động tốt hơn là tail -n 0 -f /path/to/serverLog Điều đó sẽ đọc 0 dòng cuối cùng của tệp, sau đó chờ thêm dòng để in.
chim cánh cụt359

16

Nếu bạn chỉ tìm kiếm một khả năng và muốn chủ yếu ở trong vỏ thay vì sử dụng awkhoặc perl, bạn có thể làm một cái gì đó như:

tail -F /path/to/serverLog | 
grep --line-buffered 'server is up' | 
while read ; do my_command ; done

... Nó sẽ chạy my_commandmỗi khi " máy chủ hoạt động " xuất hiện trong tệp nhật ký. Đối với nhiều khả năng, bạn có thể bỏ grepvà thay vào đó sử dụng một casetrong while.

Thủ đô -Fbảo tailphải xem tệp nhật ký được xoay; tức là nếu tệp hiện tại được đổi tên và một tệp khác có cùng tên sẽ thay thế, tailsẽ chuyển sang tệp mới.

Các --line-bufferedtùy chọn kể grepđể tuôn ra bộ đệm của nó sau mỗi dòng; mặt khác, my_commandcó thể không đạt được một cách kịp thời (giả sử các bản ghi có các dòng có kích thước hợp lý).


2
Tôi thực sự thích câu trả lời này, nhưng nó không hiệu quả với tôi lúc đầu. Tôi nghĩ rằng bạn cần thêm --line-bufferedtùy chọn này grep, hoặc nếu không, hãy chắc chắn rằng nó tuôn ra đầu ra giữa các dòng: nếu không, nó chỉ bị treo và my_commandkhông bao giờ đạt được. Nếu bạn thích ack, nó có một --flushlá cờ; nếu bạn thích ag, hãy thử gói với stdbuf. stackoverflow.com/questions/28982518/ từ
doctaphred

Tôi đã thử điều này, bằng cách sử dụng do exit ;. Nó có vẻ hoạt động tốt, nhưng đuôi không bao giờ kết thúc và kịch bản của chúng tôi không bao giờ chuyển sang dòng tiếp theo. Có cách nào để dừng đuôi trong dophần?
Machtyn

14

Câu hỏi này dường như đã được trả lời, nhưng tôi nghĩ có một giải pháp tốt hơn.

Thay vì tail | whatever, tôi nghĩ những gì bạn thực sự muốn là swatch. Swatch là một chương trình được thiết kế rõ ràng để thực hiện những gì bạn yêu cầu, xem tệp nhật ký và thực hiện các hành động dựa trên các dòng nhật ký. Sử dụng tail|foosẽ yêu cầu bạn có một thiết bị đầu cuối tích cực chạy để làm điều này. Swatch mặt khác chạy như một daemon và sẽ luôn luôn xem nhật ký của bạn. Swatch có sẵn trong tất cả các bản phân phối Linux,

Tôi khuyến khích bạn thử nó. Mặc dù bạn có thể đóng đinh vào mặt sau của tuốc nơ vít không có nghĩa là bạn nên làm vậy.

Hướng dẫn 30 giây tốt nhất về swatch tôi có thể tìm thấy ở đây: http://www.campin.net/newlogcheck.html


1
Hướng dẫn đó là 404 ngay bây giờ: /
từ đó

1
Sun

10

Điều kỳ lạ là không ai đề cập đến multitailtiện ích có sẵn chức năng này. Một trong những ví dụ sử dụng:

Hiển thị đầu ra của lệnh ping và nếu nó hiển thị thời gian chờ, hãy gửi tin nhắn cho tất cả người dùng hiện đang đăng nhập

multitail -ex timeout "echo timeout | wall" -l "ping 192.168.0.1"

Xem thêm một ví dụ của multitailviệc sử dụng.


+1, tôi không biết `đa nhiệm có những loại kỹ năng ninja như vậy. Cảm ơn đã chỉ ra rằng.
Caleb

8

có thể tự làm công việc

Hãy xem nó đơn giản và dễ đọc như thế nào:

mylog() {
    echo >>/path/to/myscriptLog "$@"
}

while read line;do
    case "$line" in
        *"Printer on fire"* )
            mylog Halting immediately
            shutdown -h now
            ;;
        *DHCPREQUEST* )
            [[ "$line" =~ DHCPREQUEST\ for\ ([^\ ]*)\  ]]
            mylog Incomming or refresh for ${BASH_REMATCH[1]}
            $HOME/SomethingWithNewClient ${BASH_REMATCH[1]}
            ;;
        * )
            mylog "untrapped entry: $line"
            ;;
    esac
  done < <(tail -f /path/to/logfile)

Mặc dù bạn không sử dụng bash's regex, nhưng điều này có thể rất nhanh!

Nhưng + là một song song rất hiệu quả và thú vị

Nhưng đối với máy chủ tải cao và như tôi thích sedvì nó rất nhanh và rất có thể mở rộng, tôi thường sử dụng điều này:

while read event target lost ; do
    case $event in
        NEW )
            ip2int $target intTarget
            ((count[intTarget]++))
        ...

    esac
done < <(tail -f /path/logfile | sed -une '
  s/^.*New incom.*from ip \([0-9.]\+\) .*$/NEW \1/p;
  s/^.*Auth.*ip \([0-9.]\+\) failed./FAIL \1/p;
  ...
')

6

Đó là cách tôi bắt đầu làm điều này quá nhưng đã trở nên tinh vi hơn nhiều với nó. Một vài điều cần quan tâm:

  1. Nếu đuôi của nhật ký đã chứa "máy chủ hoạt động".
  2. Tự động kết thúc quá trình đuôi khi nó được tìm thấy.

Tôi sử dụng một cái gì đó dọc theo dòng này:

RELEASE=/tmp/${RANDOM}$$
(
  trap 'false' 1
  trap "rm -f ${RELEASE}" 0
  while ! [ -s ${RELEASE} ]; do sleep 3; done
  # You can put code here if you want to do something
  # once the grep succeeds.
) & wait_pid=$!
tail --pid=${wait_pid} -F /path/to/serverLog \
| sed "1,10d" \
| grep "server is up" > ${RELEASE}

Nó hoạt động bằng cách giữ tailmở cho đến khi ${RELEASE}tập tin chứa dữ liệu.

Một khi grepthành công nó:

  1. viết đầu ra để ${RELEASE}mà sẽ
  2. chấm dứt ${wait_pid}quá trình để
  3. thoát khỏi tail

Lưu ý: Có sedthể tinh vi hơn để thực sự xác định số lượng dòng tailsẽ tạo khi khởi động và loại bỏ số đó. Nhưng nói chung, nó là 10.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.