Cơ chế sự kiện toàn cầu JavaScript


350

Tôi muốn bắt tất cả các lỗi chức năng không xác định ném. Có một cơ sở xử lý lỗi toàn cầu trong JavaScript? Ca sử dụng đang bắt các cuộc gọi chức năng từ flash không được xác định.


Bạn muốn làm gì với một lỗi khi bạn bắt nó? Bạn chỉ cần đăng nhập nó để bạn có thể tạo chức năng bị thiếu, hoặc bạn đang tìm cách ngăn chặn ngoại lệ phá vỡ mã của bạn?
Dan Herbert

2
Tôi muốn lấy tên của hàm bị thiếu được gọi và dựa trên sự hiện diện của một số chuỗi gọi hàm riêng của tôi. Bất kỳ cuộc gọi nào đến một hàm có chuỗi 'close' sẽ gọi hàm close () của tôi chẳng hạn. Tôi cũng muốn bẫy lỗi tại thời điểm đó.

2
exceptionsjs.com cung cấp chức năng này và có thể được xử lý để chỉ bắt các lỗi liên quan đến chức năng không xác định với chức năng "bảo vệ" của nó.
Steven Wexler

Câu trả lời:


192

Điều này có giúp bạn không:

<script type="text/javascript">
window.onerror = function() {
    alert("Error caught");
};

xxx();
</script>

Tôi không chắc cách nó xử lý lỗi Flash mặc dù ...

Cập nhật: nó không hoạt động trong Opera, nhưng tôi đang hack Dragonfly ngay bây giờ để xem những gì nó nhận được. Gợi ý về việc hack Dragonfly xuất phát từ câu hỏi này:

Cửa sổ bắt chước. onerror trong Opera bằng cách sử dụng javascript


3
Với việc bổ sung các thông điệp, file_loc, line_no, điều này sẽ làm điều đó cho tôi. Cảm ơn!

1
Tôi có hiểu chính xác rằng mã này ghi đè bất kỳ trình xử lý lỗi hiện có nào không?
Mars Robertson

@MarsRobertson số
atilkan

@atilkan window.onerror = function() { alert(42) };bây giờ mã trong câu trả lời: window.onerror = function() { alert("Error caught"); };không overriden, tôi vẫn không chắc chắn ..
Mars Robertson

@MarsRobertson Tôi hiểu. Nó có thể ghi đè lên. Vâng, nó có, chỉ cần thử nghiệm. Sử dụng addEventListener sẽ tốt hơn.
atilkan

646

Cách bắt lỗi Javascript chưa xử lý

Chỉ định window.onerrorsự kiện cho một trình xử lý sự kiện như:

<script type="text/javascript">
window.onerror = function(msg, url, line, col, error) {
   // Note that col & error are new to the HTML 5 spec and may not be 
   // supported in every browser.  It worked for me in Chrome.
   var extra = !col ? '' : '\ncolumn: ' + col;
   extra += !error ? '' : '\nerror: ' + error;

   // You can view the information in an alert to see things working like this:
   alert("Error: " + msg + "\nurl: " + url + "\nline: " + line + extra);

   // TODO: Report this error via ajax so you can keep track
   //       of what pages have JS issues

   var suppressErrorAlert = true;
   // If you return true, then error alerts (like in older versions of 
   // Internet Explorer) will be suppressed.
   return suppressErrorAlert;
};
</script>

Như đã nhận xét trong mã, nếu giá trị trả về window.onerrortruethì trình duyệt sẽ chặn hiển thị hộp thoại cảnh báo.

Khi nào window.onerror Event Fire?

Tóm lại, sự kiện được nêu ra khi 1.) có ngoại lệ chưa được xử lý hoặc 2.) xảy ra lỗi thời gian biên dịch.

ngoại lệ

  • ném "vài tin nhắn"
  • call_s Something_und xác định ();
  • cross_origin_iframe.contentWindow.document;, một ngoại lệ bảo mật

