Ứng dụng thực tế của ELK. Thiết lập nhật ký

Giới thiệu

Trong khi triển khai một hệ thống khác, chúng tôi gặp phải nhu cầu xử lý một số lượng lớn nhật ký khác nhau. ELK được chọn làm công cụ. Bài viết này sẽ thảo luận về kinh nghiệm của chúng tôi trong việc thiết lập ngăn xếp này.

Chúng tôi không đặt mục tiêu mô tả hết khả năng của nó mà muốn tập trung cụ thể vào việc giải quyết các vấn đề thực tế. Điều này là do mặc dù có một lượng tài liệu và hình ảnh làm sẵn khá lớn nhưng cũng có khá nhiều cạm bẫy nhưng ít nhất chúng tôi đã tìm thấy chúng.

Chúng tôi đã triển khai ngăn xếp thông qua docker-compose. Hơn nữa, chúng tôi đã có docker-compose.yml được viết tốt, cho phép chúng tôi nâng cấp ngăn xếp hầu như không gặp vấn đề gì. Và đối với chúng tôi, dường như chiến thắng đã đến rất gần, bây giờ chúng tôi sẽ điều chỉnh nó một chút cho phù hợp với nhu cầu của mình và thế là xong.

Thật không may, nỗ lực định cấu hình hệ thống để nhận và xử lý nhật ký từ ứng dụng của chúng tôi đã không thành công ngay lập tức. Do đó, chúng tôi quyết định rằng cần nghiên cứu từng thành phần riêng biệt và sau đó quay lại các kết nối của chúng.

Vì vậy, chúng tôi bắt đầu với logstash.

Môi trường, triển khai, chạy Logstash trong container

Để triển khai, chúng tôi sử dụng docker-compose; các thử nghiệm được mô tả ở đây được thực hiện trên MacOS và Ubuntu 18.0.4.

Hình ảnh logstash đã được đăng ký trong docker-compose.yml ban đầu của chúng tôi là docker.elastic.co/logstash/logstash:6.3.2

Chúng tôi sẽ sử dụng nó cho các thí nghiệm.

Chúng tôi đã viết một docker-compose.yml riêng để chạy logstash. Tất nhiên, có thể khởi chạy hình ảnh từ dòng lệnh, nhưng chúng tôi đang giải quyết một vấn đề cụ thể, trong đó chúng tôi chạy mọi thứ từ docker-compose.

Sơ lược về file cấu hình

Như sau mô tả, logstash có thể được chạy cho một kênh, trong trường hợp đó, nó cần chuyển tệp *.conf hoặc cho một số kênh, trong trường hợp đó, nó cần chuyển tệp pipes.yml, do đó, tệp này , sẽ liên kết đến các tập tin .conf cho mỗi kênh.
Chúng tôi đã đi theo con đường thứ hai. Đối với chúng tôi, nó dường như phổ quát hơn và có thể mở rộng hơn. Do đó, chúng tôi đã tạo pipes.yml và tạo một thư mục pipes trong đó chúng tôi sẽ đặt các tệp .conf cho mỗi kênh.

Bên trong vùng chứa có một tệp cấu hình khác - logstash.yml. Chúng tôi không chạm vào nó, chúng tôi sử dụng nó như vậy.

Vì vậy, cấu trúc thư mục của chúng tôi:

Ứng dụng thực tế của ELK. Thiết lập nhật ký

Để nhận dữ liệu đầu vào, hiện tại chúng tôi giả định rằng đây là tcp trên cổng 5046 và đối với đầu ra, chúng tôi sẽ sử dụng thiết bị xuất chuẩn.

Đây là cấu hình đơn giản cho lần khởi chạy đầu tiên. Bởi vì nhiệm vụ ban đầu là khởi động.

Vì vậy, chúng ta có docker-compose.yml này

version: '3'

networks:
  elk:

volumes:
  elasticsearch:
    driver: local

services:

  logstash:
    container_name: logstash_one_channel
    image: docker.elastic.co/logstash/logstash:6.3.2
    networks:
      	- elk
    ports:
      	- 5046:5046
    volumes:
      	- ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
	- ./config/pipelines:/usr/share/logstash/config/pipelines:ro