Lỗi biên dịch

  • <script>{</script>
  • <script>for(;)</script>
  • <script>"oops</script>
  • setTimeout("{", 10);, nó sẽ cố gắng biên dịch đối số đầu tiên dưới dạng tập lệnh

Trình duyệt hỗ trợ window.onerror

  • Chrome 13+
  • Firefox 6.0+
  • Internet Explorer 5.5+
  • Opera 11,60+
  • Safari 5.1+

Ảnh chụp màn hình:

Ví dụ về mã onerror ở trên trong hành động sau khi thêm mã này vào trang thử nghiệm:

<script type="text/javascript">
call_something_undefined();
</script>

Thông báo Javascript hiển thị thông tin lỗi được chi tiết bởi sự kiện window.onerror

Ví dụ cho báo cáo lỗi AJAX

var error_data = {
    url: document.location.href,
};

if(error != null) {
    error_data['name'] = error.name; // e.g. ReferenceError
    error_data['message'] = error.line;
    error_data['stack'] = error.stack;
} else {
    error_data['msg'] = msg;
    error_data['filename'] = filename;
    error_data['line'] = line;
    error_data['col'] = col;
}

var xhr = new XMLHttpRequest();

xhr.open('POST', '/ajax/log_javascript_error');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
    if (xhr.status === 200) {
        console.log('JS error logged');
    } else if (xhr.status !== 200) {
        console.error('Failed to log JS error.');
        console.error(xhr);
        console.error(xhr.status);
        console.error(xhr.responseText);
    }
};
xhr.send(JSON.stringify(error_data));

Mã thông báo:

https://jsfiddle.net/nzfvm44d/

Người giới thiệu:


3
đáng nói đến firefox không trả lại thông báo lỗi khi throwđược thực hiện thủ công. stackoverflow.com/questions/15036165/ cường
Sebas

2
Câu trả lời chính xác. Bạn có thể triển khai "Báo cáo lỗi này thông qua ajax" với gói như JSNLog, đó là bản ghi ajax và phía máy chủ cho bạn.
dùng1147862

4
Ngoài câu trả lời này, tôi đã thêm đối tượng err để tôi có thể theo dõi ngăn xếp. stackoverflow.com/a/20972210/511438 . Bây giờ tôi có thể phát triển với nhiều phản hồi hơn vì các lỗi dev của tôi xuất hiện dưới dạng một hộp ở đầu trang (như tôi đã tạo ra nó).
Valama

Sẽ không tốt hơn nếu đóng gói triển khai trình khởi động của bạn trong một khối thử bắt (bỏ qua), ngăn chặn trình khởi động của bạn () ném nhiều lỗi hơn nữa? (không phải là trường hợp ở đây, nhưng chỉ để chắc chắn)
Pieter De Bie

1
@ super1ha1 Bạn có thể cung cấp một jsfiddle không? Tôi không nghĩ các trình duyệt sẽ giới thiệu một sự thay đổi đột phá trong đó trình xử lý sự kiện onerror ngừng bắn các sự kiện. Bạn có thể dùng thử jsfiddle này để xem trình xử lý onerror hoạt động: jsfiddle.net/nzfvm44d Điều này vẫn hoạt động với tôi trong phiên bản Chrome 62.0.3202.94 (Bản dựng chính thức) (64-bit).
Sam

38

xử lý lỗi tinh vi

Nếu việc xử lý lỗi của bạn rất phức tạp và do đó có thể tự gây ra lỗi, sẽ rất hữu ích khi thêm cờ cho biết bạn đã ở trong "chế độ lỗi". Thích như vậy:

var appIsHandlingError = false;

window.onerror = function() {
    if (!appIsHandlingError) {
        appIsHandlingError = true;
        handleError();
    }
};

function handleError() {
    // graceful error handling
    // if successful: appIsHandlingError = false;
}

Nếu không, bạn có thể thấy mình trong một vòng lặp vô hạn.


29
Hoặc một cách không an toàn hơn sẽ là sử dụng handleErrorphương pháp thử bắt xung quanh phương pháp.
Aidiakapi

8
Bạn không thể sử dụng thử bắt nếu bạn có cuộc gọi không đồng bộ khi xử lý lỗi. Vì vậy, giải pháp của anh ấy vẫn là giải pháp tốt
Emrys Myrooin

@EmrysMyrooin Tuy nhiên, nó sẽ gây ra đồng bộ hóa hoặc vòng lặp không đồng bộ do xử lý lỗi và có thể bị sập nếu không có thông tin ngăn xếp có thể sử dụng.
inf3rno

1
Tôi nghĩ rằng cờ nên được thiết lập lại tại một số điểm, phải không? Tôi tin rằng chúng ta cần thử / bắt xung quanh nếu lớn nếu và đảm bảo cờ được đặt lại ngay trước khi rời khỏi phương thức onerror. Nếu không, chỉ có một lỗi sẽ được xử lý.
Seb

23

Hãy thử Atatus cung cấp tính năng Theo dõi lỗi nâng cao và Giám sát người dùng thực cho các ứng dụng web hiện đại.

https://www.atatus.com/

Hãy để tôi giải thích làm thế nào để có được stacktraces hoàn thành hợp lý trong tất cả các trình duyệt.

Xử lý lỗi trong JavaScript

Chrome và Opera hiện đại hỗ trợ đầy đủ thông số dự thảo HTML 5 cho ErrorEvent và window.onerror. Trong cả hai trình duyệt này, bạn có thể sử dụng window.onerrorhoặc liên kết đúng với sự kiện 'lỗi':

// Only Chrome & Opera pass the error object.
window.onerror = function (message, file, line, col, error) {
    console.log(message, "from", error.stack);
    // You can send data to your server
    // sendError(data);
};
// Only Chrome & Opera have an error attribute on the event.
window.addEventListener("error", function (e) {
    console.log(e.error.message, "from", e.error.stack);
    // You can send data to your server
    // sendError(data);
})

Thật không may, Firefox, Safari và IE vẫn còn và chúng tôi cũng phải hỗ trợ chúng. Vì stacktrace không có sẵn nên window.onerrorchúng tôi phải làm thêm một chút.

Nó chỉ ra rằng điều duy nhất chúng ta có thể làm để nhận stacktraces từ các lỗi là bọc tất cả các mã của chúng ta trong một try{ }catch(e){ }khối và sau đó xem xét e.stack. Chúng ta có thể làm cho quá trình dễ dàng hơn với một hàm gọi là hàm lấy hàm và trả về một hàm mới với khả năng xử lý lỗi tốt.

function wrap(func) {
    // Ensure we only wrap the function once.
    if (!func._wrapped) {
        func._wrapped = function () {
            try{
                func.apply(this, arguments);
            } catch(e) {
                console.log(e.message, "from", e.stack);
                // You can send data to your server
                // sendError(data);
                throw e;
            }
        }
    }
    return func._wrapped;
};

Những công việc này. Bất kỳ chức năng nào bạn bọc thủ công sẽ xử lý lỗi tốt, nhưng hóa ra chúng tôi thực sự có thể tự động làm điều đó cho bạn trong hầu hết các trường hợp.

Bằng cách thay đổi định nghĩa toàn cầu addEventListenerđể nó tự động kết thúc cuộc gọi lại, chúng ta có thể tự động chèn try{ }catch(e){ }xung quanh hầu hết mã. Điều này cho phép mã hiện có tiếp tục hoạt động, nhưng thêm theo dõi ngoại lệ chất lượng cao.

var addEventListener = window.EventTarget.prototype.addEventListener;
window.EventTarget.prototype.addEventListener = function (event, callback, bubble) {
    addEventListener.call(this, event, wrap(callback), bubble);
}

Chúng tôi cũng cần đảm bảo rằng removeEventListenertiếp tục làm việc. Tại thời điểm này, nó sẽ không vì đối số addEventListenerđược thay đổi. Một lần nữa chúng ta chỉ cần sửa lỗi này trên prototypeđối tượng:

var removeEventListener = window.EventTarget.prototype.removeEventListener;
window.EventTarget.prototype.removeEventListener = function (event, callback, bubble) {
    removeEventListener.call(this, event, callback._wrapped || callback, bubble);
}

Truyền dữ liệu lỗi đến phụ trợ của bạn

Bạn có thể gửi dữ liệu lỗi bằng thẻ hình ảnh như sau

function sendError(data) {
    var img = newImage(),
        src = 'http://yourserver.com/jserror&data=' + encodeURIComponent(JSON.stringify(data));

    img.crossOrigin = 'anonymous';
    img.onload = function success() {
        console.log('success', data);
    };
    img.onerror = img.onabort = function failure() {
        console.error('failure', data);
    };
    img.src = src;
}