Chúng ta thấy gì ở đây?

  1. Mạng và khối lượng được lấy từ docker-compose.yml ban đầu (nơi khởi chạy toàn bộ ngăn xếp) và tôi nghĩ rằng chúng không ảnh hưởng lớn đến bức tranh tổng thể ở đây.
  2. Chúng tôi tạo một (các) dịch vụ logstash từ hình ảnh docker.elastic.co/logstash/logstash:6.3.2 và đặt tên là logstash_one_channel.
  3. Chúng tôi chuyển tiếp cổng 5046 bên trong container đến cùng một cổng nội bộ.
  4. Chúng tôi ánh xạ tệp cấu hình đường ống ./config/pipelines.yml của mình vào tệp /usr/share/logstash/config/pipelines.yml bên trong vùng chứa, nơi logstash sẽ chọn nó và đặt nó ở chế độ chỉ đọc, đề phòng.
  5. Chúng tôi ánh xạ thư mục ./config/pipelines, nơi chúng tôi có các tệp có cài đặt kênh, vào thư mục /usr/share/logstash/config/pipelines và cũng đặt nó ở chế độ chỉ đọc.

Ứng dụng thực tế của ELK. Thiết lập nhật ký

Tệp Pipelines.yml

- pipeline.id: HABR
  pipeline.workers: 1
  pipeline.batch.size: 1
  path.config: "./config/pipelines/habr_pipeline.conf"

Một kênh có mã định danh HABR và đường dẫn đến tệp cấu hình của nó được mô tả ở đây.

Và cuối cùng là tập tin “./config/pipelines/habr_pipeline.conf”

input {
  tcp {
    port => "5046"
   }
  }
filter {
  mutate {
    add_field => [ "habra_field", "Hello Habr" ]
    }
  }
output {
  stdout {
      
    }
  }

Bây giờ chúng ta đừng đi vào phần mô tả của nó, hãy thử chạy nó:

docker-compose up

Chúng ta thấy gì?

Thùng chứa đã bắt đầu. Chúng ta có thể kiểm tra hoạt động của nó:

echo '13123123123123123123123213123213' | nc localhost 5046

Và chúng tôi thấy phản hồi trong bảng điều khiển vùng chứa:

Ứng dụng thực tế của ELK. Thiết lập nhật ký

Nhưng đồng thời, chúng ta cũng thấy:

logstash_one_channel | [2019-04-29T11:28:59,790][ERROR][logstash.licensechecker.licensereader] Không thể truy xuất thông tin giấy phép từ máy chủ cấp phép {:message=>“Elasticsearch Unreachable: [http://elasticsearch:9200/][Manticore ::ResolutionFailure] elaticsearch", ...

logstash_one_channel | [2019-04-29T11:28:59,894][INFO ][logstash.pipeline] Đường ống đã khởi động thành công {:pipeline_id=>".monitoring-logstash", :thread=>"# "}

logstash_one_channel | [2019-04-29T11:28:59,988][INFO ][logstash.agent ] Đường ống chạy {:count=>2, :running_pipelines=>[:HABR, :".monitoring-logstash"], :non_running_pipelines=>[ ]}
logstash_one_channel | [2019-04-29T11:29:00,015][ERROR][logstash.inputs.metrics] X-Pack được cài đặt trên Logstash nhưng không được cài đặt trên Elaticsearch. Vui lòng cài đặt X-Pack trên Elaticsearch để sử dụng tính năng giám sát. Các tính năng khác có thể có sẵn.
logstash_one_channel | [2019-04-29T11:29:00,526][INFO ][logstash.agent ] Đã khởi động thành công điểm cuối API Logstash {:port=>9600}
logstash_one_channel | [2019-04-29T11:29:04,478][INFO ][logstash.outputs.elasticsearch] Chạy kiểm tra tình trạng để xem kết nối Elaticsearch có hoạt động hay không {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,487][WARN ][logstash.outputs.elasticsearch] Đã cố gắng khôi phục kết nối với phiên bản ES đã chết nhưng gặp lỗi. {:url=>“nghiên cứu:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch Unreachable: [http://elasticsearch:9200/][Manticore::ResolutionFailure] elaticsearch"}
logstash_one_channel | [2019-04-29T11:29:04,704][INFO ][logstash.licensechecker.licensereader] Chạy kiểm tra tình trạng để xem kết nối Elaticsearch có hoạt động hay không {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,710][WARN ][logstash.licensechecker.licensereader] Đã cố gắng khôi phục kết nối với phiên bản ES đã chết nhưng gặp lỗi. {:url=>“nghiên cứu:9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch Unreachable: [http://elasticsearch:9200/][Manticore::ResolutionFailure] elaticsearch"}

Và nhật ký của chúng tôi ngày càng tăng lên.

Ở đây tôi đã đánh dấu thông báo màu xanh lục rằng quy trình đã khởi chạy thành công, thông báo lỗi màu đỏ và thông báo màu vàng về nỗ lực liên hệ nghiên cứu: 9200.
Điều này xảy ra vì logstash.conf, có trong hình ảnh, chứa phần kiểm tra tính khả dụng của elaticsearch. Rốt cuộc, logstash giả định rằng nó hoạt động như một phần của ngăn xếp Elk, nhưng chúng tôi đã tách nó ra.

Có thể làm việc nhưng không thuận tiện.

Giải pháp là tắt tính năng kiểm tra này thông qua biến môi trường XPACK_MONITORING_ENABLED.

Hãy thay đổi docker-compose.yml và chạy lại:

version: '3'

networks:
  elk:

volumes:
  elasticsearch:
    driver: local

services:

  logstash:
    container_name: logstash_one_channel
    image: docker.elastic.co/logstash/logstash:6.3.2
    networks:
      - elk
    environment:
      XPACK_MONITORING_ENABLED: "false"
    ports:
      - 5046:5046
   volumes:
      - ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
      - ./config/pipelines:/usr/share/logstash/config/pipelines:ro

Bây giờ, mọi thứ đều ổn. Thùng chứa đã sẵn sàng cho các thí nghiệm.

Chúng ta có thể gõ lại trong bảng điều khiển tiếp theo:

echo '13123123123123123123123213123213' | nc localhost 5046

Và nhìn thấy:

logstash_one_channel | {
logstash_one_channel |         "message" => "13123123123123123123123213123213",
logstash_one_channel |      "@timestamp" => 2019-04-29T11:43:44.582Z,
logstash_one_channel |        "@version" => "1",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |            "host" => "gateway",
logstash_one_channel |            "port" => 49418
logstash_one_channel | }

Làm việc trong một kênh

Vì vậy, chúng tôi đã phát động. Bây giờ bạn thực sự có thể dành thời gian để tự cấu hình logstash. Bây giờ chúng ta đừng chạm vào tệp pipes.yml, hãy xem chúng ta có thể nhận được gì khi làm việc với một kênh.

Tôi phải nói rằng nguyên tắc chung khi làm việc với tệp cấu hình kênh được mô tả rõ ràng trong hướng dẫn chính thức tại đây đây
Nếu bạn muốn đọc bằng tiếng Nga, chúng tôi đã sử dụng cái này bài báo(nhưng cú pháp truy vấn đã cũ, chúng ta cần tính đến điều này).

Chúng ta hãy đi tuần tự từ phần Đầu vào. Chúng tôi đã thấy công việc trên TCP. Điều gì khác có thể thú vị ở đây?

Kiểm tra tin nhắn bằng nhịp tim

Có một cơ hội thú vị để tạo các thông báo kiểm tra tự động.
Để làm điều này, bạn cần kích hoạt plugin heartbean trong phần nhập liệu.

input {
  heartbeat {
    message => "HeartBeat!"
   }
  } 

Bật nó lên, bắt đầu nhận mỗi phút một lần

logstash_one_channel | {
logstash_one_channel |      "@timestamp" => 2019-04-29T13:52:04.567Z,
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |         "message" => "HeartBeat!",
logstash_one_channel |        "@version" => "1",
logstash_one_channel |            "host" => "a0667e5c57ec"
logstash_one_channel | }

Nếu muốn nhận thường xuyên hơn, chúng ta cần thêm tham số khoảng thời gian.
Đây là cách chúng tôi sẽ nhận được tin nhắn cứ sau 10 giây.

input {
  heartbeat {
    message => "HeartBeat!"
    interval => 10
   }
  }

Lấy dữ liệu từ một tập tin

Chúng tôi cũng quyết định xem xét chế độ tập tin. Nếu nó hoạt động tốt với tệp thì có lẽ không cần tác nhân nào, ít nhất là để sử dụng cục bộ.

Theo mô tả, chế độ hoạt động phải tương tự như tail -f, tức là. đọc các dòng mới hoặc, như một tùy chọn, đọc toàn bộ tệp.

Vì vậy, những gì chúng tôi muốn nhận được:

  1. Chúng tôi muốn nhận các dòng được thêm vào một tệp nhật ký.
  2. Chúng tôi muốn nhận dữ liệu được ghi vào một số tệp nhật ký, đồng thời có thể tách biệt những gì nhận được từ đâu.
  3. Chúng tôi muốn đảm bảo rằng khi khởi động lại logstash, nó sẽ không nhận lại dữ liệu này.
  4. Chúng tôi muốn kiểm tra xem nếu logstash bị tắt và dữ liệu tiếp tục được ghi vào tệp thì khi chạy nó, chúng tôi sẽ nhận được dữ liệu này.

Để tiến hành thử nghiệm, hãy thêm một dòng khác vào docker-compose.yml, mở thư mục chứa các tệp.

version: '3'

networks:
  elk:

volumes:
  elasticsearch:
    driver: local

services:

  logstash:
    container_name: logstash_one_channel
    image: docker.elastic.co/logstash/logstash:6.3.2
    networks:
      - elk
    environment:
      XPACK_MONITORING_ENABLED: "false"
    ports:
      - 5046:5046
   volumes:
      - ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
      - ./config/pipelines:/usr/share/logstash/config/pipelines:ro
      - ./logs:/usr/share/logstash/input

Và thay đổi phần đầu vào trong habr_pipeline.conf

input {
  file {
    path => "/usr/share/logstash/input/*.log"
   }
  }

Hãy bắt đầu:

docker-compose up

Để tạo và ghi file log chúng ta sẽ sử dụng lệnh:


echo '1' >> logs/number1.log

{
logstash_one_channel |            "host" => "ac2d4e3ef70f",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |      "@timestamp" => 2019-04-29T14:28:53.876Z,
logstash_one_channel |        "@version" => "1",
logstash_one_channel |         "message" => "1",
logstash_one_channel |            "path" => "/usr/share/logstash/input/number1.log"
logstash_one_channel | }

Đúng, nó hoạt động!

Đồng thời, chúng tôi thấy rằng chúng tôi đã tự động thêm trường đường dẫn. Điều này có nghĩa là trong tương lai, chúng tôi sẽ có thể lọc các bản ghi theo nó.

Hãy thử lại lần nữa:

echo '2' >> logs/number1.log

{
logstash_one_channel |            "host" => "ac2d4e3ef70f",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |      "@timestamp" => 2019-04-29T14:28:59.906Z,
logstash_one_channel |        "@version" => "1",
logstash_one_channel |         "message" => "2",
logstash_one_channel |            "path" => "/usr/share/logstash/input/number1.log"
logstash_one_channel | }

Và bây giờ đến một tập tin khác:

 echo '1' >> logs/number2.log

{
logstash_one_channel |            "host" => "ac2d4e3ef70f",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |      "@timestamp" => 2019-04-29T14:29:26.061Z,
logstash_one_channel |        "@version" => "1",
logstash_one_channel |         "message" => "1",
logstash_one_channel |            "path" => "/usr/share/logstash/input/number2.log"
logstash_one_channel | }

Tuyệt vời! Tệp đã được chọn, đường dẫn được chỉ định chính xác, mọi thứ đều ổn.

Dừng logstash và bắt đầu lại. Hãy chờ đợi. Im lặng. Những thứ kia. Chúng tôi không nhận được những hồ sơ này nữa.

Và bây giờ là thí nghiệm táo bạo nhất.

Cài đặt logstash và thực thi:

echo '3' >> logs/number2.log
echo '4' >> logs/number1.log

Chạy lại logstash và xem:

logstash_one_channel | {
logstash_one_channel |            "host" => "ac2d4e3ef70f",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |         "message" => "3",
logstash_one_channel |        "@version" => "1",
logstash_one_channel |            "path" => "/usr/share/logstash/input/number2.log",
logstash_one_channel |      "@timestamp" => 2019-04-29T14:48:50.589Z
logstash_one_channel | }
logstash_one_channel | {
logstash_one_channel |            "host" => "ac2d4e3ef70f",
logstash_one_channel |     "habra_field" => "Hello Habr",
logstash_one_channel |         "message" => "4",
logstash_one_channel |        "@version" => "1",
logstash_one_channel |            "path" => "/usr/share/logstash/input/number1.log",
logstash_one_channel |      "@timestamp" => 2019-04-29T14:48:50.856Z
logstash_one_channel | }

Hoan hô! Mọi thứ đã được nhặt lên.

Nhưng chúng tôi phải cảnh báo bạn về những điều sau đây. Nếu vùng chứa logstash bị xóa (docker stop logstash_one_channel && docker rm logstash_one_channel), thì sẽ không có gì được chọn. Vị trí của tệp mà nó được đọc được lưu trữ bên trong vùng chứa. Nếu bạn chạy nó từ đầu, nó sẽ chỉ chấp nhận các dòng mới.

Đọc các tập tin hiện có

Giả sử chúng tôi đang khởi chạy logstash lần đầu tiên nhưng chúng tôi đã có nhật ký và chúng tôi muốn xử lý chúng.
Nếu chúng tôi chạy logstash với phần đầu vào mà chúng tôi đã sử dụng ở trên, chúng tôi sẽ không nhận được gì. Chỉ những dòng mới sẽ được xử lý bằng logstash.

Để kéo các dòng từ file hiện có lên, bạn nên thêm một dòng bổ sung vào phần nhập:

input {
  file {
    start_position => "beginning"
    path => "/usr/share/logstash/input/*.log"
   }
  }

Hơn nữa, có một sắc thái: điều này chỉ ảnh hưởng đến các tệp mới mà logstash chưa thấy. Đối với các tệp tương tự đã có trong trường xem logstash, nó đã ghi nhớ kích thước của chúng và giờ đây sẽ chỉ nhận các mục nhập mới trong đó.

Hãy dừng lại ở đây và nghiên cứu phần đầu vào. Vẫn còn nhiều lựa chọn, nhưng hiện tại như vậy là đủ để chúng tôi thử nghiệm thêm.

Định tuyến và chuyển đổi dữ liệu

Hãy thử giải quyết vấn đề sau, giả sử chúng ta có thông báo từ một kênh, một số trong số đó là thông tin và một số là thông báo lỗi. Chúng khác nhau theo thẻ. Một số là THÔNG TIN, số khác là LỖI.

Chúng ta cần tách chúng ra ở lối ra. Những thứ kia. Chúng tôi viết thông báo thông tin trong một kênh và thông báo lỗi ở kênh khác.

Để làm điều này, hãy chuyển từ phần đầu vào sang bộ lọc và đầu ra.

Bằng cách sử dụng phần bộ lọc, chúng tôi sẽ phân tích cú pháp tin nhắn đến, lấy hàm băm (cặp khóa-giá trị) từ nó mà chúng tôi có thể làm việc với nó, tức là. tháo rời theo điều kiện. Và ở phần đầu ra, chúng ta sẽ chọn tin nhắn và gửi từng tin nhắn đến kênh riêng.

Phân tích tin nhắn bằng Grok

Để phân tích các chuỗi văn bản và lấy một tập hợp các trường từ chúng, có một plugin đặc biệt trong phần bộ lọc - grok.

Không đặt ra mục tiêu cho mình là đưa ra một mô tả chi tiết về nó ở đây (để làm điều này tôi tham khảo tài liệu chính thức), tôi sẽ đưa ra ví dụ đơn giản của mình.

Để làm điều này, bạn cần quyết định định dạng của chuỗi đầu vào. Tôi có chúng như thế này:

1 tin nhắn THÔNG TIN1
2 thông báo LỖI2

Những thứ kia. Mã định danh xuất hiện đầu tiên, sau đó là THÔNG TIN/LỖI, sau đó là một số từ không có dấu cách.
Nó không khó nhưng đủ để hiểu nguyên lý hoạt động.

Vì vậy, trong phần bộ lọc của plugin Grok, chúng ta phải xác định mẫu để phân tích chuỗi của mình.

Nó sẽ trông giống thế này:

filter {
  grok {
    match => { "message" => ["%{INT:message_id} %{LOGLEVEL:message_type} %{WORD:message_text}"] }
   }
  } 

Về cơ bản nó là một biểu thức chính quy. Các mẫu tạo sẵn được sử dụng, chẳng hạn như INT, LOGLEVEL, WORD. Bạn có thể tìm thấy mô tả của chúng cũng như các mẫu khác tại đây đây

Bây giờ, khi đi qua bộ lọc này, chuỗi của chúng ta sẽ biến thành hàm băm gồm ba trường: message_id, message_type, message_text.

Chúng sẽ được hiển thị trong phần đầu ra.

Định tuyến tin nhắn đến phần đầu ra bằng lệnh if

Trong phần đầu ra, như chúng ta nhớ, chúng ta sẽ chia tin nhắn thành hai luồng. Một số - là iNFO, sẽ được xuất ra bảng điều khiển và nếu có lỗi, chúng tôi sẽ xuất ra một tệp.

Làm cách nào để phân tách các tin nhắn này? Tình trạng của vấn đề đã gợi ý giải pháp - xét cho cùng, chúng ta đã có trường message_type chuyên dụng, trường này chỉ có thể nhận hai giá trị: INFO và ERROR. Trên cơ sở này, chúng ta sẽ đưa ra lựa chọn bằng cách sử dụng câu lệnh if.

if [message_type] == "ERROR" {
        # Здесь выводим в файл
       } else
     {
      # Здесь выводим в stdout
    }

Bạn có thể tìm thấy mô tả cách làm việc với các trường và toán tử trong phần này hướng dẫn chính thức.

Bây giờ, về kết luận thực tế.

Đầu ra của bảng điều khiển, mọi thứ đều rõ ràng ở đây - stdout {}

Nhưng đầu ra cho một tệp - hãy nhớ rằng chúng ta đang chạy tất cả những thứ này từ một vùng chứa và để tệp mà chúng ta ghi kết quả có thể truy cập được từ bên ngoài, chúng ta cần mở thư mục này trong docker-compose.yml.

Tổng số:

Phần đầu ra của tệp của chúng tôi trông như thế này:


output {
  if [message_type] == "ERROR" {
    file {
          path => "/usr/share/logstash/output/test.log"
          codec => line { format => "custom format: %{message}"}
         }
    } else
     {stdout {
             }
     }
  }

Trong docker-compose.yml chúng tôi thêm một ổ đĩa khác cho đầu ra:

version: '3'

networks:
  elk:

volumes:
  elasticsearch:
    driver: local

services:

  logstash:
    container_name: logstash_one_channel
    image: docker.elastic.co/logstash/logstash:6.3.2
    networks:
      - elk
    environment:
      XPACK_MONITORING_ENABLED: "false"
    ports:
      - 5046:5046
   volumes:
      - ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
      - ./config/pipelines:/usr/share/logstash/config/pipelines:ro
      - ./logs:/usr/share/logstash/input
      - ./output:/usr/share/logstash/output

Chúng tôi khởi chạy nó, dùng thử và thấy sự phân chia thành hai luồng.

Nguồn: www.habr.com

Thêm một lời nhận xét