Tuyên bố miễn trừ trách nhiệm: Tôi là nhà phát triển web tại https://www.atatus.com/ .


Yourerver.com/jserror là gì? REST, Dịch vụ web, Dịch vụ Wcf ? Bất kỳ đơn giản về phụ trợ?
Kiquenet

Nếu bạn muốn gửi lỗi js từ trình duyệt người dùng đến máy chủ của bạn. bạn phải viết phần phụ trợ ( http://yourserver.com) để nhận và lưu trữ. Nếu bạn chọn atatus.com , bạn không cần phải làm gì cả. Chỉ cần bao gồm hai dòng script trong trang của bạn.
Fizer Khan

18

Dường như window.onerrorkhông cung cấp quyền truy cập vào tất cả các lỗi có thể. Cụ thể nó bỏ qua:

  1. <img> tải lỗi (phản hồi> = 400).
  2. <script> tải lỗi (phản hồi> = 400).
  3. lỗi toàn cầu nếu bạn có nhiều thư viện khác trong ứng dụng của bạn cũng thao túng window.onerror theo cách không xác định (jquery, angular, v.v.).
  4. có lẽ nhiều trường hợp tôi chưa gặp phải sau khi khám phá điều này ngay bây giờ (iframe, stack stack, v.v.).

Đây là sự khởi đầu của một tập lệnh bắt được nhiều lỗi này, do đó bạn có thể thêm gỡ lỗi mạnh mẽ hơn cho ứng dụng của mình trong quá trình phát triển.

(function(){

/**
 * Capture error data for debugging in web console.
 */

var captures = [];

/**
 * Wait until `window.onload`, so any external scripts
 * you might load have a chance to set their own error handlers,
 * which we don't want to override.
 */

window.addEventListener('load', onload);

/**
 * Custom global function to standardize 
 * window.onerror so it works like you'd think.
 *
 * @see http://www.quirksmode.org/dom/events/error.html
 */

window.onanyerror = window.onanyerror || onanyerrorx;

/**
 * Hook up all error handlers after window loads.
 */

function onload() {
  handleGlobal();
  handleXMLHttp();
  handleImage();
  handleScript();
  handleEvents();
}

/**
 * Handle global window events.
 */

function handleGlobal() {
  var onerrorx = window.onerror;
  window.addEventListener('error', onerror);

  function onerror(msg, url, line, col, error) {
    window.onanyerror.apply(this, arguments);
    if (onerrorx) return onerrorx.apply(null, arguments);
  }
}

/**
 * Handle ajax request errors.
 */

function handleXMLHttp() {
  var sendx = XMLHttpRequest.prototype.send;
  window.XMLHttpRequest.prototype.send = function(){
    handleAsync(this);
    return sendx.apply(this, arguments);
  };
}

/**
 * Handle image errors.
 */

function handleImage() {
  var ImageOriginal = window.Image;
  window.Image = ImageOverride;

  /**
   * New `Image` constructor. Might cause some problems,
   * but not sure yet. This is at least a start, and works on chrome.
   */

  function ImageOverride() {
    var img = new ImageOriginal;
    onnext(function(){ handleAsync(img); });
    return img;
  }
}

/**
 * Handle script errors.
 */

function handleScript() {
  var HTMLScriptElementOriginal = window.HTMLScriptElement;
  window.HTMLScriptElement = HTMLScriptElementOverride;

  /**
   * New `HTMLScriptElement` constructor.
   *
   * Allows us to globally override onload.
   * Not ideal to override stuff, but it helps with debugging.
   */

  function HTMLScriptElementOverride() {
    var script = new HTMLScriptElement;
    onnext(function(){ handleAsync(script); });
    return script;
  }
}

/**
 * Handle errors in events.
 *
 * @see http://stackoverflow.com/questions/951791/javascript-global-error-handling/31750604#31750604
 */

function handleEvents() {
  var addEventListenerx = window.EventTarget.prototype.addEventListener;
  window.EventTarget.prototype.addEventListener = addEventListener;
  var removeEventListenerx = window.EventTarget.prototype.removeEventListener;
  window.EventTarget.prototype.removeEventListener = removeEventListener;

  function addEventListener(event, handler, bubble) {
    var handlerx = wrap(handler);
    return addEventListenerx.call(this, event, handlerx, bubble);
  }

  function removeEventListener(event, handler, bubble) {
    handler = handler._witherror || handler;
    removeEventListenerx.call(this, event, handler, bubble);
  }

  function wrap(fn) {
    fn._witherror = witherror;

    function witherror() {
      try {
        fn.apply(this, arguments);
      } catch(e) {
        window.onanyerror.apply(this, e);
        throw e;
      }
    }
    return fn;
  }
}

/**
 * Handle image/ajax request errors generically.
 */

function handleAsync(obj) {
  var onerrorx = obj.onerror;
  obj.onerror = onerror;
  var onabortx = obj.onabort;
  obj.onabort = onabort;
  var onloadx = obj.onload;
  obj.onload = onload;

  /**
   * Handle `onerror`.
   */

  function onerror(error) {
    window.onanyerror.call(this, error);
    if (onerrorx) return onerrorx.apply(this, arguments);
  };

  /**
   * Handle `onabort`.
   */

  function onabort(error) {
    window.onanyerror.call(this, error);
    if (onabortx) return onabortx.apply(this, arguments);
  };

  /**
   * Handle `onload`.
   *
   * For images, you can get a 403 response error,
   * but this isn't triggered as a global on error.
   * This sort of standardizes it.
   *
   * "there is no way to get the HTTP status from a 
   * request made by an img tag in JavaScript."
   * @see http://stackoverflow.com/questions/8108636/how-to-get-http-status-code-of-img-tags/8108646#8108646
   */

  function onload(request) {
    if (request.status && request.status >= 400) {
      window.onanyerror.call(this, request);
    }
    if (onloadx) return onloadx.apply(this, arguments);
  }
}

/**
 * Generic error handler.
 *
 * This shows the basic implementation, 
 * which you could override in your app.
 */

function onanyerrorx(entity) {
  var display = entity;

  // ajax request
  if (entity instanceof XMLHttpRequest) {
    // 400: http://example.com/image.png
    display = entity.status + ' ' + entity.responseURL;
  } else if (entity instanceof Event) {
    // global window events, or image events
    var target = entity.currentTarget;
    display = target;
  } else {
    // not sure if there are others
  }

  capture(entity);
  console.log('[onanyerror]', display, entity);
}

/**
 * Capture stuff for debugging purposes.
 *
 * Keep them in memory so you can reference them
 * in the chrome debugger as `onanyerror0` up to `onanyerror99`.
 */

function capture(entity) {
  captures.push(entity);
  if (captures.length > 100) captures.unshift();

  // keep the last ones around
  var i = captures.length;
  while (--i) {
    var x = captures[i];
    window['onanyerror' + i] = x;
  }
}

/**
 * Wait til next code execution cycle as fast as possible.
 */

function onnext(fn) {
  setTimeout(fn, 0);
}

})();

Nó có thể được sử dụng như thế này:

window.onanyerror = function(entity){
  console.log('some error', entity);
};

Tập lệnh đầy đủ có cài đặt mặc định cố gắng in ra một phiên bản "hiển thị" có thể đọc được của thực thể / lỗi mà nó nhận được. Có thể được sử dụng cho cảm hứng cho một trình xử lý lỗi dành riêng cho ứng dụng. Việc triển khai mặc định cũng giữ một tham chiếu đến 100 thực thể lỗi cuối cùng, vì vậy bạn có thể kiểm tra chúng trong bảng điều khiển web sau khi chúng xảy ra như:

window.onanyerror0
window.onanyerror1
...
window.onanyerror99

Lưu ý: Điều này hoạt động bằng cách ghi đè các phương thức trên một số trình xây dựng / trình tạo riêng. Điều này có thể có tác dụng phụ ngoài ý muốn. Tuy nhiên, nó rất hữu ích khi sử dụng trong quá trình phát triển, để tìm ra lỗi xảy ra ở đâu, gửi nhật ký đến các dịch vụ như NewRelic hoặc Sentry trong quá trình phát triển để chúng tôi có thể đo lường lỗi trong quá trình phát triển và để dàn dựng để chúng tôi có thể gỡ lỗi những gì đang diễn ra một mức độ sâu sắc hơn. Sau đó nó có thể được tắt trong sản xuất.

Hi vọng điêu nay co ich.


1
Hình ảnh rõ ràng kích hoạt sự kiện lỗi: stackoverflow.com/a/18152753/607033
inf3rno

6
// display error messages for a page, but never more than 3 errors
window.onerror = function(msg, url, line) {
if (onerror.num++ < onerror.max) {
alert("ERROR: " + msg + "\n" + url + ":" + line);
return true;
}
}
onerror.max = 3;
onerror.num = 0;

6

Người ta cũng nên duy trì cuộc gọi lại onerror liên quan trước đó

<script type="text/javascript">

(function() {
    var errorCallback = window.onerror;
    window.onerror = function () {
        // handle error condition
        errorCallback && errorCallback.apply(this, arguments);
    };
})();

</script>

3

Nếu bạn muốn cách thống nhất để xử lý cả lỗi chưa được xử lý và từ chối lời hứa chưa được xử lý, bạn có thể xem thư viện chưa bị bắt .

BIÊN TẬP

<script type="text/javascript" src=".../uncaught/lib/index.js"></script>

<script type="text/javascript">
    uncaught.start();
    uncaught.addListener(function (error) {
        console.log('Uncaught error or rejection: ', error.message);
    });
</script>

Nó nghe cửa sổ. unandledrejection ngoài window.onerror.


2
Lance Pollard trong câu trả lời ở trên đã đề cập đến một vài lỗi, mà onerror bình thường không xử lý được. Bạn có thư viện xử lý chúng? Nếu một số trong số họ, xin vui lòng chỉ định đó?
Michael Freidgeim

@ MiFreidgeimSO-stopbeingevil Chỉ cần kiểm tra mã nguồn - không may là nó không. Aleksandr Oleynikov: Sẽ thật tuyệt vời nếu bạn có thể hỗ trợ điều này - Sau đó tôi sẽ chuyển downvote của mình sang upvote ;-)
sáng chói

2

Tôi khuyên bạn nên thử Trackjs .

Đó là lỗi đăng nhập như một dịch vụ.

Thật đơn giản để thiết lập. Chỉ cần thêm một dòng <script> vào mỗi trang và đó là dòng đó. Điều này cũng có nghĩa là sẽ rất đơn giản để xóa nếu bạn quyết định không thích nó.

Có những dịch vụ khác như Sentry (là nguồn mở nếu bạn có thể lưu trữ máy chủ của riêng mình), nhưng nó không làm được những gì Trackjs làm. Trackjs ghi lại sự tương tác của người dùng giữa trình duyệt của họ và máy chủ web của bạn để bạn thực sự có thể theo dõi các bước của người dùng dẫn đến lỗi, trái ngược với chỉ một tham chiếu số tệp và dòng (và có thể ngăn xếp dấu vết).


2
TrackJS dường như không còn cấp miễn phí nữa, mặc dù có bản dùng thử miễn phí.
pkaeding

Điều này tốt cho việc theo dõi và được cảnh báo lỗi nhưng không thực sự giải quyết được phần xử lý. Lý tưởng nhất là tôi nghĩ người hỏi đang tìm cách xử lý các lỗi này để phần còn lại của mã vẫn chạy.
wgp

Điều gì xảy ra nếu bạn bắt sự kiện lỗi và thêm một cuộc gọi xhr vào bộ ghi với dấu vết ngăn xếp và trạng thái ứng dụng? Trackjs tốt hơn như thế nào?
inf3rno

2
@ inf3rno nó không chỉ là dấu vết ngăn xếp, mà chỉ theo dõi sự khởi đầu của lỗi. TrackJS sẽ theo dõi toàn bộ phiên người dùng để bạn có thể thấy những gì đã dẫn đến nó. đây là một ảnh chụp màn hình làm ví dụ trackjs.com/assets/images/sc Muff.png
kane

1

Bạn lắng nghe sự kiện onerror bằng cách gán một hàm cho window.onerror:

 window.onerror = function (msg, url, lineNo, columnNo, error) {
        var string = msg.toLowerCase();
        var substring = "script error";
        if (string.indexOf(substring) > -1){
            alert('Script Error: See Browser Console for Detail');
        } else {
            alert(msg, url, lineNo, columnNo, error);
        }   
      return false; 
  };
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.